RNTFS  =&ҝ3м| 3Sh hjˊ$sf@fр?Afff ôAU$rUu tf`fff; :fjfPSfh> >aB$fX[fXfX-f3ffŠff6֊$ ̸ fofaà <t  A disk read error occurred NTLDR is missing NTLDR is compressed Press Ctrl+Alt+Del to restart UNTLDR$I300Ȏf f ffNf@fffNffRf f3ffVqfJf"fRf&fRf*fRf:fRfBff"_ f Wf.ff&F f2ff*4 f6f.f $gxgfPgBgfH fbgfHf^f^f f3ffffBf^fFf>2f>6f6f>Ff*fff  gff>:1f:f fff#f:ffff DW f3ff:f @ f>:f:fffrf  gfX f fh f+f:$6 +h Pf`ff ffff߃fPfag@gf8Lgf93f  gx #g:H fgp fQffYgfxgf@f+flgfg@ 4gfPg:J@gfrBIfQffYgxg@f3g{f`gfSgf fgrfafPgfSf gfJfAgfBf3f6Nff+f^f`g{ffafSfPfQfVfWff_f^fYf4f;ff+fff ff fffXff[f+fQfWgfC f ff+ffTfVgfs fff^fPfPgffPgfCfPgfV f ffqffZfYfBfQfV?ff^fYfNfffYfZfQfVfff^fYff_fYffXf[ff`&gf_&gfOf affffIf &g&gfffIfaf`ffffRfZfRfJf0f ffJfffVffJf7fQff fff ff>f3ff?_fSfGf&VfPf3f ffRf afVf ffZffJff f+f;fff+fZuffPf3f ffQfYf f ffJfff;f f;ff륃fJff f;ff낃ffJf[_f? fafff6Zf6RfRfQfRfZf>Vffff+& f>ZfZffff ffXfV,f^f f[f[fYfZf3f`fPfQf3f ffRfWSf_f f ffZfffYf f;f+fXffPfQfXffPffQfW߃fPf_f>NfYfXfpfaf`f&VfVUfaf`f&bf2fbf6&f>BfafPfSfQfFffffffgfYf[fXg{f+gfsgfVf; gff;f+g^f+g;>f9ff;!ffPgf ffffffCfXf+f+ff+f+g ff+fSfRfgffIfKf fgfKfIffZf[fSfRf+gff+g ff+fZf[ffgffIfKf fgfKfIffZf[f fQfVg>a g>zg. ff^fYfPfQff.gfXgCgf@fDf fYfYf2f fYfYf3f2gfRgfBf3f6^f3fPfVfXf^f;:fVf@fPfHrfZf^fYf[fSfQfVfRfBgf@f tfYfYfYfYfYfYf3fQfPffff ff3f[fYfff f3f3fffPfS#f[f_f 5ff ffRfQf fff cff>f3YffYfZ&f9 &f9W1&f?/&&&fG؋%tˌ&fGfYfZf3à * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Handles the structure of the cache. * * Copyright 2010 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. * * @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 */ interface Horde_Kolab_FreeBusy_Cache_Structure { /** * Return a handler for a partial based on a folder and an owner. * * @param Horde_Kolab_FreeBusy_Params_Freebusy_Folder $folder The folder being accessed. * @param Horde_Kolab_FreeBusy_Owner $owner Owner of the folder. * * @return Horde_FreeBusy_Cache_Partial A handler for cached partials. */ public function getPartialByOwnerAndFolder( Horde_Kolab_FreeBusy_Params_Freebusy_Folder $folder, Horde_Kolab_FreeBusy_Owner $owner ); /** * Return a handler for a partial based on an ID. * * @param string $id The ID. * * @return Horde_FreeBusy_Cache_Partial A handler for cached partials. */ public function getPartialById($id); /** * Return the ACL handler. * * @return Horde_Kolab_FreeBusy_Cache_Acl The ACL handler. */ public function getAcl( Horde_Kolab_FreeBusy_Cache_Structure $self = null ); /** * Return the extended ACL handler. * * @return Horde_Kolab_FreeBusy_Cache_Xacl The extended ACL handler. */ public function getExtendedAcl( Horde_Kolab_FreeBusy_Cache_Structure $self = null ); /** * Return the partials handler. * * @param Horde_Kolab_FreeBusy_Owner $owner The owner of the data being accessed. * @param Horde_Kolab_FreeBusy_User $user The user accessing the cache. * * @return Horde_Kolab_FreeBusy_Cache_Freebusy_Partials The representation of the cached data. */ public function getCombined( Horde_Kolab_FreeBusy_Owner $owner, Horde_Kolab_FreeBusy_User $user, Horde_Kolab_FreeBusy_Cache_Structure $self = null ); /** * Return the DB based cache for ACL. * * @return Horde_Kolab_FreeBusy_Cache_Db_Acl The cache. */ public function getAclDbCache(); /** * Return the DB based cache for extended ACL. * * @return Horde_Kolab_FreeBusy_Cache_Db_Xacl The cache. */ public function getXaclDbCache(); /** * Return the file based cache for ACL. * * @param Horde_Kolab_FreeBusy_Cache_Partial $partial The partial represented * by the cache. * * @return Horde_Kolab_FreeBusy_Cache_File_Acl The cache. */ public function getAclFileCache( Horde_Kolab_FreeBusy_Cache_Partial $partial ); /** * Return the file based cache for extended ACL. * * @param Horde_Kolab_FreeBusy_Cache_Partial $partial The partial represented * by the cache. * * @return Horde_Kolab_FreeBusy_Cache_File_Xacl The cache. */ public function getXaclFileCache( Horde_Kolab_FreeBusy_Cache_Partial $partial ); /** * Return the path to the cache directory. * * @return string The path to the cache directory. */ public function getCacheDir(); } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Extended free/busy access control based on cached ACL information. * * Copyright 2008-2010 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. * * @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_Cache_Xacl_Cache extends Horde_Kolab_FreeBusy_Cache_Xacl_Base { /** * Is extended access to the given partial allowed? * * @param Horde_Kolab_FreeBusy_User $user The user accessing the system. * @param Horde_Kolab_FreeBusy_Cache_Partial $partial Partial to forget. * * @return boolean True if extended access is allowed, false otherwise. */ public function allow( Horde_Kolab_FreeBusy_User $user, Horde_Kolab_FreeBusy_Cache_Partial $partial ) { try { $groups = $user->getGroupAddresses(); } catch (Horde_Kolab_FreeBusy_Exception $e) { /** * It is kind of unlikely that we hit an error here (if retrieving * the groups is problematic then retrieving the user_object in the * first place is likely to have been problematic too. If the * unthinkable happens though I consider the assumption that the * user has no groups a safe alternative as this will only hide * information and not disclose anything that might be problematic. */ $groups = array(); } $groups[] = $user->getPrimaryId(); foreach ($groups as $id) { if ($this->_structure ->getXaclDbCache() ->has($partial->getId(), $id)) { return true; } } return false; } /** * Purge the extended ACL information for a partial. * * @param Horde_Kolab_FreeBusy_Cache_Partial $partial Partial to forget. * * @return NULL */ public function delete(Horde_Kolab_FreeBusy_Cache_Partial $partial) { $filecache = $this->_structure ->getXaclFileCache($partial); $this->_structure ->getXaclDbCache() ->store( $partial->getId(), '', $this->_getOldXacl($filecache) ); $filecache->delete(); } /** * Store the extended ACL information for a partial. * * @param Horde_Kolab_FreeBusy_Cache_Partial $partial Partial to store. * @param Horde_Kolab_FreeBusy_Resource $resource Resource handler providing * the extended ACL information. * @oaram array $acl The ACL for the partial. * * @return NULL */ public function store( Horde_Kolab_FreeBusy_Cache_Partial $partial, Horde_Kolab_FreeBusy_Resource $resource, array $acl ) { $filecache = $this->_structure ->getXaclFileCache($partial); $xacl = $resource->getAttributeAcl(); /* 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; } } } $this->_structure ->getXaclDbCache() ->store( $partial->getId(), $xacl, $this->_getOldXacl($filecache) ); $filecache->store($xacl); } /** * Retrieve the old extended ACL settings for this partial. * * @param Horde_Kolab_FreeBusy_Cache_File_Xacl $filecache The cached data. * * @return string The old extended ACL settings. */ private function _getOldXacl($filecache) { try { return $filecache->load(); } catch (Horde_Kolab_FreeBusy_Exception $e) { return ''; } } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Configuration based extended free/busy access control for free/busy exports. * * Copyright 2008-2010 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. * * @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_Cache_Xacl_Configuration extends Horde_Kolab_FreeBusy_Cache_Xacl_Base { /** * Is access to extended information allowed? * * @var boolean */ private $_allow; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Cache_Structure $structure Cache structure. * @param boolean $allow Allow access to * extended information * or not? */ public function __construct( Horde_Kolab_FreeBusy_Cache_Structure $structure, $allow ) { $this->_allow = $allow; parent::__construct($structure); } /** * Is extended access to the given partial allowed? * * @param Horde_Kolab_FreeBusy_User $user The user accessing the system. * @param Horde_Kolab_FreeBusy_Cache_Partial $partial Partial to forget. * * @return boolean True if extended access is allowed, false otherwise. */ public function allow( Horde_Kolab_FreeBusy_User $user, Horde_Kolab_FreeBusy_Cache_Partial $partial ) { return $this->_allow; } /** * Purge the extended ACL information for a partial. * * @param Horde_Kolab_FreeBusy_Cache_Partial $partial Partial to forget. * * @return NULL */ public function delete(Horde_Kolab_FreeBusy_Cache_Partial $partial) { } /** * Store the extended ACL information for a partial. * * @param Horde_Kolab_FreeBusy_Cache_Partial $partial Partial to store. * @param Horde_Kolab_FreeBusy_Resource $resource Resource handler providing * the extended ACL information. * @oaram array $acl The ACL for the partial. * * @return NULL */ public function store( Horde_Kolab_FreeBusy_Cache_Partial $partial, Horde_Kolab_FreeBusy_Resource $resource, array $acl ) { } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Logs extended free/busy access control. * * Copyright 2008-2010 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. * * @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_Cache_Xacl_Decorator_Log implements Horde_Kolab_FreeBusy_Cache_Xacl { /** * The decorated extended ACL handler. * * @var Horde_Kolab_FreeBusy_Cache_Xacl */ private $_xacl; /** * The logger. * * @var Horde_Kolab_FreeBusy_Logger */ private $_logger; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Cache_Xacl $xacl The decorated instance. * @param Horde_Kolab_FreeBusy_Logger $logger The logger. */ public function __construct( Horde_Kolab_FreeBusy_Cache_Xacl $xacl, Horde_Kolab_FreeBusy_Logger $logger ) { $this->_xacl = $xacl; $this->_logger = $logger; } /** * Is extended access to the given partial allowed? * * @param Horde_Kolab_FreeBusy_User $user The user accessing the system. * @param Horde_Kolab_FreeBusy_Cache_Partial $partial Partial to forget. * * @return boolean True if extended access is allowed, false otherwise. */ public function allow( Horde_Kolab_FreeBusy_User $user, Horde_Kolab_FreeBusy_Cache_Partial $partial ) { $result = $this->_xacl->allow($user, $partial); $this->_logger->debug( sprintf( "Extended attributes on file %s %s for user \"%s\".", $partial->getId(), $result ? 'allowed' : 'disallowed', $user->getPrimaryId() ) ); return $result; } /** * Purge the extended ACL information for a partial. * * @param Horde_Kolab_FreeBusy_Cache_Partial $partial Partial to forget. * * @return NULL */ public function delete(Horde_Kolab_FreeBusy_Cache_Partial $partial) { $result = $this->_xacl->delete($partial); $this->_logger->debug( sprintf( "Deleted extended attribute cache for file %s.", $partial->getId() ) ); return $result; } /** * Store the extended ACL information for a partial. * * @param Horde_Kolab_FreeBusy_Cache_Partial $partial Partial to store. * @param Horde_Kolab_FreeBusy_Resource $resource Resource handler providing * the extended ACL information. * @oaram array $acl The ACL for the partial. * * @return NULL */ public function store( Horde_Kolab_FreeBusy_Cache_Partial $partial, Horde_Kolab_FreeBusy_Resource $resource, array $acl ) { //@todo: Log the stored values on the side of the cache. $result = $this->_xacl->store($partial, $resource, $acl); $this->_logger->debug( sprintf( "Stored extended attributes cache for file %s.", $partial->getId() ) ); return $result; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Extended free/busy access control for free/busy exports. * * Copyright 2008-2010 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. * * @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 */ interface Horde_Kolab_FreeBusy_Cache_Xacl { /** * Is extended access to the given partial allowed? * * @param Horde_Kolab_FreeBusy_User $user The user accessing the system. * @param Horde_Kolab_FreeBusy_Cache_Partial $partial Partial to forget. * * @return boolean True if extended access is allowed, false otherwise. */ public function allow( Horde_Kolab_FreeBusy_User $user, Horde_Kolab_FreeBusy_Cache_Partial $partial ); /** * Purge the extended ACL information for a partial. * * @param Horde_Kolab_FreeBusy_Cache_Partial $partial Partial to forget. * * @return NULL */ public function delete(Horde_Kolab_FreeBusy_Cache_Partial $partial); /** * Store the extended ACL information for a partial. * * @param Horde_Kolab_FreeBusy_Cache_Partial $partial Partial to store. * @param Horde_Kolab_FreeBusy_Resource $resource Resource handler providing * the extended ACL information. * @oaram array $acl The ACL for the partial. * * @return NULL */ public function store( Horde_Kolab_FreeBusy_Cache_Partial $partial, Horde_Kolab_FreeBusy_Resource $resource, array $acl ); } * @author Steffen Hansen * @package Kolab_FreeBusy */ class Horde_Kolab_FreeBusy_Cache { /** * The handler for the cache structure. * * @var Horde_Kolab_FreeBusy_Cache_Structure */ private $_structure; /** * The owner of the data being accessed. * * @var Horde_Kolab_FreeBusy_Owner */ private $_owner; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Cache_Structure $structure The cache structure. * @param Horde_Kolab_FreeBusy_Owner $owner The cache owner. */ public function __construct( Horde_Kolab_FreeBusy_Cache_Structure $structure, Horde_Kolab_FreeBusy_Owner $owner ) { $this->_structure = $structure; $this->_owner = $owner; } /** * Delete the cache information for a calendar. * * @param Horde_Kolab_FreeBusy_Params_Freebusy_Folder $folder The folder to delete. * * @return NULL */ public function deletePartial( Horde_Kolab_FreeBusy_Params_Freebusy_Folder $folder ) { $partial = $this->_structure ->getPartialByOwnerAndFolder($folder, $this->_owner); $this->_structure ->getExtendedAcl() ->delete($partial); $this->_structure ->getAcl() ->delete($partial); $partial->delete(); } /** * Update the cache information for a resource. * * @param Horde_Kolab_FreeBusy_User $user The user accessing the cache. * @param Horde_Kolab_FreeBusy_Params_Freebusy_Folder $folder The folder being accessed. * @param Horde_Kolab_FreeBusy_Resource $resource The resource. * @param mixed $data The data to store. * * @return NULL */ public function storePartial( Horde_Kolab_FreeBusy_User $user, Horde_Kolab_FreeBusy_Params_Freebusy_Folder $folder, Horde_Kolab_FreeBusy_Resource $resource, $data ) { $partial = $this->_structure ->getPartialByOwnerAndFolder($folder, $this->_owner); $partial->store($data); $this->_structure ->getExtendedAcl() ->store( $partial, $resource, $this->_structure ->getAcl() ->store($user, $partial, $resource) ); } /** * Load partial free/busy data. * * @param Horde_Kolab_FreeBusy_User $user The user accessing the cache. * @param Horde_Kolab_FreeBusy_Params_Freebusy_Folder $folder The folder being accessed. * @param boolean $extended Should the data hold the extended * free/busy information? * * @return Horde_iCalendar The free/busy data of a single calendar. */ public function loadPartial( Horde_Kolab_FreeBusy_User $user, Horde_Kolab_FreeBusy_Params_Freebusy_Folder $folder, $extended ) { $partial = $this->_structure ->getPartialByOwnerAndFolder($folder, $this->_owner); if ($extended && $this->_structure ->getExtendedAcl() ->allow($user, $partial)) { return $partial->load(); } else { return $partial->loadSimple(); } } /** * Load the complete free/busy data of a user. * * @param Horde_Kolab_FreeBusy_User $user The user accessing the cache. * @param boolean $extended Should the data hold the extended * free/busy information? * * @return Horde_iCalendar The free/busy data for a user. */ function loadCombined( Horde_Kolab_FreeBusy_User $user, $extended ) { require_once 'Horde/Kolab/FreeBusy/Export/Freebusy/Combined.php'; require_once 'Horde/Kolab/FreeBusy/Export/Freebusy/Combined/Decorator/Cache.php'; $combined = new Horde_Kolab_FreeBusy_Export_Freebusy_Combined_Decorator_Cache( new Horde_Kolab_FreeBusy_Export_Freebusy_Combined( $this->_owner, $this->_structure->getCombined( $this->_owner, $user ) ), $this->_owner, $user, $this->_structure->getCacheDir() ); return $combined->generate($extended); } /** * Delete the cache information for the current owner. * * @return NULL */ public function deleteOwner() { $partial = $this->_structure ->deleteOwner($this->_owner); } } * @author Steffen Hansen * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Free/busy access control based on cached ACL information. * * Copyright 2004-2010 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. * * @category Kolab * @package Kolab_FreeBusy * @author Gunnar Wrobel * @author Steffen Hansen * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ class Horde_Kolab_FreeBusy_Export_Freebusy_Acl_Cache implements Horde_Kolab_FreeBusy_Export_Freebusy_Acl { /** * Free/Busy access control object. * * @var Horde_Kolab_FreeBusy_Access */ private $_access; /** * The db based cache for free/busy ACLs * * @var Horde_Kolab_FreeBusy_Cache_DB_acl */ private $_dbcache; /** * The file based cache for free/busy ACLs * * @var Horde_Kolab_FreeBusy_Cache_File_acl */ private $_filecache; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Access $access Free/Busy access control. * @param Horde_Kolab_FreeBusy_Cache_DB_acl $dbcache DB based ACL cache. * @param Horde_Kolab_FreeBusy_Cache_File_acl $filecache File based ACL cache. */ public function __construct( Horde_Kolab_FreeBusy_Access $access, Horde_Kolab_FreeBusy_Cache_DB_acl $dbcache, Horde_Kolab_FreeBusy_Cache_File_acl $filecache ) { $this->_access = $access; $this->_dbcache = $dbcache; $this->_filecache = $filecache; } /** * Which partials need to be combined into the final vCalendar information? * * @return array|PEAR_Error The list of files to be combined. */ public function getFiles() { return $this->_dbcache->get($this->_access->owner); } /** * Purge the ACL information. * * @param string $file Name of the cached ACL information. * * @return boolean|PEAR_Error True if purging worked. */ public function purge($file) { return $this->_filecache->purge($file); } public function store($file, $fb) { $relevance = $fb->getRelevance(); if (is_a($relevance, 'PEAR_Error')) { return $relevance; } $acl = $fb->getACL(); if (is_a($acl, 'PEAR_Error')) { return $acl; } /** * Only store the acl information by overwriting if the current user has * admin rights on the folder and can actually retrieve the full ACL * information. Otherwise the ACL should only be appended. */ $append = false; if (!isset($acl[$this->_access->user]) || (strpos($acl[$this->_access->user], 'a') === false)) { $append = true; } $result = $this->_filecache->storeACL($file, $acl, $relevance, $append); if (is_a($result, 'PEAR_Error')) { return $result; } return $acl; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Free/busy access control that ignores ACL information and simply collects all * partials belonging to one user. * * Copyright 2008-2010 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. * * @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_Export_Freebusy_Acl_Null implements Horde_Kolab_FreeBusy_Export_Freebusy_Acl { /** * Free/Busy access control object. * * @var Horde_Kolab_FreeBusy_Access */ private $_access; /** * Partial free/busy cache directory. * * @var string */ private $_cache_dir; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Access $access Free/Busy access control. * @param string $cache_dir Directory with cached * partial free/busy files. */ public function __construct( Horde_Kolab_FreeBusy_Access $access, $cache_dir ) { $this->_access = $access; $this->_cache_dir = $cache_dir; } /** * Which partials need to be combined into the final vCalendar information? * * @return array|PEAR_Error The list of files to be combined. */ public function getFiles() { $file_uid = str_replace("\0", '', str_replace(".", "^", $this->_access->owner)); $files = array(); $this->_findAllReaddir($file_uid, $files); return $files; } private function _findAllReaddir($uid, &$lst) { if ($dir = @opendir($this->_cache_dir . '/' . $uid)) { while (($file = readdir($dir)) !== false) { if ($file == '.' || $file == '..') continue; $full_path = $this->_cache_dir . '/' . $uid . '/' . $file; if (is_file($full_path) && preg_match('/(.*)\.x?pvc$/', $file, $matches)) $lst[] = $uid . '/' . $matches[1]; else if(is_dir($full_path)) $this->_findAllReaddir($uid . '/' . $file, $full_path, $lst); } closedir($dir); } } /** * Purge the ACL information. * * @param string $file Name of the cached ACL information. * * @return boolean|PEAR_Error True if purging worked. */ public function purge($file) { } public function store($file, $fb) { } } * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * The Kolab backend for the free/busy export. * * Copyright 2004-2010 Klarälvdalens Datakonsult AB * Copyright 2010 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If * you did not receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @category Kolab * @package Kolab_FreeBusy * @author Steffen Hansen * @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_Export_Freebusy_Backend_Kolab implements Horde_Kolab_FreeBusy_Export_Freebusy_Backend { public function getProductId() { return '-//kolab.org//NONSGML Kolab Server 2//EN'; } public function getUrl() { return ''; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * The Kolab backend for the free/busy export. * * Copyright 2010 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If * you did not receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @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 */ interface Horde_Kolab_FreeBusy_Export_Freebusy_Backend { public function getProductId(); public function getUrl(); } * @author Steffen Hansen * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Converts the data from the free/busy resource into a free/busy iCal object, * * Copyright 2004-2010 Klarälvdalens Datakonsult AB * Copyright 2008-2009 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If * you did not receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @category Kolab * @package Kolab_FreeBusy * @author Chuck Hagenbuch * @author Steffen Hansen * @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_Export_Freebusy_Base implements Horde_Kolab_FreeBusy_Export_Freebusy { /** * The resource to export. * * @var Horde_Kolab_FreeBusy_Resource */ private $_resource; /** * The backend definition. * * @var Horde_Kolab_FreeBusy_Export_Freebusy_Backend */ private $_backend; /** * The request. * * @var Horde_Controller_Request_Base */ private $_request; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Export_Freebusy_Backend $backend The export backend. * @param Horde_Kolab_FreeBusy_Resource $resource The resource to export. * @param Horde_Controller_Request_Base $request The incoming request. */ public function __construct( Horde_Kolab_FreeBusy_Export_Freebusy_Backend $backend, Horde_Kolab_FreeBusy_Resource $resource, //@todo: revert // Horde_Controller_Request_Base $request Horde_Kolab_FreeBusy_Request $request ) { $this->_resource = $resource; $this->_backend = $backend; $this->_request = $request; } private function _today() { return new Horde_Date( array( 'year' => date('Y'), 'month' => date('n'), 'mday' => date('j') ) ); } /** * Get the start timestamp for the export. * * @return Horde_Date The start timestamp for the export. */ public function getStart() { try { $past = $this->_resource->getOwner()->getFreeBusyPast(); } catch (Horde_Kolab_FreeBusy_Exception $e) { $past = 0; } $start = $this->_today(); $start->mday = $start->mday - $past; return $start; } /** * Get the end timestamp for the export. * * @return Horde_Date The end timestamp for the export. */ public function getEnd() { try { $future = $this->_resource->getOwner()->getFreeBusyFuture(); } catch (Horde_Kolab_FreeBusy_Exception $e) { } if (empty($future)) { global $conf; if (isset($conf['fb']['future_days'])) { $future = $conf['fb']['future_days']; } else { $future = 60; } } $end = $this->_today(); $end->mday = $end->mday + $future; return $end; } /** * Get the name of the resource. * * @return string The name of the resource. */ public function getResourceName() { return $this->_resource->getName(); } /** * Return the organizer mail for the export. * * @return string The organizer mail. */ public function getOrganizerMail() { return 'MAILTO:' . $this->_resource->getOwner()->getMail(); } /** * Return the organizer name for the export. * * @return string The organizer name. */ public function getOrganizerName() { $params = array(); $name = $this->_resource->getOwner()->getName(); if (!empty($name)) { $params['cn'] = $name; } return $params; } /** * Return the timestamp for the export. * * @return string The timestamp. */ public function getDateStamp() { return $this->_request->getServer('REQUEST_TIME'); } /** * Generates the free/busy export. * * @return Horde_iCalendar The iCal object. */ public function export() { /* Create the new iCalendar. */ $vCal = new Horde_iCalendar(); $vCal->setAttribute('PRODID', $this->_backend->getProductId()); $vCal->setAttribute('METHOD', 'PUBLISH'); /* Create the new vFreebusy component. */ $vFb = &Horde_iCalendar::newComponent('vfreebusy', $vCal); $vFb->setAttribute( 'ORGANIZER', $this->getOrganizerMail(), $this->getOrganizerName() ); $vFb->setAttribute('DTSTAMP', $this->getDateStamp()); $vFb->setAttribute('DTSTART', $this->getStart()->timestamp()); $vFb->setAttribute('DTEND', $this->getEnd()->timestamp()); $url = $this->_backend->getUrl(); if (!empty($url)) { $vFb->setAttribute('URL', $this->getUrl()); } /* Add all the busy periods. */ foreach ( $this->_resource->listEvents($this->getStart(), $this->getEnd()) as $event ) { foreach ( $event->getBusyTimes($this->getStart(), $this->getEnd()) as $busy ) { $vFb->addBusyPeriod( 'BUSY', $busy, null, $event->duration(), $event->getEncodedInformation() ); } } /* Remove the overlaps. */ $vFb->simplify(); /* Combine and return. */ $vCal->addComponent($vFb); return $vCal; } } * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * A cache for combined free/busy lists. * * Copyright 2004-2010 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. * * @category Kolab * @package Kolab_FreeBusy * @author Steffen Hansen * @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_Export_Freebusy_Combined_Decorator_Cache { /** * Free/Busy access control object. * * @var Horde_Kolab_FreeBusy_Export_Freebusy_Combined */ private $_combined; /** * The owner of the accessed data. * * @var Horde_Kolab_FreeBusy_Owner */ private $_owner; /** * The user accessing the system. * * @var Horde_Kolab_FreeBusy_User */ private $_user; /** * Partial free/busy cache directory. * * @var string */ private $_cache_dir; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Export_Freebusy_Combined $combined The handler joined free/busy data. * @param Horde_Kolab_FreeBusy_Access $access Free/Busy access control */ public function __construct( Horde_Kolab_FreeBusy_Export_Freebusy_Combined $combined, Horde_Kolab_FreeBusy_Owner $owner, Horde_Kolab_FreeBusy_User $user, $cache_dir ) { $this->_combined = $combined; $this->_owner = $owner; $this->_user = $user; $this->_cache_dir = $cache_dir; } public function generate($extended = false) { global $conf; if (preg_match('/(.*)@(.*)/', $this->_owner->getPrimaryId(), $regs)) { $owner = $regs[2] . '/' . $regs[1]; } if (preg_match('/(.*)@(.*)/', $this->_user->getPrimaryId(), $regs)) { $user = $regs[2] . '/' . $regs[1]; } $c_file = str_replace("\0", '', str_replace('.', '^', $user . '/' . $owner)); if (empty($conf['fb']['vcal_cache']['min_age'])) { $min_age = 300; } else { $min_age = $conf['fb']['vcal_cache']['min_age']; } if (empty($conf['fb']['vcal_cache']['max_age'])) { $max_age = 259200; } else { $max_age = $conf['fb']['vcal_cache']['max_age']; } require_once 'Horde/Kolab/FreeBusy/Cache/File.php'; require_once 'Horde/Kolab/FreeBusy/Cache/File/Vcal.php'; $c_vcal = new Horde_Kolab_FreeBusy_Cache_File_Vcal( $this->_cache_dir, $extended, $min_age, $max_age ); $c_vcal->setFilename($c_file); /* If the current vCal cache did not expire, we can deliver it */ if (!$c_vcal->expired($this->_combined, $extended)) { return $c_vcal->loadVcal(); } list($vCal, $mtimes) = $this->_combined->generate($extended); $c_vcal->storeVcal($vCal, $mtimes, $this->_combined->getSignature($extended)); return $vCal; } } * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** We require the iCalendar library to build the free/busy list */ require_once 'Horde/iCalendar.php'; require_once 'Horde/iCalendar/vfreebusy.php'; /** * Combines several partial free/busy lists into the free/busy list for a user. * * Copyright 2004-2010 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. * * @category Kolab * @package Kolab_FreeBusy * @author Steffen Hansen * @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_Export_Freebusy_Combined { /** * Owner of the data being accessed. * * @var Horde_Kolab_FreeBusy_Owner */ private $_owner; /** * The partial free/busy lists. * * @var Horde_Kolab_FreeBusy_Cache_Combined_Freebusy */ private $_combined; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Owner $owner Owner of the accessed data. * @param Horde_Kolab_FreeBusy_Cache_Freebusy_Partials $partials Partial free/busy information handler. */ public function __construct( Horde_Kolab_FreeBusy_Owner $owner, Horde_Kolab_FreeBusy_Cache_Combined_Freebusy $combined ) { $this->_owner = $owner; $this->_combined = $combined; } public function getSignature($extended) { return md5( join(':', $this->getOwnerCnParameter()) . '|' . join(':', $this->_combined->getPartialIds()) . '|' . join(':', $this->_combined->getExtendedAccess($extended)) ); } public function getOwnerCnParameter() { $cn_parameter = array(); $cn = $this->_owner->getName(); if (!empty($cn) && !is_a($cn, 'PEAR_Error')) { $cn_parameter['cn'] = $cn; }; return $cn_parameter; } public function getUrlAttribute() { if (isset($_SERVER['SERVER_NAME'])) { $host = $_SERVER['SERVER_NAME']; } else { $host = 'localhost'; } if (isset($_SERVER['REQUEST_URI'])) { $uri = $_SERVER['REQUEST_URI']; } else { $uri = '/'; } return 'http://' . $host . $uri; } public function hasRemoteServers() { return !empty($conf['fb']['remote_servers']); } public function generate($extended = false) { global $conf; // 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); $vFb->setAttribute( 'ORGANIZER', 'MAILTO:' . $this->_owner->getPrimaryId(), $this->getOwnerCnParameter() ); $vFb->setAttribute('DTSTAMP', time()); $vFb->setAttribute('URL', $this->getUrlAttribute()); $mtimes = $this->_combined->combineResult($vFb, $extended); if ($this->hasRemoteServers()) { $remote_vfb = $this->_fetchRemote($conf['fb']['remote_servers'], $this->_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); $result = array($vCal, $mtimes); return $result; } /** * 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. * * @todo Fixme and extract to class. Combine with the other "fetchRemote" */ 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; } }INDX( 9v(0fXHetǍ%tǍ%tǍ%tǍ%Acli`PetǍ%tǍ%tǍ%tǍ%BackendkhXeiitǍ%Pa`p Backend.phplhReiitǍ%|p  Base.phpmhRetǍ%tǍ%tǍ%tǍ%CombinedppZeiitǍ%jGq  Combined.phpqhTetǍ%tǍ%tǍ%tǍ% DecoratorqhRetǍ%tǍ%tǍ%tǍ%DECORA~1shXeiitǍ%T)ssq Fwthree.phpt`JetǍ%tǍ%tǍ%tǍ%Xacl * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Logs exporting free/busy data. * * Copyright 2009 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If * you did not receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @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_Export_Freebusy_Decorator_Log implements Horde_Kolab_FreeBusy_Export_Freebusy { /** * The decorated exporter. * * @var Horde_Kolab_FreeBusy_Export_Freebusy_Interface */ private $_export; /** * The logger. * * @var mixed */ private $_logger; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Export_Freebusy_Interface $export The decorated * export. * @param mixed $logger The log handler. The * class must at least * provide the debug() * method. */ public function __construct( Horde_Kolab_FreeBusy_Export_Freebusy_Base $export, $logger ) { $this->_export = $export; $this->_logger = $logger; } /** * Get the start timestamp for the export. * * @return Horde_Date The start timestamp for the export. */ public function getStart() { return $this->_export->getStart(); } /** * Get the end timestamp for the export. * * @return Horde_Date The end timestamp for the export. */ public function getEnd() { return $this->_export->getEnd(); } /** * Get the name of the resource. * * @return string The name of the resource. */ public function getResourceName() { return $this->_export->getResourceName(); } /** * Return the organizer mail for the export. * * @return string The organizer mail. */ public function getOrganizerMail() { return $this->_export->getOrganizerMail(); } /** * Return the organizer name for the export. * * @return string The organizer name. */ public function getOrganizerName() { return $this->_export->getOrganizerName(); } /** * Return the timestamp for the export. * * @return string The timestamp. */ public function getDateStamp() { return $this->_export->getDateStamp(); } /** * Generates the free/busy export. * * @return Horde_iCalendar The iCal object. */ public function export() { $this->_logger->debug( sprintf('Exporting free/busy data for resource %s from %s to %s', $this->_export->getResourceName(), $this->_export->getStart()->timestamp(), $this->_export->getEnd()->timestamp() ) ); return $this->_export->export(); } } * @author Steffen Hansen * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * A wrapper for the free/busy export that is specific to cope with * the situation in Horde framework 3. * * Copyright 2010 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. * * @category Kolab * @package Kolab_FreeBusy * @author Chuck Hagenbuch * @author Steffen Hansen * @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_Export_Freebusy_Fwthree extends Horde_Kolab_FreeBusy_Export_Freebusy_Base { /** * The timestamp for the generation of this export. * * @var string */ private $_date_stamp; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Export_Freebusy_Backend $backend The export backend. * @param Horde_Kolab_FreeBusy_Resource $resource The resource to export. * @param string $date_stamp The timestamp of the export. */ public function __construct( Horde_Kolab_FreeBusy_Export_Freebusy_Backend $backend, Horde_Kolab_FreeBusy_Resource $resource, $date_stamp ) { $this->_date_stamp = $date_stamp; parent::__construct($backend, $resource); } /** * Return the timestamp for the export. * * @return string The timestamp. */ public function getDateStamp() { return $this->_date_stamp; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Extended free/busy access control based on cached ACL information. * * Copyright 2008-2010 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. * * @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_Export_Freebusy_Xacl_Cache implements Horde_Kolab_FreeBusy_Export_Freebusy_Xacl { /** * Free/Busy access control object. * * @var Horde_Kolab_FreeBusy_Access */ private $_access; /** * The db based cache for extended free/busy ACLs * * @var Horde_Kolab_FreeBusy_Cache_DB_xacl */ private $_dbcache; /** * The file based cache for extended free/busy ACLs * * @var Horde_Kolab_FreeBusy_Cache_DB_xacl */ private $_filecache; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Access $access Free/Busy access control. * @param Horde_Kolab_FreeBusy_Cache_DB_xacl $dbcache Db based extended ACL cache. * @param Horde_Kolab_FreeBusy_Cache_File_xacl $filecache File based extended ACL cache. */ public function __construct( Horde_Kolab_FreeBusy_Access $access, Horde_Kolab_FreeBusy_Cache_DB_xacl $dbcache, Horde_Kolab_FreeBusy_Cache_File_xacl $filecache ) { $this->_access = $access; $this->_dbcache = $dbcache; $this->_filecache = $filecache; } /** * Is extended access to the given file allowed? * * @param string $file Name of the cached partial free/busy information. * * @return boolean|PEAR_Error True if extended access is allowed. */ public function allow($file) { $groups = array(); if (!empty($this->_access->user_object)) { /* Check if the calling user has access to the extended information of * the folder we are about to integrate into the free/busy data. */ $result = $this->_access->user_object->getGroupAddresses(); if (!is_a($result, 'PEAR_Error')) { /** * It is kind of unlikely that we hit an error here (if retrieving * the groups is problematic then retrieving the user_object in the * first place is likely to have been problematic too. If the * unthinkable happens though I consider the assumption that the * user has no groups a safe alternative as this will only hide * information and not disclose anything that might be problematic. */ $groups = $result; } } $groups[] = $this->_access->user; foreach ($groups as $id) { if ($this->_dbcache->has($file, $id) === true) { return true; } } return false; } /** * Return the ID of the user for whom extended free/busy access is being checked. * * @return string The user ID. */ public function getUserId() { if (!isset($this->_access->user_object)) { return 'anonymous'; } return $this->_access->user; } /** * Purge the extended ACL information. * * @param string $file Name of the cached extended ACL information. * * @return boolean|PEAR_Error True if purging worked. */ public function purge($file) { return $this->_filecache->purge($file); } public function store($file, $fb, $acl) { $xacl = $fb->getExtendedACL(); if (is_a($xacl, 'PEAR_Error')) { return $xacl; } return $this->_filecache->storeXACL($file, $xacl, $acl); } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Configuration based extended free/busy access control for free/busy exports. * * Copyright 2008-2010 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. * * @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_Export_Freebusy_Xacl_Configuration implements Horde_Kolab_FreeBusy_Export_Freebusy_Xacl { /** * Free/Busy access control object. * * @var Horde_Kolab_FreeBusy_Access */ private $_access; /** * Is access to extended information allowed? * * @var boolean */ private $_allow; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Access $access Free/Busy access control. * @param boolean $allow Allow access to extended * information or not? */ public function __construct( Horde_Kolab_FreeBusy_Access $access, $allow ) { $this->_access = $access; $this->_allow = $allow; } /** * Is extended access to the given file allowed? * * @param string $file Name of the cached partial free/busy information. * * @return boolean|PEAR_Error True if extended access is allowed. */ public function allow($file) { return $this->_allow; } /** * Return the ID of the user for whom extended free/busy access is being checked. * * @return string The user ID. */ public function getUserId() { if (!isset($this->_access->user_object)) { return 'anonymous'; } return $this->_access->user; } /** * Purge the extended ACL information. * * @param string $file Name of the cached extended ACL information. * * @return boolean|PEAR_Error True if purging worked. */ public function purge($file) { } public function store($file, $fb, $acl) { } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Logs extended free/busy access control. * * Copyright 2008-2010 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. * * @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_Export_Freebusy_Xacl_Decorator_Log implements Horde_Kolab_FreeBusy_Export_Freebusy_Xacl { /** * The decorated extended ACL handler. * * @var Horde_Kolab_FreeBusy_Export_Freebusy_Xacl */ private $_xacl; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Export_Freebusy_Xacl $xacl The decorated instance. */ public function __construct( Horde_Kolab_FreeBusy_Export_Freebusy_Xacl $xacl ) { $this->_xacl = $xacl; } /** * Is extended access to the given file allowed? * * @param string $file Name of the cached partial free/busy information. * * @return boolean True if extended access is allowed. */ public function allow($file) { $result = $this->_xacl->allow($file); Horde::logMessage( sprintf( "Extended attributes on file %s %s for user \"%s\".", $file, $result ? 'allowed' : 'disallowed', $this->_xacl->getUserId() ), __FILE__, __LINE__, PEAR_LOG_DEBUG ); return $result; } /** * Return the ID of the user for whom extended free/busy access is being checked. * * @return string The user ID. */ public function getUserId() { return $this->_xacl->getUserId(); } /** * Purge the extended ACL information. * * @param string $file Name of the cached extended ACL information. * * @return boolean|PEAR_Error True if purging worked. */ public function purge($file) { $result = $this->_xacl->purge($file); Horde::logMessage( sprintf( "Purged extended attribute cache for file %s.", $file ), __FILE__, __LINE__, PEAR_LOG_DEBUG ); return $result; } public function store($file, $fb, $acl) { //@todo: Log the stored values on the side of the cache. $result = $this->_xacl->store($file, $fb, $acl); Horde::logMessage( sprintf( "Stored extended attributes cache for file %s.", $file ), __FILE__, __LINE__, PEAR_LOG_DEBUG ); return $result; } } * @author Steffen Hansen * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Interface definition for the free/busy exporter. * * Copyright 2004-2010 Klarälvdalens Datakonsult AB * Copyright 2008-2009 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If * you did not receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @category Kolab * @package Kolab_FreeBusy * @author Chuck Hagenbuch * @author Steffen Hansen * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ interface Horde_Kolab_FreeBusy_Export_Freebusy { /** * Get the start timestamp for the export. * * @return Horde_Date The start timestamp for the export. */ public function getStart(); /** * Get the end timestamp for the export. * * @return Horde_Date The end timestamp for the export. */ public function getEnd(); /** * Get the name of the resource. * * @return string The name of the resource. */ public function getResourceName(); /** * Return the organizer mail for the export. * * @return string The organizer mail. */ public function getOrganizerMail(); /** * Return the organizer name for the export. * * @return string The organizer name. */ public function getOrganizerName(); /** * Return the timestamp for the export. * * @return string The timestamp. */ public function getDateStamp(); /** * Generates the free/busy export. * * @return Horde_iCalendar The iCal object. */ public function export(); } * @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)); } } function getFolder() { return $this->_folder; } /** * 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; } $perm->getPerm(); $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.9.2.5 2010-10-10 16:26:40 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 * @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_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); } } INDX( qv(<hV;ii%o8;7 Access.php=`L;%%%%CachebhT;iie%p  Cache.phpcp\;iitǍ%rA Exception.phpcpZ;iitǍ%rA EXCEPT~1.PHPd`N;tǍ%tǍ%tǍ%tǍ%ExportzhR;ii)%BtH{B Imap.php{hV;ii)%6Nw Logger.php|`N;)%)%)%)%Object~`L;)%)%)%)%OwnerhT;ii)%͌y  Owner.php`N;)%)%)%)%ParamshV;ii)%7{5 Report.phphX;ii)%7{d Request.phphR;)%)%)%)%ResourcepZ;ii% * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * A log wrapper for Horde framework 3. * * Copyright 2010 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. * * @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_Logger { public function error($message) { Horde::logMessage($message, $this->_getFile(), $this->_getLine(), PEAR_LOG_ERR); } public function debug($message) { Horde::logMessage($message, $this->_getFile(), $this->_getLine(), PEAR_LOG_DEBUG); } private function _getFile() { $backtrace = debug_backtrace(); if (isset($backtrace[2]['file'])) { return $backtrace[2]['file']; } return 'UNDEFINED'; } private function _getLine() { $backtrace = debug_backtrace(); if (isset($backtrace[2]['line'])) { return $backtrace[2]['line']; } return 'UNDEFINED'; } } * @package Kolab_FreeBusy */ class Horde_Kolab_FreeBusy_Object_Event { /** Event status - Taken from Kronolith */ const STATUS_NONE = 0; const STATUS_TENTATIVE = 1; const STATUS_CONFIRMED = 2; const STATUS_CANCELLED = 3; const STATUS_FREE = 4; /** * 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; public function __construct(array $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 = self::STATUS_FREE; break; case 'tentative': $this->status = self::STATUS_TENTATIVE; break; case 'busy': case 'outofoffice': default: $this->status = self::STATUS_CONFIRMED; } } else { $this->status = self::STATUS_CONFIRMED; } // Recurrence if (isset($event['recurrence'])) { $this->recurrence = new Horde_Date_Recurrence($this->start); $this->recurrence->fromHash($event['recurrence']); } } /** * Determines if the event recurs in the given time span. * * @param Horde_Date $startDate Start of the time span. * @param Horde_Date $endDate End of the time span. * * @return boolean True if the event recurs in this time span. */ public function recursIn(Horde_Date $startDate, Horde_Date $endDate) { $next = $this->recurrence->nextRecurrence($startDate); while ($next !== false && $this->recurrence->hasException($next->year, $next->month, $next->mday)) { $next->mday++; $next = $this->recurrence->nextRecurrence($next); } if ($next !== false) { $duration = $next->timestamp() - $this->start->timestamp(); $next_end = new Horde_Date($this->end->timestamp() + $duration); if ((!(($endDate->compareDateTime($next) < 0) || ($startDate->compareDateTime($next_end) > 0)))) { return true; } } return false; } /** * 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); } public function duration() { return $this->end->timestamp() - $this->start->timestamp(); } public function getEncodedInformation() { return array( 'X-UID' => base64_encode($this->eventID), 'X-SUMMARY' => base64_encode($this->private ? '' : $this->title), 'X-LOCATION' => base64_encode($this->private ? '' : $this->location) ); } public function isFree() { return ( $this->status == self::STATUS_FREE || $this->status == self::STATUS_CANCELLED ); } public function getBusyTimes(Horde_Date $startDate, Horde_Date $endDate) { if ($this->isFree()) { return array(); } if (!$this->recurs()) { return array($this->start->timestamp()); } else { $result = array(); $next = $this->recurrence->nextRecurrence($startDate); while ($next) { if ($endDate->compareDateTime($next) < 0) { break; } if (!$this->recurrence->hasException($next->year, $next->month, $next->mday)) { $result[] = $next->timestamp(); } $next->mday++; $next = $this->recurrence->nextRecurrence($next); } } } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * This class represents a Kolab calendar owner. * * Copyright 2010 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @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_Owner_Event_Kolab extends Horde_Kolab_FreeBusy_Owner_Kolab implements Horde_Kolab_FreeBusy_Owner_Event { } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * This basic interface for a calendar owner. * * Copyright 2010 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @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 */ interface Horde_Kolab_FreeBusy_Owner_Event extends Horde_Kolab_FreeBusy_Owner { /** * Indicates the correct free/busy server for the resource owner. * * @return string The server name. */ public function getFreeBusyServer(); /** * Return how many days into the past the free/busy data should be * calculated for this owner. * * @return int The number of days. */ public function getFreeBusyPast(); /** * Return how many days into the future the free/busy data should be * calculated for this owner. * * @return int The number of days. */ public function getFreeBusyFuture(); } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * This class represents a Kolab resource owner. * * Copyright 2010 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @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_Owner_Kolab extends Horde_Kolab_FreeBusy_UserDb_User_Kolab implements Horde_Kolab_FreeBusy_Owner { /** * The owner information. * * @var Horde_Kolab_FreeBusy_Params_Owner */ private $_owner; /** * The user accessing the system. * * @var Horde_Kolab_FreeBusy_User */ private $_user; /** * The connection to the user database. * * @var Horde_Kolab_FreeBusy_UserDb */ private $_userdb; /** * The owner data retrieved from the user database. * * @var array */ private $_owner_data; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Params_Owner $owner The resource owner. * @param Horde_Kolab_FreeBusy_UserDb $userdb The connection to the user database. * @param Horde_Kolab_FreeBusy_User $user The user accessing the system. */ public function __construct( Horde_Kolab_FreeBusy_Params_Owner $owner, Horde_Kolab_FreeBusy_UserDb $userdb, Horde_Kolab_FreeBusy_User $user ) { $this->_owner = $owner; $this->_user = $user; parent::__construct($userdb); } /** * Fetch the user data from the user db. * * @return NULL */ protected function fetchUserDbUser() { try { return $this->fetchOwner($this->_owner->getOwner()); } catch (Horde_Kolab_FreeBusy_Exception $e) { $domain = $this->_user->getDomain(); if (!empty($domain)) { return $this->fetchOwner( $this->_owner->getOwner() . '@' . $this->_user->getDomain() ); } else { throw $e; } } } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * This basic interface for a resource owner. * * Copyright 2010 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @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 */ interface Horde_Kolab_FreeBusy_Owner { /** * Return the primary id of the resource owner. * * @return string The primary id. */ public function getPrimaryId(); /** * Return the mail address of the resource owner. * * @return string The mail address. */ public function getMail(); /** * Return the name of the resource owner. * * @return string The name of the owner. */ public function getName(); } * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * This class provides the folder name requested from the free/busy system. * * Copyright 2004-2007 Klarälvdalens Datakonsult AB * Copyright 2009-2010 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @category Kolab * @package Kolab_FreeBusy * @author Steffen Hansen * @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_Params_Freebusy_Folder implements Horde_Kolab_FreeBusy_Params_Owner { /** * The request made to the application. * * @var Horde_Controller_Request_Base */ private $_request; /** * The owner of the folder. * * @var string */ private $_owner; /** * The extracted folder name. * * @var string */ private $_folder; /** * Constructor. * * @param Horde_Controller_Request_Base $request The incoming request. */ //@todo: reenable // public function __construct(Horde_Controller_Request_Base $request) public function __construct(Horde_Kolab_FreeBusy_Request $request) { $this->_request = $request; } /** * Extract the folder name from the request. * * @return string The requested folder. */ public function getFolder() { if ($this->_folder === null) { $this->_extractOwnerAndFolder(); } return $this->_folder; } /** * Extract the resource owner from the request. * * @return string The resource owner. */ public function getOwner() { if ($this->_owner === null) { $this->_extractOwnerAndFolder(); } return $this->_owner; } /** * Extract the owner and folder name from the request. * * @return NULL */ private function _extractOwnerAndFolder() { $folder = explode('/', $this->_getFolderParameter()); if (count($folder) < 2) { throw new Horde_Kolab_FreeBusy_Exception( sprintf( 'No such folder %s. A folder must have at least to components separated by "/".', $this->_getFolderParameter() ) ); } $folder[0] = strtolower($folder[0]); $this->_owner = $folder[0]; unset($folder[0]); $this->_folder = join('/', $folder); } /** * Return the raw folder name from the request. * * @return string The folder name. */ //@todo: private again protected function _getFolderParameter() { $parameters = $this->_request->getParameters(); return isset($parameters['folder']) ? $parameters['folder'] : ''; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * This class provides the owner id requested from the free/busy system. * * Copyright 2009-2010 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @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_Params_Freebusy_Owner implements Horde_Kolab_FreeBusy_Params_Owner { /** * The request made to the application. * * @var Horde_Controller_Request_Base */ private $_request; /** * The owner id. * * @var string */ private $_owner; /** * Constructor. * * @param Horde_Controller_Request_Base $request The incoming request. */ //@todo: reenable // public function __construct(Horde_Controller_Request_Base $request) public function __construct(Horde_Kolab_FreeBusy_Request $request) { $this->_request = $request; } /** * Extract the resource owner from the request. * * @return string The resource owner. */ public function getOwner() { if ($this->_owner === null) { $this->_owner = $this->_getOwnerParameter(); } return $this->_owner; } /** * Return the raw owner id from the request. * * @return string The owner id. */ private function _getOwnerParameter() { $parameters = $this->_request->getParameters(); return isset($parameters['uid']) ? $parameters['uid'] : ''; } } * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * This class provides the Kolab specific resource name requested from * the free/busy system. * * Copyright 2004-2007 Klarälvdalens Datakonsult AB * Copyright 2009-2010 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @category Kolab * @package Kolab_FreeBusy * @author Steffen Hansen * @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_Params_Freebusy_Resource_Kolab { /** * The current user. * * @var Horde_Kolab_FreeBusy_Params_User */ private $_user; /** * The requested folder. * * @var Horde_Kolab_FreeBusy_Params_Freebusy_Folder */ private $_folder; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Params_User $user The current user. * @param Horde_Kolab_FreeBusy_Params_Freebusy_Folder $folder The requested * folder. */ public function __construct( Horde_Kolab_FreeBusy_User $user, Horde_Kolab_FreeBusy_Params_Freebusy_Folder $folder ) { $this->_user = $user; $this->_folder = $folder; } /** * Extract the resource name from the request. * * @return string The requested resource. */ public function getResourceId() { list($user, $userdom) = $this->_splitMailAddress( $this->_user->getPrimaryId() ); list($owner, $ownerdom) = $this->_splitMailAddress( $this->_folder->getOwner() ); //@todo: This should be based on the namespaces. $fldrcomp = array(); if ($user == $owner) { $fldrcomp[] = 'INBOX'; } else { $fldrcomp[] = 'user'; $fldrcomp[] = $owner; } $fld = $this->_folder->getFolder(); if (!empty($fld)) { $fldrcomp[] = $fld; } $folder = join('/', $fldrcomp); if ($ownerdom && !$userdom) { $folder .= '@' . $ownerdom; } return $folder; } /** * Split a mail address at the '@' sign. * * @param string $address The address to split. * * @return array The two splitted parts. */ private function _splitMailAddress($address) { if (preg_match('/(.*)@(.*)/', $address, $regs)) { return array($regs[1], $regs[2]); } else { return array($address, false); } } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Defines a parameter delivering resource owner information. * * Copyright 2010 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @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 */ interface Horde_Kolab_FreeBusy_Params_Owner { /** * Extract the resource owner from the request. * * @return string The resource owner. */ public function getOwner(); } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * This class provides the credentials for the user currently accessing * the export system. * * Copyright 2007-2010 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @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_Params_User { /** * The request made to the application. * * @var Horde_Controller_Request_Base */ private $_request; /** * The user id. * * @var string */ private $_user; /** * The user password. * * @var string */ private $_pass; /** * Constructor. * * @param Horde_Controller_Request_Base $request The incoming request. */ //@todo:reenable //public function __construct(Horde_Controller_Request_Base $request) public function __construct(Horde_Kolab_FreeBusy_Request $request) { $this->_request = $request; } /** * Return the user credentials extracted from the request. * * @return array The user credentials. */ public function getCredentials() { if ($this->_user === null) { $this->_extractUserAndPassword(); } return array($this->_user, $this->_pass); } /** * Return the user id. * * @return array The user id. */ public function getId() { if ($this->_user === null) { $this->_extractUserAndPassword(); } return $this->_user; } /** * Extract user name and password from the request. * * @return NULL */ private function _extractUserAndPassword() { $this->_user = $this->_request->getServer('PHP_AUTH_USER'); $this->_pass = $this->_request->getServer('PHP_AUTH_PW'); //@todo: Fix! // 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)) { $remote_user = $this->_request->getServer('REDIRECT_REDIRECT_REMOTE_USER'); if (!empty($remote_user)) { $a = base64_decode(substr($remote_user, 6)); if ((strlen($a) != 0) && (strcasecmp($a, ':') == 0)) { list($this->_user, $this->_pass) = explode(':', $a, 2); } } else { $this->_user = ''; } } } } * @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; } } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * A dummy request provider for Horde 3. * * Copyright 2010 Kolab Systems AG * * 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. * * @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_Request { /** * Parameters. * * @var array */ private $_parameters; /** * Constructor. */ public function __construct() { $this->_parameters = array( 'folder' => Util::getFormData('folder', ''), 'uid' => Util::getFormData('uid', ''), ); } /** * Return the parameters. * * @return array The parameters. */ public function getParameters() { return $this->_parameters; } /** * Set a parameter. * * @param string $key The parameter key. * @param string $calue The parameter value. * * @return array The parameters. */ public function setParameter($key, $value) { $this->_parameters[$key] = $value; } /** * Return an element from the server parameters. * * @param string $key The server parameter to return. * * @return string The server parameter. */ public function getServer($key) { return isset($_SERVER[$key]) ? $_SERVER[$key] : ''; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Logs the resource access. * * Copyright 2009 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @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_Resource_Decorator_Log implements Horde_Kolab_FreeBusy_Resource { /** * The decorated resource. * * @var Horde_Kolab_FreeBusy_Resource_Interface */ private $_resource; /** * The logger. * * @var mixed */ private $_logger; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Resource_Interface $resource The decorated * resource. * @param mixed $logger The log handler. The * class must at least * provide the debug() * method. */ public function __construct( Horde_Kolab_FreeBusy_Resource $resource, $logger ) { $this->_resource = $resource; $this->_logger = $logger; } protected function getLogger() { return $this->_logger; } protected function getResource() { return $this->_resource; } /** * Connect to the resource. * * @return NULL * * @throws Horde_Kolab_FreeBusy_Exception If connecting to the resource * failed. */ public function connect() { $this->_resource->connect(); $this->logger->debug( sprintf( 'Successfully connected to resource %s', $this->_resource->getName() ) ); } /** * Return the name of the resource. * * @return string The name for the resource. */ public function getName() { return $this->_resource->getName(); } /** * Return the owner of the resource. * * @return Horde_Kolab_FreeBusy_Owner The resource owner. */ public function getOwner() { return $this->_resource->getOwner(); } /** * Return for whom this resource exports relevant data. * * @return string The user type the exported data of this resource is * relevant for. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the relevance * information failed. */ public function getRelevance() { $relevance = $this->_resource->getRelevance(); if (empty($relevance)) { $this->_logger->debug( sprintf( 'No relevance value found for %s', $this->_resource->getName() ) ); } else { $this->_logger->debug( sprintf( 'Relevance for %s is %s', $this->_resource->getName(), $relevance ) ); } return $relevance; } /** * Fetch the resource ACL. * * @return array ACL for this resource. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the ACL information * failed. */ public function getAcl() { $acl = $this->_resource->getAcl(); if (empty($acl)) { $this->_logger->debug( sprintf( 'No ACL found for %s', $this->_resource->getName() ) ); } else { $this->_logger->debug( sprintf( 'ACL for %s is %s', $this->_resource->getName(), serialize($acl) ) ); } return $acl; } /** * Fetch the access controls on specific attributes of this * resource. * * @return array Attribute ACL for this resource. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the attribute ACL * information failed. */ public function getAttributeAcl() { $attribute_acl = $this->_resource->getAttributeAcl(); if (empty($attribute_acl)) { $this->_logger->debug( sprintf( 'No attribute ACL found for %s', $this->_resource->getName() ) ); } else { $this->_logger->debug( sprintf( 'Attribute ACL for %s is %s', $this->_resource->getName(), serialize($attribute_acl) ) ); } return $attribute_acl; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Caches the resource return values in class variables. * * Copyright 2009 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @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_Resource_Decorator_Mcache implements Horde_Kolab_FreeBusy_Resource { /** * The decorated resource. * * @var Horde_Kolab_FreeBusy_Resource_Interface */ private $_resource; /** * The cached resource relevance. * * @var string */ private $_relevance; /** * The cached resource ACL. * * @var array */ private $_acl; /** * The cached resource attribute ACL. * * @var array */ private $_attribute_acl; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Resource_Interface $resource The decorated resource. */ public function __construct( Horde_Kolab_FreeBusy_Resource $resource ) { $this->_resource = $resource; } /** * Return the name of the resource. * * @return string The name for the resource. */ public function getName() { return $this->_resource->getName(); } /** * Return the owner of the resource. * * @return Horde_Kolab_FreeBusy_Owner The resource owner. */ public function getOwner() { return $this->_resource->getOwner(); } /** * Connect to the resource. * * @return NULL * * @throws Horde_Kolab_FreeBusy_Exception If connecting to the resource * failed. */ public function connect() { $this->_resource->connect(); } /** * Return for whom this resource exports relevant data. * * @return string The user type the exported data of this resource is * relevant for. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the relevance * information failed. */ public function getRelevance() { if (!isset($this->_relevance)) { $this->_relevance = $this->_resource->getRelevance(); } return $this->_relevance; } /** * Fetch the resource ACL. * * @return array ACL for this resource. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the ACL information * failed. */ public function getAcl() { if (!isset($this->_acl)) { $this->_acl = $this->_resource->getAcl(); } return $this->_acl; } /** * Fetch the access controls on specific attributes of this * resource. * * @return array Attribute ACL for this resource. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the attribute ACL * information failed. */ public function getAttributeAcl() { if (!isset($this->_attribute_acl)) { $this->_attribute_acl = $this->_resource->getAttributeAcl(); } return $this->_attribute_acl; } /** * Return the decorated resource. * * @return Horde_Kolab_FreeBusy_Resource_Interface The decorated resource. */ protected function getResource() { return $this->_resource; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Logs the free/busy resource access. * * Copyright 2009 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @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_Resource_Event_Decorator_Log extends Horde_Kolab_FreeBusy_Resource_Decorator_Log implements Horde_Kolab_FreeBusy_Resource_Event { /** * Constructor. * * @param Horde_Kolab_FreeBusy_Resource_Interface $resource The decorated * resource. * @param mixed $logger The log handler. The * class must at least * provide the debug() * method. */ public function __construct( Horde_Kolab_FreeBusy_Resource_Event $resource, $logger ) { parent::__construct($resource, $logger); } /** * Lists all events in the given time range. * * * @param Horde_Date $startDate Start of range date object. * @param Horde_Date $endDate End of range data object. * * @return array Events in the given time range. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the events failed. */ public function listEvents(Horde_Date $startDate, Horde_Date $endDate) { $this->getLogger()->debug( sprintf( 'Listing events for resource %s between %s and %s.', $this->getResource()->getName(), //@todo: (string) is enough in Horde4 (string) $startDate->rfc2822DateTime(), (string) $endDate->rfc2822DateTime() ) ); $events = $this->getResource()->listEvents($startDate, $endDate); $this->getLogger()->debug( sprintf( 'Found %s events.', count($events) ) ); return $events; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Allows to cache data from a free/busy resource. * * Copyright 2009 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @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_Resource_Event_Decorator_Mcache extends Horde_Kolab_FreeBusy_Resource_Decorator_Mcache implements Horde_Kolab_FreeBusy_Resource_Event { /** * Constructor. * * @param Horde_Kolab_FreeBusy_Resource_Interface $resource The decorated resource. */ public function __construct( Horde_Kolab_FreeBusy_Resource_Event $resource ) { parent::__construct($resource); } /** * Lists all events in the given time range. * * @param Horde_Date $startDate Start of range date object. * @param Horde_Date $endDate End of range data object. * * @return array Events in the given time range. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the events failed. */ public function listEvents(Horde_Date $startDate, Horde_Date $endDate) { return $this->getResource()->listEvents($startDate, $endDate); } } * @author Steffen Hansen * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * The free/busy Kolab backend for Horde framework 3. * * Copyright 2004-2010 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. * * @category Kolab * @package Kolab_FreeBusy * @author Steffen Hansen * @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_Resource_Event_Fwthree extends Horde_Kolab_FreeBusy_Resource_Event_Kolab { /** * IMAP handler * * @var Horde_Kolab_FreeBusy_Imap */ private $_imap; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Imap $imap The IMAP connection handler. * @param Horde_Kolab_FreeBusy_Owner_Freebusy $owner The resource owner. */ public function __construct( Horde_Kolab_FreeBusy_Imap $imap, Horde_Kolab_FreeBusy_Owner_Event $owner ) { $this->_imap = $imap; $this->_owner = $owner; } /** * Return the name of the resource. * * @return string The name for the resource. */ public function getName() { return $this->_imap->getFolder()->getName(); } /** * Return the folder represented by this resource. * * @return Horde_Kolab_Storage_Folder The folder. */ protected function getFolder() { return $this->_imap->getFolder(); } /** * Return the data represented by this resource. * * @return Horde_Kolab_Storage_Data The data. */ protected function getData() { return $this->getFolder()->getData(); } /** * Return for whom this resource exports relevant data. * * @return string The user type the exported data of this resource is * relevant for. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the relevance * information failed. */ public function getRelevance() { $relevance = $this->_imap->getRelevance(); if (is_a($relevance, 'PEAR_Error')) { throw new Horde_Kolab_FreeBusy_Exception($relevance->getMessage()); } return $relevance; } /** * Fetch the resource ACL. * * @return array ACL for this resource. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the ACL information * failed. */ public function getAcl() { $perm = $this->getFolder()->getPermission(); $acl = &$perm->acl; if (empty($acl)) { $acl = array(); } return $acl; } /** * Fetch the access controls on specific attributes of this * resource. * * @return array Attribute ACL for this resource. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the attribute ACL * information failed. */ public function getAttributeAcl() { $acl = $this->_imap->getExtendedACL(); if (is_a($acl, 'PEAR_Error')) { throw new Horde_Kolab_FreeBusy_Exception($acl->getMessage()); } return $acl; } /** * Return the UTF7-IMAP compliant folder name from the request. * * @return string The folder name. */ private function _getImapFolderName() { return Horde_String::convertCharset( $this->_getFolderParameter(), 'UTF-8', 'UTF7-IMAP' ); } /** * Lists all events in the given time range. * * @param Horde_Date $startDate Start of range date object. * @param Horde_Date $endDate End of range data object. * * @return array Events in the given time range. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the events failed. */ public function listEvents(Horde_Date $startDate, Horde_Date $endDate) { require_once 'Horde/Kolab/FreeBusy/Object/Event.php'; $data = $this->getData(); if (is_a($data, 'PEAR_Error')) { throw new Horde_Kolab_FreeBusy_Exception($data->getMessage()); } $objects = $data->getObjects(); $startts = $startDate->timestamp(); $endts = $endDate->timestamp(); $result = array(); /** * PERFORMANCE START * * The following section has been performance optimized using * xdebug and kcachegrind. * * If there are many events it takes a lot of time and memory to create * new objects from the array and use those for time comparison. So the * code tries to use the original data array as long as possible and * only converts it to an object if really required (e.g. the event * actually lies in the time span or it recurs in which case the * calculations are more complex). */ foreach($objects as $object) { /* check if event period intersects with given period */ if (!(($object['start-date'] > $endts) || ($object['end-date'] < $startts))) { $result[] = new Horde_Kolab_FreeBusy_Object_Event($object); continue; } /* do recurrence expansion if not keeping anyway */ if (isset($object['recurrence'])) { $event = new Horde_Kolab_FreeBusy_Object_Event($object); if ($event->recursIn($startDate, $endDate)) { $result[] = $event; } } } /** PERFORMANCE END */ return $result; } } * @author Steffen Hansen * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * The free/busy Kolab backend. * * Copyright 2004-2008 Klarälvdalens Datakonsult AB * Copyright 2008-2009 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @category Kolab * @package Kolab_FreeBusy * @author Chuck Hagenbuch * @author Steffen Hansen * @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_Resource_Event_Kolab extends Horde_Kolab_FreeBusy_Resource_Kolab implements Horde_Kolab_FreeBusy_Resource_Event { /** * Constructor. * * @param Horde_Kolab_Storage_Folder $folder The storage folder * representing this * resource. * @param Horde_Kolab_FreeBusy_Owner_Freebusy $owner The resource owner. */ public function __construct( Horde_Kolab_Storage_Folder $folder, Horde_Kolab_FreeBusy_Owner_Event $owner ) { if ($folder->getType() != 'event') { throw new Horde_Kolab_FreeBusy_Exception( sprintf( 'Resource %s has type "%s" not "event"!', $folder->getName(), $folder->getType() ) ); } parent::__construct($folder, $owner); } /** * Return for whom this resource exports relevant data. * * @return string The user type the exported data of this resource is * relevant for. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the relevance * information failed. */ public function getRelevance() { return $this->getFolder()->getKolabAttribute('incidences-for'); } /** * Fetch the access controls on specific attributes of this * resource. * * @return array Attribute ACL for this resource. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the attribute ACL * information failed. */ public function getAttributeAcl() { return $this->getFolder()->getXfbaccess(); } /** * Lists all events in the given time range. * * @param Horde_Date $startDate Start of range date object. * @param Horde_Date $endDate End of range data object. * * @return array Events in the given time range. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the events failed. */ public function listEvents(Horde_Date $startDate, Horde_Date $endDate) { try { $objects = $this->getData()->getObjects(); } catch (Horde_Kolab_Storage_Exception $e) { //todo: prior exception throw new Horde_Kolab_FreeBusy_Exception($e); } $startts = $startDate->timestamp(); $endts = $endDate->timestamp(); $result = array(); /** * PERFORMANCE START * * The following section has been performance optimized using * xdebug and kcachegrind. * * If there are many events it takes a lot of time and memory to create * new objects from the array and use those for time comparison. So the * code tries to use the original data array as long as possible and * only converts it to an object if really required (e.g. the event * actually lies in the time span or it recurs in which case the * calculations are more complex). */ foreach($objects as $object) { /* check if event period intersects with given period */ if (!(($object['start-date'] > $endts) || ($object['end-date'] < $startts))) { $result[] = new Horde_Kolab_FreeBusy_Object_Event($object); continue; } /* do recurrence expansion if not keeping anyway */ if (isset($object['recurrence'])) { $event = new Horde_Kolab_FreeBusy_Object_Event($object); if ($event->recursIn($startDate, $endDate)) { $result[] = $event; } } } /** PERFORMANCE END */ return $result; } } * @author Steffen Hansen * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Interface definition for free/busy resources. * * Copyright 2009 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @category Kolab * @package Kolab_FreeBusy * @author Chuck Hagenbuch * @author Steffen Hansen * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ interface Horde_Kolab_FreeBusy_Resource_Event extends Horde_Kolab_FreeBusy_Resource { /** * Lists all events in the given time range. * * * @param Horde_Date $startDate Start of range date object. * @param Horde_Date $endDate End of range data object. * * @return array Events in the given time range. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the events failed. */ public function listEvents(Horde_Date $startDate, Horde_Date $endDate); } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * The backend for Kolab resources. * * Copyright 2004-2008 Klarälvdalens Datakonsult AB * Copyright 2008-2009 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @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_Resource_Kolab implements Horde_Kolab_FreeBusy_Resource { /** * The link to the folder. * * @var Horde_Kolab_Storage_Folder */ private $_folder; /** * The folder owner. * * @var Horde_Kolab_FreeBusy_Owner_Freebusy */ protected $_owner; /** * Constructor. * * @param Horde_Kolab_Storage_Folder $folder The storage folder * representing this * resource. * @param Horde_Kolab_FreeBusy_Owner_Freebusy $owner The resource owner. */ public function __construct( Horde_Kolab_Storage_Folder $folder, Horde_Kolab_FreeBusy_Owner $owner ) { $this->_folder = $folder; $this->_owner = $owner; } /** * Return the owner of the resource. * * @return Horde_Kolab_FreeBusy_Owner The resource owner. */ public function getOwner() { return $this->_owner; } /** * Return the name of the resource. * * @return string The name for the resource. */ public function getName() { return $this->_folder->getName(); } /** * Return the folder represented by this resource. * * @return Horde_Kolab_Storage_Folder The folder. */ protected function getFolder() { return $this->_folder; } /** * Return the data represented by this resource. * * @return Horde_Kolab_Storage_Data The data. */ protected function getData() { return $this->_folder->getData(); } /** * Return for whom this resource exports relevant data. * * @return string The user type the exported data of this resource is * relevant for. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the relevance * information failed. * * @todo It would be nice if we would not only have the free/busy specific * relevance but a generic way of setting the relevance of resources. */ public function getRelevance() { throw new Horde_Kolab_FreeBusy_Exception( 'There is no generic definition for relevance available!' ); } /** * Fetch the resource ACL. * * @return array ACL for this resource. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the ACL information * failed. */ public function getAcl() { $perm = $this->_folder->getPermission(); $acl = &$perm->acl; return $acl; } /** * Fetch the access controls on specific attributes of this * resource. * * @return array Attribute ACL for this resource. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the attribute ACL * information failed. * * @todo It would be nice if we would not only have the free/busy specific * attribute acls but a generic way of setting attribute ACL for resources. */ public function getAttributeAcl() { throw new Horde_Kolab_FreeBusy_Exception( 'There is no generic definition for attribute ACL available!' ); } /** * Return the UTF7-IMAP compliant folder name from the request. * * @return string The folder name. */ private function _getImapFolderName() { return Horde_String::convertCharset( $this->_getFolderParameter(), 'UTF-8', 'UTF7-IMAP' ); } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Interface definition for resources exporting data. * * Copyright 2009 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @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 */ interface Horde_Kolab_FreeBusy_Resource { /** * Return the name of the resource. * * @return string The name for the resource. */ public function getName(); /** * Return the owner of the resource. * * @return Horde_Kolab_FreeBusy_Owner The resource owner. */ public function getOwner(); /** * Return for whom this resource exports relevant data. * * @return string The user type the exported data of this resource is * relevant for. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the relevance * information failed. */ public function getRelevance(); /** * Fetch the resource ACL. * * @return array ACL for this resource. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the ACL information * failed. */ public function getAcl(); /** * Fetch the access controls on specific attributes of this * resource. * * @return array Attribute ACL for this resource. * * @throws Horde_Kolab_FreeBusy_Exception If retrieving the attribute ACL * information failed. */ public function getAttributeAcl(); } * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * Logs access to the export system. * * Copyright 2009 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @category Kolab * @package Kolab_FreeBusy * @author Steffen Hansen * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy * @since Horde 3.2 */ class Horde_Kolab_FreeBusy_User_Decorator_Log { /** * The decorated user. * * @var Horde_Kolab_FreeBusy_User_Interface */ private $_user; /** * The logger. * * @var mixed */ private $_logger; /** * Constructor. * * @param Horde_Kolab_FreeBusy_User_Interface $user The decorated * user. * @param mixed $logger The log handler. The * class must at least * provide the notice() * and err() methods. */ public function __construct( Horde_Kolab_FreeBusy_User_Interface $user, $logger ) { $this->_user = $user; $this->_logger = $logger; } /** * Finds out if a set of login credentials are valid. * * @param array $pass The password to check. * * @return boolean Whether or not the password was correct. */ public function authenticate($pass) { $result = $this->_user->authenticate($pass); if ($result) { $this->_logger->notice( sprintf( 'Login success for %s from %s to free/busy.', $this->user, $_SERVER['REMOTE_ADDR'])); } else { $this->_logger->err(sprintf('Failed login for %s from %s to free/busy', $this->user, $_SERVER['REMOTE_ADDR'])); } } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * This class represents a Kolab accessing the export system. * * Copyright 2010 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @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_User_Kolab extends Horde_Kolab_FreeBusy_UserDb_User_Kolab implements Horde_Kolab_FreeBusy_User { /** * The user details. * * @var Horde_Kolab_FreeBusy_Params_User */ private $_user; /** * The connection to the user database. * * @var Horde_Kolab_FreeBusy_UserDb */ private $_userdb; /** * Has the user authenticated successfully? * * @var boolean */ private $_authenticated; /** * Constructor. * * @param Horde_Kolab_FreeBusy_Params_User $user The user parameters. * @param Horde_Kolab_FreeBusy_UserDb $userdb The connection to the user database. */ public function __construct( Horde_Kolab_FreeBusy_Params_User $user, Horde_Kolab_FreeBusy_UserDb $userdb ) { $this->_user = $user; $this->_userdb = $userdb; parent::__construct($userdb); } /** * Fetch the user data from the user db. * * @return NULL */ protected function fetchUserDbUser() { return $this->fetchUser($this->_user->getId()); } /** * Finds out if a set of login credentials are valid. * * @return boolean Whether or not the password was correct. */ public function isAuthenticated() { if ($this->_authenticated === null) { list($user, $pass) = $this->_user->getCredentials(); try { $this->_userdb->connect($this->getGuid(), $pass); $this->_authenticated = true; } catch (Exception $e) { //@todo: Not just Exception. This must be made more specific. $this->_authenticated = false; } } return $this->_authenticated; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * This interface defines a user accessing the export system. * * Copyright 2010 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did not * receive this file, see * http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @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 */ interface Horde_Kolab_FreeBusy_User { /** * Return the primary id of the user accessing the system. * * @return string The primary id. */ public function getPrimaryId(); /** * Return the primary domain of the user accessing the system. * * @return string The primary domain. */ public function getDomain(); /** * Return the groups this user is member of. * * @return array The groups for this user. */ public function getGroupAddresses(); /** * Finds out if a set of login credentials are valid. * * @return boolean Whether or not the password was correct. */ public function isAuthenticated(); } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * This class represents the Kolab user database behind the free/busy system. * * Copyright 2010 Kolab Systems AG * * 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. * * @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_UserDb_Kolab implements Horde_Kolab_FreeBusy_UserDb { /** * Connect to the database using the provided credentials. * * @param string $user The ID of the user. * @param string $pass The password of the user. * * @return NULL * * @throws Horde_Kolab_Server_Exception_Bindfailed In case the login failed. */ public function connect($id, $pass) { $server = Horde_Kolab_Server::singleton( array( 'uid' => $id, 'pass' => $pass, ) ); $user_object = $server->fetch(); if (is_a($user_object, 'PEAR_Error')) { throw new Horde_Kolab_FreeBusy_Exception( $user_object->getMessage(), $user_object->getCode() ); } } /** * Get the actual database handler. * * @return mixed * * //@todo: fix return/refactor */ public function fetchDb() { global $conf; require_once 'Horde/Kolab/Server.php'; /* Connect to the Kolab user database */ $db = &Horde_Kolab_Server::singleton(array('uid' => $conf['kolab']['ldap']['phpdn'])); // TODO: Remove once Kolab_Server has been fixed to always return the base dn $db->fetch(); return $db; } public function getUser(Horde_Kolab_FreeBusy_Params_User $user) { //@todo This is a bad structure. A decent approach might be to ask the param to return the user object. Logging needs to be taken into account. $id = $user->getId(); if (!empty($id)) { return new Horde_Kolab_FreeBusy_User_Kolab($user, $this); } else { //@todo implement // public function getPrimaryId() { // return 'anonymous'; // } return new Horde_Kolab_FreeBusy_User_Anonymous(); } } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * This interface represents a user from the user database. * * Copyright 2010 Kolab Systems AG * * 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. * * @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 */ abstract class Horde_Kolab_FreeBusy_UserDb_User_Kolab { /** * The connection to the database. * * @var Horde_Kolab_Server */ private $_db; /** * The user representation. * * @var ??? */ private $_user; /** * The user ID in the db. * * @var string */ private $_guid; /** * The representation of the server configuration. * * @var ??? */ private $_server; /** * Constructor * * @param Horde_Kolab_Server $db The connection to the server. */ public function __construct(Horde_Kolab_FreeBusy_UserDb $db) { $this->_db = $db->fetchDb(); } protected function getServer() { if ($this->_server === null) { $this->_server = $this->_validate( $this->_db->fetch( sprintf('k=kolab,%s', $this->_db->getBaseUid()), KOLAB_OBJECT_SERVER ) ); } return $this->_server; } protected function getUserDbUser() { if ($this->_user === null) { $this->_user = $this->fetchUserDbUser(); } return $this->_user; } abstract protected function fetchUserDbUser(); protected function fetchUser($user) { $this->_guid = $this->_validate( $this->_db->uidForIdOrMail($user) ); return $this->_validate( $this->_db->fetch($this->_guid, KOLAB_OBJECT_USER) ); } protected function fetchOwner($owner) { $this->_guid = $this->_validate( $this->_db->uidForMailOrIdOrAlias($owner) ); return $this->_validate( $this->_db->fetch($this->_guid, KOLAB_OBJECT_USER) ); } /** * Return the primary id of the user accessing the system. * * @return string The primary id. */ public function getGuid() { $this->getUserDbUser(); return $this->_guid; } /** * Return the primary id of the user accessing the system. * * @return string The primary id. */ public function getPrimaryId() { return $this->getMail(); } /** * Return the mail address of the resource owner. * * @return string The mail address. */ public function getMail() { return $this->_validate($this->getUserDbUser()->get(KOLAB_ATTR_MAIL)); } /** * Return the primary domain of the user accessing the system. * * @return string The primary domain. */ public function getDomain() { $mail = $this->getUserDbUser()->getMail(); $idx = strpos($mail, '@'); if ($idx !== false) { return substr($mail, $idx + 1); } else { return ''; } } /** * Return the name of the resource owner. * * @return string The name of the owner. */ public function getName() { return $this->_validate($this->getUserDbUser()->get(KOLAB_ATTR_CN)); } /** * Indicates the correct free/busy server for the resource owner. * * @return string The server name. */ public function getFreeBusyServer() { return $this->_validate($this->getUserDbUser()->getServer('freebusy')); } /** * Return how many days into the past the free/busy data should be * calculated for this owner. * * @return int The number of days. */ public function getFreeBusyPast() { return $this->_validate($this->getServer()->get(KOLAB_ATTR_FBPAST)); } /** * Return how many days into the future the free/busy data should be * calculated for this owner. * * @return int The number of days. */ public function getFreeBusyFuture() { return $this->_validate($this->getUserDbUser()->get(KOLAB_ATTR_FBFUTURE)); } /** * Return the groups this user is member of. * * @return array The groups for this user. */ public function getGroupAddresses() { return $this->_validate($this->getUserDbUser()->getGroupAddresses()); } protected function _validate($result) { if (is_a($result, 'PEAR_Error')) { throw new Horde_Kolab_FreeBusy_Exception($result->getMessage(), $result->getCode()); } return $result; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * This interface represents a user from the user database. * * Copyright 2010 Kolab Systems AG * * 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. * * @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 */ interface Horde_Kolab_FreeBusy_UserDb_User { /** * Return the primary id of the user accessing the system. * * @return string The primary id. */ public function getPrimaryId(); /** * Return the mail address of the resource owner. * * @return string The mail address. */ public function getMail(); /** * Return the primary domain of the user accessing the system. * * @return string The primary domain. */ public function getDomain(); /** * Return the name of the resource owner. * * @return string The name of the owner. */ public function getName(); /** * Return how many days into the past the free/busy data should be * calculated for this owner. * * @return int The number of days. */ public function getFreeBusyPast(); /** * Return how many days into the future the free/busy data should be * calculated for this owner. * * @return int The number of days. */ public function getFreeBusyFuture(); } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy */ /** * This interface represents the user database behind the free/busy system. * * Copyright 2010 Kolab Systems AG * * 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. * * @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 */ interface Horde_Kolab_FreeBusy_UserDb { /** * Connect to the database using the provided credentials. * * @param string $user The ID of the user. * @param string $pass The password of the user. * * @return NULL * * @throws Horde_Kolab_Server_Exception_Bindfailed In case the login failed. */ public function connect($id, $pass); /** * Get the actual database handler. * * @return mixed * * //@todo: fix return/refactor */ public function fetchDb(); } * @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.2.1 2009-03-06 18:12:01 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.2.1 2009-03-06 18:12:01 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"; } } trigger(); * * OR * * $fb->fetch(); * * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy.php,v 1.10.2.4 2010-10-10 18:37:56 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. * * @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 */ private $_cache; private $_access; private $_request; private $_db_owner; private $_db_user; /** * Trigger regeneration of free/busy data in a calender. */ function &trigger() { global $conf; /* Get the folder name */ list($user, $owner, $folder) = $this->_getAccess(); try { Horde::logMessage( sprintf( "Partial free/busy data for folder %s of owner %s requested by user %s.", $folder->getFolder(), $owner->getOwner(), $user->getId() ), __FILE__, __LINE__, PEAR_LOG_DEBUG ); } catch (Horde_Kolab_FreeBusy_Exception $e) { $error = array('type' => FREEBUSY_ERROR_NOTFOUND, 'error' => $e); $view = new Horde_Kolab_FreeBusy_View_error($error); return $view; } /* Get the cache request variables */ $req_cache = Util::getFormData('cache', false); $req_extended = Util::getFormData('extended', false); /* Try to fetch the data if it is stored on a remote server */ $result = $this->fetchRemote($user, $owner, $folder, 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; } list($user, $owner, $folder) = $this->_getAccess(); if (!$req_cache) { /* User wants to regenerate the cache */ $db_user = $this->_getDbUser(); $db_owner = $this->_getDbOwner(); /* Here we really need an authenticated IMAP user */ if (!$db_user->isAuthenticated()) { $error = array( 'type' => FREEBUSY_ERROR_UNAUTHORIZED, 'error' => $result ); $view = new Horde_Kolab_FreeBusy_View_error($error); return $view; } $id = $db_owner->getPrimaryId(); if (empty($id)) { $message = sprintf(_("No such account %s!"), htmlentities($owner->getOwner())); $error = array('type' => FREEBUSY_ERROR_NOTFOUND, 'error' => PEAR::raiseError($message)); $view = new Horde_Kolab_FreeBusy_View_error($error); return $view; } /* Update the cache */ $vCal = $this->_generate($this->_getResource()); if (is_a($vCal, 'PEAR_Error')) { $error = array('type' => FREEBUSY_ERROR_NOTFOUND, 'error' => $vCal); $view = new Horde_Kolab_FreeBusy_View_error($error); return $view; } if (empty($vCal)) { $result = $this->_getCache()->deletePartial($this->_getDbUser(), $folder); } else { $result = $this->_getCache()->storePartial($this->_getDbUser(), $folder, $this->_getResource(), $vCal); } 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->_getCache()->loadPartial($this->_getDbUser(), $folder, $req_extended); if (is_a($vfb, 'PEAR_Error')) { var_dump($vfb->getMessage()); $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' => $this->_getDbOwner()->getPrimaryId() . '.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 = 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 = 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; } $result = $this->_getCache()->loadCombined( $this->_getDbUser(), $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(); } $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 = 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, 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 */ $vCal = $this->_generate($this->_getResource()); if (is_a($vCal, 'PEAR_Error')) { $reporter->failure($calendar->name, $vCal->getMessage()); continue; } if (empty($vCal)) { $result = $this->_getCache()->deletePartial($this->_getDbUser(), $folder); } else { $result = $this->_getCache()->storePartial($this->_getDbUser(), $folder, $vCal, $this->_getResource()); } 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; } /** * 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($owner, $user, $folder, $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; } $db_owner = $this->_getDbOwner(); /* Check if we are on the right server and redirect if appropriate */ if ($db_owner->getFreebusyServer() && $db_owner->getFreebusyServer() != $server) { if ($trigger) { $path = sprintf('/trigger/%s/%s.' . ($extended)?'pxfb':'pfb', urlencode($db_owner->getPrimaryId()), urlencode($folder->getFolder())); } else { $path = sprintf('/%s.' . ($extended)?'xfb':'ifb', urlencode($db_owner->getPrimaryId())); } $redirect = $db_owner->getFreebusyServer() . $path; Horde::logMessage(sprintf("URL %s indicates remote free/busy server since we only offer %s. Redirecting.", $db_owner->getFreebusyServer(), $server), __FILE__, __LINE__, PEAR_LOG_ERR); if ($do_redirect) { header("Location: $redirect"); } else { header("X-Redirect-To: $redirect"); $redirect = 'https://' . urlencode($user->getId()) . ':' . urlencode(Auth::getCredential('password')) . '@' . $db_owner->getFreebusyServer() . $path; if (!@readfile($redirect)) { $message = sprintf(_("Unable to read free/busy information from %s"), 'https://' . urlencode($user->getPrimaryId()) . ':XXX' . '@' . $db_owner->getFreebusyServer() . $_SERVER['REQUEST_URI']); return PEAR::raiseError($message); } } exit; } } /** * Generate partial free/busy data for a calendar. * * @param Horde_Kolab_FreeBusy_Resource $resource The calendar resource. * * @return Horde_iCalendar|PEAR_Error The partial free/busy data if successful. */ private function _generate($resource) { require_once 'Horde/Kolab/FreeBusy/Export/Freebusy.php'; require_once 'Horde/Kolab/FreeBusy/Export/Freebusy/Backend.php'; require_once 'Horde/Kolab/FreeBusy/Export/Freebusy/Backend/Kolab.php'; require_once 'Horde/Kolab/FreeBusy/Export/Freebusy/Base.php'; require_once 'Horde/Kolab/FreeBusy/Export/Freebusy/Fwthree.php'; require_once 'Horde/Kolab/FreeBusy/Export/Freebusy/Decorator/Log.php'; require_once 'Horde/Kolab/FreeBusy/Logger.php'; $export = new Horde_Kolab_FreeBusy_Export_Freebusy_Decorator_Log( new Horde_Kolab_FreeBusy_Export_Freebusy_Base( new Horde_Kolab_FreeBusy_Export_Freebusy_Backend_Kolab(), $resource, $this->_getRequest() ), new Horde_Kolab_FreeBusy_Logger() ); return $export->export(); /* 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; */ /* if (!empty($access->server_object)) { */ /* $result = $access->server_object->get(KOLAB_ATTR_FBPAST); */ /* if (!is_a($result, 'PEAR_Error')) { */ /* $fbpast = $result; */ /* } */ /* } */ /* if (!empty($access->owner_object)) { */ /* $result = $access->owner_object->get(KOLAB_ATTR_FBFUTURE); */ /* if (!is_a($result, 'PEAR_Error')) { */ /* $fbfuture = $result; */ /* } */ /* } */ /* return $fb->generate(null, null, */ /* !empty($fbpast) ? $fbpast : 0, */ /* !empty($fbfuture)? $fbfuture : isset($conf['fb']['future_days']) ? $conf['fb']['future_days'] : 60, */ /* $access->owner, */ /* $access->owner_object->get(KOLAB_ATTR_CN)); */ } /** * Generate partial free/busy data for a calendar. * * @return Horde_Kolab_FreeBusy_Resource The calendar resource. */ private function _getResource() { /* Now we really need the free/busy library */ require_once 'Horde/Kolab/FreeBusy/Imap.php'; require_once 'Horde/Kolab/FreeBusy/Resource.php'; require_once 'Horde/Kolab/FreeBusy/Resource/Event.php'; require_once 'Horde/Kolab/FreeBusy/Resource/Kolab.php'; require_once 'Horde/Kolab/FreeBusy/Resource/Decorator/Log.php'; require_once 'Horde/Kolab/FreeBusy/Resource/Decorator/Mcache.php'; require_once 'Horde/Kolab/FreeBusy/Resource/Event/Kolab.php'; require_once 'Horde/Kolab/FreeBusy/Resource/Event/Fwthree.php'; require_once 'Horde/Kolab/FreeBusy/Resource/Event/Decorator/Log.php'; require_once 'Horde/Kolab/FreeBusy/Resource/Event/Decorator/Mcache.php'; require_once 'Horde/Kolab/FreeBusy/Logger.php'; $imap = new Horde_Kolab_FreeBusy_Imap(); $imap->connect($this->_getImapFolder()->getResourceId()); return new Horde_Kolab_FreeBusy_Resource_Event_Decorator_Log( new Horde_Kolab_FreeBusy_Resource_Event_Decorator_Mcache( new Horde_Kolab_FreeBusy_Resource_Event_Fwthree( $imap, $this->_getDbOwner() ) ), new Horde_Kolab_FreeBusy_Logger() ); } private function _getImapFolder() { list($user, $owner, $folder) = $this->_getAccess(); require_once 'Horde/Kolab/FreeBusy/Params/Freebusy/Resource/Kolab.php'; return new Horde_Kolab_FreeBusy_Params_Freebusy_Resource_Kolab( $this->_getDbUser(), $folder ); } private function _getAccess() { if ($this->_access === null) { require_once 'Horde/Kolab/FreeBusy/Params/User.php'; require_once 'Horde/Kolab/FreeBusy/Params/Owner.php'; require_once 'Horde/Kolab/FreeBusy/Params/Freebusy/Owner.php'; require_once 'Horde/Kolab/FreeBusy/Params/Freebusy/Folder.php'; $this->_access = array( new Horde_Kolab_FreeBusy_Params_User( $this->_getRequest() ), new Horde_Kolab_FreeBusy_Params_Freebusy_Owner( $this->_getRequest() ), new Horde_Kolab_FreeBusy_Params_Freebusy_Folder( $this->_getRequest() ) ); } return $this->_access; } private function _getRequest() { if ($this->_request === null) { require_once 'Horde/Kolab/FreeBusy/Request.php'; $this->_request = new Horde_Kolab_FreeBusy_Request(); } return $this->_request; } private function _getDbOwner() { list($user, $owner, $folder) = $this->_getAccess(); if ($this->_db_owner === null) { require_once 'Horde/Kolab/FreeBusy/UserDb.php'; require_once 'Horde/Kolab/FreeBusy/UserDb/Kolab.php'; require_once 'Horde/Kolab/FreeBusy/UserDb/User.php'; require_once 'Horde/Kolab/FreeBusy/UserDb/User/Kolab.php'; require_once 'Horde/Kolab/FreeBusy/Owner.php'; require_once 'Horde/Kolab/FreeBusy/Owner/Kolab.php'; require_once 'Horde/Kolab/FreeBusy/Owner/Event.php'; require_once 'Horde/Kolab/FreeBusy/Owner/Event/Kolab.php'; $id = $owner->getOwner(); if (empty($id)) { $owner = $folder; } $id = $owner->getOwner(); if (empty($id)) { //@todo: Hm. throw new Horde_Kolab_FreeBusy_Exception(); } $this->_db_owner = new Horde_Kolab_FreeBusy_Owner_Event_Kolab( $owner, new Horde_Kolab_FreeBusy_UserDb_Kolab(), $this->_getDbUser() ); } return $this->_db_owner; } private function _getDbUser() { list($user, $owner, $folder) = $this->_getAccess(); if ($this->_db_user === null) { require_once 'Horde/Kolab/FreeBusy/UserDb.php'; require_once 'Horde/Kolab/FreeBusy/UserDb/Kolab.php'; require_once 'Horde/Kolab/FreeBusy/UserDb/User/Kolab.php'; require_once 'Horde/Kolab/FreeBusy/User.php'; require_once 'Horde/Kolab/FreeBusy/User/Kolab.php'; $this->_db_user = new Horde_Kolab_FreeBusy_User_Kolab( $user, new Horde_Kolab_FreeBusy_UserDb_Kolab() ); } return $this->_db_user; } public function _getCache() { global $conf; /* 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'; } } /* Load the cache class now */ require_once 'Horde/Kolab/FreeBusy/Cache.php'; require_once 'Horde/Kolab/FreeBusy/Cache/Structure.php'; require_once 'Horde/Kolab/FreeBusy/Cache/Structure/Base.php'; require_once 'Horde/Kolab/FreeBusy/Cache/Structure/Decorator/Log.php'; return new Horde_Kolab_FreeBusy_Cache( new Horde_Kolab_FreeBusy_Cache_Structure_Decorator_Log( new Horde_Kolab_FreeBusy_Cache_Structure_Base( $cache_dir ), new Horde_Kolab_FreeBusy_Logger() ), $this->_getDbOwner() ); } } INDX( 7v(xp^ii b%ar`[ Deprecated.phppZii b%ar`[ DEPREC~1.PHP`N b% b% b% b%Filter/`N<+q%<+q%<+q%<+q%Format:hViif%Q[s Format.php;hRf%f%f%f%FreeBusypZiiP%is`Y FreeBusy.php`JP%P%P%P%IMAPhRiiP%Bt IMAP.phphRP%P%P%P%ResourcepZii6% * @author Thomas Jarosch * @package Kolab_Storage */ class Horde_Kolab_IMAP_cclient extends Horde_Kolab_IMAP { /** * Basic IMAP connection string. * * @var string */ var $_base_mbox; /** * IMAP connection string that includes the folder. * * @var string */ var $_mbox; /** * The signature of the current connection. * * @var string */ var $_signature; /** * IMAP user name. * * @var string */ var $_login; /** * IMAP password. * * @var string */ var $_password; /** * Connects to the IMAP server. * * @param string $login The user account name. * @param string $password The user password. * @param boolean $tls Should TLS be used for the connection? * * @return boolean|PEAR_Error True in case the connection was opened * successfully. */ function connect($login, $password, $tls = false) { $options = ''; if (!$tls) { $options = '/notls'; } $mbox = '{' . $this->_server . ':' . $this->_port . $options . '}'; $this->_signature = "$mbox|$login|$password"; if ($this->_signature == $this->_reuse_detection) { return true; } $this->_mbox = $this->_base_mbox = $mbox; $this->_login = $login; $this->_password = $password; $this->_imap = null; $this->_reuse_detection = $this->_signature; return true; } /** * Lazy connect to the IMAP server. * * @return mixed True in case the connection was opened successfully, a * PEAR error otherwise. */ function _connect() { $result = @imap_open($this->_base_mbox, $this->_login, $this->_password, OP_HALFOPEN); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Server: %s. Error: %s"), $this->_server, @imap_last_error())); } $this->_imap = $result; return true; } /** * Disconnects from the IMAP server. If not really necessary this * should not be called. Once the page got served the connections * should be closed anyhow and if there is a chance to reuse the * connection it should be used. * * @return mixed True in case the connection was closed successfully, a * PEAR error otherwise. */ function disconnect() { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $this->_reuse_detection = null; $result = @imap_close($this->_imap); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Server: %s. Error: %s"), $this->_server, @imap_last_error())); } return $result; } /** * Opens the given folder. * * @param string $folder The folder to open. * * @return mixed True in case the folder was opened successfully, a PEAR * error otherwise. */ function select($folder) { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $this->_mbox = $this->_base_mbox . $folder; $result = @imap_reopen($this->_imap, $this->_mbox); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $folder, @imap_last_error())); } return $result; } /** * Does the given folder exist? * * @param string $folder The folder to check. * * @return mixed True in case the folder exists, false otherwise */ function exists($folder) { $folders = $this->getMailboxes(); if (is_a($folders, 'PEAR_Error')) { return $folders; } return in_array($folder, $folders); } /** * Create the specified folder. * * @param string $folder The folder to create. * * @return mixed True in case the operation was successfull, a * PEAR error otherwise. */ function create($folder) { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $mbox = $this->_base_mbox . $folder; $result = @imap_createmailbox($this->_imap, $mbox); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $folder, @imap_last_error())); } return $result; } /** * Delete the specified folder. * * @param string $folder The folder to delete. * * @return mixed True in case the operation was successfull, a * PEAR error otherwise. */ function delete($folder) { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $mbox = $this->_base_mbox . $folder; $result = @imap_deletemailbox($this->_imap, $mbox); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $folder, @imap_last_error())); } return $result; } /** * Rename the specified folder. * * @param string $old The folder to rename. * @param string $new The new name of the folder. * * @return mixed True in case the operation was successfull, a * PEAR error otherwise. */ function rename($old, $new) { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $result = @imap_renamemailbox($this->_imap, $this->_base_mbox . $old, $this->_base_mbox . $new); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $old, @imap_last_error())); } return $result; } /** * Returns the status of the current folder. * * @return array An array that contains 'uidvalidity' and 'uidnext'. */ function status() { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $status = @imap_status_current($this->_imap, SA_MESSAGES | SA_UIDVALIDITY | SA_UIDNEXT); if (!$status) { return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $this->_mbox, @imap_last_error())); } return array('uidvalidity' => $status->uidvalidity, 'uidnext' => $status->uidnext); } /** * Returns the uids of the messages in this folder. * * @return mixed The message ids or a PEAR error in case of an error. */ function getUids() { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $uids = @imap_search($this->_imap, 'UNDELETED', SE_UID); if (!is_array($uids)) { $uids = array(); } return $uids; } /** * Searches the current folder using the given list of search criteria. * * @param string $search_list A list of search criteria. * * @return mixed The list of matching message ids or a PEAR error in case * of an error. */ function search($search_list) { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $result = @imap_search($this->_imap, $search_list, SE_UID); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $this->_mbox, @imap_last_error())); } return $result; } /** * Searches the headers of the messages. c-client does not allow using * "HEADER" as it is possible with Net/IMAP, so we need a workaround. * * @param string $field The name of the header field. * @param string $value The value that field should match. * * @return mixed The list of matching message ids or a PEAR error in case * of an error. */ function searchHeaders($field, $value) { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $uids = $this->getUids(); if (is_a($uids, 'PEAR_Error')) { return $uids; } $result = array(); foreach ($uids as $uid) { $header = $this->getMessageHeader($uid, false); if (is_a($header, 'PEAR_Error')) { return $header; } $header_array = MIME_Headers::parseMIMEHeaders($header); if (isset($header_array[$field]) && $header_array[$field] == $value) { $result[] = $uid; } } return $result; } /** * Retrieves the message headers for a given message id. * * @param integer $uid The message id. * @param boolean $peek_for_body Prefetch the body. * * @return mixed The message header or a PEAR error in case of an error. */ function getMessageHeader($uid, $peek_for_body = true) { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $flags = FT_UID; if ($peek_for_body) { $flags |= FT_PREFETCHTEXT; } $result = @imap_fetchheader($this->_imap, $uid, $flags); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), $uid, @imap_last_error())); } return $result; } /** * Retrieves the message body for a given message id. * * @param integer $uid The message id. * * @return mixed The message body or a PEAR error in case of an error. */ function getMessageBody($uid) { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $result = @imap_body($this->_imap, $uid, FT_UID); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), $uid, @imap_last_error())); } return $result; } /** * Retrieves the full message text for a given message id. * * @param integer $uid The message id. * * @return mixed The message text or a PEAR error in case of an error. */ function getMessage($uid) { $header = $this->getMessageHeader($uid); if (is_a($header, 'PEAR_Error')) { return $header; } $body = $this->getMessageBody($uid); if (is_a($body, 'PEAR_Error')) { return $body; } return $header . $body; } /** * Retrieves a list of mailboxes on the server. * * @return mixed The list of mailboxes or a PEAR error in case of an * error. */ function getMailboxes() { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $folders = array(); $result = @imap_list($this->_imap, $this->_base_mbox, '*'); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $this->_base_mbox, @imap_last_error())); } $server_len = strlen($this->_base_mbox); foreach ($result as $folder) { if (substr($folder, 0, $server_len) == $this->_base_mbox) { $folders[] = substr($folder, $server_len); } } return $folders; } /** * Fetches the annotation on a folder. * * @param string $entries The entry to fetch. * @param string $value The specific value to fetch. * @param string $mailbox_name The name of the folder. * * @return mixed The annotation value or a PEAR error in case of an error. */ function getAnnotation($entries, $value, $mailbox_name) { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } static $annotations = array(); $signature = "$this->_signature|$entries|$value|$mailbox_name"; if (!isset($annotations[$signature])) { $result = @imap_getannotation($this->_imap, $mailbox_name, $entries, $value); if (isset($result[$value])) { $annotations[$signature] = $result[$value]; } else { $annotations[$signature] = ''; } } return $annotations[$signature]; } /** * Sets the annotation on a folder. * * @param string $entries The entry to set. * @param array $values The values to set * @param string $mailbox_name The name of the folder. * * @return mixed True if successfull, a PEAR error otherwise. */ function setAnnotation($entries, $values, $mailbox_name) { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } foreach ($values as $key => $value) { $result = @imap_setannotation($this->_imap, $mailbox_name, $entries, $key, $value); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $mailbox_name, @imap_last_error())); } } return true; } /** * Retrieve the access rights from a folder * * @param string $folder The folder to retrieve the ACLs from. * * @return mixed An array of rights if successfull, a PEAR error * otherwise. */ function getACL($folder) { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $result = @imap_getacl($this->_imap, $folder); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $folder, @imap_last_error())); } return $result; } /** * Retrieve the access rights from a folder not owned by the current user * * @param string $folder The folder to retrieve the ACLs from. * * @return mixed An array of rights if successfull, a PEAR error * otherwise. */ function getMyRights($folder) { if (!function_exists('imap_myrights')) { return PEAR::raiseError(sprintf(_("PHP does not support imap_myrights."), $folder, @imap_last_error())); } if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $result = @imap_myrights($this->_imap, $folder); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $folder, @imap_last_error())); } return $result; } /** * Set the access rights for a folder * * @param string $folder The folder to retrieve the ACLs from. * @param string $user The user to set the ACLs for * @param string $acl The ACLs * * @return mixed True if successfull, a PEAR error otherwise. */ function setACL($folder, $user, $acl) { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $result = @imap_setacl($this->_imap, $folder, $user, $acl); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $folder, @imap_last_error())); } return $result; } /** * Delete the access rights for a user. * * @param string $folder The folder that should be modified. * @param string $user The user that should get the ACLs removed * * @return mixed True if successfull, a PEAR error otherwise. */ function deleteACL($folder, $user) { return $this->setACL($folder, $user, ''); } /** * Appends a message to the current folder. * * @param string $msg The message to append. * * @return mixed True or a PEAR error in case of an error. */ function appendMessage($msg) { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $result = @imap_append($this->_imap, $this->_mbox, $msg); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $this->_mbox, @imap_last_error())); } return $result; } /** * Copies a message to a new folder. * * @param integer $uid IMAP message id. * @param string $new_folder Target folder. * * @return mixed True or a PEAR error in case of an error. */ function copyMessage($uid, $new_folder) { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $result = @imap_mail_copy($this->_imap, $uid, $new_folder, CP_UID); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $new_folder, @imap_last_error())); } return $result; } /** * Moves a message to a new folder. * * @param integer $uid IMAP message id. * @param string $new_folder Target folder. * * @return mixed True or a PEAR error in case of an error. */ function moveMessage($uid, $new_folder) { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $result = @imap_mail_move($this->_imap, $uid, $new_folder, CP_UID); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $new_folder, @imap_last_error())); } return $result; } /** * Deletes messages from the current folder. * * @param integer $uids IMAP message ids. * * @return mixed True or a PEAR error in case of an error. */ function deleteMessages($uids) { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } if (!is_array($uids)) { $uids = array($uids); } foreach($uids as $uid) { $result = @imap_delete($this->_imap, $uid, FT_UID); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), $uid, @imap_last_error())); } } return true; } /** * Undeletes a message in the current folder. * * @param integer $uid IMAP message id. * * @return mixed True or a PEAR error in case of an error. */ function undeleteMessages($uid) { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $result = @imap_undelete($this->_imap, $uid, FT_UID); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), $uid, @imap_last_error())); } return $result; } /** * Expunges messages in the current folder. * * @return mixed True or a PEAR error in case of an error. */ function expunge() { if (!isset($this->_imap)) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } $result = @imap_expunge($this->_imap); if (!$result) { return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), $this->_mbox, @imap_last_error())); } return $result; } /** * Return the currently selected mailbox * * @return string The mailbox currently selected */ function current() { return $this->_mbox; } } = 1.0.3 of Net_IMAP (i.e. a * version that includes support for the ANNOTATEMORE IMAP extension). The * latest version of Net_IMAP can be obtained from * http://pear.php.net/get/Net_IMAP */ require_once 'Net/IMAP.php'; /** * The Horde_Kolab_IMAP_Connection_pear class connects to an IMAP server using the * Net_IMAP PEAR package. * * $Horde: framework/Kolab_Server/lib/Horde/Kolab/IMAP/pear.php,v 1.1.2.2 2009-01-06 15:23:15 jan Exp $ * * Copyright 2007-2009 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * * @author Gunnar Wrobel * @author Thomas Jarosch * @package Kolab_Storage */ class Horde_Kolab_IMAP_pear extends Horde_Kolab_IMAP { /** * The signature of the current connection * * @var string */ var $_signature; /** * Connects to the IMAP server. * * @param string $login The user account name. * @param string $password The user password. * @param boolean $tls Should TLS be used for the connection? * * @return mixed True in case the connection was opened successfully, a * PEAR error otherwise. */ function connect($login, $password, $tls = false) { $this->_signature = $this->_server . '|' . $this->_port . "|$login|$password|$tls"; // Reuse existing connection? if ($this->_signature == $this->_reuse_detection) { return true; } $this->_imap = &new Net_IMAP($this->_server, $this->_port); $result = $this->_imap->login($login, $password, true, false); if (is_a($result, 'PEAR_Error')) { return $result; } $this->_reuse_detection = $this->_signature; return true; } /** * Disconnects from the IMAP server. * * @return mixed True in case the connection was closed successfully, a * PEAR error otherwise. */ function disconnect() { $this->_reuse_detection = null; return $this->_imap->disconnect(); } /** * Opens the given folder. * * @param string $folder The folder to open * * @return mixed True in case the folder was opened successfully, a PEAR * error otherwise. */ function select($folder) { return $this->_imap->selectMailbox($folder); } /** * Does the given folder exist? * * @param string $folder The folder to check. * * @return mixed True in case the folder exists, false otherwise */ function exists($folder) { return $this->_imap->mailboxExist($folder); } /** * Create the specified folder. * * @param string $folder The folder to create. * * @return mixed True in case the operation was successfull, a * PEAR error otherwise. */ function create($folder) { return $this->_imap->createMailbox($folder); } /** * Delete the specified folder. * * @param string $folder The folder to delete. * * @return mixed True in case the operation was successfull, a * PEAR error otherwise. */ function delete($folder) { return $this->_imap->deleteMailbox($folder); } /** * Rename the specified folder. * * @param string $old The folder to rename. * @param string $new The new name of the folder. * * @return mixed True in case the operation was successfull, a * PEAR error otherwise. */ function rename($old, $new) { return $this->_imap->renameMailbox($old, $new); } /** * Returns the status of the current folder. * * @return array An array that contains 'uidvalidity' and 'uidnext'. */ function status() { $result = array(); $mailbox = $this->_imap->getCurrentMailbox(); // Net_IMAP is not very efficent here $ret = $this->_imap->cmdStatus($mailbox, 'UIDVALIDITY'); $result['uidvalidity'] = $ret['PARSED']['STATUS']['ATTRIBUTES']['UIDVALIDITY']; $ret = $this->_imap->cmdStatus($mailbox, 'UIDNEXT'); $result['uidnext'] = $ret['PARSED']['STATUS']['ATTRIBUTES']['UIDNEXT']; return $result; } /** * Returns the message ids of the messages in this folder. * * @return array The message ids. */ function getUids() { $uids = $this->_imap->search('UNDELETED', true); if (!is_array($uids)) { $uids = array(); } return $uids; } /** * Searches the current folder using the given list of search criteria. * * @param string $search_list A list of search criteria. * * @return mixed The list of matching message ids or a PEAR error in case * of an error. */ function search($search_list, $uidSearch = true) { return $this->_imap->search($search_list, $uidSearch); } /** * Searches the headers of the messages. * * @param string $field The name of the header field. * @param string $value The value that field should match. * * @return mixed The list of matching message ids or a PEAR error in case * of an error. */ function searchHeaders($field, $value) { return $this->_imap->search('HEADER "' . $field . '" "' . $value . '"', true); } /** * Retrieves the message headers for a given message id. * * @param int $uid The message id. * @param boolean $peek_for_body Prefetch the body. * * @return mixed The message header or a PEAR error in case of an error. */ function getMessageHeader($uid, $peek_for_body = true) { $ret = $this->_imap->cmdUidFetch($uid, 'BODY[HEADER]'); if (String::upper($ret['RESPONSE']['CODE']) != 'OK') { return PEAR::raiseError(sprintf(_("Failed fetching headers of IMAP message %s. Error was %s"), $uid, $ret['RESPONSE']['CODE'] . ', ' . $ret['RESPONSE']['STR_CODE'])); } if (isset($ret['PARSED'])) { foreach ($ret['PARSED'] as $msg) { if (isset($msg['EXT']['BODY[HEADER]']['CONTENT'])) { return $msg['EXT']['BODY[HEADER]']['CONTENT']; } } } return ''; } /** * Retrieves the message body for a given message id. * * @param integet $uid The message id. * * @return mixed The message body or a PEAR error in case of an error. */ function getMessageBody($uid) { $ret = $this->_imap->cmdUidFetch($uid, 'BODY[TEXT]'); if (String::upper($ret['RESPONSE']['CODE']) != 'OK') { return PEAR::raiseError(sprintf(_("Failed fetching body of IMAP message %s. Error was %s"), $uid, $ret['RESPONSE']['CODE'] . ', ' . $ret['RESPONSE']['STR_CODE'])); } if (isset($ret['PARSED'])) { foreach ($ret['PARSED'] as $msg) { if (isset($msg['EXT']['BODY[TEXT]']['CONTENT'])) { return $msg['EXT']['BODY[TEXT]']['CONTENT']; } } } return ''; } /** * Retrieves the full message text for a given message id. * * @param integer $uid The message id. * * @return mixed The message text or a PEAR error in case of an error. */ function getMessage($uid) { $ret = $this->_imap->cmdUidFetch($uid, 'RFC822'); if (String::upper($ret['RESPONSE']['CODE']) != 'OK') { return PEAR::raiseError(sprintf(_("Failed fetching IMAP message %s. Error was %s"), $uid, $ret['RESPONSE']['CODE'] . ', ' . $ret['RESPONSE']['STR_CODE'])); } if (isset($ret['PARSED'])) { foreach ($ret['PARSED'] as $msg) { if (isset($msg['EXT']['RFC822']['CONTENT'])) { return $msg['EXT']['RFC822']['CONTENT']; } } } return ''; } /** * Retrieves a list of mailboxes on the server. * * @return mixed The list of mailboxes or a PEAR error in case of an * error. */ function getMailboxes() { return $this->_imap->getMailboxes(); } /** * Fetches the annotation on a folder. * * @param string $entries The entry to fetch. * @param string $value The specific value to fetch. * @param string $mailbox_name The name of the folder. * * @return mixed The annotation value or a PEAR error in case of an error. */ function getAnnotation($entries, $value, $mailbox_name) { static $annotations = array(); $signature = "$this->_signature|$entries|$value|$mailbox_name"; if (!isset($annotations[$signature])) { $annotations[$signature] = $this->_imap->getAnnotation($entries, $value, $mailbox_name); } return $annotations[$signature]; } /** * Sets the annotation on a folder. * * @param string $entries The entry to set. * @param array $values The values to set * @param string $mailbox_name The name of the folder. * * @return mixed True if successfull, a PEAR error otherwise. */ function setAnnotation($entries, $values, $mailbox_name) { return $this->_imap->setAnnotation($entries, $values, $mailbox_name); } /** * Retrieve the access rights from a folder * * @param string $folder The folder to retrieve the ACLs from. * * @return mixed An array of rights if successfull, a PEAR error * otherwise. */ function getACL($folder) { $result = $this->_imap->getACL($folder); if (is_a($result, 'PEAR_Error')) { return $result; } $acl = array(); foreach ($result as $user) { $acl[$user['USER']] = $user['RIGHTS']; } return $acl; } /** * Retrieve the access rights on a folder not owned by the current user * * @param string $folder The folder to retrieve the ACLs from. * * @return mixed An array of rights if successfull, a PEAR error * otherwise. */ function getMyRights($folder) { $result = $this->_imap->getMyRights($folder); return $result; } /** * Set the access rights for a folder * * @param string $folder The folder to retrieve the ACLs from. * @param string $user The user to set the ACLs for * @param string $acl The ACLs * * @return mixed True if successfull, a PEAR error otherwise. */ function setACL($folder, $user, $acl) { return $this->_imap->setACL($folder, $user, $acl); } /** * Delete the access rights for a user. * * @param string $folder The folder that should be modified. * @param string $user The user that should get the ACLs removed * * @return mixed True if successfull, a PEAR error otherwise. */ function deleteACL($folder, $user) { return $this->_imap->deleteACL($folder, $user); } /** * Appends a message to the current folder. * * @param string $msg The message to append. * * @return mixed True or a PEAR error in case of an error. */ function appendMessage($msg) { return $this->_imap->appendMessage($msg); } /** * Copies a message to a new folder. * * @param integer $uid IMAP message id. * @param string $new_folder Target folder. * * @return mixed True or a PEAR error in case of an error. */ function copyMessage($uid, $new_folder) { $ret = $this->_imap->cmdUidCopy($uid, $new_folder); if (String::upper($ret['RESPONSE']['CODE']) != 'OK') { return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), $uid, $ret['RESPONSE']['CODE'] . ', ' . $ret['RESPONSE']['STR_CODE'])); } return true; } /** * Moves a message to a new folder. * * @param integer $uid IMAP message id. * @param string $new_folder Target folder. * * @return mixed True or a PEAR error in case of an error. */ function moveMessage($uid, $new_folder) { $result = $this->copyMessage($uid, $new_folder); if (is_a($result, 'PEAR_Error')) { return $result; } $result = $this->deleteMessages($uid); if (is_a($result, 'PEAR_Error')) { return $result; } $result = $this->expunge(); if (is_a($result, 'PEAR_Error')) { return $result; } return true; } /** * Deletes messages from the current folder. * * @param integer $uids IMAP message ids. * * @return mixed True or a PEAR error in case of an error. */ function deleteMessages($uids) { if (!is_array($uids)) { $uids = array($uids); } foreach ($uids as $uid) { $ret = $this->_imap->cmdUidStore($uid, '+FLAGS.SILENT', '\Deleted'); if (String::upper($ret['RESPONSE']['CODE']) != 'OK') { return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), $uid, $ret['RESPONSE']['CODE'] . ', ' . $ret['RESPONSE']['STR_CODE'])); } } return true; } /** * Undeletes a message in the current folder. * * @param integer $uid IMAP message id. * * @return mixed True or a PEAR error in case of an error. */ function undeleteMessages($uid) { $ret = $this->_imap->cmdUidStore($uid, '-FLAGS.SILENT', '\Deleted'); if (String::upper($ret['RESPONSE']['CODE']) != 'OK') { return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), $uid, $ret['RESPONSE']['CODE'] . ', ' . $ret['RESPONSE']['STR_CODE'])); } return true; } /** * Expunges messages in the current folder. * * @return mixed True or a PEAR error in case of an error. */ function expunge() { return $this->_imap->expunge(); } /** * Return the currently selected mailbox * * @return string The mailbox currently selected */ function current() { return $this->_imap->getCurrentMailbox(); } } * @package Kolab_Storage */ class Horde_Kolab_IMAP_test extends Horde_Kolab_IMAP { /** * If we are supposed to be connected this holds the user * credentials and some connection details. * * @var string */ var $_connected; /** * Login of the current user * * @var string */ var $_user; /** * The data of the mailbox currently opened * * @var array */ var $_mbox = null; /** * The name of the mailbox currently opened * * @var array */ var $_mboxname = null; /** * Prepare the dummy server. * * @param string $login The user account name. * @param string $password The user password. * @param boolean $tls Should TLS be used for the connection? * * @return mixed True in case the connection was opened successfully, a * PEAR error otherwise. */ function connect($login, $password, $tls = false) { if (!is_array($GLOBALS['KOLAB_TESTING'])) { /* Simulate an empty IMAP server */ $GLOBALS['KOLAB_TESTING'] = array(); } $tls = ($tls) ? 'tls' : 'notls'; $this->_connected = $login . ':' . $password . ':' . $tls; $this->_user = $login; unset($this->_mbox); $this->_mboxname = null; } /** * Disconnects from the IMAP server. * * @return mixed True in case the connection was closed successfully, a * PEAR error otherwise. */ function disconnect() { $this->_connected = null; } function _parseFolder($folder) { if (substr($folder, 0, 5) == 'INBOX') { $user = split('@', $this->_user); return 'user/' . $user[0] . substr($folder, 5); } return $folder; } /** * Opens the given folder. * * @param string $folder The folder to open * * @return mixed True in case the folder was opened successfully, a PEAR * error otherwise. */ function select($folder) { $folder = $this->_parseFolder($folder); if (!isset($GLOBALS['KOLAB_TESTING'][$folder])) { return PEAR::raiseError(sprintf("IMAP folder %s does not exist!", $folder)); } $this->_mbox = &$GLOBALS['KOLAB_TESTING'][$folder]; $this->_mboxname = $folder; return true; } /** * Does the given folder exist? * * @param string $folder The folder to check. * * @return mixed True in case the folder exists, false otherwise */ function exists($folder) { $folder = $this->_parseFolder($folder); if (!isset($GLOBALS['KOLAB_TESTING'][$folder])) { return false; } return true; } /** * Create the specified folder. * * @param string $folder The folder to create. * * @return mixed True in case the operation was successfull, a * PEAR error otherwise. */ function create($folder) { $folder = $this->_parseFolder($folder); if (isset($GLOBALS['KOLAB_TESTING'][$folder])) { return PEAR::raiseError(sprintf("IMAP folder %s does already exist!", $folder)); } $GLOBALS['KOLAB_TESTING'][$folder] = array( 'status' => array( 'uidvalidity' => time(), 'uidnext' => 1), 'mails' => array(), 'permissions' => array(), 'annotations' => array(), ); return true; } /** * Delete the specified folder. * * @param string $folder The folder to delete. * * @return mixed True in case the operation was successfull, a * PEAR error otherwise. */ function delete($folder) { $folder = $this->_parseFolder($folder); if (!isset($GLOBALS['KOLAB_TESTING'][$folder])) { return PEAR::raiseError(sprintf("IMAP folder %s does not exist!", $folder)); } unset($GLOBALS['KOLAB_TESTING'][$folder]); return true; } /** * Rename the specified folder. * * @param string $old The folder to rename. * @param string $new The new name of the folder. * * @return mixed True in case the operation was successfull, a * PEAR error otherwise. */ function rename($old, $new) { $old = $this->_parseFolder($old); $new = $this->_parseFolder($new); if (!isset($GLOBALS['KOLAB_TESTING'][$old])) { return PEAR::raiseError(sprintf("IMAP folder %s does not exist!", $old)); } if (isset($GLOBALS['KOLAB_TESTING'][$new])) { return PEAR::raiseError(sprintf("IMAP folder %s does already exist!", $new)); } $GLOBALS['KOLAB_TESTING'][$new] = $GLOBALS['KOLAB_TESTING'][$old]; unset($GLOBALS['KOLAB_TESTING'][$old]); return true; } /** * Returns the status of the current folder. * * @return array An array that contains 'uidvalidity' and 'uidnext'. */ function status() { if (!$this->_mbox) { return PEAR::raiseError("No IMAP folder selected!"); } return $this->_mbox['status']; } /** * Returns the message ids of the messages in this folder. * * @return array The message ids. */ function getUids() { if (!$this->_mbox) { return PEAR::raiseError("No IMAP folder selected!"); } $uids = array(); foreach ($this->_mbox['mails'] as $uid => $mail) { if (!($mail['flags'] & KOLAB_IMAP_FLAG_DELETED)) { $uids[] = $uid; } } return $uids; } /** * Searches the current folder using the given list of search criteria. * * @param string $search_list A list of search criteria. * * @return mixed The list of matching message ids or a PEAR error in case * of an error. */ function search($search_list, $uidSearch = true) { if (!$this->_mbox) { return PEAR::raiseError("No IMAP folder selected!"); } $uids = array(); if (substr($search_list, 0, 7) == 'SUBJECT') { $needle = '^Subject: ' . substr($search_list, 8); foreach ($this->_mbox['mails'] as $uid => $mail) { if (preg_match($needle, $mail['header'])) { $uids[] = $uid; } } } else if (substr($search_list, 0, 6) == 'HEADER') { preg_match('([^ ]*) ([^ ]*)', substr($search_list, 7), $matches); $needle = '^' . $matches[0] . ': ' . $matches[1]; foreach ($this->_mbox['mails'] as $uid => $mail) { if (preg_match($needle, $mail['header'])) { $uids[] = $uid; } } } return $uids; } /** * Searches the headers of the messages. * * @param string $field The name of the header field. * @param string $value The value that field should match. * * @return mixed The list of matching message ids or a PEAR error in case * of an error. */ function searchHeaders($field, $value) { return $this->search('HEADER "' . $field . '" "' . $value . '"', true); } /** * Retrieves the message headers for a given message id. * * @param int $uid The message id. * @param boolean $peek_for_body Prefetch the body. * * @return mixed The message header or a PEAR error in case of an error. */ function getMessageHeader($uid, $peek_for_body = true) { if (!$this->_mbox) { return PEAR::raiseError("No IMAP folder selected!"); } if (!isset($this->_mbox['mails'][$uid])) { return PEAR::raiseError(sprintf("No IMAP message %s!", $uid)); } return $this->_mbox['mails'][$uid]['header']; } /** * Retrieves the message body for a given message id. * * @param integet $uid The message id. * * @return mixed The message body or a PEAR error in case of an error. */ function getMessageBody($uid) { if (!$this->_mbox) { return PEAR::raiseError("No IMAP folder selected!"); } if (!isset($this->_mbox['mails'][$uid])) { return PEAR::raiseError(sprintf("No IMAP message %s!", $uid)); } return $this->_mbox['mails'][$uid]['body']; } /** * Retrieves the full message text for a given message id. * * @param integer $uid The message id. * * @return mixed The message text or a PEAR error in case of an error. */ function getMessage($uid) { if (!$this->_mbox) { return PEAR::raiseError("No IMAP folder selected!"); } if (!isset($this->_mbox['mails'][$uid])) { return PEAR::raiseError(sprintf("No IMAP message %s!", $uid)); } return $this->_mbox['mails'][$uid]['header'] . $this->_mbox['mails'][$uid]['body']; } /** * Retrieves a list of mailboxes on the server. * * @return mixed The list of mailboxes or a PEAR error in case of an * error. */ function getMailboxes() { $mboxes = array_keys($GLOBALS['KOLAB_TESTING']); $user = split('@', $this->_user); $pattern = '#^user/' . $user[0] . '#'; $result = array(); foreach ($mboxes as $mbox) { $result[] = preg_replace($pattern, 'INBOX', $mbox); } return $result; } /** * Fetches the annotation on a folder. * * @param string $entries The entry to fetch. * @param string $value The specific value to fetch. * @param string $mailbox_name The name of the folder. * * @return mixed The annotation value or a PEAR error in case of an error. */ function getAnnotation($entries, $value, $mailbox_name) { $mailbox_name = $this->_parseFolder($mailbox_name); $old_mbox = null; if ($mailbox_name != $this->_mboxname) { $old_mbox = $this->_mboxname; $result = $this->select($mailbox_name); if (is_a($result, 'PEAR_Error')) { return $result; } } if (!isset($this->_mbox['annotations'][$entries]) || !isset($this->_mbox['annotations'][$entries][$value])) { return false; } $annotation = $this->_mbox['annotations'][$entries][$value]; if ($old_mbox) { $this->select($old_mbox); } return $annotation; } /** * Sets the annotation on a folder. * * @param string $entries The entry to set. * @param array $values The values to set * @param string $mailbox_name The name of the folder. * * @return mixed True if successfull, a PEAR error otherwise. */ function setAnnotation($entries, $values, $mailbox_name) { $mailbox_name = $this->_parseFolder($mailbox_name); $old_mbox = null; if ($mailbox_name != $this->_mboxname) { $old_mbox = $this->_mboxname; $result = $this->select($mailbox_name); if (is_a($result, 'PEAR_Error')) { return $result; } } if (!isset($this->_mbox['annotations'][$entries])) { $this->_mbox['annotations'][$entries] = array(); } foreach ($values as $key => $value) { $this->_mbox['annotations'][$entries][$key] = $value; } if ($old_mbox) { $result = $this->select($old_mbox); if (is_a($result, 'PEAR_Error')) { return $result; } } return true; } /** * Retrieve the access rights from a folder * * @param string $folder The folder to retrieve the ACLs from. * * @return mixed An array of rights if successfull, a PEAR error * otherwise. */ function getACL($folder) { $folder = $this->_parseFolder($folder); $old_mbox = null; if ($folder != $this->_mboxname) { $old_mbox = $this->_mboxname; $result = $this->select($folder); if (is_a($result, 'PEAR_Error')) { return $result; } } $acl = $this->_mbox['permissions']; if ($old_mbox) { $result = $this->select($old_mbox); if (is_a($result, 'PEAR_Error')) { return $result; } } return $acl; } /** * Retrieve the access rights on a folder not owned by the current user * * @param string $folder The folder to retrieve the ACLs from. * * @return mixed An array of rights if successfull, a PEAR error * otherwise. */ function getMyRights($folder) { $folder = $this->_parseFolder($folder); $old_mbox = null; if ($folder != $this->_mboxname) { $old_mbox = $this->_mboxname; $result = $this->select($folder); if (is_a($result, 'PEAR_Error')) { return $result; } } $acl = ''; if (isset($this->_mbox['permissions'][$this->_user])) { $acl = $this->_mbox['permissions'][$this->_user]; } if ($old_mbox) { $result = $this->select($old_mbox); if (is_a($result, 'PEAR_Error')) { return $result; } } return $acl; } /** * Set the access rights for a folder * * @param string $folder The folder to retrieve the ACLs from. * @param string $user The user to set the ACLs for * @param string $acl The ACLs * * @return mixed True if successfull, a PEAR error otherwise. */ function setACL($folder, $user, $acl) { $folder = $this->_parseFolder($folder); $old_mbox = null; if ($folder != $this->_mboxname) { $old_mbox = $this->_mboxname; $result = $this->select($folder); if (is_a($result, 'PEAR_Error')) { return $result; } } $this->_mbox['permissions'][$user] = $acl; if ($old_mbox) { $result = $this->select($old_mbox); if (is_a($result, 'PEAR_Error')) { return $result; } } return true; } /** * Delete the access rights for a user. * * @param string $folder The folder that should be modified. * @param string $user The user that should get the ACLs removed * * @return mixed True if successfull, a PEAR error otherwise. */ function deleteACL($folder, $user) { $folder = $this->_parseFolder($folder); $old_mbox = null; if ($folder != $this->_mboxname) { $old_mbox = $this->_mboxname; $result = $this->select($folder); if (is_a($result, 'PEAR_Error')) { return $result; } } unset($this->_mbox['permissions'][$user]); if ($old_mbox) { $result = $this->select($old_mbox); if (is_a($result, 'PEAR_Error')) { return $result; } } return true; } /** * Appends a message to the current folder. * * @param string $msg The message to append. * * @return mixed True or a PEAR error in case of an error. */ function appendMessage($msg) { $split = strpos($msg, "\r\n\r\n"); $mail = array('header' => substr($msg, 0, $split + 2), 'body' => substr($msg, $split + 3)); return $this->_appendMessage($mail); } /** * Appends a message to the current folder. * * @param array $msg The message to append. * * @return mixed True or a PEAR error in case of an error. */ function _appendMessage($msg) { if (!$this->_mbox) { return PEAR::raiseError("No IMAP folder selected!"); } $mail = array(); $mail['flags'] = 0; $mail['header'] = $msg['header']; $mail['body'] = $msg['body']; $this->_mbox['mails'][$this->_mbox['status']['uidnext']] = $mail; $this->_mbox['status']['uidnext']++; return true; } /** * Copies a message to a new folder. * * @param integer $uid IMAP message id. * @param string $new_folder Target folder. * * @return mixed True or a PEAR error in case of an error. */ function copyMessage($uid, $new_folder) { $new_folder = $this->_parseFolder($new_folder); if (!$this->_mbox) { return PEAR::raiseError("No IMAP folder selected!"); } if (!isset($this->_mbox['mails'][$uid])) { return PEAR::raiseError(sprintf("No IMAP message %s!", $uid)); } $mail = $this->_mbox['mails'][$uid]; $old_mbox = null; $result = $this->select($new_folder); if (is_a($result, 'PEAR_Error')) { return $result; } $this->_appendMessage($mail); if ($old_mbox) { $result = $this->select($old_mbox); if (is_a($result, 'PEAR_Error')) { return $result; } } return true; } /** * Moves a message to a new folder. * * @param integer $uid IMAP message id. * @param string $new_folder Target folder. * * @return mixed True or a PEAR error in case of an error. */ function moveMessage($uid, $new_folder) { $new_folder = $this->_parseFolder($new_folder); if (!$this->_mbox) { return PEAR::raiseError("No IMAP folder selected!"); } if (!isset($this->_mbox['mails'][$uid])) { return PEAR::raiseError(sprintf("No IMAP message %s!", $uid)); } $mail = $this->_mbox['mails'][$uid]; unset($this->_mbox['mails'][$uid]); $old_mbox = null; $result = $this->select($new_folder); if (is_a($result, 'PEAR_Error')) { return $result; } $this->_appendMessage($mail); if ($old_mbox) { $result = $this->select($old_mbox); if (is_a($result, 'PEAR_Error')) { return $result; } } return true; } /** * Deletes messages from the current folder. * * @param integer $uids IMAP message ids. * * @return mixed True or a PEAR error in case of an error. */ function deleteMessages($uids) { if (!$this->_mbox) { return PEAR::raiseError("No IMAP folder selected!"); } if (!is_array($uids)) { $uids = array($uids); } foreach ($uids as $uid) { if (!isset($this->_mbox['mails'][$uid])) { return PEAR::raiseError(sprintf("No IMAP message %s!", $uid)); } $this->_mbox['mails'][$uid]['flags'] |= KOLAB_IMAP_FLAG_DELETED; } return true; } /** * Undeletes a message in the current folder. * * @param integer $uid IMAP message id. * * @return mixed True or a PEAR error in case of an error. */ function undeleteMessages($uid) { if (!$this->_mbox) { return PEAR::raiseError("No IMAP folder selected!"); } if (!isset($this->_mbox['mails'][$uid])) { return PEAR::raiseError(sprintf("No IMAP message %s!", $uid)); } $this->_mbox['mails'][$uid]['flags'] &= ~KOLAB_IMAP_FLAG_DELETED; return true; } /** * Expunges messages in the current folder. * * @return mixed True or a PEAR error in case of an error. */ function expunge() { if (!$this->_mbox) { return PEAR::raiseError("No IMAP folder selected!"); } $remaining = array(); foreach ($this->_mbox['mails'] as $uid => $mail) { if (!($mail['flags'] & KOLAB_IMAP_FLAG_DELETED)) { $remaining[$uid] = $mail; } } $this->_mbox['mails'] = $remaining; return true; } /** * Return the currently selected mailbox * * @return string The mailbox currently selected */ function current() { return $this->_mboxname; } } * @author Thomas Jarosch * @package Kolab_Storage */ class Horde_Kolab_IMAP { /** * IMAP server to connect to. * * @var string */ var $_server; /** * IMAP server port to connect to. * * @var int */ var $_port; /** * IMAP connection. * * @var mixed */ var $_imap; /** * Connection reuse detection signature. * * @var string */ var $_reuse_detection; /** * Constructor. * * @param string $server Server to connect to * @param int $port Port to connect to */ function Horde_Kolab_IMAP($server, $port) { $this->_server = $server; $this->_port = $port; } /** * Attempts to return a reference to a concrete Horde_Kolab_IMAP instance. * It will only create a new instance if no Horde_Kolab_IMAP instance * exists. * * @static * * @param string $server Server name * @param int $port Port * @param boolean $annotation_required Do we actually need * the annotation calls? * * @return Horde_Kolab_IMAP|PEAR_Error The concrete reference. */ function &singleton($server, $port, $annotation_required = true) { static $instances = array(); /** * There are Kolab specific PHP functions available that make the IMAP * access more efficient. If these are detected, or if they are not * required for the current operation, the PHP IMAP implementation * should be used. * * The c-client Kolab driver provides quicker IMAP routines so is * preferable whenever possible. */ if ($annotation_required) { if (function_exists('imap_status_current') && function_exists('imap_getannotation')) { $driver = 'cclient'; } else { $driver = 'pear'; } } else { $driver = 'cclient'; } if (isset($GLOBALS['KOLAB_TESTING'])) { $driver = 'test'; } $signature = "$server|$port|$driver"; if (!isset($instances[$signature])) { $instances[$signature] = &Horde_Kolab_IMAP::factory($server, $port, $driver); } return $instances[$signature]; } /** * Attempts to return a concrete Horde_Kolab_IMAP instance based on the * available PHP functionality. * * @param string $server Server name. * @param int $port Server port. * @param string $driver Which driver should we use? * * @return Horde_Kolab_IMAP|PEAR_Error The newly created concrete * Horde_Kolab_IMAP instance. */ function &factory($server, $port, $driver = 'cclient') { @include_once dirname(__FILE__) . '/IMAP/' . $driver . '.php'; $class = 'Horde_Kolab_IMAP_' . $driver; if (class_exists($class)) { $driver = &new $class($server, $port); } else { return PEAR::raiseError(sprintf(_("Failed to load Kolab IMAP driver %s"), $driver)); } return $driver; } } * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ class Horde_Kolab_Resource_Availability { public function isFree($resource, $object, $dtstart, $dtend, $storage, $ignore, $freebusyfuture) { $freebusyfuture = $freebusyfuture * 60 * 60 * 24; //@ŧodo additional class list($vfb, $vfbstart, $vfbend, $evfbstart, $evfbend) = $this->getFreeBusyData($resource); if ($evfbend->getEpoch() < $dtend && $dtend < time() + $freebusyfuture && $evfbend->getEpoch() < time() + $freebusyfuture - 24 * 60 * 60) { Horde::logMessage('Triggering resource to generate updated freebusy information', __FILE__, __LINE__, PEAR_LOG_NOTICE); $storage->trigger(); list($vfb, $vfbstart, $vfbend, $evfbstart, $evfbend) = $this->getFreeBusyData($resource); } if ($vfbstart && $dtend > $evfbend->getEpoch()) { Horde::logMessage('No freebusy information available', __FILE__, __LINE__, PEAR_LOG_NOTICE); throw new Horde_Kolab_Resource_Exception_NotBookable(); } // Check whether we are busy or not $busyperiods = $vfb->getBusyPeriods(); Horde::logMessage(sprintf('Busyperiods: %s', print_r($busyperiods, true)), __FILE__, __LINE__, PEAR_LOG_DEBUG); $extraparams = $vfb->getExtraParams(); Horde::logMessage(sprintf('Extraparams: %s', print_r($extraparams, true)), __FILE__, __LINE__, PEAR_LOG_DEBUG); $conflict = false; if (!empty($object['recurrence'])) { $recurrence = new Horde_Date_Recurrence($dtstart); $recurrence->fromHash($object['recurrence']); $duration = $dtend - $dtstart; $events = array(); $next_start = $vfbstart; $next = $recurrence->nextActiveRecurrence($vfbstart); while ($next !== false && $next->compareDate($vfbend) <= 0) { $next_ts = $next->timestamp(); $events[$next_ts] = $next_ts + $duration; $next = $recurrence->nextActiveRecurrence(array('year' => $next->year, 'month' => $next->month, 'mday' => $next->mday + 1, 'hour' => $next->hour, 'min' => $next->min, 'sec' => $next->sec)); } } else { $events = array($dtstart => $dtend); } foreach ($events as $dtstart => $dtend) { Horde::logMessage(sprintf('Requested event from %s to %s', strftime('%a, %d %b %Y %H:%M:%S %z', $dtstart), strftime('%a, %d %b %Y %H:%M:%S %z', $dtend) ), __FILE__, __LINE__, PEAR_LOG_DEBUG); foreach ($busyperiods as $busyfrom => $busyto) { if (empty($busyfrom) && empty($busyto)) { continue; } Horde::logMessage(sprintf('Busy period from %s to %s', strftime('%a, %d %b %Y %H:%M:%S %z', $busyfrom), strftime('%a, %d %b %Y %H:%M:%S %z', $busyto) ), __FILE__, __LINE__, PEAR_LOG_DEBUG); if ((isset($extraparams[$busyfrom]['X-UID']) && in_array(base64_decode($extraparams[$busyfrom]['X-UID']), $ignore)) || (isset($extraparams[$busyfrom]['X-SID']) && in_array(base64_decode($extraparams[$busyfrom]['X-SID']), $ignore))) { // Ignore continue; } if (($busyfrom >= $dtstart && $busyfrom < $dtend) || ($dtstart >= $busyfrom && $dtstart < $busyto)) { Horde::logMessage('Request overlaps', __FILE__, __LINE__, PEAR_LOG_DEBUG); return false; } } } return true; } public function getFreeBusyData($resource) { require_once 'Horde/Kolab/Resource/Freebusy.php'; $fb = Horde_Kolab_Resource_Freebusy::singleton(); $vfb = $fb->get($resource); $vfbstart = $vfb->getAttributeDefault('DTSTART', 0); $evfbstart = new Horde_Kolab_Resource_Epoch($vfbstart); $vfbend = $vfb->getAttributeDefault('DTEND', 0); $evfbend = new Horde_Kolab_Resource_Epoch($vfbend); Horde::logMessage(sprintf('Free/busy info starts on <%s> %s and ends on <%s> %s', $vfbstart, $evfbstart->iCalDate2Kolab(), $vfbend, $evfbend->iCalDate2Kolab()), __FILE__, __LINE__, PEAR_LOG_DEBUG); return array($vfb, $vfbstart, $vfbend, $evfbstart, $evfbend); } } * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ class Horde_Kolab_Resource_Data { /** * Constructor. * * @param string $sender The sender address * @param string $resource The resource */ public function fetch($sender, $resource) { require_once 'Horde/Kolab/Server.php'; $db = Horde_Kolab_Server::singleton(); if ($db instanceOf PEAR_Error) { $db->code = OUT_LOG | EX_SOFTWARE; return $db; } $dn = $db->uidForMail($resource, KOLAB_SERVER_RESULT_MANY); if ($dn instanceOf PEAR_Error) { $dn->code = OUT_LOG | EX_NOUSER; return $dn; } if (is_array($dn)) { if (count($dn) > 1) { Horde::logMessage(sprintf("%s objects returned for %s", $count($dn), $resource), __FILE__, __LINE__, PEAR_LOG_WARNING); return false; } else { $dn = $dn[0]; } } $user = $db->fetch($dn, KOLAB_OBJECT_USER); $cn = $user->get(KOLAB_ATTR_CN); $id = $user->get(KOLAB_ATTR_MAIL); $hs = $user->get(KOLAB_ATTR_HOMESERVER); if (is_a($hs, 'PEAR_Error')) { return $hs; } $hs = strtolower($hs); $actions = $user->get(KOLAB_ATTR_IPOLICY); if (is_a($actions, 'PEAR_Error')) { $actions->code = OUT_LOG | EX_UNAVAILABLE; return $actions; } if ($actions === false) { $actions = array(RM_ACT_MANUAL); } $fbfuture = $user->get(KOLAB_ATTR_FBFUTURE); if (is_a($fbfuture, 'PEAR_Error')) { $fbfuture = null; } $policies = array(); $defaultpolicy = false; foreach ($actions as $action) { if (preg_match('/(.*):(.*)/', $action, $regs)) { $policies[strtolower($regs[1])] = $regs[2]; } else { $defaultpolicy = $action; } } // Find sender's policy if (array_key_exists($sender, $policies)) { // We have an exact match, stop processing $action = $policies[$sender]; } else { $action = false; $dn = $db->uidForMailOrAlias($sender); if (is_a($dn, 'PEAR_Error')) { $dn->code = OUT_LOG | EX_NOUSER; return $dn; } if ($dn) { // Sender is local, check for groups foreach ($policies as $gid => $policy) { if ($db->memberOfGroupAddress($dn, $gid)) { // User is member of group if (!$action) { $action = $policy; } else { $action = min($action, $policy); } } } } if (!$action && $defaultpolicy) { $action = $defaultpolicy; } } return array('cn' => $cn, 'id' => $id, 'homeserver' => $hs, 'action' => $action, 'fbfuture' => $fbfuture); } } * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** * Handles Date conversion for the resource handler. * * Copyright 2004-2010 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. * * @category Kolab * @package Kolab_Filter * @author Steffen Hansen * @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_Resource_Epoch { /** * The date to be converted. * * @var mixed */ private $_date; /** * Constructor. * * @param mixed $date The date to be converted. */ public function __construct($date) { $this->_date = $date; } /** * Clear information from a date array. * * @param array $ical_date The array to clear. * * @return array The cleaned array. */ private function cleanArray($ical_date) { if (!array_key_exists('hour', $ical_date)) { $temp['DATE'] = '1'; } $temp['hour'] = array_key_exists('hour', $ical_date) ? $ical_date['hour'] : '00'; $temp['minute'] = array_key_exists('minute', $ical_date) ? $ical_date['minute'] : '00'; $temp['second'] = array_key_exists('second', $ical_date) ? $ical_date['second'] : '00'; $temp['year'] = array_key_exists('year', $ical_date) ? $ical_date['year'] : '0000'; $temp['month'] = array_key_exists('month', $ical_date) ? $ical_date['month'] : '00'; $temp['mday'] = array_key_exists('mday', $ical_date) ? $ical_date['mday'] : '00'; $temp['zone'] = array_key_exists('zone', $ical_date) ? $ical_date['zone'] : 'UTC'; return $temp; } /** * Convert a date to an epoch. * * @param array $values The array to convert. * * @return int Time. */ private function convert2epoch($values) { Horde::logMessage(sprintf('Converting to epoch %s', print_r($values, true)), __FILE__, __LINE__, PEAR_LOG_DEBUG); if (is_array($values)) { $temp = $this->cleanArray($values); $epoch = gmmktime($temp['hour'], $temp['minute'], $temp['second'], $temp['month'], $temp['mday'], $temp['year']); } else { $epoch=$values; } Horde::logMessage(sprintf('Converted <%s>', $epoch), __FILE__, __LINE__, PEAR_LOG_DEBUG); return $epoch; } public function getEpoch() { return $this->convert2Epoch($this->_date); } /** * Convert iCal dates to Kolab format. * * An all day event must have a dd--mm-yyyy notation and not a * yyyy-dd-mmT00:00:00z notation Otherwise the event is shown as a * 2-day event --> do not try to convert everything to epoch first * * @param array $ical_date The array to convert. * @param string $type The type of the date to convert. * * @return string The converted date. */ function iCalDate2Kolab($type= ' ') { Horde::logMessage(sprintf('Converting to kolab format %s', print_r($this->_date, true)), __FILE__, __LINE__, PEAR_LOG_DEBUG); // $this->_date should be a timestamp if (is_array($this->_date)) { // going to create date again $temp = $this->cleanArray($this->_date); if (array_key_exists('DATE', $temp)) { if ($type == 'ENDDATE') { $etemp = new Horde_Kolab_Resource_Epoch($temp); // substract a day (86400 seconds) using epochs to take number of days per month into account $epoch= $etemp->getEpoch() - 86400; $date = gmstrftime('%Y-%m-%d', $epoch); } else { $date= sprintf('%04d-%02d-%02d', $temp['year'], $temp['month'], $temp['mday']); } } else { $time = sprintf('%02d:%02d:%02d', $temp['hour'], $temp['minute'], $temp['second']); if ($temp['zone'] == 'UTC') { $time .= 'Z'; } $date = sprintf('%04d-%02d-%02d', $temp['year'], $temp['month'], $temp['mday']) . 'T' . $time; } } else { $date = gmstrftime('%Y-%m-%dT%H:%M:%SZ', $this->_date); } Horde::logMessage(sprintf('To <%s>', $date), __FILE__, __LINE__, PEAR_LOG_DEBUG); return $date; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ /** * Indicates that the requested resource is not bookable in the targeted time * span. * * Copyright 2009-2010 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_Resource * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ class Horde_Kolab_Resource_Exception_NotBookable extends Horde_Kolab_Resource_Exception { } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ /** * This class provides the standard error class for Kolab_Resource exceptions. * * Copyright 2009-2010 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_Resource * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ class Horde_Kolab_Resource_Exception extends Exception { /** * Constants to define the error type. */ const SYSTEM = 1; const NO_FREEBUSY = 2; } INDX( v(exbiiP%4Rp Availability.phppZiiP%4Rp AVAILA~1.PHPhRiiP%6wq Data.phphTiiP%r. Epoch.phphTP%P%P%P% Exceptionp\iiP%r Exception.phphRP%P%P%P%EXCEPT~1pZiiP%r EXCEPT~1.PHPhRP%P%P%P%FrebusypZii6%is8 Freebusy.php`J6%6%6%6%ItiphRii6%f[ua Itip.phphRii6%Gw Lock.phphTii6%7{  Reply.phphXii6%9{H Request.phphXii6%1| Storage.php * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** * Retrieves free/busy data for an email address on a Kolab server. * * Copyright 2004-2009 Klarälvdalens Datakonsult AB * * See the enclosed file COPYING for license information (LGPL>=2.1). If you * did not receive this file, * see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @category Kolab * @package Kolab_Filter * @author Steffen Hansen * @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_Resource_Freebusy_Kolab extends Horde_Kolab_Resource_Freebusy { /** * Retrieve Free/Busy URL for the specified resource id. * * @param string $resource The id of the resource (usually a mail address). * * @return string The Free/Busy URL for that resource. */ protected function getUrl($resource) { $server = Horde_Kolab_Server::singleton(); $uid = $server->uidForMailAddress($resource); $result = $server->fetch($uid)->getServer('freebusy'); return sprintf('%s/%s.xfb', $result, $resource); } /** * Retrieve Free/Busy data for the specified resource. * * @param string $resource Fetch the Free/Busy data for this resource. * * @return Horde_iCalendar_vfreebusy The Free/Busy data. */ public function get($resource) { global $conf; $url = self::getUrl($resource); Horde::logMessage(sprintf('Freebusy URL for resource %s is %s', $resource, $url), __FILE__, __LINE__, PEAR_LOG_DEBUG); list($user, $domain) = explode('@', $resource); if (empty($domain)) { $domain = $conf['kolab']['filter']['email_domain']; } /** * This section matches Kronolith_Freebusy and should be merged with it * again in a single Horde_Freebusy module. */ $options = array( 'method' => 'GET', 'timeout' => 5, 'allowRedirects' => true ); if (!empty($conf['http']['proxy']['proxy_host'])) { $options = array_merge($options, $conf['http']['proxy']); } $http = new HTTP_Request($url, $options); $http->setBasicAuth($conf['kolab']['filter']['calendar_id'] . '@' . $domain, $conf['kolab']['filter']['calendar_pass']); @$http->sendRequest(); if ($http->getResponseCode() != 200) { throw new Horde_Kolab_Resource_Exception(sprintf('Unable to retrieve free/busy information for %s', $resource), Horde_Kolab_Resource_Exception::NO_FREEBUSY); } $vfb_text = $http->getResponseBody(); // Detect the charset of the iCalendar data. $contentType = $http->getResponseHeader('Content-Type'); if ($contentType && strpos($contentType, ';') !== false) { list(,$charset,) = explode(';', $contentType); $charset = trim(str_replace('charset=', '', $charset)); } else { $charset = 'UTF-8'; } $iCal = new Horde_iCalendar; $iCal->parsevCalendar($vfb_text, 'VCALENDAR', $charset); $vfb = &$iCal->findComponent('VFREEBUSY'); if ($vfb === false) { throw new Horde_Kolab_Resource_Exception(sprintf('Invalid or no free/busy information available for %s', $resource), Horde_Kolab_Resource_Exception::NO_FREEBUSY); } $vfb->simplify(); return $vfb; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** * Retrieves free/busy mockup data. * * Copyright 2004-2009 Klarälvdalens Datakonsult AB * * See the enclosed file COPYING for license information (LGPL>=2.1). If you * did not receive this file, * see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @category Kolab * @package Kolab_Filter * @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_Resource_Freebusy_Mock extends Horde_Kolab_Resource_Freebusy { /** * Retrieve Free/Busy URL for the specified resource id. * * @param string $resource The id of the resource (usually a mail address). * * @return string The Free/Busy URL for that resource. */ protected function getUrl($resource) { return ''; } /** * Retrieve Free/Busy data for the specified resource. * * @param string $resource Fetch the Free/Busy data for this resource * (usually a mail address). * * @return Horde_iCalendar_vfreebusy The Free/Busy data. */ public function get($resource) { return $this->_params['data']; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** * Retrieves free/busy data for an email address. * * Copyright 2004-2010 Klarälvdalens Datakonsult AB * * See the enclosed file COPYING for license information (LGPL>=2.1). If you * did not receive this file, * see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @category Kolab * @package Kolab_Filter * @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_Resource_Freebusy { /** * Singleton instances. * * @var array */ static protected $_instances = array(); /** * Class parameters. * * @var array */ protected $_params; /** * Constructor. * * @param array $params A hash containing any additional configuration or * connection parameters a subclass might need. */ protected function __construct($params) { $this->_params = $params; } /** * Attempts to return a concrete Horde_Kolab_Resource_Getfreebusy instance * based on $driver. * * @param mixed $driver The type of concrete * Horde_Kolab_Resource_Getfreebusy subclass to * return. * @param array $params A hash containing any additional configuration or * connection parameters a subclass might need. * * @return Horde_Kolab_Resource_Getfreebusy The newly created concrete * Horde_Kolab_Resource_Getfreebusy * instance, or false an error. */ static public function factory($driver, $params = array()) { $driver = ucfirst(basename($driver)); $class = ($driver == 'None') ? 'Horde_Kolab_Resource_Freebusy' : 'Horde_Kolab_Resource_Freebusy_' . $driver; require_once dirname(__FILE__) . '/Freebusy/' . $driver . '.php'; if (!class_exists($class)) { $class = 'Horde_Kolab_Resource_Freebusy'; } return new $class($params); } /** * Attempts to return a reference to a concrete * Horde_Kolab_Resource_Getfreebusy instance based on $driver. * * It will only create a new instance if no Horde_Kolab_Resource_Getfreebusy * instance with the same parameters currently exists. * * This method must be invoked as: * $var = Horde_Kolab_Resource_Getfreebusy::singleton(); * * @param mixed $driver The type of concrete * Horde_Kolab_Resource_Getfreebusy subclass to * return. * @param array $params A hash containing any additional configuration or * connection parameters a subclass might need. * * @return Horde_Token The concrete Horde_Kolab_Resource_Getfreebusy * reference, or false on error. */ static public function singleton($driver = null, $params = array()) { global $conf; if (isset($GLOBALS['KOLAB_FILTER_TESTING'])) { $driver = 'mock'; $params['data'] = $GLOBALS['KOLAB_FILTER_TESTING']; } if (empty($driver)) { if (isset($conf['freebusy']['driver'])) { $driver = $conf['freebusy']['driver']; } else { $driver = 'Kolab'; } } ksort($params); $sig = hash('md5', serialize(array($driver, $params))); if (!isset(self::$_instances[$sig])) { self::$_instances[$sig] = Horde_Kolab_Resource_Freebusy::factory($driver, $params); } return self::$_instances[$sig]; } /** * Retrieve Free/Busy URL for the specified resource id. * * @param string $resource The id of the resource (usually a mail address). * * @return string The Free/Busy URL for that resource. */ protected function getUrl($resource) { return ''; } /** * Retrieve Free/Busy data for the specified resource. * * @param string $resource Fetch the Free/Busy data for this resource * (usually a mail address). * * @return Horde_iCalendar_vfreebusy The Free/Busy data. */ public function get($resource) { /* Return an empty VFB object. */ $vCal = new Horde_iCalendar(); $vFb = Horde_iCalendar::newComponent('vfreebusy', $vCal); $vFb->setAttribute('ORGANIZER', $resource); return $vFb; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** * A wrapper for vEvent iCalender data. * * Copyright 2010 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. * * @category Kolab * @package Kolab_Filter * @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_Resource_Itip_Event_Vevent implements Horde_Kolab_Resource_Itip_Event { /** * The wrapped vEvent. * * @var Horde_iCalendar_vevent */ private $_vevent; /** * Constructor. * * @param Horde_iCalendar_vevent $vevent The iCalendar object that will be * wrapped by this instance. */ public function __construct(Horde_iCalendar_vevent $vevent) { $this->_vevent = $vevent; } /** * Returns the wrapped vEvent. * * @return Horde_iCalendar_vevent The wrapped event. */ public function getVevent() { return $this->_vevent; } /** * Return the method of the iTip request. * * @return string The method of the request. */ public function getMethod() { return $this->_vevent->getAttributeDefault('METHOD', 'REQUEST'); } /** * Return the uid of the iTip event. * * @return string The uid of the event. */ public function getUid() { return $this->_vevent->getAttributeDefault('UID', ''); } /** * Return the summary for the event. * * @return string|PEAR_Error The summary. */ public function getSummary() { return $this->_vevent->getAttributeDefault('SUMMARY', _("No summary available")); } /** * Return the start of the iTip event. * * @return string The start of the event. */ public function getStart() { return $this->_vevent->getAttributeDefault('DTSTART', 0); } /** * Return the end of the iTip event. * * @return string The end of the event. */ public function getEnd() { return $this->_vevent->getAttributeDefault('DTEND', 0); } /** * Return the organizer of the iTip event. * * @return string The organizer of the event. */ public function getOrganizer() { return preg_replace('/^mailto:\s*/i', '', $this->_vevent->getAttributeDefault('ORGANIZER', '')); } /** * Copy the details from an event into this one. * * @param Horde_Kolab_Resource_Itip_Event $event The event to copy from. * * @return NULL */ public function copyEventInto(Horde_Kolab_Resource_Itip_Event $event) { $this->copyUid($event); $this->copySummary($event); $this->copyDescription($event); $this->copyStart($event); $this->copyEndOrDuration($event); $this->copySequence($event); $this->copyLocation($event); $this->copyOrganizer($event); } /** * Set the attendee parameters. * * @param string $attendee The mail address of the attendee. * @param string $common_name Common name of the attendee. * @param string $status Attendee status (ACCPETED, DECLINED, TENTATIVE) * * @return NULL */ public function setAttendee($attendee, $common_name, $status) { $this->_vevent->setAttribute( 'ATTENDEE', 'MAILTO:' . $attendee, array( 'CN' => $common_name, 'PARTSTAT' => $status ) ); } public function getKolabObject() { $object = array(); $object['uid'] = $this->getUid(); $org_params = $this->_vevent->getAttribute('ORGANIZER', true); if (!is_a( $org_params, 'PEAR_Error')) { if (!empty($org_params[0]['CN'])) { $object['organizer']['display-name'] = $org_params[0]['CN']; } $orgemail = $this->_vevent->getAttributeDefault('ORGANIZER', ''); if (preg_match('/mailto:(.*)/i', $orgemail, $regs )) { $orgemail = $regs[1]; } $object['organizer']['smtp-address'] = $orgemail; } $object['summary'] = $this->_vevent->getAttributeDefault('SUMMARY', ''); $object['location'] = $this->_vevent->getAttributeDefault('LOCATION', ''); $object['body'] = $this->_vevent->getAttributeDefault('DESCRIPTION', ''); $dtend = $this->_vevent->getAttributeDefault('DTEND', ''); if (is_array($dtend)) { $object['_is_all_day'] = true; } $start = new Horde_Kolab_Resource_Epoch($this->getStart()); $object['start-date'] = $start->getEpoch(); $end = new Horde_Kolab_Resource_Epoch($dtend); $object['end-date'] = $end->getEpoch(); $attendees = $this->_vevent->getAttribute('ATTENDEE'); if (!is_a( $attendees, 'PEAR_Error')) { $attendees_params = $this->_vevent->getAttribute('ATTENDEE', true); if (!is_array($attendees)) { $attendees = array($attendees); } if (!is_array($attendees_params)) { $attendees_params = array($attendees_params); } $object['attendee'] = array(); for ($i = 0; $i < count($attendees); $i++) { $attendee = array(); if (isset($attendees_params[$i]['CN'])) { $attendee['display-name'] = $attendees_params[$i]['CN']; } $attendeeemail = $attendees[$i]; if (preg_match('/mailto:(.*)/i', $attendeeemail, $regs)) { $attendeeemail = $regs[1]; } $attendee['smtp-address'] = $attendeeemail; if (!isset($attendees_params[$i]['RSVP']) || $attendees_params[$i]['RSVP'] == 'FALSE') { $attendee['request-response'] = false; } else { $attendee['request-response'] = true; } if (isset($attendees_params[$i]['ROLE'])) { $attendee['role'] = $attendees_params[$i]['ROLE']; } if (isset($attendees_params[$i]['PARTSTAT'])) { $status = strtolower($attendees_params[$i]['PARTSTAT']); switch ($status) { case 'needs-action': case 'delegated': $attendee['status'] = 'none'; break; default: $attendee['status'] = $status; break; } } $object['attendee'][] = $attendee; } } // Alarm $valarm = $this->_vevent->findComponent('VALARM'); if ($valarm) { $trigger = $valarm->getAttribute('TRIGGER'); if (!is_a($trigger, 'PEAR_Error')) { $p = $valarm->getAttribute('TRIGGER', true); if ($trigger < 0) { // All OK, enter the alarm into the XML // NOTE: The Kolab XML format seems underspecified // wrt. alarms currently... $object['alarm'] = -$trigger / 60; } } } // Recurrence $rrule_str = $this->_vevent->getAttribute('RRULE'); if (!is_a($rrule_str, 'PEAR_Error')) { require_once 'Horde/Date/Recurrence.php'; $recurrence = new Horde_Date_Recurrence(time()); $recurrence->fromRRule20($rrule_str); $object['recurrence'] = $recurrence->toHash(); } return $object; } public function setAccepted($resource) { // Update our status within the iTip request and send the reply $this->_vevent->setAttribute('STATUS', 'CONFIRMED', array(), false); $attendees = $this->_vevent->getAttribute('ATTENDEE'); if (!is_array($attendees)) { $attendees = array($attendees); } $attparams = $this->_vevent->getAttribute('ATTENDEE', true); foreach ($attendees as $i => $attendee) { $attendee = preg_replace('/^mailto:\s*/i', '', $attendee); if ($attendee != $resource) { continue; } $attparams[$i]['PARTSTAT'] = 'ACCEPTED'; if (array_key_exists('RSVP', $attparams[$i])) { unset($attparams[$i]['RSVP']); } } // Re-add all the attendees to the event, using our updates status info $firstatt = array_pop($attendees); $firstattparams = array_pop($attparams); $this->_vevent->setAttribute('ATTENDEE', $firstatt, $firstattparams, false); foreach ($attendees as $i => $attendee) { $this->_vevent->setAttribute('ATTENDEE', $attendee, $attparams[$i]); } } /** * Set the uid of the iTip event. * * @param string $uid The uid of the event. * * @return NULL */ private function setUid($uid) { $this->_vevent->setAttribute('UID', $uid); } /** * Copy the uid from the request into the provided iTip instance. * * @return NULL */ private function copyUid(Horde_Kolab_Resource_Itip_Event $itip) { $itip->setUid($this->getUid()); } /** * Set the summary for the event. * * @param string $summary The summary. * * @return NULL */ private function setSummary($summary) { $this->_vevent->setAttribute('SUMMARY', $summary); } /** * Copy the summary from the request into the provided iTip instance. * * @return NULL */ private function copySummary(Horde_Kolab_Resource_Itip_Event $itip) { $itip->setSummary($this->getSummary()); } /** * Does the event have a description? * * @return boolean True if it has a description, false otherwise. */ private function hasDescription() { return !($this->_vevent->getAttribute('DESCRIPTION') instanceOf PEAR_Error); } /** * Return the description for the event. * * @return string|PEAR_Error The description. */ private function getDescription() { return $this->_vevent->getAttribute('DESCRIPTION'); } /** * Set the description for the event. * * @param string $description The description. * * @return NULL */ private function setDescription($description) { $this->_vevent->setAttribute('DESCRIPTION', $description); } /** * Copy the description from the request into the provided iTip instance. * * @return NULL */ private function copyDescription(Horde_Kolab_Resource_Itip_Event $itip) { if ($this->hasDescription()) { $itip->setDescription($this->getDescription()); } } /** * Return the start parameters of the iTip event. * * @return array The start parameters of the event. */ public function getStartParameters() { return array_pop($this->_vevent->getAttribute('DTSTART', true)); } /** * Set the start of the iTip event. * * @param string $start The start of the event. * @param array $parameters Additional parameters. * * @return NULL */ private function setStart($start, $parameters) { $this->_vevent->setAttribute('DTSTART', $start, $parameters); } /** * Copy the start time from the request into the provided iTip instance. * * @return NULL */ private function copyStart(Horde_Kolab_Resource_Itip_Event $itip) { $itip->setStart($this->getStart(), $this->getStartParameters()); } /** * Does the event have an end? * * @return boolean True if it has an end, false otherwise. */ private function hasEnd() { return !($this->_vevent->getAttribute('DTEND') instanceOf PEAR_Error); } /** * Return the end parameters of the iTip event. * * @return array The end parameters of the event. */ private function getEndParameters() { return array_pop($this->_vevent->getAttribute('DTEND', true)); } /** * Set the end of the iTip event. * * @param string $end The end of the event. * @param array $parameters Additional parameters. * * @return NULL */ private function setEnd($end, $parameters) { $this->_vevent->setAttribute('DTEND', $end, $parameters); } /** * Return the duration for the event. * * @return string|PEAR_Error The duration of the event. */ private function getDuration() { return $this->_vevent->getAttribute('DURATION'); } /** * Return the duration parameters of the iTip event. * * @return array The duration parameters of the event. */ private function getDurationParameters() { return array_pop($this->_vevent->getAttribute('DURATION', true)); } /** * Set the duration of the iTip event. * * @param string $duration The duration of the event. * @param array $parameters Additional parameters. * * @return NULL */ private function setDuration($duration, $parameters) { $this->_vevent->setAttribute('DURATION', $duration, $parameters); } /** * Copy the end time or event duration from the request into the provided * iTip instance. * * @return NULL */ private function copyEndOrDuration(Horde_Kolab_Resource_Itip_Event $itip) { if ($this->hasEnd()) { $itip->setEnd($this->getEnd(), $this->getEndParameters()); } else { $itip->setDuration($this->getDuration(), $this->getDurationParameters()); } } /** * Return the sequence for the event. * * @return string|PEAR_Error The sequence. */ private function getSequence() { return $this->_vevent->getAttributeDefault('SEQUENCE', 0); } /** * Set the sequence for the event. * * @param string $sequence The sequence. * * @return NULL */ private function setSequence($sequence) { $this->_vevent->setAttribute('SEQUENCE', $sequence); } /** * Copy the sequence from the request into the provided iTip instance. * * @return NULL */ private function copySequence(Horde_Kolab_Resource_Itip_Event $itip) { $itip->setSequence($this->getSequence()); } /** * Does the event have a location? * * @return boolean True if it has a location, false otherwise. */ private function hasLocation() { return !($this->_vevent->getAttribute('LOCATION') instanceOf PEAR_Error); } /** * Return the location for the event. * * @return string|PEAR_Error The location. */ private function getLocation() { return $this->_vevent->getAttribute('LOCATION'); } /** * Set the location for the event. * * @param string $location The location. * * @return NULL */ private function setLocation($location) { $this->_vevent->setAttribute('LOCATION', $location); } /** * Copy the location from the request into the provided iTip instance. * * @return NULL */ private function copyLocation(Horde_Kolab_Resource_Itip_Event $itip) { if ($this->hasLocation()) { $itip->setLocation($this->getLocation()); } } /** * Return the organizer for the event. * * @return string|PEAR_Error The organizer of the event. */ private function getRawOrganizer() { return $this->_vevent->getAttribute('ORGANIZER'); } /** * Return the organizer parameters of the iTip event. * * @return array The organizer parameters of the event. */ private function getOrganizerParameters() { return array_pop($this->_vevent->getAttribute('ORGANIZER', true)); } /** * Set the organizer of the iTip event. * * @param string $organizer The organizer of the event. * @param array $parameters Additional parameters. * * @return NULL */ private function setOrganizer($organizer, $parameters) { $this->_vevent->setAttribute('ORGANIZER', $organizer, $parameters); } /** * Copy the organizer from the request into the provided iTip instance. * * @return NULL */ private function copyOrganizer(Horde_Kolab_Resource_Itip_Event $itip) { $itip->setOrganizer($this->getRawOrganizer(), $this->getOrganizerParameters()); } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** * Defines the event interface required for iTip-Handling / resource booking. * * Copyright 2010 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. * * @category Kolab * @package Kolab_Filter * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ interface Horde_Kolab_Resource_Itip_Event { /** * Returns the event as vEvent. * * @return Horde_iCalendar_vevent The wrapped event. */ public function getVevent(); /** * Return the method of the iTip request. * * @return string The method of the request. */ public function getMethod(); /** * Return the uid of the iTip event. * * @return string The uid of the event. */ public function getUid(); /** * Return the summary for the event. * * @return string The summary. */ public function getSummary(); /** * Return the start of the iTip event. * * @return string The start of the event. */ public function getStart(); /** * Return the end of the iTip event. * * @return string The end of the event. */ public function getEnd(); /** * Return the organizer of the iTip event. * * @return string The organizer of the event. */ public function getOrganizer(); /** * Copy the details from an event into this one. * * @param Horde_Kolab_Resource_Itip_Event $event The event to copy from. * * @return NULL */ public function copyEventInto(Horde_Kolab_Resource_Itip_Event $event); /** * Set the attendee parameters. * * @param string $attendee The mail address of the attendee. * @param string $common_name Common name of the attendee. * @param string $status Attendee status (ACCPETED, DECLINED, TENTATIVE) * * @return NULL */ public function setAttendee($attendee, $common_name, $status); public function getKolabObject(); public function setAccepted($resource); } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ /** * This class provides the standard error class for Horde_Kolab_Resource_Itip * exceptions. * * Copyright 2010 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_Resource * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ class Horde_Kolab_Resource_Itip_Exception extends Exception { } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ /** * Simple information provider for an invited resource. * * Copyright 2010 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. * * @category Kolab * @package Kolab_Resource * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ class Horde_Kolab_Resource_Itip_Resource_Base implements Horde_Kolab_Resource_Itip_Resource { /** * The mail address. * * @var string */ private $_mail; /** * The common name. * * @var string */ private $_common_name; /** * Constructor. * * @param string $mail The mail address. * @param string $common_name The common name. */ public function __construct($mail, $common_name) { $this->_mail = $mail; $this->_common_name = $common_name; } /** * Retrieve the mail address of the resource. * * @return string The mail address. */ public function getMailAddress() { return $this->_mail; } /** * Retrieve the common name of the resource. * * @return string The common name. */ public function getCommonName() { return $this->_common_name; } /** * Retrieve the "From" address for this resource. * * @return string The "From" address. */ public function getFrom() { return sprintf("%s <%s>", $this->_common_name, $this->_mail); } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ /** * Provides information about an invited resource. * * Copyright 2010 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. * * @category Kolab * @package Kolab_Resource * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ interface Horde_Kolab_Resource_Itip_Resource { /** * Retrieve the mail address of the resource. * * @return string The mail address. */ public function getMailAddress(); /** * Retrieve the common name of the resource. * * @return string The common name. */ public function getCommonName(); /** * Retrieve the "From" address for this resource. * * @return string The "From" address. */ public function getFrom(); }INDX( -v(`L6%6%6%6%EventhTii6%Pr9 Event.phpp\ii6%r Exception.phppZii6%r EXCEPT~1.PHPhR6%6%6%6%ResourcepZii6% * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** * Indicates an accepted invitation. * * Copyright 2010 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. * * @category Kolab * @package Kolab_Filter * @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_Resource_Itip_Response_Type_Accept extends Horde_Kolab_Resource_Itip_Response_Type_Base { /** * Return the status of the response. * * @return string The status. */ public function getStatus() { return 'ACCEPTED'; } /** * Return the abbreviated subject of the response. * * @return string The short subject. */ public function getShortSubject() { return _("Accepted"); } /** * Return the short message for the response. * * @param boolean $is_update Indicates if the request was an update. * * @return string The short message. */ public function getShortMessage($is_update = false) { return $is_update ? _("has accepted the update to the following event") : _("has accepted the invitation to the following event"); } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** * Basic iTip response type definition. * * Copyright 2010 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. * * @category Kolab * @package Kolab_Filter * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ abstract class Horde_Kolab_Resource_Itip_Response_Type_Base implements Horde_Kolab_Resource_Itip_Response_Type { /** * The request we are going to answer. * * @var Horde_Kolab_Resource_Itip_Event */ private $_request; /** * Set the request. * * @param Horde_Kolab_Resource_Itip_Event $request The request this * instance will respond * to. * * @return NULL */ public function setRequest( Horde_Kolab_Resource_Itip_Event $request ) { $this->_request = $request; } /** * Get the request for this response. * * @return Horde_Kolab_Resource_Itip_Event The request this instance will * respond to. * * @throws Horde_Kolab_Resource_Itip_Exception If the request has not been * set yet. */ public function getRequest() { if (empty($this->_request)) { throw new Horde_Kolab_Resource_Itip_Exception( 'The iTip request is still undefined!' ); } return $this->_request; } /** * Return the subject of the response. * * @param string $comment An optional comment that should appear in the * response subject. * * @return string The subject. */ public function getSubject($comment = null) { if ($comment === null) { return sprintf( '%s: %s', $this->getShortSubject(), $this->getRequest()->getSummary() ); } else { return sprintf( '%s [%s]: %s', $this->getShortSubject(), $comment, $this->getRequest()->getSummary() ); } } /** * Return an additional message for the response. * * @param boolean $is_update Indicates if the request was an update. * @param string $comment An optional comment that should appear in the * response message. * * @return string The message. */ public function getMessage($is_update = false, $comment = null) { if ($comment === null) { return sprintf( "%s %s:\n\n%s", $this->getShortMessage($update), $this->getRequest()->getSummary() ); } else { return sprintf( "%s %s:\n\n%s\n\n%s", $this->getShortMessage($update), $this->getRequest()->getSummary(), $comment ); } } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** * Indicates a declined invitation. * * Copyright 2010 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. * * @category Kolab * @package Kolab_Filter * @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_Resource_Itip_Response_Type_Decline extends Horde_Kolab_Resource_Itip_Response_Type_Base { /** * Return the status of the response. * * @return string The status. */ public function getStatus() { return 'DECLINED'; } /** * Return the abbreviated subject of the response. * * @return string The short subject. */ public function getShortSubject() { return _("Declined"); } /** * Return the short message for the response. * * @param boolean $is_update Indicates if the request was an update. * * @return string The short message. */ public function getShortMessage($is_update = false) { return $is_update ? _("has declined the update to the following event") : _("has declined the invitation to the following event"); } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** * Indicates a tentatively accepted invitation. * * Copyright 2010 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. * * @category Kolab * @package Kolab_Filter * @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_Resource_Itip_Response_Type_Tentative extends Horde_Kolab_Resource_Itip_Response_Type_Base { /** * Return the status of the response. * * @return string The status. */ public function getStatus() { return 'TENTATIVE'; } /** * Return the abbreviated subject of the response. * * @return string The short subject. */ public function getShortSubject() { return _("Tentative"); } /** * Return the short message for the response. * * @param boolean $is_update Indicates if the request was an update. * * @return string The short message. */ public function getShortMessage($is_update = false) { return $is_update ? _("has tentatively accepted the update to the following event") : _("has tentatively accepted the invitation to the following event"); } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** * Marks the response type. * * Copyright 2010 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. * * @category Kolab * @package Kolab_Filter * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ interface Horde_Kolab_Resource_Itip_Response_Type { /** * Return the status of the response. * * @return string The status. */ public function getStatus(); /** * Return the abbreviated subject of the response. * * @return string The short subject. */ public function getShortSubject(); /** * Return the subject of the response. * * @param string $comment An optional comment that should appear in the * response subject. * * @return string The subject. */ public function getSubject($comment = null); /** * Return an additional message for the response. * * @param boolean $is_update Indicates if the request was an update. * @param string $comment An optional comment that should appear in the * response message. * * @return string The message. */ public function getMessage($is_update = false, $comment = null); } * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ /** * Handles Itip response data. * * Copyright 2004-2010 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. * * @category Kolab * @package Kolab_Resource * @author Steffen Hansen * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ class Horde_Kolab_Resource_Itip_Response { /** * The request we are going to answer. * * @var Horde_Kolab_Resource_Itip_Event */ private $_request; /** * The requested resource. * * @var Horde_Kolab_Resource_Itip_Resource */ private $_resource; /** * The status type of this response. * * @var Horde_Kolab_Resource_Itip_Response_Type */ private $_type; /** * Constructor. * * @param Horde_Kolab_Resource_Itip_Event $request The request this * instance will respond * to. * @param Horde_Kolab_Resource_Itip_Resource $resource The requested * resource. */ public function __construct( Horde_Kolab_Resource_Itip_Event $request, Horde_Kolab_Resource_Itip_Resource $resource ) { $this->_request = $request; $this->_resource = $resource; } /** * Return the response as an iCalendar vEvent object. * * @param Horde_Kolab_Resource_Itip_Response_Type $type The response type. * @param Horde_iCalendar|boolean $vCal The parent container * or false if not * provided. * * @return Horde_iCalendar_vevent The response object. */ public function getVevent( Horde_Kolab_Resource_Itip_Response_Type $type, $vCal = false ) { $itip_reply = new Horde_Kolab_Resource_Itip_Event_Vevent( Horde_iCalendar::newComponent('VEVENT', $vCal) ); $this->_request->copyEventInto($itip_reply); $type->setRequest($this->_request); $itip_reply->setAttendee( $this->_resource->getMailAddress(), $this->_resource->getCommonName(), $type->getStatus() ); return $itip_reply->getVevent(); } /** * Return the response as an iCalendar object. * * @param Horde_Kolab_Resource_Itip_Response_Type $type The response * type. * @param string $product_id The ID that * should be set * as the iCalendar * product id. * * @return Horde_iCalendar The response object. */ public function getIcalendar( Horde_Kolab_Resource_Itip_Response_Type $type, $product_id ) { $vCal = new Horde_iCalendar(); $vCal->setAttribute('PRODID', $product_id); $vCal->setAttribute('METHOD', 'REPLY'); $vCal->addComponent($this->getVevent($type, $vCal)); return $vCal; } /** * Return the response as a MIME message. * * @param Horde_Kolab_Resource_Itip_Response_Type $type The response * type. * @param string $product_id The ID that * should be set * as the iCalendar * product id. * @param string $subject_comment An optional comment on the subject line. * * @return array A list of two object: The mime headers and the mime * message. */ public function getMessage( Horde_Kolab_Resource_Itip_Response_Type $type, $product_id, $subject_comment = null ) { $ics = new MIME_Part( 'text/calendar', $this->getIcalendar($type, $product_id)->exportvCalendar(), 'UTF-8' ); $ics->setContentTypeParameter('method', 'REPLY'); //$mime->addPart($body); //$mime->addPart($ics); // The following was ::convertMimePart($mime). This was removed so that we // send out single-part MIME replies that have the iTip file as the body, // with the correct mime-type header set, etc. The reason we want to do this // is so that Outlook interprets the messages as it does Outlook-generated // responses, i.e. double-clicking a reply will automatically update your // meetings, showing different status icons in the UI, etc. $message = MIME_Message::convertMimePart($ics); $message->setCharset('UTF-8'); $message->setTransferEncoding('quoted-printable'); $message->transferEncodeContents(); // Build the reply headers. $headers = new MIME_Headers(); $headers->addHeader('Date', date('r')); $headers->addHeader('From', $this->_resource->getFrom()); $headers->addHeader('To', $this->_request->getOrganizer()); $headers->addHeader( 'Subject', $type->getSubject($subject_comment) ); $headers->addMIMEHeaders($message); return array($headers, $message); } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ /** * Handles iTip invitation requests/responses. * * Copyright 2010 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. * * @category Kolab * @package Kolab_Resource * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ class Horde_Kolab_Resource_Itip { /** * The iTip response. * * @var Horde_Kolab_Resource_Itip_Response */ private $_response; /** * Constructor. * * @param Horde_Kolab_Resource_Itip_Response $response The iTip response. */ public function __construct( Horde_Kolab_Resource_Itip_Response $response ) { $this->_response = $response; } /** * Return the response as an iCalendar vEvent object. * * @param Horde_Kolab_Resource_Itip_Response_Type $type The response type. * * @return Horde_iCalendar_vevent The response object. */ public function getVeventResponse( Horde_Kolab_Resource_Itip_Response_Type $type ) { return $this->_response->getVevent( $type, false ); } /** * Return the response as an iCalendar object. * * @param Horde_Kolab_Resource_Itip_Response_Type $type The response * type. * @param string $product_id The ID that * should be set * as the iCalendar * product id. * * @return Horde_iCalendar The response object. */ public function getIcalendarResponse( Horde_Kolab_Resource_Itip_Response_Type $type, $product_id ) { return $this->_response->getIcalendar( $type, $product_id ); } /** * Return the response as a MIME message. * * @param Horde_Kolab_Resource_Itip_Response_Type $type The response * type. * @param string $product_id The ID that * should be set * as the iCalendar * product id. * @param string $subject_comment An optional comment on the subject line. * * @return array A list of two object: The mime headers and the mime * message. */ public function getMessageResponse( Horde_Kolab_Resource_Itip_Response_Type $type, $product_id, $subject_comment = null ) { return $this->_response->getMessage( $type, $product_id, $subject_comment ); } /** * Factory for generating a response object for an iCalendar invitation. * * @param Horde_iCalendar_vevent $vevent The iCalendar request. * @param Horde_Kolab_Resource_Itip_Resource $resource The invited resource. * * @return Horde_Kolab_Resource_Itip_Response The prepared response. */ static public function prepareResponse( Horde_iCalendar_vevent $vevent, Horde_Kolab_Resource_Itip_Resource $resource ) { return new Horde_Kolab_Resource_Itip_Response( new Horde_Kolab_Resource_Itip_Event_Vevent( $vevent ), $resource ); } /** * Factory for generating an iTip handler for an iCalendar invitation. * * @param Horde_iCalendar_vevent $vevent The iCalendar request. * @param Horde_Kolab_Resource_Itip_Resource $resource The invited resource. * * @return Horde_Kolab_Resource_Itip The iTip handler. */ static public function factory( Horde_iCalendar_vevent $vevent, Horde_Kolab_Resource_Itip_Resource $resource ) { return new Horde_Kolab_Resource_Itip( self::prepareResponse($vevent, $resource) ); } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ /** * Handles resource locking. * * Copyright 2009-2010 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. * * @package Kolab_Filter * @author Steffen Hansen * @author Gunnar Wrobel */ class Horde_Kolab_Resource_Lock { public function isLocked($resource) { global $conf; if (!empty($conf['kolab']['filter']['simple_locks'])) { if (!empty($conf['kolab']['filter']['simple_locks_timeout'])) { $timeout = $conf['kolab']['filter']['simple_locks_timeout']; } else { $timeout = 60; } if (!empty($conf['kolab']['filter']['simple_locks_dir'])) { $lockdir = $conf['kolab']['filter']['simple_locks_dir']; } else { $lockdir = Horde::getTempDir() . '/Kolab_Filter_locks'; if (!is_dir($lockdir)) { mkdir($lockdir, 0700); } } if (is_dir($lockdir)) { $lockfile = $lockdir . '/' . $resource . '.lock'; $counter = 0; while ($counter < $timeout && file_exists($lockfile)) { sleep(1); $counter++; } if ($counter == $timeout) { Horde::logMessage(sprintf('Lock timeout of %s seconds exceeded. Rejecting invitation.', $timeout), __FILE__, __LINE__, PEAR_LOG_ERR); return true; } $result = file_put_contents($lockfile, 'LOCKED'); if ($result === false) { Horde::logMessage(sprintf('Failed creating lock file %s.', $lockfile), __FILE__, __LINE__, PEAR_LOG_ERR); } else { $this->lockfile = $lockfile; } } else { Horde::logMessage(sprintf('The lock directory %s is missing. Disabled locking.', $lockdir), __FILE__, __LINE__, PEAR_LOG_ERR); } } return false; } /** * Helper function to clean up after handling an invitation * * @return NULL */ function cleanup() { if (!empty($this->lockfile)) { @unlink($this->lockfile); if (file_exists($this->lockfile)) { Horde::logMessage(sprintf('Failed removing the lockfile %s.', $lockfile), __FILE__, __LINE__, PEAR_LOG_ERR); } $this->lockfile = null; } } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** * Represents a reply for an iTip inviation. * * Copyright 2004-2010 Klarälvdalens Datakonsult AB * * See the enclosed file COPYING for license information (LGPL>=2.1). If you * did not receive this file, * see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * * @category Kolab * @package Kolab_Filter * @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_Resource_Reply { /** * Sender of the iTip reply. * * @var string */ protected $_sender; /** * Recipient of the iTip reply. * * @var string */ protected $_recipient; /** * Reply headers. * * @var MIME_Headers */ protected $_headers; /** * Reply body. * * @var MIME_Message */ protected $_body; /** * Constructor. * * @param string $sender Sender of the iTip reply. * @param string $recipient Recipient of the iTip reply. * @param MIME_Headers $headers Reply headers. * @param MIME_Message $body Reply body. */ public function __construct( $sender, $recipient, MIME_Headers $headers, MIME_Message $body ) { $this->_sender = $sender; $this->_recipient = MIME::encodeAddress($recipient); $this->_headers = $headers; $this->_body = $body; } public function getSender() { return $this->_sender; } public function getRecipient() { return $this->_recipient; } public function getData() { return $this->_headers->toString() . '\r\n\r\n' . $this->_body->toString(); } } * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ class Horde_Kolab_Resource_Request { function getICalendar($filename) { $requestText = ''; $handle = fopen($filename, 'r'); while (!feof($handle)) { $requestText .= fread($handle, 8192); } $mime = &MIME_Structure::parseTextMIMEMessage($requestText); $parts = $mime->contentTypeMap(); foreach ($parts as $mimeid => $conttype) { if ($conttype == 'text/calendar') { $part = $mime->getPart($mimeid); $iCalendar = new Horde_iCalendar(); $iCalendar->parsevCalendar($part->transferDecode()); return $iCalendar; } } // No iCal found return false; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ /** * Access to the resource storage backend. * * Copyright 2004-2010 Klarälvdalens Datakonsult AB * Copyright 2010 Kolab Systems AG * * 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. * * @package Kolab_Filter * @author Steffen Hansen * @author Gunnar Wrobel */ class Horde_Kolab_Resource_Storage { /** * The ID of the resource. * * @var string */ private $_resource_id; /** * An error in case opening the resource did not work. * * @var PEAR_Error */ private $_error; /** * The link to the Kolab storage folder handler. * * @var Kolab_Folder */ private $_folder; /** * The link to the Kolab storage data handler. * * @var Kolab_Data */ private $_data; /** * Constructor. * * @param string $resource_id The ID of the resource the class should * manage. */ public function __construct($resource_id) { $this->_resource_id = $resource_id; } public function getFolder() { global $conf; // Handle virtual domains list($user, $domain) = explode('@', $this->_resource_id); if (empty($domain)) { $domain = $conf['kolab']['filter']['email_domain']; } $calendar_user = $conf['kolab']['filter']['calendar_id'] . '@' . $domain; /* Load the authentication libraries */ require_once "Horde/Auth.php"; require_once 'Horde/Secret.php'; $auth = &Auth::singleton(isset($conf['auth']['driver'])?$conf['auth']['driver']:'kolab'); $authenticated = $auth->authenticate($calendar_user, array('password' => $conf['kolab']['filter']['calendar_pass']), false); if (is_a($authenticated, 'PEAR_Error')) { $authenticated->code = OUT_LOG | EX_UNAVAILABLE; return $authenticated; } if (!$authenticated) { return PEAR::raiseError(sprintf('Failed to authenticate as calendar user: %s', $auth->getLogoutReasonString()), OUT_LOG | EX_UNAVAILABLE); } @session_start(); $_SESSION['__auth'] = array( 'authenticated' => true, 'userId' => $calendar_user, 'timestamp' => time(), 'credentials' => Secret::write(Secret::getKey('auth'), serialize(array('password' => $conf['kolab']['filter']['calendar_pass']))), 'remote_addr' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null, ); /* Kolab IMAP handling */ require_once 'Horde/Kolab/Storage/List.php'; $list = &Kolab_List::singleton(); $default = $list->getForeignDefault($this->_resource_id, 'event'); if (!$default || is_a($default, 'PEAR_Error')) { $default = new Kolab_Folder(); $default->setList($list); $default->setName($conf['kolab']['filter']['calendar_store']); //FIXME: The calendar user needs access here $attributes = array('default' => true, 'type' => 'event', 'owner' => $this->_resource_id); $result = $default->save($attributes); if (is_a($result, 'PEAR_Error')) { $result->code = OUT_LOG | EX_UNAVAILABLE; return $result; } } if ($default instanceOf PEAR_Error) { $this->_error = $default; return; } if (!$default->exists()) { $this->_error = PEAR::raiseError( 'Error, could not open calendar folder!', OUT_LOG | EX_TEMPFAIL ); return; } $this->_folder = $default; $data = $default->getData(); if ($data instanceOf PEAR_Error) { $this->_error = $data; return; } $this->_data = $data; } public function failed() { if ($this->_error instanceOf PEAR_Error) { return $this->_error->getMessage(); } return false; } public function objectUidExists($uid) { if (!$this->failed()) { return $this->_data->objectUidExists($uid); } return false; } public function save($object, $old_uid) { return $this->_data->save($object, $old_uid); } public function delete($uid) { return $this->_data->delete($uid); } public function trigger() { return $this->_folder->trigger(); } } * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Resource */ /** Load the iCal handling */ require_once 'Horde/iCalendar.php'; /** Load MIME handlers */ require_once 'Horde/MIME.php'; require_once 'Horde/MIME/Message.php'; require_once 'Horde/MIME/Headers.php'; require_once 'Horde/MIME/Part.php'; require_once 'Horde/MIME/Structure.php'; /** Load Kolab_Resource elements */ require_once 'Horde/Kolab/Resource/Availability.php'; require_once 'Horde/Kolab/Resource/Data.php'; require_once 'Horde/Kolab/Resource/Epoch.php'; require_once 'Horde/Kolab/Resource/Exception.php'; require_once 'Horde/Kolab/Resource/Exception/NotBookable.php'; require_once 'Horde/Kolab/Resource/Itip/Exception.php'; require_once 'Horde/Kolab/Resource/Itip/Response.php'; require_once 'Horde/Kolab/Resource/Itip/Response/Type.php'; require_once 'Horde/Kolab/Resource/Itip/Response/Type/Base.php'; require_once 'Horde/Kolab/Resource/Itip/Response/Type/Accept.php'; require_once 'Horde/Kolab/Resource/Itip/Response/Type/Decline.php'; require_once 'Horde/Kolab/Resource/Itip/Response/Type/Tentative.php'; require_once 'Horde/Kolab/Resource/Itip/Resource.php'; require_once 'Horde/Kolab/Resource/Itip/Resource/Base.php'; require_once 'Horde/Kolab/Resource/Itip/Event.php'; require_once 'Horde/Kolab/Resource/Itip/Event/Vevent.php'; require_once 'Horde/Kolab/Resource/Lock.php'; require_once 'Horde/Kolab/Resource/Reply.php'; require_once 'Horde/Kolab/Resource/Request.php'; require_once 'Horde/Kolab/Resource/Storage.php'; require_once 'Horde/Kolab/Resource/Freebusy.php'; require_once 'Horde/String.php'; String::setDefaultCharset('utf-8'); // What actions we can take when receiving an event request define('RM_ACT_ALWAYS_ACCEPT', 'ACT_ALWAYS_ACCEPT'); define('RM_ACT_REJECT_IF_CONFLICTS', 'ACT_REJECT_IF_CONFLICTS'); define('RM_ACT_MANUAL_IF_CONFLICTS', 'ACT_MANUAL_IF_CONFLICTS'); define('RM_ACT_MANUAL', 'ACT_MANUAL'); define('RM_ACT_ALWAYS_REJECT', 'ACT_ALWAYS_REJECT'); // What possible ITIP notification we can send define('RM_ITIP_DECLINE', 1); define('RM_ITIP_ACCEPT', 2); define('RM_ITIP_TENTATIVE', 3); /** * Provides Kolab resource handling * * Copyright 2004-2010 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. * * @package Kolab_Filter * @author Steffen Hansen * @author Gunnar Wrobel */ class Kolab_Resource { function handleMessage($fqhostname, $sender, $resource, $tmpfname) { global $conf; $data = new Horde_Kolab_Resource_Data(); $rdata = $data->fetch($sender, $resource); if (is_a($rdata, 'PEAR_Error')) { return $rdata; } else if ($rdata === false) { /* No data, probably not a local user */ return true; } else if ($rdata['homeserver'] && $rdata['homeserver'] != $fqhostname) { /* Not the users homeserver, ignore */ return true; } $cn = $rdata['cn']; $id = $rdata['id']; if (isset($rdata['action'])) { $action = $rdata['action']; } else { // Manual is the only safe default! $action = RM_ACT_MANUAL; } Horde::logMessage(sprintf('Action for %s is %s', $sender, $action), __FILE__, __LINE__, PEAR_LOG_DEBUG); // Get out as early as possible if manual if ($action == RM_ACT_MANUAL) { Horde::logMessage(sprintf('Passing through message to %s', $id), __FILE__, __LINE__, PEAR_LOG_INFO); return true; } /* Get the iCalendar data (i.e. the iTip request) */ $request = new Horde_Kolab_Resource_Request(); $iCalendar = $request->getICalendar($tmpfname); if ($iCalendar === false) { // No iCal in mail Horde::logMessage(sprintf('Could not parse iCalendar data, passing through to %s', $id), __FILE__, __LINE__, PEAR_LOG_INFO); return true; } // Get the event details out of the iTip request $itip = &$iCalendar->findComponent('VEVENT'); if ($itip === false) { Horde::logMessage(sprintf('No VEVENT found in iCalendar data, passing through to %s', $id), __FILE__, __LINE__, PEAR_LOG_INFO); return true; } $itip = new Horde_Kolab_Resource_Itip_Event_Vevent($itip); // What is the request's method? i.e. should we create a new event/cancel an // existing event, etc. $method = strtoupper( $iCalendar->getAttributeDefault( 'METHOD', $itip->getMethod() ) ); // What resource are we managing? Horde::logMessage(sprintf('Processing %s method for %s', $method, $id), __FILE__, __LINE__, PEAR_LOG_DEBUG); // This is assumed to be constant across event creation/modification/deletipn $uid = $itip->getUid(); Horde::logMessage(sprintf('Event has UID %s', $uid), __FILE__, __LINE__, PEAR_LOG_DEBUG); // Who is the organiser? $organiser = $itip->getOrganizer(); Horde::logMessage(sprintf('Request made by %s', $organiser), __FILE__, __LINE__, PEAR_LOG_DEBUG); // What is the events summary? $summary = $itip->getSummary(); $estart = new Horde_Kolab_Resource_Epoch($itip->getStart()); $dtstart = $estart->getEpoch(); $eend = new Horde_Kolab_Resource_Epoch($itip->getEnd()); $dtend = $eend->getEpoch(); Horde::logMessage(sprintf('Event starts on <%s> %s and ends on <%s> %s.', $dtstart, $estart->iCalDate2Kolab(), $dtend, $eend->iCalDate2Kolab()), __FILE__, __LINE__, PEAR_LOG_DEBUG); if ($action == RM_ACT_ALWAYS_REJECT) { if ($method == 'REQUEST') { Horde::logMessage(sprintf('Rejecting %s method', $method), __FILE__, __LINE__, PEAR_LOG_INFO); return $this->sendITipReply($cn, $resource, $itip, RM_ITIP_DECLINE, $organiser, $uid, $is_update); } else { Horde::logMessage(sprintf('Passing through %s method for ACT_ALWAYS_REJECT policy', $method), __FILE__, __LINE__, PEAR_LOG_INFO); return true; } } $is_update = false; $ignore = array(); $storage = new Horde_Kolab_Resource_Storage($id); $storage->getFolder(); if ($storage->failed() && $action == RM_ACT_MANUAL_IF_CONFLICTS) { Horde::logMessage(sprintf('Failed accessing IMAP calendar: %s', $storage->failed()), __FILE__, __LINE__, PEAR_LOG_ERR); return true; } switch ($method) { case 'REQUEST': if ($action == RM_ACT_MANUAL) { Horde::logMessage(sprintf('Passing through %s method', $method), __FILE__, __LINE__, PEAR_LOG_INFO); break; } if (!$storage->objectUidExists($uid)) { $old_uid = null; } else { $old_uid = $uid; $ignore[] = $uid; $is_update = true; } /** Generate the Kolab object */ $object = $itip->getKolabObject(); Horde::logMessage(sprintf('Assembled event object: %s', print_r($object, true)), __FILE__, __LINE__, PEAR_LOG_DEBUG); // Don't even bother checking free/busy info if RM_ACT_ALWAYS_ACCEPT // is specified if ($action != RM_ACT_ALWAYS_ACCEPT) { $availability = new Horde_Kolab_Resource_Availability(); try { if (isset($rdata['fbfuture']) && $rdata['fbfuture'] !== null) { $fbfuture = $rdata['fbfuture']; } else { $fbfuture = $conf['kolab']['freebusy']['future_days']; } if (!$availability->isFree($resource, $object, $dtstart, $dtend, $storage, $ignore, $fbfuture)) { if ($action == RM_ACT_MANUAL_IF_CONFLICTS) { Horde::logMessage('Conflict detected; Passing mail through', __FILE__, __LINE__, PEAR_LOG_INFO); return true; } else if ($action == RM_ACT_REJECT_IF_CONFLICTS) { Horde::logMessage('Conflict detected; rejecting', __FILE__, __LINE__, PEAR_LOG_INFO); return $this->sendITipReply($cn, $id, $itip, RM_ITIP_DECLINE, $organiser, $uid, $is_update); } } } catch (Horde_Kolab_Resource_Exception_NotBookable $e) { if ($action == RM_ACT_MANUAL_IF_CONFLICTS) { Horde::logMessage('Invitation outside bookable period; Passing mail through', __FILE__, __LINE__, PEAR_LOG_INFO); return true; } return $this->sendITipReply( $cn, $resource, $itip, RM_ITIP_DECLINE, $organiser, $uid, $is_update, _("outside bookable period") ); /* } catch (Exception $e) { */ /* return PEAR::raiseError($e->getMessage(), */ /* OUT_LOG | EX_UNAVAILABLE); */ } } if ($storage->failed()) { Horde::logMessage('Could not access users calendar; rejecting', __FILE__, __LINE__, PEAR_LOG_INFO); return $this->sendITipReply($cn, $id, $itip, RM_ITIP_DECLINE, $organiser, $uid, $is_update); } // At this point there was either no conflict or RM_ACT_ALWAYS_ACCEPT // was specified; either way we add the new event & send an 'ACCEPT' // iTip reply Horde::logMessage(sprintf('Adding event %s', $uid), __FILE__, __LINE__, PEAR_LOG_INFO); $this->locking = new Horde_Kolab_Resource_Lock(); if ($this->locking->isLocked($resource)) { return $this->sendITipReply($cn, $id, $itip, RM_ITIP_DECLINE, $organiser, $uid, $is_update); } $itip->setAccepted($resource); $result = $storage->save($itip->getKolabObject(), $old_uid); if (is_a($result, 'PEAR_Error')) { $result->code = OUT_LOG | EX_UNAVAILABLE; return $result; } return $this->sendITipReply( $cn, $resource, $itip, RM_ITIP_ACCEPT, $organiser, $uid, $is_update ); case 'CANCEL': Horde::logMessage(sprintf('Removing event %s', $uid), __FILE__, __LINE__, PEAR_LOG_INFO); if ($storage->failed()) { $body = sprintf(_("Unable to access %s's calendar:"), $resource) . "\n\n" . $summary; $subject = sprintf(_("Error processing \"%s\""), $summary); } else if (!$storage->objectUidExists($uid)) { Horde::logMessage(sprintf('Canceled event %s is not present in %s\'s calendar', $uid, $resource), __FILE__, __LINE__, PEAR_LOG_WARNING); $body = sprintf(_("The following event that was canceled is not present in %s's calendar:"), $resource) . "\n\n" . $summary; $subject = sprintf(_("Error processing \"%s\""), $summary); } else { /** * Delete the messages from IMAP * Delete any old events that we updated */ Horde::logMessage(sprintf('Deleting %s because of cancel', $uid), __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $storage->delete($uid); if (is_a($result, 'PEAR_Error')) { Horde::logMessage(sprintf('Deleting %s failed with %s', $uid, $result->getMessage()), __FILE__, __LINE__, PEAR_LOG_DEBUG); } $body = _("The following event has been successfully removed:") . "\n\n" . $summary; $subject = sprintf(_("%s has been cancelled"), $summary); } Horde::logMessage(sprintf('Sending confirmation of cancelation to %s', $organiser), __FILE__, __LINE__, PEAR_LOG_WARNING); $body = new MIME_Part('text/plain', String::wrap($body, 76, "\n", 'utf-8'), 'utf-8'); $mime = &MIME_Message::convertMimePart($body); $mime->setTransferEncoding('quoted-printable'); $mime->transferEncodeContents(); // Build the reply headers. $msg_headers = new MIME_Headers(); $msg_headers->addHeader('Date', date('r')); $msg_headers->addHeader('From', $resource); $msg_headers->addHeader('To', $organiser); $msg_headers->addHeader('Subject', $subject); $msg_headers->addMIMEHeaders($mime); $reply = new Horde_Kolab_Resource_Reply( $resource, $organiser, $msg_headers, $mime ); Horde::logMessage('Successfully prepared cancellation iTip reply', __FILE__, __LINE__, PEAR_LOG_DEBUG); return $reply; default: // We either don't currently handle these iTip methods, or they do not // apply to what we're trying to accomplish here Horde::logMessage(sprintf('Ignoring %s method and passing message through to %s', $method, $resource), __FILE__, __LINE__, PEAR_LOG_INFO); return true; } } /** * Helper function to clean up after handling an invitation * * @return NULL */ function cleanup() { if (!empty($this->locking)) { $this->locking->cleanup(); } } /** * Send an automated reply. * * @param string $cn Common name to be used in the iTip * response. * @param string $resource Resource we send the reply for. * @param string $Horde_iCalendar_vevent The iTip information. * @param int $type Type of response. * @param string $organiser The event organiser. * @param string $uid The UID of the event. * @param boolean $is_update Is this an event update? */ function sendITipReply( $cn, $resource, $itip, $type, $organiser, $uid, $is_update, $comment = null ) { Horde::logMessage(sprintf('sendITipReply(%s, %s, %s, %s)', $cn, $resource, get_class($itip), $type), __FILE__, __LINE__, PEAR_LOG_DEBUG); $itip_reply = new Horde_Kolab_Resource_Itip_Response( $itip, new Horde_Kolab_Resource_Itip_Resource_Base( $resource, $cn ) ); switch($type) { case RM_ITIP_DECLINE: $type = new Horde_Kolab_Resource_Itip_Response_Type_Decline( $resource, $itip ); break; case RM_ITIP_ACCEPT: $type = new Horde_Kolab_Resource_Itip_Response_Type_Accept( $resource, $itip ); break; case RM_ITIP_TENTATIVE: $type = new Horde_Kolab_Resource_Itip_Response_Type_Tentative( $resource, $itip ); break; } list($headers, $message) = $itip_reply->getMessage( $type, '-//kolab.org//NONSGML Kolab Server 2//EN', $comment ); Horde::logMessage(sprintf('Sending %s iTip reply to %s', $type->getStatus(), $organiser), __FILE__, __LINE__, PEAR_LOG_DEBUG); $reply = new Horde_Kolab_Resource_Reply( $resource, $organiser, $headers, $message ); Horde::logMessage('Successfully prepared iTip reply', __FILE__, __LINE__, PEAR_LOG_DEBUG); return $reply; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** We need the Horde LDAP tools for this class **/ require_once 'Horde/LDAP.php'; /** * This class provides methods to deal with Kolab objects stored in * the standard Kolab LDAP db. * * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/ldap.php,v 1.2.2.11 2009-04-25 08:56:34 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_Server_ldap extends Horde_Kolab_Server { /** * LDAP connection handle. * * @var resource */ var $_connection; /** * Flag that indicates bound state for the LDAP connection. * * @var boolean */ var $_bound; /** * The base dn . * * @var boolean */ var $_base_dn; /** * Connects to the LDAP server. * * @param string $server LDAP server URL. * @param string $base_dn LDAP server base DN. * * @return boolean|PEAR_Error True if the connection succeeded. */ function _connect($server = null, $base_dn = null) { if (!function_exists('ldap_connect')) { return PEAR::raiseError(_("Cannot connect to the Kolab LDAP server. PHP does not support LDAP!")); } if (!$server) { if (isset($this->_params['server'])) { $server = $this->_params['server']; } else { return PEAR::raiseError(_("Horde_Kolab_Server_ldap needs a server parameter!")); } } if (!$base_dn) { if (isset($this->_params['base_dn'])) { $this->_base_dn = $this->_params['base_dn']; } else { return PEAR::raiseError(_("Horde_Kolab_Server_ldap needs a base_dn parameter!")); } } else { $this->_base_dn = $base_dn; } $this->_connection = @ldap_connect($server); if (!$this->_connection) { return PEAR::raiseError(sprintf(_("Error connecting to LDAP server %s!"), $server)); } /* We need version 3 for Kolab */ if (!ldap_set_option($this->_connection, LDAP_OPT_PROTOCOL_VERSION, 3)) { return PEAR::raiseError(sprintf(_("Error setting LDAP protocol on server %s to v3: %s"), $server, ldap_error($this->_connection))); } return true; } /** * Binds the LDAP connection with a specific user and pass. * * @param string $dn DN to bind with * @param string $pw Password associated to this DN. * * @return boolean|PEAR_Error Whether or not the binding succeeded. */ function _bind($dn = false, $pw = '') { if (!$this->_connection) { $result = $this->_connect(); if (is_a($result, 'PEAR_Error')) { return $result; } } if (!$dn) { if (isset($this->_params['uid'])) { $dn = $this->_params['uid']; } else { $dn = ''; } } if (!$pw) { if (isset($this->_params['pass'])) { $pw = $this->_params['pass']; } } $this->_bound = @ldap_bind($this->_connection, $dn, $pw); if (!$this->_bound) { return PEAR::raiseError(sprintf(_("Unable to bind to the LDAP server as %s!"), $dn)); } return true; } /** * Disconnect from LDAP. * * @return NULL */ function unbind() { $result = @ldap_unbind($this->_connection); if (!$result) { return PEAR::raiseError("Failed to unbind from the LDAP server!"); } $this->_bound = false; } /** * Search for an object. * * @param string $filter Filter criteria. * @param array $attributes Restrict the search result to * these attributes. * @param string $base The base location for searching. * * @return array|PEAR_Error A LDAP search result. */ function _search($filter, $attributes = null, $base = null) { if (!$this->_bound) { $result = $this->_bind(); if (is_a($result, 'PEAR_Error')) { return $result; } } if (empty($base)) { $base = $this->_base_dn; } if (isset($attributes)) { $result = @ldap_search($this->_connection, $base, $filter, $attributes); } else { $result = @ldap_search($this->_connection, $base, $filter); } if (!$result && $this->_errno()) { return PEAR::raiseError(sprintf(_("LDAP Error: Failed to search using filter %s. Error was: %s"), $filter, $this->_error())); } return $result; } /** * Read object data. * * @param string $dn The object to retrieve. * @param string $attrs Restrict to these attributes. * * @return array|PEAR_Error An array of attributes. */ function _read($dn, $attrs = null) { if (!$this->_bound) { $result = $this->_bind(); if (is_a($result, 'PEAR_Error')) { return $result; } } if (isset($attrs)) { $this->mapKeys($attrs); $result = @ldap_read($this->_connection, $dn, '(objectclass=*)', $attrs); } else { $result = @ldap_read($this->_connection, $dn, '(objectclass=*)'); } if (!$result && $this->_errno()) { return PEAR::raiseError(sprintf(_("LDAP Error: No such object: %s: %s"), $dn, $this->_error())); } $entry = $this->_firstEntry($result); if (!$entry) { @ldap_free_result($result); return PEAR::raiseError(sprintf(_("LDAP Error: Empty result for: %s."), $dn)); } $object = $this->_getAttributes($entry); if (!$object && $this->_errno()) { return PEAR::raiseError(sprintf(_("LDAP Error: No such dn: %s: %s"), $dn, $this->_error())); } @ldap_free_result($result); $this->unmapAttributes($object); return $object; } /** * Add a new object * * @param string $dn The DN of the object to be added. * @param array $data The attributes of the object to be added. * * @return boolean True if adding succeeded. */ function _add($dn, $data) { if (!$this->_bound) { $result = $this->_bind(); if (is_a($result, 'PEAR_Error')) { return $result; } } $this->mapAttributes($data); return @ldap_add($this->_connection, $dn, $data); } /** * Count the number of results. * * @param string $result The LDAP search result. * * @return int The number of records found. */ function _count($result) { return @ldap_count_entries($this->_connection, $result); } /** * Return the dn of an entry. * * @param resource $entry The LDAP entry. * * @return string The DN of the entry. */ function _getDn($entry) { return @ldap_get_dn($this->_connection, $entry); } /** * Return the attributes of an entry. * * @param resource $entry The LDAP entry. * * @return array The attributes of the entry. */ function _getAttributes($entry) { return @ldap_get_attributes($this->_connection, $entry); } /** * Return the first entry of a result. * * @param resource $result The LDAP search result. * * @return resource The first entry of the result. */ function _firstEntry($result) { return @ldap_first_entry($this->_connection, $result); } /** * Return the next entry of a result. * * @param resource $entry The current LDAP entry. * * @return resource The next entry of the result. */ function _nextEntry($entry) { return @ldap_next_entry($this->_connection, $entry); } /** * Return the entries of a result. * * @param resource $result The LDAP search result. * @param int $from Only return results after this position. * @param int $to Only return results until this position. * * @return array The entries of the result. */ function _getEntries($result, $from = -1, $to = -1) { if ($from >= 0 || $to >= 0) { $result = array(); $i = 0; for ($entry = $this->_firstEntry($result); $entry != false; $entry = $this->_nextEntry($entry)) { if (!$entry && $this->_errno()) { return false; } if ($i > $from && ($i <= $to || $to == -1)) { $attributes = $this->_getAttributes($entry); if (!$attributes && $this->_errno()) { return false; } $result[] = $attributes; } $i++; } return $result; } return @ldap_get_entries($this->_connection, $result); } /** * Sort the entries of a result. * * @param resource $result The LDAP search result. * @param string $attribute The attribute used for sorting. * * @return boolean True if sorting succeeded. */ function _sort($result, $attribute) { return @ldap_sort($this->_connection, $result, $attribute); } /** * Return the current LDAP error number. * * @return int The current LDAP error number. */ function _errno() { return @ldap_errno($this->_connection); } /** * Return the current LDAP error description. * * @return string The current LDAP error description. */ function _error() { return @ldap_error($this->_connection); } /* * ------------------------------------------------------------------ * The functions defined below do not call ldap_* functions directly. * ------------------------------------------------------------------ */ /** * Return the root of the UID values on this server. * * @return string The base UID on this server (base DN on ldap). */ function getBaseUid() { return $this->_base_dn; } /** * Return the DNs of a result. * * @param resource $result The LDAP search result. * @param int $from Only return results after this position. * @param int $to Only return results until this position. * * @return array The DNs of the result. */ function _getDns($result, $from = -1, $to = -1) { $dns = array(); $entry = $this->_firstEntry($result); $i = 0; for ($entry = $this->_firstEntry($result); $entry != false; $entry = $this->_nextEntry($entry)) { if ($i > $from && ($i <= $to || $to == -1)) { $dn = $this->_getDn($entry); if (!$dn && $this->_errno()) { return false; } $dns[] = $dn; } $i++; } if ($this->_errno()) { return false; } return $dns; } /** * Identify the DN of the first result entry. * * @param array $result The LDAP search result. * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. * * @return string|PEAR_Error The DN. */ function _dnFromResult($result, $restrict = KOLAB_SERVER_RESULT_SINGLE) { switch ($restrict) { case KOLAB_SERVER_RESULT_STRICT: $count = $this->_count($result); if (!$count) { return false; } else if ($count > 1) { return PEAR::raiseError(sprintf(_("Found %s results when expecting only one!"), $count)); } case KOLAB_SERVER_RESULT_SINGLE: $entry = $this->_firstEntry($result); if (!$entry && $this->_errno()) { return PEAR::raiseError(sprintf(_("Search failed. Error was: %s"), $this->_error())); } if (!$entry) { return false; } $dn = $this->_getDn($entry); if (!$dn && $this->_errno()) { return PEAR::raiseError(sprintf(_("Retrieving DN failed. Error was: %s"), $this->_error())); } return $dn; case KOLAB_SERVER_RESULT_MANY: $entries = $this->_getDns($result); if (!$entries && $this->_errno()) { return PEAR::raiseError(sprintf(_("Search failed. Error was: %s"), $this->_error())); } if (!$entries) { return false; } return $entries; } return false; } /** * Get the attributes of the first result entry. * * @param array $result The LDAP search result. * @param array $attrs The attributes to retrieve. * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. * * @return mixed|PEAR_Error The attributes or false if there were * no results. */ function _attrsFromResult($result, $attrs, $restrict = KOLAB_SERVER_RESULT_SINGLE) { $entries = array(); switch ($restrict) { case KOLAB_SERVER_RESULT_STRICT: $count = $this->_count($result); if (!$count) { return false; } else if ($count > 1) { return PEAR::raiseError(sprintf(_("Found %s results when expecting only one!"), $count)); } case KOLAB_SERVER_RESULT_SINGLE: $first = $this->_firstEntry($result); if (!$first && $this->_errno()) { return PEAR::raiseError(sprintf(_("Search failed. Error was: %s"), $this->_error())); } if (!$first) { return false; } $entry = $this->_getAttributes($first); if (!$entry && $this->_errno()) { return PEAR::raiseError(sprintf(_("Retrieving attributes failed. Error was: %s"), $this->_error())); } $result = array(); foreach ($attrs as $attr) { if (isset($entry[$attr]) && $entry[$attr]['count'] > 0) { unset($entry[$attr]['count']); $result[$attr] = $entry[$attr]; } } return $result; case KOLAB_SERVER_RESULT_MANY: $entries = $this->_getEntries($result); if (!$entries && $this->_errno()) { return PEAR::raiseError(sprintf(_("Search failed. Error was: %s"), $this->_error())); } if (!$entries) { return false; } unset($entries['count']); $result = array(); $i = 0; foreach ($entries as $entry) { $result[$i] = array(); foreach ($attrs as $attr) { if (isset($entry[$attr])) { if ($entry[$attr]['count'] > 0) { unset($entry[$attr]['count']); $result[$i][$attr] = $entry[$attr]; } } } $i++; } return $result; } return false; } /** * Determine the type of a Kolab object. * * @param string $dn The DN of the object to examine. * * @return int The corresponding Kolab object type. */ function _determineType($dn) { $oc = $this->_getObjectClasses($dn); if (is_a($oc, 'PEAR_Error')) { return $oc; } // Not a user type? if (!in_array('kolabinetorgperson', $oc)) { // Is it a group? if (in_array('kolabgroupofnames', $oc)) { return KOLAB_OBJECT_GROUP; } // Is it a shared Folder? if (in_array('kolabsharedfolder', $oc)) { return KOLAB_OBJECT_SHAREDFOLDER; } return PEAR::raiseError(sprintf(_("Unkown Kolab object type for DN %s."), $dn)); } $groups = $this->getGroups($dn); if (is_a($groups, 'PEAR_Error')) { return $groups; } if (!empty($groups)) { if (in_array('cn=admin,cn=internal,' . $this->_base_dn, $groups)) { return KOLAB_OBJECT_ADMINISTRATOR; } if (in_array('cn=maintainer,cn=internal,' . $this->_base_dn, $groups)) { return KOLAB_OBJECT_MAINTAINER; } if (in_array('cn=domain-maintainer,cn=internal,' . $this->_base_dn, $groups)) { return KOLAB_OBJECT_DOMAINMAINTAINER; } } if (strpos($dn, 'cn=external') !== false) { return KOLAB_OBJECT_ADDRESS; } return KOLAB_OBJECT_USER; } /** * Get the LDAP object classes for the given DN. * * @param string $dn DN of the object. * * @return array|PEAR_Error An array of object classes. */ function _getObjectClasses($dn) { $object = $this->_read($dn, array('objectClass')); if (is_a($object, 'PEAR_Error')) { return $object; } if (!isset($object['objectClass'])) { return PEAR::raiseError('The result has no object classes!'); } unset($object['count']); unset($object['objectClass']['count']); $result = array_map('strtolower', $object['objectClass']); return $result; } /** * Identify the DN for the first object found using a filter. * * @param string $filter The LDAP filter to use. * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. * * @return mixed|PEAR_Error The DN or false if there was no result. */ function _dnForFilter($filter, $restrict = KOLAB_SERVER_RESULT_SINGLE) { $result = $this->_search($filter, array()); if (is_a($result, 'PEAR_Error')) { return $result; } if (!$this->_count($result)) { return false; } return $this->_dnFromResult($result, $restrict); } /** * Identify attributes for the first object found using a filter. * * @param string $filter The LDAP filter to use. * @param array $attrs The attributes to retrieve. * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. * * @return mixed|PEAR_Error The DN or false if there was no result. */ function _attrsForFilter($filter, $attrs, $restrict = KOLAB_SERVER_RESULT_SINGLE) { $result = $this->_search($filter, $attrs); if (is_a($result, 'PEAR_Error')) { return $result; } return $this->_attrsFromResult($result, $attrs, $restrict); } /** * Identify the primary mail attribute for the first object found * with the given ID or mail. * * @param string $id Search for objects with this ID/mail. * * @return mixed|PEAR_Error The mail address or false if there was * no result. */ function mailForIdOrMail($id) { $criteria = array('AND' => array( array('field' => KOLAB_ATTR_OC, 'op' => '=', 'test' => KOLAB_OC_KOLABINETORGPERSON), array('OR' => array( array('field' => KOLAB_ATTR_SID, 'op' => '=', 'test' => $id), array('field' => KOLAB_ATTR_MAIL, 'op' => '=', 'test' => $id), ), ), ), ); $filter = $this->searchQuery($criteria); $result = $this->_attrsForFilter($filter, array('mail'), KOLAB_SERVER_RESULT_STRICT); if (is_a($result, 'PEAR_Error')) { return $result; } return $result['mail'][0]; } /** * Identify the UID for the first object found with the given ID * or mail. * * @param string $id Search for objects with this ID/mail. * * @return mixed|PEAR_Error The UID or false if there was no result. */ function uidForIdOrMail($id) { $criteria = array('AND' => array( array('field' => KOLAB_ATTR_OC, 'op' => '=', 'test' => KOLAB_OC_KOLABINETORGPERSON), array('OR' => array( array('field' => KOLAB_ATTR_SID, 'op' => '=', 'test' => $id), array('field' => KOLAB_ATTR_MAIL, 'op' => '=', 'test' => $id), ), ), ), ); $filter = $this->searchQuery($criteria); return $this->_dnForFilter($filter, KOLAB_SERVER_RESULT_STRICT); } /** * Returns a list of allowed email addresses for the given user. * * @param string $id The users primary mail address or ID. * * @return array|PEAR_Error An array of allowed mail addresses */ function addrsForIdOrMail($id) { $criteria = array('AND' => array( array('field' => KOLAB_ATTR_OC, 'op' => '=', 'test' => KOLAB_OC_KOLABINETORGPERSON), array('OR' => array( array('field' => KOLAB_ATTR_SID, 'op' => '=', 'test' => $id), array('field' => KOLAB_ATTR_MAIL, 'op' => '=', 'test' => $id), ), ), ), ); $filter = $this->searchQuery($criteria); $result = $this->_attrsForFilter($filter, array('mail', 'alias'), KOLAB_SERVER_RESULT_STRICT); if (empty($result)) { return array(); } if (is_a($result, 'PEAR_Error')) { return $result; } if (isset($result['alias'])) { $addrs = array_merge((array) $result['mail'], (array) $result['alias']); } else { $addrs = $result['mail']; } $mail = $result['mail'][0]; $filter = '(&(objectClass=kolabInetOrgPerson)(kolabDelegate=' . Horde_LDAP::quote($mail) . '))'; $result = $this->_attrsForFilter($filter, array('mail', 'alias'), KOLAB_SERVER_RESULT_MANY); if (is_a($result, 'PEAR_Error')) { return $result; } if (!empty($result)) { foreach ($result as $adr) { if (isset($adr['mail'])) { $addrs = array_merge((array) $addrs, (array) $adr['mail']); } if (isset($adr['alias'])) { $addrs = array_merge((array) $addrs, (array) $adr['alias']); } } } $addrs = array_map('strtolower', $addrs); return $addrs; } /** * Return the UID for a given primary mail, ID, or alias. * * @param string $mail A valid mail address for the user. * * @return mixed|PEAR_Error The UID or false if there was no result. */ function uidForMailAddress($mail) { $criteria = array('AND' => array( array('field' => KOLAB_ATTR_OC, 'op' => '=', 'test' => KOLAB_OC_KOLABINETORGPERSON), array('OR' => array( array('field' => KOLAB_ATTR_ALIAS, 'op' => '=', 'test' => $mail), array('field' => KOLAB_ATTR_MAIL, 'op' => '=', 'test' => $mail), array('field' => KOLAB_ATTR_SID, 'op' => '=', 'test' => $mail), ), ), ), ); $filter = $this->searchQuery($criteria); return $this->_dnForFilter($filter); } /** * Identify the UID for the first object found using a specified * attribute value. * * @param string $attr The name of the attribute used for searching. * @param string $value The desired value of the attribute. * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. * * @return mixed|PEAR_Error The UID or false if there was no result. */ function uidForAttr($attr, $value, $restrict = KOLAB_SERVER_RESULT_SINGLE) { $criteria = array('AND' => array( array('field' => KOLAB_ATTR_OC, 'op' => '=', 'test' => KOLAB_OC_KOLABINETORGPERSON), array('field' => $attr, 'op' => '=', 'test' => $value), ), ); $filter = $this->searchQuery($criteria); return $this->_dnForFilter($filter, $restrict); } /** * Identify the GID for the first group found using a specified * attribute value. * * @param string $attr The name of the attribute used for searching. * @param string $value The desired value of the attribute. * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. * * @return mixed|PEAR_Error The GID or false if there was no result. */ function gidForAttr($attr, $value, $restrict = KOLAB_SERVER_RESULT_SINGLE) { $criteria = array('AND' => array( array('field' => KOLAB_ATTR_OC, 'op' => '=', 'test' => KOLAB_OC_KOLABGROUPOFNAMES), array('field' => $attr, 'op' => '=', 'test' => $value), ), ); $filter = $this->searchQuery($criteria); return $this->_dnForFilter($filter, $restrict); } /** * Is the given UID member of the group with the given mail address? * * @param string $uid UID of the user. * @param string $mail Search the group with this mail address. * * @return boolen|PEAR_Error True in case the user is in the * group, false otherwise. */ function memberOfGroupAddress($uid, $mail) { $filter = '(&(objectClass=kolabGroupOfNames)(mail=' . Horde_LDAP::quote($mail) . ')(member=' . Horde_LDAP::quote($uid) . '))'; $result = $this->_dnForFilter($filter, KOLAB_SERVER_RESULT_STRICT); if (is_a($result, 'PEAR_Error')) { return $result; } if (empty($result)) { return false; } return true; } /** * Get the groups for this object. * * @param string $uid The UID of the object to fetch. * * @return array|PEAR_Error An array of group ids. */ function getGroups($uid) { $filter = '(&(objectClass=kolabGroupOfNames)(member=' . Horde_LDAP::quote($uid) . '))'; $result = $this->_dnForFilter($filter, KOLAB_SERVER_RESULT_MANY); if (empty($result)) { return array(); } return $result; } /** * Get the mail addresses for the group of this object. * * @param string $uid The UID of the object to fetch. * * @return array|PEAR_Error An array of group ids. * * @since 0.5.0 */ function getGroupAddresses($uid) { $filter = '(&(objectClass=kolabGroupOfNames)(member=' . Horde_LDAP::quote($uid) . '))'; $result = $this->_attrsForFilter($filter, array(KOLAB_ATTR_MAIL), KOLAB_SERVER_RESULT_MANY); if (empty($result)) { return array(); } $mails = array(); foreach ($result as $element) { if (isset($element[KOLAB_ATTR_MAIL])) { if (is_array($element[KOLAB_ATTR_MAIL])) { $mails = array_merge($mails, $element[KOLAB_ATTR_MAIL]); } else { $mails[] = $element[KOLAB_ATTR_MAIL]; } } } return $mails; } /** * List all objects of a specific type * * @param string $type The type of the objects to be listed * @param array $params Additional parameters. * * @return array|PEAR_Error An array of Kolab objects. */ function _listObjects($type, $params = null) { if (empty($params['base_dn'])) { $base = $this->_base_dn; } else { $base = $params['base_dn']; } $result = Horde_Kolab_Server_Object::loadClass($type); if (is_a($result, 'PEAR_Error')) { return $result; } $vars = get_class_vars($type); $methods = get_class_methods($type); if (!in_array('getFilter', $methods)) { $filter = $vars['filter']; } else { $criteria = call_user_func(array($type, 'getFilter')); $filter = $this->searchQuery($criteria); } $sort = $vars['sort_by']; if (isset($params['sort'])) { $sort = $params['sort']; } $result = $this->_search($filter, null, $base); if (is_a($result, 'PEAR_Error')) { return $result; } if (empty($result)) { return array(); } if ($sort) { $this->_sort($result, $sort); } if (isset($params['from'])) { $from = $params['from']; } else { $from = -1; } if (isset($params['to'])) { $sort = $params['to']; } else { $to = -1; } $entries = $this->_getDns($result, $from, $to); if (!$entries && $this->_errno()) { return PEAR::raiseError(sprintf(_("Search failed. Error was: %s"), $this->_error())); } if (!$entries) { return false; } if (!empty($vars['required_group'])) { $required_group = $this->fetch($vars['required_group'], KOLAB_OBJECT_GROUP); } $objects = array(); foreach ($entries as $dn) { if (!empty($vars['required_group']) && $required_group->isMember($dn)) { continue; } $result = $this->fetch($dn, $type); if (is_a($result, 'PEAR_Error')) { return $result; } $objects[] = $result; } return $objects; } /** * Generates a UID for the given information. * * @param string $type The type of the object to create. * @param string $id The id of the object. * @param array $info Any additional information about the object to create. * * @return string|PEAR_Error The DN. */ function _generateUid($type, $id, $info) { switch ($type) { case KOLAB_OBJECT_USER: if (!isset($info['user_type']) || $info['user_type'] == 0) { return sprintf('cn=%s,%s', $id, $this->_base_dn); } else if ($info['user_type'] == KOLAB_UT_INTERNAL) { return sprintf('cn=%s,cn=internal,%s', $id, $this->_base_dn); } else if ($info['user_type'] == KOLAB_UT_GROUP) { return sprintf('cn=%s,cn=groups,%s', $id, $this->_base_dn); } else if ($info['user_type'] == KOLAB_UT_RESOURCE) { return sprintf('cn=%s,cn=resources,%s', $id, $this->_base_dn); } else { return sprintf('cn=%s,%s', $id, $this->_base_dn); } case KOLAB_OBJECT_ADDRESS: return sprintf('cn=%s,cn=external,%s', $id, $this->_base_dn); case KOLAB_OBJECT_SHAREDFOLDER: case KOLAB_OBJECT_ADMINISTRATOR: case KOLAB_OBJECT_MAINTAINER: case KOLAB_OBJECT_DOMAINMAINTAINER: return sprintf('cn=%s,%s', $id, $this->_base_dn); case KOLAB_OBJECT_GROUP: case KOLAB_OBJECT_DISTLIST: if (!isset($info['visible']) || !empty($info['visible'])) { return sprintf('cn=%s,%s', $id, $this->_base_dn); } else { return sprintf('cn=%s,cn=internal,%s', $id, $this->_base_dn); } default: return PEAR::raiseError(_("Not implemented!")); } } /** * Save an object. * * @param string $dn The DN of the object. * @param array $data The data for the object. * * @return boolean|PEAR_Error True if successfull. */ function save($dn, $data) { $result = $this->_add($dn, $data); if (!$result && $this->_errno()) { return PEAR::raiseError(sprintf(_("Failed saving object. Error was: %s"), $this->_error())); } } /** * Build a search query. * * Taken from the Turba LDAP driver. * * @param array $criteria The array of criteria. * * @return string An LDAP query filter. */ public function searchQuery($criteria) { require_once 'Net/LDAP2.php'; /* Accept everything. */ $filter = '(' . strtolower(KOLAB_ATTR_OC) . '=*)'; /* Build the LDAP filter. */ if (count($criteria)) { $f = $this->buildSearchQuery($criteria); if (is_a($f, 'Net_LDAP2_Filter')) { $filter = $f->asString(); } } return $filter; } /** * Build a piece of a search query. * * Taken from the Turba LDAP driver. * * @param array $criteria The array of criteria. * * @return string An LDAP query fragment. */ protected function &buildSearchQuery($criteria) { if (isset($criteria['field'])) { require_once 'Horde/String.php'; require_once 'Horde/NLS.php'; $rhs = $criteria['test']; /* Keep this in for reference as we did not really test servers with different encoding yet */ //$rhs = String::convertCharset($criteria['test'], NLS::getCharset(), $this->params['charset']); switch ($criteria['op']) { case '=': $op = 'equals'; break; } return Net_LDAP2_Filter::create($this->mapField($criteria['field']), $op, $rhs); } foreach ($criteria as $key => $vals) { if (!empty($vals['OR']) || !empty($vals['AND']) || !empty($vals['NOT'])) { $parts = $this->buildSearchQuery($vals); if (count($parts) > 1) { if (!empty($vals['OR'])) { $operator = '|'; } else if (!empty($vals['NOT'])) { $operator = '!'; } else { $operator = '&'; } return Net_LDAP2_Filter::combine($operator, $parts); } else { return $parts[0]; } } else { $parts = array(); foreach ($vals as $test) { $parts[] = &$this->buildSearchQuery($test); } switch ($key) { case 'OR': $operator = '|'; break; case 'AND': $operator = '&'; break; case 'NOT': $operator = '!'; break; } if (count($parts) > 1) { return Net_LDAP2_Filter::combine($operator, $parts); } else if ($operator == '!') { return Net_LDAP2_Filter::combine($operator, $parts[0]); } else { return $parts[0]; } } } } /** * Map attributes defined within this library their their real world * counterparts. * * @param array $data The data that has been read and needs to be mapped. * * @return NULL */ protected function unmapAttributes(&$data) { if (!empty($this->_params['map'])) { foreach ($this->_params['map'] as $attribute => $map) { if (isset($data[$map])) { $data[$attribute] = $data[$map]; unset($data[$map]); } } } } /** * Map attributes defined within this library into their real world * counterparts. * * @param array $data The data to be written. * * @return NULL */ protected function mapAttributes(&$data) { if (!empty($this->_params['map'])) { foreach ($this->_params['map'] as $attribute => $map) { if (isset($data[$attribute])) { $data[$map] = $data[$attribute]; unset($data[$attribute]); } } } } /** * Map attribute keys defined within this library into their real world * counterparts. * * @param array $keys The attribute keys. * * @return NULL */ protected function mapKeys(&$keys) { if (!empty($this->_params['map'])) { foreach ($this->_params['map'] as $attribute => $map) { $key = array_search($attribute, $keys); if ($key !== false) { $keys[$key] = $map; } } } } /** * Map a single attribute key defined within this library into its real * world counterpart. * * @param array $field The attribute name. * * @return The real name of this attribute on the server we connect to. */ protected function mapField($field) { if (!empty($this->_params['map']) && isset($this->_params['map'][$field])) { return $this->_params['map'][$field]; } return $field; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** * This class provides methods to deal with global address book * entries for Kolab. * * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/address.php,v 1.2.2.6 2009-04-25 08:56:33 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_Server_Object_address extends Horde_Kolab_Server_Object { /** * The attributes supported by this class * * @var array */ var $_supported_attributes = array( KOLAB_ATTR_SN, KOLAB_ATTR_CN, KOLAB_ATTR_GIVENNAME, KOLAB_ATTR_FN, KOLAB_ATTR_LNFN, KOLAB_ATTR_MAIL, KOLAB_ATTR_DELETED, ); /** * Attributes derived from the LDAP values. * * @var array */ var $_derived_attributes = array( KOLAB_ATTR_LNFN, KOLAB_ATTR_FNLN, ); /** * The attributes required when creating an object of this class. * * @var array */ var $_required_attributes = array( KOLAB_ATTR_SN, KOLAB_ATTR_GIVENNAME, ); /** * The ldap classes for this type of object. * * @var array */ var $_object_classes = array( KOLAB_OC_TOP, KOLAB_OC_INETORGPERSON, KOLAB_OC_KOLABINETORGPERSON, ); /** * The LDAP filter to retrieve this object type * * @return string */ function getFilter() { $criteria = array('AND' => array( array('field' => KOLAB_ATTR_SN, 'op' => '=', 'test' => '*'), array('field' => KOLAB_ATTR_OC, 'op' => '=', 'test' => KOLAB_OC_INETORGPERSON), array('NOT' => array( array('field' => KOLAB_ATTR_SID, 'op' => '=', 'test' => '*'), ), ), ), ); return $criteria; } /** * Convert the object attributes to a hash. * * @param string $attrs The attributes to return. * * @return array|PEAR_Error The hash representing this object. */ function toHash($attrs = null) { if (!isset($attrs)) { $attrs = array( KOLAB_ATTR_LNFN, ); } return parent::toHash($attrs); } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ require_once 'Horde/Kolab/Server/Object/adminrole.php'; /** * This class provides methods to deal with administrator * entries for Kolab. * * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/administrator.php,v 1.2.2.4 2009-01-06 15:23:15 jan 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_Server_Object_administrator extends Horde_Kolab_Server_Object_adminrole { /** * The group the UID must be member of so that this object really * matches this class type. This may not include the root UID. * * @var string */ var $required_group = 'cn=admin,cn=internal'; } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** * This class provides methods to deal with administrator object types. * * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/adminrole.php,v 1.1.2.4 2009-04-25 08:56:33 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_Server_Object_adminrole extends Horde_Kolab_Server_Object { /** * The attributes supported by this class * * @var array */ var $_supported_attributes = array( KOLAB_ATTR_SN, KOLAB_ATTR_CN, KOLAB_ATTR_GIVENNAME, KOLAB_ATTR_FN, KOLAB_ATTR_SID, KOLAB_ATTR_USERPASSWORD, KOLAB_ATTR_DELETED, ); /** * The attributes required when creating an object of this class. * * @var array */ var $_required_attributes = array( KOLAB_ATTR_SN, KOLAB_ATTR_GIVENNAME, KOLAB_ATTR_USERPASSWORD, KOLAB_ATTR_SID, ); /** * Attributes derived from the LDAP values. * * @var array */ var $_derived_attributes = array( KOLAB_ATTR_ID, KOLAB_ATTR_LNFN, ); /** * The ldap classes for this type of object. * * @var array */ var $_object_classes = array( KOLAB_OC_TOP, KOLAB_OC_INETORGPERSON, KOLAB_OC_KOLABINETORGPERSON, ); /** * The LDAP filter to retrieve this object type * * @return string */ function getFilter() { $criteria = array('AND' => array( array('field' => KOLAB_ATTR_CN, 'op' => '=', 'test' => '*'), array('field' => KOLAB_ATTR_SN, 'op' => '=', 'test' => '*'), array('field' => KOLAB_ATTR_OC, 'op' => '=', 'test' => KOLAB_OC_INETORGPERSON), array('NOT' => array( array('field' => KOLAB_ATTR_SID, 'op' => '=', 'test' => 'manager'), ), ), ), ); return $criteria; } /** * Convert the object attributes to a hash. * * @param string $attrs The attributes to return. * * @return array|PEAR_Error The hash representing this object. */ function toHash($attrs = null) { if (!isset($attrs)) { $attrs = array( KOLAB_ATTR_SID, KOLAB_ATTR_LNFN, ); } return parent::toHash($attrs); } /** * Saves object information. * * @param array $info The information about the object. * * @return boolean|PEAR_Error True on success. */ function save($info) { if (!isset($info['cn'])) { if (!isset($info['sn']) || !isset($info['givenName'])) { return PEAR::raiseError('Either the last name or the given name is missing!'); } else { $info['cn'] = $this->generateId($info); } } $admins_uid = sprintf('%s,%s', $this->required_group, $this->_db->getBaseUid()); $admin_group = $this->_db->fetch($admins_uid, KOLAB_OBJECT_GROUP); if (is_a($admin_group, 'PEAR_Error') || !$admin_group->exists()) { $members = array($this->_uid); //FIXME: This is not okay and also contains too much LDAP knowledge $parts = split(',', $this->required_group); list($groupname) = sscanf($parts[0], 'cn=%s'); $result = $this->_db->add(array(KOLAB_ATTR_CN => $groupname, 'type' => KOLAB_OBJECT_GROUP, KOLAB_ATTR_MEMBER => $members, KOLAB_ATTR_VISIBILITY => false)); if (is_a($result, 'PEAR_Error')) { return $result; } } else { $result = $admin_group->isMember($this->_uid); if (is_a($result, 'PEAR_Error')) { return $result; } if ($result === false) { $members = $admin_group->getMembers(); $members[] = $this->_uid; $admin_group->save(array(KOLAB_ATTR_MEMBER => $members)); } } return parent::save($info); } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ require_once 'Horde/Kolab/Server/Object/group.php'; /** * This class provides methods to deal with distribution lists for Kolab. * * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/distlist.php,v 1.1.2.3 2009-04-25 08:56:33 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_Server_Object_distlist extends Horde_Kolab_Server_Object_group { /** * The attributes required when creating an object of this class. * * @var array */ var $_required_attributes = array( KOLAB_ATTR_MAIL, ); /** * Return the filter string to retrieve this object type. * * @static * * @return string The filter to retrieve this object type from the server * database. */ public static function getFilter() { $criteria = array('AND' => array( array('field' => KOLAB_ATTR_MAIL, 'op' => '=', 'test' => '*'), array('field' => KOLAB_ATTR_OC, 'op' => '=', 'test' => KOLAB_OC_KOLABGROUPOFNAMES), ), ); return $criteria; } }; * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ require_once 'Horde/Kolab/Server/Object/adminrole.php'; /** * This class provides methods associated to Kolab domain maintainers. * * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/domainmaintainer.php,v 1.2.2.5 2009-01-06 15:23:15 jan 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_Server_Object_domainmaintainer extends Horde_Kolab_Server_Object_adminrole { /** * The attributes required when creating an object of this class. * * @var array */ var $_required_attributes = array( KOLAB_ATTR_SN, KOLAB_ATTR_GIVENNAME, KOLAB_ATTR_USERPASSWORD, KOLAB_ATTR_SID, KOLAB_ATTR_DOMAIN, ); /** * Attributes derived from the LDAP values. * * @var array */ var $_derived_attributes = array( KOLAB_ATTR_ID, KOLAB_ATTR_LNFN, KOLAB_ATTR_DOMAIN, ); /** * The group the UID must be member of so that this object really * matches this class type. This may not include the root UID. * * @var string */ var $required_group = 'cn=domain-maintainer,cn=internal'; /** * Convert the object attributes to a hash. * * @param string $attrs The attributes to return. * * @return array|PEAR_Error The hash representing this object. */ function toHash($attrs = null) { if (!isset($attrs)) { $attrs = array( KOLAB_ATTR_SID, KOLAB_ATTR_LNFN, KOLAB_ATTR_DOMAIN, ); } return parent::toHash($attrs); } /** * Saves object information. * * @param array $info The information about the object. * * @return boolean|PEAR_Error True on success. */ function save($info) { foreach ($info[KOLAB_ATTR_DOMAIN] as $domain) { $domain_uid = sprintf('cn=%s,cn=domain,cn=internal,%s', $domain, $this->_db->getBaseUid()); //FIXME: This should be made easier by the group object $domain_group = $this->_db->fetch($domain_uid, KOLAB_OBJECT_GROUP); if (is_a($domain_group, 'PEAR_Error')) { return $domain_group; } if (!$domain_group->exists()) { $members = array($this->_uid); $domain_group->save(array(KOLAB_ATTR_MEMBER => $members)); } else { $result = $domain_group->isMember($this->_uid); if (is_a($result, 'PEAR_Error')) { return $result; } if ($result === false) { $members = $domain_group->getMembers(); $members[] = $this->_uid; $domain_group->save(array(KOLAB_ATTR_MEMBER => $members)); } } } return parent::save($info); } } INDX( )v(hXii%o? address.phpxdii%N*p administrator.phppZii%N*p ADMINI~1.PHPp\ii%N*p adminrole.phppZii%N*p ADMINR~1.PHPpZii%"rE distlist.phpjii%v'r domainmaintainer.phppZii%v'r DOMAIN~1.PHPhTii%҈s  group.phpp^ii%Jw maintainer.phppZii%Jw MAINTA~1.PHPhVii%7@| server.phpxbii%X|^ sharedfolder.phppZii%X|^ SHARED~1.PHPhRii%~0\( user.php * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** * This class provides methods to deal with groups for Kolab. * * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/group.php,v 1.2.2.5 2009-04-25 08:56:33 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_Server_Object_group extends Horde_Kolab_Server_Object { /** * The attributes supported by this class * * @var array */ var $_supported_attributes = array( KOLAB_ATTR_CN, KOLAB_ATTR_MAIL, KOLAB_ATTR_MEMBER, KOLAB_ATTR_DELETED, ); /** * Attributes derived from the LDAP values. * * @var array */ var $_derived_attributes = array( KOLAB_ATTR_ID, KOLAB_ATTR_VISIBILITY, ); /** * The attributes required when creating an object of this class. * * @var array */ var $_required_attributes = array( KOLAB_ATTR_CN, ); /** * The ldap classes for this type of object. * * @var array */ var $_object_classes = array( KOLAB_OC_TOP, KOLAB_OC_KOLABGROUPOFNAMES, ); /** * Sort by this attributes (must be a LDAP attribute). * * @var string */ var $sort_by = KOLAB_ATTR_MAIL; /** * Derive an attribute value. * * @param string $attr The attribute to derive. * * @return mixed The value of the attribute. */ function _derive($attr) { switch ($attr) { case KOLAB_ATTR_VISIBILITY: return strpos($this->_uid, 'cn=internal') === false; default: return parent::_derive($attr); } } /** * Return the filter string to retrieve this object type. * * @return string The filter to retrieve this object type from the server * database. */ public static function getFilter() { $criteria = array('AND' => array(array('field' => KOLAB_ATTR_OC, 'op' => '=', 'test' => KOLAB_OC_KOLABGROUPOFNAMES), ), ); return $criteria; } /** * Convert the object attributes to a hash. * * @param string $attrs The attributes to return. * * @return array|PEAR_Error The hash representing this object. */ function toHash($attrs = null) { if (!isset($attrs)) { $attrs = array( KOLAB_ATTR_ID, KOLAB_ATTR_MAIL, KOLAB_ATTR_VISIBILITY, ); } return parent::toHash($attrs); } /** * Generates an ID for the given information. * * @param array $info The data of the object. * * @static * * @return string|PEAR_Error The ID. */ function generateId($info) { if (isset($info['mail'])) { return trim($info['mail'], " \t\n\r\0\x0B,"); } else { return trim($info['cn'], " \t\n\r\0\x0B,"); } } /** * Saves object information. * * @param array $info The information about the object. * * @return boolean|PEAR_Error True on success. */ function save($info) { if (!isset($info['cn'])) { if (!isset($info['mail'])) { return PEAR::raiseError('Either the mail address or the common name has to be specified for a group object!'); } else { $info['cn'] = $info['mail']; } } return parent::save($info); } /** * Retrieve the member list for this group. * * @return array|PEAR_Error The list of members in this group. */ function getMembers() { return $this->_get(KOLAB_ATTR_MEMBER, false); } /** * Add a member to this group. * * @param string $member The UID of the member to add. * * @return array|PEAR_Error True if successful. */ function addMember($member) { $members = $this->getMembers(); if (is_a($members, 'PEAR_Error')) { return $members; } if (!in_array($member, $members)) { $this->_cache[KOLAB_ATTR_MEMBER][] = $member; } else { return PEAR::raiseError(_("The UID %s is already a member of the group %s!"), $member, $this->_uid); } return $this->save($this->_cache); } /** * Delete a member from this group. * * @param string $member The UID of the member to delete. * * @return array|PEAR_Error True if successful. */ function deleteMember($member) { $members = $this->getMembers(); if (is_a($members, 'PEAR_Error')) { return $members; } if (in_array($member, $members)) { $this->_cache[KOLAB_ATTR_MEMBER] = array_diff($this->_cache[KOLAB_ATTR_MEMBER], array($member)); } else { return PEAR::raiseError(_("The UID %s is no member of the group %s!"), $member, $this->_uid); } return $this->save($this->_cache); } /** * Is the specified UID member of this group? * * @param string $member The UID of the member to check. * * @return boolean|PEAR_Error True if the UID is a member of the group, * false otherwise. */ function isMember($member) { $members = $this->getMembers(); if (is_a($members, 'PEAR_Error') || !is_array($members)) { return $members; } if (!in_array($member, $members)) { return false; } else { return true; } } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ require_once 'Horde/Kolab/Server/Object/adminrole.php'; /** * This class provides methods to deal with maintainer * entries for Kolab. * * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/maintainer.php,v 1.2.2.4 2009-01-06 15:23:15 jan 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_Server_Object_maintainer extends Horde_Kolab_Server_Object_adminrole { /** * The group the UID must be member of so that this object really * matches this class type. This may not include the root UID. * * @var string */ var $required_group = 'cn=maintainer,cn=internal'; } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** * This class provides methods to deal with Kolab server configuration. * * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/server.php,v 1.2.2.5 2009-04-25 08:56:33 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_Server_Object_server extends Horde_Kolab_Server_Object { /** * Return the filter string to retrieve this object type. * * @static * * @return string The filter to retrieve this object type from the server * database. */ public static function getFilter() { $criteria = array('AND' => array( array('field' => 'k', 'op' => '=', 'test' => 'kolab'), array('field' => KOLAB_ATTR_OC, 'op' => '=', 'test' => KOLAB_OC_KOLAB), ), ); return $criteria; } /** * The attributes supported by this class * * @var array */ var $_supported_attributes = array( KOLAB_ATTR_FBPAST, ); } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** * This class provides methods to deal with shared folders * entries for Kolab. * * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/sharedfolder.php,v 1.2.2.5 2009-04-25 08:56:33 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_Server_Object_sharedfolder extends Horde_Kolab_Server_Object { /** * The attributes supported by this class * * @var array */ var $_supported_attributes = array( KOLAB_ATTR_CN, KOLAB_ATTR_DELETED, KOLAB_ATTR_FOLDERTYPE, KOLAB_ATTR_HOMESERVER, KOLAB_ATTR_IMAPHOST, KOLAB_ATTR_QUOTA, KOLAB_ATTR_ACL, ); /** * The attributes required when creating an object of this class. * * @var array */ var $_required_attributes = array( KOLAB_ATTR_CN, KOLAB_ATTR_HOMESERVER, ); /** * The ldap classes for this type of object. * * @var array */ var $_object_classes = array( KOLAB_OC_TOP, KOLAB_OC_KOLABSHAREDFOLDER, ); /** * Generates an ID for the given information. * * @param array $info The data of the object. * * @static * * @return string|PEAR_Error The ID. */ function generateId($info) { return trim($info['cn'], " \t\n\r\0\x0B,"); } /** * Return the filter string to retrieve this object type. * * @return string The filter to retrieve this object type from the server * database. */ public static function getFilter() { $criteria = array('AND' => array(array('field' => KOLAB_ATTR_OC, 'op' => '=', 'test' => KOLAB_OC_KOLABSHAREDFOLDER), ), ); return $criteria; } /** * Convert the object attributes to a hash. * * @param string $attrs The attributes to return. * * @return array|PEAR_Error The hash representing this object. */ function toHash($attrs = null) { if (!isset($attrs)) { $attrs = array( KOLAB_ATTR_CN, KOLAB_ATTR_HOMESERVER, KOLAB_ATTR_FOLDERTYPE, ); } return parent::toHash($attrs); } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** * This class provides methods to deal with Kolab users stored in * the Kolab db. * * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/user.php,v 1.2.2.12 2009-04-25 12:34:52 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_Server_Object_user extends Horde_Kolab_Server_Object { /** * The attributes supported by this class * * @var array */ var $_supported_attributes = array( KOLAB_ATTR_SN, KOLAB_ATTR_CN, KOLAB_ATTR_GIVENNAME, KOLAB_ATTR_FN, KOLAB_ATTR_SID, KOLAB_ATTR_USERPASSWORD, KOLAB_ATTR_MAIL, KOLAB_ATTR_DELETED, KOLAB_ATTR_IMAPHOST, KOLAB_ATTR_FREEBUSYHOST, KOLAB_ATTR_HOMESERVER, KOLAB_ATTR_KOLABDELEGATE, KOLAB_ATTR_IPOLICY, KOLAB_ATTR_FBFUTURE, ); /** * Attributes derived from the LDAP values. * * @var array */ var $_derived_attributes = array( KOLAB_ATTR_ID, KOLAB_ATTR_USERTYPE, KOLAB_ATTR_LNFN, KOLAB_ATTR_FNLN, ); /** * The attributes required when creating an object of this class. * * @var array */ var $_required_attributes = array( KOLAB_ATTR_SN, KOLAB_ATTR_GIVENNAME, KOLAB_ATTR_USERPASSWORD, KOLAB_ATTR_MAIL, KOLAB_ATTR_HOMESERVER, ); /** * The ldap classes for this type of object. * * @var array */ var $_object_classes = array( KOLAB_OC_TOP, KOLAB_OC_INETORGPERSON, KOLAB_OC_KOLABINETORGPERSON, KOLAB_OC_HORDEPERSON, ); /** * Initialize the Kolab Object. Provide either the UID or a * LDAP search result. * * @param Horde_Kolab_Server &$db The link into the Kolab db. * @param string $dn UID of the object. * @param array $data A possible array of data for the object */ function Horde_Kolab_Server_Object_user(&$db, $dn = null, $data = null) { global $conf; /** Allows to customize the supported user attributes. */ if (isset($conf['kolab']['server']['user_supported_attrs'])) { $this->_supported_attributes = $conf['kolab']['server']['user_supported_attrs']; } /** Allows to customize the required user attributes. */ if (isset($conf['kolab']['server']['user_required_attrs'])) { $this->_required_attributes = $conf['kolab']['server']['user_required_attrs']; } /** Allows to customize the user object classes. */ if (isset($conf['kolab']['server']['user_objectclasses'])) { $this->_object_classes = $conf['kolab']['server']['user_object_classes']; } Horde_Kolab_Server_Object::Horde_Kolab_Server_Object($db, $dn, $data); } /** * Derive an attribute value. * * @param string $attr The attribute to derive. * * @return mixed The value of the attribute. */ function _derive($attr) { switch ($attr) { case KOLAB_ATTR_USERTYPE: if (strpos($this->_uid, 'cn=internal')) { return KOLAB_UT_INTERNAL; } else if (strpos($this->_uid, 'cn=group')) { return KOLAB_UT_GROUP; } else if (strpos($this->_uid, 'cn=resource')) { return KOLAB_UT_RESOURCE; } else { return KOLAB_UT_STANDARD; } default: return parent::_derive($attr); } } /** * The LDAP filter to retrieve this object type * * @return string */ function getFilter() { $criteria = array('AND' => array( array('field' => KOLAB_ATTR_SN, 'op' => '=', 'test' => '*'), array('field' => KOLAB_ATTR_MAIL, 'op' => '=', 'test' => '*'), array('field' => KOLAB_ATTR_SID, 'op' => '=', 'test' => '*'), array('field' => KOLAB_ATTR_OC, 'op' => '=', 'test' => KOLAB_OC_KOLABINETORGPERSON), ), ); return $criteria; } /** * Convert the object attributes to a hash. * * @param string $attrs The attributes to return. * * @return array|PEAR_Error The hash representing this object. */ function toHash($attrs = null) { if (!isset($attrs)) { $attrs = array( KOLAB_ATTR_SID, KOLAB_ATTR_FN, KOLAB_ATTR_MAIL, KOLAB_ATTR_USERTYPE, ); } return parent::toHash($attrs); } /** * Get the groups for this object * * @return mixed|PEAR_Error An array of group ids, false if no groups were * found. */ function getGroups() { return $this->_db->getGroups($this->_uid); } /** * Get the group mail addresses for this object * * @return mixed|PEAR_Error An array of group addresses, false if no groups were * found. */ function getGroupAddresses() { return $this->_db->getGroupAddresses($this->_uid); } /** * Returns the server url of the given type for this user. * * This method is used to encapsulate multidomain support. * * @param string $server_type The type of server URL that should be returned. * * @return string The server url or empty on error. */ function getServer($server_type) { global $conf; switch ($server_type) { case 'freebusy': $server = $this->get(KOLAB_ATTR_FREEBUSYHOST); if (!is_a($server, 'PEAR_Error') && !empty($server)) { return $server; } $server = $this->getServer('homeserver'); if (is_a($server, 'PEAR_Error')) { return $server; } if (empty($server)) { $server = $_SERVER['SERVER_NAME']; } if (isset($conf['kolab']['freebusy']['server'])) { return $conf['kolab']['freebusy']['server']; } if (isset($conf['kolab']['server']['freebusy_url_format'])) { return sprintf($conf['kolab']['server']['freebusy_url_format'], $server); } else { return 'https://' . $server . '/freebusy'; } case 'imap': $server = $this->get(KOLAB_ATTR_IMAPHOST); if (!is_a($server, 'PEAR_Error') && !empty($server)) { return $server; } case 'homeserver': default: $server = $this->get(KOLAB_ATTR_HOMESERVER); if (empty($server)) { $server = $_SERVER['SERVER_NAME']; } return $server; } } /** * Generates an ID for the given information. * * @param array $info The data of the object. * * @static * * @return string|PEAR_Error The ID. */ function generateId($info) { global $conf; /** The fields that should get mapped into the user ID. */ if (isset($conf['kolab']['server']['user_id_mapfields'])) { $id_mapfields = $conf['kolab']['server']['user_id_mapfields']; } else { $id_mapfields = array('givenName', 'sn'); } /** The user ID format. */ if (isset($conf['kolab']['server']['user_id_format'])) { $id_format = $conf['kolab']['server']['user_id_format']; } else { $id_format = '%s %s'; } $fieldarray = array(); foreach ($id_mapfields as $mapfield) { if (isset($info[$mapfield])) { $fieldarray[] = $info[$mapfield]; } else { $fieldarray[] = ''; } } return trim(vsprintf($id_format, $fieldarray), " \t\n\r\0\x0B,"); } /** * Saves object information. * * @param array $info The information about the object. * * @return boolean|PEAR_Error True on success. */ function save($info) { if (!isset($info['cn'])) { if (!isset($info['sn']) || !isset($info['givenName'])) { return PEAR::raiseError('Either the last name or the given name is missing!'); } else { $info['cn'] = $this->generateId($info); } } if (isset($conf['kolab']['server']['user_mapping'])) { $mapped = array(); $map = $conf['kolab']['server']['user_mapping']; foreach ($map as $key => $val) { $mapped[$val] = $info[$key]; } $info = $mapped; } if (isset($conf['kolab']['server']['user_mapping'])) { $mapped = array(); $map = $conf['kolab']['server']['user_mapping']; foreach ($map as $key => $val) { $mapped[$val] = $info[$key]; } $info = $mapped; } return parent::save($info); } }; * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** Define the different Kolab object types */ define('KOLAB_OBJECT_ADDRESS', 'Horde_Kolab_Server_Object_address'); define('KOLAB_OBJECT_ADMINISTRATOR', 'Horde_Kolab_Server_Object_administrator'); define('KOLAB_OBJECT_DOMAINMAINTAINER', 'Horde_Kolab_Server_Object_domainmaintainer'); define('KOLAB_OBJECT_GROUP', 'Horde_Kolab_Server_Object_group'); define('KOLAB_OBJECT_DISTLIST', 'Horde_Kolab_Server_Object_distlist'); define('KOLAB_OBJECT_MAINTAINER', 'Horde_Kolab_Server_Object_maintainer'); define('KOLAB_OBJECT_SHAREDFOLDER', 'Horde_Kolab_Server_Object_sharedfolder'); define('KOLAB_OBJECT_USER', 'Horde_Kolab_Server_Object_user'); define('KOLAB_OBJECT_SERVER', 'Horde_Kolab_Server_Object_server'); /** Define the possible Kolab object attributes */ define('KOLAB_ATTR_OC', 'objectClass'); define('KOLAB_ATTR_UID', 'dn'); define('KOLAB_ATTR_ID', 'id'); define('KOLAB_ATTR_SN', 'sn'); define('KOLAB_ATTR_CN', 'cn'); define('KOLAB_ATTR_GIVENNAME', 'givenName'); define('KOLAB_ATTR_FN', 'fn'); define('KOLAB_ATTR_LNFN', 'lnfn'); define('KOLAB_ATTR_FNLN', 'fnln'); define('KOLAB_ATTR_MAIL', 'mail'); define('KOLAB_ATTR_ALIAS', 'alias'); define('KOLAB_ATTR_SID', 'uid'); define('KOLAB_ATTR_ACL', 'acl'); define('KOLAB_ATTR_MEMBER', 'member'); define('KOLAB_ATTR_USERTYPE', 'usertype'); define('KOLAB_ATTR_DOMAIN', 'domain'); define('KOLAB_ATTR_FOLDERTYPE', 'kolabFolderType'); define('KOLAB_ATTR_USERPASSWORD', 'userPassword'); define('KOLAB_ATTR_DELETED', 'kolabDeleteFlag'); define('KOLAB_ATTR_FREEBUSYHOST', 'kolabFreeBusyServer'); define('KOLAB_ATTR_IMAPHOST', 'kolabImapServer'); define('KOLAB_ATTR_HOMESERVER', 'kolabHomeServer'); define('KOLAB_ATTR_KOLABDELEGATE','kolabDelegate'); define('KOLAB_ATTR_IPOLICY', 'kolabInvitationPolicy'); define('KOLAB_ATTR_QUOTA', 'cyrus-userquota'); define('KOLAB_ATTR_FBPAST', 'kolabFreeBusyPast'); define('KOLAB_ATTR_FBFUTURE', 'kolabFreeBusyFuture'); define('KOLAB_ATTR_VISIBILITY', 'visible'); /** Define the possible Kolab object classes */ define('KOLAB_OC_TOP', 'top'); define('KOLAB_OC_INETORGPERSON', 'inetOrgPerson'); define('KOLAB_OC_KOLABINETORGPERSON', 'kolabInetOrgPerson'); define('KOLAB_OC_HORDEPERSON', 'hordePerson'); define('KOLAB_OC_KOLABGROUPOFNAMES', 'kolabGroupOfNames'); define('KOLAB_OC_KOLABSHAREDFOLDER', 'kolabSharedFolder'); /** Define the possible Kolab user types */ define('KOLAB_UT_STANDARD', 0); define('KOLAB_UT_INTERNAL', 1); define('KOLAB_UT_GROUP', 2); define('KOLAB_UT_RESOURCE', 3); /** * This class provides methods to deal with Kolab objects stored in * the Kolab db. * * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object.php,v 1.2.2.9 2009-04-25 08:56:34 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_Server_Object { /** * Link into the Kolab server. * * @var Kolab_Server */ var $_db; /** * UID of this object on the Kolab server. * * @var string */ var $_uid; /** * The cached LDAP result * * FIXME: Include _ldap here * * @var mixed */ var $_cache = false; /** FIXME: Add an attribute cache for the get() function */ /** * The LDAP filter to retrieve this object type. * * @var string */ var $filter = ''; /** * The group the UID must be member of so that this object really * matches this class type. This may not include the root UID. * * @var string */ var $required_group; /** * The LDAP attributes supported by this class. * * @var array */ var $_supported_attributes = array(); /** * Attributes derived from the LDAP values. * * @var array */ var $_derived_attributes = array( KOLAB_ATTR_ID, ); /** * The attributes required when creating an object of this class. * * @var array */ var $_required_attributes = array(); /** * The ldap classes for this type of object. * * @var array */ var $_object_classes = array(); /** * Sort by this attributes (must be a LDAP attribute). * * @var string */ var $sort_by = KOLAB_ATTR_SN; /** * Initialize the Kolab Object. Provide either the UID or a * LDAP search result. * * @param Horde_Kolab_Server &$db The link into the Kolab db. * @param string $uid UID of the object. * @param array $data A possible array of data for the object */ function Horde_Kolab_Server_Object(&$db, $uid = null, $data = null) { $this->_db = &$db; if (empty($uid)) { if (empty($data) || !isset($data[KOLAB_ATTR_UID])) { $this->_cache = PEAR::raiseError(_('Specify either the UID or a search result!')); return; } if (is_array($data[KOLAB_ATTR_UID])) { $this->_uid = $data[KOLAB_ATTR_UID][0]; } else { $this->_uid = $data[KOLAB_ATTR_UID]; } $this->_cache = $data; } else { $this->_uid = $uid; } } /** * Attempts to return a concrete Horde_Kolab_Server_Object instance based on * $type. * * @param mixed $type The type of the Horde_Kolab_Server_Object subclass * to return. * @param string $uid UID of the object * @param array &$storage A link to the Kolab_Server class handling read/write. * @param array $data A possible array of data for the object * * @return Horde_Kolab_Server_Object|PEAR_Error The newly created concrete * Horde_Kolab_Server_Object instance. */ function &factory($type, $uid, &$storage, $data = null) { $result = Horde_Kolab_Server_Object::loadClass($type); if (is_a($result, 'PEAR_Error')) { return $result; } if (class_exists($type)) { $object = &new $type($storage, $uid, $data); } else { $object = PEAR::raiseError('Class definition of ' . $type . ' not found.'); } return $object; } /** * Attempts to load the concrete Horde_Kolab_Server_Object class based on * $type. * * @param mixed $type The type of the Horde_Kolab_Server_Object subclass. * * @static * * @return true|PEAR_Error True if successfull. */ function loadClass($type) { if (in_array($type, array(KOLAB_OBJECT_ADDRESS, KOLAB_OBJECT_ADMINISTRATOR, KOLAB_OBJECT_DISTLIST, KOLAB_OBJECT_DOMAINMAINTAINER, KOLAB_OBJECT_GROUP, KOLAB_OBJECT_MAINTAINER, KOLAB_OBJECT_SHAREDFOLDER, KOLAB_OBJECT_USER, KOLAB_OBJECT_SERVER))) { $name = substr($type, 26); } else { return PEAR::raiseError(sprintf('Object type "%s" not supported.', $type)); } $name = basename($name); if (file_exists(dirname(__FILE__) . '/Object/' . $name . '.php')) { include_once dirname(__FILE__) . '/Object/' . $name . '.php'; } } /** * Does the object exist? * * @return NULL */ function exists() { $this->_read(); if (!$this->_cache || is_a($this->_cache, 'PEAR_Error')) { return false; } return true; } /** * Read the object into the cache * * @return NULL */ function _read() { $this->_cache = $this->_db->read($this->_uid, $this->_supported_attributes); } /** * Get the specified attribute of this object * * @param string $attr The attribute to read * * @return string the value of this attribute */ function get($attr) { if ($attr != KOLAB_ATTR_UID) { if (!in_array($attr, $this->_supported_attributes) && !in_array($attr, $this->_derived_attributes)) { return PEAR::raiseError(sprintf(_("Attribute \"%s\" not supported!"), $attr)); } if (!$this->_cache) { $this->_read(); } if (is_a($this->_cache, 'PEAR_Error')) { return $this->_cache; } } if (in_array($attr, $this->_derived_attributes)) { return $this->_derive($attr); } switch ($attr) { case KOLAB_ATTR_UID: return $this->_getUID(); case KOLAB_ATTR_FN: return $this->_getFn(); case KOLAB_ATTR_SN: case KOLAB_ATTR_CN: case KOLAB_ATTR_GIVENNAME: case KOLAB_ATTR_MAIL: case KOLAB_ATTR_SID: case KOLAB_ATTR_USERPASSWORD: case KOLAB_ATTR_DELETED: case KOLAB_ATTR_IMAPHOST: case KOLAB_ATTR_FREEBUSYHOST: case KOLAB_ATTR_HOMESERVER: case KOLAB_ATTR_FBPAST: case KOLAB_ATTR_FBFUTURE: case KOLAB_ATTR_FOLDERTYPE: return $this->_get($attr, true); default: return $this->_get($attr, false); } } /** * Get the specified attribute of this object * * @param string $attr The attribute to read * @param boolean $single Should a single value be returned * or are multiple values allowed? * * @return string the value of this attribute */ function _get($attr, $single = true) { if (isset($this->_cache[$attr])) { if ($single && is_array($this->_cache[$attr])) { return $this->_cache[$attr][0]; } else { return $this->_cache[$attr]; } } return false; } /** * Derive an attribute value. * * @param string $attr The attribute to derive. * * @return mixed The value of the attribute. */ function _derive($attr) { switch ($attr) { case KOLAB_ATTR_ID: $result = split(',', $this->_uid); if (substr($result[0], 0, 3) == 'cn=') { return substr($result[0], 3); } else { return $result[0]; } case KOLAB_ATTR_LNFN: $gn = $this->_get(KOLAB_ATTR_GIVENNAME, true); $sn = $this->_get(KOLAB_ATTR_SN, true); return sprintf('%s, %s', $sn, $gn); case KOLAB_ATTR_FNLN: $gn = $this->_get(KOLAB_ATTR_GIVENNAME, true); $sn = $this->_get(KOLAB_ATTR_SN, true); return sprintf('%s %s', $gn, $sn); default: return false; } } /** * Convert the object attributes to a hash. * * @param string $attrs The attributes to return. * * @return array|PEAR_Error The hash representing this object. */ function toHash($attrs = null) { if (!isset($attrs)) { $attrs = array(); } $result = array(); foreach ($attrs as $key) { $value = $this->get($key); if (is_a($value, 'PEAR_Error')) { return $value; } $result[$key] = $value; } return $result; } /** * Get the UID of this object * * @return string the UID of this object */ function _getUid() { return $this->_uid; } /** * Get the "first name" attribute of this object * * FIXME: This should get refactored to be combined with the Id value. * * @return string the "first name" of this object */ function _getFn() { $sn = $this->_get(KOLAB_ATTR_SN, true); $cn = $this->_get(KOLAB_ATTR_CN, true); return trim(substr($cn, 0, strlen($cn) - strlen($sn))); } /** * Get the groups for this object * * @return mixed An array of group ids or a PEAR Error in case of * an error. */ function getGroups() { return array(); } /** * Returns the server url of the given type for this user. * * This method can be used to encapsulate multidomain support. * * @param string $server_type The type of server URL that should be returned. * * @return string|PEAR_Error The server url or empty. */ function getServer($server_type) { return PEAR::raiseError('Not implemented!'); } /** * Generates an ID for the given information. * * @param array $info The data of the object. * * @static * * @return string|PEAR_Error The ID. */ function generateId($info) { $id_mapfields = array('givenName', 'sn'); $id_format = '%s %s'; $fieldarray = array(); foreach ($id_mapfields as $mapfield) { if (isset($info[$mapfield])) { $fieldarray[] = $info[$mapfield]; } else { $fieldarray[] = ''; } } return trim(vsprintf($id_format, $fieldarray), " \t\n\r\0\x0B,"); } /** * Saves object information. * * @param array $info The information about the object. * * @return boolean|PEAR_Error True on success. */ function save($info) { foreach ($this->_required_attributes as $attribute) { if (!isset($info[$attribute])) { return PEAR::raiseError(sprintf('The value for "%s" is missing!', $attribute)); } } $info['objectClass'] = $this->_object_classes; $result = $this->_db->save($this->_uid, $info); if ($result === false || is_a($result, 'PEAR_Error')) { return $result; } $this->_cache = $info; return $result; } }; * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** Require the LDAP based class as our base class */ require_once 'Horde/Kolab/Server/ldap.php'; /** * This class provides a class for testing the Kolab Server DB. * * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/test.php,v 1.2.2.8 2009-04-25 08:56:34 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_Server_test extends Horde_Kolab_Server_ldap { /** * Array holding the current result set. * * @var array */ var $_result; /** * Buffer for error numbers. * * @var int */ var $_errno = 0; /** * Buffer for error descriptions. * * @var int */ var $_error = ''; /** * Attribute used for sorting. * * @var string */ var $_sort_by; /** * A result cache for iterating over the result. * * @var array */ var $_current_result; /** * An index into the current result for iterating. * * @var int */ var $_current_index; /** * Construct a new Horde_Kolab_Server object. * * @param array $params Parameter array. */ function Horde_Kolab_Server_test($params = array()) { if (isset($params['data'])) { $GLOBALS['KOLAB_SERVER_TEST_DATA'] = $params['data']; } else { if (!isset($GLOBALS['KOLAB_SERVER_TEST_DATA'])) { $GLOBALS['KOLAB_SERVER_TEST_DATA'] = array(); } } Horde_Kolab_Server::Horde_Kolab_Server($params); } /** * Binds the LDAP connection with a specific user and pass. * * @param string $dn DN to bind with * @param string $pw Password associated to this DN. * * @return boolean|PEAR_Error Whether or not the binding succeeded. */ function _bind($dn = false, $pw = '') { if (!$dn) { if (isset($this->_params['uid'])) { $dn = $this->_params['uid']; } else { $dn = ''; } } if (!$pw) { if (isset($this->_params['pass'])) { $pw = $this->_params['pass']; } } if (!empty($dn)) { if (!isset($GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn])) { return PEAR::raiseError('User does not exist!'); } $this->_bound = true; $data = $this->_read($dn, $attrs = array('userPassword')); if (is_a($data, 'PEAR_Error')) { $this->_bound = false; return $data; } if (!isset($data['userPassword'])) { $this->_bound = false; return PEAR::raiseError('User has no password entry!'); } $this->_bound = $data['userPassword'][0] == $pw; if (!$this->_bound) { return PEAR::raiseError('Incorrect password!'); } } else if (!empty($this->_params['no_anonymous_bind'])) { $this->_bound = false; return PEAR::raiseError('Anonymous bind is not allowed!'); } else { $this->_bound = true; } return $this->_bound; } /** * Disconnect from LDAP. * * @return NULL */ function unbind() { $this->_bound = false; } /** * Parse LDAP filter. * Partially derived from Net_LDAP_Filter. * * @param string $filter The filter string. * * @return array|PEAR_Error An array of the parsed filter. */ function _parse($filter) { $result = array(); if (preg_match('/^\((.+?)\)$/', $filter, $matches)) { if (in_array(substr($matches[1], 0, 1), array('!', '|', '&'))) { $result['op'] = substr($matches[1], 0, 1); $result['sub'] = $this->_parseSub(substr($matches[1], 1)); return $result; } else { if (stristr($matches[1], ')(')) { return PEAR::raiseError("Filter parsing error: invalid filter syntax - multiple leaf components detected!"); } else { $filter_parts = preg_split('/(?|<|>=|<=)/', $matches[1], 2, PREG_SPLIT_DELIM_CAPTURE); if (count($filter_parts) != 3) { return PEAR::raiseError("Filter parsing error: invalid filter syntax - unknown matching rule used"); } else { $result['att'] = $filter_parts[0]; $result['log'] = $filter_parts[1]; $result['val'] = $filter_parts[2]; return $result; } } } } else { return PEAR::raiseError(sprintf("Filter parsing error: %s - filter components must be enclosed in round brackets", $filter)); } } /** * Parse a LDAP subfilter. * * @param string $filter The subfilter string. * * @return array|PEAR_Error An array of the parsed subfilter. */ function _parseSub($filter) { $result = array(); $level = 0; $collect = ''; while (preg_match('/^(\(.+?\))(.*)/', $filter, $matches)) { if (in_array(substr($matches[1], 0, 2), array('(!', '(|', '(&'))) { $level++; } if ($level) { $collect .= $matches[1]; if (substr($matches[2], 0, 1) == ')') { $collect .= ')'; $matches[2] = substr($matches[2], 1); $level--; if (!$level) { $result[] = $this->_parse($collect); } } } else { $result[] = $this->_parse($matches[1]); } $filter = $matches[2]; } return $result; } /** * Search for an object. * * @param string $filter Filter criteria. * @param array $attributes Restrict the search result to * these attributes. * @param string $base DN of the search base. * * @return array|PEAR_Error A LDAP serach result. */ function _search($filter, $attributes = null, $base = null) { if (!$this->_bound) { $result = $this->_bind(); if (is_a($result, 'PEAR_Error')) { return $result; } } if (!empty($attributes)) { $this->mapKeys($attributes); } $filter = $this->_parse($filter); if (is_a($filter, 'PEAR_Error')) { return $filter; } $result = $this->_doSearch($filter, $attributes); if (empty($result)) { return null; } if ($base) { $subtree = array(); foreach ($result as $entry) { if (strpos($entry['dn'], $base)) { $subtree[] = $entry; } } $result = $subtree; } $this->unmapAttributes($result); return $result; } /** * Perform the search. * * @param array $filter Filter criteria- * @param array $attributes Restrict the search result to * these attributes. * * @return array|PEAR_Error A LDAP serach result. */ function _doSearch($filter, $attributes = null) { if (isset($filter['log'])) { $result = array(); foreach ($GLOBALS['KOLAB_SERVER_TEST_DATA'] as $element) { if (isset($element['data'][$filter['att']])) { switch ($filter['log']) { case '=': $value = $element['data'][$filter['att']]; if ((($filter['val'] == '*' || $filter['val'] == '\2a') && !empty($value)) || $value == $filter['val'] || (is_array($value) && in_array($filter['val'], $value))) { if (empty($attributes)) { $result[] = $element; } else { $selection = $element; foreach ($element['data'] as $attr => $value) { if (!in_array($attr, $attributes)) { unset($selection['data'][$attr]); } } $result[] = $selection; } } break; default: return PEAR::raiseError(_("Not implemented!")); } } } return $result; } else { $subresult = array(); $filtercount = count($filter['sub']); foreach ($filter['sub'] as $subfilter) { $subresult = array_merge($subresult, $this->_doSearch($subfilter, $attributes)); } $result = array(); $dns = array(); foreach ($subresult as $element) { $dns[] = $element['dn']; $result[$element['dn']] = $element; } switch ($filter['op']) { case '&': $count = array_count_values($dns); $selection = array(); foreach ($count as $dn => $value) { if ($value == $filtercount) { $selection[] = $result[$dn]; } } return $selection; case '|': return array_values($result); case '!': $dns = array(); foreach ($result as $entry) { if (!in_array($entry['dn'], $dns) ) { $dns[] = $entry['dn']; } } $all_dns = array_keys($GLOBALS['KOLAB_SERVER_TEST_DATA']); $diff = array_diff($all_dns, $dns); $result = array(); foreach ($diff as $dn) { if (empty($attributes)) { $result[] = $GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn]; } else { $selection = $GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn]; foreach ($GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn]['data'] as $attr => $value) { if (!in_array($attr, $attributes)) { unset($selection['data'][$attr]); } } $result[] = $selection; } } return $result; default: return PEAR::raiseError(_("Not implemented!")); } } } /** * Read object data. * * @param string $dn The object to retrieve. * @param string $attrs Restrict to these attributes * * @return array|PEAR_Error An array of attributes. */ function _read($dn, $attrs = null) { if (!$this->_bound) { $result = $this->_bind(); if (is_a($result, 'PEAR_Error')) { return $result; } } if (!isset($GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn])) { return PEAR::raiseError(sprintf("LDAP Error: No such object: %s: No such object", $dn)); } if (empty($attrs)) { $data = $GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn]['data']; $this->unmapAttributes($data); return $data; } else { $this->mapKeys($attrs); $result = array(); $data = $GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn]['data']; foreach ($attrs as $attr) { if (isset($data[$attr])) { $result[$attr] = $data[$attr]; array_push($result, $attr); } } $this->unmapAttributes($result); $result['count'] = 1; return $result; } } /** * Add a new object * * @param string $dn The DN of the object to be added. * @param array $data The attributes of the object to be added. * * @return boolean True if adding succeeded. */ function _add($dn, $data) { if (!$this->_bound) { $result = $this->_bind(); if (is_a($result, 'PEAR_Error')) { return $result; } } $this->mapAttributes($data); $ldap_data = array(); foreach ($data as $key => $val) { if (!is_array($val)) { $val = array($val); } $ldap_data[$key] = array_merge(array('count' => count($val)), $val); } $GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn] = array( 'dn' => $dn, 'data' => $ldap_data ); } /** * Count the number of results. * * @param array $result The LDAP search result. * * @return int The number of records found. */ function _count($result) { if (is_array($result)) { return count($result); } else { return false; } } /** * Return the dn of an entry. * * @param array $entry The LDAP entry. * * @return string The DN of the entry. */ function _getDn($entry) { if (is_array($entry) && isset($entry['dn'])) { if (isset($entry['count'])) { return $entry['dn'][0]; } else { return $entry['dn']; } } return false; } /** * Return the attributes of an entry. * * @param array $entry The LDAP entry. * * @return array The attributes of the entry. */ function _getAttributes($entry) { if (is_array($entry)) { return $entry; } return false; } /** * Return the current entry of a result. * * @return mixe The current entry of the result or false. */ function _fetchEntry() { if (is_array($this->_current_result) && $this->_current_index < count($this->_current_result)) { $data = array_keys($this->_current_result[$this->_current_index]['data']); $data['count'] = 1; $data['dn'] = array($this->_current_result[$this->_current_index]['dn']); $data['dn']['count'] = 1; foreach ($this->_current_result[$this->_current_index]['data'] as $attr => $value) { if (!is_array($value)) { $value = array($value); } $data[$attr] = $value; $data[$attr]['count'] = count($value); } $this->_current_index++; return $data; } return false; } /** * Return the first entry of a result. * * @param array $result The LDAP search result. * * @return mixed The first entry of the result or false. */ function _firstEntry($result) { $this->_current_result = $result; $this->_current_index = 0; return $this->_fetchEntry(); } /** * Return the next entry of a result. * * @param resource $entry The current LDAP entry. * * @return resource The next entry of the result. */ function _nextEntry($entry) { return $this->_fetchEntry(); } /** * Return the entries of a result. * * @param array $result The LDAP search result. * * @return mixed The entries of the result or false. */ function _getEntries($result) { if (is_array($result)) { $data = array(); $data['count'] = count($result); foreach ($result as $entry) { $t = $entry['data']; $t['dn'] = $entry['dn']; $data[] = $t; } return $data; } return false; } /** * Sort the entries of a result. * * @param resource &$result The LDAP search result. * @param string $attribute The attribute used for sorting. * * @return boolean True if sorting succeeded. */ function _sort(&$result, $attribute) { if (empty($result)) { return $result; } $this->_sort_by = $attribute; usort($result, array($this, '_resultSort')); return false; } /** * Sort two entries. * * @param array $a First entry. * @param array $b Second entry. * * @return int Comparison result. */ function _resultSort($a, $b) { $x = isset($a['data'][$this->_sort_by][0])?$a['data'][$this->_sort_by][0]:''; $y = isset($b['data'][$this->_sort_by][0])?$b['data'][$this->_sort_by][0]:''; return strcasecmp($x, $y); } /** * Return the current LDAP error number. * * @return int The current LDAP error number. */ function _errno() { return $this->_errno; } /** * Return the current LDAP error description. * * @return string The current LDAP error description. */ function _error() { return $this->_error; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** We need PEAR */ require_once 'PEAR.php'; /** Provide access to the Kolab specific objects. */ require_once 'Horde/Kolab/Server/Object.php'; /** Define types of return values. */ define('KOLAB_SERVER_RESULT_SINGLE', 1); define('KOLAB_SERVER_RESULT_STRICT', 2); define('KOLAB_SERVER_RESULT_MANY', 3); /** * This class provides methods to deal with Kolab objects stored in * the Kolab object db. * * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server.php,v 1.2.2.9 2009-04-25 08:56:34 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_Server { /** * Server parameters. * * @var array */ var $_params = array(); /** * The UID of the current user. * * @var string */ var $uid; /** * Valid Kolab object types * * @var array */ var $valid_types = array( KOLAB_OBJECT_ADDRESS, KOLAB_OBJECT_ADMINISTRATOR, KOLAB_OBJECT_DISTLIST, KOLAB_OBJECT_DOMAINMAINTAINER, KOLAB_OBJECT_GROUP, KOLAB_OBJECT_MAINTAINER, KOLAB_OBJECT_SERVER, KOLAB_OBJECT_SHAREDFOLDER, KOLAB_OBJECT_USER, ); /** * Construct a new Horde_Kolab_Server object. * * @param array $params Parameter array. */ function Horde_Kolab_Server($params = array()) { $this->_params = $params; if (isset($params['uid'])) { $this->uid = $params['uid']; } } /** * Attempts to return a concrete Horde_Kolab_Server instance based * on $driver. * * @param mixed $driver The type of concrete Horde_Kolab_Server subclass to * return. * @param array $params A hash containing any additional * configuration or connection parameters a subclass * might need. * * @return Horde_Kolab_Server|PEAR_Error The newly created concrete * Horde_Kolab_Server instance. */ function &factory($driver, $params = array()) { $driver = basename($driver); if (empty($driver) || $driver == 'none') { $db = new Horde_Kolab_Server($params); return $db; } if (file_exists(dirname(__FILE__) . '/Server/' . $driver . '.php')) { include_once dirname(__FILE__) . '/Server/' . $driver . '.php'; } $class = 'Horde_Kolab_Server_' . $driver; if (class_exists($class)) { $db = new $class($params); } else { $db = PEAR::raiseError('Class definition of ' . $class . ' not found.'); } return $db; } /** * Attempts to return a reference to a concrete Horde_Kolab_Server * instance based on $driver. It will only create a new instance * if no Horde_Kolab_Server instance with the same parameters currently * exists. * * This method must be invoked as: * $var = &Horde_Kolab_Server::singleton() * * @param array $params An array of optional login parameters. May * contain "uid" (for the login uid), "user" * (if the uid is not yet known), and "pass" * (for a password). * * @return Horde_Kolab_Server|PEAR_Error The concrete Horde_Kolab_Server * reference. */ function &singleton($params = null) { global $conf; static $instances = array(); if (isset($conf['kolab']['server']['driver'])) { $driver = $conf['kolab']['server']['driver']; if (isset($conf['kolab']['server']['params'])) { $server_params = $conf['kolab']['server']['params']; } else { $server_params = array(); } } else if (isset($conf['kolab']['ldap']['server']) && isset($conf['kolab']['ldap']['basedn']) && isset($conf['kolab']['ldap']['phpdn']) && isset($conf['kolab']['ldap']['phppw'])) { $driver = 'ldap'; $server_params = array('server' => $conf['kolab']['ldap']['server'], 'base_dn' => $conf['kolab']['ldap']['basedn'], 'uid' => $conf['kolab']['ldap']['phpdn'], 'pass' => $conf['kolab']['ldap']['phppw']); if (isset($conf['kolab']['ldap']['map'])) { $server_params['map'] = $conf['kolab']['ldap']['map']; } } else { $driver = null; $server_params = array(); } if (!empty($params)) { if (isset($params['user'])) { $tmp_server = &Horde_Kolab_Server::factory($driver, $server_params); $uid = $tmp_server->uidForIdOrMail($params['user']); if (is_a($uid, 'PEAR_Error')) { return PEAR::raiseError(sprintf(_("Failed identifying the UID of the Kolab user %s. Error was: %s"), $params['user'], $uid->getMessage())); } $server_params['uid'] = $uid; } if (isset($params['pass'])) { if (isset($server_params['pass'])) { $server_params['search_pass'] = $server_params['pass']; } $server_params['pass'] = $params['pass']; } if (isset($params['uid'])) { if (isset($server_params['uid'])) { $server_params['search_uid'] = $server_params['pass']; } $server_params['uid'] = $params['uid']; } } $sparam = $server_params; $sparam['pass'] = isset($sparam['pass']) ? md5($sparam['pass']) : ''; $signature = serialize(array($driver, $sparam)); if (empty($instances[$signature])) { $instances[$signature] = &Horde_Kolab_Server::factory($driver, $server_params); } return $instances[$signature]; } /** * Return the root of the UID values on this server. * * @return string The base UID on this server (base DN on ldap). */ function getBaseUid() { return ''; } /** * Fetch a Kolab object. * * @param string $uid The UID of the object to fetch. * @param string $type The type of the object to fetch. * * @return Kolab_Object|PEAR_Error The corresponding Kolab object. */ function &fetch($uid = null, $type = null) { if (!isset($uid)) { $uid = $this->uid; } if (empty($type)) { $type = $this->_determineType($uid); if (is_a($type, 'PEAR_Error')) { return $type; } } else { if (!in_array($type, $this->valid_types)) { return PEAR::raiseError(sprintf(_("Invalid Kolab object type \"%s\"."), $type)); } } $object = &Horde_Kolab_Server_Object::factory($type, $uid, $this); return $object; } /** * Add a Kolab object. * * @param array $info The object to store. * * @return Kolab_Object|PEAR_Error The newly created Kolab object. */ function &add($info) { if (!isset($info['type'])) { return PEAR::raiseError('The type of a new object must be specified!'); } if (!in_array($info['type'], $this->valid_types)) { return PEAR::raiseError(sprintf(_("Invalid Kolab object type \"%s\"."), $type)); } $uid = $this->generateUid($info['type'], $info); if (is_a($uid, 'PEAR_Error')) { return $uid; } $object = &Horde_Kolab_Server_Object::factory($info['type'], $uid, $this); if (is_a($object, 'PEAR_Error')) { return $object; } if ($object->exists()) { return PEAR::raiseError('The object does already exist!'); } $result = $object->save($info); if (is_a($result, 'PEAR_Error')) { return PEAR::raiseError(sprintf('Adding object failed: %s', $result->getMessage())); } return $object; } /** * Update or create a Kolab object. * * @param string $type The type of the object to store. * @param array $info Any additional information about the object to store. * @param string $uid The unique id of the object to store. * * @return Kolab_Object|PEAR_Error The updated Kolab object. */ function &store($type, $info, $uid = null) { if (!in_array($type, $this->valid_types)) { return PEAR::raiseError(sprintf(_("Invalid Kolab object type \"%s\"."), $type)); } if (empty($uid)) { $uid = $this->generateUid($type, $info); } $object = &Horde_Kolab_Server_Object::factory($type, $uid, $this); $result = $object->save($info); if (is_a($result, 'PEAR_Error')) { return $result; } return $object; } /** * Get the groups for this object * * @param string $uid The UID of the object to fetch. * * @return array|PEAR_Error An array of group ids. */ function getGroups($uid) { return array(); } /** * Read object data. * * @param string $uid The object to retrieve. * @param string $attrs Restrict to these attributes. * * @return array|PEAR_Error An array of attributes. */ function read($uid, $attrs = null) { return $this->_read($uid, $attrs); } /** * Stub for reading object data. * * @param string $uid The object to retrieve. * @param string $attrs Restrict to these attributes. * * @return array|PEAR_Error An array of attributes. */ function _read($uid, $attrs = null) { return PEAR::raiseError(_("Not implemented!")); } /** * Stub for saving object data. * * @param string $uid The object to save. * @param string $data The data of the object. * * @return array|PEAR_Error An array of attributes. */ function save($uid, $data) { return PEAR::raiseError(_("Not implemented!")); } /** * Determine the type of a Kolab object. * * @param string $uid The UID of the object to examine. * * @return string The corresponding Kolab object type. */ function _determineType($uid) { return KOLAB_OBJECT_USER; } /** * Identify the primary mail attribute for the first object found * with the given ID or mail. * * @param string $id Search for objects with this ID/mail. * * @return mixed|PEAR_Error The mail address or false if there was * no result. */ function mailForIdOrMail($id) { /* In the default class we just return the id */ return $id; } /** * Returns a list of allowed email addresses for the given user. * * @param string $user The user name. * * @return array|PEAR_Error An array of allowed mail addresses. */ function addrsForIdOrMail($user) { /* In the default class we just return the user name */ return $user; } /** * Return the UID for a given primary mail, uid, or alias. * * @param string $mail A valid mail address for the user. * * @return mixed|PEAR_Error The UID or false if there was no result. */ function uidForMailAddress($mail) { /* In the default class we just return the mail address */ return $mail; } /** * Identify the UID for the first user found using a specified * attribute value. * * @param string $attr The name of the attribute used for searching. * @param string $value The desired value of the attribute. * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. * * @return mixed|PEAR_Error The UID or false if there was no result. */ function uidForAttr($attr, $value, $restrict = KOLAB_SERVER_RESULT_SINGLE) { /* In the default class we just return false */ return false; } /** * Identify the GID for the first group found using a specified * attribute value. * * @param string $attr The name of the attribute used for searching. * @param string $value The desired value of the attribute. * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. * * @return mixed|PEAR_Error The GID or false if there was no result. */ function gidForAttr($attr, $value, $restrict = KOLAB_SERVER_RESULT_SINGLE) { /* In the default class we just return false */ return false; } /** * Is the given UID member of the group with the given mail address? * * @param string $uid UID of the user. * @param string $mail Search the group with this mail address. * * @return boolean|PEAR_Error True in case the user is in the * group, false otherwise. */ function memberOfGroupAddress($uid, $mail) { /* No groups in the default class */ return false; } /** * Identify the UID for the first object found with the given ID. * * @param string $id Search for objects with this ID. * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. * * @return mixed|PEAR_Error The UID or false if there was no result. */ function uidForId($id, $restrict = KOLAB_SERVER_RESULT_SINGLE) { return $this->uidForAttr(KOLAB_ATTR_SID, $id); } /** * Identify the UID for the first user found with the given mail. * * @param string $mail Search for users with this mail address. * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. * * @return mixed|PEAR_Error The UID or false if there was no result. */ function uidForMail($mail, $restrict = KOLAB_SERVER_RESULT_SINGLE) { return $this->uidForAttr('mail', $mail); } /** * Identify the GID for the first group found with the given mail. * * @param string $mail Search for groups with this mail address. * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. * * @return mixed|PEAR_Error The GID or false if there was no result. */ function gidForMail($mail, $restrict = KOLAB_SERVER_RESULT_SINGLE) { return $this->gidForAttr('mail', $mail); } /** * Identify the UID for the first object found with the given ID or mail. * * @param string $id Search for objects with this uid/mail. * * @return mixed|PEAR_Error The UID or false if there was no result. */ function uidForIdOrMail($id) { $uid = $this->uidForAttr(KOLAB_ATTR_SID, $id); if (!$uid) { $uid = $this->uidForAttr('mail', $id); } return $uid; } /** * Identify the UID for the first object found with the given alias. * * @param string $mail Search for objects with this mail alias. * @param int $restrict A KOLAB_SERVER_RESULT_* result restriction. * * @return mixed|PEAR_Error The UID or false if there was no result. */ function uidForAlias($mail, $restrict = KOLAB_SERVER_RESULT_SINGLE) { return $this->uidForAttr('alias', $mail); } /** * Identify the UID for the first object found with the given mail * address or alias. * * @param string $mail Search for objects with this mail address * or alias. * * @return mixed|PEAR_Error The UID or false if there was no result. */ function uidForMailOrAlias($mail) { $uid = $this->uidForAttr('alias', $mail); if (!$uid) { $uid = $this->uidForAttr('mail', $mail); } return $uid; } /** * Identify the UID for the first object found with the given ID, * mail or alias. * * @param string $id Search for objects with this ID/mail/alias. * * @return mixed|PEAR_Error The UID or false if there was no result. */ function uidForMailOrIdOrAlias($id) { $uid = $this->uidForAttr(KOLAB_ATTR_SID, $id); if (!$uid) { $uid = $this->uidForAttr('mail', $id); if (!$uid) { $uid = $this->uidForAttr('alias', $id); } } return $uid; } /** * Generate a hash representation for a list of objects. * * @param string $type The type of the objects to be listed * @param array $params Additional parameters. * * @return array|PEAR_Error An array of Kolab objects. */ function listHash($type, $params = null) { $list = $this->_listObjects($type, $params); if (is_a($list, 'PEAR_Error')) { return $list; } if (isset($params['attributes'])) { $attributes = $params['attributes']; } else { $attributes = null; } $hash = array(); foreach ($list as $entry) { $hash[] = $entry->toHash($attributes); } return $hash; } /** * List all objects of a specific type * * @param string $type The type of the objects to be listed * @param array $params Additional parameters. * * @return array|PEAR_Error An array of Kolab objects. */ function listObjects($type, $params = null) { if (!in_array($type, $this->valid_types)) { return PEAR::raiseError(sprintf(_("Invalid Kolab object type \"%s\"."), $type)); } return $this->_listObjects($type, $params); } /** * List all objects of a specific type * * @param string $type The type of the objects to be listed * @param array $params Additional parameters. * * @return array|PEAR_Error An array of Kolab objects. */ function _listObjects($type, $params = null) { return array(); } /** * Generates a unique ID for the given information. * * @param string $type The type of the object to create. * @param array $info Any additional information about the object to create. * * @return string|PEAR_Error The UID. */ function generateUid($type, $info) { if (!class_exists($type)) { $result = Horde_Kolab_Server_Object::loadClass($type); if (is_a($result, 'PEAR_Error')) { return $result; } } $id = call_user_func(array($type, 'generateId'), $info); if (is_a($id, 'PEAR_Error')) { return $id; } return $this->_generateUid($type, $id, $info); } /** * Generates a UID for the given information. * * @param string $type The type of the object to create. * @param string $id The id of the object. * @param array $info Any additional information about the object to create. * * @return string|PEAR_Error The UID. */ function _generateUid($type, $id, $info) { return $id; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Server */ /** We need the Auth library */ require_once 'Horde/Auth.php'; /** * The Horde_Kolab_Session class holds additional user details for the current * session. * * The core user credentials (login, pass) are kept within the Auth module and * can be retrieved using Auth::getAuth() respectively * Auth::getCredential('password'). Any additional Kolab user data * relevant for the user session should be accessed via the Horde_Kolab_Session * class. * * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Session.php,v 1.1.2.11 2009-02-07 14:09:56 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_Session { /** * User ID. * * @var string */ var $user_id; /** * User UID. * * @var string */ var $user_uid; /** * Primary user mail address. * * @var string */ var $user_mail; /** * Full name. * * @var string */ var $user_name = ''; /** * True if the Kolab_Server login was successfull. * * @var boolean|PEAR_Error */ var $auth; /** * The connection parameters for the IMAP server. * * @var array|PEAR_Error */ var $_imap_params; /** * Our IMAP connection. * * @var Horde_Kolab_IMAP */ var $_imap; /** * The free/busy server for the current user. * * @var array|PEAR_Error */ var $freebusy_server; /** * Constructor. * * @param string $user The session will be setup for the user with * this ID. * @param array $credentials An array of login credentials. For Kolab, * this must contain a "password" entry. */ function Horde_Kolab_Session($user = null, $credentials = null) { global $conf; if (empty($user)) { $user = Auth::getAuth(); if (empty($user)) { $user = 'anonymous'; } else if (!strpos($user, '@')) { $user = $user . '@' . (!empty($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost'); } } $this->user_id = $user; $this->_imap_params = array(); $user_object = null; if ($user != 'anonymous') { $server = $this->getServer($user, $credentials); if (is_a($server, 'PEAR_Error')) { $this->auth = $server; } else { $this->user_uid = $server->uid; $user_object = $server->fetch(); if (is_a($user_object, 'PEAR_Error')) { $this->auth = $user_object; } else { if (empty($conf['kolab']['imap']['allow_special_users']) && !is_a($user_object, 'Horde_Kolab_Server_Object_user')) { $this->auth = PEAR::raiseError(_('Access to special Kolab users is denied.')); } else if (isset($conf['kolab']['server']['deny_group'])) { $dn = $server->gidForMail($conf['kolab']['server']['deny_group']); if (is_a($dn, 'PEAR_Error')) { $this->auth = $dn; } else if (empty($dn)) { Horde::logMessage('The Kolab configuratin setting $conf[\'kolab\'][\'server\'][\'deny_group\'] holds a non-existing group!', __FILE__, __LINE__, PEAR_LOG_WARNING); $this->auth = true; } else if (in_array($dn, $user_object->getGroups())) { $this->auth = PEAR::raiseError(_('You are member of a group that may not login on this server.')); } else { $this->auth = true; } } else if (isset($conf['kolab']['server']['allow_group'])) { $dn = $server->gidForMail($conf['kolab']['server']['allow_group']); if (is_a($dn, 'PEAR_Error')) { $this->auth = $dn; } else if (empty($dn)) { Horde::logMessage('The Kolab configuratin setting $conf[\'kolab\'][\'server\'][\'allow_group\'] holds a non-existing group!', __FILE__, __LINE__, PEAR_LOG_WARNING); $this->auth = true; } else if (!in_array($dn, $user_object->getGroups())) { $this->auth = PEAR::raiseError(_('You are no member of a group that may login on this server.')); } else { $this->auth = true; } } else { /** * At this point we can be certain the user is an * authenticated Kolab user. */ $this->auth = true; } if (empty($this->auth) || is_a($this->auth, 'PEAR_Error')) { return; } $result = $user_object->get(KOLAB_ATTR_MAIL); if (!empty($result) && !is_a($result, 'PEAR_Error')) { $this->user_mail = $result; } $result = $user_object->get(KOLAB_ATTR_SID); if (!empty($result) && !is_a($result, 'PEAR_Error')) { $this->user_id = $result; } $result = $user_object->get(KOLAB_ATTR_FNLN); if (!empty($result) && !is_a($result, 'PEAR_Error')) { $this->user_name = $result; } $result = $user_object->getServer('imap'); if (!empty($result) && !is_a($result, 'PEAR_Error')) { $server = explode(':', $result, 2); if (!empty($server[0])) { $this->_imap_params['hostspec'] = $server[0]; } if (!empty($server[1])) { $this->_imap_params['port'] = $server[1]; } } $result = $user_object->getServer('freebusy'); if (!empty($result) && !is_a($result, 'PEAR_Error')) { $this->freebusy_server = $result; } } } } if (empty($this->user_mail)) { $this->user_mail = $user; } if (!isset($this->_imap_params['hostspec'])) { if (isset($conf['kolab']['imap']['server'])) { $this->_imap_params['hostspec'] = $conf['kolab']['imap']['server']; } else { $this->_imap_params['hostspec'] = 'localhost'; } } if (!isset($this->_imap_params['port'])) { if (isset($conf['kolab']['imap']['port'])) { $this->_imap_params['port'] = $conf['kolab']['imap']['port']; } else { $this->_imap_params['port'] = 143; } } $this->_imap_params['protocol'] = 'imap/notls/novalidate-cert'; } /** * Returns the properties that need to be serialized. * * @return array List of serializable properties. */ function __sleep() { $properties = get_object_vars($this); unset($properties['_imap']); $properties = array_keys($properties); return $properties; } /** * Get the Kolab Server connection. * * @param string $user The session will be setup for the user with * this ID. * @param array $credentials An array of login credentials. For Kolab, * this must contain a "password" entry. * * @return Horde_Kolab_Server|PEAR_Error The Kolab Server connection. */ function &getServer($user = null, $credentials = null) { /** We need the Kolab Server access. */ require_once 'Horde/Kolab/Server.php'; $params = array(); if ($this->user_uid) { $params['uid'] = $this->user_uid; $params['pass'] = Auth::getCredential('password'); } else if (isset($user)) { $params['user'] = $user; if (isset($credentials['password'])) { $params['pass'] = $credentials['password']; } else { $params['pass'] = Auth::getCredential('password'); } } return Horde_Kolab_Server::singleton($params); } /** * Get the IMAP connection parameters. * * @return array|PEAR_Error The IMAP connection parameters. */ function &getImapParams() { return $this->_imap_params; } /** * Create an IMAP connection. * * @return Horde_Kolab_IMAP|PEAR_Error The IMAP connection. */ function &getImap() { if (!isset($this->_imap)) { $params = $this->getImapParams(); if (is_a($params, 'PEAR_Error')) { return $params; } /** We need the Kolab IMAP library now. */ require_once 'Horde/Kolab/IMAP.php'; $imap = &Horde_Kolab_IMAP::singleton($params['hostspec'], $params['port'], true, false); if (is_a($imap, 'PEAR_Error')) { return $imap; } $result = $imap->connect(Auth::getAuth(), Auth::getCredential('password')); if (is_a($result, 'PEAR_Error')) { return $result; } $this->_imap = &$imap; } return $this->_imap; } /** * Attempts to return a reference to a concrete Horde_Kolab_Session instance. * * It will only create a new instance if no Horde_Kolab_Session instance * currently exists or if a user ID has been specified that does not match the * user ID/user mail of the current session. * * This method must be invoked as: * $var = &Horde_Kolab_Session::singleton(); * * @param string $user The session will be setup for the user with * this ID. * @param array $credentials An array of login credentials. For Kolab, * this must contain a "password" entry. * * @static * * @return Horde_Kolab_Session The concrete Session reference. */ function &singleton($user = null, $credentials = null, $destruct = false) { static $session; if (!isset($session)) { /** * Horde_Kolab_Server currently has no caching so we mainly * cache some user information here as reading this data * may be expensive when running in a multi-host * environment. */ require_once 'Horde/SessionObjects.php'; $hs = &Horde_SessionObjects::singleton(); $session = $hs->query('kolab_session'); } if (empty($user)) { $user = Auth::getAuth(); } if ($destruct || empty($session) || ($user != $session->user_mail && $user != $session->user_id)) { $session = new Horde_Kolab_Session($user, $credentials); } register_shutdown_function(array(&$session, 'shutdown')); return $session; } /** * Stores the object in the session cache. * * @return NULL */ function shutdown() { require_once 'Horde/SessionObjects.php'; $session = &Horde_SessionObjects::singleton(); $session->overwrite('kolab_session', $this, false); } } * @author Thomas Jarosch * @package Kolab_Storage */ class Kolab_Cache { /** * The version of the cache we loaded. * * @var int */ var $_version; /** * The internal version of the cache format represented by the * code. * * @var int */ var $_base_version = 1; /** * The version of the data format provided by the storage handler. * * @var int */ var $_data_version; /** * The version of the cache format that includes the data version. * * @var int */ var $_cache_version = -1; /** * A validity marker for a share in the cache. This allows the * storage handler to invalidate the cache for this share. * * @var int */ var $validity; /** * A nextid marker for a share in the cache. This allows the * storage handler to invalidate the cache for this share. * * @var int */ var $nextid; /** * The objects of the current share. * * | objects: key is uid (GUID) * | ----------- hashed object data * |----------- uid: object id (GUID) * | |----------- all fields from kolab specification * * @var array */ var $objects; /** * The uid<->object mapping of the current share. * * | uids Mapping between imap uid and object uids: imap uid -> object uid * Special: A value of "false" means we've seen the uid * but we deciced to ignore it in the future * * @var array */ var $uids; /** * The unique key for the currently loaded data. * * @var string */ var $_key; /** * The link to the horde cache. * * @var Horde_Cache */ var $_horde_cache; /** * Constructor. */ function Kolab_Cache() { /** * We explicitly select the file based cache to ensure * that different users have access to the same cache * data. I am not certain this is guaranteed for the other * possible cache drivers. */ $this->_horde_cache = &Horde_Cache::singleton('file', array('prefix' => 'kolab_cache', 'dir' => Horde::getTempDir())); } /** * Attempts to return a reference to a concrete * Kolab_Cache instance. It will only create a new * instance if no Kolab_Cache instance currently exists. * * This method must be invoked as: $var = &Kolab_Cache::singleton() * * @return Kolab_Cache The concrete Kolab_Cache * reference, or false on error. */ function &singleton() { static $kolab_cache; if (!isset($kolab_cache)) { $kolab_cache = new Kolab_Cache(); } return $kolab_cache; } /** * Load the cached share data identified by $key. * * @param string $key Access key to the cached data. * @param int $data_version A version identifier provided by * the storage manager. * @param bool $force Force loading the cache. */ function load($key, $data_version, $force = false) { if (!$force && $this->_key == $key && $this->_data_version == $data_version) { return; } $this->_key = $key; $this->_data_version = $data_version; $this->_cache_version = ($data_version << 8) | $this->_base_version; $this->reset(); $cache = $this->_horde_cache->get($this->_key, 0); if (!$cache) { return; } $data = unserialize($cache); // Delete disc cache if it's from an old version if ($data['version'] != $this->_cache_version) { $this->_horde_cache->expire($this->_key); $this->reset(); } else { $this->_version = $data['version']; $this->validity = $data['uidvalidity']; $this->nextid = $data['uidnext']; $this->objects = $data['objects']; $this->uids = $data['uids']; } } /** * Load a cached attachment. * * @param string $key Access key to the cached data. * * @return mixed The data of the object. */ function loadAttachment($key) { return $this->_horde_cache->get($key, 0); } /** * Cache an attachment. * * @param string $key Access key to the cached data. * @param string $data The data to be cached. * * @return boolean True if successfull. */ function storeAttachment($key, $data) { return $this->_horde_cache->set($key, $data); } /** * Initialize the cache structure. */ function reset() { $this->_version = $this->_cache_version; $this->validity = -1; $this->nextid = -1; $this->objects = array(); $this->uids = array(); } /** * Save the share data in the cache. * * @return boolean True on success. */ function save() { if (!isset($this->_key)) { return PEAR::raiseError('The cache has not been loaded yet!'); } $data = array('version' => $this->_version, 'uidvalidity' => $this->validity, 'uidnext' => $this->nextid, 'objects' => $this->objects, 'uids' => $this->uids); return $this->_horde_cache->set($this->_key, serialize($data)); } /** * Store an object in the cache. * * @param int $id The storage ID. * @param string $object_id The object ID. * @param array $object The object data. */ function store($id, $object_id, &$object) { $this->uids[$id] = $object_id; $this->objects[$object_id] = $object; } /** * Mark the ID as invalid (cannot be correctly parsed). * * @param int $id The ID of the storage item to ignore. */ function ignore($id) { $this->uids[$id] = false; } /** * Deliberately expire a cache. */ function expire() { if (!isset($this->_key)) { return PEAR::raiseError('The cache has not been loaded yet!'); } $this->_version = -1; $this->save(); $this->load($this->_key, $this->_data_version, true); } } * @author Gunnar Wrobel * @author Thomas Jarosch * @package Kolab_Storage */ class Kolab_Data { /** * The link to the folder object. * * @var Kolab_Folder */ var $_folder; /** * The folder type. * * @var string */ var $_type; /** * The object type of the data. * * @var string */ var $_object_type; /** * The version of the data. * * @var int */ var $_data_version; /** * The data cache. * * @var Kolab_Cache */ var $_cache; /** * The Id of this data object in the cache. * * @var string */ var $_cache_key; /** * An addition to the cache key in case we are operating on * something other than the default type. * * @var string */ var $_type_key; /** * Do we optimize for cyrus IMAPD? * * @var boolean */ var $_cache_cyrus_optimize = true; /** * Creates a Kolab Folder Data representation. * * @param string $type Type of the folder. * @param string $object_type Type of the objects we want to read. * @param int $data_version Format version of the object data. */ function Kolab_Data($type, $object_type = null, $data_version = 1) { $this->_type = $type; if (!empty($object_type)) { $this->_object_type = $object_type; } else { $this->_object_type = $type; } $this->_data_version = $data_version; if ($this->_object_type != $this->_type) { $this->_type_key = '@' . $this->_object_type; } else { $this->_type_key = ''; } $this->__wakeup(); } /** * Initializes the object. */ function __wakeup() { $this->_cache = &Kolab_Cache::singleton(); } /** * Returns the properties that need to be serialized. * * @return array List of serializable properties. */ function __sleep() { $properties = get_object_vars($this); unset($properties['_cache'], $properties['_folder']); $properties = array_keys($properties); return $properties; } /** * Set the folder handler. * * @param Kolab_Folder $folder The handler for the folder of folders. */ function setFolder(&$folder) { $this->_folder = &$folder; $this->_cache_key = $this->_getCacheKey(); } /** * Return a unique key for the current folder. * * @return string A key that represents the current folder. */ function _getCacheKey() { if ($this->_cache_cyrus_optimize) { $search_prefix = 'INBOX/'; $pos = strpos($this->_folder->name, $search_prefix); if ($pos !== false && $pos == 0) { $key = 'user/' . Auth::getBareAuth() . '/' . substr($this->_folder->name, strlen($search_prefix)) . $this->_type_key; } else { $key = $this->_folder->name; } } else { $key = $this->_folder->getOwner() . '/' . $this->_folder->name; } return $key; } /** * Delete the specified message from this folder. * * @param string $object_uid Id of the message to be deleted. * * @return boolean|PEAR_Error True is successful, false if the * message does not exist. */ function delete($object_uid) { if (!$this->objectUidExists($object_uid)) { return false; } // Find the storage ID $id = $this->_getStorageId($object_uid); if ($id === false) { return false; } $result = $this->_folder->deleteMessage($id); if (is_a($result, 'PEAR_Error')) { return $result; } $this->_cache->load($this->_cache_key, $this->_data_version); unset($this->_cache->objects[$object_uid]); unset($this->_cache->uids[$id]); $this->_cache->save(); return true; } /** * Delete all messages from the current folder. * * @return boolean|PEAR_Error True if successful. */ function deleteAll() { if (empty($this->_cache->uids)) { return true; } foreach ($this->_cache->uids as $id => $object_uid) { $result = $this->_folder->deleteMessage($id, false); if (is_a($result, 'PEAR_Error')) { return $result; } $this->_cache->load($this->_cache_key, $this->_data_version); unset($this->_cache->objects[$object_uid]); unset($this->_cache->uids[$id]); } $this->_cache->save(); $result = $this->_folder->trigger(); if (is_a($result, 'PEAR_Error')) { Horde::logMessage(sprintf('Failed triggering folder %s!', $this->_folder->name), __FILE__, __LINE__, PEAR_LOG_ERR); } return true; } /** * Move the specified message from the current folder into a new * folder. * * @param string $object_uid ID of the message to be deleted. * @param string $new_share ID of the target share. * * @return boolean|PEAR_Error True is successful, false if the * object does not exist. */ function move($object_uid, $new_share) { if (!$this->objectUidExists($object_uid)) { return false; } // Find the storage ID $id = $this->_getStorageId($object_uid); if ($id === false) { return false; } $result = $this->_folder->moveMessageToShare($id, $new_share); if (is_a($result, 'PEAR_Error')) { return $result; } $this->_cache->load($this->_cache_key, $this->_data_version); unset($this->_cache->objects[$object_uid]); unset($this->_cache->uids[$id]); $this->_cache->save(); return true; } /** * Save an object. * * @param array $object The array that holds the data object. * @param string $old_object_id The id of the object if it existed before. * * @return boolean|PEAR_Error True on success. */ function save($object, $old_object_id = null) { // update existing kolab object if ($old_object_id != null) { // check if object really exists if (!$this->objectUidExists($old_object_id)) { return PEAR::raiseError(sprintf(_("Old object %s does not exist."), $old_object_id)); } // get the storage ID $id = $this->_getStorageId($old_object_id); if ($id === false) { return PEAR::raiseError(sprintf(_("Old object %s does not map to a uid."), $old_object_id)); } $old_object = $this->getObject($old_object_id); } else { $id = null; $old_object = null; } $result = $this->_folder->saveObject($object, $this->_data_version, $this->_object_type, $id, $old_object); if (is_a($result, 'PEAR_Error')) { return $result; } $result = $this->synchronize($old_object_id); if (is_a($result, 'PEAR_Error')) { return $result; } return true; } /** * Synchronize the data cache for the current folder. * * @param string $history_ignore Object uid that should not be * updated in the History */ function synchronize($history_ignore = null) { $this->_cache->load($this->_cache_key, $this->_data_version); $result = $this->_folder->getStatus(); if (is_a($result, 'PEAR_Error')) { return $result; } list($validity, $nextid, $ids) = $result; $changes = $this->_folderChanged($validity, $nextid, array_keys($this->_cache->uids), $ids); if ($changes) { $modified = array(); $recent_uids = array_diff($ids, array_keys($this->_cache->uids)); $formats = $this->_folder->getFormats(); $handler = Horde_Kolab_Format::factory('XML', $this->_object_type, $this->_data_version); if (is_a($handler, 'PEAR_Error')) { return $handler; } $count = 0; foreach ($recent_uids as $id) { if ($this->_type == 'annotation' && $id != 1) { continue; } $mime = $this->_folder->parseMessage($id, $handler->getMimeType(), false); if (is_a($mime, 'PEAR_Error')) { Horde::logMessage($text, __FILE__, __LINE__, PEAR_LOG_WARNING); $text = false; } else { $text = $mime[0]; } if ($text) { $object = $handler->load($text); if (is_a($object, 'PEAR_Error')) { $this->_cache->ignore($id); $object->addUserInfo('STORAGE ID: ' . $id); Horde::logMessage($object, __FILE__, __LINE__, PEAR_LOG_WARNING); continue; } } else { $object = false; } if ($object !== false) { $message = &$mime[2]; $handler_type = $handler->getMimeType(); foreach ($message->getParts() as $part) { $name = $part->getName(); $type = $part->getType(); $dp = $part->getDispositionParameter('x-kolab-type'); if (!empty($name) && $type != $handler_type || (!empty($dp) && in_array($dp, $formats))) { $object['_attachments'][$name]['type'] = $type; $object['_attachments'][$name]['key'] = $this->_cache_key . '/' . $object['uid'] . ':' . $name; $part->transferDecodeContents(); $result = $this->_cache->storeAttachment($object['_attachments'][$name]['key'], $part->getContents()); if (is_a($result, 'PEAR_Error')) { Horde::logMessage(sprintf('Failed storing attachment of object %s: %s', $id, $result->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR); $object = false; break; } } } } if ($object !== false) { $this->_cache->store($id, $object['uid'], $object); $mod_ts = time(); if (is_array($changes) && in_array($object['uid'], $changes) && $object['uid'] != $history_ignore) { $this->_updateHistory($object['uid'], $mod_ts, 'modify'); $modified[] = $object['uid']; } else { $this->_updateHistory($object['uid'], $mod_ts, 'add'); } } else { $this->_cache->ignore($id); } // write out cache once in a while so if the browser times out // we don't have to start from the beginning. if ($count > 500) { $count = 0; $this->_cache->save(); } $count++; } $this->_cache->save(); if (is_array($changes)) { $deleted = array_diff($changes, $modified); foreach ($deleted as $deleted_oid) { if ($deleted_oid != $history_ignore) { $this->_updateHistory($deleted_oid, time(), 'delete'); } } } } } /** * Update the Horde history in case an element was modified * outside of Horde. * * @param string $object_uid Object uid that should be updated. * @param int $mod_ts Timestamp of the modification. * @param string $action The action that was performed. */ function _updateHistory($object_uid, $mod_ts, $action) { global $registry; if (!isset($registry)) { return; } $app = $registry->getApp(); if (empty($app) || is_a($app, 'PEAR_Error')) { /** * Ignore the history if we are not in application * context. */ return $app; } /* Log the action on this item in the history log. */ $history = &Horde_History::singleton(); $history_id = $app . ':' . $this->_folder->getShareId() . ':' . $object_uid; $history->log($history_id, array('action' => $action, 'ts' => $mod_ts), true); } /** * Check if the folder has changed and the cache needs to be updated. * * @param string $validity ID validity of the folder. * @param string $nextid next ID for the folder. * @param array $old_ids Old list of IDs in the folder. * @param array $new_ids New list of IDs in the folder. * * @return mixed True or an array of deleted IDs if the * folder changed and false otherwise. */ function _folderChanged($validity, $nextid, &$old_ids, &$new_ids) { $changed = false; $reset_done = false; // uidvalidity changed? if ($validity != $this->_cache->validity) { $this->_cache->reset(); $reset_done = true; } // nextid changed? if ($nextid != $this->_cache->nextid) { $changed = true; } $this->_cache->validity = $validity; $this->_cache->nextid = $nextid; if ($reset_done) { return true; } // Speed optimization: if nextid and validity didn't change // and count(old_ids) == count(new_ids), the folder didn't change. if ($changed || count($old_ids) != count ($new_ids)) { // remove deleted messages from cache $delete_ids = array_diff($old_ids, $new_ids); $deleted_oids = array(); foreach ($delete_ids as $delete_id) { $object_id = $this->_cache->uids[$delete_id]; if ($object_id !== false) { unset($this->_cache->objects[$object_id]); $deleted_oids[] = $object_id; } unset($this->_cache->uids[$delete_id]); } if (!empty($deleted_oids)) { $changed = $deleted_oids; } else { $changed = true; } } return $changed; } /** * Return the IMAP ID for the given object ID. * * @param string $object_id The object ID. * * @return int The IMAP ID. */ function _getStorageId($object_uid) { $this->_cache->load($this->_cache_key, $this->_data_version); $id = array_search($object_uid, $this->_cache->uids); if ($id === false) { return false; } return $id; } /** * Test if the storage ID exists. * * @param int $uid The storage ID. * * @return boolean True if the ID exists. */ function _storageIdExists($uid) { $this->_cache->load($this->_cache_key, $this->_data_version); return array_key_exists($uid, $this->_cache->uids); } /** * Generate a unique object id. * * @return string The unique id. */ function generateUID() { do { $key = md5(uniqid(mt_rand(), true)); } while($this->objectUidExists($key)); return $key; } /** * Check if the given id exists. * * @param string $uid The object id. * * @return boolean True if the id was found, false otherwise. */ function objectUidExists($uid) { $this->_cache->load($this->_cache_key, $this->_data_version); return array_key_exists($uid, $this->_cache->objects); } /** * Return the specified object. * * @param string $object_id The object id. * * @return array|PEAR_Error The object data as an array. */ function getObject($object_id) { $this->_cache->load($this->_cache_key, $this->_data_version); if (!$this->objectUidExists($object_id)) { return PEAR::raiseError(sprintf(_("Kolab cache: Object uid %s does not exist in the cache!"), $object_id)); } return $this->_cache->objects[$object_id]; } /** * Return the specified attachment. * * @param string $attachment_id The attachment id. * * @return string|PEAR_Error The attachment data as a string. */ function getAttachment($attachment_id) { return $this->_cache->loadAttachment($attachment_id); } /** * Retrieve all object ids in the current folder. * * @return array The object ids. */ function getObjectIds() { $this->_cache->load($this->_cache_key, $this->_data_version); return array_keys($this->_cache->objects); } /** * Retrieve all objects in the current folder. * * @return array All object data arrays. */ function getObjects() { $this->_cache->load($this->_cache_key, $this->_data_version); return array_values($this->_cache->objects); } /** * Retrieve all objects in the current folder as an array. * * @return array The object data array. */ function getObjectArray() { $this->_cache->load($this->_cache_key, $this->_data_version); return $this->_cache->objects; } } * @author Gunnar Wrobel * @author Thomas Jarosch * @package Kolab_Storage */ class Kolab_Folder { /** * The folder name. * * @var string */ var $name; /** * A new folder name if the folder should be renamed on the next * save. * * @var string */ var $new_name; /** * The handler for the list of Kolab folders. * * @var Kolab_List */ var $_list; /** * The type of this folder. * * @var string */ var $_type; /** * The complete folder type annotation (type + default). * * @var string */ var $_type_annotation; /** * The owner of this folder. * * @var string */ var $_owner; /** * The pure folder. * * @var string */ var $_subpath; /** * Additional Horde folder attributes. * * @var array */ var $_attributes; /** * Additional Kolab folder attributes. * * @var array */ var $_kolab_attributes; /** * Is this a default folder? * * @var boolean */ var $_default; /** * The title of this folder. * * @var string */ var $_title; /** * The permission handler for the folder. * * @var Horde_Permission_Kolab */ var $_perms; /** * Links to the data handlers for this folder. * * @var array */ var $_data; /** * Links to the annotation data handlers for this folder. * * @var array */ var $_annotation_data; /** * Indicate that the folder data has been modified from the * outside and all Data handlers need to synchronize. * * @var boolean */ var $tainted = false; /** * Creates a Kolab Folder representation. * * @param string $name Name of the folder */ function Kolab_Folder($name = null) { $this->name = $name; $this->__wakeup(); } /** * Initializes the object. */ function __wakeup() { if (!isset($this->_data)) { $this->_data = array(); } foreach($this->_data as $data) { $data->setFolder($this); } if (isset($this->_perms)) { $this->_perms->setFolder($this); } } /** * Returns the properties that need to be serialized. * * @return array List of serializable properties. */ function __sleep() { $properties = get_object_vars($this); unset($properties['_list']); $properties = array_keys($properties); return $properties; } /** * Set the list handler. * * @param Kolab_List $list The handler for the list of folders. */ function setList(&$list) { $this->_list = &$list; } /** * Set a new name for the folder. The new name will be realized * when saving the folder. * * @param string $name The new folder name */ function setName($name) { $this->new_name = $this->_list->namespace->setName($name); } /** * Set a new IMAP folder name for the folder. The new name will be * realized when saving the folder. * * @param string $name The new folder name. */ function setFolder($name) { $this->new_name = $name; } /** * Return the share ID of this folder. * * @return string The share ID of this folder. */ function getShareId() { $current_user = Auth::getAuth(); if ($this->isDefault() && $this->getOwner() == $current_user) { return $current_user; } return rawurlencode($this->name); } /** * Saves the folder. * * @param array $attributes An array of folder attributes. You can * set any attribute but there are a few * special ones like 'type', 'default', * 'owner' and 'desc'. * * @return boolean|PEAR_Error True on success. */ function save($attributes = null) { if (!isset($this->name)) { /* A new folder needs to be created */ if (!isset($this->new_name)) { return PEAR::raiseError(_("Cannot create this folder! The name has not yet been set.")); } if (isset($attributes['type'])) { $this->_type = $attributes['type']; unset($attributes['type']); } else { $this->_type = 'mail'; } if (isset($attributes['default'])) { $this->_default = $attributes['default']; unset($attributes['default']); } else { $this->_default = false; } $result = $this->_list->create($this); if (is_a($result, 'PEAR_Error')) { return $result; } $this->name = $this->new_name; $this->new_name = null; /* Initialize the new folder to default permissions */ if (empty($this->_perms)) { $this->getPermission(); } } else { $type = $this->getType(); if (isset($attributes['type'])) { if ($attributes['type'] != $type) { Horde::logMessage(sprintf('Cannot modify the type of a folder from %s to %s!', $type, $attributes['type']), __FILE__, __LINE__, PEAR_LOG_ERR); } unset($attributes['type']); } if (isset($attributes['default'])) { $this->_default = $attributes['default']; unset($attributes['default']); } else { $this->_default = $this->isDefault(); } if (isset($this->new_name) && $this->new_name != $this->name) { /** The folder needs to be renamed */ $result = $this->_list->rename($this); if (is_a($result, 'PEAR_Error')) { return $result; } /** * Trigger the old folder on an empty IMAP folder. */ $session = &Horde_Kolab_Session::singleton(); $imap = &$session->getImap(); if (!is_a($imap, 'PEAR_Error')) { $result = $imap->create($this->name); if (is_a($result, 'PEAR_Error')) { Horde::logMessage(sprintf('Failed creating dummy folder: %s!', $result->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR); } $imap->setAnnotation(KOLAB_ANNOT_FOLDER_TYPE, array('value.shared' => $this->_type), $this->name); $result = $this->trigger($this->name); if (is_a($result, 'PEAR_Error')) { Horde::logMessage(sprintf('Failed triggering dummy folder: %s!', $result->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR); } $result = $imap->delete($this->name); if (is_a($result, 'PEAR_Error')) { Horde::logMessage(sprintf('Failed deleting dummy folder: %s!', $result->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR); } } $this->name = $this->new_name; $this->new_name = null; $this->_title = null; $this->_owner = null; } } if (isset($attributes['owner'])) { if ($attributes['owner'] != $this->getOwner()) { Horde::logMessage(sprintf('Cannot modify the owner of a folder from %s to %s!', $this->getOwner(), $attributes['owner']), __FILE__, __LINE__, PEAR_LOG_ERR); } unset($attributes['owner']); } /** Handle the folder type */ $folder_type = $this->_type . ($this->_default ? '.default' : ''); if ($this->_type_annotation != $folder_type) { $result = $this->_setAnnotation(KOLAB_ANNOT_FOLDER_TYPE, $folder_type); if (is_a($result, 'PEAR_Error')) { $this->_type = null; $this->_default = false; $this->_type_annotation = null; return $result; } } if (!empty($attributes)) { if (!is_array($attributes)) { $attributes = array($attributes); } foreach ($attributes as $key => $value) { if ($key == 'params') { $params = unserialize($value); if (isset($params['xfbaccess'])) { $result = $this->setXfbAccess($params['xfbaccess']); if (is_a($result, 'PEAR_Error')) { return $result; } } if (isset($params['fbrelevance'])) { $result = $this->setFbrelevance($params['fbrelevance']); if (is_a($result, 'PEAR_Error')) { return $result; } } } // setAnnotation apparently does not suppoort UTF-8 nor any special characters $store = base64_encode($value); if ($key == 'desc') { $entry = '/comment'; } else { $entry = HORDE_ANNOT_SHARE_ATTR . $key; } $result = $this->_setAnnotation($entry, $store); if (is_a($result, 'PEAR_Error')) { return $result; } } $this->_attributes = $attributes; } /** Now save the folder permissions */ if (isset($this->_perms)) { $result = $this->_perms->save(); if (is_a($result, 'PEAR_Error')) { return $result; } } $result = $this->trigger(); if (is_a($result, 'PEAR_Error')) { Horde::logMessage(sprintf('Failed triggering folder %s! Error was: %s', $this->name, $result->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR); } return true; } /** * Delete this folder. * * @return boolean|PEAR_Error True if the operation succeeded. */ function delete() { $result = $this->_list->remove($this); if (is_a($result, 'PEAR_Error')) { return $result; } return true; } /** * Return the name of the folder. * * @return string The name of the folder. */ public function getName() { if (isset($this->name)) { return $this->name; } if (!isset($this->name) && isset($this->new_name)) { return $this->new_name; } } /** * Returns the owner of the folder. * * @return string|PEAR_Error The owner of this folder. */ function getOwner() { if (!isset($this->_owner)) { $owner = $this->_list->namespace->getOwner($this->getName()); /** * @todo: Reconsider if this handling should really be done here * rather than in a module nearer to the applications. */ switch ($owner) { case Horde_Kolab_Storage_Namespace::PERSONAL: $this->_owner = Auth::getAuth(); break; case Horde_Kolab_Storage_Namespace::SHARED: $this->_owner = 'anonymous'; break; default: list($prefix, $user) = explode(':', $owner, 2); if (strpos($user, '@') === false) { $domain = strstr(Auth::getAuth(), '@'); if (!empty($domain)) { $user .= $domain; } } $this->_owner = $user; break; } } return $this->_owner; } /** * Returns the subpath of the folder. * * @param string $name Name of the folder that should be triggered. * * @return string|PEAR_Error The subpath of this folder. */ function getSubpath($name = null) { if (!empty($name)) { return $this->_list->namespace->getSubpath($name); } if (!isset($this->_subpath)) { $this->_subpath = $this->_list->namespace->getSubpath($this->getName()); } return $this->_subpath; } /** * Returns a readable title for this folder. * * @return string The folder title. */ function getTitle() { if (!isset($this->_title)) { $this->_title = $this->_list->namespace->getTitle($this->getName()); } return $this->_title; } /** * The type of this folder. * * @return string|PEAR_Error The folder type. */ function getType() { if (!isset($this->_type)) { $type_annotation = $this->_getAnnotation(KOLAB_ANNOT_FOLDER_TYPE, $this->name); if (is_a($type_annotation, 'PEAR_Error')) { $this->_default = false; return $type_annotation; } else if (empty($type_annotation)) { $this->_default = false; $this->_type = ''; } else { $type = explode('.', $type_annotation); $this->_default = (!empty($type[1]) && $type[1] == 'default'); $this->_type = $type[0]; } $this->_type_annotation = $type_annotation; } return $this->_type; } /** * Is this a default folder? * * @return boolean Boolean that indicates the default status. */ function isDefault() { if (!isset($this->_default)) { /* This call also determines default status */ $this->getType(); } return $this->_default; } /** * Returns one of the attributes of the folder, or an empty string * if it isn't defined. * * @param string $attribute The attribute to retrieve. * * @return mixed The value of the attribute, an empty string or an * error. */ function getAttribute($attribute) { if (!isset($this->_attributes[$attribute])) { if ($attribute == 'desc') { $entry = '/comment'; } else { $entry = HORDE_ANNOT_SHARE_ATTR . $attribute; } $annotation = $this->_getAnnotation($entry, $this->name); if (is_a($annotation, 'PEAR_Error')) { return $annotation; } if (empty($annotation)) { $this->_attributes[$attribute] = ''; } else { $this->_attributes[$attribute] = base64_decode($annotation); } } return $this->_attributes[$attribute]; } /** * Returns one of the Kolab attributes of the folder, or an empty * string if it isn't defined. * * @param string $attribute The attribute to retrieve. * * @return mixed The value of the attribute, an empty string or an * error. */ function getKolabAttribute($attribute) { if (!isset($this->_kolab_attributes[$attribute])) { $entry = KOLAB_ANNOT_ROOT . $attribute; $annotation = $this->_getAnnotation($entry, $this->name); if (is_a($annotation, 'PEAR_Error')) { return $annotation; } if (empty($annotation)) { $this->_kolab_attributes[$attribute] = ''; } else { $this->_kolab_attributes[$attribute] = $annotation; } } return $this->_kolab_attributes[$attribute]; } /** * Returns whether the folder exists. * * @return boolean|PEAR_Error True if the folder exists. */ function exists() { $session = &Horde_Kolab_Session::singleton(); $imap = &$session->getImap(); if (is_a($imap, 'PEAR_Error')) { return $imap; } $result = $imap->exists($this->name); if (is_a($result, 'PEAR_Error')) { return false; } return $result; } /** * Returns whether the folder is accessible. * * @return boolean|PEAR_Error True if the folder can be accessed. */ function accessible() { $session = &Horde_Kolab_Session::singleton(); $imap = &$session->getImap(); if (is_a($imap, 'PEAR_Error')) { return $imap; } $result = $imap->select($this->name); if (is_a($result, 'PEAR_Error')) { return false; } return $result; } /** * Retrieve a handler for the data in this folder. * * @param Kolab_List $list The handler for the list of folders. * * @return Kolab_Data|PEAR_Error The data handler. */ function &getData($object_type = null, $data_version = 1) { if (empty($object_type)) { $object_type = $this->getType(); if (is_a($object_type, 'PEAR_Error')) { return $object_type; } } if ($this->tainted) { foreach ($this->_data as $data) { $data->synchronize(); } $this->tainted = false; } $key = $object_type . '|' . $data_version; if (!isset($this->_data[$key])) { if ($object_type != 'annotation') { $type = $this->getType(); } else { $type = 'annotation'; } $data = new Kolab_Data($type, $object_type, $data_version); $data->setFolder($this); $data->synchronize(); $this->_data[$key] = &$data; } return $this->_data[$key]; } /** * Delete the specified message from this folder. * * @param string $id IMAP id of the message to be deleted. * @param boolean $trigger Should the folder be triggered? * * @return boolean|PEAR_Error True if successful. */ function deleteMessage($id, $trigger = true) { $session = &Horde_Kolab_Session::singleton(); $imap = &$session->getImap(); if (is_a($imap, 'PEAR_Error')) { return $imap; } // Select folder $result = $imap->select($this->name); if (is_a($result, 'PEAR_Error')) { return $result; } $result = $imap->deleteMessages($id); if (is_a($result, 'PEAR_Error')) { return $result; } $result = $imap->expunge(); if (is_a($result, 'PEAR_Error')) { return $result; } if ($trigger) { $result = $this->trigger(); if (is_a($result, 'PEAR_Error')) { Horde::logMessage(sprintf('Failed triggering folder %s! Error was: %s', $this->name, $result->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR); } } return true; } /** * Move the specified message to the specified folder. * * @param string $id IMAP id of the message to be moved. * @param string $folder Name of the receiving folder. * * @return boolean|PEAR_Error True if successful. */ function moveMessage($id, $folder) { $session = &Horde_Kolab_Session::singleton(); $imap = &$session->getImap(); if (is_a($imap, 'PEAR_Error')) { return $imap; } // Select folder $result = $imap->select($this->name); if (is_a($result, 'PEAR_Error')) { return $result; } $result = $imap->moveMessage($id, $folder); if (is_a($result, 'PEAR_Error')) { return $result; } $result = $imap->expunge(); if (is_a($result, 'PEAR_Error')) { return $result; } $result = $this->trigger(); if (is_a($result, 'PEAR_Error')) { Horde::logMessage(sprintf('Failed triggering folder %s! Error was: %s', $this->name, $result->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR); } return true; } /** * Move the specified message to the specified share. * * @param string $id IMAP id of the message to be moved. * @param string $share Name of the receiving share. * * @return boolean|PEAR_Error True if successful. */ function moveMessageToShare($id, $share) { $folder = $this->_list->getByShare($share, $this->getType()); if (is_a($folder, 'PEAR_Error')) { return $folder; } $folder->tainted = true; $success = $this->moveMessage($id, $folder->name); $result = $this->trigger(); if (is_a($result, 'PEAR_Error')) { Horde::logMessage(sprintf('Failed triggering folder %s! Error was: %s', $this->name, $result->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR); } return $success; } /** * Retrieve the supported formats. * * @return array The names of the supported formats. */ function getFormats() { global $conf; if (empty($conf['kolab']['misc']['formats'])) { $formats = array('XML'); } else { $formats = $conf['kolab']['misc']['formats']; } if (!is_array($formats)) { $formats = array($formats); } if (!in_array('XML', $formats)) { $formats[] = 'XML'; } return $formats; } /** * Save an object in this folder. * * @param array $object The array that holds the data of the object. * @param int $data_version The format handler version. * @param string $object_type The type of the kolab object. * @param string $id The IMAP id of the old object if it * existed before * @param array $old_object The array that holds the current data of the * object. * * @return boolean|PEAR_Error True on success. */ function saveObject(&$object, $data_version, $object_type, $id = null, &$old_object = null) { $session = &Horde_Kolab_Session::singleton(); $imap = &$session->getImap(); if (is_a($imap, 'PEAR_Error')) { return $imap; } // Select folder $result = $imap->select($this->name); if (is_a($result, 'PEAR_Error')) { return $result; } $new_headers = new MIME_Headers(); $formats = $this->getFormats(); $handlers = array(); foreach ($formats as $type) { $handlers[$type] = &Horde_Kolab_Format::factory($type, $object_type, $data_version); if (is_a($handlers[$type], 'PEAR_Error')) { if ($type == 'XML') { return $handlers[$type]; } Horde::logMessage(sprintf('Loading format handler "%s" failed: %s', $type, $handlers[$type]->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR); continue; } } if ($id != null) { /** Update an existing kolab object */ $session = &Horde_Kolab_Session::singleton(); $imap = &$session->getImap(); if (is_a($imap, 'PEAR_Error')) { return $imap; } if (!in_array($id, $imap->getUids())) { return PEAR::raiseError(sprintf(_("The message with ID %s does not exist. This probably means that the Kolab object has been modified by somebody else while you were editing it. Your edits have been lost."), $id)); } /** Parse email and load Kolab format structure */ $result = $this->parseMessage($id, $handlers['XML']->getMimeType(), true, $formats); if (is_a($result, 'PEAR_Error')) { return $result; } list($old_message, $part_ids, $mime_message, $mime_headers) = $result; if (is_a($old_message, 'PEAR_Error')) { return $old_message; } if (isset($object['_attachments']) && isset($old_object['_attachments'])) { $attachments = array_keys($object['_attachments']); foreach (array_keys($old_object['_attachments']) as $attachment) { if (!in_array($attachment, $attachments)) { foreach ($mime_message->getParts() as $part) { if ($part->getName() === $attachment) { foreach (array_keys($mime_message->_parts) as $key) { if ($mime_message->_parts[$key]->getMIMEId() == $part->getMIMEId()) { unset($mime_message->_parts[$key]); break; } } $mime_message->_generateIdMap($mime_message->_parts); } } } } } $object = array_merge($old_object, $object); if (isset($attachments)) { foreach ($mime_message->getParts() as $part) { $name = $part->getName(); foreach ($attachments as $attachment) { if ($name === $attachment) { $object['_attachments'][$attachment]['id'] = $part->getMIMEId(); } } } } /** Copy email header */ if (!empty($mime_headers) && !$mime_headers === false) { foreach ($mime_headers as $header => $value) { $new_headers->addheader($header, $value); } } } else { $mime_message = $this->_prepareNewMessage($new_headers); $mime_part_id = false; } if (isset($object['_attachments'])) { $attachments = array_keys($object['_attachments']); foreach ($attachments as $attachment) { $data = $object['_attachments'][$attachment]; if (!isset($data['content']) && !isset($data['path'])) { /** * There no new content and no new path. Do not rewrite the * attachment. */ continue; } $part = new MIME_Part(isset($data['type']) ? $data['type'] : null, isset($data['content']) ? $data['content'] : file_get_contents($data['path']), NLS::getCharset()); $part->setTransferEncoding('quoted-printable'); $part->setDisposition('attachment'); $part->setName($attachment); if (!isset($data['id'])) { $mime_message->addPart($part); } else { $mime_message->alterPart($data['id'], $part); } } } foreach ($formats as $type) { $new_content = $handlers[$type]->save($object); if (is_a($new_content, 'PEAR_Error')) { return $new_content; } /** Update mime part */ $part = new MIME_Part($handlers[$type]->getMimeType(), $new_content, NLS::getCharset()); $part->setTransferEncoding('quoted-printable'); $part->setDisposition($handlers[$type]->getDisposition()); $part->setDispositionParameter('x-kolab-type', $type); $part->setName($handlers[$type]->getName()); if (!isset($part_ids) || $part_ids[$type] === false) { $mime_message->addPart($part); } else { $mime_message->alterPart($part_ids[$type], $part); } } $session = &Horde_Kolab_Session::singleton(); // Update email headers $new_headers->addHeader('From', $session->user_mail); $new_headers->addHeader('To', $session->user_mail); $new_headers->addHeader('Date', date('r')); $new_headers->addHeader('X-Kolab-Type', $handlers['XML']->getMimeType()); $new_headers->addHeader('Subject', $object['uid']); $new_headers->addHeader('User-Agent', 'Horde::Kolab::Storage v0.2'); $new_headers->addMIMEHeaders($mime_message); $msg = preg_replace("/\r\n|\n|\r/s", "\r\n", $new_headers->toString() . $mime_message->toString(false)); // delete old email? if ($id != null) { $result = $imap->deleteMessages($id); if (is_a($result, 'PEAR_Error')) { return $result; } } // store new email $result = $imap->appendMessage($msg); if (is_a($result, 'PEAR_Error')) { if ($id != null) { $result = $imap->undeleteMessages($id); if (is_a($result, 'PEAR_Error')) { return $result; } } return $result; } // remove deleted object if ($id != null) { $result = $imap->expunge(); if (is_a($result, 'PEAR_Error')) { return $result; } } $result = $this->trigger(); if (is_a($result, 'PEAR_Error')) { Horde::logMessage(sprintf('Failed triggering folder %s! Error was: %s', $this->name, $result->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR); } return true; } /** * Get an IMAP message and retrieve the Kolab Format object. * * @param int $id The message to retrieve. * @param string $mime_type The mime type of the part to retrieve. * @param boolean $parse_headers Should the heades be MIME parsed? * @param array $formats The list of possible format parts. * * @return array|PEAR_Error An array that list the Kolab XML * object text, the mime ID of the part * with the XML object, the MIME parsed * message and the MIME parsed headers if * requested. */ function parseMessage($id, $mime_type, $parse_headers = true, $formats = array('XML')) { $session = &Horde_Kolab_Session::singleton(); $imap = &$session->getImap(); if (is_a($imap, 'PEAR_Error')) { return $imap; } $raw_headers = $imap->getMessageHeader($id); if (is_a($raw_headers, 'PEAR_Error')) { return PEAR::raiseError(sprintf(_("Failed retrieving the message with ID %s. Original error: %s."), $id, $raw_headers->getMessage())); } $body = $imap->getMessageBody($id); if (is_a($body, 'PEAR_Error')) { return PEAR::raiseError(sprintf(_("Failed retrieving the message with ID %s. Original error: %s."), $id, $body->getMessage())); } $mime_message = MIME_Structure::parseTextMIMEMessage($raw_headers . $body); $parts = $mime_message->contentTypeMap(); $mime_headers = false; $xml = false; // Read in a Kolab event object, if one exists $part_ids['XML'] = array_search($mime_type, $parts); if ($part_ids['XML'] !== false) { if ($parse_headers) { $mime_headers = MIME_Structure::parseMIMEHeaders($raw_headers); } $part = $mime_message->getPart($part_ids['XML']); $part->transferDecodeContents(); $xml = $part->getContents(); } $alternate_formats = array_diff(array('XML'), $formats); if (!empty($alternate_formats)) { foreach ($alternate_formats as $type) { $part_ids[$type] = false; } foreach ($mime_message->getParts() as $part) { $params = $part->getDispositionParameters(); foreach ($alternate_formats as $type) { if (isset($params['x-kolab-format']) && $params['x-kolab-format'] == $type) { $part_ids[$type] = $part->getMIMEId(); } } } } $result = array($xml, $part_ids, $mime_message, $mime_headers); return $result; } /** * Prepares a new kolab Groupeware message. * * @return string The MIME message */ function _prepareNewMessage() { $mime_message = new MIME_Message(); $kolab_text = sprintf(_("This is a Kolab Groupware object. To view this object you will need an email client that understands the Kolab Groupware format. For a list of such email clients please visit %s"), 'http://www.kolab.org/kolab2-clients.html'); $part = new MIME_Part('text/plain', String::wrap($kolab_text, 76, "\r\n", NLS::getCharset()), NLS::getCharset()); $part->setTransferEncoding('quoted-printable'); $mime_message->addPart($part); return $mime_message; } /** * Report the status of this folder. * * @return array|PEAR_Error An array listing the validity ID, the * next IMAP ID and an array of IMAP IDs. */ function getStatus() { $session = &Horde_Kolab_Session::singleton(); $imap = &$session->getImap(); if (is_a($imap, 'PEAR_Error')) { return $imap; } // Select the folder to update uidnext $result = $imap->select($this->name); if (is_a($result, 'PEAR_Error')) { return $result; } $status = $imap->status(); if (is_a($status, 'PEAR_Error')) { return $status; } $uids = $imap->getUids(); if (is_a($uids, 'PEAR_Error')) { return $uids; } return array($status['uidvalidity'], $status['uidnext'], $uids); } /** * Triggers any required updates after changes within the * folder. This is currently only required for handling free/busy * information with Kolab. * * @param string $name Name of the folder that should be triggered. * * @return boolean|PEAR_Error True if successfull. */ function trigger($name = null) { $type = $this->getType(); if (is_a($type, 'PEAR_Error')) { return $type; } $owner = $this->getOwner(); if (is_a($owner, 'PEAR_Error')) { return $owner; } $subpath = $this->getSubpath($name); if (is_a($subpath, 'PEAR_Error')) { return $subpath; } switch($type) { case 'event': $session = &Horde_Kolab_Session::singleton(); $url = sprintf('%s/trigger/%s/%s.pfb', $session->freebusy_server, $owner, $subpath); break; default: return true; } $result = $this->triggerUrl($url); if (is_a($result, 'PEAR_Error')) { return PEAR::raiseError(sprintf(_("Failed triggering folder %s. Error was: %s"), $this->name, $result->getMessage())); } return $result; } /** * Triggers a URL. * * @param string $url The URL to be triggered. * * @return boolean|PEAR_Error True if successfull. */ function triggerUrl($url) { global $conf; if (!empty($conf['kolab']['no_triggering'])) { return true; } $options['method'] = 'GET'; $options['timeout'] = 5; $options['allowRedirects'] = true; if (isset($conf['http']['proxy']) && !empty($conf['http']['proxy']['proxy_host'])) { $options = array_merge($options, $conf['http']['proxy']); } require_once 'HTTP/Request.php'; $http = new HTTP_Request($url, $options); $http->setBasicAuth(Auth::getAuth(), Auth::getCredential('password')); @$http->sendRequest(); if ($http->getResponseCode() != 200) { return PEAR::raiseError(sprintf(_("Unable to trigger URL %s. Response: %s"), $url, $http->getResponseCode())); } return true; } /** * Checks to see if a user has a given permission. * * @param string $userid The userid of the user. * @param integer $permission A PERMS_* constant to test for. * @param string $creator The creator of the shared object. * * @return boolean|PEAR_Error Whether or not $userid has $permission. */ function hasPermission($userid, $permission, $creator = null) { if ($userid == $this->getOwner()) { return true; } $perm = &$this->getPermission(); if (is_a($perm, 'PEAR_Error')) { return $perm; } return $perm->hasPermission($userid, $permission, $creator); } /** * Returns the permissions from this storage object. * * @return Horde_Permission_Kolab The permissions on the share. */ function &getPermission() { if (!isset($this->_perms)) { if ($this->exists()) { // The permissions are unknown but the folder exists // -> discover permissions $perms = null; } else { $perms = array( 'users' => array( Auth::getAuth() => PERMS_SHOW | PERMS_READ | PERMS_EDIT | PERMS_DELETE)); } $this->_perms = &new Horde_Permission_Kolab($this, $perms); } return $this->_perms; } /** * Sets the permissions on the share. * * @param Horde_Permission_Kolab $perms Permission object to store on the * object. * @param boolean $update Save the updated information? * * @return boolean|PEAR_Error True on success. */ function setPermission(&$perms, $update = true) { if (!is_a($perms, 'Horde_Permission')) { return PEAR::raiseError('The permissions for this share must be specified as an instance of the Horde_Permission class!'); } if (!is_a($perms, 'Horde_Permission_Kolab')) { $this->_perms = &new Horde_Permission_Kolab($this, $perms->data); } else { $this->_perms = &$perms; $this->_perms->setFolder($this); } if ($update) { return $this->save(); } return true; } /** * Return the IMAP ACL of this folder. * * @return array|PEAR_Error An array with IMAP ACL. */ function getACL() { global $conf; $session = &Horde_Kolab_Session::singleton(); $imap = &$session->getImap(); if (is_a($imap, 'PEAR_Error')) { return $imap; } if (!empty($conf['kolab']['imap']['no_acl'])) { $acl = array(); $acl[Auth::getAuth()] = 'lrid'; return $acl; } $acl = $imap->getACL($this->name); /* * Check if the getPerm comes from the owner in this case we * can use getACL to have all the right of the share Otherwise * we just ask for the right of the current user for a folder */ if ($this->getOwner() == Auth::getAuth()) { return $acl; } else { if (!is_a($acl, 'PEAR_Error')) { return $acl; } $my_rights = $imap->getMyrights($this->name); if (is_a($my_rights, 'PEAR_Error')) { return $my_rights; } $acl = array(); $acl[Auth::getAuth()] = $my_rights; return $acl; } } /** * Set the IMAP ACL of this folder. * * @param $user The user for whom the ACL should be set. * @param $acl The new ACL value. * * @return boolean|PEAR_Error True on success. */ function setACL($user, $acl) { global $conf; $session = &Horde_Kolab_Session::singleton(); $imap = &$session->getImap(); if (is_a($imap, 'PEAR_Error')) { return $imap; } if (!empty($conf['kolab']['imap']['no_acl'])) { return true; } $iresult = $imap->setACL($this->name, $user, $acl); if (is_a($iresult, 'PEAR_Error')) { return $iresult; } if (!empty($this->_perms)) { /** Refresh the cache after changing the permissions */ $this->_perms->getPerm(); } $result = $this->trigger(); if (is_a($result, 'PEAR_Error')) { Horde::logMessage(sprintf('Failed triggering folder %s! Error was: %s', $this->name, $result->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR); } return $iresult; } /** * Delete the IMAP ACL for a user on this folder. * * @param $user The user for whom the ACL should be deleted. * * @return boolean|PEAR_Error True on success. */ function deleteACL($user) { global $conf; $session = &Horde_Kolab_Session::singleton(); $imap = &$session->getImap(); if (is_a($imap, 'PEAR_Error')) { return $imap; } if (!empty($conf['kolab']['imap']['no_acl'])) { return true; } $iresult = $imap->deleteACL($this->name, $user); if (is_a($iresult, 'PEAR_Error')) { return $iresult; } $result = $this->trigger(); if (is_a($result, 'PEAR_Error')) { Horde::logMessage(sprintf('Failed triggering folder %s! Error was: %s', $this->name, $result->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR); } return $iresult; } /** * Get annotation values on IMAP server that do not support * METADATA. * * @return array|PEAR_Error The anotations of this folder. */ function _getAnnotationData() { $this->_annotation_data = $this->getData('annotation'); } /** * Get an annotation value of this folder. * * @param $key The key of the annotation to retrieve. * * @return string|PEAR_Error The anotation value. */ function _getAnnotation($key) { global $conf; if (empty($conf['kolab']['imap']['no_annotations'])) { $session = &Horde_Kolab_Session::singleton(); $imap = &$session->getImap(); if (is_a($imap, 'PEAR_Error')) { return $imap; } return $imap->getAnnotation($key, 'value.shared', $this->name); } if (!isset($this->_annotation_data)) { $this->_getAnnotationData(); } $data = $this->_annotation_data->getObject('KOLAB_FOLDER_CONFIGURATION'); if (is_a($data, 'PEAR_Error')) { Horde::logMessage(sprintf('Error retrieving annotation data on folder %s: %s', $this->name, $data->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR); return ''; } if (isset($data[$key])) { return $data[$key]; } else { return ''; } } /** * Set an annotation value of this folder. * * @param $key The key of the annotation to change. * @param $value The new value. * * @return boolean|PEAR_Error True on success. */ function _setAnnotation($key, $value) { if (empty($conf['kolab']['imap']['no_annotations'])) { $session = &Horde_Kolab_Session::singleton(); $imap = &$session->getImap(); if (is_a($imap, 'PEAR_Error')) { return $imap; } return $imap->setAnnotation($key, array('value.shared' => $value), $this->name); } if (!isset($this->_annotation_data)) { $this->_getAnnotationData(); } $data = $this->_annotation_data->getObject('KOLAB_FOLDER_CONFIGURATION'); if (is_a($data, 'PEAR_Error')) { Horde::logMessage(sprintf('Error retrieving annotation data on folder %s: %s', $this->name, $data->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR); $data = array(); $uid = null; } else { $uid = 'KOLAB_FOLDER_CONFIGURATION'; } $data[$key] = $value; $data['uid'] = 'KOLAB_FOLDER_CONFIGURATION'; return $this->_annotation_data->save($data, $uid); } /** * Get the free/busy relevance for this folder * * @return int Value containing the FB_RELEVANCE. */ function getFbrelevance() { $result = $this->getKolabAttribute('incidences-for'); if (is_a($result, 'PEAR_Error') || empty($result)) { return KOLAB_FBRELEVANCE_ADMINS; } switch ($result) { case 'admins': return KOLAB_FBRELEVANCE_ADMINS; case 'readers': return KOLAB_FBRELEVANCE_READERS; case 'nobody': return KOLAB_FBRELEVANCE_NOBODY; default: return KOLAB_FBRELEVANCE_ADMINS; } } /** * Set the free/busy relevance for this folder * * @param int $relevance Value containing the FB_RELEVANCE * * @return mixed True on success or a PEAR_Error. */ function setFbrelevance($relevance) { switch ($relevance) { case KOLAB_FBRELEVANCE_ADMINS: $value = 'admins'; break; case KOLAB_FBRELEVANCE_READERS: $value = 'readers'; break; case KOLAB_FBRELEVANCE_NOBODY: $value = 'nobody'; break; default: $value = 'admins'; } return $this->_setAnnotation(KOLAB_ANNOT_ROOT . 'incidences-for', $value); } /** * Get the extended free/busy access settings for this folder * * @return array Array containing the users with access to the * extended information. */ function getXfbaccess() { $result = $this->getKolabAttribute('pxfb-readable-for'); if (is_a($result, 'PEAR_Error') || empty($result)) { return array(); } return explode(' ', $result); } /** * Set the extended free/busy access settings for this folder * * @param array $access Array containing the users with access to the * extended information. * * @return mixed True on success or a PEAR_Error. */ function setXfbaccess($access) { $value = join(' ', $access); return $this->_setAnnotation(KOLAB_ANNOT_ROOT . 'pxfb-readable-for', $value); } } * @package Kolab_Storage */ class Kolab_List { /** * The list of existing folders on this server. * * @var array */ var $_list; /** * A cache for folder objects (these do not necessarily exist). * * @var array */ var $_folders; /** * A cache array listing a default folder for each folder type. * * @var array */ var $_defaults; /** * A cache array listing a the folders for each folder type. * * @var array */ var $_types; /** * A validity marker. * * @var int */ var $validity; /** * The namespace handler. * * @var Horde_Kolab_Storage_Namespace */ var $namespace; /** * Constructor. */ function Kolab_List() { $this->validity = 0; if (isset($GLOBALS['conf']['kolab']['imap']['namespaces'])) { $this->namespace = new Horde_Kolab_Storage_Namespace_Config($GLOBALS['conf']['kolab']['imap']['namespaces']); } else { $this->namespace = new Horde_Kolab_Storage_Namespace_Fixed(); } $this->__wakeup(); } /** * Initializes the object. */ function __wakeup() { if (!isset($this->_folders)) { $this->_folders = array(); } foreach($this->_folders as $folder) { $folder->setList($this); } } /** * Attempts to return a reference to a concrete Kolab_Folders_List instance. * * It will only create a new instance if no Kolab_Folders instance currently * exists. * * This method must be invoked as: * $var = &Kolab_Folders_List::singleton(); * * @static * * @return Kolab_Folders_List The concrete List reference. */ function &singleton($destruct = false) { static $list; if (!isset($list) && !empty($GLOBALS['conf']['kolab']['imap']['cache_folders'])) { require_once 'Horde/SessionObjects.php'; $session = &Horde_SessionObjects::singleton(); $list = $session->query('kolab_folderlist'); } if (empty($list[Auth::getAuth()]) || $destruct) { $list[Auth::getAuth()] = new Kolab_List(); } if (!empty($GLOBALS['conf']['kolab']['imap']['cache_folders'])) { register_shutdown_function(array(&$list, 'shutdown')); } return $list[Auth::getAuth()]; } /** * Stores the object in the session cache. */ function shutdown() { require_once 'Horde/SessionObjects.php'; $session = &Horde_SessionObjects::singleton(); $session->overwrite('kolab_folderlist', $this, false); } /** * Returns the list of folders visible to the current user. * * @return array|PEAR_Error The list of IMAP folders, represented * as Kolab_Folder objects. */ function &listFolders() { if (!isset($this->_list)) { $session = &Horde_Kolab_Session::singleton(); $imap = &$session->getImap(); if (is_a($imap, 'PEAR_Error')) { return $imap; } // Obtain a list of all folders the current user has access to $list = $imap->getMailboxes(); if (is_a($list, 'PEAR_Error')) { return $list; } $this->_list = $list; } return $this->_list; } /** * Get several or all Folder objects. * * @param array $folders Several folder names or unset to retrieve * all folders. * * @return array|PEAR_Error An array of Kolab_Folder objects. */ function getFolders($folders = null) { if (!isset($folders)) { $folders = $this->listFolders(); if (is_a($folders, 'PEAR_Error')) { return $folders; } } $result = array(); foreach ($folders as $folder) { $result[] = $this->getFolder($folder); } return $result; } /** * Get a Folder object. * * @param string $folder The folder name. * * @return Kolab_Folder|PEAR_Error The Kolab folder object. */ function getFolder($folder) { if (!isset($this->_folders[$folder])) { $kf = new Kolab_Folder($folder); $kf->setList($this); $this->_folders[$folder] = &$kf; } return $this->_folders[$folder]; } /** * Get a new Folder object. * * @return Kolab_Folder|PEAR_Error The new Kolab folder object. */ function getNewFolder() { $folder = new Kolab_Folder(null); $folder->setList($this); return $folder; } /** * Get a Folder object based on a share ID. * * @param string $share The share ID. * @param string $type The type of the share/folder. * * @return Kolab_Folder|PEAR_Error The Kolab folder object. */ function getByShare($share, $type) { $folder = $this->parseShare($share, $type); if (is_a($folder, 'PEAR_Error')) { return $folder; } return $this->getFolder($folder); } /** * Get a list of folders based on the type. * * @param string $type The type of the share/folder. * * @return Kolab_Folder|PEAR_Error The list of Kolab folder * objects. */ function getByType($type) { if (!isset($this->_types)) { $result = $this->initiateCache(); if (is_a($result, 'PEAR_Error')) { return $result; } } if (isset($this->_types[$type])) { return $this->getFolders($this->_types[$type]); } else { return array(); } } /** * Get the default folder for a certain type. * * @param string $type The type of the share/folder. * * @return mixed The default folder, false if there is no default * and a PEAR_Error in case of an error. */ function getDefault($type) { if (!isset($this->_defaults)) { $result = $this->initiateCache(); if (is_a($result, 'PEAR_Error')) { return $result; } } if (isset($this->_defaults[Auth::getAuth()][$type])) { return $this->getFolder($this->_defaults[Auth::getAuth()][$type]); } else { return false; } } /** * Get the default folder for a certain type from a different owner. * * @param string $owner The folder owner. * @param string $type The type of the share/folder. * * @return mixed The default folder, false if there is no default * and a PEAR_Error in case of an error. */ function getForeignDefault($owner, $type) { if (!isset($this->_defaults)) { $result = $this->initiateCache(); if (is_a($result, 'PEAR_Error')) { return $result; } } if (isset($this->_defaults[$owner][$type])) { return $this->getFolder($this->_defaults[$owner][$type]); } else { return false; } } /** * Start the cache for the type specific and the default folders. */ function initiateCache() { $folders = $this->getFolders(); if (is_a($folders, 'PEAR_Error')) { return $folders; } $this->_types = array(); $this->_defaults = array(); foreach ($folders as $folder) { $type = $folder->getType(); if (is_a($type, 'PEAR_Error')) { return $type; } $default = $folder->isDefault(); if (is_a($default, 'PEAR_Error')) { return $default; } $owner = $folder->getOwner(); if (is_a($owner, 'PEAR_Error')) { return $owner; } if (!isset($this->_types[$type])) { $this->_types[$type] = array(); } $this->_types[$type][] = $folder->name; if ($default) { $this->_defaults[$owner][$type] = $folder->name; } } } /** * Converts the horde syntax for shares to storage identifiers. * * @param string $share The share ID that should be parsed. * @param string $type The type of the share/folder. * * @return string|PEAR_Error The corrected folder name. */ function parseShare($share, $type) { // Handle default shares if ($share == Auth::getAuth()) { $result = $this->getDefault($type); if (is_a($result, 'PEAR_Error')) { return $result; } if (!empty($result)) { return $result->name; } } return rawurldecode($share); } /** * Creates a new IMAP folder. * * @param Kolab_Folder $folder The folder that should be created. * * @return boolean|PEAR_Error True on success. */ function create(&$folder) { $session = &Horde_Kolab_Session::singleton(); $imap = &$session->getImap(); if (is_a($imap, 'PEAR_Error')) { return $imap; } $result = $imap->exists($folder->new_name); if (is_a($result, 'PEAR_Error')) { return $result; } if ($result) { return PEAR::raiseError(sprintf(_("Unable to add %s: destination folder already exists"), $folder->new_name)); } $result = $imap->create($folder->new_name); if (is_a($result, 'PEAR_Error')) { return $result; } $this->updateCache($folder); $this->validity++; return true; } /** * Rename an IMAP folder. * * @param Kolab_Folder $folder The folder that should be renamed. * * @return boolean|PEAR_Error True on success. */ function rename(&$folder) { $session = &Horde_Kolab_Session::singleton(); $imap = &$session->getImap(); if (is_a($imap, 'PEAR_Error')) { return $imap; } $result = $imap->exists($folder->new_name); if (is_a($result, 'PEAR_Error')) { return $result; } if ($result) { return PEAR::raiseError(sprintf(_("Unable to rename %s to %s: destination folder already exists"), $folder->name, $folder->new_name)); } $result = $imap->rename($folder->name, $folder->new_name); if (is_a($result, 'PEAR_Error')) { return $result; } $this->updateCache($folder, false); $this->updateCache($folder); $this->validity++; return true; } /** * Delete an IMAP folder. * * @param Kolab_Folder $folder The folder that should be deleted. * * @return boolean|PEAR_Error True on success. */ function remove(&$folder) { $session = &Horde_Kolab_Session::singleton(); $imap = &$session->getImap(); if (is_a($imap, 'PEAR_Error')) { return $imap; } $result = $imap->exists($folder->name); if (is_a($result, 'PEAR_Error')) { return $result; } if ($result === true) { $result = $imap->delete($folder->name); if (is_a($result, 'PEAR_Error')) { return $result; } } $this->updateCache($folder, false); $this->validity++; return true; } /** * Update the cache variables. * * @param Kolab_Folder $folder The folder that was changed. * @param boolean $added Has the folder been added or removed? */ function updateCache(&$folder, $added = true) { $type = $folder->getType(); if (is_a($type, 'PEAR_Error')) { Horde::logMessage(sprintf("Error while updating the Kolab folder list cache: %s.", $type->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR); return; } $default = $folder->isDefault(); if (is_a($default, 'PEAR_Error')) { Horde::logMessage(sprintf("Error while updating the Kolab folder list cache: %s.", $default->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR); return; } $owner = $folder->getOwner(); if (is_a($owner, 'PEAR_Error')) { Horde::logMessage(sprintf("Error while updating the Kolab folder list cache: %s.", $owner->getMessage()), __FILE__, __LINE__, PEAR_LOG_ERR); return; } if (!isset($this->_types) || !isset($this->_defaults)) { $this->initiateCache(); } if ($added) { $this->_folders[$folder->new_name] = &$folder; if (isset($this->_list)) { $this->_list[] = $folder->new_name; } $this->_types[$type][] = $folder->new_name; if ($default) { $this->_defaults[$owner][$type] = $folder->new_name; } } else { unset($this->_folders[$folder->name]); if (isset($this->_list)) { $idx = array_search($folder->name, $this->_list); if ($idx !== false) { unset($this->_list[$idx]); } } if (isset($this->_types[$type])) { $idx = array_search($folder->name, $this->_types[$type]); if ($idx !== false) { unset($this->_types[$type][$idx]); } } if ($default && isset($this->_defaults[$owner][$type])) { unset($this->_defaults[$owner][$type]); } } } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Storage */ /** * The Horde_Kolab_Storage_Namespace_Config:: allows to configure the available * IMAP namespaces on the Kolab server. * * Copyright 2004-2010 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_Storage * @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_Storage_Namespace_Config extends Horde_Kolab_Storage_Namespace { /** * Constructor. */ public function __construct(array $configuration) { parent::__construct(); foreach ($configuration as $element) { if ($element['type'] == Horde_Kolab_Storage_Namespace::SHARED && isset($element['prefix'])) { $namespace_element = new Horde_Kolab_Storage_Namespace_Element_SharedWithPrefix( $element['name'], $element['delimiter'], $element['prefix'] ); $this->_sharedPrefix = $element['prefix']; } else { $class = 'Horde_Kolab_Storage_Namespace_Element_' . ucfirst($element['type']); $namespace_element = new $class($element['name'], $element['delimiter']); } if (empty($element['name'])) { $this->_any = $namespace_element; } else { $this->_namespaces[] = $namespace_element; } if (isset($element['add'])) { $this->_primaryPersonalNamespace = $namespace_element; } } } }_delimiter, $name); $user = $path[1]; if (strpos($user, '@') === false) { $domain = strstr(array_pop($path), '@'); if (!empty($domain)) { $user .= $domain; } } return Horde_Kolab_Storage_Namespace::OTHER . ':' . $user; } /** * Return an array describing the path elements of the folder. * * @param string $name The name of the folder. * * @return array The path elements. */ protected function _subpath($name) { $path = parent::_subpath($name); array_shift($path); return $path; } }_prefix = $prefix; } /** * Return an array describing the path elements of the folder. * * @param string $name The name of the folder. * * @return array The path elements. */ protected function _subpath($name) { $path = parent::_subpath($name); if (strpos($path[0], $this->_prefix) === 0) { $path[0] = substr($path[0], strlen($this->_prefix)); } return $path; } }_name = $name; $this->_delimiter = $delimiter; } /** * Return the type of this namespace (personal, other, or shared). * * @return string The type. */ abstract public function getType(); /** * Return the name of this namespace. * * @return string The name/prefix. */ public function getName() { return $this->_name; } /** * Does the folder name lie in this namespace? * * @param string $name The name of the folder. * * @return boolean True if the folder is element of this namespace. */ public function matches($name) { return (strpos($name, $this->_name) === 0); } /** * Return the owner of a folder. * * @param string $name The name of the folder. * * @return string The owner of the folder. */ abstract public function getOwner($name); /** * Return the title of a folder. * * @param string $name The name of the folder. * * @return string The title of the folder. */ public function getTitle($name) { return join($this->_subpath($name), ':'); } /** * Get the sub path for the given folder name. * * @param string $name The folder name. * * @return string The sub path. */ public function getSubpath($name) { return join($this->_subpath($name), $this->_delimiter); } /** * Return an array describing the path elements of the folder. * * @param string $name The name of the folder. * * @return array The path elements. */ protected function _subpath($name) { $path = explode($this->_delimiter, $name); if ($path[0] == $this->_name) { array_shift($path); } //@todo: What about the potential trailing domain? return $path; } /** * Generate a folder path for the given path in this namespace. * * @param array $path The path of the folder. * * @return string The name of the folder. */ public function generateName($path) { return join($path, $this->_delimiter); } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Storage */ /** * The Horde_Kolab_Storage_Namespace_Fixed:: implements the default IMAP * namespaces on the Kolab server. * * Copyright 2004-2010 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_Storage * @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_Storage_Namespace_Fixed extends Horde_Kolab_Storage_Namespace { /** * Indicates the personal namespace that the class will use to create new * folders. * * @var string */ protected $_primaryPersonalNamespace = 'INBOX'; /** * Constructor. */ public function __construct() { parent::__construct(); $personal = new Horde_Kolab_Storage_Namespace_Element_Personal('INBOX/', '/'); $other = new Horde_Kolab_Storage_Namespace_Element_Other('user/', '/'); $shared = new Horde_Kolab_Storage_Namespace_Element_SharedWithPrefix('', '/', 'shared.'); $this->_namespaces = array($personal, $other); $this->_any = $shared; $this->_primaryPersonalNamespace = $personal; $this->_sharedPrefix = 'shared.'; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Storage */ /** * The Horde_Kolab_Storage_Namespace_Config:: allows to use the information from * the IMAP NAMESPACE command to identify the IMAP namespaces on the Kolab * server. * * Copyright 2004-2010 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_Storage * @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_Storage_Namespace_Imap extends Horde_Kolab_Storage_Namespace_Config { /** * Constructor. */ public function __construct(array $namespaces, array $configuration) { $c = array(); foreach ($namespaces as $namespace) { if (in_array($namespace['name'], array_keys($configuration))) { $namespace = array_merge($namespace, $configuration[$namespace['name']]); } $c[] = $namespace; } parent::__construct($c); } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Storage */ /** * The Horde_Kolab_Storage_Namespace:: class handles IMAP namespaces and allows * to derive folder information from folder names. * * Copyright 2004-2010 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_Storage * @author Gunnar Wrobel * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Kolab_Storage */ abstract class Horde_Kolab_Storage_Namespace { /** The possible namespace types (RFC 2342 [5]) */ const PERSONAL = 'personal'; const OTHER = 'other'; const SHARED = 'shared'; /** * The namespaces. * * @var array */ protected $_namespaces = array(); /** * The characterset this module uses to communicate with the outside world. * * @var string */ protected $_charset; /** * A prefix in the shared namespaces that will be ignored/removed. * * @var string */ protected $_sharedPrefix; /** * The namespace that matches any folder name not matching to another * namespace. * * @var Horde_Kolab_Storage_Namespace_Element */ protected $_any; /** * Indicates the personal namespace that the class will use to create new * folders. * * @var Horde_Kolab_Storage_Namespace_Element */ protected $_primaryPersonalNamespace; /** * Constructor. */ public function __construct() { $this->_charset = NLS::getCharset(); if (empty($this->_primaryPersonalNamespace)) { $personal = null; foreach ($this->_namespaces as $namespace) { if ($namespace->getName() == 'INBOX') { $this->_primaryPersonalNamespace = $namespace; break; } if (empty($personal) && $namespace->getType() == self::PERSONAL) { $personal = $namespace; } } if (empty($this->_primaryPersonalNamespace)) { $this->_primaryPersonalNamespace = $personal; } } } /** * Match a folder name with the corresponding namespace. * * @param string $name The name of the folder. * * @return Horde_Kolab_Storage_Namespace_Element The corresponding namespace. * * @throws Horde_Kolab_Storage_Exception If the namespace of the folder * cannot be determined. */ public function matchNamespace($name) { foreach ($this->_namespaces as $namespace) { if ($namespace->matches($name)) { return $namespace; } } if (!empty($this->_any)) { return $this->_any; } } /** * Get the character set used/expected when calling the getTitle() or * setName() methods. * * @return string The character set. */ public function getCharset() { return $this->_charset; } /** * Return the title of a folder. * * @param string $name The name of the folder. * * @return string The title of the folder. */ public function getTitle($name) { $name = String::convertCharset($name, 'UTF7-IMAP', $this->_charset); return $this->matchNamespace($name)->getTitle($name); } /** * Return the owner of a folder. * * @param string $name The name of the folder. * * @return string The owner of the folder. */ public function getOwner($name) { $name = String::convertCharset($name, 'UTF7-IMAP', $this->_charset); return $this->matchNamespace($name)->getOwner($name); } /** * Get the sub path for the given folder name. * * @param string $name The folder name. * * @return string The sub path. */ public function getSubpath($name) { $name = String::convertCharset($name, 'UTF7-IMAP', $this->_charset); return $this->matchNamespace($name)->getSubpath($name); } /** * Generate an IMAP folder name. * * @param string $name The new folder name. * * @return string The IMAP folder name. */ public function setName($name) { $namespace = $this->matchNamespace($name); $path = explode(':', $name); if (empty($this->_sharedPrefix) || (strpos($path[0], $this->_sharedPrefix) === false && $namespace->getType() != self::OTHER)) { array_unshift($path, $this->_primaryPersonalNamespace->getName()); $namespace = $this->_primaryPersonalNamespace; } return String::convertCharset($namespace->generateName($path), $this->_charset, 'UTF7-IMAP'); } }INDX( fv(hTii%p  Cache.phphRiiw%6wqPK Data.phphViiw%Es- Folder.phphRiiw%8w@< List.phphTw%w%w%w% Namespacep\iiw%x Namespace.phphRw%w%w%w%NAMESP~1pZiiw%x NAMESP~1.PHPhTiiw%dhy82 Perms.php * @package Kolab_Storage */ class Horde_Permission_Kolab extends Horde_Permission { /** * The folder name. * * @var string */ var $_folder; /** * A cache for the folder acl settings. The cache holds the permissions * in horde compatible format, not in the IMAP permission format. * * @var string */ var $data; /** * A cache for the raw IMAP folder acl settings. * * @var string */ var $acl; /** * Constructor. * * @param Kolab_Folder $folder The Kolab Folder these permissions belong to. * @param array $perms A set of initial permissions. */ function Horde_Permission_Kolab(&$folder, $perms = null) { $this->setFolder($folder); if (!isset($perms)) { $result = $this->getPerm(); if (is_a($result, 'PEAR_Error')) { Horde::logMessage(sprintf("Failed parsing permission information. Error was: %s", $result->getMessage()), __FILE__, __LINE__); } else { $perms = $result; } } $this->data = $perms; } /** * Returns the properties that need to be serialized. * * @return array List of serializable properties. */ function __sleep() { $properties = get_object_vars($this); unset($properties['_folder']); $properties = array_keys($properties); return $properties; } /** * Sets the folder object for this permission object. * * @param string $folder Kolab Folder object. */ function setFolder(&$folder) { $this->_folder = $folder; } /** * Gets one of the attributes of the object, or null if it isn't defined. * * @param string $attribute The attribute to get. * * @return mixed The value of the attribute, or null. */ function get($attribute) { // This object only handles permissions. So only return these switch ($attribute) { case 'perm': return $this->data; case 'type': return 'matrix'; default: // User requested something other than permissions: return null return null; } } /** * Gets the current permission of the folder and stores the values in the * cache. * * @return array|PEAR_Error The data array representing the permissions. */ function getPerm() { $acl = $this->_folder->getACL(); if (is_a($acl, 'PEAR_Error')) { Horde::logMessage($acl, __FILE__, __LINE__); return array(); } if (empty($acl)) { return array(); } $this->acl = &$acl; // Loop through the returned users $data = array(); foreach ($acl as $user => $rights) { // Convert the user rights to horde format $result = 0; for ($i = 0, $j = strlen($rights); $i < $j; $i++) { switch ($rights[$i]) { case 'l': $result |= PERMS_SHOW; break; case 'r': $result |= PERMS_READ; break; case 'i': $result |= PERMS_EDIT; break; case 'd': $result |= PERMS_DELETE; break; } } // Check for special users $name = ''; switch ($user) { case 'anyone': $name = 'default'; break; case 'anonymous': $name = 'guest'; break; } // Did we have a special user? if ($name) { // Store the converted acl in the cache $data[$name] = $result; continue; } // Is it a group? if (substr($user, 0, 6) == 'group:') { if (!isset($groups)) { require_once 'Horde/Group.php'; $groups = &Group::singleton(); } $group_id = $groups->getGroupId(substr($user, 6)); if (!is_a($group_id, 'PEAR_Error')) { // Store the converted acl in the cache $data['groups'][$group_id] = $result; } continue; } // Standard user // Store the converted acl in the cache $data['users'][$user] = $result; } return $data; } /** * Saves the current permission values from the cache to the IMAP folder. * * @return boolean|PEAR_Error True on success, false if there is * nothing to save. */ function save() { if (!isset($this->data)) { return false; } // FIXME: If somebody else accessed the folder before us, we will overwrite // the change here. $current = $this->getPerm(); foreach ($this->data as $user => $user_perms) { if (is_array($user_perms)) { foreach ($user_perms as $userentry => $perms) { if ($user == 'groups') { if (!isset($groups)) { require_once 'Horde/Group.php'; $groups = &Group::singleton(); } // Convert group id back to name $group_name = $groups->getGroupName($userentry); if (is_a($group_name, 'PEAR_Error')) { return $group_name; } $name = 'group:' . $group_name; } else if ($user == 'users') { $name = $userentry; } else { continue; } $result = $this->savePermission($name, $perms); if (is_a($result, 'PEAR_Error')) { return $result; } unset($current[$user][$userentry]); } } else { if ($user == 'default') { $name = 'anyone'; } else if ($user == 'guest') { $name = 'anonymous'; } else { continue; } $result = $this->savePermission($name, $user_perms); if (is_a($result, 'PEAR_Error')) { return $result; } unset($current[$user]); } } // Delete ACLs that have been removed foreach ($current as $user => $user_perms) { if (is_array($user_perms)) { foreach ($user_perms as $userentry => $perms) { if ($user == 'groups') { if (!isset($groups)) { require_once 'Horde/Group.php'; $groups = &Group::singleton(); } // Convert group id back to name $group_name = $groups->getGroupName($userentry); if (is_a($group_name, 'PEAR_Error')) { return $group_name; } $name = 'group:' . $group_name; } else { $name = $userentry; } $result = $this->_folder->deleteACL($name); if (is_a($result, 'PEAR_Error')) { return $result; } } } else { if ($user == 'default') { $name = 'anyone'; } else if ($user == 'guest') { $name = 'anonymous'; } else { continue; } $result = $this->_folder->deleteACL($name); if (is_a($result, 'PEAR_Error')) { return $result; } } } // Load the permission from the folder again $this->data = $this->getPerm(); return true; } /** * Saves the specified permission values for the given user on the * IMAP folder. * * @return boolean|PEAR_Error True on success. */ function savePermission($user, $perms) { // Convert the horde permission style to IMAP permissions $result = $user == $this->_folder->getOwner() ? 'a' : ''; if ($perms & PERMS_SHOW) { $result .= 'l'; } if ($perms & PERMS_READ) { $result .= 'r'; } if ($perms & PERMS_EDIT) { $result .= 'iswc'; } if ($perms & PERMS_DELETE) { $result .= 'd'; } $result = $this->_folder->setACL($user, $result); if (is_a($result, 'PEAR_Error')) { return $result; } return true; } /** * Finds out what rights the given user has to this object. * * @param string $user The user to check for. Defaults to the current * user. * @param string $creator The user who created the object. * * @return mixed A bitmask of permissions, a permission value, or * an array of permission values the user has, * depending on the permission type and whether the * permission value is ambiguous. False if there is * no such permsission. */ function getPermissions($user = null, $creator = null) { if ($user === null) { $user = Auth::getAuth(); } // If $creator was specified, check creator permissions. if ($creator !== null) { // If the user is the creator see if there are creator // permissions. if (strlen($user) && $user === $creator && ($perms = $this->getCreatorPermissions()) !== null) { return $perms; } } // Check user-level permissions. $userperms = $this->getUserPermissions(); if (isset($userperms[$user])) { return $userperms[$user]; } // If no user permissions are found, try group permissions. $groupperms = $this->getGroupPermissions(); if (!empty($groupperms)) { require_once 'Horde/Group.php'; $groups = &Group::singleton(); $composite_perm = null; foreach ($this->data['groups'] as $group => $perm) { $result = $groups->userIsInGroup($user, $group); if (is_a($result, 'PEAR_Error')) { return $result; } if ($result) { if ($composite_perm === null) { $composite_perm = 0; } $composite_perm |= $perm; } } if ($composite_perm !== null) { return $composite_perm; } } // If there are default permissions, return them. if (($perms = $this->getDefaultPermissions()) !== null) { return $perms; } // Otherwise, deny all permissions to the object. return false; } /** * Finds out if the user has the specified rights to the given object. * * @param string $user The user to check for. * @param integer $perm The permission level that needs to be checked * for. * @param string $creator The creator of the shared object. * * @return boolean True if the user has the specified permissions. */ function hasPermission($user, $perm, $creator = null) { return ($this->getPermissions($user, $creator) & $perm); } } * require_once 'Horde/Kolab/Storage.php'; * $folder = Kolab_Storage::getFolder('INBOX/Calendar'); * * * or (in case you are dealing with share identifications): * * * require_once 'Horde/Kolab/Storage.php'; * $folder = Kolab_Storage::getShare(Auth::getAuth(), 'event'); * * * To access data in a share (or folder) you need to retrieve the * corresponding data object: * * * require_once 'Horde/Kolab/Storage.php'; * $folder = Kolab_Storage::getShareData(Auth::getAuth(), 'event'); * * * $Horde: framework/Kolab_Storage/lib/Horde/Kolab/Storage.php,v 1.2.2.3 2009-01-06 15:23:17 jan Exp $ * * Copyright 2004-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. * * @author Gunnar Wrobel * @package Kolab_Storage */ class Kolab_Storage { /** * Return the folder object corresponding to the share of the * specified type (e.g. "contact", "event" etc.). * * @param string $share The id of the share. * @param string $type The share type. * * @return Kolab_Folder|PEAR_Error The folder object representing * the share. */ function &getShare($share, $type) { $list = &Kolab_List::singleton(); $share = $list->getByShare($share, $type); return $share; } /** * Return the folder object. * * @param string $folder The name of the folder. * * @return Kolab_Folder|PEAR_Error The folder object. */ function &getFolder($folder) { $list = &Kolab_List::singleton(); $share = $list->getFolder($folder); return $share; } /** * Return a data object for accessing data in the specified * folder. * * @param Kolab_Folder $folder The folder object. * @param string $data_type The type of data we want to * access in the folder. * @param int $data_format The version of the data format * we want to access in the folder. * * @return Kolab_Data|PEAR_Error The data object. */ function &getData(&$folder, $data_type = null, $data_format = 1) { if (empty($data_type)) { $data_type = $folder->getType(); } $data = $folder->getData($data_type, $data_format); return $data; } /** * Return a data object for accessing data in the specified * share. * * @param string $share The id of the share. * @param string $type The share type. * @param string $data_type The type of data we want to * access in the folder. * @param int $data_format The version of the data format * we want to access in the folder. * * @return Kolab_Data|PEAR_Error The data object. */ function &getShareData($share, $type, $data_type = null, $data_format = 1) { $folder = Kolab_Storage::getShare($share, $type); if (is_a($folder, 'PEAR_Error')) { return $folder; } $data = Kolab_Storage::getData($folder, $data_type, $data_format); return $data; } /** * Return a data object for accessing data in the specified * folder. * * @param string $folder The name of the folder. * @param string $data_type The type of data we want to * access in the folder. * @param int $data_format The version of the data format * we want to access in the folder. * * @return Kolab_Data|PEAR_Error The data object. */ function &getFolderData($folder, $data_type = null, $data_format = 1) { $folder = Kolab_Storage::getFolder($folder); if (is_a($folder, 'PEAR_Error')) { return $folder; } $data = Kolab_Storage::getData($folder, $data_type, $data_format); return $data; } } * @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_Filter/lib/Horde/Kolab/Test/Filter.php,v 1.1.2.3 2009-03-06 08:43:15 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_Filter extends Horde_Kolab_Test_Storage { /** * Set up testing. */ protected function setUp() { $result = $this->prepareBasicSetup(); $this->server = &$result['server']; $this->storage = &$result['storage']; $this->auth = &$result['auth']; global $conf; $conf['kolab']['imap']['server'] = 'localhost'; $conf['kolab']['imap']['port'] = 0; $conf['kolab']['imap']['allow_special_users'] = true; $conf['kolab']['filter']['reject_forged_from_header'] = false; $conf['kolab']['filter']['email_domain'] = 'example.org'; $conf['kolab']['filter']['privileged_networks'] = '127.0.0.1,192.168.0.0/16'; $conf['kolab']['filter']['verify_from_header'] = true; $conf['kolab']['filter']['calendar_id'] = 'calendar'; $conf['kolab']['filter']['calendar_pass'] = 'calendar'; $conf['kolab']['filter']['lmtp_host'] = 'imap.example.org'; $conf['kolab']['filter']['simple_locks'] = true; $conf['kolab']['filter']['simple_locks_timeout'] = 3; $result = $this->auth->authenticate('wrobel', array('password' => 'none')); $this->assertNoError($result); $folder = $this->storage->getNewFolder(); $folder->setName('Kalender'); $result = $folder->save(array('type' => 'event', 'default' => true)); $this->assertNoError($result); } /** * 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) { 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) { default: return parent::runThen($world, $action, $arguments); } } /** * Fill a Kolab Server with test users. * * @param Kolab_Server &$server The server to populate. * * @return Horde_Kolab_Server The empty server. */ public function prepareUsers(&$server) { parent::prepareUsers(&$server); $result = $server->add($this->provideFilterUserOne()); $this->assertNoError($result); $result = $server->add($this->provideFilterUserTwo()); $this->assertNoError($result); $result = $server->add($this->provideFilterUserThree()); $this->assertNoError($result); $result = $server->add($this->provideFilterCalendarUser()); $this->assertNoError($result); } /** * Return a test user. * * @return array The test user. */ public function provideFilterUserOne() { return array('givenName' => 'Me', 'sn' => 'Me', 'type' => KOLAB_OBJECT_USER, 'mail' => 'me@example.org', 'uid' => 'me', 'userPassword' => 'me', 'kolabHomeServer' => 'home.example.org', 'kolabImapServer' => 'imap.example.org', 'kolabFreeBusyServer' => 'https://fb.example.org/freebusy', KOLAB_ATTR_IPOLICY => array('ACT_REJECT_IF_CONFLICTS'), 'alias' => array('me.me@example.org', 'MEME@example.org'), ); } /** * Return a test user. * * @return array The test user. */ public function provideFilterUserTwo() { return array('givenName' => 'You', 'sn' => 'You', 'type' => KOLAB_OBJECT_USER, 'mail' => 'you@example.org', 'uid' => 'you', 'userPassword' => 'you', 'kolabHomeServer' => 'home.example.org', 'kolabImapServer' => 'home.example.org', 'kolabFreeBusyServer' => 'https://fb.example.org/freebusy', 'alias' => array('you.you@example.org'), KOLAB_ATTR_KOLABDELEGATE => 'wrobel@example.org',); } /** * Return a test user. * * @return array The test user. */ public function provideFilterUserThree() { return array('givenName' => 'Else', 'sn' => 'Else', 'type' => KOLAB_OBJECT_USER, 'mail' => 'else@example.org', 'uid' => 'else', 'userPassword' => 'else', 'kolabHomeServer' => 'imap.example.org', 'kolabImapServer' => 'imap.example.org', 'kolabFreeBusyServer' => 'https://fb.example.org/freebusy', KOLAB_ATTR_KOLABDELEGATE => 'me@example.org',); } /** * Return the calendar user. * * @return array The calendar user. */ public function provideFilterCalendarUser() { return array('cn' => 'calendar', 'sn' => 'calendar', 'givenName' => '', 'type' => KOLAB_OBJECT_USER, 'mail' => 'calendar@example.org', 'uid' => 'calendar@home.example.org', 'userPassword' => 'calendar', 'kolabHomeServer' => 'home.example.org', 'kolabImapServer' => 'imap.example.org', ); } public function sendFixture($infile, $outfile, $user, $client, $from, $to, $host, $params = array()) { $_SERVER['argv'] = array($_SERVER['argv'][0], '--sender=' . $from, '--recipient=' . $to, '--user=' . $user, '--host=' . $host, '--client=' . $client); $in = file_get_contents($infile, 'r'); $tmpfile = Util::getTempFile('KolabFilterTest'); $tmpfh = @fopen($tmpfile, 'w'); if (empty($params['unmodified_content'])) { @fwrite($tmpfh, sprintf($in, $from, $to)); } else { @fwrite($tmpfh, $in); } @fclose($tmpfh); $inh = @fopen($tmpfile, 'r'); /* Setup the class */ if (empty($params['incoming'])) { require_once 'Horde/Kolab/Filter/Content.php'; $parser = &new Horde_Kolab_Filter_Content(); } else { require_once 'Horde/Kolab/Filter/Incoming.php'; $parser = &new Horde_Kolab_Filter_Incoming(); } ob_start(); /* Parse the mail */ $result = $parser->parse($inh, 'echo'); if (empty($params['error'])) { $this->assertNoError($result); $this->assertTrue(empty($result)); $output = ob_get_contents(); ob_end_clean(); $out = file_get_contents($outfile); $output = preg_replace('/^--+=.*$/m', '----', $output); $out = preg_replace('/^--+=.*$/m', '----', $out); $output = preg_replace('/^Message-ID.*$/m', '----', $output); $out = preg_replace('/^Message-ID.*$/m', '----', $out); $output = preg_replace('/boundary=.*$/m', '----', $output); $out = preg_replace('/boundary=.*$/m', '----', $out); $output = preg_replace('/\s/', '', $output); $out = preg_replace('/\s/', '', $out); if (empty($params['unmodified_content'])) { $this->assertEquals(sprintf($out, $from, $to), $output); } else { $this->assertEquals($out, $output); } } else { $this->assertError($result, $params['error']); } } } * @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.1.2.3 2010-10-10 16:26:50 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 { /** * Set up testing. */ protected function setUp() { $result = $this->prepareBasicSetup(); $this->server = &$result['server']; $this->storage = &$result['storage']; $this->auth = &$result['auth']; global $conf; $conf['kolab']['ldap']['phpdn'] = null; $conf['fb']['cache_dir'] = Horde::getTempDir() . '/Kolab_FreeBusy_Test_' . md5(uniqid(mt_rand(), true)); mkdir($conf['fb']['cache_dir'], 0755); $conf['kolab']['freebusy']['server'] = 'https://fb.example.org/freebusy'; $conf['fb']['use_acls'] = true; } /** * Set up testing. */ protected function tearDown() { if (file_exists('/tmp/aclcache.db')) { unlink('/tmp/aclcache.db'); } if (file_exists('/tmp/xaclcache.db')) { unlink('/tmp/xaclcache.db'); } if (file_exists('/tmp/example^org')) { $this->unlinkDir('/tmp/example^org'); } } function unlinkDir($dir) { if(!$dh = @opendir($dir)) { return; } while (false !== ($obj = readdir($dh))) { if($obj == '.' || $obj == '..') { continue; } if (!@unlink($dir . '/' . $obj)) { $this->unlinkDir($dir . '/' . $obj); } } closedir($dh); @rmdir($dir); return; } /** * Handle a "given" step. * * @param array &$world Joined "world" of variables. * @param string $action The description of the step. * @param array $arguments Additional arguments to the step. * * @return mixed The outcome of the step. */ public function runGiven(&$world, $action, $arguments) { switch($action) { default: return parent::runGiven($world, $action, $arguments); } } /** * Handle a "when" step. * * @param array &$world Joined "world" of variables. * @param string $action The description of the step. * @param array $arguments Additional arguments to the step. * * @return mixed The outcome of the step. */ public function runWhen(&$world, $action, $arguments) { switch($action) { case 'adding an event to a folder': $world['result']['add_event'][] = $this->addEvent($arguments[0], $arguments[1]); break; case 'triggering the folder': include_once 'Horde/Kolab/FreeBusy.php'; $_GET['folder'] = $arguments[0]; $_GET['extended'] = '1'; $_SERVER['PHP_AUTH_USER'] = $arguments[1]; $_SERVER['PHP_AUTH_PW'] = $arguments[2]; $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'; $_SERVER['PHP_AUTH_USER'] = $arguments[1]; $_SERVER['PHP_AUTH_PW'] = $arguments[2]; $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; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Share */ /** * We need the unit test framework */ require_once 'PHPUnit/Framework.php'; require_once 'PHPUnit/Extensions/Story/TestCase.php'; /** * We need the classes to be tested */ require_once 'Horde.php'; require_once 'Horde/Kolab/Server.php'; /** * Base for PHPUnit scenarios. * * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Test/Server.php,v 1.1.2.3 2009-01-06 15:23:17 jan 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=Share */ class Horde_Kolab_Test_Server extends PHPUnit_Extensions_Story_TestCase { /** * 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) { case 'an empty Kolab server': $world['server'] = &$this->prepareEmptyKolabServer(); break; case 'a basic Kolab server': $world['server'] = &$this->prepareBasicKolabServer(); break; case 'the Kolab auth driver has been selected': $world['auth'] = &$this->prepareKolabAuthDriver(); break; default: return $this->notImplemented($action); } } /** * 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 a Kolab server object': $world['result']['add'] = $world['server']->add($arguments[0]); break; case 'logging in as a user with a password': $world['login'] = $world['auth']->authenticate($arguments[0], array('password' => $arguments[1])); break; case 'adding an object list': foreach ($arguments[0] as $object) { $result = $world['server']->add($object); if (is_a($result, 'PEAR_Error')) { $world['result']['add'] = $result; return; } } $world['result']['add'] = true; break; case 'adding a user without first name': $world['result']['add'] = $world['server']->add($this->provideInvalidUserWithoutGivenName()); break; case 'adding a user without last name': $world['result']['add'] = $world['server']->add($this->provideInvalidUserWithoutLastName()); break; case 'adding a user without password': $world['result']['add'] = $world['server']->add($this->provideInvalidUserWithoutPassword()); break; case 'adding a user without primary mail': $world['result']['add'] = $world['server']->add($this->provideInvalidUserWithoutMail()); break; case 'adding a distribution list': $world['result']['add'] = $world['server']->add($this->provideDistributionList()); break; case 'listing all users': $world['list'] = $world['server']->listObjects(KOLAB_OBJECT_USER); break; case 'listing all groups': $world['list'] = $world['server']->listObjects(KOLAB_OBJECT_GROUP); break; case 'listing all objects of type': $world['list'] = $world['server']->listObjects($arguments[0]); break; case 'retrieving a hash list with all objects of type': $world['list'] = $world['server']->listHash($arguments[0]); break; default: return $this->notImplemented($action); } } /** * 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 result should be an object of type': if (!isset($world['result'])) { $this->fail('Did not receive a result!'); } foreach ($world['result'] as $result) { if ($result instanceOf PEAR_Error) { $this->assertEquals('', $result->getMessage()); } else { $this->assertEquals($arguments[0], get_class($result)); } } break; case 'the result indicates success.': if (!isset($world['result'])) { $this->fail('Did not receive a result!'); } foreach ($world['result'] as $result) { if ($result instanceOf PEAR_Error) { $this->assertEquals('', $result->getMessage()); } else { $this->assertTrue($result); } } break; case 'the result should indicate an error with': if (!isset($world['result'])) { $this->fail('Did not receive a result!'); } foreach ($world['result'] as $result) { if ($result instanceOf PEAR_Error) { $this->assertEquals($arguments[0], $result->getMessage()); } else { $this->assertEquals($arguments[0], 'Action succeeded without an error.'); } } break; case 'the list has a number of entries equal to': if ($world['list'] instanceOf PEAR_Error) { $this->assertEquals('', $world['list']->getMessage()); } else { $this->assertEquals($arguments[0], count($world['list'])); } break; case 'the list is an empty array': if ($world['list'] instanceOf PEAR_Error) { $this->assertEquals('', $world['list']->getMessage()); } else { $this->assertEquals(array(), $world['list']); } break; case 'the list is an empty array': if ($world['list'] instanceOf PEAR_Error) { $this->assertEquals('', $world['list']->getMessage()); } else { $this->assertEquals(array(), $world['list']); } break; case 'the provided list and the result list match with regard to these attributes': if ($world['list'] instanceOf PEAR_Error) { $this->assertEquals('', $world['list']->getMessage()); } else { $provided_vals = array(); foreach ($arguments[2] as $provided_element) { if (isset($provided_element[$arguments[0]])) { $provided_vals[] = $provided_element[$arguments[0]]; } else { $this->fail(sprintf('The provided element %s does have no value for %s.', print_r($provided_element, true), print_r($arguments[0]))); } } $result_vals = array(); foreach ($world['list'] as $result_element) { if (isset($result_element[$arguments[1]])) { $result_vals[] = $result_element[$arguments[1]]; } else { $this->fail(sprintf('The result element %s does have no value for %s.', print_r($result_element, true), print_r($arguments[1]))); } } $this->assertEquals(array(), array_diff($provided_vals, $result_vals)); } break; case 'each element in the result list has an attribute': if ($world['list'] instanceOf PEAR_Error) { $this->assertEquals('', $world['list']->getMessage()); } else { $result_vals = array(); foreach ($world['list'] as $result_element) { if (!isset($result_element[$arguments[0]])) { $this->fail(sprintf('The result element %s does have no value for %s.', print_r($result_element, true), print_r($arguments[0]))); } } } break; case 'each element in the result list has an attribute set to a given value': if ($world['list'] instanceOf PEAR_Error) { $this->assertEquals('', $world['list']->getMessage()); } else { $result_vals = array(); foreach ($world['list'] as $result_element) { if (!isset($result_element[$arguments[0]])) { $this->fail(sprintf('The result element %s does have no value for %s.', print_r($result_element, true), print_r($arguments[0], true))); } if ($result_element[$arguments[0]] != $arguments[1]) { $this->fail(sprintf('The result element %s has an unexpected value %s for %s.', print_r($result_element, true), print_r($result_element[$arguments[0]], true), print_r($arguments[0], true))); } } } break; case 'the login was successful': $this->assertNoError($world['login']); $this->assertTrue($world['login']); break; case 'the list contains a number of elements equal to': $this->assertEquals($arguments[0], count($world['list'])); break; default: return $this->notImplemented($action); } } /** * Prepare an empty Kolab server. * * @return Horde_Kolab_Server The empty server. */ public function &prepareEmptyKolabServer() { global $conf; include_once 'Horde/Kolab/Server.php'; $GLOBALS['KOLAB_SERVER_TEST_DATA'] = array(); /** Prepare a Kolab test server */ $conf['kolab']['server']['driver'] = 'test'; $server = Horde_Kolab_Server::singleton(); /** Ensure we don't use a connection from older tests */ $server->unbind(); /** Set base DN */ $server->_base_dn = 'dc=example,dc=org'; /** Clean the server data */ return $server; } /** * Prepare a Kolab server with some basic entries. * * @return Horde_Kolab_Server The empty server. */ public function &prepareBasicServer() { $server = $this->prepareEmptyKolabServer(); $this->prepareUsers($server); return $server; } /** * Fill a Kolab Server with test users. * * @param Kolab_Server &$server The server to populate. * * @return Horde_Kolab_Server The empty server. */ public function prepareUsers(&$server) { $result = $server->add($this->provideBasicUserOne()); $this->assertNoError($result); $result = $server->add($this->provideBasicUserTwo()); $this->assertNoError($result); $result = $server->add($this->provideBasicAddress()); $this->assertNoError($result); $result = $server->add($this->provideBasicAdmin()); $this->assertNoError($result); $result = $server->add($this->provideBasicDomainMaintainer()); $this->assertNoError($result); $result = $server->add($this->provideBasicGroupOne()); $this->assertNoError($result); $result = $server->add($this->provideBasicGroupTwo()); $this->assertNoError($result); $result = $server->add($this->provideBasicMaintainer()); $this->assertNoError($result); $result = $server->add($this->provideBasicSharedFolder()); $this->assertNoError($result); } /** * Prepare a Kolab Auth Driver. * * @return Auth The auth driver. */ public function &prepareKolabAuthDriver() { include_once 'Horde/Auth.php'; $auth = Auth::singleton('kolab'); return $auth; } /** * Return a test user. * * @return array The test user. */ public function provideBasicUserOne() { return array('givenName' => 'Gunnar', 'sn' => 'Wrobel', 'type' => KOLAB_OBJECT_USER, 'mail' => 'wrobel@example.org', 'uid' => 'wrobel', 'userPassword' => 'none', 'kolabHomeServer' => 'home.example.org', 'kolabImapServer' => 'imap.example.org', 'kolabFreeBusyServer' => 'https://fb.example.org/freebusy', KOLAB_ATTR_IPOLICY => array('ACT_REJECT_IF_CONFLICTS'), 'alias' => array('gunnar@example.org', 'g.wrobel@example.org'), ); } /** * Return a test user. * * @return array The test user. */ public function provideBasicUserTwo() { return array('givenName' => 'Test', 'sn' => 'Test', 'type' => KOLAB_OBJECT_USER, 'mail' => 'test@example.org', 'uid' => 'test', 'userPassword' => 'test', 'kolabHomeServer' => 'home.example.org', 'kolabImapServer' => 'home.example.org', 'kolabFreeBusyServer' => 'https://fb.example.org/freebusy', 'alias' => array('t.test@example.org'), KOLAB_ATTR_KOLABDELEGATE => 'wrobel@example.org',); } /** * Return a test address. * * @return array The test address. */ public function provideBasicAddress() { return array('givenName' => 'Test', 'sn' => 'Address', 'type' => KOLAB_OBJECT_ADDRESS, 'mail' => 'address@example.org'); } /** * Return a test administrator. * * @return array The test administrator. */ public function provideBasicAdmin() { return array('sn' => 'Administrator', 'givenName' => 'The', 'uid' => 'admin', 'type' => KOLAB_OBJECT_ADMINISTRATOR, 'userPassword' => 'none'); } /** * Return a test maintainer. * * @return array The test maintainer. */ public function provideBasicMaintainer() { return array('sn' => 'Tainer', 'givenName' => 'Main', 'uid' => 'maintainer', 'type' => KOLAB_OBJECT_MAINTAINER, 'userPassword' => 'none', ); } /** * Return a test domain maintainer. * * @return array The test domain maintainer. */ public function provideBasicDomainMaintainer() { return array('sn' => 'Maintainer', 'givenName' => 'Domain', 'uid' => 'domainmaintainer', 'type' => KOLAB_OBJECT_DOMAINMAINTAINER, 'userPassword' => 'none', 'domain' => array('example.com'), ); } /** * Return a test shared folder. * * @return array The test shared folder. */ public function provideBasicSharedFolder() { return array('cn' => 'shared@example.org', 'kolabHomeServer' => 'example.org', 'type' => KOLAB_OBJECT_SHAREDFOLDER); } /** * Return a test group. * * @return array The test group. */ public function provideBasicGroupOne() { return array('mail' => 'empty.group@example.org', 'type' => KOLAB_OBJECT_GROUP); } /** * Return a test group. * * @return array The test group. */ public function provideBasicGroupTwo() { return array('mail' => 'group@example.org', 'type' => KOLAB_OBJECT_GROUP, 'member' => array('cn=Test Test,dc=example,dc=org', 'cn=Gunnar Wrobel,dc=example,dc=org')); } public function provideDistributionList() { return array('mail' => 'distlist@example.org', 'type' => KOLAB_OBJECT_DISTLIST, 'member' => array('cn=Test Test,dc=example,dc=org', 'cn=Gunnar Wrobel,dc=example,dc=org')); } public function provideInvalidUserWithoutPassword() { return array('givenName' => 'Test', 'sn' => 'Test', 'type' => KOLAB_OBJECT_USER, 'mail' => 'test@example.org'); } public function provideInvalidUserWithoutGivenName() { return array('sn' => 'Test', 'userPassword' => 'none', 'type' => KOLAB_OBJECT_USER, 'mail' => 'test@example.org'); } public function provideInvalidUserWithoutLastName() { return array('givenName' => 'Test', 'userPassword' => 'none', 'type' => KOLAB_OBJECT_USER, 'mail' => 'test@example.org'); } public function provideInvalidUserWithoutMail() { return array('givenName' => 'Test', 'sn' => 'Test', 'userPassword' => 'none', 'type' => KOLAB_OBJECT_USER); } public function provideInvalidUsers() { return array( array( $this->provideInvalidUserWithoutPassword(), 'Adding object failed: The value for "userPassword" is missing!' ), array( $this->provideInvalidUserWithoutGivenName(), 'Adding object failed: Either the last name or the given name is missing!' ), array( $this->provideInvalidUserWithoutLastName(), 'Adding object failed: Either the last name or the given name is missing!' ), array( $this->provideInvalidUserWithoutMail(), 'Adding object failed: The value for "mail" is missing!' ), ); } /** FIXME: Prefix the stuff bewlow with provide...() */ public function validUsers() { return array( array( $this->provideBasicUserOne(), ), array( $this->provideBasicUserTwo(), ), ); } public function validAddresses() { return array( array( $this->provideBasicAddress(), ), ); } public function validAdmins() { return array( array( $this->provideBasicAdmin(), ), ); } public function validMaintainers() { return array( array( $this->provideBasicMaintainer(), ) ); } public function validDomainMaintainers() { return array( array( $this->provideBasicDomainMaintainer(), ) ); } public function validGroups() { return array( array( $this->validGroupWithoutMembers(), ), array( array('mail' => 'group@example.org', 'type' => KOLAB_OBJECT_GROUP, 'member' => array('cn=Test Test,dc=example,dc=org', 'cn=Gunnar Wrobel,dc=example,dc=org') ), ), array( array('mail' => 'group2@example.org', 'type' => KOLAB_OBJECT_GROUP, 'member' => array('cn=Gunnar Wrobel,dc=example,dc=org') ), ), ); } public function validSharedFolders() { return array( array('cn' => 'Shared', 'type' => KOLAB_OBJECT_SHAREDFOLDER ), ); } public function validGroupWithoutMembers() { return array('mail' => 'empty.group@example.org', 'type' => KOLAB_OBJECT_GROUP, ); } public function userLists() { return array( ); } public function groupLists() { return array( array( array( array('type' => KOLAB_OBJECT_GROUP, 'mail' => 'empty.group@example.org', ), ) ), array( array( array('mail' => 'empty.group@example.org', 'type' => KOLAB_OBJECT_GROUP, ), ), array( array('mail' => 'group@example.org', 'type' => KOLAB_OBJECT_GROUP, 'member' => array('cn=Test Test,dc=example,dc=org', 'cn=Gunnar Wrobel,dc=example,dc=org') ), ), array( array('mail' => 'group2@example.org', 'type' => KOLAB_OBJECT_GROUP, 'member' => array('cn=Gunnar Wrobel,dc=example,dc=org') ), ), ) ); } public function userListByLetter() { return array( ); } public function userListByAttribute() { return array( ); } public function userAdd() { return array( ); } public function invalidMails() { return array( ); } public function largeList() { return array( ); } /** * Ensure that the variable contains no PEAR_Error and fail if it does. * * @param mixed $var The variable to check. * * @return NULL. */ public function assertNoError($var) { if (is_a($var, 'PEAR_Error')) { $this->assertEquals('', $var->getMessage()); } } /** * Ensure that the variable contains a PEAR_Error and fail if it does * not. Optionally compare the error message with the provided message and * fail if both do not match. * * @param mixed $var The variable to check. * @param string $msg The expected error message. * * @return NULL. */ public function assertError($var, $msg = null) { $this->assertEquals('PEAR_Error', get_class($var)); if (isset($msg)) { $this->assertEquals($msg, $var->getMessage()); } } } * @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/Server.php'; /** * We need the classes to be tested */ require_once 'Horde/Kolab/Storage/List.php'; /** * Base for PHPUnit scenarios. * * $Horde: framework/Kolab_Storage/lib/Horde/Kolab/Test/Storage.php,v 1.1.2.5 2009-04-25 18:43:40 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_Storage extends Horde_Kolab_Test_Server { /** * 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) { case 'an empty Kolab storage': $world['storage'] = &$this->prepareEmptyKolabStorage(); break; case 'a Kolab setup': $result = $this->prepareKolabSetup(); $world['server'] = &$result['server']; $world['storage'] = &$result['storage']; $world['auth'] = &$result['auth']; break; case 'a populated Kolab setup': $result = $this->prepareBasicSetup(); $world['server'] = &$result['server']; $world['storage'] = &$result['storage']; $world['auth'] = &$result['auth']; break; 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 'create a Kolab default calendar with name': $folder = $world['storage']->getNewFolder(); $folder->setName($arguments[0]); $world['folder_creation'] = $folder->save(array('type' => 'event', 'default' => true)); $folder->setACL(Auth::getAuth(), 'alrid'); break; case 'allow a group full access to a folder': $folder = $world['storage']->getFolder($arguments[1]); $folder->setACL($arguments[0], 'alrid'); break; case 'retrieving the list of shares for the application': require_once 'Horde/Share.php'; $shares = Horde_Share::singleton($arguments[0], 'kolab'); $world['list'] = $shares->listShares(Auth::getAuth()); break; case 'logging in as a user with a password': $world['login'] = $world['auth']->authenticate($arguments[0], array('password' => $arguments[1])); $world['storage'] = &$this->prepareEmptyKolabStorage(); return parent::runWhen($world, $action, $arguments); 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 creation of the folder was successful': $this->assertNoError($world['folder_creation']); break; case 'the list contains a share named': $this->assertNoError($world['list']); $this->assertContains($arguments[0], array_keys($world['list'])); break; default: return parent::runThen($world, $action, $arguments); } } /** * Prepare a Kolab server with some basic entries. * * @return Horde_Kolab_Server The empty server. */ public function &prepareBasicSetup() { $world = &$this->prepareKolabSetup(); $this->prepareUsers($world['server']); return $world; } /** * Prepare an empty Kolab storage. * * @return Kolab_List The empty storage. */ public function &prepareEmptyKolabStorage() { /** Ensure that IMAP runs in testing mode and is empty */ $GLOBALS['KOLAB_TESTING'] = array(); /** Prepare a Kolab test storage */ $storage = Kolab_List::singleton(true); return $storage; } /** * Prepare the browser setup. * * @return NULL */ public function prepareBrowser() { /** Provide a browser setup */ include_once 'Horde/Browser.php'; $GLOBALS['browser'] = new Browser(); } /** * Prepare the configuration. * * @return NULL */ public function prepareConfiguration() { $fh = fopen(HORDE_BASE . '/config/conf.php', 'w'); $data = <<applications['horde'] = array( 'fileroot' => dirname(__FILE__) . '/..', 'webroot' => '/', 'initial_page' => 'login.php', 'name' => _("Horde"), 'status' => 'active', 'templates' => dirname(__FILE__) . '/../templates', 'provides' => 'horde', ); EOD; fwrite($fh, "prepareEmptyKolabServer(); $world['storage'] = &$this->prepareEmptyKolabStorage(); $world['auth'] = &$this->prepareKolabAuthDriver(); $this->prepareBasicConfiguration(); if (!defined('HORDE_BASE')) { define('HORDE_BASE', $this->provideHordeBase()); } if (!file_exists(HORDE_BASE . '/config')) { $result = mkdir(HORDE_BASE . '/config', 0755, true); } $this->prepareConfiguration(); $this->prepareRegistry(); $this->prepareNotification(); if (!isset($GLOBALS['perms'])) { $GLOBALS['perms'] = &Perms::singleton(); } /** Provide the horde registry */ include_once 'Horde/Registry.php'; include_once 'Horde/Notification.php'; $GLOBALS['registry'] = &Registry::singleton(); $GLOBALS['notification'] = &Notification::singleton(); $this->prepareFixedConfiguration(); $this->prepareBrowser(); /* Make sure the configuration is correct after initializing the registry */ $this->prepareBasicConfiguration(); return $world; } /** * Fix the read configuration. * * @return NULL */ public function prepareFixedConfiguration() { $GLOBALS['conf'] = &$GLOBALS['registry']->_confCache['horde']; } /** * Prepare a basic Kolab configuration. * * @return NULL */ public function prepareBasicConfiguration() { /** We need a server name for MIME processing */ $_SERVER['SERVER_NAME'] = $this->provideServerName(); $_SERVER['SERVER_PORT'] = 80; $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; } /** * Create a new folder. * * @param string $name Name of the new folder. * @param string $type Type of the new folder. * @param boolean $default Should the new folder be a default folder? * * @return Kolab_Folder The new folder. */ public function &prepareNewFolder(&$storage, $name, $type, $default = false) { $folder = $storage->getNewFolder(); $folder->setName($name); $this->assertNoError($folder->save(array('type' => $type, 'default' => $default))); return $folder; } function provideServerName() { return 'localhost'; } function provideHordeBase() { return Horde::getTempDir() . '/test_config'; } } * @license http://www.fsf.org/copyleft/lgpl.html LGPL * @link http://pear.horde.org/index.php?package=Share */ /** * We need the unit test framework */ require_once 'PHPUnit/Framework.php'; require_once 'PHPUnit/Extensions/Story/TestCase.php'; /** * We need the classes to be tested */ require_once 'Horde.php'; require_once 'Horde/Share.php'; require_once 'Horde/Kolab/Storage/List.php'; require_once 'Horde/Kolab/Server.php'; /** * Base for PHPUnit scenarios. * * $Horde: framework/Kolab_Test/lib/Horde/Kolab/Test.php,v 1.1.2.6 2009-01-06 15:23:19 jan 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=Share */ class Horde_Kolab_Test extends PHPUnit_Extensions_Story_TestCase { /** * 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) { case 'an empty Kolab server': $world['server'] = &$this->prepareEmptyKolabServer(); break; case 'a basic Kolab server': $world['server'] = &$this->prepareBasicKolabServer(); break; case 'an empty Kolab storage': $world['storage'] = &$this->prepareEmptyKolabStorage(); break; case 'the Kolab auth driver has been selected': $world['auth'] = &$this->prepareKolabAuthDriver(); break; case 'a Kolab setup': $result = $this->prepareKolabSetup(); $world['server'] = &$result['server']; $world['storage'] = &$result['storage']; $world['auth'] = &$result['auth']; break; case 'a populated Kolab setup': $result = $this->prepareBasicSetup(); $world['server'] = &$result['server']; $world['storage'] = &$result['storage']; $world['auth'] = &$result['auth']; break; default: return $this->notImplemented($action); } } /** * 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 a Kolab server object': $world['result']['add'] = $world['server']->add($arguments[0]); break; case 'logging in as a user with a password': $world['login'] = $world['auth']->authenticate($arguments[0], array('password' => $arguments[1])); break; case 'create a Kolab default calendar with name': $folder = $world['storage']->getNewFolder(); $folder->setName($arguments[0]); $world['folder_creation'] = $folder->save(array('type' => 'event', 'default' => true)); break; case 'retrieving the list of shares for the application': $shares = Horde_Share::singleton($arguments[0], 'kolab'); $world['list'] = $shares->listShares(Auth::getAuth()); break; case 'adding an object list': foreach ($arguments[0] as $object) { $result = $world['server']->add($object); if (is_a($result, 'PEAR_Error')) { $world['result']['add'] = $result; return; } } $world['result']['add'] = true; break; case 'adding a user without first name': $world['result']['add'] = $world['server']->add($this->provideInvalidUserWithoutGivenName()); break; case 'adding a user without last name': $world['result']['add'] = $world['server']->add($this->provideInvalidUserWithoutLastName()); break; case 'adding a user without password': $world['result']['add'] = $world['server']->add($this->provideInvalidUserWithoutPassword()); break; case 'adding a user without primary mail': $world['result']['add'] = $world['server']->add($this->provideInvalidUserWithoutMail()); break; case 'adding a distribution list': $world['result']['add'] = $world['server']->add($this->provideDistributionList()); break; case 'listing all users': $world['list'] = $world['server']->listObjects(KOLAB_OBJECT_USER); break; case 'listing all groups': $world['list'] = $world['server']->listObjects(KOLAB_OBJECT_GROUP); break; case 'listing all objects of type': $world['list'] = $world['server']->listObjects($arguments[0]); break; case 'retrieving a hash list with all objects of type': $world['list'] = $world['server']->listHash($arguments[0]); 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; default: return $this->notImplemented($action); } } /** * 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 result should be an object of type': if (!isset($world['result'])) { $this->fail('Did not receive a result!'); } foreach ($world['result'] as $result) { if ($result instanceOf PEAR_Error) { $this->assertEquals('', $result->getMessage()); } else { $this->assertEquals($arguments[0], get_class($result)); } } break; case 'the result indicates success.': if (!isset($world['result'])) { $this->fail('Did not receive a result!'); } foreach ($world['result'] as $result) { if ($result instanceOf PEAR_Error) { $this->assertEquals('', $result->getMessage()); } else { $this->assertTrue($result); } } break; case 'the result should indicate an error with': if (!isset($world['result'])) { $this->fail('Did not receive a result!'); } foreach ($world['result'] as $result) { if ($result instanceOf PEAR_Error) { $this->assertEquals($arguments[0], $result->getMessage()); } else { $this->assertEquals($arguments[0], 'Action succeeded without an error.'); } } break; case 'the list has a number of entries equal to': if ($world['list'] instanceOf PEAR_Error) { $this->assertEquals('', $world['list']->getMessage()); } else { $this->assertEquals($arguments[0], count($world['list'])); } break; case 'the list is an empty array': if ($world['list'] instanceOf PEAR_Error) { $this->assertEquals('', $world['list']->getMessage()); } else { $this->assertEquals(array(), $world['list']); } break; case 'the list is an empty array': if ($world['list'] instanceOf PEAR_Error) { $this->assertEquals('', $world['list']->getMessage()); } else { $this->assertEquals(array(), $world['list']); } break; case 'the provided list and the result list match with regard to these attributes': if ($world['list'] instanceOf PEAR_Error) { $this->assertEquals('', $world['list']->getMessage()); } else { $provided_vals = array(); foreach ($arguments[2] as $provided_element) { if (isset($provided_element[$arguments[0]])) { $provided_vals[] = $provided_element[$arguments[0]]; } else { $this->fail(sprintf('The provided element %s does have no value for %s.', print_r($provided_element, true), print_r($arguments[0]))); } } $result_vals = array(); foreach ($world['list'] as $result_element) { if (isset($result_element[$arguments[1]])) { $result_vals[] = $result_element[$arguments[1]]; } else { $this->fail(sprintf('The result element %s does have no value for %s.', print_r($result_element, true), print_r($arguments[1]))); } } $this->assertEquals(array(), array_diff($provided_vals, $result_vals)); } break; case 'each element in the result list has an attribute': if ($world['list'] instanceOf PEAR_Error) { $this->assertEquals('', $world['list']->getMessage()); } else { $result_vals = array(); foreach ($world['list'] as $result_element) { if (!isset($result_element[$arguments[0]])) { $this->fail(sprintf('The result element %s does have no value for %s.', print_r($result_element, true), print_r($arguments[0]))); } } } break; case 'each element in the result list has an attribute set to a given value': if ($world['list'] instanceOf PEAR_Error) { $this->assertEquals('', $world['list']->getMessage()); } else { $result_vals = array(); foreach ($world['list'] as $result_element) { if (!isset($result_element[$arguments[0]])) { $this->fail(sprintf('The result element %s does have no value for %s.', print_r($result_element, true), print_r($arguments[0], true))); } if ($result_element[$arguments[0]] != $arguments[1]) { $this->fail(sprintf('The result element %s has an unexpected value %s for %s.', print_r($result_element, true), print_r($result_element[$arguments[0]], true), print_r($arguments[0], true))); } } } break; case 'the login was successful': $this->assertNoError($world['login']); $this->assertTrue($world['login']); break; case 'the creation of the folder was successful': $this->assertNoError($world['folder_creation']); break; case 'the list contains a share named': $this->assertNoError($world['list']); $this->assertContains($arguments[0], array_keys($world['list'])); break; case 'the list contains a number of elements equal to': $this->assertEquals($arguments[0], count($world['list'])); break; default: return $this->notImplemented($action); } } /** * Prepare an empty Kolab server. * * @return Horde_Kolab_Server The empty server. */ public function &prepareEmptyKolabServer() { global $conf; include_once 'Horde/Kolab/Server.php'; $GLOBALS['KOLAB_SERVER_TEST_DATA'] = array(); /** Prepare a Kolab test server */ $conf['kolab']['server']['driver'] = 'test'; $server = Horde_Kolab_Server::singleton(); /** Ensure we don't use a connection from older tests */ $server->unbind(); /** Set base DN */ $server->_base_dn = 'dc=example,dc=org'; /** Clean the server data */ return $server; } /** * Prepare a Kolab server with some basic entries. * * @return Horde_Kolab_Server The empty server. */ public function &prepareBasicServer() { $server = $this->prepareEmptyKolabServer(); $this->prepareUsers($server); return $server; } /** * Prepare a Kolab server with some basic entries. * * @return Horde_Kolab_Server The empty server. */ public function &prepareBasicSetup() { $world = &$this->prepareKolabSetup(); $this->prepareUsers($world['server']); return $world; } /** * Fill a Kolab Server with test users. * * @param Kolab_Server &$server The server to populate. * * @return Horde_Kolab_Server The empty server. */ public function prepareUsers(&$server) { $result = $server->add($this->provideBasicUserOne()); $this->assertNoError($result); $result = $server->add($this->provideBasicUserTwo()); $this->assertNoError($result); $result = $server->add($this->provideBasicAddress()); $this->assertNoError($result); $result = $server->add($this->provideBasicAdmin()); $this->assertNoError($result); $result = $server->add($this->provideBasicDomainMaintainer()); $this->assertNoError($result); $result = $server->add($this->provideBasicGroupOne()); $this->assertNoError($result); $result = $server->add($this->provideBasicGroupTwo()); $this->assertNoError($result); $result = $server->add($this->provideBasicMaintainer()); $this->assertNoError($result); $result = $server->add($this->provideBasicSharedFolder()); $this->assertNoError($result); } /** * Prepare an empty Kolab storage. * * @return Kolab_List The empty storage. */ public function &prepareEmptyKolabStorage() { /** Ensure that IMAP runs in testing mode and is empty */ $GLOBALS['KOLAB_TESTING'] = array(); /** Prepare a Kolab test storage */ $storage = &new Kolab_List(); return $storage; } /** * Prepare a Kolab Auth Driver. * * @return Auth The auth driver. */ public function &prepareKolabAuthDriver() { include_once 'Horde/Auth.php'; $auth = Auth::singleton('kolab'); return $auth; } /** * Prepare the browser setup. * * @return NULL */ public function prepareBrowser() { /** Provide a browser setup */ include_once 'Horde/Browser.php'; $GLOBALS['browser'] = new Browser(); } /** * Prepare the registry. * * @return NULL */ public function prepareRegistry() { define('HORDE_BASE', dirname(__FILE__) . '/../../../../..'); /** Provide the horde registry */ include_once 'Horde/Registry.php'; include_once 'Horde/Notification.php'; $GLOBALS['registry'] = Registry::singleton(); } /** * Prepare a Kolab setup. * * @return NULL */ public function &prepareKolabSetup() { $world = array(); $world['server'] = &$this->prepareEmptyKolabServer(); $world['storage'] = &$this->prepareEmptyKolabStorage(); $world['auth'] = &$this->prepareKolabAuthDriver(); $this->prepareBasicConfiguration(); $this->prepareRegistry(); $this->prepareBrowser(); /* Make sure the configuration is correct after initializing the registry */ $this->prepareBasicConfiguration(); return $world; } /** * Prepare a basic Kolab configuration. * * @return NULL */ public function prepareBasicConfiguration() { /** We need a server name for MIME processing */ $_SERVER['SERVER_NAME'] = 'localhost'; $_SERVER['SERVER_PORT'] = 80; $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; /** Additional config variables required for a clean Horde setup */ $GLOBALS['conf']['session']['use_only_cookies'] = false; $GLOBALS['conf']['session']['timeout'] = 3600; $GLOBALS['conf']['cookie']['path'] = '/'; $GLOBALS['conf']['cookie']['domain'] = $_SERVER['SERVER_NAME']; $GLOBALS['conf']['use_ssl'] = false; $GLOBALS['conf']['session']['cache_limiter'] = 'nocache'; $GLOBALS['conf']['session']['name'] = 'Horde'; $GLOBALS['conf']['log']['enabled'] = false; $GLOBALS['conf']['prefs']['driver'] = 'session'; $GLOBALS['conf']['auth']['driver'] = 'kolab'; $GLOBALS['conf']['share']['driver'] = 'kolab'; /** Make the share driver happy */ $GLOBALS['conf']['kolab']['enabled'] = true; /** Ensure we still use the LDAP test driver */ $GLOBALS['conf']['kolab']['server']['driver'] = 'test'; /** Storage location for the free/busy system */ $GLOBALS['conf']['fb']['cache_dir'] = '/tmp'; $GLOBALS['conf']['kolab']['freebusy']['server'] = 'https://fb.example.org/freebusy'; } /** * Create a new folder. * * @param string $name Name of the new folder. * @param string $type Type of the new folder. * @param boolean $default Should the new folder be a default folder? * * @return Kolab_Folder The new folder. */ public function &prepareNewFolder(&$storage, $name, $type, $default = false) { $folder = $storage->getNewFolder(); $folder->setName($name); $this->assertNoError($folder->save(array('type' => $type, 'default' => $default))); return $folder; } /** * Return a test user. * * @return array The test user. */ public function provideBasicUserOne() { return array('givenName' => 'Gunnar', 'sn' => 'Wrobel', 'type' => KOLAB_OBJECT_USER, 'mail' => 'wrobel@example.org', 'uid' => 'wrobel', 'userPassword' => 'none', 'kolabHomeServer' => 'home.example.org', 'kolabImapServer' => 'imap.example.org', 'kolabFreeBusyServer' => 'https://fb.example.org/freebusy', KOLAB_ATTR_IPOLICY => array('ACT_REJECT_IF_CONFLICTS'), 'alias' => array('gunnar@example.org', 'g.wrobel@example.org'), ); } /** * Return a test user. * * @return array The test user. */ public function provideBasicUserTwo() { return array('givenName' => 'Test', 'sn' => 'Test', 'type' => KOLAB_OBJECT_USER, 'mail' => 'test@example.org', 'uid' => 'test', 'userPassword' => 'test', 'kolabHomeServer' => 'home.example.org', 'kolabImapServer' => 'home.example.org', 'kolabFreeBusyServer' => 'https://fb.example.org/freebusy', 'alias' => array('t.test@example.org'), KOLAB_ATTR_KOLABDELEGATE => 'wrobel@example.org',); } /** * Return a test address. * * @return array The test address. */ public function provideBasicAddress() { return array('givenName' => 'Test', 'sn' => 'Address', 'type' => KOLAB_OBJECT_ADDRESS, 'mail' => 'address@example.org'); } /** * Return a test administrator. * * @return array The test administrator. */ public function provideBasicAdmin() { return array('sn' => 'Administrator', 'givenName' => 'The', 'uid' => 'admin', 'type' => KOLAB_OBJECT_ADMINISTRATOR, 'userPassword' => 'none'); } /** * Return a test maintainer. * * @return array The test maintainer. */ public function provideBasicMaintainer() { return array('sn' => 'Tainer', 'givenName' => 'Main', 'uid' => 'maintainer', 'type' => KOLAB_OBJECT_MAINTAINER, 'userPassword' => 'none', ); } /** * Return a test domain maintainer. * * @return array The test domain maintainer. */ public function provideBasicDomainMaintainer() { return array('sn' => 'Maintainer', 'givenName' => 'Domain', 'uid' => 'domainmaintainer', 'type' => KOLAB_OBJECT_DOMAINMAINTAINER, 'userPassword' => 'none', 'domain' => array('example.com'), ); } /** * Return a test shared folder. * * @return array The test shared folder. */ public function provideBasicSharedFolder() { return array('cn' => 'shared@example.org', 'kolabHomeServer' => 'example.org', 'type' => KOLAB_OBJECT_SHAREDFOLDER); } /** * Return a test group. * * @return array The test group. */ public function provideBasicGroupOne() { return array('mail' => 'empty.group@example.org', 'type' => KOLAB_OBJECT_GROUP); } /** * Return a test group. * * @return array The test group. */ public function provideBasicGroupTwo() { return array('mail' => 'group@example.org', 'type' => KOLAB_OBJECT_GROUP, 'member' => array('cn=Test Test,dc=example,dc=org', 'cn=Gunnar Wrobel,dc=example,dc=org')); } public function provideDistributionList() { return array('mail' => 'distlist@example.org', 'type' => KOLAB_OBJECT_DISTLIST, 'member' => array('cn=Test Test,dc=example,dc=org', 'cn=Gunnar Wrobel,dc=example,dc=org')); } public function provideInvalidUserWithoutPassword() { return array('givenName' => 'Test', 'sn' => 'Test', 'type' => KOLAB_OBJECT_USER, 'mail' => 'test@example.org'); } public function provideInvalidUserWithoutGivenName() { return array('sn' => 'Test', 'userPassword' => 'none', 'type' => KOLAB_OBJECT_USER, 'mail' => 'test@example.org'); } public function provideInvalidUserWithoutLastName() { return array('givenName' => 'Test', 'userPassword' => 'none', 'type' => KOLAB_OBJECT_USER, 'mail' => 'test@example.org'); } public function provideInvalidUserWithoutMail() { return array('givenName' => 'Test', 'sn' => 'Test', 'userPassword' => 'none', 'type' => KOLAB_OBJECT_USER); } public function provideInvalidUsers() { return array( array( $this->provideInvalidUserWithoutPassword(), 'Adding object failed: The value for "userPassword" is missing!' ), array( $this->provideInvalidUserWithoutGivenName(), 'Adding object failed: Either the last name or the given name is missing!' ), array( $this->provideInvalidUserWithoutLastName(), 'Adding object failed: Either the last name or the given name is missing!' ), array( $this->provideInvalidUserWithoutMail(), 'Adding object failed: The value for "mail" is missing!' ), ); } /** FIXME: Prefix the stuff bewlow with provide...() */ public function validUsers() { return array( array( $this->provideBasicUserOne(), ), array( $this->provideBasicUserTwo(), ), ); } public function validAddresses() { return array( array( $this->provideBasicAddress(), ), ); } public function validAdmins() { return array( array( $this->provideBasicAdmin(), ), ); } public function validMaintainers() { return array( array( $this->provideBasicMaintainer(), ) ); } public function validDomainMaintainers() { return array( array( $this->provideBasicDomainMaintainer(), ) ); } public function validGroups() { return array( array( $this->validGroupWithoutMembers(), ), array( array('mail' => 'group@example.org', 'type' => KOLAB_OBJECT_GROUP, 'member' => array('cn=Test Test,dc=example,dc=org', 'cn=Gunnar Wrobel,dc=example,dc=org') ), ), array( array('mail' => 'group2@example.org', 'type' => KOLAB_OBJECT_GROUP, 'member' => array('cn=Gunnar Wrobel,dc=example,dc=org') ), ), ); } public function validSharedFolders() { return array( array('cn' => 'Shared', 'type' => KOLAB_OBJECT_SHAREDFOLDER ), ); } public function validGroupWithoutMembers() { return array('mail' => 'empty.group@example.org', 'type' => KOLAB_OBJECT_GROUP, ); } public function userLists() { return array( ); } public function groupLists() { return array( array( array( array('type' => KOLAB_OBJECT_GROUP, 'mail' => 'empty.group@example.org', ), ) ), array( array( array('mail' => 'empty.group@example.org', 'type' => KOLAB_OBJECT_GROUP, ), ), array( array('mail' => 'group@example.org', 'type' => KOLAB_OBJECT_GROUP, 'member' => array('cn=Test Test,dc=example,dc=org', 'cn=Gunnar Wrobel,dc=example,dc=org') ), ), array( array('mail' => 'group2@example.org', 'type' => KOLAB_OBJECT_GROUP, 'member' => array('cn=Gunnar Wrobel,dc=example,dc=org') ), ), ) ); } public function userListByLetter() { return array( ); } public function userListByAttribute() { return array( ); } public function userAdd() { return array( ); } public function invalidMails() { return array( ); } public function largeList() { return array( ); } /** * Ensure that the variable contains no PEAR_Error and fail if it does. * * @param mixed $var The variable to check. * * @return NULL. */ public function assertNoError($var) { if (is_a($var, 'PEAR_Error')) { $this->assertEquals('', $var->getMessage()); } } /** * Ensure that the variable contains a PEAR_Error and fail if it does * not. Optionally compare the error message with the provided message and * fail if both do not match. * * @param mixed $var The variable to check. * @param string $msg The expected error message. * * @return NULL. */ public function assertError($var, $msg = null) { $this->assertEquals('PEAR_Error', get_class($var)); if (isset($msg)) { $this->assertEquals($msg, $var->getMessage()); } } } Horde Share synchronisation * process. * * $Horde: framework/Kolab/Kolab.php,v 1.26.10.32 2009-01-06 15:23:13 jan Exp $ * * Copyright 2004-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. * * @author Stuart Binge * @package Horde_Kolab */ class Kolab { /** * The current application that this Kolab object instance is catering to. * * @deprecated * * @var string */ var $_app; /** * The storage driver for the Kolab server. * * @deprecated * * @var Kolab_Storage */ var $_storage; /** * Indicates the version of this driver * * @deprecated * * @var int */ var $version = 2; /** * The DomDocument object that contains the XML DOM tree of the currently * loaded groupware object. We cache this here to ensure preservation of * unknown fields when re-saving the object. * * @deprecated * * @var DomDocument */ var $_xml; /** * The (Kolab) UID of the current message. * * @deprecated * * @var string */ var $_uid; /** * * @deprecated * */ function Kolab($app = null) { if (!isset($app)) { global $registry; $app = $registry->getApp(); } $this->_app = $app; $this->_storage = &new Kolab_Storage_Deprecated(); } /** * Return the uid of the message we are currently dealing with. * * @deprecated * * @return string The Kolab UID of the message we are currently * dealing with. */ function getUID() { return $this->_uid; } /** * Open the specified share. * * @deprecated * * @param string $share_uid The uid of the share that * should be opened. * @param int $loader The version of the XML * loader * * @return mixed True on success, a PEAR error otherwise */ function open($share_uid, $loader = 0) { $app_consts = Kolab::getAppConsts($this->_app); if (is_a($app_consts, 'PEAR_Error')) { return $app_consts; } return $this->_storage->open($share_uid, $app_consts, $loader); } /** * Close the current share. * * @deprecated */ function close() { $this->_storage->close(); } /** * Retrieve all objects in the current folder * * @deprecated * * @return array All object data arrays */ function getObjects() { return $this->_storage->getObjects(); } /** * Returns a list of all IMAP folders (including their groupware type) * that the current user has acccess to. * * @deprecated * * @return array An array of array($foldername, $foldertype) items (empty * on error). */ function listFolders() { return Kolab_Storage_Deprecated::listFolders(); } /** * List the objects in the current share. * * @deprecated * * @return mixed false if there are no objects, a list of message * ids or a PEAR error. */ function listObjects() { return $this->_storage->listObjects(); } /** * List the objects in the specified folder. * * @deprecated * * @param string $folder The folder to search. * * @return mixed false if there are no objects, a list of message * ids otherwise. */ function listObjectsInFolder($folder) { return $this->_storage->listObjectsInFolder($folder); } /** * Find the object with the given UID in the current share. * * @deprecated * * @param string $uid The UID of the object. * * @return mixed false if there is no such object */ function findObject($uid) { return $this->_storage->findObject($uid); } /** * Load the object with the given UID into $this->_xml * * @deprecated * * @param string $uid The UID of the object. * @param boolean $is_msgno Indicate if $uid holds an * IMAP message number * * @return mixed false if there is no such object, a PEAR error if * the object could not be loaded. Otherwise the xml * document will be returned */ function &loadObject($uid, $is_msgno = false) { $result = $this->_storage->loadObject($uid, $is_msgno); if (is_a($result, 'PEAR_Error')) { return $result; } $this->_xml = $result; $this->_uid = $this->getVal('uid'); $element = $this->_xml->document_element(); return $element; } /** * Create the object with UID in the current share * * @deprecated * * @param string $uid The UID of the object. * * @return mixed false if there is no open share, a PEAR error if * the object could not be created. Otherwise the xml * document will be returned */ function &newObject($uid) { $result = $this->_storage->newObject($uid); if (is_a($result, 'PEAR_Error')) { return $result; } $this->_xml = $result; $this->_uid = $uid; $element = $this->_xml->document_element(); return $element; } /** * Save the current object. * * @deprecated * * @return mixed false if there is no open share, a PEAR error if * the object could not be saved. True otherwise */ function saveObject() { $this->setVal('last-modification-date', Kolab::encodeDateTime()); $this->setVal('product-id', KOLAB_PRODUCT_ID); return $this->_storage->saveObject($this->_xml, $this->_uid); } /** * Move the object with the given UID from the current share into * the specified new share. * * @deprecated * * @param string $uid The UID of the object. * @param boolean $new_share The share the object should be moved to. * * @return mixed false if there is no current share, a PEAR error if * the object could not be moved. True otherwise. */ function moveObject($uid, $new_share) { return $this->_storage->moveObject($uid, $new_share); } /** * Remove the specified objects from the current share. * * @deprecated * * @param string $objects The UIDs (or maessage numbers) * of the objects to be deleted. * @param boolean $is_msgno Indicate if $objects holds * IMAP message numbers * * @return mixed false if there is no IMAP connection, a PEAR * error if the objects could not be removed. True * if the call succeeded. */ function removeObjects($objects, $is_msgno = false) { return $this->_storage->removeObjects($objects, $is_msgno); } /** * Remove all objects from the current share. * * @deprecated * * @return mixed false if there is no IMAP connection, a PEAR * error if the objects could not be removed. True * if the call succeeded. */ function removeAllObjects() { return $this->_storage->removeAllObjects(); } /** * Returns the groupware type of the given IMAP folder. * * @deprecated * * @param object $mailbox The mailbox of interest. * * @return mixed A string indicating the groupware type of $mailbox or * boolean "false" on error. */ function getMailboxType($mailbox) { $list = &Kolab_List::singleton(); $folder = $list->getFolder($mailbox); if (is_a($folder, 'PEAR_Error')) { return $folder; } return $folder->getType(); } /** * Find the object using the given criteria in the current share. * * @deprecated * * @param string $criteria The search criteria. * * @return mixed false if no object can be found */ function findObjects($criteria) { return $this->_storage->findObjects($criteria); } /** * Return the MIME type of the message we are currently dealing with. * * @deprecated * * @return string The MIME type of the message we are currently * dealing with. */ function getMimeType() { return $this->_mime_type; } function &getCurrentObject() { $element = $this->_xml->document_element(); return $element; } function &getElem($name, &$parent) { $elements = $this->getAllElems($name, $parent); if (empty($elements)) { $elements = false; return $elements; } return $elements[0]; } function &getAllElems($name, &$parent) { $elements = $parent->get_elements_by_tagname($name); return $elements; } function &getRootElem($name) { $element = $this->getElem($name, $this->getCurrentObject()); return $element; } function &getAllRootElems($name) { $elements = $this->getAllElems($name, $this->getCurrentObject()); return $elements; } function delElem($name, &$parent) { $element = $this->getElem($name, $parent); if ($element === false) { return; } return $parent->remove_child($element); } function delAllElems($name, &$parent) { $elements = $this->getAllElems($name, $parent); for ($i = 0, $j = count($elements); $i < $j; $i++) { $parent->remove_child($elements[$i]); } return true; } function delAllRootElems($name) { return $this->delAllElems($name, $this->getCurrentObject()); } function delRootElem(&$element) { if ($element === false) { return; } $root = $this->getCurrentObject(); return $root->remove_child($element); } function getElemVal(&$parent, $name, $default = 0) { if ($parent === false) { return $default; } $element = $this->getElem($name, $parent); if ($element === false) { return $default; } return $element->get_content(); } function getElemStr(&$parent, $name, $default = '') { if ($parent === false) { return $default; } $element = $this->getElem($name, $parent); if ($element === false) { return $default; } return String::convertCharset($element->get_content(), 'utf-8'); } function getVal($name, $default = 0) { $val = $this->getElemVal($this->getCurrentObject(), $name, $default); return $val; } function getStr($name, $default = '') { $str = $this->getElemStr($this->getCurrentObject(), $name, $default); return $str; } function &initElem($name, &$parent) { if ($parent === false) { $parent = $this->getCurrentObject(); } $element = $this->getElem($name, $parent); if ($element === false) { $element = $parent->append_child($this->_xml->create_element($name)); } $children = $element->child_nodes(); foreach ($children as $child) { if ($child->node_type() == XML_TEXT_NODE) { $element->remove_child($child); } } return $element; } function &initRootElem($name) { $rootElement = $this->initElem($name, $this->getCurrentObject()); return $rootElement; } function &appendElem($name, &$parent) { $child = $parent->append_child($this->_xml->create_element($name)); return $child; } function &appendRootElem($name) { $append = $this->appendElem($name, $this->getCurrentObject()); return $append; } function &setElemVal(&$parent, $name, $value = '') { $element = $this->initElem($name, $parent); $element->set_content($value); return $element; } function &setElemStr(&$parent, $name, $value = '') { return $this->setElemVal($parent, $name, String::convertCharset($value, NLS::getCharset(), 'utf-8')); } function &setVal($name, $value = '') { $result = $this->setElemVal($this->getCurrentObject(), $name, $value); return $result; } function &setStr($name, $value = '') { $result = $this->setElemStr($this->getCurrentObject(), $name, $value); return $result; } /** * Converts a string in the current character set to an IMAP UTF-7 string, * suitable for use as the name of an IMAP folder. * * @deprecated * * @param string $name The text in the current character set to convert. * * @return string $name encoded in the IMAP variation of UTF-7. */ function encodeImapFolderName($name) { return String::convertCharset($name, NLS::getCharset(), 'UTF7-IMAP'); } /** * Converts a string in the IMAP variation of UTF-7 into a string in the * current character set. * * @deprecated * * @param string $name The text in IMAP UTF-7 to convert. * * @return string $name encoded in the current character set. */ function decodeImapFolderName($name) { return String::convertCharset($name, 'UTF7-IMAP'); } /** * Converts all newlines (in DOS, MAC & UNIX format) in the specified text * to unix-style (LF) format. * * @deprecated * * @param string $text The text to convert. * * @return string $text with all newlines replaced by LF. */ function unixNewlines($text) { return preg_replace("/\r\n|\n|\r/s", "\n", $text); } /** * Returns the unfolded representation of the given text. * * @deprecated * * @param string $text The text to unfold. * * @return string The unfolded representation of $text. */ function unfoldText($text) { return preg_replace("/\r\n[ \t]+/", "", $text); } /** * Returns a string containing the current UTC date in the format * prescribed by the Kolab Format Specification. * * @deprecated * * @return string The current UTC date in the format 'YYYY-MM-DD'. */ function encodeDate($date = false) { return Horde_Kolab_Format_Date::encodeDate($date); } /** * Returns a UNIX timestamp corresponding the given date string which is * in the format prescribed by the Kolab Format Specification. * * @deprecated * * @param string $date The string representation of the date. * * @return integer The unix timestamp corresponding to $date. */ function decodeDate($date) { return Horde_Kolab_Format_Date::decodeDate($date); } /** * Returns a string containing the current UTC date and time in the format * prescribed by the Kolab Format Specification. * * @deprecated * * @return string The current UTC date and time in the format * 'YYYY-MM-DDThh:mm:ssZ', where the T and Z are literal * characters. */ function encodeDateTime($datetime = false) { return Horde_Kolab_Format_Date::encodeDateTime($datetime); } /** * Returns a UNIX timestamp corresponding the given date-time string which * is in the format prescribed by the Kolab Format Specification. * * @deprecated * * @param string $datetime The string representation of the date & time. * * @return integer The unix timestamp corresponding to $datetime. */ function decodeDateTime($datetime) { return Horde_Kolab_Format_Date::decodeDateTime($datetime); } /** * Returns a UNIX timestamp corresponding the given date or date-time * string which is in either format prescribed by the Kolab Format * Specification. * * @deprecated * * @param string $date The string representation of the date (& time). * * @return integer The unix timestamp corresponding to $date. */ function decodeDateOrDateTime($date) { return Horde_Kolab_Format_Date::decodeDateOrDateTime($date); } /** * Returns a UNIX timestamp corresponding the given date-time string which * is in the format prescribed by the Kolab Format Specification. * * @deprecated * * @param string $date The string representation of the date (& time). * * @return integer The unix timestamp corresponding to $datetime. */ function decodeFullDayDate($date) { if (empty($date)) { return 0; } return (strlen($date) == 10 ? Kolab::decodeDate($date) + 24 * 60 * 60 : Kolab::decodeDateTime($date)); } function percentageToBoolean($percentage) { return $percentage == 100 ? '1' : '0'; } function booleanToPercentage($boolean) { return $boolean ? '100' : '0'; } /** * Returns an array of application-specific constants, that are used in * a generic manner throughout the library. * * @deprecated * * @param string $app The application whose constants to query. * * @return mixed An array of application-specific constants if $app is a * supported application, or a PEAR_Error object if $app is * not supported. */ function getAppConsts($app) { switch ($app) { case 'mnemo': return array( 'folder_type' => 'note', 'mime_type_suffix' => 'note', 'allowed_types' => array( 'note', ), 'default_folder_name' => _("Notes"), 'application' => $app, ); case 'kronolith': return array( 'folder_type' => 'event', 'mime_type_suffix' => 'event', 'allowed_types' => array( 'event', ), 'default_folder_name' => _("Calendar"), 'application' => $app, ); case 'turba': return array( 'folder_type' => 'contact', 'mime_type_suffix' => 'contact', 'allowed_types' => array( 'contact', 'distribution-list', ), 'default_folder_name' => _("Contacts"), 'application' => $app, ); case 'nag': return array( 'folder_type' => 'task', 'mime_type_suffix' => 'task', 'allowed_types' => array( 'task', ), 'default_folder_name' => _("Tasks"), 'application' => $app, ); case 'h-prefs': return array( 'folder_type' => 'h-prefs', 'mime_type_suffix' => 'h-prefs', 'allowed_types' => array( 'h-prefs', ), 'default_folder_name' => _("Preferences"), 'application' => $app, ); default: return PEAR::raiseError(sprintf(_("The Horde/Kolab integration engine does not support \"%s\""), $app)); } } /** * Returns the server url of the given type. * * This method is used to encapsulate multidomain support. * * @return string The server url or empty on error. */ function getServer($server_type) { global $conf; switch ($server_type) { case 'imap': return $conf['kolab']['imap']['server']; case 'ldap': return $conf['kolab']['ldap']['server']; case 'smtp': return $conf['kolab']['smtp']['server']; default: return ''; } } /** * @deprecated */ function triggerFreeBusyUpdate() { } } * @since Horde 2.2 * @package Horde_LDAP */ class Horde_LDAP { /** * Return a boolean expression using the specified operator. * * @static * * @param string $lhs The attribute to test. * @param string $op The operator. * @param string $rhs The comparison value. * @param array $params Any additional parameters for the operator. @since * Horde 3.2 * * @return string The LDAP search fragment. */ function buildClause($lhs, $op, $rhs, $params = array()) { switch ($op) { case 'LIKE': if (empty($rhs)) { return '(' . $lhs . '=*)'; } elseif (!empty($params['begin'])) { return sprintf('(|(%s=%s*)(%s=* %s*))', $lhs, Horde_LDAP::quote($rhs), $lhs, Horde_LDAP::quote($rhs)); } elseif (!empty($params['approximate'])) { return sprintf('(%s=~%s)', $lhs, Horde_LDAP::quote($rhs)); } return sprintf('(%s=*%s*)', $lhs, Horde_LDAP::quote($rhs)); default: return sprintf('(%s%s%s)', $lhs, $op, Horde_LDAP::quote($rhs)); } } /** * Escape characters with special meaning in LDAP searches. * * @static * * @param string $clause The string to escape. * * @return string The escaped string. */ function quote($clause) { return str_replace(array('\\', '(', ')', '*', "\0"), array('\\5c', '\(', '\)', '\*', "\\00"), $clause); } /** * Take an array of DN elements and properly quote it according to RFC * 1485. * * @static * * @param array $parts An array of tuples containing the attribute * name and that attribute's value which make * up the DN. Example: * * $parts = array(0 => array('cn', 'John Smith'), * 1 => array('dc', 'example'), * 2 => array('dc', 'com')); * * @return string The properly quoted string DN. */ function quoteDN($parts) { $dn = ''; $count = count($parts); for ($i = 0; $i < $count; $i++) { if ($i > 0) { $dn .= ','; } $dn .= $parts[$i][0] . '='; // See if we need to quote the value. if (preg_match('/^\s|\s$|\s\s|[,+="\r\n<>#;]/', $parts[$i][1])) { $dn .= '"' . str_replace('"', '\\"', $parts[$i][1]) . '"'; } else { $dn .= $parts[$i][1]; } } return $dn; } } _i = $i; if ($d !== null) { $this->setLens($d); } } /** * Set or change the Lens modifying the inner iterator. Sets the * current object of the lens automatically and returns the lens. */ public function setLens(Horde_Lens_Interface $d) { $this->_d = $d; return $this->current(); } /** * Rewind the inner iterator. */ function rewind() { $this->_i->rewind(); } /** * Move to next element. * * @return void */ function next() { $this->_i->next(); } /** * @return Whether more elements are available. */ function valid() { return $this->_i->valid(); } /** * @return The current key. */ function key() { return $this->_i->key(); } /** * @return The current value. */ function current() { return $this->_d->decorate($this->_i->current()); } /** * @return Iterator The inner iterator. */ function getInnerIterator() { return $this->_i; } /** * Aggregate the inner iterator. * * @param func Name of method to invoke. * @param params Array of parameters to pass to method. */ function __call($func, $params) { return call_user_func_array(array($this->_i, $func), $params); } } _target = $target; return $this; } /** */ public function __get($key) { return $this->_target->$key; } /** */ public function __set($key, $value) { $this->_target->$key = $value; return $this; } /** */ public function __call($func, $params) { return call_user_func_array(array($this->_target, $func), $params); } } * 'phptype' The database type (ie. 'pgsql', 'mysql', etc.). * * Required by some database implementations:
 *   'database'     The name of the database.
 *   'hostspec'     The hostname of the database server.
 *   'username'     The username with which to connect to the database.
 *   'password'     The password associated with 'username'.
 *   'options'      Additional options to pass to the database.
 *   'tty'          The TTY on which to connect to the database.
 *   'port'         The port on which to connect to the database.
* * Optional parameters:
 *   'table'               The name of the lock table in 'database'.
 *                         Defaults to 'horde_locks'.
 *
 * Optional values when using separate reading and writing servers, for example
 * in replication settings:
 *   'splitread'   Boolean, whether to implement the separation or not.
 *   'read'        Array containing the parameters which are different for
 *                 the read database connection, currently supported
 *                 only 'hostspec' and 'port' parameters.
* * The table structure for the locks is as follows: *
 * CREATE TABLE horde_locks (
 *     lock_id                  VARCHAR(36) NOT NULL,
 *     lock_owner               VARCHAR(32) NOT NULL,
 *     lock_scope               VARCHAR(32) NOT NULL,
 *     lock_principal           VARCHAR(255) NOT NULL,
 *     lock_origin_timestamp    BIGINT NOT NULL,
 *     lock_update_timestamp    BIGINT NOT NULL,
 *     lock_expiry_timestamp    BIGINT NOT NULL,
 *     lock_type                TINYINT NOT NULL,
 *
 *     PRIMARY KEY (lock_id)
 * );
 * 
* * * 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://opensource.org/licenses/lgpl-license.php. * * $Horde: framework/Lock/Lock/sql.php,v 1.8.2.8 2009-02-25 05:35:42 chuck Exp $ * * @author Ben Klang * @since Horde 3.2 * @package Horde_Lock */ class Horde_Lock_sql extends Horde_Lock { /** * Handle for the current database connection. * * @var DB */ var $_db; /** * Handle for the current database connection, used for writing. Defaults * to the same handle as $_db if a separate write database isn't required. * * @var DB */ var $_write_db; /** * Boolean indicating whether or not we're connected to the SQL server. * * @var boolean */ var $_connected = false; /** * Constructs a new Horde_Lock_sql object. * * @param array $params A hash containing configuration parameters. */ function Horde_Lock_sql($params = array()) { $options = array( 'database' => '', 'username' => '', 'password' => '', 'hostspec' => '', 'table' => '', ); $this->_params = array_merge($options, $params); if (empty($this->_params['table'])) { $this->_params['table'] = 'horde_locks'; } /* Only do garbage collection if asked for, and then only 0.1% of the * time we create an object. */ if (rand(0, 999) == 0) { register_shutdown_function(array(&$this, '_doGC')); } parent::Horde_Lock($this->_params); } /** * Return an array of information about the requested lock. * * @param string $lockid Lock ID to look up * * @return mixed Array of lock information on success; false if no * valid lock exists; PEAR_Error on failure. */ function getLockInfo($lockid) { if (is_a(($result = $this->_connect()), 'PEAR_Error')) { Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); return PEAR::raiseError(_("Internal database error. Details have been logged for the administrator.")); } $now = time(); $sql = 'SELECT lock_id, lock_owner, lock_scope, lock_principal, ' . 'lock_origin_timestamp, lock_update_timestamp, ' . 'lock_expiry_timestamp, lock_type FROM ' . $this->_params['table'] . ' WHERE lock_id = ? AND lock_expiry_timestamp >= ?'; $values = array($lockid, $now); Horde::logMessage('SQL Query by Horde_Lock_sql::getLockInfo(): ' . $sql, __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_db->query($sql, $values); if (is_a($result, 'PEAR_Error')) { return $result; } $locks = array(); $row = $result->fetchRow(DB_FETCHMODE_ASSOC); if (is_a($row, 'PEAR_Error')) { return false; } $result->free(); return $row; } /** * Return a list of valid locks with the option to limit the results * by principal, scope and/or type. * * @param string $scope The scope of the lock. Typically the name of * the application requesting the lock or some * other identifier used to group locks together. * @param string $principal Principal for which to check for locks * @param int $type Only return locks of the given type. * Defaults to null, or all locks * * @return mixed Array of locks with the ID as the key and the lock details * as the value. If there are no current locks this will * return an empty array. On failure a PEAR_Error object * will be returned. */ function getLocks($scope = null, $principal = null, $type = null) { if (is_a(($result = $this->_connect()), 'PEAR_Error')) { Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); return PEAR::raiseError(_("Internal database error. Details have been logged for the administrator.")); } $now = time(); $sql = 'SELECT lock_id, lock_owner, lock_scope, lock_principal, ' . 'lock_origin_timestamp, lock_update_timestamp, ' . 'lock_expiry_timestamp, lock_type FROM ' . $this->_params['table'] . ' WHERE lock_expiry_timestamp >= ?'; $values = array($now); // Check to see if we need to filter the results if (!empty($principal)) { $sql .= ' AND lock_principal = ?'; $values[] = $principal; } if(!empty($scope)) { $sql .= ' AND lock_scope = ?'; $values[] = $scope; } if (!empty($type)) { $sql .= ' AND lock_type = ?'; $values[] = $type; } Horde::logMessage('SQL Query by Horde_Lock_sql::getLocks(): ' . $sql, __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_db->query($sql, $values); if (is_a($result, 'PEAR_Error')) { return $result; } $locks = array(); $row = $result->fetchRow(DB_FETCHMODE_ASSOC); while ($row && !is_a($row, 'PEAR_Error')) { $locks[$row['lock_id']] = $row; /* Advance to the new row in the result set. */ $row = $result->fetchRow(DB_FETCHMODE_ASSOC); } $result->free(); return $locks; } /** * Extend the valid lifetime of a valid lock to now + $newtimeout. * * @param string $lockid Lock ID to reset. Must be a valid, non-expired * lock. * @param int $extend Extend lock this many seconds from now. * * @return mixed True on success; false on expired lock; * PEAR_Error on failure. */ function resetLock($lockid, $extend) { if (is_a(($result = $this->_connect()), 'PEAR_Error')) { Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); return PEAR::raiseError(_("Internal database error. Details have been logged for the administrator.")); } $now = time(); $lockinfo = $this->getLockInfo($lockid); if ($lockinfo === true) { $expiry = $now + $extend; $sql = 'UPDATE ' . $this->_params['table'] . ' SET ' . 'lock_update_timestamp = ?, lock_expiry_timestamp = ? ' . 'WHERE lock_id = ?'; $values = array($now, $expiry, $lockid); Horde::logMessage('SQL Query by Horde_Lock_sql::resetLock(): ' . $sql, __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_write_db->query($sql, $values); if (is_a($result, 'PEAR_Error')) { return $result; } Horde::logMessage(sprintf('Lock %s reset successfully.', $lockid), __FILE__, __LINE__, PEAR_LOG_INFO); return true; } elseif (is_a($lockinfo, 'PEAR_Error')) { return $lockinfo; } else { // $lockinfo is false indicating the lock is no longer valid. return false; } } /** * Sets a lock on the requested principal and returns the generated lock ID. * NOTE: No security checks are done in the Horde_Lock API. It is expected * that the calling application has done all necessary security checks * before requesting a lock be granted. * * @param string $requestor User ID of the lock requestor. * @param string $scope The scope of the lock. Typically the name of * the application requesting the lock or some * other identifier used to group locks together. * @param string $principal A principal on which a lock should be granted. * The format can be any string but is suggested * to be in URI form. * @param int $lifetime Time (in seconds) for which the lock will be * considered valid. * @param string type One of HORDE_LOCK_TYPE_SHARED or * HORDE_LOCK_TYPE_EXCLUSIVE. * - An exclusive lock will be enforced strictly * and must be interpreted to mean that the * resource can not be modified. Only one * exclusive lock per principal is allowed. * - A shared lock is one that notifies other * potential lock requestors that the resource * is in use. This lock can be overridden * (cleared or replaced with a subsequent * call to setLock()) by other users. Multiple * users may request (and will be granted) a * shared lock on a given principal. All locks * will be considered valid until they are * cleared or expire. * * @return mixed A string lock ID on success; PEAR_Error on failure. */ function setLock($requestor, $scope, $principal, $lifetime = 1, $type = HORDE_LOCK_TYPE_SHARED) { if (is_a(($result = $this->_connect()), 'PEAR_Error')) { Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); return PEAR::raiseError(_("Internal database error. Details have been logged for the administrator.")); } $oldlocks = $this->getLocks($scope, $principal, HORDE_LOCK_TYPE_EXCLUSIVE); if (is_a($oldlocks, 'PEAR_Error')) { return $oldlocks; } if (count($oldlocks) != 0) { // An exclusive lock exists. Deny the new request. Horde::logMessage(sprintf('Lock requested for %s denied due to existing exclusive lock.', $principal), __FILE__, __LINE__, PEAR_LOG_NOTICE); return false; } $lockid = $this->_uuidgen(); $now = time(); $expiration = $now + $lifetime; $sql = 'INSERT INTO ' . $this->_params['table'] . ' (lock_id, lock_owner, lock_scope, lock_principal, lock_origin_timestamp, lock_update_timestamp, lock_expiry_timestamp, lock_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'; $values = array($lockid, $requestor, $scope, $principal, $now, $now, $expiration, $type); Horde::logMessage('SQL Query by Horde_Lock_sql::setLock(): ' . $sql, __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_write_db->query($sql, $values); if (is_a($result, 'PEAR_Error')) { return $result; } Horde::logMessage(sprintf('Lock %s set successfully by %s in scope %s on "%s"', $lockid, $requestor, $scope, $principal), __FILE__, __LINE__, PEAR_LOG_INFO); return $lockid; } /** * Removes a lock given the lock ID. * NOTE: No security checks are done in the Horde_Lock API. It is expected * that the calling application has done all necessary security checks * before requesting a lock be cleared. * * @param string $lockid The lock ID as generated by a previous call * to setLock() * * @return mixed True on success; PEAR_Error on failure. */ function clearLock($lockid) { if (is_a(($result = $this->_connect()), 'PEAR_Error')) { Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); return PEAR::raiseError(_("Internal database error. Details have been logged for the administrator.")); } if (empty($lockid)) { return PEAR::raiseError(_("Must supply a valid lock ID.")); } // Since we're trying to clear the lock we don't care // whether it is still valid or not. Unconditionally // remove it. $sql = 'DELETE FROM ' . $this->_params['table'] . ' WHERE lock_id = ?'; $values = array($lockid); Horde::logMessage('SQL Query by Horde_Lock_sql::clearLock(): ' . $sql, __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_write_db->query($sql, $values); if (is_a($result, 'PEAR_Error')) { return $result; } Horde::logMessage(sprintf('Lock %s cleared successfully.', $lockid), __FILE__, __LINE__, PEAR_LOG_INFO); return true; } /** * Opens a connection to the SQL server. * * @return boolean True on success, a PEAR_Error object on failure. */ function _connect() { if ($this->_connected) { return true; } $result = Util::assertDriverConfig($this->_params, array('phptype'), 'Lock SQL', array('driver' => 'lock')); if (is_a($result, 'PEAR_Error')) { Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); return $result; } require_once 'DB.php'; $this->_write_db = &DB::connect( $this->_params, array('persistent' => !empty($this->_params['persistent']), 'ssl' => !empty($this->_params['ssl'])) ); if (is_a($this->_write_db, 'PEAR_Error')) { Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); return $this->_write_db; } // Set DB portability options. $portability = DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS; if ($this->_write_db->phptype) { $portability |= DB_PORTABILITY_RTRIM; } $this->_write_db->setOption('portability', $portability); /* Check if we need to set up the read DB connection * seperately. */ if (!empty($this->_params['splitread'])) { $params = array_merge($this->_params, $this->_params['read']); $this->_db = &DB::connect( $params, array('persistent' => !empty($params['persistent']), 'ssl' => !empty($params['ssl'])) ); if (is_a($this->_db, 'PEAR_Error')) { Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); return $this->_db; } // Set DB portability options. $portability = DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS; if ($this->_db->phptype) { $portability |= DB_PORTABILITY_RTRIM; } $this->_db->setOption('portability', $portability); } else { /* Default to the same DB handle for read. */ $this->_db = $this->_write_db; } $this->_connected = true; return true; } /** * Do garbage collection needed for the driver. * * @access private */ function _doGC() { if (is_a(($result = $this->_connect()), 'PEAR_Error')) { Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); return false; } $now = time(); $query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE ' . 'lock_expiry_timestamp < ? AND lock_expiry_timestamp != 0'; $values = array($now); Horde::logMessage('SQL Query by Horde_Lock_sql::_doGC(): ' . $sql, __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_write_db->query($query, $values); if (is_a($result, 'PEAR_Error')) { Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); return false; } Horde::logMessage(sprintf('Lock garbage collection cleared %d locks.', $this->_write_db->affectedRows()), __FILE__, __LINE__, PEAR_LOG_INFO); return true; } } * @since Horde 3.2 * @package Horde_Lock */ /** * The exclusive lock type */ define('HORDE_LOCK_TYPE_EXCLUSIVE', 1); /** * The shared lock type */ define('HORDE_LOCK_TYPE_SHARED', 2); class Horde_Lock { /** * Local copy of driver parameters * @var $_params */ var $_params; /** * Horde_Lock constructor * * @param array $params Parameters for the specific Horde_Lock driver * * @return Horde_Lock Instance of Horde_Lock on success or PEAR_Error */ function Horde_Lock($params) { $this->_params = $params; return $this; } /** * Return an array of information about the requested lock. * * @param string $lockid Lock ID to look up * * @return mixed Array of lock information on success; PEAR_Error * on failure. */ function getLockInfo($lockid) { return PEAR::raiseError(_("No lock driver configured!")); } /** * Return a list of valid locks with the option to limit the results * by principal, scope and/or type. * * @param string $scope The scope of the lock. Typically the name of * the application requesting the lock or some * other identifier used to group locks together. * @param string $principal Principal for which to check for locks * @param int $type Only return locks of the given type. * Defaults to null, or all locks * * @return mixed Array of locks with the ID as the key and the lock details * as the value. If there are no current locks this will * return an empty array. On failure a PEAR_Error object * will be returned. */ function getLocks($scope = null, $principal = null, $type = null) { return PEAR::raiseError(_("No lock driver configured!")); } /** * Extend the valid lifetime of a valid lock to now + $extend. * * @param string $lockid Lock ID to reset. Must be a valid, non-expired * lock. * @param int $extend Extend lock this many seconds from now. * * @return mixed True on success; PEAR_Error on failure. */ function resetLock($lockid, $extend) { return PEAR::raiseError(_("No lock driver configured!")); } /** * Sets a lock on the requested principal and returns the generated lock ID. * NOTE: No security checks are done in the Horde_Lock API. It is expected * that the calling application has done all necessary security checks * before requesting a lock be granted. * * @param string $requestor User ID of the lock requestor. * @param string $scope The scope of the lock. Typically the name of * the application requesting the lock or some * other identifier used to group locks together. * @param string $principal A principal on which a lock should be granted. * The format can be any string but is suggested * to be in URI form. * @param int $lifetime Time (in seconds) for which the lock will be * considered valid. * @param string exclusive One of HORDE_LOCK_TYPE_SHARED or * HORDE_LOCK_TYPE_EXCLUSIVE. * - An exclusive lock will be enforced strictly * and must be interpreted to mean that the * resource can not be modified. Only one * exclusive lock per principal is allowed. * - A shared lock is one that notifies other * potential lock requestors that the resource * is in use. This lock can be overridden * (cleared or replaced with a subsequent * call to setLock()) by other users. Multiple * users may request (and will be granted) a * shared lock on a given principal. All locks * will be considered valid until they are * cleared or expire. * * @return mixed A string lock ID on success; PEAR_Error on failure. */ function setLock($requestor, $scope, $principal, $lifetime = 1, $exclusive = HORDE_LOCK_TYPE_SHARED) { return PEAR::raiseError(_("No lock driver configured!")); } /** * Removes a lock given the lock ID. * NOTE: No security checks are done in the Horde_Lock API. It is expected * that the calling application has done all necessary security checks * before requesting a lock be cleared. * * @param string $lockid The lock ID as generated by a previous call * to setLock() * * @return mixed True on success; PEAR_Error on failure. */ function clearLock($lockid) { return PEAR::raiseError(_("No lock driver configured!")); } /** * Generate a new Universally Unique Identifier for use as lock token * * Borrowed from HTTP_WebDAV_Server::_new_uuid() * * @param void * @return string UUID string */ function _uuidgen() { // See if PECL extension exists; if so, use it. if (function_exists('uuid_create')) { return uuid_create(); } $uuid = md5(uniqid(rand(), true)); // set variant and version fields for 'true' DCE1.1 compliant uuid $uuid{12} = "4"; $n = 8 + (ord($uuid{16}) & 3); $hex = "0123456789abcdef"; $uuid{16} = $hex{$n}; return substr($uuid, 0, 8) . "-" . substr($uuid, 8, 4) . "-" . substr($uuid, 12, 4) . "-" . substr($uuid, 16, 4) . "-" . substr($uuid, 20); } /** * Attempts to return a concrete Horde_Lock instance based on $driver. * * @param mixed $driver The type of concrete Horde_Lock subclass to return. * This is based on the storage driver ($driver). * The code is dynamically included. If $driver is an * array, then we will look in $driver[0]/lib/Lock/ * for the subclass implementation named * $driver[1].php. * @param array $params A hash containing any additional configuration or * connection parameters a subclass might need. * * @return Horde_Lock The newly created concrete Lock instance, or a * PEAR_Error object on error. */ function factory($driver, $params = null) { if (is_array($driver)) { $app = $driver[0]; $driver = $driver[1]; } $driver = basename($driver); if (empty($driver) || ($driver == 'none')) { return new Horde_Lock(); } if (is_null($params)) { $params = Horde::getDriverConfig('lock', $driver); } $class = 'Horde_Lock_' . $driver; $include_error = ''; if (!class_exists($class)) { $oldTrackErrors = ini_set('track_errors', 1); if (!empty($app)) { include $GLOBALS['registry']->get('fileroot', $app) . '/lib/Horde_Lock/' . $driver . '.php'; } else { include 'Horde/Lock/' . $driver . '.php'; } if (isset($php_errormsg)) { $include_error = $php_errormsg; } ini_set('track_errors', $oldTrackErrors); } if (class_exists($class)) { $lock = new $class($params); } else { $lock = PEAR::raiseError('Horde_Lock Driver (' . $class . ') not found' . ($include_error ? ': ' . $include_error : '') . '.'); } return $lock; } /** * Attempts to return a reference to a concrete Horde_Lock instance based on * $driver. It will only create a new instance if no Horde_Lock instance * with the same parameters currently exists. * * This should be used if multiple authentication sources (and, thus, * multiple Horde_Lock instances) are required. * * This method must be invoked as: $var = &Horde_Lock::singleton() * * @param string $driver The type of concrete Horde_Lock subclass to * return. * This is based on the storage driver ($driver). * The code is dynamically included. * @param array $params A hash containing any additional configuration or * connection parameters a subclass might need. * * @return Horde_Lock The concrete Horde_Lock reference or PEAR_Error * on failure. */ function &singleton($driver, $params = null) { static $instances = array(); if (is_null($params)) { $params = Horde::getDriverConfig('lock', is_array($driver) ? $driver[1] : $driver); } $signature = serialize(array($driver, $params)); if (empty($instances[$signature])) { $instances[$signature] = Horde_Lock::factory($driver, $params); } return $instances[$signature]; } } * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ /** * @package Horde_Log * @author Mike Naberezny * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ class Horde_Log_Exception extends Exception { /** */ public function __construct($msg, $code = 0) { parent::__construct($msg, $code); } } * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ /** * @category Horde * @package Horde_Log * @subpackage Filters * @author Mike Naberezny * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ interface Horde_Log_Filter_Interface { /** * Returns TRUE to accept the message, FALSE to block it. * * @param array $event Log event * @return boolean accepted? */ public function accept($event); } * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ /** * @package Horde_Log * @author Mike Naberezny * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ class Horde_Log_Filter_Level implements Horde_Log_Filter_Interface { /** * @var integer */ protected $_level; /** * Filter out any log messages greater than $level. * * @param integer $level Maximum log level to pass through the filter */ public function __construct($level) { if (! is_integer($level)) { throw new Horde_Log_Exception('Level must be an integer'); } $this->_level = $level; } /** * Returns TRUE to accept the message, FALSE to block it. * * @param array $event Log event * @return boolean accepted? */ public function accept($event) { return $event['level'] <= $this->_level; } } * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ /** * @category Horde * @package Horde_Log * @subpackage Filters * @author Mike Naberezny * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ class Horde_Log_Filter_Message implements Horde_Log_Filter_Interface { /** * @var string */ protected $_regexp; /** * Filter out any log messages not matching $regexp. * * @param string $regexp Regular expression to test the log message * @throws Horde_Log_Exception Invalid regular expression */ public function __construct($regexp) { if (@preg_match($regexp, '') === false) { throw new Horde_Log_Exception("Invalid regular expression '$regexp'"); } $this->_regexp = $regexp; } /** * Returns TRUE to accept the message, FALSE to block it. * * @param array $event Log event * @return boolean accepted? */ public function accept($event) { return preg_match($this->_regexp, $event['message']) > 0; } } * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ /** * @package Horde_Log * @author Mike Naberezny * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ interface Horde_Log_Formatter_Interface { /** * Formats an event to be written by the handler. * * @param array $event Log event * @return string formatted line */ public function format($event); } * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ /** * @category Horde * @package Horde_Log * @subpackage Formatters * @author Mike Naberezny * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ class Horde_Log_Formatter_Simple { /** * Format string * * @var string */ protected $_format; /** * Constructor */ public function __construct($options = null) { if (is_array($options) && isset($options['format'])) { $format = $options['format']; } else { $format = $options; } if (is_null($format)) { $format = '%timestamp% %levelName%: %message%' . PHP_EOL; } if (!is_string($format)) { throw new Horde_Log_Exception('Format must be a string'); } $this->_format = $format; } /** * Formats an event to be written by the handler. * * @param array $event Log event * @return string formatted line */ public function format($event) { $output = $this->_format; foreach ($event as $name => $value) { $output = str_replace("%$name%", $value, $output); } return $output; } } * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ /** * @category Horde * @package Horde_Log * @subpackage Formatters * @author Mike Naberezny * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ class Horde_Log_Formatter_Xml { protected $_options = array('elementEntry' => 'log', 'elementTimestamp' => 'timestamp', 'elementMessage' => 'message', 'elementLevel' => 'level', 'lineEnding' => PHP_EOL); public function __construct($options = array()) { $this->_options = array_merge($this->_options, $options); } /** * Formats an event to be written by the handler. * * @param array $event Log event * @return string XML string */ public function format($event) { $dom = new DOMDocument(); $elt = $dom->appendChild(new DOMElement($this->_options['elementEntry'])); $elt->appendChild(new DOMElement($this->_options['elementTimestamp'], date('c'))); $elt->appendChild(new DOMElement($this->_options['elementMessage'], $event['message'])); $elt->appendChild(new DOMElement($this->_options['elementLevel'], $event['level'])); $xml = $dom->saveXML(); $xml = preg_replace('/<\?xml version="1.0"( encoding="[^\"]*")?\?>\n/u', '', $xml); return $xml . $this->_options['lineEnding']; } } * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ /** * @category Horde * @package Horde_Log * @subpackage Handlers * @author Mike Naberezny * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ abstract class Horde_Log_Handler_Base { /** * @var array of key/value pair options */ protected $_options = array(); /** * @var array of Horde_Log_Filter_Interface */ protected $_filters = array(); /** * Add a filter specific to this handler. * * @param Horde_Log_Filter_Interface $filter * @return void */ public function addFilter($filter) { if (is_integer($filter)) { $filter = new Horde_Log_Filter_Level($filter); } $this->_filters[] = $filter; } /** * Log a message to this handler. * * @param array $event Log event * @return void */ public function log($event) { // if any local filter rejects the message, don't log it. foreach ($this->_filters as $filter) { if (!$filter->accept($event)) { return; } } $this->write($event); } /** * Sets an option specific to the implementation of the log handler. * * @param $optionKey Key name for the option to be changed. Keys are handler-specific * @param $optionValue New value to assign to the option * @return bool True */ public function setOption($optionKey, $optionValue) { if (!isset($this->_options[$optionKey])) { throw new Horde_Log_Exception("Unknown option \"$optionKey\"."); } $this->_options[$optionKey] = $optionValue; return true; } /** * Buffer a message to be stored in the storage * implemented by this handler. * * @param array $event Log event */ abstract public function write($event); } * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ /** * @category Horde * @package Horde_Log * @subpackage Handlers * @author Mike Naberezny * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ class Horde_Log_Handler_Db extends Horde_Log_Handler_Base { /** * Database adapter instance * @var Horde_Db_Adapter */ private $_db; /** * Name of the log table in the database * @var string */ private $_table; /** * Options to be set by setOption(). Sets the field names in the database table. * * @var array */ protected $_options = array('fieldMessage' => 'message', 'fieldLevel' => 'level'); /** * Class constructor * * @param Horde_Db_Adapter $db Database adapter instance * @param string $table Log table in database */ public function __construct($db, $table) { $this->_db = $db; $this->_table = $table; } /** * Write a message to the log. * * @param array $event Log event * @return bool Always True */ public function write($event) { $fields = array( $this->_options['fieldMessage'] => $event['message'], $this->_options['fieldLevel'] => $event['level'], ); $this->_db->insert($this->_table, $fields); return true; } } * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ /** * @category Horde * @package Horde_Log * @subpackage Handlers * @author Mike Naberezny * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ class Horde_Log_Handler_Firebug extends Horde_Log_Handler_Base { /** * Formats the log message before writing. * @var Horde_Log_Formatter_Interface */ protected $_formatter; /** * Options to be set by setOption(). Sets the field names in the database table. * * @var array */ protected $_options = array('buffering' => false); /** * Array of buffered output. * @var string */ protected $_buffer = array(); /** * Mapping of log priorities to Firebug methods. * @var array * @access private */ protected static $_methods = array( Horde_Log::EMERG => 'error', Horde_Log::ALERT => 'error', Horde_Log::CRIT => 'error', Horde_Log::ERR => 'error', Horde_Log::WARN => 'warn', Horde_Log::NOTICE => 'info', Horde_Log::INFO => 'info', Horde_Log::DEBUG => 'debug', ); /** * Class Constructor */ public function __construct() { $this->_formatter = new Horde_Log_Formatter_Simple(); } /** * Write a message to the firebug console. This function really just writes * the message to the buffer. If buffering is enabled, the * message won't be output until the buffer is flushed. If * buffering is not enabled, the buffer will be flushed * immediately. * * @param array $event Log event * @return bool Always True */ public function write($event) { $this->_buffer[] = $event; if (empty($this->_options['buffering'])) { $this->flush(); } return true; } /** */ public function flush() { if (!count($this->_buffer)) { return true; } $output = array(); foreach ($this->_buffer as $event) { $line = trim($this->_formatter->format($event)); // normalize line breaks $line = str_replace("\r\n", "\n", $line); // escape line breaks $line = str_replace("\n", "\\n\\\n", $line); // escape quotes $line = str_replace('"', '\\"', $line); // firebug call $method = isset(self::$_methods[$event['level']]) ? self::$_methods[$event['level']] : 'log'; $output[] = 'console.' . $method . '("' . $line . '");'; } echo '\n"; $this->_buffer = array(); } } * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ /** * @category Horde * @package Horde_Log * @subpackage Handlers * @author Mike Naberezny * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ class Horde_Log_Handler_Null extends Horde_Log_Handler_Base { /** * Write a message to the log buffer */ public function write($event) { return true; } } * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ /** * @category Horde * @package Horde_Log * @subpackage Handlers * @author Mike Naberezny * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ class Horde_Log_Handler_Stream extends Horde_Log_Handler_Base { /** * Formats the log message before writing. * @var Horde_Log_Formatter_Interface */ protected $_formatter; /** * Holds the PHP stream to log to. * @var null|stream */ protected $_stream = null; /** * Class Constructor * * @param mixed $streamOrUrl Stream or URL to open as a stream * @param string $mode Mode, only applicable if a URL is given */ public function __construct($streamOrUrl, $mode = 'a+') { $this->_formatter = new Horde_Log_Formatter_Simple(); if (is_resource($streamOrUrl)) { if (get_resource_type($streamOrUrl) != 'stream') { throw new Horde_Log_Exception('Resource is not a stream'); } if ($mode != 'a+') { throw new Horde_Log_Exception('Mode cannot be changed on existing streams'); } $this->_stream = $streamOrUrl; } else { if (! $this->_stream = @fopen($streamOrUrl, $mode, false)) { $msg = "\"$streamOrUrl\" cannot be opened with mode \"$mode\""; throw new Horde_Log_Exception($msg); } } } /** * Write a message to the log. * * @param array $event Log event * @return bool Always True */ public function write($event) { $line = $this->_formatter->format($event); if (! @fwrite($this->_stream, $line)) { throw new Horde_Log_Exception("Unable to write to stream"); } return true; } } * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ /** * @category Horde * @package Horde_Log * @author Mike Naberezny * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD * * @method void LOGLEVEL() LOGLEVEL($event) Log an event at LOGLEVEL, where LOGLEVEL has been added with addLevel() or already exists * @method void emerg() emerg($event) Log an event at the EMERG log level * @method void alert() alert($event) Log an event at the ALERT log level * @method void crit() crit($event) Log an event at the CRIT log level * @method void err() err($event) Log an event at the ERR log level * @method void warn() warn($event) Log an event at the WARN log level * @method void notice() notice($event) Log an event at the NOTICE log level * @method void info() info($event) Log an event at the INFO log level * @method void debug() debug($event) Log an event at the DEBUG log level */ class Horde_Log_Logger { /** * @var array of log levels where the keys are the * level priorities and the values are the level names */ private $_levels = array(); /** * @var array of Horde_Log_Handler_Abstract objects */ private $_handlers = array(); /** * @var array of Horde_Log_Filter_Interface objects */ private $_filters = array(); /** * Class constructor. Create a new logger * * @param Horde_Log_Handler_Abstract|null $handler default handler */ public function __construct($handler = null) { $r = new ReflectionClass('Horde_Log'); $this->_levels = array_flip($r->getConstants()); if ($handler !== null) { $this->addHandler($handler); } } /** * Undefined method handler allows a shortcut: * $log->levelName('message') * instead of * $log->log('message', Horde_Log_LEVELNAME) * * @param string $method log level name * @param string $params message to log * @return void */ public function __call($method, $params) { $level = strtoupper($method); if (($level = array_search($level, $this->_levels)) !== false) { $this->log(array_shift($params), $level); } else { throw new Horde_Log_Exception('Bad log level'); } } /** * Log a message at a level * * @param mixed $event Message to log, either an array or a string * @param integer $level Log level of message, required if $message is a string * @return void */ public function log($event, $level = null) { if (empty($this->_handlers)) { throw new Horde_Log_Exception('No handlers were added'); } // Create an event array from the given arguments. if (is_array($event)) { // If we are passed an array, it must contain 'message' // and 'level' indices. if (!isset($event['message'])) { throw new Horde_Log_Exception('Event array did not contain a message'); } if (!isset($event['level'])) { if ($level === null) { throw new Horde_Log_Exception('Event array did not contain a log level'); } else { $event['level'] = $level; } } } else { // Create an event array from the message and level // arguments. $event = array('message' => $event, 'level' => $level); } if (!isset($this->_levels[$event['level']])) { throw new Horde_Log_Exception('Bad log level'); } // Fill in the level name and timestamp for filters, formatters, handlers $event['levelName'] = $this->_levels[$event['level']]; if (!isset($event['timestamp'])) { $event['timestamp'] = date('c'); } // If any global filter rejects the event, don't log it. foreach ($this->_filters as $filter) { if (!$filter->accept($event)) { return; } } foreach ($this->_handlers as $handler) { $handler->log($event); } } /** * Add a custom log level * * @param string $name Name of level * @param integer $level Numeric level * @return void */ public function addLevel($name, $level) { // Log level names must be uppercase for predictability. $name = strtoupper($name); if (isset($this->_levels[$level]) || array_search($name, $this->_levels)) { throw new Horde_Log_Exception('Existing log levels cannot be overwritten'); } $this->_levels[$level] = $name; } /** * Add a filter that will be applied before all log handlers. * Before a message will be received by any of the handlers, it * must be accepted by all filters added with this method. * * @param Horde_Log_Filter_Interface $filter * @return void */ public function addFilter($filter) { if (is_integer($filter)) { $filter = new Horde_Log_Filter_Level($filter); } $this->_filters[] = $filter; } /** * Add a handler. A handler is responsible for taking a log * message and writing it out to storage. * * @param Horde_Log_Handler_Abstract $handler * @return void */ public function addHandler($handler) { $this->_handlers[] = $handler; } } INDX( w(p\ii<%r Exception.phppZii<%r EXCEPT~1.PHP`N<%<%<%<%FilterhT%%%% FormatterhR%%%%FORMAT~1 `P%%%%HandlerhVii%6Nwl Logger.php * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD */ /** * @category Horde * @package Horde_Log */ class Horde_Log { /** Emergency: system is unusable */ const EMERG = 0; /** Alert: action must be taken immediately */ const ALERT = 1; /** Critical: critical conditions */ const CRIT = 2; /** Error: error conditions */ const ERR = 3; /** Warning: warning conditions */ const WARN = 4; /** Notice: normal but significant condition */ const NOTICE = 5; /** Informational: informational messages */ const INFO = 6; /** Debug: debug-level messages */ const DEBUG = 7; } * @package Horde_Maintenance */ class Maintenance { /** * Hash holding maintenance preference names. * Syntax: PREFNAME => interval * Valid intervals are: MAINTENANCE_YEARLY, MAINTENANCE_MONTHLY, * MAINTENANCE_WEEKLY, MAINTENANCE_DAILY, * MAINTENANCE_EVERY, MAINTENANCE_FIRST_LOGIN * Operations will be run in the order they appear in the array - * MAKE SURE FUNCTIONS ARE IN THE CORRECT ORDER! * Operations can appear more than once - they will only be run once per * login though (the operation will run the first time it is seen in * the array). * * This array should be filled in for each Horde module that extends * the Maintenance class. * * @var array */ var $maint_tasks = array(); /** * UNIX timestamp of the last maintenance run for user. * * @var integer */ var $_lastRun = 0; /** * The Maintenance_Tasklist object for this login. * * @var Maintenance_Tasklist */ var $_tasklist; /** * Array to store Maintenance_Task objects. * * @var array */ var $_taskCache = array(); /** * Attempts to return a concrete Maintenance_* object based on the module * name passed into it. * * @param string $module The name of the Horde module. * @param array $params A hash containing additional data needed by the * constructor. * * @return Maintenance The Maintenance object, or false on error. */ function &factory($module, $params = array()) { global $registry; /* Spawn the relevant driver, and return it (or false on failure). */ include_once $registry->get('fileroot', $module) . '/lib/Maintenance/' . $module . '.php'; $class = 'Maintenance_' . $module; if (class_exists($class)) { $maintenance = &new $class($params); } else { $maintenance = false; } return $maintenance; } /** * Constructor. * * @param array $params A hash containing the following entries: * 'last_maintenance' => The last time maintenance * was run (UNIX timestamp). */ function Maintenance($params = array()) { /* Set the class variable $_lastRun. */ if (isset($params['last_maintenance'])) { $this->_lastRun = $params['last_maintenance']; } $this->_retrieveTasklist(); $this->_shutdown(); } /** * Do maintenance operations needed for this login. * * This function will generate the list of tasks to perform during this * login and will redirect to the maintenance page if necessary. This is * the function that should be called from the application upon login. */ function runMaintenance() { /* Check to see if we are finished with maintenance operations. */ if (!Util::getFormData(MAINTENANCE_DONE_PARAM)) { /* Determine if we should redirect to the maintenance page. */ if ($this->_needMaintenancePage() !== null) { $url = Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/services/maintenance.php', true); $url = Util::addParameter($url, array('domaintenance' => 1, 'module' => $this->_tasklist->getModule()), null, false); header('Location: ' . $url); exit; } } /* Finally, run any tasks that need to be executed. */ $this->_doMaintenanceTasks(); } /** * Do the necessary maintenance tasks for this loading of the maintenance * page. * * This is the function that is called from the maintenance page every * time it is loaded. * * @return integer The display required for the maintenance page. */ function runMaintenancePage() { /* Should we run any tasks? */ $this->_doMaintenanceTasks(); /* Get the list of tasks we need to display to the user. */ $tasks_page = $this->_needMaintenancePage(); $tasks = $this->_tasklist->getList(); /* Remove 'newflag' from first task. */ if (!$this->_tasklist->processed(true)) { if (count($tasks_page)) { $this->_tasklist->setNewPage($tasks_page[0], false); } } if (!is_null($tasks_page)) { foreach (array_keys($tasks) as $key) { if (!in_array($key, $tasks_page)) { unset($tasks[$key]); } } } if (count($tasks)) { reset($tasks); $action = $tasks[key($tasks)]['display']; } else { $action = null; } return array($action, array_keys($tasks)); } /** * Returns the informational text message on what the operation is * about to do. Also indicates whether the box should be checked * by default or not. Operations that have been locked by the * admin will return null. * * @param string $pref Name of the operation to get information for. * * @return array 1st element - Description of what the operation is about * to do during this login. * 2nd element - Whether the preference is set to on or not. */ function infoMaintenance($pref) { global $prefs; /* If the preference has been locked by the admin, do not show the user. */ if ($prefs->isLocked($pref)) { return; } $mod = &$this->_loadModule($pref); return array($mod->describeMaintenance(), $prefs->getValue($pref)); } /** * Export variable names to use for creating select tables in the * preferences menu. * * @return array An array of variable names to be imported into the * prefs.php namespace. */ function exportIntervalPrefs() { global $prefs; $return_array = array(); foreach (array_keys($this->maint_tasks) as $val) { if (!$prefs->isLocked($val . '_interval')) { $return_array[] = $val . '_interval_options'; } } return $return_array; } /** * Output hidden for elements for the POST form to ensure the calling * script has the same POST elements as when the maintenance operations * first run. * * @return string The form data. */ function getPostData() { $data = $this->_tasklist->getPostData(); $data['domaintenance'] = 1; if ($this->_needMaintenancePage() !== null) { $data['module'] = $this->_tasklist->getModule(); } else { $data[MAINTENANCE_DONE_PARAM] = 1; } $text = ''; foreach ($data as $name => $val) { $text .= '' . "\n"; } return $text; } /** * Return the URL needed for the maintenance form. * * @return string The URL to redirect to. */ function getMaintenanceFormURL() { if ($this->_needMaintenancePage() !== null) { return Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/services/maintenance.php', true); } else { return $this->_tasklist->getTarget(); } } /** * Creates the list of maintenance operations that are available * for this session (stored in a Maintenance_Tasklist object). * * @access private * * @return boolean Returns true if list was created. * False if not (e.g. list already exists). */ function _createTaskList() { global $prefs; /* Create a new Maintenance_Tasklist object. */ $this->_tasklist = &new Maintenance_Tasklist(); /* Create time objects for today's date and last login date. */ $last_date = getdate($this->_lastRun); $cur_date = getdate(); /* Go through each item in $maint_tasks and determine if we need to run it during this maintenance run. */ foreach ($this->maint_tasks as $key => $val) { /* Skip item if it already appears in the tasks list or task is * not set in the preferences. */ if ($this->_tasklist->inList($key) || !$prefs->getValue($key)) { continue; } /* Determine the correct interval for the item. */ if (($interval = $prefs->getValue($key . '_interval'))) { $val = $interval; } $addTask = false; /* FIRST LOGIN OPERATIONS */ /* If $_lastRun is empty (= 0), this is the first time the user has logged in. Don't run any other maintenance operations on the first login. */ if (empty($this->_lastRun)) { if ($val == MAINTENANCE_FIRST_LOGIN) { $addTask = true; } } /* YEARLY_OPERATIONS */ elseif (($val == MAINTENANCE_YEARLY) && ($cur_date['year'] > $last_date['year'])) { $addTask = true; } /* MONTHLY OPERATIONS */ elseif (($val == MAINTENANCE_MONTHLY) && (($cur_date['year'] > $last_date['year']) || ($cur_date['mon'] > $last_date['mon']))) { $addTask = true; } /* WEEKLY OPERATIONS */ elseif (($val == MAINTENANCE_WEEKLY) && (($cur_date['wday'] < $last_date['wday']) || ((time() - 604800) > $this->_lastRun))) { $addTask = true; } /* DAILY OPERATIONS */ elseif (($val == MAINTENANCE_DAILY) && (($cur_date['year'] > $last_date['year']) || ($cur_date['yday'] > $last_date['yday']))) { $addTask = true; } /* EVERY LOGIN OPERATIONS */ elseif ($val == MAINTENANCE_EVERY) { $addTask = true; } /* Skip the task if this task does not need to be run in this * login. */ if (!$addTask) { continue; } /* Load the task module now. */ $mod = &$this->_loadModule($key); /* Determine if this task has already been confirmed/set via some sort of admin setting. Also, if the user/admin has set the 'confirm_maintenance' flag, skip confirmation. */ $confirmed = $prefs->isLocked($key) || !$prefs->getValue('confirm_maintenance'); /* Add the task to the tasklist. */ $this->_tasklist->addTask($key, $confirmed, $mod->getDisplayType()); } } /** * Load module (if not already loaded). * * @access private * * @param string $modname Name of the module to load. * * @return Maintenance_Task A reference to the requested module. */ function &_loadModule($modname) { global $registry; if (!isset($this->_taskCache[$modname])) { include_once $registry->get('fileroot', $this->_tasklist->getModule()) . '/lib/Maintenance/Task/' . $modname . '.php'; $class = 'Maintenance_Task_' . $modname; if (class_exists($class)) { $this->_taskCache[$modname] = &new $class; } else { Horde::fatal(PEAR::raiseError(sprintf(_("Could not open Maintenance_Task module %s"), $class)), __FILE__, __LINE__); } } return $this->_taskCache[$modname]; } /** * Register the shutdown function for storing the maintenance * tasklist. * * @access private */ function _shutdown() { register_shutdown_function(array(&$this, '_cacheTasklist')); } /** * Cache the maintenance tasklist between page requests. * * @access private */ function _cacheTasklist() { $_SESSION['horde_maintenance_tasklist'][get_class($this)] = serialize($this->_tasklist); } /** * Retrieves a cached maintenance tasklist or makes sure one is * created. * * @access private */ function _retrieveTasklist() { if (isset($_SESSION['horde_maintenance_tasklist'][get_class($this)])) { $this->_tasklist = unserialize($_SESSION['horde_maintenance_tasklist'][get_class($this)]); } else { $this->_createTaskList(); } } /** * Execute all confirmed tasks. * * @access private */ function _doMaintenanceTasks() { $tasks = $this->_tasklist->getList(); foreach ($tasks as $key => $val) { if ($val['newpage']) { if ($this->_tasklist->processed()) { $this->_tasklist->setNewPage($key, false); } break; } elseif ($val['confirmed'] || Util::getFormData($key . '_confirm')) { /* Perform maintenance if confirmed. */ $mod = &$this->_loadModule($key); $mod->doMaintenance(); } $this->_tasklist->removeTask($key); } /* If we've successfully completed every task in the list (or skipped * it), record now as the last time maintenance was run. */ if (!count($this->_tasklist->getList())) { $GLOBALS['prefs']->setValue('last_maintenance', time()); } } /** * Do any of the tasks require the maintenance page? * * @access private * * @return array The list of tasks that require the maintenance page or * null if the maintenance page is no longer needed. */ function _needMaintenancePage() { $tasks = array(); $tasklist = $this->_tasklist->getList(); while (list($key, $val) = each($tasklist)) { if ($val['newpage']) { $tasks[] = $key; while ((list($key, $val) = each($tasklist)) && !$val['newpage']) { $tasks[] = $key; } return $tasks; } if (!empty($val['process']) && !Util::getFormData('domaintenance')) { $this->_tasklist->setNewPage($key, true); return $this->_needMaintenancePage(); } } return null; } } /** * The Maintenance_Tasklist:: class is used to store the list of maintenance * tasks that need to be run during this login. * * Copyright 2002-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. * * @author Michael Slusarz * @package Horde_Maintenance */ class Maintenance_Tasklist { /** * The Horde module running the maintenance tasks. * * @var string */ var $_module; /** * The URL of the web page to load after maintenance is complete. * * @var string */ var $_target; /** * POST data for the calling script. * * @var array */ var $_postdata = array(); /** * The list of tasks to run during this login. * * KEY: Task name * VALUE: Array => ( * 'confirmed' => boolean, * 'display' => integer, * 'newpage' => boolean, * 'process' => boolean * ) * * @var array */ var $_tasks = array(); /** * Internal flag for addTask(). * * @var boolean */ var $_addFlag = false; /** * Has the tasklist been processed yet? * * @var boolean */ var $_processed = false; /** * Constructor. */ function Maintenance_Tasklist() { global $registry; $this->_module = $registry->getApp(); $this->_target = Horde::selfUrl(true, true, true); } /** * Adds a task to the tasklist. * * @param string $key The name of the task to perform. * @param boolean $confirmed Has the task been confirmed? * @param integer $display The display type of the task. */ function addTask($key, $confirmed, $display) { $this->_tasks[$key] = array(); $this->_tasks[$key]['confirmed'] = $confirmed; $this->_tasks[$key]['display'] = $display; if (($display == MAINTENANCE_OUTPUT_AGREE) || ($display == MAINTENANCE_OUTPUT_NOTICE)) { $this->_tasks[$key]['newpage'] = true; $this->_addFlag = false; } elseif (!$confirmed && !$this->_addFlag) { $this->_tasks[$key]['newpage'] = true; $this->_addFlag = true; } else { $this->_tasks[$key]['newpage'] = false; } } /** * Sets the newpage flag for a task. * * @param string $task The name of the task to alter. * @param string $flag How to set the flag. */ function setNewPage($task, $flag) { if ($this->inList($task)) { $this->_tasks[$task]['newpage'] = $flag; $this->_tasks[$task]['process'] = !$flag; } } /** * Removes the task from the tasklist. * * @param string $task The name of the task to alter. */ function removeTask($task) { if ($this->inList($task)) { unset($this->_tasks[$task]); } } /** * Is this task already in the tasklist? * * @param string $task The name of the task. * * @return boolean Whether the task is already in the tasklist. */ function inList($task) { return isset($this->_tasks[$task]); } /** * Return the list of tasks. * * @return array The list of tasks that still need to be done. */ function getList() { return $this->_tasks; } /** * Return the Horde module the tasks are running under. * * @return string The Horde module name. */ function getModule() { return $this->_module; } /** * Return the POST data. * * @return array The POST data from the initial URL. */ function getPostData() { return $this->_postdata; } /** * Return the URL of the web page to load after maintenance is complete. * * @return string The target URL. */ function getTarget() { return $this->_target; } /** * Sets/displays the flag to show that tasklist has been processed at * least once. * * @param boolean $set Set the flag? * * @return boolean Has the tasklist been processed before? */ function processed($set = false) { $retvalue = $this->_processed; if ($set) { $this->_processed = true; } return $retvalue; } } /** * Abstract class to allow for modularization of specific maintenace tasks. * * For this explanation, the specific Horde application you want to create * maintenance actions for will be labeled HORDEAPP. * * To add a new maintenance task, you need to do the following: * [1] Add preference to "HORDEAPP/config/prefs.php" file. * (The name of this preference will be referred to as PREFNAME) * This preference should be of type 'checkbox' (i.e. 1 = on; 0 = off). * [Optional:] Add a preference in prefs.php of the name * 'PREFNAME_interval' to allow the user to set the interval. * 'default' value should be set to the values of the interval * constants above. * If this preference doesn't exist, the default interval * used will be the one that appears in $maint_tasks. * [2] Create a directory named "HORDEAPP/lib/Maintenance". * [3] Create a class entitled Maintenance_HORDEAPP that extends the * Maintenance class. * This class should contain only the application specific definitions of * $maint_tasks (see above for description). * Save this file as "HORDEAPP/lib/Maintenance/HORDEAPP.php". * [4] Create a directory titled "HORDEAPP/lib/Maintenance/Task". * [5] Create modules in HORDEAPP/lib/Maintenance/Task named 'PREFNAME.php' * that extend the Maintenance_Task class. * The class should be named Maintenance_Task_PREFNAME. * The class should declare the following two methods: * 'doMaintenance' - This is the function that is run to do the * specified maintenance operation. * 'describeMaintenance' - This function sets the preference text * and text to be used on the confirmation * page. Should return a description of what * your 'doMaintenance' function is about to do. * Neither function requires any parameters passed in. * * There are 3 different types of maintenance (set via $_display_type): * [1] MAINTENANCE_OUTPUT_CONFIRM * Each output from describeMaintenance() will have a checkbox associated * with it. For each checkbox selected, doMaintenance() for that task will * be run. More than 1 confirmation message can be displayed on the * maintenance page at once. * * [2] MAINTENANCE_OUTPUT_AGREE * The output from describeMaintenance() should be text asking the user to * agree/disagree to specified terms. If 'yes' is selected, the POST * variable 'agree' will be set. If 'no' is selected, the POST variable * 'not_agree' will be set. In either case, doMaintenance() will ALWAYS be * run. * * This style will be displayed on its own, separate maintenance page. * * * [3] MAINTENANCE_OUTPUT_NOTICE * The output from describeMaintenance() should be any non-interactive text * desired. There will be a single 'Click to Continue' button below this * text. doMaintenance() will ALWAYS be run. * * This style will be displayed on its own, separate maintenance page. * * * Copyright 2001-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. * * @author Michael Slusarz * @package Horde_Maintenance */ class Maintenance_Task { /** * The style of the maintenance page output. * Possible values: MAINTENANCE_OUTPUT_CONFIRM, * MAINTENANCE_OUTPUT_AGREE, * MAINTENANCE_OUTPUT_NOTICE * * @var integer */ var $_display_type = MAINTENANCE_OUTPUT_CONFIRM; /** * Constructor */ function Maintenance_Task() { } /** * Do maintenance operation (if it has been confirmed). * * @return boolean Whether the maintenance operation was successful or * not. */ function doMaintenance() { return false; } /** * Return description information for the maintenance page. * * @return string Description that will be displayed on the maintenance * confirmation page. */ function describeMaintenance() { return ''; } /** * Returns the desired output type for the maintenance page. * * @return integer Desired output type for the maintenance confirmation * page. */ function getDisplayType() { return $this->_display_type; } } * 'compression' - Compress data inside memcache? * DEFAULT: false * 'c_threshold' - The minimum value length before attempting to compress. * DEFAULT: none * 'hostspec' - The memcached host(s) to connect to. * DEFAULT: 'localhost' * 'large_items' - Allow storing large data items (larger than * MEMCACHE_MAX_SIZE)? * DEFAULT: true * 'persistent' - Use persistent DB connections? * DEFAULT: false * 'prefix' - The prefix to use for the memcache keys. * DEFAULT: 'horde' * 'port' - The port(s) memcache is listening on. Leave empty or set * to 0 if using UNIX sockets. * DEFAULT: 11211 * 'weight' - The weight to use for each memcached host. * DEFAULT: none (equal weight to all servers) *
* * Copyright 2007-2009 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * * @category Horde * @author Michael Slusarz * @author Didi Rieder * @since Horde 3.2 * @package Horde_Memcache */ class Horde_Memcache { /** * Memcache object. * * @var Memcache */ var $_memcache; /** * Memcache defaults. * * @var array */ var $_params = array( 'compression' => 0, 'hostspec' => 'localhost', 'large_items' => true, 'persistent' => false, 'port' => 11211, ); /** * Allow large data items? * * @var boolean */ var $_large = true; /** * A list of items known not to exist. * * @var array */ var $_noexist = array(); /** * Singleton. */ function &singleton() { static $instance; if (!isset($instance)) { $instance = new Horde_Memcache(); } return $instance; } /** * Constructor. */ function Horde_Memcache() { $this->_params = array_merge($this->_params, $GLOBALS['conf']['memcache']); $this->_params['prefix'] = (empty($this->_params['prefix'])) ? 'horde' : $this->_params['prefix']; $this->_large = !empty($this->_params['large_items']); $servers = array(); $this->_memcache = new Memcache; for ($i = 0, $n = count($this->_params['hostspec']); $i < $n; ++$i) { if ($this->_memcache->addServer($this->_params['hostspec'][$i], empty($this->_params['port'][$i]) ? 0 : $this->_params['port'][$i], !empty($this->_params['persistent']), !empty($this->_params['weight'][$i]) ? $this->_params['weight'][$i] : 1)) { $servers[] = $this->_params['hostspec'][$i] . (!empty($this->_params['port'][$i]) ? ':' . $this->_params['port'][$i] : ''); } } /* Check if any of the connections worked. */ if (empty($servers)) { Horde::logMessage('Could not connect to any defined memcache servers.' , __FILE__, __LINE__, PEAR_LOG_ERR); } else { if (!empty($this->_params['c_threshold'])) { $this->_memcache->setCompressThreshold($this->_params['c_threshold']); } // Force consistent hashing ini_set('memcache.hash_strategy', 'consistent'); Horde::logMessage('Connected to the following memcache servers:' . implode($servers, ', '), __FILE__, __LINE__, PEAR_LOG_DEBUG); } } /** * Delete a key. * * @see Memcache::delete() * * @param string $key The key. * @param integer $timeout Expiration time in seconds. * * @return boolean True on success. */ function delete($key, $timeout = 0) { if ($this->_large) { /* No need to delete the oversized parts - memcache's LRU * algorithm will eventually cause these pieces to be recycled. */ if (!isset($this->_noexist[$key . '_os'])) { $this->_memcache->delete($this->_key($key . '_os'), $timeout); } } if (isset($this->_noexist[$key])) { return false; } return $this->_memcache->delete($this->_key($key), $timeout); } /** * Get data associated with a key. * * @see Memcache::get() * * @param mixed $keys The key or an array of keys. * * @return mixed The string/array on success (return type is the type of * $keys), false on failure. */ function get($keys) { $key_map = $os = $os_keys = $out_array = array(); $ret_array = true; if (!is_array($keys)) { $keys = array($keys); $ret_array = false; } $search_keys = $keys; if ($this->_large) { foreach ($keys as $val) { $os_keys[$val] = $search_keys[] = $val . '_os'; } } foreach ($search_keys as $v) { $key_map[$v] = $this->_key($v); } $res = $this->_memcache->get(array_values($key_map)); if ($res === false) { return false; } /* Check to see if we have any oversize items we need to get. */ if (!empty($os_keys)) { foreach ($os_keys as $key => $val) { if (!empty($res[$key_map[$val]])) { /* This is an oversize key entry. */ $os[$key] = $this->_getOSKeyArray($key, $res[$key_map[$val]]); } } if (!empty($os)) { $search_keys = $search_keys2 = array(); foreach ($os as $val) { $search_keys = array_merge($search_keys, $val); } foreach ($search_keys as $v) { $search_keys2[] = $key_map[$v] = $this->_key($v); } $res2 = $this->_memcache->get($search_keys2); if ($res2 === false) { return false; } /* $res should now contain the same results as if we had * run a single get request with all keys above. */ $res = array_merge($res, $res2); } } foreach ($key_map as $k => $v) { if (!isset($res[$v])) { $this->_noexist[$k] = true; } } $old_error = error_reporting(0); foreach ($keys as $k) { $out_array[$k] = false; if (isset($res[$key_map[$k]])) { $data = $res[$key_map[$k]]; if (isset($os[$k])) { foreach ($os[$k] as $v) { if (isset($res[$key_map[$v]])) { $data .= $res[$key_map[$v]]; } else { $this->delete($k); continue 2; } } } $out_array[$k] = unserialize($data); } elseif (isset($os[$k]) && !isset($res[$key_map[$k]])) { $this->delete($k); } } error_reporting($old_error); return ($ret_array) ? $out_array : reset($out_array); } /** * Set the value of a key. * * @see Memcache::set() * * @param string $key The key. * @param string $var The data to store. * @param integer $timeout Expiration time in seconds. * * @return boolean True on success. */ function set($key, $var, $expire = 0) { $old_error = error_reporting(0); $var = serialize($var); error_reporting($old_error); return $this->_set($key, $var, $expire); } /** * Set the value of a key. * * @private * * @param string $key The key. * @param string $var The data to store (serialized). * @param integer $timeout Expiration time in seconds. * @param integer $lent String length of $len. * * @return boolean True on success. */ function _set($key, $var, $expire = 0, $len = null) { if (is_null($len)) { $len = strlen($var); } if (!$this->_large && ($len > MEMCACHE_MAX_SIZE)) { return false; } for ($i = 0; ($i * MEMCACHE_MAX_SIZE) < $len; ++$i) { $curr_key = ($i) ? ($key . '_s' . $i) : $key; $res = $this->_memcache->set($this->_key($curr_key), substr($var, $i * MEMCACHE_MAX_SIZE, MEMCACHE_MAX_SIZE), empty($this->_params['compression']) ? 0 : MEMCACHE_COMPRESSED, $expire); if ($res === false) { $this->delete($key); $i = 1; break; } unset($this->_noexist[$curr_key]); } if (($res !== false) && $this->_large) { $os_key = $this->_key($key . '_os'); if (--$i) { $this->_memcache->set($os_key, $i, 0, $expire); } elseif (!isset($this->_noexist[$key . '_os'])) { $this->_memcache->delete($os_key); } } return $res; } /** * Replace the value of a key. * * @see Memcache::replace() * * @param string $key The key. * @param string $var The data to store. * @param integer $timeout Expiration time in seconds. * * @return boolean True on success, false if key doesn't exist. */ function replace($key, $var, $expire = 0) { $old_error = error_reporting(0); $var = serialize($var); error_reporting($old_error); $len = strlen($var); if ($len > MEMCACHE_MAX_SIZE) { if ($this->_large) { $res = $this->_memcache->get(array($this->_key($key), $this->_key($key . '_os'))); if (!empty($res)) { return $this->_set($key, $var, $expire, $len); } } return false; } if ($this->_memcache->replace($this->_key($key), $var, empty($this->_params['compression']) ? 0 : MEMCACHE_COMPRESSED, $expire)) { if ($this->_large && !isset($this->_noexist[$key . '_os'])) { $this->_memcache->delete($this->_key($key . '_os')); } return true; } return false; } /** * Obtain lock on a key. * * @param string $key The key to lock. */ function lock($key) { /* Lock will automatically expire after 10 seconds. */ while ($this->_memcache->add($this->_key($key . '_l'), 1, 0, 10) === false) { /* Wait 0.005 secs before attempting again. */ usleep(5000); } } /** * Release lock on a key. * * @param string $key The key to lock. */ function unlock($key) { $this->_memcache->delete($this->_key($key . '_l'), 0); } /** * Mark all entries on a memcache installation as expired. */ function flush() { $this->_memcache->flush(); } /** * Get the statistics output from the current memcache pool. * * @return array The output from Memcache::getExtendedStats() using the * current Horde configuration values. */ function stats() { return $this->_memcache->getExtendedStats(); } /** * Obtains the md5 sum for a key. * * @access private * * @param string $key The key. * * @return string The corresponding memcache key. */ function _key($key) { return md5($this->_params['prefix'] . $key); } /** * Returns the key listing of all key IDs for an oversized item. * * @access private * * @return array The array of key IDs. */ function _getOSKeyArray($key, $length) { $ret = array(); for ($i = 0; $i < $length; ++$i) { $ret[] = $key . '_s' . ($i + 1); } return $ret; } } * @author Jon Parise * @since Horde 1.3 * @package Horde_Framework */ class Menu { /** * Menu array. * * @var array */ var $_menu = array(); /** * Mask defining what general Horde links are shown in this Menu. * * @var integer */ var $_mask; /** * Constructor */ function Menu($mask = HORDE_MENU_MASK_ALL) { /* Menuitem mask. */ $this->_mask = $mask; /* Location of the menufile. */ $this->_menufile = $GLOBALS['registry']->get('fileroot') . '/config/menu.php'; } /** * Add an item to the menu array. * * @param string $url String containing the value for the hyperlink. * @param string $text String containing the label for this menu * item. * @param string $icon String containing the filename of the image * icon to display for this menu item. * @param string $icon_path If the icon lives in a non-default directory, * where is it? * @param string $target If the link needs to open in another frame or * window, what is its name? * @param string $onclick Onclick javascript, if desired. * @param string $class CSS class for the menu item. * * @return integer The id (NOT guaranteed to be an array index) of the * item just added to the menu. */ function add($url, $text, $icon = '', $icon_path = null, $target = '', $onclick = null, $class = null) { $pos = count($this->_menu); if (!$pos || ($pos - 1 != max(array_keys($this->_menu)))) { $pos = count($this->_menu); } $this->_menu[$pos] = array( 'url' => $url, 'text' => $text, 'icon' => $icon, 'icon_path' => $icon_path, 'target' => $target, 'onclick' => $onclick, 'class' => $class ); return $pos; } /** * Add an item to the menu array. * * @param string $url String containing the value for the hyperlink. * @param string $text String containing the label for this menu * item. * @param string $icon String containing the filename of the image * icon to display for this menu item. * @param string $icon_path If the icon lives in a non-default directory, * where is it? * @param string $target If the link needs to open in another frame or * window, what is its name? * @param string $onclick Onclick javascript, if desired. * @param string $class CSS class for the menu item. * * @return integer The id (NOT guaranteed to be an array index) of the item * just added to the menu. */ function addArray($item) { $pos = count($this->_menu); if (!$pos || ($pos - 1 != max(array_keys($this->_menu)))) { $pos = count($this->_menu); } $this->_menu[$pos] = $item; return $pos; } function setPosition($id, $pos) { if (!isset($this->_menu[$id]) || isset($this->_menu[$pos])) { return false; } $item = $this->_menu[$id]; unset($this->_menu[$id]); $this->_menu[$pos] = $item; return true; } /** * Return the unordered list representing the list of menu items. Styling * is done through CSS. * * @return string An unordered list of menu elements that can be entirely * styled with CSS. */ function render() { global $conf, $registry, $prefs; $graphics = $registry->getImageDir('horde'); $app = $registry->getApp(); if ($this->_mask !== HORDE_MENU_MASK_NONE) { /* Add any custom menu items. */ $this->addSiteLinks(); /* Add any app menu items. */ $this->addAppLinks(); } /* Add settings link. */ if ($this->_mask & HORDE_MENU_MASK_PREFS && $url = Horde::getServiceLink('options', $app)) { $this->add($url, _("_Options"), 'prefs.png', $graphics); } /* Add problem link. */ if ($this->_mask & HORDE_MENU_MASK_PROBLEM && $problem_link = Horde::getServiceLink('problem', $app)) { $this->add($problem_link, _("Problem"), 'problem.png', $graphics); } /* Add help link. */ require_once 'Horde/Help.php'; if ($this->_mask & HORDE_MENU_MASK_HELP && $help_link = Horde::getServiceLink('help', $app)) { $this->add($help_link, _("Help"), 'help_index.png', $graphics, 'help', 'popup(this.href); return false;', 'helplink'); } /* Login/Logout. */ if ($this->_mask & HORDE_MENU_MASK_LOGIN) { /* If the sidebar isn't always shown, but is sometimes * shown, then logout links should be to the parent * frame. */ $auth_target = null; if ($conf['menu']['always'] || $prefs->getValue('show_sidebar')) { $auth_target = '_parent'; } if (Auth::getAuth()) { if ($logout_link = Horde::getServiceLink('logout', $app, !$prefs->getValue('show_sidebar'))) { $this->add($logout_link, _("_Log out"), 'logout.png', $graphics, $auth_target, null, '__noselection'); } } else { if ($login_link = Horde::getServiceLink('login', $app)) { $this->add($login_link, _("_Log in"), 'login.png', $graphics, $auth_target, null, '__noselection'); } } } /* No need to return an empty list if there are no menu * items. */ if (!count($this->_menu)) { return ''; } /* Sort to match explicitly set positions. */ ksort($this->_menu); if (!empty($GLOBALS['nls']['rtl'][$GLOBALS['language']])) { $this->_menu = array_reverse($this->_menu) ; } $menu_view = $prefs->getValue('menu_view'); $output = '
    '; foreach ($this->_menu as $m) { /* Check for separators. */ if ($m == 'separator') { $output .= "\n
  •  
  • "; continue; } /* Item class and selected indication. */ if (!isset($m['class'])) { /* Try to match the item's path against the current * script filename as well as other possible URLs to * this script. */ if (Menu::isSelected($m['url'])) { $m['class'] = 'current'; } } elseif ($m['class'] === '__noselection') { unset($m['class']); } /* Icon. */ $icon = ''; if ($menu_view == 'icon' || $menu_view == 'both') { if (!isset($m['icon_path'])) { $m['icon_path'] = null; } $icon = Horde::img($m['icon'], Horde::stripAccessKey($m['text']), '', $m['icon_path']) . '
    '; } /* Link. */ $accesskey = Horde::getAccessKey($m['text']); $link = Horde::link($m['url'], ($menu_view == 'icon') ? Horde::stripAccessKey($m['text']) : '', isset($m['class']) ? $m['class'] : '', isset($m['target']) ? $m['target'] : '', isset($m['onclick']) ? $m['onclick'] : '', '', $accesskey); $output .= sprintf("\n
  • %s%s%s
  • ", $link, $icon, ($menu_view != 'icon') ? Horde::highlightAccessKey($m['text'], $accesskey) : ''); } return $output . '
'; } /** * Any links to other Horde applications defined in an application's config * file by the $conf['menu']['apps'] array are added to the menu array. */ function addAppLinks() { global $conf, $registry; if (isset($conf['menu']['apps']) && is_array($conf['menu']['apps'])) { foreach ($conf['menu']['apps'] as $app) { if ($registry->get('status', $app) != 'inactive' && $registry->hasPermission($app, PERMS_SHOW)) { $url = $registry->getInitialPage($app); if (!is_a($url, 'PEAR_Error')) { $this->add(Horde::url($url), $registry->get('name', $app), $registry->get('icon', $app), ''); } } } } } /** * Add any other links found in $this->_menufile to be included in the * menu. */ function addSiteLinks() { if (is_readable($this->_menufile)) { include $this->_menufile; if (isset($_menu) && is_array($_menu)) { foreach ($_menu as $menuitem) { $this->addArray($menuitem); } } } } /** * Checks to see if the current url matches the given url. * * @return boolean Whether the given URL is the current location. */ function isSelected($url) { $server_url = parse_url($_SERVER['PHP_SELF']); $check_url = parse_url($url); /* Try to match the item's path against the current script filename as well as other possible URLs to this script. */ if (isset($check_url['path']) && (($check_url['path'] == $server_url['path']) || ($check_url['path'] . 'index.php' == $server_url['path']) || ($check_url['path'] . '/index.php' == $server_url['path']))) { return true; } return false; } } INDX( (hRK,R%,R%,R%,R%ICALEN~1pZKii[%kt ICALEN~1.PHPpZKii[%DrtPK Identity.php`LK[%[%[%[%ImagehTKiiz`%Z{tHD Image.php`JKz`%z`%z`%z`%IMAP`LK b% b% b% b%KolabhTKiiDڠ%|uXVT Kolab.phphRKii<%*;v. LDAP.php`JK<%<%<%<%LenshRKii<%%w Lens.phphVKii<%KBw Loader.php`JK<%<%<%<%LockhRKii<%Gw(' Lock.phpXHK<%<%<%<%Log`PKii%,rIw Log.phpp`Kii%Ywhe Maintenance.phppZKii%Ywhe MAINTE~1.PHPpZKii%`w8_2 Memcache.phpKiiR%}w0f* Menu.php`JKR%R%R%R%MIMEOhRKiinO%ʍHx MIME.phpP`NKnO%nO%nO%nO%MobileUhVKiiȱ%}x Mobile.phpVXHKȱ%ȱ%ȱ%ȱ%NLS\`PK0 '0 '"%o-yPO NLS.php]pZK"%"%"%"% NotificationexbKii|v%L[9y(A Notification.php]hRK"%"%"%"%NOTIFI~1epZKii|v%L[9y(A NOTIFI~1.PHPf`LK|v%|v%|v%|v%PermsjhTKii|v%dhyH Perms.phpk`LK|v%|v%|v%|v%PrefsvhTKiiؿ%hxzpm Prefs.phpwXHKؿ%ؿ%ؿ%ؿ%Rdo`PKii0;% zN Rdo.phppZKii0;%${T Registry.phphXKii0;%Ʀ+{? Release.php`NK0;%0;%0;%0;%Routes * @package Horde_MIME */ class MIME_Contents { /** * The MIME_Message object for the message. * * @var MIME_Message */ var $_message; /** * The MIME_Message object we use when caching. * * @var MIME_Message */ var $_cachemessage; /** * The MIME part id of the message body. * * @since Horde 3.2 * * @var integer */ var $_body_id; /** * The attachments list. * * @var array */ var $_atc = array(); /** * The message parts list. * * @var array */ var $_parts = array(); /** * List of all downloadable parts. * * @since Horde 3.2 * * @var array */ var $_downloads = null; /** * The summary parts list. * * @var array */ var $_summary = array(); /** * The summary type. * * @var string */ var $_summaryType = null; /** * The Cache_session identifier. * * @var string */ var $_sessionCacheID = null; /** * The MIME_Viewer object cache. * * @var array */ var $_viewerCache = array(); /** * The attachment display type to use. * * @var integer */ var $_displayType = MIME_CONTENTS_DISPLAY_TYPE_BOTH; /** * The MIME index key to use. * * @var string */ var $_mimekey = null; /** * The actionID value for various actions. * 'download' -- Downloading a part/attachment. * 'view' -- Viewing a part/attachment. * * @var array */ var $_viewID = array(); /** * Show the links in the summaries? * * @var boolean */ var $_links = true; /** * The base MIME_Contents object. * * @var MIME_Contents */ var $_base = null; /** * The number of message/rfc822 levels labeled as 'attachments' of the * current part. * * @var integer */ var $_attach822 = 0; /** * Constructor. * * @param MIME_Message $messageOb The object to work with. * @param array $viewID The actionID values for viewing * parts/attachments. * @param array &$contents Array containing a single value: * a reference to the base object. * (This last parameter needs to be handled via an array because PHP < * 5.0 doesn't handle optional pointer parameters otherwise.) */ function MIME_Contents($messageOb, $viewID = array(), $contents = array()) { $ptr = array(&$messageOb); MIME_Structure::addMultipartInfo($ptr); $this->_message = $messageOb; $this->_cachemessage = Util::cloneObject($messageOb); $this->_viewID = $viewID; /* Create the pointer to the base object. */ if (!empty($contents)) { $ptr = reset($contents); $old_ptr = &$ptr->getBaseObjectPtr(); $this->_base = $old_ptr; } } /** * Returns the entire body of the message. * You probably want to override this function in any subclass. * * @return string The text of the body of the message. */ function getBody() { return $this->_message->toString(); } /** * Returns the raw text for one section of the message. * You probably want to override this function in any subclass. * * @param string $id The ID of the MIME_Part. * * @return string The text of the part. */ function getBodyPart($id) { if (($part = $this->getMIMEPart($id))) { return $part->getContents(); } else { return ''; } } /** * Returns the MIME_Message object for the mail message. * * @return MIME_Message A MIME_Message object. */ function getMIMEMessage() { return $this->_message; } /** * Fetch a part of a MIME message. * * @param integer $id The MIME index of the part requested. * * @return MIME_Part The raw MIME part asked for. */ function &getMIMEPart($id) { return $this->_message->getPart($id); } /** * Rebuild the MIME_Part structure of a message. * You probably want to override this function in any subclass. * * @return MIME_Message A MIME_Message object with all of the body text * stored in the individual MIME_Parts. */ function rebuildMessage() { return $this->_message; } /** * Fetch part of a MIME message. * * @param integer $id The MIME ID of the part requested. * @param boolean $all If this is a header part, should we return all text * in the body? * * @return MIME_Part The MIME_Part. */ function getRawMIMEPart($id, $all = false) { $mime_part = $this->getMIMEPart($id); if (!is_a($mime_part, 'MIME_Part')) { return null; } /* If all text is requested, change the ID now. */ if ($all && $mime_part->getInformation('header')) { $id = substr($id, 0, strrpos($id, '.')); } /* Only set contents if there is currently none in the MIME Part. */ if (!$mime_part->getContents()) { $mime_part->setContents($this->getBodyPart($id)); } return $mime_part; } /** * Fetch part of a MIME message and decode it, if it is base_64 or * qprint encoded. * * @param integer $id The MIME ID of the part requested. * @param boolean $all If this is a header part, should we return all text * in the body? * * @return MIME_Part The MIME_Part with its contents decoded. */ function &getDecodedMIMEPart($id, $all = false) { if (($mime_part = $this->getRawMIMEPart($id, $all))) { $mime_part->transferDecodeContents(); } else { $mime_part = null; } return $mime_part; } /** * Return the attachment list (HTML table format). * * @return string The list of attachments formatted into HTML. */ function getAttachments() { $msg = ''; $akeys = array_keys($this->_atc); natsort($akeys); foreach ($akeys as $key) { $msg .= $this->_arrayToTableRow($this->_atc[$key]); } return $msg; } /** * Return the message list (HTML table format). * * @param boolean $oneframe Should the output be designed for display in a * single frame? * * @return string The message formatted into HTML. */ function getMessage($oneframe = false) { $msg = ''; $msgCount = count($this->_parts); $partDisplayed = false; // TODO: Temporary hack to display header info for a message with one // MIME part that cannot be displayed inline. if (!$msgCount || ($msgCount == 1 && !reset($this->_parts))) { $this->setSummary($this->_message, 'part'); $msgCount = 1; } uksort($this->_parts, 'strnatcmp'); reset($this->_parts); while (list($key, $value) = each($this->_parts)) { if (isset($this->_summary[$key]) && ((($msgCount == 1) && empty($value)) || ($msgCount > 1))) { $msg .= ''; if ($oneframe) { $summary = $this->_summary[$key]; $summary = array_merge(array_splice($summary, 0, 1), array_splice($summary, 1)); $msg .= $this->_arrayToTableRow($summary); } else { $msg .= $this->_arrayToTableRow($this->_summary[$key]); } $msg .= '
'; } elseif ($partDisplayed && !empty($value)) { $msg .= ''; } if (!empty($value)) { $msg .= '' . $value . ''; $partDisplayed = true; } } if (!$partDisplayed) { $msg .= '
' . _("There are no parts that can be displayed inline.") . '
'; } return $msg; } /** * Expands an array into a table row. * * @access private * * @param array $array The array to expand. * * @return string The array expanded to a HTML table row. */ function _arrayToTableRow($array) { $text = ''; foreach ($array as $elem) { if (!empty($elem)) { $text .= "$elem\n"; } } return $text . "\n"; } /** * Returns the data for a specific MIME index. * * @param string $id The MIME index. * @param string $field The field to return (message, atc, summary) * * @return string The text currently set for that index. */ function getIndex($id, $field) { $field = '_' . $field; if (is_array($this->$field) && array_key_exists($id, $this->$field)) { $entry = $this->$field; return $entry[$id]; } else { return null; } } /** * Removes the message text and summary for a specific MIME index. * * @param string $id The MIME index. */ function removeIndex($id) { unset($this->_parts[$id]); unset($this->_summary[$id]); unset($this->_atc[$id]); } /** * Determine if we can (and know how to) inline a MIME Part. * * @param MIME_Part &$mime_part A MIME_Part object. * * @return boolean True if part can be inlined. * False if it cannot. */ function canDisplayInline(&$mime_part) { $viewer = $this->getMIMEViewer($mime_part); if (!$viewer) { return false; } /* First check: The MIME headers allow the part to be inlined. * However, if we are already in view mode, then we can skip this * check. */ if (!$this->viewAsAttachment() && ($mime_part->getDisposition() != 'inline') && !$viewer->forceInlineView()) { return false; } /* Second check (save the most expensive for last): * Check to see if the driver is set to inline. */ return (is_a($viewer, 'MIME_Viewer') && $viewer->canDisplayInline()); } /** * Get MIME_Viewer object. * * @param MIME_Part &$mime_part A MIME_Part object. * * @return MIME_Viewer The MIME_Viewer object, or false on error. */ function &getMIMEViewer(&$mime_part, $mime_type = null) { /* Make sure we have a MIME_Part to process. */ if (empty($mime_part)) { $ret = false; return $ret; } if (empty($mime_type)) { $mime_type = $mime_part->getType(); } $key = $mime_part->getUniqueID() . '|' . $mime_type; if (!isset($this->_viewerCache[$key])) { require_once dirname(__FILE__) . '/Viewer.php'; $this->_viewerCache[$key] = MIME_Viewer::factory($mime_part, $mime_type); } return $this->_viewerCache[$key]; } /** * Get the MIME Content-Type output by a MIME_Viewer for a particular * MIME_Part. * * @param MIME_Part &$mime_part A MIME_Part object. * * @return string The MIME type output by the MIME_Viewer, or false on * error. */ function getMIMEViewerType(&$mime_part) { if (($viewer = $this->getMIMEViewer($mime_part))) { return $viewer->getType(); } else { return false; } } /** * Returns the key to use for a particular MIME_Part. * * @access private * * @param MIME_Part &$mime_part A MIME_Part object. * @param boolean $override Respect the MIME key override value? * * @return string The unique identifier of the MIME_Part. * Returns false if no key found. */ function _getMIMEKey(&$mime_part, $override = true) { if ($override) { $id = $this->getMIMEKeyOverride(); if (!is_null($id)) { return $id; } } $id = $mime_part->getMIMEId(); if (is_null($id)) { return false; } else { return $id; } } /** * Gets the MIME key override. * * @return string The MIME key override - null if no override. */ function getMIMEKeyOverride() { return $this->_mimekey; } /** * Sets an override for the MIME key. * * @param string $mimekey */ function setMIMEKeyOverride($mimekey = null) { $this->_mimekey = $mimekey; } /** * Should we display links for the summaries? * * @param boolean $show Show the summary links? */ function showSummaryLinks($show = null) { if (!is_null($show)) { $this->_links = $show; } return $this->_links; } /** * Render a MIME Part. * * @param MIME_Part &$mime_part A MIME_Part object. * * @return string The rendered data. */ function renderMIMEPart(&$mime_part) { return $this->_renderMIMEPart($mime_part, false); } /** * Render MIME Part attachment info. * * @param MIME_Part &$mime_part A MIME_Part object. * * @return string The rendered data. */ function renderMIMEAttachmentInfo(&$mime_part) { return $this->_renderMIMEPart($mime_part, true); } /** * Render MIME Part data. * * @access private * * @param MIME_Part &$mime_part A MIME_Part object. * @param boolean $attachment Render MIME Part attachment info? * * @return string The rendered data. */ function _renderMIMEPart(&$mime_part, $attachment = false) { /* Get the MIME_Viewer object for this MIME part */ $viewer = &$this->getMIMEViewer($mime_part); if (!is_a($viewer, 'MIME_Viewer')) { return ''; } $msg = ''; $mime_part->transferDecodeContents(); /* If this is a text/* part, AND the text is in a different character * set than the browser, convert to the current character set. * Additionally, if the browser does not support UTF-8, give the * user a link to open the part in a new window with the correct * character set. */ $charset = $mime_part->getCharset(); if ($charset) { $charset_upper = String::upper($charset); if (($charset_upper != 'US-ASCII') && !$this->viewAsAttachment()) { $default_charset = String::upper(NLS::getCharset()); if ($charset_upper != $default_charset) { $mime_part->setContents(String::convertCharset($mime_part->getContents(), $charset, $default_charset)); $mime_part->setCharset($default_charset); if ($default_charset != 'UTF-8') { $status = array( sprintf(_("This message was written in a character set (%s) other than your own."), htmlspecialchars($charset)), sprintf(_("If it is not displayed correctly, %s to open it in a new window."), $this->linkViewJS($mime_part, 'view_attach', _("click here"))) ); $msg = $this->formatStatusMsg($status, null, false) . $msg; } } } } $viewer->setMIMEPart($mime_part); $params = array(&$this); if ($attachment) { $msg .= $viewer->renderAttachmentInfo($params); } else { $msg .= $viewer->render($params); } return $msg; } /** * Build the message deciding what MIME Parts to show. * * @return boolean False on error. */ function buildMessage() { $this->_atc = array(); $this->_parts = array(); $this->_summary = array(); if (!is_a($this->_message, 'MIME_Message')) { return false; } /* Now display the parts. */ $mime_part = $this->_message->getBasePart(); $this->buildMessagePart($mime_part); return true; } /** * Processes a MIME_Part and stores the display information in the internal * class variables. * * @param MIME_Part &$mime_part The MIME_Part object to process. * * @return string The rendered text. */ function buildMessagePart(&$mime_part) { $msg = ''; /* If we can't display the part inline, add it to the attachment list. If the MIME ID of the current part is '0', then force a render of the part (since it is the base part and, without attempting to render, the message will ALWAYS appear empty. */ if (!$this->canDisplayInline($mime_part) && ($mime_part->getMIMEId() != 0)) { /* Not displaying inline; add to the attachments list. */ if (($this->_displayType == MIME_CONTENTS_DISPLAY_TYPE_LIST) || ($this->_displayType == MIME_CONTENTS_DISPLAY_TYPE_BOTH)) { $this->setSummary($mime_part, 'attachment'); } if (($this->_displayType == MIME_CONTENTS_DISPLAY_TYPE_INLINE) || ($this->_displayType == MIME_CONTENTS_DISPLAY_TYPE_BOTH)) { $this->setSummary($mime_part, 'part'); } /* Check to see if any attachment information can be rendered by the MIME_Viewer. */ $msg = $this->renderMIMEAttachmentInfo($mime_part); if (!empty($msg)) { $key = $this->_getMIMEKey($mime_part); $this->_parts[$key] = $msg; } } else { $msg = $this->renderMIMEPart($mime_part); $key = $this->_getMIMEKey($mime_part); if (!$this->_attach822) { $this->_parts[$key] = $msg; } /* Some MIME_Viewers set the summary by themelves, so only * add to attachment/inline lists if nothing has been set * as of yet. */ if ((($mime_part->getType() != 'multipart/mixed') || !empty($msg)) && !empty($key) && !$this->getIndex($key, 'summary') && $this->_attach822 && (($this->_displayType == MIME_CONTENTS_DISPLAY_TYPE_LIST) || ($this->_displayType == MIME_CONTENTS_DISPLAY_TYPE_BOTH))) { $this->setSummary($mime_part, 'attachment'); } } if ($mime_part->getInformation('header')) { /* If this is message/rfc822 part, and it is marked as an * attachment, we need to let future calls to buildMessagePart() * know that it should mark embedded parts as not viewable * inline. */ $increment_822 = false; if (($mime_part->getType() == 'message/rfc822') && ($mime_part->getDisposition() == 'attachment')) { $this->_attach822++; $increment_822 = true; } $parts = $mime_part->getParts(); reset($parts); while (list(,$part) = each($parts)) { $msg .= $this->buildMessagePart($part); } if ($increment_822) { $this->_attach822--; } } return $msg; } /** * Generate the list of MIME IDs to use for download all. * * @since Horde 3.2 * * @return array The list of MIME IDs that should be downloaded when * downloading all attachments. */ function getDownloadAllList() { if (is_array($this->_downloads)) { return $this->_downloads; } $this->_downloads = array(); $bodyid = $this->findBody(); /* Here is what we consider 'downloadable': * All parts not 'multipart/*' and 'message/*' except for * 'message/rfc822' * All parts that are not PGP or S/MIME signature information * NOT the body part (if one exists) * Parts that are either marked a 'attachment' or have a filename. */ foreach ($this->_message->contentTypeMap() as $key => $val) { if ($key === $bodyid) { continue; } $mime_part = $this->getMIMEPart($key); if (strpos($val, 'message/') === 0) { if (strpos($val, 'message/rfc822') === 0) { $this->_downloads[] = $key; } } elseif ((($mime_part->getDisposition() == 'attachment') || $mime_part->getContentTypeParameter('name')) && ($val != 'application/applefile') && (strpos($val, 'multipart/') === false) && (strpos($val, 'application/x-pkcs7-signature') === false) && (strpos($val, 'application/pkcs7-signature') === false)) { $this->_downloads[] = $key; } } return $this->_downloads; } /** * Returns a list of attachments and their contents. * * @since Horde 3.2 * * @return array List of hashes with the keys 'name' and 'data'. */ function getAttachmentContents() { $attachments = array(); foreach ($this->getDownloadAllList() as $attachment) { $part = &$this->_message->getPart($attachment); $part->transferDecodeContents(); $part_name = $part->getName(true, true); if (empty($part_name)) { $part_name = MIME_DEFAULT_DESCRIPTION; } $attachments[] = array('name' => $part_name, 'data' => $part->getContents()); } return $attachments; } /** * Finds the main "body" text part (if any) in a message. * "Body" data is the first text part in the base MIME part. * * @since Horde 3.2 * * @param string $subtype Specifically search for this subtype. * * @return string The MIME ID of the main "body" part. */ function findBody($subtype = null) { if (isset($this->_body_id) && ($subtype === null)) { return $this->_body_id; } /* Look for potential body parts. */ $part = $this->_message->getBasePart(); $primary_type = $part->getPrimaryType(); if (($primary_type == MIME::type(TYPEMULTIPART)) || ($primary_type == MIME::type(TYPETEXT))) { $body_id = $this->_findBody($part, $subtype); if ($subtype !== null) { $this->_body_id = $body_id; } return $body_id; } return null; } /** * Processes a MIME Part and looks for "body" data. * * @since Horde 3.2 * * @access private * * @return string The MIME ID of the main "body" part. */ function _findBody($mime_part, $subtype) { if (intval($mime_part->getMIMEId()) < 2 || $mime_part->getInformation('alternative') === 0) { if ($mime_part->getPrimaryType() == MIME::type(TYPEMULTIPART)) { $parts = $mime_part->getParts(); while ($part = array_shift($parts)) { if (($partid = $this->_findBody($part, $subtype))) { return $partid; } } } elseif ($mime_part->getPrimaryType() == MIME::type(TYPETEXT)) { if ($mime_part->getDisposition() != 'attachment' && (($subtype === null) || ($subtype == $mime_part->getSubType())) && ($mime_part->getBytes() || $this->getBodyPart($mime_part->getMIMEId()))) { return $mime_part->getMIMEId(); } } } return null; } /** * Are we viewing this page as an attachment through view.php? * This method can also be called via MIME_Contents::viewAsAttachment(). * * @param boolean $popup If true, also check if we are viewing attachment * in popup view window. * * @return boolean True if we are viewing this part as an attachment * through view.php. */ function viewAsAttachment($popup = false) { return ((strpos($_SERVER['PHP_SELF'], 'view.php') !== false) && (!$popup || Util::getFormData('popup_view'))); } /** * Sets a summary entry. * * @param MIME_Part &$mime_part The MIME_Part object. * @param string $type The summary cache to use. */ function setSummary(&$mime_part, $type) { if ($type == 'attachment') { $cache = &$this->_atc; } elseif ($type == 'part') { $cache = &$this->_summary; } else { return; } $key = $this->_getMIMEKey($mime_part); $this->_summaryType = $type; $summary = $this->partSummary($mime_part, null); $this->_summaryType = null; if (!empty($summary)) { if (!isset($this->_parts[$key])) { $this->_parts[$key] = null; } $cache[$key] = $summary; } } /** * Returns an array summarizing a part of a MIME message. * * @param MIME_Part &$mime_part The MIME_Part to summarize. * @param boolean $guess Is this a temporary guessed-type part? * * @return array The summary of the part. * [0] = Icon * [1] = IMAP ID * [2] = Description * [3] = MIME Type * [4] = Size * [5] = Download link/icon */ function partSummary(&$mime_part, $guess = false) { $attachment = ($mime_part->getDisposition() == 'attachment'); $bytes = $mime_part->getBytes(); $size = $mime_part->getSize(true); $description = $mime_part->getDescription(true, true); $summary = array(); $mime_type = $mime_part->getType(); /* If the MIME Type is application/octet-stream or application/base64, try to use the file extension to determine the actual MIME type. */ $param_array = array(); if ($this->_links && !empty($size) && ($mime_type == 'application/octet-stream') || ($mime_type == 'application/base64')) { require_once dirname(__FILE__) . '/Magic.php'; $mime_type = MIME_Magic::filenameToMIME(MIME::decode($mime_part->getName())); $param_array['ctype'] = $mime_type; } $viewer = &$this->getMIMEViewer($mime_part, $mime_type); if (!$viewer) { return $summary; } /* Icon column. */ $summary[] = Horde::img($viewer->getIcon($mime_type), '', array('title' => $mime_type), ''); /* Number column. */ $id = $this->_getMIMEKey($mime_part); if (($this->_displayType == MIME_CONTENTS_DISPLAY_TYPE_BOTH) && !is_null($this->_summaryType)) { $summary[] = '' . $id . ''; } else { $summary[] = $id; } /* Name/text part column. */ $description = htmlspecialchars($description); if (!$this->_links || (!$attachment && empty($bytes)) || !isset($this->_viewID['view']) || is_a($viewer, 'MIME_Viewer_default')) { $summary[] = $description; } else { $summary[] = $this->linkViewJS($mime_part, $this->_viewID['view'], $description, sprintf(_("View %s [%s]"), $description, $mime_type), null, $param_array); } /* Size. */ if (!empty($bytes) && ($mime_part->getCurrentEncoding() == 'base64')) { /* From RFC 2045 [6.8]: "...the encoded data are consistently only about 33 percent larger than the unencoded data." */ $size = max((($bytes * 0.75) / 1024), 1); if ($size > 1024) { $size = sprintf(_("%s MB"), NLS::numberFormat(max(($size / 1024), 1))); } else { $size = sprintf(_("%s KB"), NLS::numberFormat($size)); } } else { if ($size > 1024) { $size = sprintf(_("%s MB"), NLS::numberFormat(max(($size / 1024), 1))); } else { $size = sprintf(_("%s KB"), NLS::numberFormat($size)); } } /* Download column. */ if (!$this->_links || is_null($size) || !isset($this->_viewID['download'])) { $summary[] = '' . $size . ''; } else { $summary[] = $this->linkView($mime_part, $this->_viewID['download'], $size, array('class' => 'download', 'jstext' => sprintf(_("Download %s"), $description)), true); } return $summary; } /** * Return the URL to the view.php page. * * @param MIME_Part &$mime_part The MIME_Part object to view. * @param integer $actionID The ActionID to perform. * @param array $params A list of any additional parameters that * need to be passed to view.php. (key = * name) * @param boolean $dload Should we generate a download link? * * @return string The URL to view.php. */ function urlView(&$mime_part, $actionID, $params = array(), $dload = false) { /* Get the MIME ID for this part. */ $id = (isset($params['id'])) ? $params['id'] : $mime_part->getMIMEId(); /* Add the necessary local parameters. */ $params['actionID'] = $actionID; $params['id'] = $id; $params = array_merge($params, $this->cacheIDURLParam()); if ($dload) { $url = Horde::downloadUrl($mime_part->getName(true, true), $params); } else { $url = Util::addParameter(Horde::applicationUrl('view.php'), $params); } return $url; } /** * Generate a link to the view.php page. * * Important: the calling code has to make sure that the $text parameter * is properly escaped! * * @param MIME_Part &$mime_part The MIME_Part object to view. * @param integer $actionID The actionID value. * @param string $text The ESCAPED link text. * @param array $params A list of additional parameters. * 'class' - The CSS class to use. * 'jstext' - The JS text to use. * 'viewparams' - A list of any additional parameters that need to be * passed to view.php. * @param boolean $dload Should we generate a download link? * * @return string A HTML href link to view.php. */ function linkView(&$mime_part, $actionID, $text, $params = array(), $dload = false) { if (!isset($params['class'])) { $params['class'] = null; } if (!isset($params['jstext'])) { $params['jstext'] = $text; } if (!isset($params['viewparams'])) { $params['viewparams'] = array(); } if ($dload) { $window = null; } else { $window = 'view_' . abs(crc32(mt_rand())); } return Horde::link($this->urlView($mime_part, $actionID, $params['viewparams'], $dload), $params['jstext'], $params['class'], $window) . $text . ''; } /** * Generate a javascript link to the view.php page. * * Important: the calling code has to make sure that the $text parameter * is properly escaped! * * @param MIME_Part &$mime_part The MIME_Part object to view. * @param integer $actionID The ActionID to perform. * @param string $text The ESCAPED link text. * @param string $jstext The Javascript link text. * @param string $css The CSS class to use. * @param array $params A list of any additional parameters that * need to be passed to view.php. (key = * name) * @param boolean $widget If true use Horde::widget() to generate, * Horde::link() otherwise. * * @return string A HTML href link to view.php. */ function linkViewJS(&$mime_part, $actionID, $text, $jstext = null, $css = null, $params = array(), $widget = false) { /* If viewing via view.php, we don't want a JS link. */ if ($this->viewAsAttachment()) { return $this->linkView($mime_part, $actionID, $text, $params); } if (empty($jstext)) { $jstext = sprintf(_("View %s"), $mime_part->getDescription(true, true)); } $params['popup_view'] = 1; $url = $this->urlView($mime_part, $actionID, $params); if (!($id = $mime_part->getMIMEId())) { $id = abs(crc32(serialize($mime_part))); } if ($widget) { return Horde::widget('#', $jstext, $css, null, "view('" . $url . "', '" . $id . "'); return false;", $text); } else { return Horde::link('#', $jstext, $css, null, "view('" . $url . "', '" . $id . "'); return false;") . $text . ''; } } /** * Prints out the status message for a given MIME Part. * * @param string $msg The message to output. * @param string $img An image link to add to the beginning of the * message. * @param boolean $print Output this message when in a print view? * @param string $class An optional style for the status box. * * @return string The formatted status message string. */ function formatStatusMsg($msg, $img = null, $printable = true, $class = null) { if (empty($msg)) { return ''; } if (!is_array($msg)) { $msg = array($msg); } /* If we are viewing as an attachment, don't print HTML code. */ if ($this->viewAsAttachment()) { return implode("\n", $msg); } if (is_null($class)) { $class = 'mimeStatusMessage'; } $text = ''; /* If no image, simply print out the message. */ if (is_null($img)) { foreach ($msg as $val) { $text .= '' . "\n"; } } else { $text .= '' . "\n"; } return $text . '
' . $val . '
' . $img . ''; if (count($msg) == 1) { $text .= $msg[0]; } else { $text .= ''; foreach ($msg as $val) { $text .= '' . "\n"; } $text .= '
' . $val . '
'; } $text .= '
'; } /** * Return a pointer to the base object. * * @return mixed Returns a pointer to the base object. * Returns false if there is no base object. */ function &getBaseObjectPtr() { if ($this->_base === null) { return $this; } else { return $this->_base; } } /** * Set the MIME_Contents:: object to be cached. * * @access private * * @param string The cache OID. */ function _addCache() { if (is_null($this->_sessionCacheID)) { $this->_sessionCacheID = $this->_createCacheID(); register_shutdown_function(array(&$this, '_addCacheShutdown')); } return $this->_sessionCacheID; } /** * Creates a unique cache ID for this object. * * @access private * * @return string A unique cache ID. */ function _createCacheID() { // Use Auth class, if available, to add uniqueness. $entropy = class_exists('Auth') ? Auth::getAuth() : ''; return md5(mt_rand() . $entropy . getmypid()); } /** * Saves a copy of the MIME_Contents object at the end of a request. * * @access private */ function _addCacheShutdown() { /* Don't save the MIME_Viewer cached objects since there is no easy way to regenerate them on cache reload. */ $this->_viewerCache = array(); /* Copy the MIME_Message cache object to the _message variable. */ $this->_message = $this->_cachemessage; if (!empty($GLOBALS['conf']['cache']['driver'])) { require_once 'Horde/Cache.php'; $cache = &Horde_Cache::singleton($GLOBALS['conf']['cache']['driver'], Horde::getDriverConfig('cache', $GLOBALS['conf']['cache']['driver'])); $cache->set($this->_sessionCacheID, @serialize($this)); } else { require_once 'Horde/SessionObjects.php'; $cache = &Horde_SessionObjects::singleton(); $cache->overwrite($this->_sessionCacheID, $this); } } /** * Returns the cached MIME_Contents:: object. * This function should be called statically e.g.: * $ptr = &MIME_Contents::getCache(). * * @param string $cacheid The cache ID to use. If empty, will use the * cache ID located in the URL parameter named * MIME_CONTENTS_CACHE. * * @return MIME_Contents The MIME_Contents object, or null if it does not * exist. */ function &getCache($cacheid = null) { if (is_null($cacheid)) { $cacheid = Util::getFormData(MIME_CONTENTS_CACHE); } if (!empty($GLOBALS['conf']['cache']['driver'])) { require_once 'Horde/Cache.php'; $cache = &Horde_Cache::singleton($GLOBALS['conf']['cache']['driver'], Horde::getDriverConfig('cache', $GLOBALS['conf']['cache']['driver'])); $contents = @unserialize($cache->get($cacheid, 0)); } else { require_once 'Horde/SessionObjects.php'; $cache = &Horde_SessionObjects::singleton(); $contents = &$cache->query($cacheid); } return $contents; } /** * Add the current object to the cache, and return the cache identifier * to be used in URLs. * * @return array The parameter key/value set to use in URLs. */ function cacheIDURLParam() { return array(MIME_CONTENTS_CACHE => $this->_addCache()); } } * @package Horde_MIME */ class MIME_Headers { /** * The internal headers array. * * @var array */ var $_headers = array(); /** * Cached output of the MIME_Structure::parseMIMEHeaders() command. * * @var array */ var $_allHeaders; /** * Cached output of the imap_fetchheader() command. * * @var string */ var $_headerText; /** * The header object returned from imap_headerinfo(). * * @var stdClass */ var $_headerObject; /** * The User-Agent string to use. * THIS VALUE SHOULD BE OVERRIDEN BY ALL SUBCLASSES. * * @var string */ var $_agent = HORDE_AGENT_HEADER; /** * The sequence to use as EOL for the headers. * The default is currently to output the EOL sequence internally as * just "\n" instead of the canonical "\r\n" required in RFC 822 & 2045. * To be RFC complaint, the full EOL combination should be used * when sending a message. * * @var string */ var $_eol = "\n"; /** * The index of the message. * * @var integer */ var $_index; /** * Constructor. * * @param integer $index The message index to parse headers (DEPRECATED). */ function MIME_Headers($index = null) { $this->_index = $index; } /** * Returns a reference to a currently open IMAP stream. * THIS VALUE SHOULD BE OVERRIDEN BY ALL SUBCLASSES. * * @return resource An IMAP resource stream. */ function &_getStream() { return false; } /** * Return the full list of headers from the imap_fetchheader() function. * * @return string See imap_fetchheader(). */ function getHeaderText() { if (!is_null($this->_index) && empty($this->_headerText)) { $this->_headerText = @imap_fetchheader($this->_getStream(), $this->_index, FT_UID); require_once 'Horde/MIME.php'; if (MIME::is8bit($this->_headerText)) { $this->_headerText = String::convertCharset($this->_headerText, !empty($GLOBALS['mime_headers']['default_charset']) ? $GLOBALS['mime_headers']['default_charset'] : 'US-ASCII'); } } return $this->_headerText; } /** * Return the full list of headers. * * @param boolean $decode Decode the headers? * * @return array See MIME_Structure::parseMIMEHeaders(). */ function getAllHeaders($decode = true) { if (!is_null($this->_index) && empty($this->_allHeaders)) { require_once 'Horde/MIME/Structure.php'; $this->_allHeaders = MIME_Structure::parseMIMEHeaders($this->getHeaderText(), $decode); } return $this->_allHeaders; } /** * Build the header array. * * @param boolean $decode MIME decode the headers? */ function buildHeaders($decode = true) { if (empty($this->_headers)) { foreach ($this->getAllHeaders($decode) as $key => $val) { $this->addHeader($key, $val); } } } /** * Returns the internal header array in array format. * * @return array The headers in array format. */ function toArray() { $return_array = array(); foreach ($this->_headers as $ob) { $header = $ob['header']; if (is_array($ob['value'])) { if (String::lower($header) == 'received') { $return_array[$header] = $ob['value']; } else { $return_array[$header] = reset($ob['value']); } } else { $return_array[$header] = $ob['value']; } } return $return_array; } /** * Returns the internal header array in string format. * * @return string The headers in string format. */ function toString() { $text = ''; foreach ($this->_headers as $ob) { if (!is_array($ob['value'])) { $ob['value'] = array($ob['value']); } foreach ($ob['value'] as $entry) { $text .= $ob['header'] . ': ' . $entry . $this->_eol; } } return $text . $this->_eol; } /** * Generate the 'Received' header for the Web browser->Horde hop * (attempts to conform to guidelines in RFC 2821). */ function addReceivedHeader() { $have_netdns = @include_once 'Net/DNS.php'; if ($have_netdns) { $resolver = new Net_DNS_Resolver(); $resolver->retry = isset($GLOBALS['conf']['dns']['retry']) ? $GLOBALS['conf']['dns']['retry'] : 1; $resolver->retrans = isset($GLOBALS['conf']['dns']['retrans']) ? $GLOBALS['conf']['dns']['retrans'] : 1; } if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { /* This indicates the user is connecting through a proxy. */ $remote_path = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); $remote_addr = $remote_path[0]; if ($have_netdns) { $response = $resolver->query($remote_addr, 'PTR'); $remote = $response ? $response->answer[0]->ptrdname : $remote_addr; } else { $remote = @gethostbyaddr($remote_addr); } } else { $remote_addr = $_SERVER['REMOTE_ADDR']; if (empty($_SERVER['REMOTE_HOST'])) { if ($have_netdns) { $response = $resolver->query($remote_addr, 'PTR'); $remote = $response ? $response->answer[0]->ptrdname : $remote_addr; } else { $remote = @gethostbyaddr($remote_addr); } } else { $remote = $_SERVER['REMOTE_HOST']; } } $received = 'from ' . $remote . ' ('; if (!empty($_SERVER['REMOTE_IDENT'])) { $received .= $_SERVER['REMOTE_IDENT'] . '@' . $remote . ' '; } elseif ($remote != $_SERVER['REMOTE_ADDR']) { $received .= $remote . ' '; } $received .= '[' . $remote_addr . ']) '; if (!empty($GLOBALS['conf']['server']['name'])) { $server_name = $GLOBALS['conf']['server']['name']; } elseif (!empty($_SERVER['SERVER_NAME'])) { $server_name = $_SERVER['SERVER_NAME']; } elseif (!empty($_SERVER['HTTP_HOST'])) { $server_name = $_SERVER['HTTP_HOST']; } else { $server_name = 'unknown'; } $received .= 'by ' . $server_name . ' (Horde Framework) with HTTP; '; $received .= date('r'); $this->addHeader('Received', $received); } /** * Generate the 'Message-ID' header. */ function addMessageIdHeader() { require_once 'Horde/MIME.php'; $this->addHeader('Message-ID', MIME::generateMessageID()); } /** * Generate the 'Resent' headers (conforms to guidelines in * RFC 2822 [3.6.6]). * * @param string $from The address to use for 'Resent-From'. * @param string $to The address to use for 'Resent-To'. */ function addResentHeaders($from, $to) { require_once 'Horde/MIME.php'; /* We don't set Resent-Sender, Resent-Cc, or Resent-Bcc. */ $this->addHeader('Resent-Date', date('r')); $this->addHeader('Resent-From', $from); $this->addHeader('Resent-To', $to); $this->addHeader('Resent-Message-ID', MIME::generateMessageID()); } /** * Generate the user agent description header. */ function addAgentHeader() { $this->addHeader('User-Agent', $this->_agent); } /** * Returns the user agent description header. * * @return string The user agent header. */ function getAgentHeader() { return $this->_agent; } /** * Add a header to the header array. * * @param string $header The header name. * @param string $value The header value. */ function addHeader($header, $value) { $header = trim($header); $lcHeader = String::lower($header); if (!isset($this->_headers[$lcHeader])) { $this->_headers[$lcHeader] = array(); } $this->_headers[$lcHeader]['header'] = $header; $this->_headers[$lcHeader]['value'] = $value; $this->_headers[$lcHeader]['_alter'] = false; } /** * Remove a header from the header array. * * @param string $header The header name. */ function removeHeader($header) { $header = trim($header); $lcHeader = String::lower($header); unset($this->_headers[$lcHeader]); } /** * Set a value for a particular header ONLY if that header is set. * * @param string $header The header name. * @param string $value The header value. * * @return boolean True if value was set, false if not. */ function setValue($header, $value) { $lcHeader = String::lower($header); if (isset($this->_headers[$lcHeader])) { $this->_headers[$lcHeader]['value'] = $value; $this->_headers[$lcHeader]['_alter'] = true; return true; } else { return false; } } /** * Attempts to return the header in the correct case. * * @param string $header The header to search for. * * @return string The value for the given header. * If the header is not found, returns null. */ function getString($header) { $lcHeader = String::lower($header); return (isset($this->_headers[$lcHeader])) ? $this->_headers[$lcHeader]['header'] : null; } /** * Attempt to return the value for a given header. * The following header fields can only have 1 entry, so if duplicate * entries exist, the first value will be used: * * To, From, Cc, Bcc, Date, Sender, Reply-to, Message-ID, In-Reply-To, * References, Subject (RFC 2822 [3.6]) * * All List Headers (RFC 2369 [3]) * * @param string $header The header to search for. * * @return mixed The value for the given header. * If the header is not found, returns null. */ function getValue($header) { $header = String::lower($header); if (isset($this->_headers[$header])) { $single = array('to', 'from', 'cc', 'bcc', 'date', 'sender', 'reply-to', 'message-id', 'in-reply-to', 'references', 'subject', 'x-priority'); $single = array_merge($single, array_keys($this->listHeaders())); if (is_array($this->_headers[$header]['value']) && in_array($header, $single)) { return $this->_headers[$header]['value'][0]; } else { return $this->_headers[$header]['value']; } } else { return null; } } /** * Has the header been altered from the original? * * @param string $header The header to analyze. * * @return boolean True if the header has been altered. */ function alteredHeader($header) { $lcHeader = String::lower($header); return (isset($this->_headers[$lcHeader])) ? $this->_headers[$lcHeader]['_alter'] : false; } /** * Transforms a Header value using the list of functions provided. * * @param string $header The header to alter. * @param mixed $funcs A function, or an array of functions. * The functions will be performed from right to * left. */ function setValueByFunction($header, $funcs) { $header = String::lower($header); if (is_array($funcs)) { $funcs = array_reverse($funcs); } else { $funcs = array($funcs); } if (isset($this->_headers[$header])) { $val = $this->getValue($header); if (is_array($val)) { $val = implode("\n", $val); } foreach ($funcs as $func) { $val = call_user_func($func, $val); } $this->setValue($header, $val); } } /** * Add any MIME headers required for the MIME_Part. * * @param MIME_Part &$mime_part The MIME_Part object. */ function addMIMEHeaders(&$mime_part) { foreach ($mime_part->header(array()) as $head => $val) { $this->addHeader($head, $val); } } /** * Return the list of addresses for a header object. * * @param array $obs An array of header objects (See imap_headerinfo() * for the object structure). * * @return array An array of objects. *
     * Object elements:
     * 'address'   -  Full address
     * 'display'   -  A displayable version of the address (Horde 3.2.1+)
     * 'host'      -  Host name
     * 'inner'     -  Trimmed, bare address
     * 'personal'  -  Personal string
     * 
*/ function getAddressesFromObject($obs) { $retArray = array(); if (!is_array($obs) || empty($obs)) { return $retArray; } foreach ($obs as $ob) { /* Ensure we're working with initialized values. */ $ob->personal = (isset($ob->personal)) ? stripslashes(trim(MIME::decode($ob->personal), '"')) : ''; if (isset($ob->mailbox)) { /* Don't process invalid addresses. */ if (strpos($ob->mailbox, 'UNEXPECTED_DATA_AFTER_ADDRESS') !== false || strpos($ob->mailbox, 'INVALID_ADDRESS') !== false) { continue; } } else { $ob->mailbox = ''; } if (!isset($ob->host)) { $ob->host = ''; } $inner = MIME::trimEmailAddress(MIME::rfc822WriteAddress($ob->mailbox, $ob->host, '')); /* Generate the new object. */ $newOb = &new stdClass; $newOb->address = MIME::addrObject2String($ob, array('undisclosed-recipients@', 'Undisclosed recipients@')); $newOb->display = (empty($ob->personal) ? '' : $ob->personal . ' <') . $inner . (empty($ob->personal) ? '' : '>'); $newOb->host = $ob->host; $newOb->inner = $inner; $newOb->personal = $ob->personal; $retArray[] = &$newOb; } return $retArray; } /** * Returns the list of valid mailing list headers. * * @return array The list of valid mailing list headers. */ function listHeaders() { return array( /* RFC 2369 */ 'list-help' => _("List-Help"), 'list-unsubscribe' => _("List-Unsubscribe"), 'list-subscribe' => _("List-Subscribe"), 'list-owner' => _("List-Owner"), 'list-post' => _("List-Post"), 'list-archive' => _("List-Archive"), /* RFC 2919 */ 'list-id' => _("List-Id") ); } /** * Do any mailing list headers exist? * * @return boolean True if any mailing list headers exist. */ function listHeadersExist() { return (bool) count(array_intersect(array_keys($this->listHeaders()), array_keys($this->_headers))); } /** * Sets a new string to use for EOLs. * * @param string $eol The string to use for EOLs. */ function setEOL($eol) { $this->_eol = $eol; } /** * Get the string to use for EOLs. * * @return string The string to use for EOLs. */ function getEOL() { return $this->_eol; } /** * Get the primary from address (first address in the From: header). * * @return string The from address (user@host). */ function getFromAddress() { if (!($ob = $this->getOb('from'))) { return null; } require_once 'Horde/MIME.php'; return trim(MIME::trimEmailAddress(MIME::rfc822WriteAddress($ob[0]->mailbox, (isset($ob[0]->host)) ? $ob[0]->host : '', ''))); } /** * Get a header from the header object. * * @todo Replace with getOb() from IMP's IMP_Headers for Horde 4.0. * * @param string $field The object field to retrieve (see * imap_headerinfo() for the list of fields). * @param boolean $decode Should the return value be MIME decoded? * It will only be decoded if it is not an object * itself. * * @return mixed The field requested. */ function getOb($field, $decode = false) { $data = array(); $ob = $this->getHeaderObject(); if (!is_object($ob)) { return $data; } if (isset($ob->$field)) { $data = $ob->$field; if (!empty($decode) && !is_object($data) && !is_array($data)) { require_once 'Horde/MIME.php'; if (MIME::is8bit($this->_headerText)) { $data = String::convertCharset($data, !empty($GLOBALS['mime_headers']['default_charset']) ? $GLOBALS['mime_headers']['default_charset'] : 'US-ASCII'); } $data = MIME::decode($data); } } return (is_string($data)) ? strtr($data, "\t", ' ') : $data; } /* Deprecated functions. */ /** * The internal flags array. * * @deprecated since Horde 3.2 * @var array */ var $_flags = array(); /** * Build the flags array. * * @deprecated since Horde 3.2 */ function buildFlags() { if (!empty($this->_flags)) { return; } /* Get the IMAP header object. */ $ob = $this->getHeaderObject(); if (!is_object($ob)) { return; } /* Unseen flag */ if (($ob->Unseen == 'U') || ($ob->Recent == 'N')) { $this->_flags['unseen'] = true; } /* Recent flag */ if (($ob->Recent == 'N') || ($ob->Recent == 'R')) { $this->_flags['recent'] = true; } /* Answered flag */ if ($ob->Answered == 'A') { $this->_flags['answered'] = true; } /* Draft flag */ if (isset($ob->Draft) && ($ob->Draft == 'X')) { $this->_flags['draft'] = true; } /* Flagged flag */ if ($ob->Flagged == 'F') { $this->_flags['flagged'] = true; } /* Deleted flag */ if ($ob->Deleted == 'D') { $this->_flags['deleted'] = true; } } /** * Returns the flag status. * * @deprecated since Horde 3.2 * * @param string $flag Is this flag set? * Flags: recent, unseen, answered, draft, important, * deleted * * @return boolean True if the flag has been set, false if not. */ function getFlag($flag) { if (!empty($this->_flags[String::lower($flag)])) { return true; } else { return false; } } /** * Return the header object from imap_headerinfo(). * * @deprecated since Horde 3.2 * * @return stdClass See imap_headerinfo(). */ function getHeaderObject() { if (!is_null($this->_index) && empty($this->_headerObject)) { $stream = $this->_getStream(); $this->_headerObject = @imap_headerinfo($stream, @imap_msgno($stream, $this->_index)); } return $this->_headerObject; } /** * Generate delivery receipt headers. * * @deprecated since Horde 3.2 * * @param string $to The address the receipt should be mailed to. */ function addDeliveryReceiptHeaders($to) { /* This is old sendmail (pre-8.7) behavior. */ $this->addHeader('Return-Receipt-To', $to); } } * @author Michael Slusarz * @package Horde_MIME */ class MIME_Magic { /** * Returns a copy of the MIME extension map. * * @access private * * @return array The MIME extension map. */ function _getMimeExtensionMap() { static $mime_extension_map; if (!isset($mime_extension_map)) { require dirname(__FILE__) . '/mime.mapping.php'; } return $mime_extension_map; } /** * Returns a copy of the MIME magic file. * * @access private * * @return array The MIME magic file. */ function _getMimeMagicFile() { static $mime_magic; if (!isset($mime_magic)) { require dirname(__FILE__) . '/mime.magic.php'; } return $mime_magic; } /** * Attempt to convert a file extension to a MIME type, based * on the global Horde and application specific config files. * * If we cannot map the file extension to a specific type, then * we fall back to a custom MIME handler 'x-extension/$ext', which * can be used as a normal MIME type internally throughout Horde. * * @param string $ext The file extension to be mapped to a MIME type. * * @return string The MIME type of the file extension. */ function extToMIME($ext) { if (empty($ext)) { return 'application/octet-stream'; } else { $ext = String::lower($ext); $map = MIME_Magic::_getMimeExtensionMap(); $pos = 0; while (!isset($map[$ext]) && $pos !== false) { $pos = strpos($ext, '.'); if ($pos !== false) { $ext = substr($ext, $pos + 1); } } if (isset($map[$ext])) { return $map[$ext]; } else { return 'x-extension/' . $ext; } } } /** * Attempt to convert a filename to a MIME type, based on the global Horde * and application specific config files. * * @param string $filename The filename to be mapped to a MIME type. * @param boolean $unknown How should unknown extensions be handled? If * true, will return 'x-extension/*' types. If * false, will return 'application/octet-stream'. * * @return string The MIME type of the filename. */ function filenameToMIME($filename, $unknown = true) { $pos = strlen($filename) + 1; $type = ''; $map = MIME_Magic::_getMimeExtensionMap(); for ($i = 0; $i <= $map['__MAXPERIOD__'] && strrpos(substr($filename, 0, $pos - 1), '.') !== false; $i++) { $pos = strrpos(substr($filename, 0, $pos - 1), '.') + 1; } $type = MIME_Magic::extToMIME(substr($filename, $pos)); if (empty($type) || (!$unknown && (strpos($type, 'x-extension') !== false))) { return 'application/octet-stream'; } else { return $type; } } /** * Attempt to convert a MIME type to a file extension, based * on the global Horde and application specific config files. * * If we cannot map the type to a file extension, we return false. * * @param string $type The MIME type to be mapped to a file extension. * * @return string The file extension of the MIME type. */ function MIMEToExt($type) { if (empty($type)) { return false; } $key = array_search($type, MIME_Magic::_getMimeExtensionMap()); if ($key === false) { list($major, $minor) = explode('/', $type); if ($major == 'x-extension') { return $minor; } if (strpos($minor, 'x-') === 0) { return substr($minor, 2); } return false; } else { return $key; } } /** * Uses variants of the UNIX "file" command to attempt to determine the * MIME type of an unknown file. * * @param string $path The path to the file to analyze. * @param string $magic_db Path to the mime magic database. * * @return string The MIME type of the file. Returns false if the file * type isn't recognized or an error happened. */ function analyzeFile($path, $magic_db = null) { /* If the PHP Mimetype extension is available, use that. */ if (Util::extensionExists('fileinfo')) { if (empty($magic_db)) { $res = @finfo_open(FILEINFO_MIME); } else { $res = @finfo_open(FILEINFO_MIME, $magic_db); } if ($res) { $type = finfo_file($res, $path); finfo_close($res); /* Remove any additional information. */ foreach (array(';', ',', '\\0') as $separator) { $pos = strpos($type, $separator); if ($pos !== false) { $type = rtrim(substr($type, 0, $pos)); } } if (preg_match('|^[a-z0-9]+/[.-a-z0-9]+$|i', $type)) { return $type; } } } if (Util::extensionExists('mime_magic')) { return trim(mime_content_type($path)); } else { /* Use a built-in magic file. */ $mime_magic = MIME_Magic::_getMimeMagicFile(); if (!($fp = @fopen($path, 'rb'))) { return false; } while (list($offset, $odata) = each($mime_magic)) { while (list($length, $ldata) = each($odata)) { @fseek($fp, $offset, SEEK_SET); $lookup = @fread($fp, $length); if (!empty($ldata[$lookup])) { fclose($fp); return $ldata[$lookup]; } } } fclose($fp); } return false; } /** * Uses variants of the UNIX "file" command to attempt to determine the * MIME type of an unknown byte stream. * * @param string $data The file data to analyze. * @param string $magic_db Path to the mime magic database. * * @return string The MIME type of the file. Returns false if the file * type isn't recognized or an error happened. */ function analyzeData($data, $magic_db = null) { /* If the PHP Mimetype extension is available, use that. */ if (Util::extensionExists('fileinfo')) { if (empty($magic_db)) { $res = @finfo_open(FILEINFO_MIME); } else { $res = @finfo_open(FILEINFO_MIME, $magic_db); } if (!$res) { return false; } $type = finfo_buffer($res, $data); finfo_close($res); /* Remove any additional information. */ $pos = strpos($type, ';'); if ($pos !== false) { $type = rtrim(substr($type, 0, $pos)); } $pos = strpos($type, ','); if ($pos !== false) { $type = rtrim(substr($type, 0, $pos)); } return $type; } /* Use a built-in magic file. */ $mime_magic = MIME_Magic::_getMimeMagicFile(); while (list($offset, $odata) = each($mime_magic)) { while (list($length, $ldata) = each($odata)) { $lookup = substr($data, $offset, $length); if (!empty($ldata[$lookup])) { return $ldata[$lookup]; } } } return false; } } * @since Horde 3.2 * @package Horde_MIME */ class MIME_Mail { /** * The message headers. * * @var MIME_Headers */ var $_headers; /** * The main body part. * * @var MIME_Part */ var $_body; /** * The main HTML body part. * * @var MIME_Part */ var $_htmlBody; /** * The message recipients. * * @var array */ var $_recipients = array(); /** * All MIME parts except the main body part. * * @var array */ var $_parts = array(); /** * The Mail driver name. * * @link http://pear.php.net/Mail * @var string */ var $_mailer_driver = 'smtp'; /** * The Mail driver parameters. * * @link http://pear.php.net/Mail * @var array */ var $_mailer_params = array(); /** * Constructor. * * @param string $subject The message subject. * @param string $body The message body. * @param string $to The message recipient(s). * @param string $from The message sender. * @param string $charset The character set of the message. */ function MIME_Mail($subject = null, $body = null, $to = null, $from = null, $charset = null) { /* Set SERVER_NAME. */ if (!isset($_SERVER['SERVER_NAME'])) { $_SERVER['SERVER_NAME'] = php_uname('n'); } if (!$charset) { $charset = 'iso-8859-1'; } $this->_headers = new MIME_Headers(); if ($subject) { $this->addHeader('Subject', $subject, $charset); } if ($to) { $this->addHeader('To', $to, $charset); } if ($from) { $this->addHeader('From', $from, $charset); } if ($body) { $this->setBody($body, $charset); } } /** * Adds several message headers at once. * * @see addHeader() * * @param array $header Hash with header names as keys and header * contents as values. * @param string $charset The header value's charset. */ function addHeaders($headers = array(), $charset = null) { foreach ($headers as $header => $value) { if (is_a($added = $this->addHeader($header, $value, $charset), 'PEAR_Error')) { return $added; } } } /** * Adds a message header. * * @see addHeaders() * * @param string $header The header name. * @param string $value The header value. * @param string $charset The header value's charset. * @param boolean $overwrite If true, an existing header of the same name * is being overwritten; if false, multiple * headers are added; if null, the correct * behaviour is automatically chosen depending * on the header name. */ function addHeader($header, $value, $charset = null, $overwrite = null) { $lc_header = String::lower($header); /* Only encode value if charset is explicitly specified, otherwise * the message's charset will be used when building the message. */ if (!empty($charset)) { if (in_array($lc_header, array('to', 'from', 'cc', 'bcc', 'sender', 'reply-to'))) { $value = MIME::encodeAddress($value, $charset); } else { $value = MIME::encode($value, $charset); } } if (is_null($overwrite)) { /* The following header fields can only have 1 entry, so if * duplicate entries exist, the first value will be used: * * To, From, Cc, Bcc, Date, Sender, Reply-to, Message-ID, * In-Reply-To, References, Subject (RFC 2822 [3.6]) * * All List Headers (RFC 2369 [3]) */ $single = array('to', 'from', 'cc', 'bcc', 'date', 'sender', 'reply-to', 'message-id', 'in-reply-to', 'references', 'subject', 'x-priority'); $single = array_merge($single, array_keys($this->_headers->listHeaders())); if (in_array($lc_header, $single)) { $overwrite = true; } } if ($overwrite) { $this->_headers->removeHeader($header); } if ($lc_header !== 'bcc') { $this->_headers->addHeader($header, $value); } if (in_array($lc_header, array('to', 'cc', 'bcc'))) { return $this->addRecipients($value); } } /** * Removes a message header. * * @param string $header The header name. */ function removeHeader($header) { $value = $this->_headers->getValue($header); $this->_headers->removeHeader($header); if (in_array(String::lower($header), array('to', 'cc', 'bcc'))) { $this->removeRecipients($value); } } /** * Sets the message body text. * * @param string $body The message content. * @param string $charset The character set of the message. * @param boolean|integer $wrap If true, wrap the message at column 76; * If an integer wrap the message at that * column. Don't use wrapping if sending * flowed messages. */ function setBody($body, $charset = 'iso-8859-1', $wrap = false) { if ($wrap) { if ($wrap === true) { $wrap = 76; } $body = String::wrap($body, $wrap, "\n"); } $this->_body = new MIME_Part('text/plain', $body, $charset); } /** * Sets the HTML message body text. * * @param string $body The message content. * @param string $charset The character set of the message. * @param boolean $alternative If true, a multipart/alternative message is * created and the text/plain part is * generated automatically. If false, a * text/html message is generated. */ function setHTMLBody($body, $charset = 'iso-8859-1', $alternative = true) { $this->_htmlBody = new MIME_Part('text/html', $body, $charset); if ($alternative) { require_once 'Horde/Text/Filter.php'; $body = Text_Filter::filter($body, 'html2text', array('wrap' => false)); $this->_body = new MIME_Part('text/plain', $body, $charset); } } /** * Adds a message part. * * @param string $mime_type The content type of the part. * @param string $content The content of the part. * @param string $charset The character set of the part. * @param string $disposition The content disposition of the part. * * @return integer The part number. */ function addPart($mime_type, $content, $charset = 'us-ascii', $disposition = null) { $part = new MIME_Part($mime_type, $content, $charset, $disposition); $part->transferEncodeContents(); $this->_parts[] = $part; return count($this->_parts) - 1; } /** * Adds a MIME message part. * * @param MIME_Part $part A MIME_Part object. * * @return integer The part number. */ function addMIMEPart($part) { $part->transferEncodeContents(); $this->_parts[] = $part; return count($this->_parts) - 1; } /** * Adds an attachment. * * @param string $file The path to the file. * @param string $name The file name to use for the attachment. * @param string $type The content type of the file. * @param string $charset The character set of the part (only relevant for * text parts. * * @return integer The part number. */ function addAttachment($file, $name = null, $type = null, $charset = 'us-ascii') { if (empty($name)) { $name = basename($file); } if (empty($type)) { require_once 'Horde/MIME/Magic.php'; $type = MIME_Magic::filenameToMIME($file, false); } $part = new MIME_Part($type, file_get_contents($file), $charset, 'attachment'); $part->setName($name); $part->transferEncodeContents(); $this->_parts[] = $part; return count($this->_parts) - 1; } /** * Removes a message part. * * @param integer $part The part number. */ function removePart($part) { if (isset($this->_parts[$part])) { unset($this->_parts[$part]); } } /** * Adds message recipients. * * Recipients specified by To:, Cc:, or Bcc: headers are added * automatically. * * @param string|array List of recipients, either as a comma separated * list or as an array of email addresses. */ function addRecipients($recipients) { $recipients = $this->_buildRecipients($recipients); if (is_a($recipients, 'PEAR_Error')) { return $recipients; } $this->_recipients = array_merge($this->_recipients, $recipients); } /** * Removes message recipients. * * @param string|array List of recipients, either as a comma separated * list or as an array of email addresses. */ function removeRecipients($recipients) { $recipients = $this->_buildRecipients($recipients); if (is_a($recipients, 'PEAR_Error')) { return $recipients; } $this->_recipients = array_diff($this->_recipients, $recipients); } /** * Removes all message recipients. */ function clearRecipients() { $this->_recipients = array(); } /** * Builds a recipients list. * * @param string|array List of recipients, either as a comma separated * list or as an array of email addresses. * * @return array Normalized list of recipients or PEAR_Error on failure. */ function _buildRecipients($recipients) { if (is_string($recipients)) { $recipients = MIME::rfc822Explode($recipients, ','); } $recipients = array_filter(array_map('trim', $recipients)); $addrlist = array(); foreach ($recipients as $email) { if (!empty($email)) { $unique = MIME::bareAddress($email); if ($unique && ($unique != 'UNEXPECTED_DATA_AFTER_ADDRESS@.SYNTAX-ERROR.')) { $addrlist[$unique] = $email; } else { $addrlist[$email] = $email; } } } foreach (MIME::bareAddress(implode(', ', $addrlist), null, true) as $val) { if (MIME::is8bit($val)) { return PEAR::raiseError(sprintf(_("Invalid character in e-mail address: %s."), $val)); } } return $addrlist; } /** * Sends this message. * * For the possible Mail drivers and parameters see the PEAR Mail * documentation. * @link http://pear.php.net/Mail * * @param string $driver The Mail driver to use. * @param array $params Any parameters necessary for the Mail driver. * @param boolean $resend If true, the message id and date are re-used; * If false, they will be updated. * @param boolean $flowed Send message in flowed text format. @since * Horde 3.2.1 * * @return mixed True on success, PEAR_Error on error. */ function send($driver = null, $params = array(), $resend = false, $flowed = true) { /* Add mandatory headers if missing. */ if (!$resend || !$this->_headers->getString('Message-ID')) { $this->_headers->addMessageIdHeader(); } if (!$this->_headers->getString('User-Agent')) { $this->_headers->addAgentHeader(); } if (!$resend || !$this->_headers->getString('Date')) { $this->_headers->addHeader('Date', date('r')); } /* Send in flowed format. */ if ($flowed && !empty($this->_body)) { require_once 'Text/Flowed.php'; $flowed = new Text_Flowed($this->_body->getContents(), $this->_body->getCharset()); $flowed->setDelSp(true); $this->_body->setContentTypeParameter('DelSp', 'Yes'); $this->_body->setContents($flowed->toFlowed()); $this->_body->setContentTypeParameter('format', 'flowed'); } /* Build mime message. */ $mime = new MIME_Message(); if (!empty($this->_body) && !empty($this->_htmlBody)) { $basepart = new MIME_Part('multipart/alternative'); $this->_body->setDescription(_("Plaintext Version of Message")); $basepart->addPart($this->_body); $this->_htmlBody->setDescription(_("HTML Version of Message")); $basepart->addPart($this->_htmlBody); $mime->addPart($basepart); } elseif (!empty($this->_htmlBody)) { $mime->addPart($this->_htmlBody); } elseif (!empty($this->_body)) { $mime->addPart($this->_body); } foreach ($this->_parts as $mime_part) { $mime->addPart($mime_part); } $this->_headers->addMIMEHeaders($mime); /* Check mailer configuration. */ if (!empty($driver)) { $this->_mailer_driver = $driver; } if (!empty($params)) { $this->_mailer_params = $params; } /* Send message. */ return $mime->send(implode(', ', $this->_recipients), $this->_headers, $this->_mailer_driver, $this->_mailer_params); } } * @package Horde_MIME */ class MIME_MDN { /** * The MIME_Headers object. * * @var MIME_Headers */ var $_headers; /** * The text of the original message. * * @var string */ var $_msgtext = false; /** * Constructor. * * @param MIME_Headers $mime_headers A MIME_Headers object. */ function MIME_MDN($mime_headers = null) { $this->_headers = $mime_headers; } /** * Returns the address to return the MDN to. * Returns null if no MDN is requested. * * @return string The address to send the MDN to. Returns null if no * MDN is requested. */ function getMDNReturnAddr() { /* RFC 3798 [2.1] requires the Disposition-Notificaion-To header * for an MDN to be created. */ return $this->_headers->getValue('Disposition-Notification-To'); } /** * Is user input required to send the MDN? * Explicit confirmation is needed in some cases to prevent mail loops * and the use of MDNs for mail bombing. * * @return boolean Is explicit user input required to send the MDN? */ function userConfirmationNeeded() { $return_path = $this->_headers->getValue('Return-Path'); /* RFC 3798 [2.1]: Explicit confirmation is needed if there is no * Return-Path in the header. Also, "if the message contains more * than one Return-Path header, the implementation may [] treat the * situation as a failure of the comparison. */ if (empty($return_path) || is_array($return_path)) { return true; } require_once 'Horde/MIME.php'; /* RFC 3798 [2.1]: Explicit confirmation is needed if there is more * than one distinct address in the Disposition-Notification-To * header. */ $addr_arr = MIME::parseAddressList($this->getMDNReturnAddr()); if (is_a($addr_arr, 'PEAR_Error') || (count($addr_arr) > 1)) { return true; } /* RFC 3798 [2.1] states that "MDNs SHOULD NOT be sent automatically * if the address in the Disposition-Notification-To header differs * from the address in the Return-Path header." This comparison is * case-sensitive for the mailbox part and case-insensitive for the * host part. */ $ret_arr = MIME::parseAddressList($return_path); if (!is_a($ret_arr, 'PEAR_Error') && (($addr_arr[0]->mailbox != $ret_arr[0]->mailbox) || (String::lower($addr_arr[0]->host) != String::lower($ret_arr[0]->host)))) { return false; } return true; } /** * When generating the MDN, should we return the enitre text of the * original message? The default is no - we only return the headers of * the original message. If the text is passed in via this method, we * will return the entire message. * * @param string $text The text of the original message. */ function originalMessageText($text) { $this->_msgtext = $text; } /** * Generate the MDN according to the specifications listed in RFC * 3798 [3]. * * @param boolean $action Was this MDN type a result of a manual action * on part of the user? * @param boolean $sending Was this MDN sent as a result of a manual * action on part of the user? * @param string $type The type of action performed by the user. *
     * Per RFC 3798 [3.2.6.2] the following types are valid:
     * ====================================================
     * 'displayed'
     * 'deleted'
     * 
* @param array $mod The list of modifications. *
     * Per RFC 3798 [3.2.6.3] the following modifications are valid:
     * ============================================================
     * 'error'
     * 
* @param array $err If $mod is 'error', the additional information * to provide. Key is the type of modification, * value is the text. * * @return mixed True on success, PEAR_Error object on error. */ function generate($action, $sending, $type, $mod = array(), $err = array()) { require_once 'Horde/MIME/Headers.php'; require_once 'Horde/MIME/Message.php'; require_once 'Horde/Identity.php'; require_once 'Horde/Text.php'; /* Set up some variables we use later. */ $identity = &Identity::singleton(); $from_addr = $identity->getDefaultFromAddress(); $to = $this->getMDNReturnAddr(); $ua = $this->_headers->getAgentHeader(); $orig_recip = $this->_headers->getValue('Original-Recipient'); if (!empty($orig_recip) && is_array($orig_recip)) { $orig_recip = $orig_recip[0]; } $msg_id = $this->_headers->getValue('Message-ID'); /* Create the Disposition field now (RFC 3798 [3.2.6]). */ $dispo = 'Disposition: ' . (($action) ? 'manual-action' : 'automatic-action') . '/' . (($sending) ? 'MDN-sent-manually' : 'MDN-sent-automatically') . '; ' . $type; if (!empty($mod)) { $dispo .= '/' . implode(', ', $mod); } /* Set up the mail headers. */ $msg_headers = &new MIME_Headers(); $msg_headers->addMessageIdHeader(); $msg_headers->addAgentHeader($ua); $msg_headers->addHeader('Date', date('r')); $msg_headers->addHeader('From', $from_addr); $msg_headers->addHeader('To', $this->getMDNReturnAddr()); $msg_headers->addHeader('Subject', _("Disposition Notification")); /* MDNs are a subtype of 'multipart/report'. */ $msg = &new MIME_Message(); $msg->setType('multipart/report'); $msg->setContentTypeParameter('report-type', 'disposition-notification'); $charset = NLS::getCharset(); /* The first part is a human readable message. */ $part_one = &new MIME_Part('text/plain'); $part_one->setCharset($charset); if ($type == 'displayed') { $contents = sprintf(_("The message sent on %s to %s with subject \"%s\" has been displayed.\n\nThis is no guarantee that the message has been read or understood."), $this->_headers->getValue('Date'), $this->_headers->getValue('To'), $this->_headers->getValue('Subject')); require_once 'Text/Flowed.php'; $flowed = new Text_Flowed($contents, $charset); $flowed->setDelSp(true); $part_one->setContentTypeParameter('format', 'flowed'); $part_one->setContentTypeParameter('DelSp', 'Yes'); $part_one->setContents($flowed->toFlowed()); } // TODO: Messages for other notification types. $msg->addPart($part_one); /* The second part is a machine-parseable description. */ $part_two = &new MIME_Part('message/disposition-notification'); $part_two->setContents('Reporting-UA: ' . $GLOBALS['conf']['server']['name'] . '; ' . $ua . "\n"); if (!empty($orig_recip)) { $part_two->appendContents('Original-Recipient: rfc822;' . $orig_recip . "\n"); } $part_two->appendContents('Final-Recipient: rfc822;' . $from_addr . "\n"); if (!empty($msg_id)) { $part_two->appendContents('Original-Message-ID: rfc822;' . $msg_id . "\n"); } $part_two->appendContents($dispo . "\n"); if (in_array('error', $mod) && isset($err['error'])) { $part_two->appendContents('Error: ' . $err['error'] . "\n"); } $msg->addPart($part_two); /* The third part is the text of the original message. RFC 3798 [3] * allows us to return only a portion of the entire message - this * is left up to the user. */ $part_three = &new MIME_Part('message/rfc822'); $part_three->setContents($this->_headers->toString()); if (!empty($this->_msgtext)) { $part_three->appendContents("\n" . $this->_msgtext); } $msg->addPart($part_three); $msg_headers->addMIMEHeaders($msg); return $msg->send($to, $msg_headers); } /** * Add a MDN (read receipt) request headers to the MIME_Headers object. * * @param MIME_Headers &$headob The MIME_Headers object to add the headers * to. * @param string $to The address the receipt should be mailed * to. */ function addMDNRequestHeaders(&$headob, $to) { /* This is the RFC 3798 way of requesting a receipt. */ $headob->addHeader('Disposition-Notification-To', $to); } } * @author Michael Slusarz * @package Horde_MIME */ class MIME_Message extends MIME_Part { /** * Has the message been parsed via buildMessage()? * * @var boolean */ var $_build = false; /** * The server to default unqualified addresses to. * * @var string */ var $_defaultServer = null; /** * Constructor - creates a new MIME email message. * * @param string $defaultServer The server to default unqualified * addresses to. */ function MIME_Message($defaultServer = null) { if (is_null($defaultServer)) { $this->_defaultServer = $_SERVER['SERVER_NAME']; } else { $this->_defaultServer = $defaultServer; } } /** * Create a MIME_Message object from a MIME_Part object. * This function can be called statically via: * MIME_Message::convertMIMEPart(); * * @param MIME_Part &$mime_part The MIME_Part object. * @param string $server The server to default unqualified * addresses to. * * @return MIME_Message The new MIME_Message object. */ function &convertMIMEPart(&$mime_part, $server = null) { if (!$mime_part->getMIMEId()) { $mime_part->setMIMEId(1); } $mime_message = &new MIME_Message($server); $mime_message->addPart($mime_part); $mime_message->buildMessage(); return $mime_message; } /** * Sends this message. * * @param string $email The address list to send to. * @param mixed &$headers The MIME_Headers object holding this message's * headers, or a hash with header->value mappings. * @param string $driver The Mail driver to use (since Horde 3.0.4). * @param array $params Any parameters necessary for the Mail driver * (since Horde 3.0.4). * * @return mixed True on success, PEAR_Error on error. */ function send($email, &$headers, $driver = null, $params = array()) { global $conf; static $mailer; if (!isset($driver)) { $driver = $conf['mailer']['type']; $params = $conf['mailer']['params']; } if (!isset($mailer)) { require_once 'Mail.php'; $mailer = Mail::factory($driver, $params); } $msg = $this->toString(); if (is_object($headers)) { $headerArray = $this->encode($headers->toArray(), $this->getCharset()); } else { $headerArray = $this->encode($headers, $this->getCharset()); } /* Make sure the message has a trailing newline. */ if (substr($msg, -1) != "\n") { $msg .= "\n"; } $result = $mailer->send(MIME::encodeAddress($email), $headerArray, $msg); if (is_a($result, 'PEAR_Error') && $driver == 'sendmail') { $userinfo = $result->toString(); // Interpret return values as defined in /usr/include/sysexits.h switch ($result->getCode()) { case 64: // EX_USAGE $error = 'sendmail: ' . _("command line usage error") . ' (64)'; break; case 65: // EX_DATAERR $error = 'sendmail: ' . _("data format error") . ' (65)'; break; case 66: // EX_NOINPUT $error = 'sendmail: ' . _("cannot open input") . ' (66)'; break; case 67: // EX_NOUSER $error = 'sendmail: ' . _("addressee unknown") . ' (67)'; break; case 68: // EX_NOHOST $error = 'sendmail: ' . _("host name unknown") . ' (68)'; break; case 69: // EX_UNAVAILABLE $error = 'sendmail: ' . _("service unavailable") . ' (69)'; break; case 70: // EX_SOFTWARE $error = 'sendmail: ' . _("internal software error") . ' (70)'; break; case 71: // EX_OSERR $error = 'sendmail: ' . _("system error") . ' (71)'; break; case 72: // EX_OSFILE $error = 'sendmail: ' . _("critical system file missing") . ' (72)'; break; case 73: // EX_CANTCREAT $error = 'sendmail: ' . _("cannot create output file") . ' (73)'; break; case 74: // EX_IOERR $error = 'sendmail: ' . _("input/output error") . ' (74)'; break; case 75: // EX_TEMPFAIL $error = 'sendmail: ' . _("temporary failure") . ' (75)'; break; case 76: // EX_PROTOCOL $error = 'sendmail: ' . _("remote error in protocol") . ' (76)'; break; case 77: // EX_NOPERM $error = 'sendmail: ' . _("permission denied") . ' (77)'; break; case 78: // EX_CONFIG $error = 'sendmail: ' . _("configuration error") . ' (78)'; break; case 79: // EX_NOTFOUND $error = 'sendmail: ' . _("entry not found") . ' (79)'; break; default: $error = $result; $userinfo = null; } return PEAR::raiseError($error, null, null, null, $userinfo); } return $result; } /** * Take a set of headers and make sure they are encoded properly. * * @param array $headers The headers to encode. * @param string $charset The character set to use. * * @return array The array of encoded headers. */ function encode($headers, $charset) { require_once 'Horde/MIME.php'; $addressKeys = array('To', 'Cc', 'Bcc', 'From'); $asciikeys = array('MIME-Version', 'Received', 'Message-ID', 'Date', 'Content-Disposition', 'Content-Transfer-Encoding', 'Content-ID', 'Content-Type', 'Content-Description'); foreach ($headers as $key => $val) { if (is_array($val)) { foreach ($val as $key2 => $val2) { $headers[$key][$key2] = MIME::wrapHeaders($key, $val2, $this->getEOL()); } } else { if (in_array($key, $addressKeys)) { $text = MIME::encodeAddress($val, $charset, $this->_defaultServer); if (is_a($text, 'PEAR_Error')) { $text = $val; } } else { $text = MIME::encode($val, in_array($key, $asciikeys) ? 'US-ASCII' : $charset); } $headers[$key] = MIME::wrapHeaders($key, $text, $this->getEOL()); } } return $headers; } /** * Add the proper set of MIME headers for this message to an array. * * @param array $headers The headers to add the MIME headers to. * * @return array The full set of headers including MIME headers. */ function header($headers = array()) { /* Per RFC 2045 [4], this MUST appear in the message headers. */ $headers['MIME-Version'] = '1.0'; if ($this->_build) { return parent::header($headers); } else { $this->buildMessage(); return $this->encode($this->header($headers), $this->getCharset()); } } /** * Return the entire message contents, including headers, as a string. * * @return string The encoded, generated message. */ function toString() { if ($this->_build) { return parent::toString(false); } else { $this->buildMessage(); return $this->toString(); } } /** * Build message from current contents. */ function buildMessage() { if ($this->_build) { return; } if (empty($this->_flags['setType'])) { if (count($this->_parts) > 1) { $this->setType('multipart/mixed'); } else { /* Copy the information from the single part to the current base part. */ if (($obVars = get_object_vars(reset($this->_parts)))) { foreach ($obVars as $key => $val) { $this->$key = $val; } } } } /* Set the build flag now. */ $this->_build = true; } /** * Get a list of all MIME subparts. * * @return array An array of the MIME_Part subparts. */ function getParts() { if ($this->_build) { return parent::getParts(); } else { $this->buildMessage(); return $this->getParts(); } } /** * Return the base part of the message. This function does NOT * return a reference to make sure that the whole MIME_Message * object isn't accidentally modified. * * @return MIME_Message The base MIME_Part of the message. */ function getBasePart() { $this->buildMessage(); return $this; } /** * Retrieve a specific MIME part. * * @param string $id The MIME_Part ID string. * * @return MIME_Part The MIME_Part requested, or false if the part * doesn't exist. */ function &getPart($id) { if ($this->_build) { $part = parent::getPart($id); } else { $this->buildMessage(); $part = $this->getPart($id); } if (is_a($part, 'MIME_Message')) { $newpart = &new MIME_Part(); $skip = array('_build', '_defaultServer'); foreach (array_keys(get_object_vars($part)) as $key) { /* Ignore local variables that aren't a part of the original * class. */ if (!in_array($key, $skip)) { $newpart->$key = &$part->$key; } } return $newpart; } else { return $part; } } } nn__________E";s:16:"application/x-ar";s:19:"!n________64E";s:16:"application/data";s:19:"#!/usr/local/bin/ae";s:11:"text/script";}i:18;a:3:{s:18:"FiLeStArTfIlEsTaRt";s:20:"text/x-apple-binscii";s:18:"#! /usr/local/tcsh";s:17:"application/x-csh";s:18:"%!PS-AdobeFont-1.0";s:10:"font/type1";}i:17;a:1:{s:17:"#!/usr/local/tcsh";s:17:"application/x-csh";}i:16;a:10:{s:16:"Extended Module:";s:15:"audio/x-ft2-mod";s:16:"StartFontMetrics";s:17:"font/x-sunos-news";s:16:"#! /usr/bin/gawk";s:17:"application/x-awk";s:16:"#! /usr/bin/nawk";s:17:"application/x-awk";s:16:"#! /usr/bin/perl";s:18:"application/x-perl";s:16:"#! /usr/bin/gawk";s:17:"application/x-awk";s:16:"#! /usr/bin/nawk";s:17:"application/x-awk";s:16:"#! /usr/bin/perl";s:18:"application/x-perl";s:16:"ndebian";s:18:"application/x-dpkg";s:14:"#!/usr/bin/awk";s:17:"application/x-awk";s:14:"!n";s:18:"application/x-prof";s:8:"";s:16:"application/x-ar";s:7:": shell";s:16:"application/data";}i:6;a:8:{s:6:"NuFile";s:16:"application/data";s:6:"NFl";s:16:"application/data";s:6:"070701";s:18:"application/x-cpio";s:6:"070702";s:18:"application/x-cpio";s:6:"070707";s:18:"application/x-cpio";s:6:"";s:16:"application/x-ar";}i:4;a:114:{s:4:"";s:29:"application/x-executable-file";s:4:"";s:10:"font/x-snf";s:4:"G";s:25:"application/x-object-file";s:4:"K";s:29:"application/x-executable-file";s:4:"M";s:29:"application/x-executable-file";s:4:"O";s:29:"application/x-executable-file";s:4:"";s:25:"application/x-object-file";s:4:"";s:16:"application/data";s:4:"";s:10:"video/mpeg";s:4:"";s:10:"video/mpeg";s:4:"";s:29:"application/x-executable-file";s:4:"l";s:27:"application/x-apl-workspace";s:4:"e";s:26:"application/x-library-file";s:4:"m";s:16:"application/data";s:4:"";s:26:"application/x-library-file";s:4:"";s:29:"application/x-executable-file";s:4:"ds.";s:11:"audio/basic";s:4:"W";s:16:"application/core";s:4:"և";s:9:"image/x11";s:4:"";s:16:"application/data";s:4:"";s:16:"application/data";s:4:"";s:16:"application/data";s:4:"";s:16:"application/data";s:4:"1b";s:16:"application/x-db";s:4:"a";s:16:"application/x-db";s:4:"CTMF";s:11:"audio/x-cmf";s:4:"EMOD";s:12:"audio/x-emod";s:4:"FFIL";s:8:"font/ttf";s:4:"FONT";s:12:"font/x-vfont";s:4:"GDBM";s:18:"application/x-gdbm";s:4:"GIF8";s:9:"image/gif";s:4:" ";s:16:"application/data";s:4:"HPAK";s:16:"application/data";s:4:"IIN1";s:10:"image/tiff";s:4:"II*";s:10:"image/tiff";s:4:"LDHi";s:16:"application/data";s:4:"LWFN";s:10:"font/type1";s:4:"MM*";s:10:"image/tiff";s:4:"MOVI";s:17:"video/x-sgi-movie";s:4:"MThd";s:10:"audio/midi";s:4:"M";s:17:"font/x-hp-windows";s:4:"NTRK";s:18:"audio/x-multitrack";s:4:"PK";s:15:"application/zip";s:4:"RIFF";s:11:"audio/x-wav";s:4:"Rar!";s:17:"application/x-rar";s:4:"SQSH";s:16:"application/data";s:4:"TADS";s:23:"application/x-tads-game";s:4:"UC2";s:16:"application/data";s:4:"UN05";s:18:"audio/x-mikmod-uni";s:4:" ";s:16:"application/data";s:4:"Yj";s:20:"x/x-image-sun-raster";s:4:"e";s:16:"application/x-ar";s:4:"hsi1";s:24:"image/x-jpeg-proprietary";s:4:" ";s:16:"application/data";s:4:"ELF";s:29:"application/x-executable-file";s:4:" ";s:16:"application/data";s:4:"X!";s:16:"application/core";s:4:"fcp";s:10:"font/x-pcf";s:4:"PNG";s:11:"image/x-png";s:4:"W";s:18:"application/x-gdbm";s:4:"z)D";s:17:"font/x-sunos-news";s:4:"z)G";s:17:"font/x-sunos-news";s:4:"z)P";s:17:"font/x-sunos-news";s:4:"z)Q";s:17:"font/x-sunos-news";s:4:"Y";s:13:"font/x-libgrx";s:4:"13`";s:22:"application/x-bootable";s:4:"";s:29:"application/x-executable-file";s:4:"";s:29:"application/x-executable-file";s:4:"";s:29:"application/x-executable-file";s:4:"e";s:26:"application/x-library-file";s:4:" ";s:29:"application/x-executable-file";s:4:" ";s:29:"application/x-executable-file";s:4:" e";s:26:"application/x-library-file";s:4:" ";s:29:"application/x-executable-file";s:4:"  ";s:29:"application/x-executable-file";s:4:"  ";s:26:"application/x-library-file";s:4:" ";s:26:"application/x-library-file";s:4:" ";s:25:"application/x-object-file";s:4:" ";s:29:"application/x-executable-file";s:4:" ";s:29:"application/x-executable-file";s:4:"  ";s:29:"application/x-executable-file";s:4:"  ";s:18:"application/x-lisp";s:4:"  ";s:26:"application/x-library-file";s:4:" ";s:26:"application/x-library-file";s:4:" ";s:29:"application/x-executable-file";s:4:" ";s:29:"application/x-executable-file";s:4:" e";s:26:"application/x-library-file";s:4:"";s:29:"application/x-executable-file";s:4:" ";s:29:"application/x-executable-file";s:4:" ";s:26:"application/x-library-file";s:4:"";s:26:"application/x-library-file";s:4:"";s:25:"application/x-object-file";s:4:"";s:29:"application/x-executable-file";s:4:"";s:29:"application/x-executable-file";s:4:" ";s:29:"application/x-executable-file";s:4:" ";s:25:"application/x-object-file";s:4:"";s:26:"application/x-library-file";s:4:"";s:25:"application/x-object-file";s:4:"";s:29:"application/x-executable-file";s:4:"0@";s:18:"image/x-cmu-raster";s:4:"";s:23:"application/x-pc-floppy";s:4:"FON";s:10:"font/x-dos";s:4:"!";s:16:"application/x-ar";}i:3;a:18:{s:3:"BZh";s:19:"application/x-bzip2";s:3:"FAR";s:9:"audio/mod";s:3:"MTM";s:18:"audio/x-multitrack";s:3:"SBI";s:11:"audio/x-sbi";s:3:"TOC";s:11:"audio/x-toc";s:3:" GL";s:16:"application/data";s:3:"flc";s:18:"application/x-font";s:3:"flf";s:13:"font/x-figlet";s:3:"E";s:14:"image/x-pcl-hp";s:3:"c";s:16:"application/data";s:3:"|";s:16:"application/data";s:3:"~";s:16:"application/data";s:3:"";s:16:"application/data";s:3:"#! ";s:11:"text/script";s:3:"#!/";s:11:"text/script";s:3:"%!";s:22:"application/postscript";s:3:"-h-";s:16:"application/data";s:3:"1cw";s:16:"application/data";}i:2;a:62:{s:2:"";s:29:"application/x-executable-file";s:2:"BM";s:11:"image/x-bmp";s:2:"BZ";s:18:"application/x-bzip";s:2:"IC";s:11:"image/x-ico";s:2:"JN";s:15:"audio/x-669-mod";s:2:"MZ";s:31:"application/x-ms-dos-executable";s:2:"P1";s:23:"image/x-portable-bitmap";s:2:"P2";s:24:"image/x-portable-graymap";s:2:"P3";s:23:"image/x-portable-pixmap";s:2:"P4";s:23:"image/x-portable-bitmap";s:2:"P5";s:24:"image/x-portable-graymap";s:2:"P6";s:23:"image/x-portable-pixmap";s:2:"if";s:15:"audio/x-669-mod";s:2:"q";s:18:"application/x-cpio";s:2:"v";s:16:"application/data";s:2:"H";s:29:"application/x-executable-file";s:2:"I";s:29:"application/x-executable-file";s:2:"T";s:16:"application/data";s:2:"U";s:29:"application/x-executable-file";s:2:"p";s:29:"application/x-executable-file";s:2:"q";s:29:"application/x-executable-file";s:2:"}";s:29:"application/x-executable-file";s:2:"";s:29:"application/x-executable-file";s:2:"";s:29:"application/x-executable-file";s:2:"";s:29:"application/x-executable-file";s:2:"";s:29:"application/x-executable-file";s:2:"";s:29:"application/x-executable-file";s:2:"";s:25:"application/x-object-file";s:2:"";s:29:"application/x-executable-file";s:2:"";s:29:"application/x-executable-file";s:2:"";s:13:"x/x-image-sgi";s:2:"";s:12:"font/x-vfont";s:2:"";s:29:"application/x-executable-file";s:2:"q";s:19:"application/x-bcpio";s:2:"";s:16:"application/data";s:2:"`";s:17:"application/x-arj";s:2:"Y";s:10:"font/x-tex";s:2:"";s:10:"font/x-tex";s:2:"";s:10:"font/x-tex";s:2:"";s:12:"font/x-vfont";s:2:"v";s:17:"application/x-lzh";s:2:"v";s:16:"application/data";s:2:"e";s:16:"application/data";s:2:"m";s:16:"application/data";s:2:"v";s:16:"application/data";s:2:"";s:10:"image/jpeg";s:2:"";s:16:"application/data";s:2:"";s:18:"application/x-gzip";s:2:"";s:20:"application/compress";s:2:"";s:16:"application/data";s:2:"";s:16:"application/data";s:2:"";s:16:"application/data";s:2:"";s:16:"application/data";s:2:"";s:16:"application/data";s:2:"";s:16:"application/data";s:2:"%!";s:22:"application/postscript";s:2:"6";s:14:"font/linux-psf";s:2:"//";s:8:"text/cpp";s:2:"";s:20:"application/x-locale";s:2:"";s:29:"application/x-executable-file";s:2:"";s:33:"application/x-alan-adventure-game";s:2:"";s:29:"application/x-executable-file";}}i:1;a:1:{i:3;a:2:{s:3:"PNG";s:11:"image/x-png";s:3:"WPC";s:27:"application/vnd.wordperfect";}}i:2;a:3:{i:6;a:1:{s:6:"-lh40-";s:17:"application/x-lha";}i:5;a:10:{s:5:"-lhd-";s:17:"application/x-lha";s:5:"-lh0-";s:17:"application/x-lha";s:5:"-lh1-";s:17:"application/x-lha";s:5:"-lh2-";s:17:"application/x-lha";s:5:"-lh3-";s:17:"application/x-lha";s:5:"-lh4-";s:17:"application/x-lha";s:5:"-lh5-";s:17:"application/x-lha";s:5:"-lzs-";s:17:"application/x-lha";s:5:"-lz4-";s:17:"application/x-lha";s:5:"-lz5-";s:17:"application/x-lha";}i:2;a:2:{s:2:"";s:14:"font/x-tex-tfm";s:2:"";s:14:"font/x-tex-tfm";}}i:4;a:2:{i:4;a:4:{s:4:"mdat";s:15:"video/quicktime";s:4:"moov";s:15:"video/quicktime";s:4:"pipe";s:16:"application/data";s:4:"prof";s:16:"application/data";}i:2;a:2:{s:2:"";s:9:"video/fli";s:2:"";s:9:"video/flc";}}i:6;a:1:{i:18;a:1:{s:18:"%!PS-AdobeFont-1.0";s:10:"font/type1";}}i:7;a:2:{i:22;a:1:{s:22:"00000000000000000000";s:16:"application/core";}i:4;a:2:{s:4:"EGA";s:10:"font/x-dos";s:4:"VID";s:10:"font/x-dos";}}i:8;a:1:{i:4;a:2:{s:4:"z+E";s:17:"font/x-sunos-news";s:4:"z+H";s:17:"font/x-sunos-news";}}i:10;a:1:{i:25;a:1:{s:25:"# This is a shell archive";s:18:"application/x-shar";}}i:20;a:1:{i:4;a:3:{s:4:"GIMP";s:24:"application/x-gimp-brush";s:4:"GPAT";s:26:"application/x-gimp-pattern";s:4:"ħ";s:17:"application/x-zoo";}}i:21;a:1:{i:8;a:1:{s:8:"!SCREAM!";s:15:"audio/x-st2-mod";}}i:24;a:1:{i:4;a:4:{s:4:"k";s:18:"application/x-dump";s:4:"l";s:18:"application/x-dump";s:4:"m";s:16:"application/data";s:4:"n";s:16:"application/data";}}i:65;a:1:{i:4;a:2:{s:4:"FFIL";s:8:"font/ttf";s:4:"LWFN";s:10:"font/type1";}}i:257;a:2:{i:8;a:1:{s:8:"ustar 0";s:18:"application/x-gtar";}i:6;a:1:{s:6:"ustar0";s:17:"application/x-tar";}}i:508;a:1:{i:2;a:1:{s:2:"ھ";s:16:"application/data";}}i:1080;a:1:{i:4;a:10:{s:4:"CD81";s:21:"audio/x-oktalyzer-mod";s:4:"FLT4";s:23:"audio/x-startracker-mod";s:4:"M!K!";s:22:"audio/x-protracker-mod";s:4:"M.K.";s:22:"audio/x-protracker-mod";s:4:"OKTA";s:21:"audio/x-oktalyzer-mod";s:4:"16CN";s:23:"audio/x-taketracker-mod";s:4:"32CN";s:23:"audio/x-taketracker-mod";s:4:"4CHN";s:23:"audio/x-fasttracker-mod";s:4:"6CHN";s:23:"audio/x-fasttracker-mod";s:4:"8CHN";s:23:"audio/x-fasttracker-mod";}}i:2048;a:1:{i:7;a:1:{s:7:"PCD_IPI";s:22:"x/x-photo-cd-pack-file";}}i:2080;a:3:{i:29;a:1:{s:29:"Microsoft Excel 5.0 Worksheet";s:24:"application/vnd.ms-excel";}i:27;a:1:{s:27:"Microsoft Word 6.0 Document";s:16:"text/vnd.ms-word";}i:26;a:1:{s:26:"Documento Microsoft Word 6";s:16:"text/vnd.ms-word";}}i:2112;a:1:{i:9;a:1:{s:9:"MSWordDoc";s:16:"text/vnd.ms-word";}}i:2114;a:1:{i:5;a:1:{s:5:"Biff5";s:24:"application/vnd.ms-excel";}}i:4098;a:1:{i:7;a:1:{s:7:"DOSFONT";s:10:"font/x-dos";}}i:68158480;a:1:{i:2;a:4:{s:2:"";s:24:"application/x-filesystem";s:2:"";s:24:"application/x-filesystem";s:2:"$h";s:24:"application/x-filesystem";s:2:"$x";s:24:"application/x-filesystem";}}i:70779960;a:1:{i:2;a:1:{s:2:"S";s:26:"application/x-linux-ext2fs";}}}'); INDX( _w(HpZiic%rTwqG Contents.phphXiic%sXS Headers.phphTiic%̗ow(K! Magic.phphRiic%̗ow@: Mail.php`Piic%w($ MDN.phphXiic%L.x0o* Message.phpp^iic%h|xHqB mime.magic.phpxbiic%h|xǮ mime.mapping.phppZiic%h|xHqB MIMEMA~1.PPpZiic%h|xǮ MIMEMA~2.PHPhRiic%y Part.php p\iic%X|@8> Structure.php pZiic%X|@8> STRUCT~1.PHP!`Nc%c%c%c%ViewerNhViinO%~@> Viewer.php' where is the unknown file extension. * * @package Horde_MIME * * $Horde: framework/MIME/MIME/mime.mapping.php,v 1.11.2.7 2010-09-21 13:32:10 jan Exp $ * * Generated: 11/05/08 23:30:23 by chuck on technest.org */ $mime_extension_map = array( '__MAXPERIOD__' => '1', 'ez' => 'application/andrew-inset', 'atom' => 'application/atom+xml', 'atomcat' => 'application/atomcat+xml', 'atomsvc' => 'application/atomsvc+xml', 'ccxml' => 'application/ccxml+xml', 'davmount' => 'application/davmount+xml', 'ecma' => 'application/ecmascript', 'pfr' => 'application/font-tdpfr', 'stk' => 'application/hyperstudio', 'js' => 'application/x-javascript', 'json' => 'application/json', 'hqx' => 'application/mac-binhex40', 'cpt' => 'application/mac-compactpro', 'mrc' => 'application/marc', 'ma' => 'application/mathematica', 'nb' => 'application/mathematica', 'mb' => 'application/mathematica', 'mathml' => 'application/mathml+xml', 'mbox' => 'application/mbox', 'mscml' => 'application/mediaservercontrol+xml', 'mp4s' => 'application/mp4', 'doc' => 'application/msword', 'dot' => 'application/msword', 'mxf' => 'application/mxf', 'bin' => 'application/octet-stream', 'dms' => 'application/octet-stream', 'lha' => 'application/x-lha', 'lzh' => 'application/x-lha', 'class' => 'application/x-java', 'so' => 'application/x-sharedlib', 'iso' => 'application/x-cd-image', 'dmg' => 'application/octet-stream', 'dist' => 'application/octet-stream', 'distz' => 'application/octet-stream', 'pkg' => 'application/octet-stream', 'bpk' => 'application/octet-stream', 'dump' => 'application/octet-stream', 'elc' => 'application/octet-stream', 'oda' => 'application/oda', 'ogg' => 'application/ogg', 'pdf' => 'application/pdf', 'pgp' => 'application/pgp', 'asc' => 'text/plain', 'sig' => 'application/pgp-signature', 'prf' => 'application/pics-rules', 'p10' => 'application/pkcs10', 'p7m' => 'application/pkcs7-mime', 'p7c' => 'application/pkcs7-mime', 'p7s' => 'application/pkcs7-signature', 'cer' => 'application/x-x509-ca-cert', 'crl' => 'application/pkix-crl', 'pkipath' => 'application/pkix-pkipath', 'pki' => 'application/pkixcmp', 'pls' => 'audio/x-scpls', 'ai' => 'application/illustrator', 'eps' => 'image/x-eps', 'ps' => 'application/postscript', 'cww' => 'application/prs.cww', 'rdf' => 'text/rdf', 'rif' => 'application/reginfo+xml', 'rnc' => 'application/relax-ng-compact-syntax', 'rl' => 'application/resource-lists+xml', 'rs' => 'application/rls-services+xml', 'rsd' => 'application/rsd+xml', 'rss' => 'text/rss', 'rtf' => 'application/rtf', 'sbml' => 'application/sbml+xml', 'scq' => 'application/scvp-cv-request', 'scs' => 'application/scvp-cv-response', 'spq' => 'application/scvp-vp-request', 'spp' => 'application/scvp-vp-response', 'sdp' => 'application/vnd.stardivision.impress', 'setpay' => 'application/set-payment-initiation', 'setreg' => 'application/set-registration-initiation', 'shf' => 'application/shf+xml', 'smi' => 'application/smil', 'smil' => 'application/smil', 'rq' => 'application/sparql-query', 'srx' => 'application/sparql-results+xml', 'gram' => 'application/srgs', 'grxml' => 'application/srgs+xml', 'ssml' => 'application/ssml+xml', 'plb' => 'application/vnd.3gpp.pic-bw-large', 'psb' => 'application/vnd.3gpp.pic-bw-small', 'pvb' => 'application/vnd.3gpp.pic-bw-var', 'tcap' => 'application/vnd.3gpp2.tcap', 'pwn' => 'application/vnd.3m.post-it-notes', 'aso' => 'application/vnd.accpac.simply.aso', 'imp' => 'application/vnd.accpac.simply.imp', 'acu' => 'application/vnd.acucobol', 'atc' => 'application/vnd.acucorp', 'acutc' => 'application/vnd.acucorp', 'xdp' => 'application/vnd.adobe.xdp+xml', 'xfdf' => 'application/vnd.adobe.xfdf', 'ami' => 'application/vnd.amiga.ami', 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', 'atx' => 'application/vnd.antix.game-component', 'mpkg' => 'application/vnd.apple.installer+xml', 'aep' => 'application/vnd.audiograph', 'mpm' => 'application/vnd.blueice.multipass', 'bmi' => 'application/vnd.bmi', 'rep' => 'application/vnd.businessobjects', 'cdxml' => 'application/vnd.chemdraw+xml', 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', 'cdy' => 'application/vnd.cinderella', 'cla' => 'application/vnd.claymore', 'c4g' => 'application/vnd.clonk.c4group', 'c4d' => 'application/vnd.clonk.c4group', 'c4f' => 'application/vnd.clonk.c4group', 'c4p' => 'application/vnd.clonk.c4group', 'c4u' => 'application/vnd.clonk.c4group', 'csp' => 'application/vnd.commonspace', 'cst' => 'application/vnd.commonspace', 'cdbcmsg' => 'application/vnd.contact.cmsg', 'cmc' => 'application/vnd.cosmocaller', 'clkx' => 'application/vnd.crick.clicker', 'clkk' => 'application/vnd.crick.clicker.keyboard', 'clkp' => 'application/vnd.crick.clicker.palette', 'clkt' => 'application/vnd.crick.clicker.template', 'clkw' => 'application/vnd.crick.clicker.wordbank', 'wbs' => 'application/vnd.criticaltools.wbs+xml', 'pml' => 'application/vnd.ctc-posml', 'ppd' => 'application/vnd.cups-ppd', 'curl' => 'application/vnd.curl', 'rdz' => 'application/vnd.data-vision.rdz', 'fe_launch' => 'application/vnd.denovo.fcselayout-link', 'dna' => 'application/vnd.dna', 'mlp' => 'application/vnd.dolby.mlp', 'dpg' => 'application/vnd.dpgraph', 'dfac' => 'application/vnd.dreamfactory', 'mag' => 'application/vnd.ecowin.chart', 'nml' => 'application/vnd.enliven', 'esf' => 'application/vnd.epson.esf', 'msf' => 'application/vnd.epson.msf', 'qam' => 'application/vnd.epson.quickanime', 'slt' => 'application/vnd.epson.salt', 'ssf' => 'application/vnd.epson.ssf', 'es3' => 'application/vnd.eszigno3+xml', 'et3' => 'application/vnd.eszigno3+xml', 'ez2' => 'application/vnd.ezpix-album', 'ez3' => 'application/vnd.ezpix-package', 'fdf' => 'application/vnd.fdf', 'gph' => 'application/vnd.flographit', 'ftc' => 'application/vnd.fluxtime.clip', 'fm' => 'application/vnd.framemaker', 'frame' => 'application/vnd.framemaker', 'maker' => 'application/vnd.framemaker', 'fnc' => 'application/vnd.frogans.fnc', 'ltf' => 'application/vnd.frogans.ltf', 'fsc' => 'application/vnd.fsc.weblaunch', 'oas' => 'application/vnd.fujitsu.oasys', 'oa2' => 'application/vnd.fujitsu.oasys2', 'oa3' => 'application/vnd.fujitsu.oasys3', 'fg5' => 'application/vnd.fujitsu.oasysgp', 'bh2' => 'application/vnd.fujitsu.oasysprs', 'ddd' => 'application/vnd.fujixerox.ddd', 'xdw' => 'application/vnd.fujixerox.docuworks', 'xbd' => 'application/vnd.fujixerox.docuworks.binder', 'fzs' => 'application/vnd.fuzzysheet', 'txd' => 'application/vnd.genomatix.tuxedo', 'kml' => 'application/vnd.google-earth.kml+xml', 'kmz' => 'application/vnd.google-earth.kmz', 'gqf' => 'application/vnd.grafeq', 'gqs' => 'application/vnd.grafeq', 'gac' => 'application/vnd.groove-account', 'ghf' => 'application/vnd.groove-help', 'gim' => 'application/vnd.groove-identity-message', 'grv' => 'application/vnd.groove-injector', 'gtm' => 'application/vnd.groove-tool-message', 'tpl' => 'application/vnd.groove-tool-template', 'vcg' => 'application/vnd.groove-vcard', 'zmm' => 'application/vnd.handheld-entertainment+xml', 'hbci' => 'application/vnd.hbci', 'les' => 'application/vnd.hhe.lesson-player', 'hpgl' => 'application/vnd.hp-hpgl', 'hpid' => 'application/vnd.hp-hpid', 'hps' => 'application/vnd.hp-hps', 'jlt' => 'application/vnd.hp-jlyt', 'pcl' => 'application/vnd.hp-pcl', 'pclxl' => 'application/vnd.hp-pclxl', 'x3d' => 'application/vnd.hzn-3d-crossword', 'mpy' => 'application/vnd.ibm.minipay', 'afp' => 'application/vnd.ibm.modcap', 'listafp' => 'application/vnd.ibm.modcap', 'list3820' => 'application/vnd.ibm.modcap', 'irm' => 'application/vnd.ibm.rights-management', 'sc' => 'application/vnd.ibm.secure-container', 'igl' => 'application/vnd.igloader', 'ivp' => 'application/vnd.immervision-ivp', 'ivu' => 'application/vnd.immervision-ivu', 'xpw' => 'application/vnd.intercon.formnet', 'xpx' => 'application/vnd.intercon.formnet', 'qbo' => 'application/vnd.intu.qbo', 'qfx' => 'application/vnd.intu.qfx', 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', 'irp' => 'application/vnd.irepository.package+xml', 'xpr' => 'application/vnd.is-xpr', 'jam' => 'application/vnd.jam', 'rms' => 'application/vnd.jcp.javame.midlet-rms', 'jisp' => 'application/vnd.jisp', 'joda' => 'application/vnd.joost.joda-archive', 'ktz' => 'application/vnd.kahootz', 'ktr' => 'application/vnd.kahootz', 'karbon' => 'application/x-karbon', 'chrt' => 'application/x-kchart', 'kfo' => 'application/x-kformula', 'flw' => 'application/x-kivio', 'kon' => 'application/x-kontour', 'kpr' => 'application/x-kpresenter', 'kpt' => 'application/x-kpresenter', 'ksp' => 'application/x-kspread', 'kwd' => 'application/x-kword', 'kwt' => 'application/x-kword', 'htke' => 'application/vnd.kenameaapp', 'kia' => 'application/vnd.kidspiration', 'kne' => 'application/vnd.kinar', 'knp' => 'application/vnd.kinar', 'skp' => 'application/vnd.koan', 'skd' => 'application/vnd.koan', 'skt' => 'application/vnd.koan', 'skm' => 'application/vnd.koan', 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', '123' => 'application/vnd.lotus-1-2-3', 'apr' => 'application/vnd.lotus-approach', 'pre' => 'application/vnd.lotus-freelance', 'nsf' => 'application/vnd.lotus-notes', 'org' => 'application/vnd.lotus-organizer', 'scm' => 'text/x-scheme', 'lwp' => 'application/vnd.lotus-wordpro', 'portpkg' => 'application/vnd.macports.portpkg', 'mcd' => 'application/vnd.mcd', 'mc1' => 'application/vnd.medcalcdata', 'cdkey' => 'application/vnd.mediastation.cdkey', 'mwf' => 'application/vnd.mfer', 'mfm' => 'application/vnd.mfmp', 'flo' => 'application/vnd.micrografx.flo', 'igx' => 'application/vnd.micrografx.igx', 'mif' => 'application/x-mif', 'daf' => 'application/vnd.mobius.daf', 'dis' => 'application/vnd.mobius.dis', 'mbk' => 'application/vnd.mobius.mbk', 'mqy' => 'application/vnd.mobius.mqy', 'msl' => 'application/vnd.mobius.msl', 'plc' => 'application/vnd.mobius.plc', 'txf' => 'application/vnd.mobius.txf', 'mpn' => 'application/vnd.mophun.application', 'mpc' => 'application/vnd.mophun.certificate', 'xul' => 'application/vnd.mozilla.xul+xml', 'cil' => 'application/vnd.ms-artgalry', 'asf' => 'video/x-ms-asf', 'cab' => 'application/vnd.ms-cab-compressed', 'xls' => 'application/vnd.ms-excel', 'xlm' => 'application/vnd.ms-excel', 'xla' => 'application/vnd.ms-excel', 'xlc' => 'application/vnd.ms-excel', 'xlt' => 'application/vnd.ms-excel', 'xlw' => 'application/vnd.ms-excel', 'eot' => 'application/vnd.ms-fontobject', 'chm' => 'application/x-chm', 'ims' => 'application/vnd.ms-ims', 'lrm' => 'application/vnd.ms-lrm', 'ppt' => 'application/vnd.ms-powerpoint', 'pps' => 'application/vnd.ms-powerpoint', 'pot' => 'text/x-gettext-translation-template', 'mpp' => 'application/vnd.ms-project', 'mpt' => 'application/vnd.ms-project', 'wps' => 'application/vnd.ms-works', 'wks' => 'application/vnd.lotus-1-2-3', 'wcm' => 'application/vnd.ms-works', 'wdb' => 'application/vnd.ms-works', 'wpl' => 'application/vnd.ms-wpl', 'xps' => 'application/vnd.ms-xpsdocument', 'mseq' => 'application/vnd.mseq', 'mus' => 'application/vnd.musician', 'msty' => 'application/vnd.muvee.style', 'nlu' => 'application/vnd.neurolanguage.nlu', 'nnd' => 'application/vnd.noblenet-directory', 'nns' => 'application/vnd.noblenet-sealer', 'nnw' => 'application/vnd.noblenet-web', 'ngdat' => 'application/vnd.nokia.n-gage.data', 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', 'rpst' => 'application/vnd.nokia.radio-preset', 'rpss' => 'application/vnd.nokia.radio-presets', 'edm' => 'application/vnd.novadigm.edm', 'edx' => 'application/vnd.novadigm.edx', 'ext' => 'application/vnd.novadigm.ext', 'odc' => 'application/vnd.oasis.opendocument.chart', 'otc' => 'application/vnd.oasis.opendocument.chart-template', 'odf' => 'application/vnd.oasis.opendocument.formula', 'otf' => 'application/vnd.oasis.opendocument.formula-template', 'odg' => 'application/vnd.oasis.opendocument.graphics', 'otg' => 'application/vnd.oasis.opendocument.graphics-template', 'odi' => 'application/vnd.oasis.opendocument.image', 'oti' => 'application/vnd.oasis.opendocument.image-template', 'odp' => 'application/vnd.oasis.opendocument.presentation', 'otp' => 'application/vnd.oasis.opendocument.presentation-template', 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', 'odt' => 'application/vnd.oasis.opendocument.text', 'otm' => 'application/vnd.oasis.opendocument.text-master', 'ott' => 'application/vnd.oasis.opendocument.text-template', 'oth' => 'application/vnd.oasis.opendocument.text-web', 'xo' => 'application/vnd.olpc-sugar', 'dd2' => 'application/vnd.oma.dd2+xml', 'oxt' => 'application/vnd.openofficeorg.extension', 'dp' => 'application/vnd.osgi.dp', 'prc' => 'application/vnd.palm', 'pdb' => 'application/vnd.palm', 'pqa' => 'application/vnd.palm', 'oprc' => 'application/vnd.palm', 'str' => 'application/vnd.pg.format', 'ei6' => 'application/vnd.pg.osasli', 'efif' => 'application/vnd.picsel', 'plf' => 'application/vnd.pocketlearn', 'pbd' => 'application/vnd.powerbuilder6', 'box' => 'application/vnd.previewsystems.box', 'mgz' => 'application/vnd.proteus.magazine', 'qps' => 'application/vnd.publishare-delta-tree', 'ptid' => 'application/vnd.pvi.ptid1', 'qxd' => 'application/vnd.quark.quarkxpress', 'qxt' => 'application/vnd.quark.quarkxpress', 'qwd' => 'application/vnd.quark.quarkxpress', 'qwt' => 'application/vnd.quark.quarkxpress', 'qxl' => 'application/vnd.quark.quarkxpress', 'qxb' => 'application/vnd.quark.quarkxpress', 'mxl' => 'application/vnd.recordare.musicxml', 'rm' => 'audio/x-pn-realaudio', 'see' => 'application/vnd.seemail', 'sema' => 'application/vnd.sema', 'semd' => 'application/vnd.semd', 'semf' => 'application/vnd.semf', 'ifm' => 'application/vnd.shana.informed.formdata', 'itp' => 'application/vnd.shana.informed.formtemplate', 'iif' => 'application/vnd.shana.informed.interchange', 'ipk' => 'application/vnd.shana.informed.package', 'twd' => 'application/vnd.simtech-mindmapper', 'twds' => 'application/vnd.simtech-mindmapper', 'mmf' => 'application/vnd.smaf', 'sdkm' => 'application/vnd.solent.sdkm+xml', 'sdkd' => 'application/vnd.solent.sdkm+xml', 'dxp' => 'application/vnd.spotfire.dxp', 'sfs' => 'application/vnd.spotfire.sfs', 'sus' => 'application/vnd.sus-calendar', 'susp' => 'application/vnd.sus-calendar', 'svd' => 'application/vnd.svd', 'xsm' => 'application/vnd.syncml+xml', 'bdm' => 'application/vnd.syncml.dm+wbxml', 'xdm' => 'application/vnd.syncml.dm+xml', 'tao' => 'application/vnd.tao.intent-module-archive', 'tmo' => 'application/vnd.tmobile-livetv', 'tpt' => 'application/vnd.trid.tpt', 'mxs' => 'application/vnd.triscape.mxs', 'tra' => 'application/vnd.trueapp', 'ufd' => 'application/vnd.ufdl', 'ufdl' => 'application/vnd.ufdl', 'utz' => 'application/vnd.uiq.theme', 'umj' => 'application/vnd.umajin', 'unityweb' => 'application/vnd.unity', 'uoml' => 'application/vnd.uoml+xml', 'vcx' => 'application/vnd.vcx', 'vsd' => 'application/vnd.visio', 'vst' => 'application/vnd.visio', 'vss' => 'application/vnd.visio', 'vsw' => 'application/vnd.visio', 'vis' => 'application/vnd.visionary', 'vsf' => 'application/vnd.vsf', 'wbxml' => 'application/vnd.wap.wbxml', 'wmlc' => 'application/vnd.wap.wmlc', 'wmlsc' => 'application/vnd.wap.wmlscriptc', 'wtb' => 'application/vnd.webturbo', 'wpd' => 'application/vnd.wordperfect', 'wqd' => 'application/vnd.wqd', 'stf' => 'application/vnd.wt.stf', 'xar' => 'application/vnd.xara', 'xfdl' => 'application/vnd.xfdl', 'hvd' => 'application/vnd.yamaha.hv-dic', 'hvs' => 'application/vnd.yamaha.hv-script', 'hvp' => 'application/vnd.yamaha.hv-voice', 'saf' => 'application/vnd.yamaha.smaf-audio', 'spf' => 'application/vnd.yamaha.smaf-phrase', 'cmp' => 'application/vnd.yellowriver-custom-menu', 'zaz' => 'application/vnd.zzazz.deck+xml', 'vxml' => 'application/voicexml+xml', 'hlp' => 'application/winhlp', 'wsdl' => 'application/wsdl+xml', 'wspolicy' => 'application/wspolicy+xml', 'ace' => 'application/x-ace-compressed', 'bcpio' => 'application/x-bcpio', 'torrent' => 'application/x-bittorrent', 'bz' => 'application/x-bzip', 'bz2' => 'application/x-bzip', 'boz' => 'application/x-bzip2', 'vcd' => 'application/x-cdlink', 'chat' => 'application/x-chat', 'pgn' => 'application/x-chess-pgn', 'cpio' => 'application/x-cpio', 'csh' => 'application/x-csh', 'dcr' => 'application/x-director', 'dir' => 'application/x-director', 'dxr' => 'application/x-director', 'fgd' => 'application/x-director', 'dvi' => 'application/x-dvi', 'spl' => 'application/x-futuresplash', 'gtar' => 'application/x-gtar', 'hdf' => 'application/x-hdf', 'latex' => 'application/x-latex', 'wmd' => 'application/x-ms-wmd', 'wmz' => 'application/x-ms-wmz', 'mdb' => 'application/x-msaccess', 'obd' => 'application/x-msbinder', 'crd' => 'application/x-mscardfile', 'clp' => 'application/x-msclip', 'exe' => 'application/x-ms-dos-executable', 'dll' => 'application/x-msdownload', 'com' => 'application/x-msdownload', 'bat' => 'application/x-msdownload', 'msi' => 'application/x-msdownload', 'mvb' => 'application/x-msmediaview', 'm13' => 'application/x-msmediaview', 'm14' => 'application/x-msmediaview', 'wmf' => 'image/x-wmf', 'mny' => 'application/x-msmoney', 'pub' => 'application/x-mspublisher', 'scd' => 'application/x-msschedule', 'trm' => 'application/x-msterminal', 'wri' => 'application/x-mswrite', 'nc' => 'application/x-netcdf', 'cdf' => 'application/x-netcdf', 'p12' => 'application/x-pkcs12', 'pfx' => 'application/x-pkcs12', 'p7b' => 'application/x-pkcs7-certificates', 'spc' => 'application/x-pkcs7-certificates', 'p7r' => 'application/x-pkcs7-certreqresp', 'rar' => 'application/x-rar', 'sh' => 'application/x-shellscript', 'shar' => 'application/x-shar', 'swf' => 'application/x-shockwave-flash', 'sit' => 'application/stuffit', 'sitx' => 'application/x-stuffitx', 'sv4cpio' => 'application/x-sv4cpio', 'sv4crc' => 'application/x-sv4crc', 'tar' => 'application/x-tar', 'tcl' => 'text/x-tcl', 'tex' => 'text/x-tex', 'texinfo' => 'text/x-texinfo', 'texi' => 'text/x-texinfo', 'ustar' => 'application/x-ustar', 'src' => 'application/x-wais-source', 'der' => 'application/x-x509-ca-cert', 'crt' => 'application/x-x509-ca-cert', 'xenc' => 'application/xenc+xml', 'xhtml' => 'application/xhtml+xml', 'xht' => 'application/xhtml+xml', 'xml' => 'text/xml', 'xsl' => 'text/x-xslt', 'dtd' => 'text/x-dtd', 'xop' => 'application/xop+xml', 'xslt' => 'text/x-xslt', 'xspf' => 'application/xspf+xml', 'mxml' => 'application/xv+xml', 'xhvml' => 'application/xv+xml', 'xvml' => 'application/xv+xml', 'xvm' => 'application/xv+xml', 'zip' => 'application/zip', 'au' => 'audio/basic', 'snd' => 'audio/basic', 'mid' => 'audio/midi', 'midi' => 'audio/midi', 'kar' => 'audio/midi', 'rmi' => 'audio/midi', 'mp4a' => 'audio/mp4', 'mpga' => 'audio/mpeg', 'mp2' => 'video/mpeg', 'mp2a' => 'audio/mpeg', 'mp3' => 'audio/mpeg', 'm2a' => 'audio/mpeg', 'm3a' => 'audio/mpeg', 'eol' => 'audio/vnd.digital-winds', 'lvp' => 'audio/vnd.lucent.voice', 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', 'wav' => 'audio/x-wav', 'aif' => 'audio/x-aiff', 'aiff' => 'audio/x-aiff', 'aifc' => 'audio/x-aiff', 'm3u' => 'audio/x-mpegurl', 'wax' => 'audio/x-ms-wax', 'wma' => 'audio/x-ms-wma', 'ram' => 'audio/x-pn-realaudio', 'ra' => 'audio/x-pn-realaudio', 'rmp' => 'audio/x-pn-realaudio-plugin', 'cdx' => 'chemical/x-cdx', 'cif' => 'chemical/x-cif', 'cmdf' => 'chemical/x-cmdf', 'cml' => 'chemical/x-cml', 'csml' => 'chemical/x-csml', 'xyz' => 'chemical/x-xyz', 'bmp' => 'image/bmp', 'cgm' => 'image/cgm', 'g3' => 'image/fax-g3', 'gif' => 'image/gif', 'ief' => 'image/ief', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'jpe' => 'image/jpeg', 'png' => 'image/png', 'btif' => 'image/prs.btif', 'svg' => 'image/svg+xml', 'svgz' => 'image/svg+xml', 'tiff' => 'image/tiff', 'tif' => 'image/tiff', 'psd' => 'image/x-psd', 'djvu' => 'image/vnd.djvu', 'djv' => 'image/vnd.djvu', 'dwg' => 'image/vnd.dwg', 'dxf' => 'image/vnd.dxf', 'fbs' => 'image/vnd.fastbidsheet', 'fpx' => 'image/vnd.fpx', 'fst' => 'image/vnd.fst', 'mmr' => 'image/vnd.fujixerox.edmics-mmr', 'rlc' => 'image/vnd.fujixerox.edmics-rlc', 'mdi' => 'image/vnd.ms-modi', 'npx' => 'image/vnd.net-fpx', 'wbmp' => 'image/vnd.wap.wbmp', 'xif' => 'image/vnd.xiff', 'ras' => 'image/x-cmu-raster', 'cmx' => 'image/x-cmx', 'ico' => 'image/x-ico', 'pcx' => 'image/x-pcx', 'pic' => 'image/x-pict', 'pct' => 'image/x-pict', 'pnm' => 'image/x-portable-anymap', 'pbm' => 'image/x-portable-bitmap', 'pgm' => 'image/x-portable-graymap', 'ppm' => 'image/x-portable-pixmap', 'rgb' => 'image/x-rgb', 'xbm' => 'image/x-xbitmap', 'xpm' => 'image/x-xpixmap', 'xwd' => 'image/x-xwindowdump', 'eml' => 'message/rfc822', 'mime' => 'message/rfc822', 'igs' => 'model/iges', 'iges' => 'model/iges', 'msh' => 'model/mesh', 'mesh' => 'model/mesh', 'silo' => 'model/mesh', 'dwf' => 'model/vnd.dwf', 'gdl' => 'model/vnd.gdl', 'gtw' => 'model/vnd.gtw', 'mts' => 'model/vnd.mts', 'vtu' => 'model/vnd.vtu', 'wrl' => 'model/vrml', 'vrml' => 'model/vrml', 'ics' => 'text/calendar', 'ifb' => 'text/calendar', 'css' => 'text/css', 'csv' => 'text/x-comma-separated-values', 'html' => 'text/html', 'htm' => 'text/html', 'txt' => 'text/plain', 'text' => 'text/plain', 'conf' => 'text/plain', 'def' => 'text/plain', 'list' => 'text/plain', 'log' => 'text/x-log', 'in' => 'text/plain', 'dsc' => 'text/prs.lines.tag', 'rtx' => 'text/richtext', 'sgml' => 'text/sgml', 'sgm' => 'text/sgml', 'tsv' => 'text/tab-separated-values', 't' => 'application/x-troff', 'tr' => 'application/x-troff', 'roff' => 'application/x-troff', 'man' => 'application/x-troff-man', 'me' => 'text/x-troff-me', 'ms' => 'text/x-troff-ms', 'uri' => 'text/x-uri', 'uris' => 'text/uri-list', 'urls' => 'text/uri-list', 'fly' => 'text/vnd.fly', 'flx' => 'text/vnd.fmi.flexstor', '3dml' => 'text/vnd.in3d.3dml', 'spot' => 'text/vnd.in3d.spot', 'jad' => 'text/vnd.sun.j2me.app-descriptor', 'wml' => 'text/vnd.wap.wml', 'wmls' => 'text/vnd.wap.wmlscript', 's' => 'text/x-asm', 'asm' => 'text/x-asm', 'c' => 'text/x-csrc', 'cc' => 'text/x-c++src', 'cxx' => 'text/x-c++src', 'cpp' => 'text/x-c++src', 'h' => 'text/x-chdr', 'hh' => 'text/x-c++hdr', 'dic' => 'text/x-c', 'f' => 'text/x-fortran', 'for' => 'text/x-fortran', 'f77' => 'text/x-fortran', 'f90' => 'text/x-fortran', 'p' => 'text/x-pascal', 'pas' => 'text/x-pascal', 'java' => 'text/x-java', 'etx' => 'text/x-setext', 'uu' => 'text/x-uuencode', 'vcs' => 'text/calendar', 'vcf' => 'text/directory', '3gp' => 'video/3gpp', '3g2' => 'video/3gpp2', 'h261' => 'video/h261', 'h263' => 'video/h263', 'h264' => 'video/h264', 'jpgv' => 'video/jpeg', 'jpm' => 'video/jpm', 'jpgm' => 'video/jpm', 'mj2' => 'video/mj2', 'mjp2' => 'video/mj2', 'mp4' => 'video/mp4', 'mp4v' => 'video/mp4', 'mpg4' => 'video/mp4', 'mpeg' => 'video/mpeg', 'mpg' => 'video/mpeg', 'mpe' => 'video/mpeg', 'm1v' => 'video/mpeg', 'm2v' => 'video/mpeg', 'qt' => 'video/quicktime', 'mov' => 'video/quicktime', 'fvt' => 'video/vnd.fvt', 'mxu' => 'video/vnd.mpegurl', 'm4u' => 'video/vnd.mpegurl', 'viv' => 'video/vnd.vivo', 'fli' => 'video/x-flic', 'asx' => 'video/x-ms-asf', 'wm' => 'video/x-ms-wm', 'wmv' => 'video/x-ms-wmv', 'wmx' => 'video/x-ms-wmx', 'wvx' => 'video/x-ms-wvx', 'avi' => 'video/x-msvideo', 'movie' => 'video/x-sgi-movie', 'ice' => 'x-conference/x-cooltalk', 'odb' => 'application/vnd.oasis.opendocument.database', 'oot' => 'application/vnd.oasis.opendocument.text', 'odm' => 'application/vnd.oasis.opendocument.text-master', 'sxc' => 'application/vnd.sun.xml.calc', 'stc' => 'application/vnd.sun.xml.calc.template', 'sxd' => 'application/vnd.sun.xml.draw', 'std' => 'application/vnd.sun.xml.draw.template', 'sxg' => 'application/vnd.sun.xml.writer.global', 'sxm' => 'application/vnd.sun.xml.math', 'sxi' => 'application/vnd.sun.xml.impress', 'sti' => 'application/vnd.sun.xml.impress.template', 'sxw' => 'application/vnd.sun.xml.writer', 'stw' => 'application/vnd.sun.xml.writer.template', 'Z' => 'application/x-compress', 'gz' => 'application/x-gzip', 'tgz' => 'application/x-compressed-tar', 'php' => 'application/x-php', 'php3' => 'application/x-php', 'pl' => 'application/x-perl', 'pm' => 'application/x-perl', 'gsm' => 'audio/x-gsm', 'vfb' => 'text/calendar', 'diff' => 'text/x-patch', 'patch' => 'text/x-patch', 'shtml' => 'text/html', 'po' => 'text/x-gettext-translation', 'sgl' => 'application/vnd.stardivision.writer', 'wk4' => 'application/vnd.lotus-1-2-3', 'pict2' => 'image/x-pict', 'lhz' => 'application/x-lhz', 'tar.bz2' => 'application/x-bzip-compressed-tar', 'rle' => 'image/rle', 'pcf.Z' => 'application/x-font-type1', 'm15' => 'audio/x-mod', 'flac' => 'audio/x-flac', 'dc' => 'application/x-dc-rom', 'm' => 'text/x-objcsrc', 'o' => 'application/x-object', 'fits' => 'image/x-fits', 'pfa' => 'application/x-font-type1', 'jnlp' => 'application/x-java-jnlp-file', 'pfb' => 'application/x-font-type1', 'smd' => 'application/vnd.stardivision.mail', 'it' => 'audio/x-it', 'bib' => 'text/x-bibtex', 'moc' => 'text/x-moc', 'theme' => 'application/x-theme', 'mod' => 'audio/x-mod', 'smf' => 'application/vnd.stardivision.math', 'uni' => 'audio/x-mod', 'mtm' => 'audio/x-mod', 'ppz' => 'application/vnd.ms-powerpoint', 's3m' => 'audio/x-s3m', 'deb' => 'application/x-deb', 'tk' => 'text/x-tcl', 'cdr' => 'application/vnd.corel-draw', 'lwob' => 'image/x-lwo', 'sml' => 'application/smil', 'etheme' => 'application/x-e-theme', '3ds' => 'image/x-3ds', 'vob' => 'video/mpeg', 'voc' => 'audio/x-voc', 'bdf' => 'application/x-font-bdf', 'ps.gz' => 'application/x-gzpostscript', 'uil' => 'text/x-uil', 'ts' => 'application/x-linguist', 'asp' => 'application/x-asp', 'nes' => 'application/x-nes-rom', 'sms' => 'application/x-sms-rom', 'BLEND' => 'application/x-blender', 'kil' => 'application/x-killustrator', 'icb' => 'image/x-icb', 'lyx' => 'application/x-lyx', 'jng' => 'image/x-jng', 'vor' => 'application/vnd.stardivision.writer', 'adb' => 'text/x-adasrc', 'flc' => 'video/x-flic', 'wpg' => 'application/x-wpg', 'wb1' => 'application/x-quattropro', 'gra' => 'application/x-graphite', 'wb2' => 'application/x-quattropro', 'ltx' => 'text/x-tex', 'xac' => 'application/x-gnucash', 'wb3' => 'application/x-quattropro', 'epsf' => 'image/x-eps', 'el' => 'text/x-emacs-lisp', 'jp2' => 'image/jpeg2000', 'tar.gz' => 'application/x-compressed-tar', 'epsi' => 'image/x-eps', 'ui' => 'application/x-designer', 'old' => 'application/x-trash', 'tar.Z' => 'application/x-tarz', 'ttf' => 'application/x-font-ttf', 'siag' => 'application/x-siag', 'sid' => 'audio/prs.sid', 'msod' => 'image/x-msod', 'h++' => 'text/x-chdr', 'tar.lzo' => 'application/x-tzo', 'tar.bz' => 'application/x-bzip-compressed-tar', 'ads' => 'text/x-adasrc', 'sda' => 'application/vnd.stardivision.draw', 'lzo' => 'application/x-lzop', 'cur' => 'image/x-win-bitmap', 'sdc' => 'application/vnd.stardivision.calc', 'sik' => 'application/x-trash', 'sdd' => 'application/vnd.stardivision.impress', 'xld' => 'application/vnd.ms-excel', 'gmo' => 'application/x-gettext-translation', 'xll' => 'application/vnd.ms-excel', 'blend' => 'application/x-blender', 'pw' => 'application/x-pw', 'kud' => 'application/x-kugar', 'mkv' => 'application/x-matroska', 'obj' => 'application/x-tgif', 'py' => 'text/x-python', 'sds' => 'application/vnd.stardivision.chart', 'idl' => 'text/x-idl', 'dat' => 'video/mpeg', 'stm' => 'audio/x-stm', 'PAR2' => 'application/x-par2', 'xcf.bz2' => 'image/x-compressed-xcf', 'psid' => 'audio/prs.sid', 'pict' => 'image/x-pict', 'ag' => 'image/x-applix-graphics', 'fo' => 'text/x-xslfo', 'sdw' => 'application/vnd.stardivision.writer', 'abw.CRASHED' => 'application/x-abiword', 'gsf' => 'application/x-font-type1', 'xcf.gz' => 'image/x-compressed-xcf', 'pcd' => 'image/x-photo-cd', 'egon' => 'application/x-egon', 'pcf' => 'application/x-font-pcf', 'al' => 'application/x-perl', 'gnc' => 'application/x-gnucash', 'tzo' => 'application/x-tzo', 'la' => 'application/x-shared-library-la', 'kpm' => 'application/x-kpovmodeler', 'qif' => 'application/x-qw', 'sty' => 'text/x-tex', 'psf' => 'application/x-font-linux-psf', 'as' => 'application/x-applix-spreadsheet', 'dbf' => 'application/x-dbase', 'ilbm' => 'image/x-ilbm', 'aw' => 'application/x-applix-word', 'gb' => 'application/x-gameboy-rom', 'xmi' => 'text/x-xmi', 'abw.gz' => 'application/x-abiword', 'XM' => 'audio/x-mod', 'gnumeric' => 'application/x-gnumeric', 'bak' => 'application/x-trash', 'xslfo' => 'text/x-xslfo', 'gg' => 'application/x-sms-rom', 'cgi' => 'application/x-cgi', 'mgp' => 'application/x-magicpoint', 'spd' => 'application/x-font-speedo', 'gnucash' => 'application/x-gnucash', 'cls' => 'text/x-tex', 'php4' => 'application/x-php', 'sun' => 'image/x-sun-raster', 'pyc' => 'application/x-python-bytecode', 'xcf' => 'image/x-xcf', 'xbel' => 'application/x-xbel', 'jpr' => 'application/x-jbuilder-project', 'afm' => 'application/x-font-afm', 'fig' => 'image/x-xfig', 'perl' => 'application/x-perl', 'rej' => 'application/x-reject', 'qtvr' => 'video/quicktime', 'jpx' => 'application/x-jbuilder-project', '669' => 'audio/x-mod', 'kdelnk' => 'application/x-desktop', 'md' => 'application/x-genesis-rom', 'pyo' => 'application/x-python-bytecode', 'oleo' => 'application/x-oleo', 'ac3' => 'audio/ac3', 'mml' => 'text/mathml', 'par2' => 'application/x-par2', 'sylk' => 'text/spreadsheet', 'C' => 'text/x-c++src', 'cert' => 'application/x-x509-ca-cert', 'ult' => 'audio/x-mod', 'lwo' => 'image/x-lwo', 'dcl' => 'text/x-dcl', 'zoo' => 'application/x-zoo', 'dcm' => 'application/dicom', 'mm' => 'text/x-troff-mm', 'iff' => 'image/x-iff', 'lws' => 'image/x-lws', 'zabw' => 'application/x-abiword', 'blender' => 'application/x-blender', 'glade' => 'application/x-glade', 'rpm' => 'application/x-rpm', 'tga' => 'image/x-tga', 'jar' => 'application/x-jar', 'cpio.gz' => 'application/x-cpio-compressed', 'dsl' => 'text/x-dsl', 'kra' => 'application/x-krita', 'n64' => 'application/x-n64-rom', 'm4a' => 'audio/x-m4a', 'c++' => 'text/x-c++src', 'moov' => 'video/quicktime', 'sam' => 'application/x-amipro', 'nsv' => 'video/x-nsv', 'dia' => 'application/x-dia-diagram', 'xi' => 'audio/x-xi', 'hp' => 'text/x-chdr', 'gen' => 'application/x-genesis-rom', 'url' => 'text/x-uri', 'hs' => 'text/x-haskell', 'xm' => 'audio/x-xm', 'sql' => 'text/x-sql', 'NSV' => 'video/x-nsv', 'desktop' => 'application/x-desktop', 'mng' => 'video/x-mng', 'pem' => 'application/x-x509-ca-cert', 'slk' => 'text/spreadsheet', 'cs' => 'text/x-csharp', 'arj' => 'application/x-arj', 'a' => 'application/x-archive', 'lhs' => 'text/x-literate-haskell', 'gcrd' => 'text/directory', 'vct' => 'text/directory', 'wk1' => 'application/vnd.lotus-1-2-3', 'msx' => 'application/x-msx-rom', 'sgi' => 'image/x-sgi', 'd' => 'text/x-dsrc', 'CSSL' => 'text/css', 'wk3' => 'application/vnd.lotus-1-2-3', 'abw' => 'application/x-abiword', 'pict1' => 'image/x-pict', 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', 'docm' => 'application/vnd.ms-word.document.macroEnabled.12', 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', 'potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12', 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' ); * @author Michael Slusarz * @package Horde_MIME */ class MIME_Part { /** * The type (ex.: text) of this part. * Per RFC 2045, the default is 'application'. * * @var string */ var $_type = 'application'; /** * The subtype (ex.: plain) of this part. * Per RFC 2045, the default is 'octet-stream'. * * @var string */ var $_subtype = 'octet-stream'; /** * The body of the part. * * @var string */ var $_contents = ''; /** * The desired transfer encoding of this part. * * @var string */ var $_transferEncoding = MIME_DEFAULT_ENCODING; /** * The current transfer encoding of the contents of this part. * * @var string */ var $_currentEncoding = null; /** * Should the message be encoded via 7-bit? * * @var boolean */ var $_encode7bit = true; /** * The description of this part. * * @var string */ var $_description = ''; /** * The disposition of this part (inline or attachment). * * @var string */ var $_disposition = MIME_DEFAULT_DISPOSITION; /** * The disposition parameters of this part. * * @var array */ var $_dispositionParameters = array(); /** * The content type parameters of this part. * * @var array */ var $_contentTypeParameters = array(); /** * The subparts of this part. * * @var array */ var $_parts = array(); /** * Information/Statistics on the subpart. * * @var array */ var $_information = array(); /** * The list of CIDs for this part. * * @var array */ var $_cids = array(); /** * The MIME ID of this part. * * @var string */ var $_mimeid = null; /** * The sequence to use as EOL for this part. * The default is currently to output the EOL sequence internally as * just "\n" instead of the canonical "\r\n" required in RFC 822 & 2045. * To be RFC complaint, the full EOL combination should be used * when sending a message. * It is not crucial here since the PHP/PEAR mailing functions will handle * the EOL details. * * @var string */ var $_eol = MIME_PART_EOL; /** * Internal class flags. * * @var array */ var $_flags = array(); /** * Part -> ID mapping cache. * * @var array */ var $_idmap = array(); /** * Unique MIME_Part boundary string. * * @var string */ var $_boundary = null; /** * Default value for this Part's size. * * @var integer */ var $_bytes = 0; /** * The content-ID for this part. * * @var string */ var $_contentid = null; /** * MIME_Part constructor. * * @param string $mimetype The content type of the part. * @param string $contents The body of the part. * @param string $charset The character set of the part. * @param string $disposition The content disposition of the part. * @param string $encoding The content encoding of the contents. */ function MIME_Part($mimetype = null, $contents = null, $charset = MIME_DEFAULT_CHARSET, $disposition = null, $encoding = null) { /* Create the unique MIME_Part boundary string. */ $this->_generateBoundary(); /* The character set should always be set, even if we are dealing * with Content-Types other than text/*. */ $this->setCharset($charset); if (!is_null($mimetype)) { $this->setType($mimetype); } if (!is_null($contents)) { $this->setContents($contents, $encoding); } if (!is_null($disposition)) { $this->setDisposition($disposition); } } /** * Set the content-disposition of this part. * * @param string $disposition The content-disposition to set (inline or * attachment). */ function setDisposition($disposition) { $disposition = String::lower($disposition); if (($disposition == 'inline') || ($disposition == 'attachment')) { $this->_disposition = $disposition; } } /** * Get the content-disposition of this part. * * @return string The part's content-disposition. */ function getDisposition() { return $this->_disposition; } /** * Set the name of this part. * TODO: MIME encode here instead of in header() - add a charset * parameter. * * @param string $name The name to set. */ function setName($name) { $this->setContentTypeParameter('name', $name); } /** * Get the name of this part. * * @param boolean $decode MIME decode description? * @param boolean $default If the name parameter doesn't exist, should we * use the default name from the description * parameter? * * @return string The name of the part. */ function getName($decode = false, $default = false) { $name = $this->getContentTypeParameter('name'); if ($default && empty($name)) { $name = preg_replace('|\W|', '_', $this->getDescription(false, true)); } if ($decode) { return trim(MIME::decode($name)); } else { return $name; } } /** * Set the body contents of this part. * * @param string $contents The part body. * @param string $encoding The current encoding of the contents. */ function setContents($contents, $encoding = null) { $this->_contents = $contents; $this->_flags['contentsSet'] = true; $this->_currentEncoding = (is_null($encoding)) ? $this->getCurrentEncoding() : MIME::encoding($encoding, MIME_STRING); } /** * Add to the body contents of this part. * * @param string $contents The contents to append to the current part * body. * @param string $encoding The current encoding of the contents. If not * specified, will try to auto determine the * encoding. */ function appendContents($contents, $encoding = null) { $this->setContents($this->_contents . $contents, $encoding); } /** * Clears the body contents of this part. */ function clearContents() { $this->_contents = ''; $this->_flags['contentsSet'] = false; $this->_currentEncoding = null; } /** * Return the body of the part. * * @return string The raw body of the part. */ function getContents() { return $this->_contents; } /** * Returns the contents in strict RFC 822 & 2045 output - namely, all * newlines end with the canonical sequence. * * @return string The entire MIME part. */ function getCanonicalContents() { return $this->replaceEOL($this->_contents, MIME_PART_RFC_EOL); } /** * Transfer encode the contents (to the transfer encoding identified via * getTransferEncoding()) and set as the part's new contents. */ function transferEncodeContents() { $contents = $this->transferEncode(); $this->_currentEncoding = $this->_flags['lastTransferEncode']; $this->setContents($contents, $this->_currentEncoding); $this->setTransferEncoding($this->_currentEncoding); } /** * Transfer decode the contents and set them as the new contents. */ function transferDecodeContents() { $contents = $this->transferDecode(); $this->_currentEncoding = $this->_flags['lastTransferDecode']; $this->setTransferEncoding($this->_currentEncoding); /* Don't set contents if they are empty, because this will do stuff like reset the internal bytes field, even though we shouldn't do that (the user has their reasons to set the bytes field to a non-zero value without putting the contents into this part. */ if (strlen($contents)) { $this->setContents($contents, $this->_currentEncoding); } } /** * Set the mimetype of this part. * * @param string $mimetype The mimetype to set (ex.: text/plain). */ function setType($mimetype) { /* RFC 2045: Any entity with unrecognized encoding must be treated as if it has a Content-Type of "application/octet-stream" regardless of what the Content-Type field actually says. */ if ($this->_transferEncoding == 'x-unknown') { return; } /* Set the 'setType' flag. */ $this->_flags['setType'] = true; list($this->_type, $this->_subtype) = explode('/', String::lower($mimetype)); if (($type = MIME::type($this->_type, MIME_STRING))) { $this->_type = $type; /* Set the boundary string for 'multipart/*' parts. */ if ($type == 'multipart') { if (!$this->getContentTypeParameter('boundary')) { $this->setContentTypeParameter('boundary', $this->_generateBoundary()); } } else { $this->clearContentTypeParameter('boundary'); } } else { $this->_type = 'x-unknown'; $this->clearContentTypeParameter('boundary'); } } /** * Get the full MIME Content-Type of this part. * * @param boolean $charset Append character set information to the end of * the content type if this is a text/* part. * * @return string The mimetype of this part * (ex.: text/plain; charset=us-ascii). */ function getType($charset = false) { if (!isset($this->_type) || !isset($this->_subtype)) { return false; } $ptype = $this->getPrimaryType(); $type = $ptype . '/' . $this->getSubType(); if ($charset && ($ptype == 'text')) { $type .= '; charset=' . $this->getCharset(); } return $type; } /** * If the subtype of a MIME part is unrecognized by an application, the * default type should be used instead (See RFC 2046). This method * returns the default subtype for a particular primary MIME Type. * * @return string The default mimetype of this part (ex.: text/plain). */ function getDefaultType() { switch ($this->getPrimaryType()) { case 'text': /* RFC 2046 (4.1.4): text parts default to text/plain. */ return 'text/plain'; case 'multipart': /* RFC 2046 (5.1.3): multipart parts default to multipart/mixed. */ return 'multipart/mixed'; default: /* RFC 2046 (4.2, 4.3, 4.4, 4.5.3, 5.2.4): all others default to application/octet-stream. */ return 'application/octet-stream'; } } /** * Get the primary type of this part. * * @return string The primary MIME type of this part. */ function getPrimaryType() { return $this->_type; } /** * Get the subtype of this part. * * @return string The MIME subtype of this part. */ function getSubType() { return $this->_subtype; } /** * Set the character set of this part. * * @param string $charset The character set of this part. */ function setCharset($charset) { $this->setContentTypeParameter('charset', $charset); } /** * Get the character set to use for of this part. Returns a charset for * all types (not just 'text/*') since we use this charset to determine * how to encode text in MIME headers. * * @return string The character set of this part. Returns null if there * is no character set. */ function getCharset() { $charset = $this->getContentTypeParameter('charset'); return (empty($charset)) ? null : $charset; } /** * Set the description of this part. * * @param string $description The description of this part. */ function setDescription($description) { $this->_description = MIME::encode($description, $this->getCharset()); } /** * Get the description of this part. * * @param boolean $decode MIME decode description? * @param boolean $default If the name parameter doesn't exist, should we * use the default name from the description * parameter? * * @return string The description of this part. */ function getDescription($decode = false, $default = false) { $desc = $this->_description; if ($default && empty($desc)) { $desc = $this->getName(); if (empty($desc)) { $desc = MIME_DEFAULT_DESCRIPTION; } } if ($decode) { return MIME::decode($desc); } else { return $desc; } } /** * Set the transfer encoding to use for this part. * * @param string $encoding The transfer encoding to use. */ function setTransferEncoding($encoding) { if (($mime_encoding = MIME::encoding($encoding, MIME_STRING))) { $this->_transferEncoding = $mime_encoding; } else { /* RFC 2045: Any entity with unrecognized encoding must be treated as if it has a Content-Type of "application/octet-stream" regardless of what the Content-Type field actually says. */ $this->setType('application/octet-stream'); $this->_transferEncoding = 'x-unknown'; } } /** * Add a MIME subpart. * * @param MIME_Part $mime_part Add a MIME_Part subpart to the current * MIME_Part. * @param string $index The index of the added MIME_Part. */ function addPart($mime_part, $index = null) { /* Add the part to the parts list. */ if (is_null($index)) { end($this->_parts); $id = key($this->_parts) + 1; $ptr = &$this->_parts; } else { $ptr = &$this->_partFind($index, $this->_parts, true); if (($pos = strrpos($index, '.'))) { $id = substr($index, $pos + 1); } else { $id = $index; } } /* Set the MIME ID if it has not already been set. */ if ($mime_part->getMIMEId() === null) { $mime_part->setMIMEId($id); } /* Store the part now. */ $ptr[$id] = $mime_part; /* Clear the ID -> Part mapping cache. */ $this->_idmap = array(); } /** * Get a list of all MIME subparts. * * @return array An array of the MIME_Part subparts. */ function getParts() { return $this->_parts; } /** * Retrieve a specific MIME part. * * @param string $id The MIME_Part ID string. * * @return MIME_Part The MIME_Part requested, or false if the part * doesn't exist. */ function getPart($id) { $mimeid = $this->getMIMEId(); /* This will convert '#.0' to simply '#', which is how the part is * internally stored. */ $search_id = $id; if (($str = strrchr($id, '.')) && ($str == '.0')) { $search_id = substr($search_id, 0, -2); } /* Return this part if: 1) There is only one part (e.g. the MIME ID is 0, or the MIME ID is 1 and there are no subparts. 2) $id matches this parts MIME ID. */ if (($search_id == 0) || (($search_id == 1) && !count($this->_parts)) || (!empty($mimeid) && ($search_id == $mimeid))) { $part = $this; } else { $part = $this->_partFind($id, $this->_parts); } if ($part && ($search_id != $id) && ($part->getType() == 'message/rfc822')) { $ret_part = Util::cloneObject($part); $ret_part->_parts = array(); return $ret_part; } return $part; } /** * Remove a MIME_Part subpart. * * @param string $id The MIME Part to delete. */ function removePart($id) { if (($ptr = &$this->_partFind($id, $this->_parts))) { unset($ptr); $this->_idmap = array(); } } /** * Alter a current MIME subpart. * * @param string $id The MIME Part ID to alter. * @param MIME_Part $mime_part The MIME Part to store. */ function alterPart($id, $mime_part) { if (($ptr = &$this->_partFind($id, $this->_parts))) { $ptr = $mime_part; $this->_idmap = array(); } } /** * Function used to find a specific MIME Part by ID. * * @access private * * @param string $id The MIME_Part ID string. * @param array &$parts A list of MIME_Part objects. * @param boolean $retarray Return a pointer to the array that stores * (would store) the part rather than the part * itself? */ function &_partFind($id, &$parts, $retarray = false) { /* Pointers don't persist through sessions; therefore, we must make * sure that the IdMap is destroyed at the end of each request. * How can we do this? We check to see if $_idmap contains an array * of MIME_Parts or an array of arrays. */ $check = reset($this->_idmap); if (empty($check) || !is_a($check, 'MIME_Part')) { $this->_idmap = array(); $this->_generateIdMap($this->_parts); } if ($retarray) { if ($pos = strrpos($id, '.')) { $id = substr($id, 0, $pos); } else { return $parts; } } if (isset($this->_idmap[$id])) { return $this->_idmap[$id]; } else { $part = false; return $part; } } /** * Generates a mapping of MIME_Parts with their MIME IDs. * * @access private * * @param array &$parts An array of MIME_Parts to map. */ function _generateIdMap(&$parts) { if (!empty($parts)) { foreach (array_keys($parts) as $key) { $ptr = &$parts[$key]; $this->_idmap[$ptr->getMIMEId()] = &$ptr; $this->_generateIdMap($ptr->_parts); } } } /** * Add information about the MIME_Part. * * @param string $label The information label. * @param mixed $data The information to store. */ function setInformation($label, $data) { $this->_information[$label] = $data; } /** * Retrieve information about the MIME_Part. * * @param string $label The information label. * * @return mixed The information requested. * Returns false if $label is not set. */ function getInformation($label) { return (isset($this->_information[$label])) ? $this->_information[$label] : false; } /** * Add a disposition parameter to this part. * * @param string $label The disposition parameter label. * @param string $data The disposition parameter data. */ function setDispositionParameter($label, $data) { $this->_dispositionParameters[$label] = $data; } /** * Get a disposition parameter from this part. * * @param string $label The disposition parameter label. * * @return string The data requested. * Returns false if $label is not set. */ function getDispositionParameter($label) { return (isset($this->_dispositionParameters[$label])) ? $this->_dispositionParameters[$label] : false; } /** * Get all parameters from the Content-Disposition header. * * @return array An array of all the parameters * Returns the empty array if no parameters set. */ function getAllDispositionParameters() { return $this->_dispositionParameters; } /** * Add a content type parameter to this part. * * @param string $label The disposition parameter label. * @param string $data The disposition parameter data. */ function setContentTypeParameter($label, $data) { $this->_contentTypeParameters[$label] = $data; } /** * Clears a content type parameter from this part. * * @param string $label The disposition parameter label. * @param string $data The disposition parameter data. */ function clearContentTypeParameter($label) { unset($this->_contentTypeParameters[$label]); } /** * Get a content type parameter from this part. * * @param string $label The content type parameter label. * * @return string The data requested. * Returns false if $label is not set. */ function getContentTypeParameter($label) { return (isset($this->_contentTypeParameters[$label])) ? $this->_contentTypeParameters[$label] : false; } /** * Get all parameters from the Content-Type header. * * @return array An array of all the parameters * Returns the empty array if no parameters set. */ function getAllContentTypeParameters() { return $this->_contentTypeParameters; } /** * Sets a new string to use for EOLs. * * @param string $eol The string to use for EOLs. */ function setEOL($eol) { $this->_eol = $eol; } /** * Get the string to use for EOLs. * * @return string The string to use for EOLs. */ function getEOL() { return $this->_eol; } /** * Add the appropriate MIME headers for this part to an existing array. * * @param array $headers An array of any other headers for the part. * * @return array The headers, with the MIME headers added. */ function header($headers = array()) { $eol = $this->getEOL(); $ptype = $this->getPrimaryType(); /* Get the character set for this part. */ $charset = $this->getCharset(); /* Get the Content-Type - this is ALWAYS required. */ $ctype = $this->getType(true); foreach ($this->getAllContentTypeParameters() as $key => $value) { /* Skip the charset key since that would have already been * added to $ctype by getType(). */ if ($key == 'charset') { continue; } $encode_2231 = MIME::encodeRFC2231($key, $value, $charset); /* Try to work around non RFC 2231-compliant MUAs by sending both * a RFC 2047-like parameter name and then the correct RFC 2231 * parameter. See: * http://lists.horde.org/archives/dev/Week-of-Mon-20040426/014240.html */ if (!empty($GLOBALS['conf']['mailformat']['brokenrfc2231']) && ((strpos($encode_2231, '*=') !== false) || (strpos($encode_2231, '*0=') !== false))) { $ctype .= '; ' . $key . '="' . MIME::encode($value, $charset) . '"'; } $ctype .= '; ' . $encode_2231; } $headers['Content-Type'] = MIME::wrapHeaders('Content-Type', $ctype, $eol); /* Get the description, if any. */ if (($descrip = $this->getDescription())) { $headers['Content-Description'] = MIME::wrapHeaders('Content-Description', MIME::encode($descrip, $charset), $eol); } /* message/* parts require no additional header information. */ if ($ptype == 'message') { return $headers; } /* Don't show Content-Disposition for multipart messages unless there is a name parameter. */ $name = $this->getName(); if (($ptype != 'multipart') || !empty($name)) { $disp = $this->getDisposition(); /* Add any disposition parameter information, if available. */ if (!empty($name)) { $encode_2231 = MIME::encodeRFC2231('filename', $name, $charset); /* Same broken RFC 2231 workaround as above. */ if (!empty($GLOBALS['conf']['mailformat']['brokenrfc2231']) && ((strpos($encode_2231, '*=') !== false) || (strpos($encode_2231, '*0=') !== false))) { $disp .= '; filename="' . MIME::encode($name, $charset) . '"'; } $disp .= '; ' . $encode_2231; } $headers['Content-Disposition'] = MIME::wrapHeaders('Content-Disposition', $disp, $eol); } /* Add transfer encoding information. */ $headers['Content-Transfer-Encoding'] = $this->getTransferEncoding(); /* Add content ID information. */ if (!is_null($this->_contentid)) { $headers['Content-ID'] = '<' . $this->_contentid . '>'; } return $headers; } /** * Return the entire part in MIME format. Includes headers on request. * * @param boolean $headers Include the MIME headers? * * @return string The MIME string. */ function toString($headers = true) { $eol = $this->getEOL(); $ptype = $this->getPrimaryType(); if ($headers) { $text = ''; foreach ($this->header() as $key => $val) { $text .= $key . ': ' . $val . $eol; } $text .= $eol; } /* Any information about a message/* is embedded in the message contents themself. Simply output the contents of the part directly and return. */ if ($ptype == 'message') { if (isset($text)) { return $text . $this->_contents; } else { return $this->_contents; } } if (isset($text)) { $text .= $this->transferEncode(); } else { $text = $this->transferEncode(); } /* Deal with multipart messages. */ if ($ptype == 'multipart') { $boundary = trim($this->getContentTypeParameter('boundary'), '"'); if (!strlen($this->_contents)) { $text .= 'This message is in MIME format.' . $eol; } reset($this->_parts); while (list(,$part) = each($this->_parts)) { $text .= $eol . '--' . $boundary . $eol; $oldEOL = $part->getEOL(); $part->setEOL($eol); $text .= $part->toString(true); $part->setEOL($oldEOL); } $text .= $eol . '--' . $boundary . '--' . $eol; } return $text; } /** * Returns the encoded part in strict RFC 822 & 2045 output - namely, all * newlines end with the canonical sequence. * * @param boolean $headers Include the MIME headers? * * @return string The entire MIME part. */ function toCanonicalString($headers = true) { $string = $this->toString($headers); return $this->replaceEOL($string, MIME_PART_RFC_EOL); } /** * Should we make sure the message is encoded via 7-bit (e.g. to adhere * to mail delivery standards such as RFC 2821)? * * @param boolean $use7bit Use 7-bit encoding? */ function strict7bit($use7bit) { $this->_encode7bit = $use7bit; } /** * Get the transfer encoding for the part based on the user requested * transfer encoding and the current contents of the part. * * @return string The transfer-encoding of this part. */ function getTransferEncoding() { $encoding = $this->_transferEncoding; $ptype = $this->getPrimaryType(); $text = str_replace($this->getEOL(), ' ', $this->_contents); /* If there are no contents, return whatever the current value of $_transferEncoding is. */ if (empty($text)) { return $encoding; } switch ($ptype) { case 'message': /* RFC 2046 [5.2.1] - message/rfc822 messages only allow 7bit, 8bit, and binary encodings. If the current encoding is either base64 or q-p, switch it to 8bit instead. RFC 2046 [5.2.2, 5.2.3, 5.2.4] - All other message/* messages only allow 7bit encodings. */ $encoding = ($this->getSubType() == 'rfc822') ? '8bit' : '7bit'; break; case 'text': $eol = $this->getEOL(); if (MIME::is8bit($text)) { $encoding = ($this->_encode7bit) ? 'quoted-printable' : '8bit'; } elseif (preg_match("/(?:" . $eol . "|^)[^" . $eol . "]{999,}(?:" . $eol . "|$)/", $this->_contents)) { /* If the text is longer than 998 characters between * linebreaks, use quoted-printable encoding to ensure the * text will not be chopped (i.e. by sendmail if being sent * as mail text). */ $encoding = 'quoted-printable'; } break; default: if (MIME::is8bit($text)) { $encoding = ($this->_encode7bit) ? 'base64' : '8bit'; } break; } /* Need to do one last check for binary data if encoding is 7bit or * 8bit. If the message contains a NULL character at all, the message * MUST be in binary format. RFC 2046 [2.7, 2.8, 2.9]. Q-P and base64 * can handle binary data fine so no need to switch those encodings. */ if ((($encoding == '8bit') || ($encoding == '7bit')) && preg_match('/\x00/', $text)) { $encoding = ($this->_encode7bit) ? 'base64' : 'binary'; } return $encoding; } /** * Retrieves the current encoding of the contents in the object. * * @return string The current encoding. */ function getCurrentEncoding() { return (is_null($this->_currentEncoding)) ? $this->_transferEncoding : $this->_currentEncoding; } /** * Encodes the contents with the part's transfer encoding. * * @return string The encoded text. */ function transferEncode() { $encoding = $this->getTransferEncoding(); $eol = $this->getEOL(); /* Set the 'lastTransferEncode' flag so that transferEncodeContents() can save a call to getTransferEncoding(). */ $this->_flags['lastTransferEncode'] = $encoding; /* If contents are empty, or contents are already encoded to the correct encoding, return now. */ if (!strlen($this->_contents) || ($encoding == $this->_currentEncoding)) { return $this->_contents; } switch ($encoding) { /* Base64 Encoding: See RFC 2045, section 6.8 */ case 'base64': /* Keeping these two lines separate seems to use much less memory than combining them (as of PHP 4.3). */ $encoded_contents = base64_encode($this->_contents); return chunk_split($encoded_contents, 76, $eol); /* Quoted-Printable Encoding: See RFC 2045, section 6.7 */ case 'quoted-printable': $output = MIME::quotedPrintableEncode($this->_contents, $eol); if (($eollength = String::length($eol)) && (substr($output, $eollength * -1) == $eol)) { return substr($output, 0, $eollength * -1); } else { return $output; } default: return $this->replaceEOL($this->_contents); } } /** * Decodes the contents of the part to either a 7bit or 8bit encoding. * * @return string The decoded text. * Returns the empty string if there is no text to decode. */ function transferDecode() { $encoding = $this->getCurrentEncoding(); /* If the contents are empty, return now. */ if (!strlen($this->_contents)) { $this->_flags['lastTransferDecode'] = $encoding; return $this->_contents; } switch ($encoding) { case 'base64': $message = base64_decode($this->_contents); $this->_flags['lastTransferDecode'] = '8bit'; break; case 'quoted-printable': $message = preg_replace("/=\r?\n/", '', $this->_contents); $message = $this->replaceEOL($message); $message = quoted_printable_decode($message); $this->_flags['lastTransferDecode'] = (MIME::is8bit($message)) ? '8bit' : '7bit'; break; /* Support for uuencoded encoding - although not required by RFCs, some mailers may still encode this way. */ case 'uuencode': case 'x-uuencode': case 'x-uue': if (function_exists('convert_uudecode')) { $message = convert_uuencode($this->_contents); } else { // TODO: Remove once PHP5 is required require_once 'Mail/mimeDecode.php'; $files = &Mail_mimeDecode::uudecode($this->_contents); $message = $files[0]['filedata']; } $this->_flags['lastTransferDecode'] = '8bit'; break; default: if (isset($this->_flags['lastTransferDecode']) && ($this->_flags['lastTransferDecode'] != $encoding)) { $message = $this->replaceEOL($this->_contents); } else { $message = $this->_contents; } $this->_flags['lastTransferDecode'] = $encoding; break; } return $message; } /** * Split the contents of the current Part into its respective subparts, * if it is multipart MIME encoding. Unlike the imap_*() functions, this * will preserve all MIME header information. * * The boundary content-type parameter must be set for this function to * work correctly. * * @return boolean True if the contents were successfully split. * False if any error occurred. */ function splitContents() { if (!($boundary = $this->getContentTypeParameter('boundary'))) { return false; } if (!strlen($this->_contents)) { return false; } $eol = $this->getEOL(); $retvalue = false; $boundary = '--' . $boundary; if (substr($this->_contents, 0, strlen($boundary)) == $boundary) { $pos1 = 0; } else { $pos1 = strpos($this->_contents, $eol . $boundary); } if ($pos1 === false) { return false; } $pos1 = strpos($this->_contents, $eol, $pos1 + 1); if ($pos1 === false) { return false; } $pos1 += strlen($eol); reset($this->_parts); $part_ptr = key($this->_parts); while ($pos2 = strpos($this->_contents, $eol . $boundary, $pos1)) { $this->_parts[$part_ptr]->setContents(substr($this->_contents, $pos1, $pos2 - $pos1)); $this->_parts[$part_ptr]->splitContents(); next($this->_parts); $part_ptr = key($this->_parts); if (is_null($part_ptr)) { return false; } $pos1 = strpos($this->_contents, $eol, $pos2 + 1); if ($pos1 === false) { return true; } $pos1 += strlen($eol); } return true; } /** * Replace newlines in this part's contents with those specified by either * the given newline sequence or the part's current EOL setting. * * @param string $text The text to replace. * @param string $eol The EOL sequence to use. If not present, uses the * part's current EOL setting. * * @return string The text with the newlines replaced by the desired * newline sequence. */ function replaceEOL($text, $eol = null) { if (is_null($eol)) { $eol = $this->getEOL(); } return preg_replace("/\r?\n/", $eol, $text); } /** * Return the unique MIME_Part boundary string generated for this object. * This may not be the boundary string used when building the message * since a user defined 'boundary' Content-Type parameter will override * this value. * * @return string The unique boundary string. */ function getUniqueID() { return $this->_boundary; } /** * Determine the size of a MIME_Part and its child members. * * @return integer Size of the MIME_Part, in bytes. */ function getBytes() { $bytes = 0; if (empty($this->_flags['contentsSet']) && $this->_bytes) { $bytes = $this->_bytes; } elseif ($this->getPrimaryType() == 'multipart') { reset($this->_parts); while (list(,$part) = each($this->_parts)) { /* Skip multipart entries (since this may result in double counting). */ if ($part->getPrimaryType() != 'multipart') { $bytes += $part->getBytes(); } } } else { if ($this->getPrimaryType() == 'text') { $bytes = String::length($this->_contents, $this->getCharset()); } else { $bytes = strlen($this->_contents); } } return $bytes; } /** * Explicitly set the size (in bytes) of this part. This value will only * be returned (via getBytes()) if there are no contents currently set. * This function is useful for setting the size of the part when the * contents of the part are not fully loaded (i.e. creating a MIME_Part * object from IMAP header information without loading the data of the * part). * * @param integer $bytes The size of this part in bytes. */ function setBytes($bytes) { $this->_bytes = $bytes; } /** * Output the size of this MIME_Part in KB. * * @return string Size of the MIME_Part, in string format. */ function getSize() { $bytes = $this->getBytes(); if (empty($bytes)) { return $bytes; } return NLS::numberFormat($bytes / 1024, 2); } /** * Add to the list of CIDs for this part. * * @param array $cids A list of MIME IDs of the part. * Key - MIME ID * Value - CID for the part */ function addCID($cids = array()) { $this->_cids += $cids; } /** * Returns the list of CIDs for this part. * * @return array The list of CIDs for this part. */ function getCIDList() { asort($this->_cids, SORT_STRING); return $this->_cids; } /** * Sets the Content-ID header for this part. * * @param string $cid Use this CID (if not already set). Else, generate a * random CID. */ function setContentID($cid = null) { if (is_null($this->_contentid)) { $this->_contentid = (is_null($cid)) ? (base_convert(uniqid(mt_rand()), 10, 36) . '@' . $_SERVER['SERVER_NAME']) : $cid; } return $this->_contentid; } /** * Returns the Content-ID for this part. * * @return string The Content-ID for this part. */ function getContentID() { return $this->_contentid; } /** * Alter the MIME ID of this part. * * @param string $mimeid The MIME ID. */ function setMIMEId($mimeid) { $this->_mimeid = $mimeid; } /** * Returns the MIME ID of this part. * * @return string The MIME ID. */ function getMIMEId() { return $this->_mimeid; } /** * Returns the relative MIME ID of this part. * e.g., if the base part has MIME ID of 2, and you want the first * subpart of the base part, the relative MIME ID is 2.1. * * @param string $id The relative part ID. * * @return string The relative MIME ID. */ function getRelativeMIMEId($id) { $rel = $this->getMIMEId(); return (empty($rel)) ? $id : $rel . '.' . $id; } /** * Returns a mapping of all MIME IDs to their content-types. * * @return array KEY: MIME ID, VALUE: Content type */ function contentTypeMap() { $map = array($this->getMIMEId() => $this->getType()); foreach ($this->_parts as $val) { $map += $val->contentTypeMap(); } return $map; } /** * Generate the unique boundary string (if not already done). * * @access private * * @return string The boundary string. */ function _generateBoundary() { if (is_null($this->_boundary)) { $this->_boundary = '=_' . base_convert(uniqid(mt_rand()), 10, 36); } return $this->_boundary; } } * @author Michael Slusarz * @package Horde_MIME */ class MIME_Structure { /** * Given the results of imap_fetchstructure(), parse the structure * of the message, figuring out correct bodypart numbers, etc. * * @param stdClass $body The result of imap_fetchstructure(). * * @return &MIME_Message The message parsed into a MIME_Message object. */ function &parse($body) { $msgOb = &new MIME_Message(); $msgOb->addPart(MIME_Structure::_parse($body)); $msgOb->buildMessage(); $ptr = array(&$msgOb); MIME_Structure::addMultipartInfo($ptr); return $msgOb; } /** * Given the results of imap_fetchstructure(), parse the structure * of the message, figuring out correct bodypart numbers, etc. * * @access private * * @param stdClass $body The result of imap_fetchstructure(). * @param string $ref The current bodypart. * * @return MIME_Part A MIME_Part object. */ function &_parse($body, $ref = 0) { static $message, $multipart; if (!isset($message)) { $message = MIME::type('message'); $multipart = MIME::type('multipart'); } $mime_part = &new MIME_Part(null, null, null); /* Top multiparts don't get their own line. */ if (empty($ref) && (!isset($body->type) || ($body->type != $multipart))) { $ref = 1; } MIME_Structure::_setInfo($body, $mime_part, $ref); if (isset($body->type) && ($body->type == $message) && $body->ifsubtype && ($body->subtype == 'RFC822')) { $mime_part->setMIMEId($ref . '.0'); } else { $mime_part->setMIMEId($ref); } /* Deal with multipart data. */ if (isset($body->parts)) { $sub_id = 1; reset($body->parts); while (list(,$sub_part) = each($body->parts)) { /* Are we dealing with a multipart message? */ if (isset($body->type) && ($body->type == $message) && isset($sub_part->type) && ($sub_part->type == $multipart)) { $sub_ref = $ref; } else { $sub_ref = (empty($ref)) ? $sub_id : $ref . '.' . $sub_id; } $mime_part->addPart(MIME_Structure::_parse($sub_part, $sub_ref), $sub_id++); } } return $mime_part; } /** * Given a mime part from imap_fetchstructure(), munge it into a * useful form and make sure that any parameters which are missing * are given default values. * * To specify the default character set, define the global variable * $GLOBALS['mime_strucutre']['default_charset']. * * @access private * * @param stdClass $part The original part info. * @param MIME_Part &$ob A MIME_Part object. * @param string $ref The ID of this part. */ function _setInfo($part, &$ob, $ref) { /* Store Content-type information. */ $primary_type = (isset($part->type)) ? MIME::type($part->type, MIME_STRING) : 'text'; $sec_type = ($part->ifsubtype && $part->subtype) ? $part->subtype : 'x-unknown'; $ob->setType($primary_type . '/' . $sec_type); /* Set transfer encoding. */ if (isset($part->encoding)) { $encoding = $part->encoding; $ob->setTransferEncoding($encoding); } else { $encoding = null; } /* Set transfer disposition. */ $ob->setDisposition(($part->ifdisposition) ? $part->disposition : MIME_DEFAULT_DISPOSITION); /* If 'body' is set, set as the contents of the part. */ if (isset($part->body)) { $ob->setContents($part->body, $encoding); } /* If 'bytes' is set, store as information variable. */ if (isset($part->bytes)) { $ob->setBytes($part->bytes); } /* Set the part's identification string, if available. */ if (!empty($ref) && $part->ifid) { $ob->setContentID($part->id); } /* Go through the content-type parameters, if any. */ foreach (MIME_Structure::_getParameters($part, 1) as $key => $val) { if ($key == 'charset') { $ob->setCharset($val); } else { $ob->setContentTypeParameter($key, $val); } } /* Set the default character set. */ if (($ob->getPrimaryType() == 'text') && (String::lower($ob->getCharset()) == 'us-ascii') && isset($GLOBALS['mime_structure']['default_charset'])) { $ob->setCharset($GLOBALS['mime_structure']['default_charset']); } /* Go through the disposition parameters, if any. */ foreach (MIME_Structure::_getParameters($part, 2) as $key => $val) { $ob->setDispositionParameter($key, $val); } /* Set the name. */ if (($fname = $ob->getContentTypeParameter('filename'))) { $ob->setName($fname); } elseif (($fname = $ob->getDispositionParameter('filename'))) { $ob->setName($fname); } /* Set the description. */ if (isset($part->description)) { $ob->setDescription(preg_replace('/\s+/', ' ', $part->description)); } } /** * Get all parameters for a given portion of a message. * * @access private * * @param stdClass $part The original part info. * @param integer $type The parameter type to retrieve. * 1 = content * 2 = disposition * * @return array An array of parameter key/value pairs. */ function _getParameters($part, $type) { $param_list = array(); $ptype = ($type == 1) ? 'parameters' : 'dparameters'; $pexists = 'if' . $ptype; if ($part->$pexists) { $attr_list = $rfc2231_list = array(); foreach ($part->$ptype as $param) { $param->value = str_replace(array("\t", '\"'), array(' ', '"'), $param->value); /* Look for an asterisk in the attribute name. If found we * know we have RFC 2231 information. */ $pos = strpos($param->attribute, '*'); if ($pos) { $attr = substr($param->attribute, 0, $pos); $rfc2231_list[$attr][] = $param->attribute . '=' . $param->value; } else { $attr_list[$param->attribute] = $param->value; } } foreach ($rfc2231_list as $val) { $res = MIME::decodeRFC2231(implode(' ', $val)); if ($res) { $attr_list[$res['attribute']] = $res['value']; } } foreach ($attr_list as $attr => $val) { $field = String::lower($attr); if ($field == 'type') { if (($type = MIME::type($val))) { $param_list['type'] = $type; } } else { $param_list[$field] = $val; } } } return $param_list; } /** * Set the special information for certain MIME types. * * @since Horde 3.2 * * @param array &$parts The list of parts contained within the multipart * object. * @param array $info Information about the multipart structure. */ function addMultipartInfo(&$parts, $info = array()) { if (empty($parts)) { return; } reset($parts); while (list($key,) = each($parts)) { $ptr = &$parts[$key]; $new_info = $info; if (isset($info['alt'])) { $ptr->setInformation('alternative', (is_null($info['alt'])) ? '-' : $info['alt']); } if (isset($info['related'])) { $ptr->setInformation('related_part', $info['related']->getMIMEId()); if ($id = $ptr->getContentID()) { $info['related']->addCID(array($ptr->getMIMEId() => $id)); } } if (isset($info['rfc822'])) { $ptr->setInformation('rfc822_part', $info['rfc822']); } switch ($ptr->getType()) { case 'multipart/alternative': $new_info['alt'] = $ptr->getMIMEId(); break; case 'multipart/related': $new_info['related'] = &$ptr; break; case 'message/rfc822': $new_info['rfc822'] = $ptr->getMIMEId(); $ptr->setInformation('header', true); break; } MIME_Structure::addMultipartInfo($ptr->_parts, $new_info); } } /** * Attempts to build a MIME_Message object from a text message. * * @param string $text The text of the MIME message. * * @return MIME_Message A MIME_Message object, or false on error. */ function &parseTextMIMEMessage($text) { /* Set up the options for the mimeDecode class. */ $decode_args = array( 'include_bodies' => true, 'decode_bodies' => false, 'decode_headers' => false ); require_once 'Mail/mimeDecode.php'; $mimeDecode = &new Mail_mimeDecode($text, MIME_PART_EOL); if (!($structure = $mimeDecode->decode($decode_args))) { $message = false; } else { /* Put the object into imap_parsestructure() form. */ MIME_Structure::_convertMimeDecodeData($structure); $message = MIME_Structure::parse($structure); } return $message; } /** * Convert the output from mimeDecode::decode() into a structure that * matches imap_fetchstructure() output. * * @access private * * @param stdClass &$ob The output from mimeDecode::decode(). */ function _convertMimeDecodeData(&$ob) { /* Primary content-type. */ if (!isset($ob->ctype_primary)) { $ob->ctype_primary = 'application'; $ob->ctype_secondary = 'octet-stream'; } $ob->type = intval(MIME::type($ob->ctype_primary)); /* Secondary content-type. */ if (isset($ob->ctype_secondary)) { $ob->subtype = String::upper($ob->ctype_secondary); $ob->ifsubtype = 1; } else { $ob->ifsubtype = 0; } /* Content transfer encoding. */ if (isset($ob->headers['content-transfer-encoding'])) { $ob->encoding = MIME::encoding($ob->headers['content-transfer-encoding']); } /* Content-type and Disposition parameters. */ $param_types = array('ctype_parameters' => 'parameters', 'd_parameters' => 'dparameters'); foreach ($param_types as $param_key => $param_value) { $if_var = 'if' . $param_value; if (isset($ob->$param_key)) { $ob->$if_var = 1; $ob->$param_value = array(); foreach ($ob->$param_key as $key => $val) { $newOb = &new stdClass; $newOb->attribute = $key; $newOb->value = $val; array_push($ob->$param_value, $newOb); } } else { $ob->$if_var = 0; } } /* Content-Disposition. */ if (isset($ob->headers['content-disposition'])) { $ob->ifdisposition = 1; $hdr = $ob->headers['content-disposition']; $pos = strpos($hdr, ';'); if ($pos !== false) { $hdr = substr($hdr, 0, $pos); } $ob->disposition = $hdr; } else { $ob->ifdisposition = 0; } /* Content-ID. */ if (isset($ob->headers['content-id'])) { $ob->ifid = 1; $ob->id = $ob->headers['content-id']; } else { $ob->ifid = 0; } /* Get file size (if 'body' text is set). */ if (isset($ob->body)) { $ob->bytes = strlen($ob->body); } /* Process parts also. */ if (isset($ob->parts)) { reset($ob->parts); while (list($key,) = each($ob->parts)) { MIME_Structure::_convertMimeDecodeData($ob->parts[$key]); } } } /** * Builds an array consisting of MIME header/value pairs. * * @param string $headers A text string containing the headers (e.g. * output from imap_fetchheader()). * @param boolean $decode Should the headers be decoded? * @param boolean $lowercase Should the keys be in lowercase? * * @return array An array consisting of the header name as the key and * the header value as the value. * A header with multiple entries will be stored in * 'value' as an array. */ function parseMIMEHeaders($headers, $decode = true, $lowercase = false) { $header = $headval = ''; $ob = $toprocess = array(); foreach (explode("\n", $headers) as $val) { $val = rtrim($val); if (preg_match("/^([^\s]+)\:\s*(.*)/", $val, $matches)) { if (!empty($header)) { $toprocess[] = array($header, $headval); } $header = $matches[1]; $headval = $matches[2]; } else { $val = ltrim($val); if ($val) { $headval .= ' ' . ltrim($val); } else { break; } } } if (!empty($header)) { $toprocess[] = array($header, $headval); } foreach ($toprocess as $val) { if ($decode) { // Fields defined in RFC 2822 that contain address information if (in_array(String::lower($val[0]), array('from', 'to', 'cc', 'bcc', 'reply-to', 'resent-to', 'resent-cc', 'resent-bcc', 'resent-from', 'sender'))) { $val[1] = MIME::decodeAddrString($val[1]); } else { $val[1] = MIME::decode($val[1]); } } if (isset($ob[$val[0]])) { if (!is_array($ob[$val[0]])) { $temp = $ob[$val[0]]; $ob[$val[0]] = array(); $ob[$val[0]][] = $temp; } $ob[$val[0]][] = $val[1]; } else { $ob[$val[0]] = $val[1]; } } return ($lowercase) ? array_change_key_case($ob, CASE_LOWER) : $ob; } } * @since Horde 3.0 * @package Horde_MIME_Viewer */ class MIME_Viewer_audio extends MIME_Viewer { /** * Return the content-type. * * @return string The content-type of the output. */ function getType() { return $this->mime_part->getType(); } } * @since Horde 3.0 * @package Horde_MIME_Viewer */ class MIME_Viewer_css extends MIME_Viewer_source { /** * Render out the currently set contents. * * @param array $params Any parameters the viewer may need. * * @return string The rendered text. */ function render($params = null) { $css = htmlspecialchars($this->mime_part->getContents(), ENT_NOQUOTES); $css = preg_replace_callback('!(}|\*/).*?({|/\*)!s', array($this, '_handles'), $css); $css = preg_replace_callback('!{[^}]*}!s', array($this, '_attributes'), $css); $css = preg_replace_callback('!/\*.*?\*/!s', array($this, '_comments'), $css); $css = trim($css); // Educated Guess at whether we are inline or not. if (headers_sent() || ob_get_length()) { return $this->lineNumber($css); } else { return Util::bufferOutput('require', $GLOBALS['registry']->get('templates', 'horde') . '/common-header.inc') . $this->lineNumber($css) . Util::bufferOutput('require', $GLOBALS['registry']->get('templates', 'horde') . '/common-footer.inc'); } } function _comments($matches) { $patterns[] = '!(http://[/\w-.]+)!s'; $replaces[] = '\\1'; $comments = preg_replace($patterns, $replaces, $matches[0]); return '' . $comments . ''; } function _attributes($matches) { // Attributes. $patterns[] = '!([-\w]+\s*):!s'; $replaces[] = '\\1:'; // Values. $patterns[] = '!:(\s*)(.+?)(\s*;)!s'; $replaces[] = ':\\1\\2\\3'; // URLs. $patterns[] = '!(url\([\'"]?)(.*?)([\'"]?\))!s'; $replaces[] = '\\1\\2\\3'; // Colors. $patterns[] = '!(#[[:xdigit:]]{3,6})!s'; $replaces[] = '\\1'; // Parentheses. $patterns[] = '!({|})!s'; $replaces[] = '\\1'; // Unity. $patterns[] = '!(em|px|%)\b!s'; $replaces[] = '\\1'; return preg_replace($patterns, $replaces, $matches[0]); } function _handles($matches) { // HTML Tags. $patterns[] = '!\b(body|h\d|a|span|div|acronym|small|strong|em|pre|ul|ol|li|p)\b!s'; $replaces[] = '\\1\\2'; // IDs. $patterns[] = '!(#[-\w]+)!s'; $replaces[] = '\\1'; // Class. $patterns[] = '!(\.[-\w]+)\b!s'; $replaces[] = '\\1'; // METAs. $patterns[] = '!(:link|:visited|:hover|:active|:first-letter)!s'; $replaces[] = '\\1'; return preg_replace($patterns, $replaces, $matches[0]); } } * @package Horde_MIME_Viewer */ class MIME_Viewer_deb extends MIME_Viewer { /** * Render the data. * * @param array $params Any parameters the viewer may need. * * @return string The rendered data. */ function render($params = array()) { global $mime_drivers; /* Check to make sure the program actually exists. */ if (!file_exists($mime_drivers['horde']['deb']['location'])) { return '
' . sprintf(_("The program used to view this data type (%s) was not found on the system."), $mime_drivers['horde']['deb']['location']) . '
'; } $tmp_deb = Horde::getTempFile('horde_deb'); $fh = fopen($tmp_deb, 'w'); fwrite($fh, $this->mime_part->getContents()); fclose($fh); $fh = popen($mime_drivers['horde']['deb']['location'] . " -f $tmp_deb 2>&1", 'r'); while (($rc = fgets($fh, 8192))) { $data .= $rc; } pclose($fh); return '
' . htmlspecialchars($data) . '
'; } /** * Return the MIME content type of the rendered content. * * @return string The content type of the output. */ function getType() { return 'text/html; charset=' . NLS::getCharset(); } } * @package Horde_MIME_Viewer */ class MIME_Viewer_default extends MIME_Viewer { } command and the next balancing * removes all other formatting commands (all text enclosed * in angle brackets), and outside of environments converts * any series of n CRLFs to n-1 CRLFs, and converts any lone CRLF * pairs to SPACE. * * We don't qualify as we don't currently track the * environment, that is we do CRLF conversion even if is * specified in the text, but we're close at least. * * $Horde: framework/MIME/MIME/Viewer/enriched.php,v 1.23.10.11 2009-01-06 15:23:21 jan Exp $ * * Copyright 2001-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. * * @author Eric Rostetter * @package Horde_MIME_Viewer */ class MIME_Viewer_enriched extends MIME_Viewer { /** * Render out the currently set contents in HTML format. The * $mime_part class variable has the information to render out, * encapsulated in a MIME_Part object. */ function render() { if (($text = $this->mime_part->getContents()) === false) { return false; } if (trim($text) == '') { return $text; } // We add space at the beginning and end of the string as it will // make some regular expression checks later much easier (so we // don't have to worry about start/end of line characters) $text = ' ' . $text . ' '; // We need to preserve << tags, so map them to ascii 1 or ascii 255 // We make the assumption here that there would never be an ascii // 1 in an email, which may not be valid, but seems reasonable... // ascii 255 would work if for some reason you don't like ascii 1 // ascii 0 does NOT seem to work for this, though I'm not sure why $text = str_replace('<<', chr(1), $text); // Remove any unrecognized tags in the text (via RFC minimal specs) // any tags we just don't want to implement can also be removed here // Note that this will remove any html links, but this is intended $implementedTags = '' . '
' . ''; // $unImplementedTags = ''; $text = strip_tags($text, $implementedTags); // restore the << tags as < tags now... $text = str_replace(chr(1), '<<', $text); // $text = str_replace(chr(255), '<', $text); // Get color parameters into a more useable format. $text = preg_replace('/([\da-fA-F]+),([\da-fA-F]+),([\da-fA-F]+)<\/param>/Uis', '', $text); $text = preg_replace('/(red|blue|green|yellow|cyan|magenta|black|white)<\/param>/Uis', '', $text); // Get font family parameters into a more useable format. $text = preg_replace('/(\w+)<\/param>/Uis', '', $text); // Just remove any remaining parameters -- we won't use // them. Any tags with parameters that we want to implement // will have to come before this Someday we hope to use these // tags (e.g. for tags) $text = preg_replace('/.*<\/param>/Uis', '', $text); // Single line breaks become spaces, double line breaks are a // real break. This needs to do tracking to be // compliant but we don't want to deal with state at this // time, so we fake it some day we should rewrite this to // handle correctly. $text = preg_replace('/([^\n])\r\n([^\r])/', '\1 \2', $text); $text = preg_replace('/(\r\n)\r\n/', '\1', $text); // We try to protect against bad stuff here. $text = @htmlspecialchars($text, ENT_QUOTES, $this->mime_part->getCharset()); // Now convert the known tags to html. Try to remove any tag // parameters to stop people from trying to pull a fast one $text = preg_replace('/(?\1', $text); $text = preg_replace('/(?\1', $text); $text = preg_replace('/(?\1', $text); $text = preg_replace_callback('/(?\2', $text); $text = preg_replace('/(?\2', $text); $text = preg_replace('/(?', $text); $text = preg_replace('/(?', $text); $text = preg_replace('/(?', $text); $text = preg_replace('/(?', $text); $text = preg_replace('/(?\1', $text); $text = preg_replace('/(?\1', $text); $text = preg_replace('/(?\1', $text); $text = preg_replace('/(?\1', $text); $text = preg_replace('/(?\1', $text); $text = preg_replace('/(?\1', $text); $text = preg_replace('/(?\1', $text); // Replace << with < now (from translated HTML form). $text = str_replace('<<', '<', $text); // Now we remove the leading/trailing space we added at the // start. $text = preg_replace('/^ (.*) $/s', '\1', $text); // Make URLs clickable. require_once 'Horde/Text/Filter.php'; $text = Text_Filter::filter($text, 'linkurls', array('callback' => 'Horde::externalUrl')); // Wordwrap -- note this could impact on our above RFC // compliance *IF* we honored nofill tags (which we don't // yet). $text = str_replace("\t", ' ', $text); $text = str_replace(' ', '  ', $text); $text = str_replace("\n ", "\n ", $text); if ($text[0] == ' ') { $text = ' ' . substr($text, 1); } $text = nl2br($text); $text = '

' . $text . '

'; return $text; } function colorize($colors) { for ($i = 1; $i < 4; $i++) { $colors[$i] = sprintf('%02X', round(hexdec($colors[$i]) / 255)); } return '' . $colors[4] . ''; } /** * Return the MIME content type of the rendered content. * * @return string The content type of the output. */ function getType() { return 'text/html; charset=' . NLS::getCharset(); } } * @package Horde_MIME_Viewer */ class MIME_Viewer_enscript extends MIME_Viewer_source { /** * Render out the data using Enscript. * * @param array $params Any parameters the Viewer may need. * * @return string The rendered data. */ function render($params = array()) { global $mime_drivers; /* Check to make sure the program actually exists. */ if (!file_exists($mime_drivers['horde']['enscript']['location'])) { return '
' . sprintf(_("The program used to view this data type (%s) was not found on the system."), $mime_drivers['horde']['enscript']['location']) . '
'; } /* Create temporary files for input to Enscript. Note that we cannot use a pipe, since enscript must have access to the whole file to determine its type for coloured syntax highlighting. */ $tmpin = Horde::getTempFile('EnscriptIn'); /* Write the contents of our buffer to the temporary input file. */ $contents = $this->mime_part->getContents(); $fh = fopen($tmpin, 'wb'); fwrite($fh, $contents, strlen($contents)); fclose($fh); /* Execute the enscript command. */ $lang = escapeshellarg($this->_typeToLang($this->mime_part->getType())); $results = shell_exec($mime_drivers['horde']['enscript']['location'] . " -E$lang --language=html --color --output=- < $tmpin"); /* Strip out the extraneous HTML from Enscript, and output it. */ $res_arr = preg_split('/\<\/?pre\>/i', $results); if (count($res_arr) == 3) { $results = trim($res_arr[1]); } /* Educated Guess at whether we are inline or not. */ if (headers_sent() || ob_get_length()) { return $this->lineNumber($results); } else { return Util::bufferOutput('require', $GLOBALS['registry']->get('templates', 'horde') . '/common-header.inc') . $this->lineNumber($results) . Util::bufferOutput('require', $GLOBALS['registry']->get('templates', 'horde') . '/common-footer.inc'); } } /** * Attempts to determine what language to use for the enscript program * from a MIME type. * * @access private * * @param string $type The MIME type. * * @return string The enscript 'language' parameter string. */ function _typeToLang($type) { include_once dirname(__FILE__) . '/../Magic.php'; $ext = MIME_Magic::MIMEToExt($type); switch ($ext) { case 'cs': return 'java'; case 'el': return 'elisp'; case 'h': return 'c'; case 'C': case 'H': case 'cc': case 'hh': case 'c++': case 'cxx': case 'cpp': return 'cpp'; case 'htm': case 'shtml': case 'xml': return 'html'; case 'js': return 'javascript'; case 'pas': return 'pascal'; case 'al': case 'cgi': case 'pl': case 'pm': return 'perl'; case 'ps': return 'postscript'; case 'vb': return 'vba'; case 'vhd': return 'vhdl'; case 'patch': case 'diff': return 'diffu'; default: return $ext; } } } * @author Jon Parise * @author Michael Slusarz * @package Horde_MIME_Viewer */ class MIME_Viewer_html extends MIME_Viewer { /** * Render out the currently set contents. * * @param array $params Any parameters the viewer may need. * * @return string The rendered text. */ function render($params = null) { return $this->_cleanHTML($this->mime_part->getContents()); } /** * Filters active content, dereferences external links, detects phishing, * etc. * * @access private * @todo Use IP checks from * http://lxr.mozilla.org/mailnews/source/mail/base/content/phishingDetector.js. * * @param string $data The HTML data. * * @return string The cleaned HTML data. */ function _cleanHTML($data) { global $browser, $prefs; $phish_warn = false; require_once 'Horde/MIME/Contents.php'; $attachment = MIME_Contents::viewAsAttachment(); /* Deal with tags in the HTML, since they will screw up our own * relative paths. */ if (preg_match('/ ]*)"? ?\/?>/i', $data, $matches)) { $base = $matches[1]; if (substr($base, -1, 1) != '/') { $base .= '/'; } /* Recursively call _cleanHTML() to prevent clever fiends from * sneaking nasty things into the page via $base. */ $base = $this->_cleanHTML($base); } /* Attempt to fix paths that were relying on a tag. */ if (!empty($base)) { $pattern = array('|src=(["\'])([^:"\']+)\1|i', '|src=([^: >"\']+)|i', '|href= *(["\'])([^:"\']+)\1|i', '|href=([^: >"\']+)|i'); $replace = array('src=\1' . $base . '\2\1', 'src=' . $base . '\1', 'href=\1' . $base . '\2\1', 'href=' . $base . '\1'); $data = preg_replace($pattern, $replace, $data); } require_once 'Horde/Text/Filter.php'; $strip_style_attributes = (($browser->isBrowser('mozilla') && $browser->getMajor() == 4) || $browser->isBrowser('msie')); $strip_styles = !$attachment || $strip_style_attributes; $data = Text_Filter::filter($data, 'xss', array('body_only' => !$attachment, 'strip_styles' => $strip_styles, 'strip_style_attributes' => $strip_style_attributes)); /* Check for phishing exploits. */ $highlight = $attachment ? 'style="background-color:#ffd0af;color:black"' : 'class="mimeStatusWarning"'; if ($this->getConfigParam('phishing_check')) { if (preg_match('/href\s*=\s*["\']?\s*(http|https|ftp):\/\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:[^>]*>\s*(?:$1:\/\/)?((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?[^<]*)<\/a)?/i', $data, $m)) { /* Check 1: Check for IP address links, but ignore if the link * text has the same IP address. */ if (!isset($m[4]) || $m[2] != $m[4]) { $data = preg_replace('/href\s*=\s*["\']?\s*(http|https|ftp):\/\/' . preg_quote($m[2], '/') . '(?:[^>]*>\s*(?:$1:\/\/)?' . preg_quote($m[3], '/') . '[^<]*<\/a)?/i', $highlight . ' $0', $data); $phish_warn = true; } } elseif (preg_match_all('/href\s*=\s*["\']?\s*(?:http|https|ftp):\/\/([^\s"\'>]+)["\']?[^>]*>\s*(?:(?:http|https|ftp):\/\/)?(.*?)<\/a/is', $data, $m)) { /* $m[1] = Link; $m[2] = Target * Check 2: Check for links that point to a different host than * the target url; if target looks like a domain name, check it * against the link. */ $links = count($m[0]); for ($i = 0; $i < $links; $i++) { $link = strtolower(urldecode($m[1][$i])); $target = strtolower(preg_replace('/^(http|https|ftp):\/\//', '', strip_tags($m[2][$i]))); if (preg_match('/^[-._\da-z]+\.[a-z]{2,}/i', $target) && strpos($link, $target) !== 0 && strpos($target, $link) !== 0) { /* Don't consider the link a phishing link if the * domain is the same on both links (e.g. * adtracking.example.com & www.example.com). */ preg_match('/\.?([^\.\/]+\.[^\.\/]+)[\/?]/', $link, $host1); preg_match('/\.?([^\.\/]+\.[^\.\/ ]+)([\/ ].*)?$/s', $target, $host2); if (!(count($host1) && count($host2)) || strcasecmp($host1[1], $host2[1]) !== 0) { $data = preg_replace('/href\s*=\s*["\']?\s*(?:http|https|ftp):\/\/' . preg_quote($m[1][$i], '/') . '["\']?[^>]*>\s*(?:(?:http|https|ftp):\/\/)?' . preg_quote($m[2][$i], '/') . '<\/a/is', $highlight . ' $0', $data); $phish_warn = true; } } } } } /* Try to derefer all external references. */ $data = preg_replace_callback('/href\s*=\s*(["\'])?((?(1)[^\1]*?|[^\s>]+))(?(1)\1|)/i', array($this, '_dereferExternalReferencesCallback'), $data); /* Prepend phishing warning. */ if ($phish_warn) { $contents = new MIME_Contents(new MIME_Part()); $phish_warning = sprintf(_("%s: This message may not be from whom it claims to be. Beware of following any links in it or of providing the sender with any personal information.") . ' ' . _("The links that caused this warning have the same background color as this message."), _("Warning")); if ($attachment) { $phish_warning = '' . String::convertCharset($phish_warning, NLS::getCharset(), $this->mime_part->getCharset()) . '
'; } $phish_warning = $contents->formatStatusMsg($phish_warning, null, true, 'mimeStatusWarning'); if (stristr($data, ')(.*)/i', '$1' . $phish_warning . '$2', $data); } } return $data; } function _dereferExternalReferencesCallback($m) { return 'href="' . Horde::externalUrl($m[2]) . '"'; } /** * Return the content-type of the rendered text. * * @return string The MIME Content-Type. */ function getType() { require_once 'Horde/MIME/Contents.php'; return MIME_Contents::viewAsAttachment() ? $this->mime_part->getType(true) : 'text/html; charset=' . NLS::getCharset(); } } INDX( Q^w(t"hT!iic%NMp audio.php#`P!iic% q css.php$`P!iic%q deb.php%hX!iic%nqo default.php&pZ!iic%r(; enriched.php'pZ!iiƬ%r enscript.php(hR!iiƬ% Xt  html.php)hV!iiƬ%߭t images.php*hX!iiƬ%׳x msexcel.php+xb!iiƬ%%x; mspowerpoint.php+pZ!iiƬ%%x; MSPOWE~1.PHP,hV!iiƬ%lx msword.php-XH!Ƭ%Ƭ%Ƭ%Ƭ%ooo9`P!ii%ʺyyS ooo.php:`P!ii%|y4 pdf.php;`P!ii%<* zD php.php<hT!ii%"Uz plain.php=`P!ii%z  rar.php>hV!ii%7{& report.php?hV!ii%V{ rfc822.php@pZ!ii%U[{  richtext.phpA`P!ii%u{ rpm.phpB`P!ii%`Tz{\ rtf.phpCpZ!ii%9|< security.phpDhV!ii%c| simple.phpEhR!ii% w|c smil.phpFhV!ii%|% source.phpGp`!ii%n|s srchighlite.phpGpZ!ii%n|s SRCHIG~1.PHPH`P!ii%VT} tgz.phpIhR!ii%s} tnef.phpJhT!ii%0~@{= vcard.phpKhV!ii%4/~ webcpp.phpLp`!ii% ~ wordperfec.phpLpZ!ii% ~ WORDPE~1.PHPM`P!ii%~2 zip.php * @package Horde_MIME_Viewer */ class MIME_Viewer_images extends MIME_Viewer { /** * Return the content-type. * * @return string The content-type of the output. */ function getType() { $type = $this->mime_part->getType(); if ($GLOBALS['browser']->isBrowser('mozilla') && ($type == 'image/pjpeg')) { /* image/jpeg and image/pjpeg *appear* to be the same * entity, but Mozilla don't seem to want to accept the * latter. For our purposes, we will treat them the * same. */ return 'image/jpeg'; } elseif ($type == 'image/x-png') { /* image/x-png is equivalent to image/png. */ return 'image/png'; } else { return $type; } } /** * Generate HTML output for a javascript auto-resize view window. * * @access private * * @param string $url The URL which contains the actual image data. * @param string $title The title to use for the page. * * @return string The HTML output. */ function _popupImageWindow($url, $title) { global $browser; $str = << $title EOD; /* Only use javascript if we are using a DOM capable browser. */ if ($browser->getFeature('dom')) { /* Translate '&' entities to '&' for JS URL links. */ $url = str_replace('&', '&', $url); /* Javascript display. */ $loading = _("Loading..."); $str .= << function resizeWindow() { var h, img = document.getElementById('disp_image'), w; document.getElementById('splash').style.display = 'none'; img.style.display = 'block'; window.moveTo(0, 0); h = img.height - (window.innerHeight ? window.innerHeight : (document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight)); w = img.width - (window.innerWidth ? window.innerWidth : (document.documentElement.clientWidth ? document.documentElement.clientWidth : document.body.clientWidth)); window.resizeBy(w, h); self.focus(); } $loading EOD; } else { /* Non-javascript display. */ $img_txt = _("Image"); $str .= << $img_txt EOD; } return $str; } } * @package Horde_MIME_Viewer */ class MIME_Viewer_msexcel extends MIME_Viewer { /** * Render out the currently data using xlhtml. * * @param array $params Any params this Viewer may need. * * @return string The rendered data. */ function render($params = array()) { global $mime_drivers; /* Check to make sure the program actually exists. */ if (!file_exists($mime_drivers['horde']['msexcel']['location'])) { return '
' . sprintf(_("The program used to view this data type (%s) was not found on the system."), $mime_drivers['horde']['msexcel']['location']) . '
'; } $data = ''; $tmp_xls = Horde::getTempFile('horde_msexcel'); $fh = fopen($tmp_xls, 'w'); fwrite($fh, $this->mime_part->getContents()); fclose($fh); $fh = popen($mime_drivers['horde']['msexcel']['location'] . " -nh $tmp_xls 2>&1", 'r'); while (($rc = fgets($fh, 8192))) { $data .= $rc; } pclose($fh); return $data; } /** * Return the MIME content type of the rendered content. * * @return string The content type of the output. */ function getType() { return 'text/html; charset=' . NLS::getCharset(); } } * @package Horde_MIME_Viewer */ class MIME_Viewer_mspowerpoint extends MIME_Viewer { /** * Render out the current data using ppthtml. * * @param array $params Any parameters the Viewer may need. * * @return string The rendered contents. */ function render($params = array()) { global $mime_drivers; /* Check to make sure the program actually exists. */ if (!file_exists($mime_drivers['horde']['mspowerpoint']['location'])) { return '
' . sprintf(_("The program used to view this data type (%s) was not found on the system."), $mime_drivers['horde']['mspowerpoint']['location']) . '
'; } $data = ''; $tmp_ppt = Horde::getTempFile('horde_mspowerpoint'); $fh = fopen($tmp_ppt, 'w'); fwrite($fh, $this->mime_part->getContents()); fclose($fh); $fh = popen($mime_drivers['horde']['mspowerpoint']['location'] . " $tmp_ppt 2>&1", 'r'); while (($rc = fgets($fh, 8192))) { $data .= $rc; } pclose($fh); return $data; } /** * Return the MIME content type of the rendered content. * * @return string The content type of the output. */ function getType() { return 'text/html; charset=' . NLS::getCharset(); } } * @package Horde_MIME_Viewer */ class MIME_Viewer_msword extends MIME_Viewer { /** * Render out the current data using wvWare. * * @param array $params Any parameters the Viewer may need. * * @return string The rendered contents. */ function render($params = array()) { global $mime_drivers; /* Check to make sure the program actually exists. */ if (!file_exists($mime_drivers['horde']['msword']['location'])) { return '
' . sprintf(_("The program used to view this data type (%s) was not found on the system."), $mime_drivers['horde']['msword']['location']) . '
'; } $data = ''; $tmp_word = Horde::getTempFile('msword'); $tmp_output = Horde::getTempFile('msword'); $tmp_dir = Horde::getTempDir(); $tmp_file = str_replace($tmp_dir . '/', '', $tmp_output); if (OS_WINDOWS) { $args = ' -x ' . dirname($mime_drivers['horde']['msword']['location']) . "\\wvHtml.xml -d $tmp_dir -1 $tmp_word > $tmp_output"; } else { $version = exec($mime_drivers['horde']['msword']['location'] . ' --version'); if (version_compare($version, '0.7.0') >= 0) { $args = " --charset=" . NLS::getCharset() . " --targetdir=$tmp_dir $tmp_word $tmp_file"; } else { $args = " $tmp_word $tmp_output"; } } $fh = fopen($tmp_word, 'w'); fwrite($fh, $this->mime_part->getContents()); fclose($fh); exec($mime_drivers['horde']['msword']['location'] . $args); if (!file_exists($tmp_output)) { return _("Unable to translate this Word document"); } return file_get_contents($tmp_output); } /** * Return the MIME content type of the rendered content. * * @return string The content type of the output. */ function getType() { return 'text/html; charset=' . NLS::getCharset(); } } =   height: ; width: ; height: ; width: ; margin-left:;             * * * * # Matching child document header No. absolute-chapter-level: encodedTitle: globalDocumentRefToCurrentFile: *** + Matching global document header No. absolute-chapter-level: encodedTitle: contentTableURL: *** + # + Creation of global document helper variable for the content table.... Finished the Creation of global document helper variable for the content table! Creation of global document helper variable for the child documents.... Finished the Creation of global document helper variable for the child documents! # level: title: encoded-title: file-url: header-no: ** ** ** childrenHeadings/heading-count: # title: ** Creating global document variable for chapter relations.... Finished global document variable for chapter relations! *** new heading currentChapterID: currentChapterTitle: currentChapterID: currentHeadingNo: headingTitle: headingLevel: headingNo: newChildURL: only a heading, but not a chapter All child documents have been walked through without finding the chapter name! childrenHeadings/heading-count: currentHeadingNo: Parsing the global document... Parsing the child documents... Starting the child transformations... Contentable data exists as global data! No Contentable global data exists! Java method transformChildren to transform all children of a global document could not be found. Be sure to use the XT processor. [ Previous document | # Content Table | Next document ] **** THE HEADING VARIABLE **** content-table-url: **** new heading: content-table-id: child-document-no: file-url: out-file-url: level: title: encoded-title: absolute-chapter-level: 0 width: align: right CSS helper variable will be created.... CSS variable ready, header will be created.... CSS header creation finished! description Creating the inline styles.... Time for instantiating style variable: ms Creating the inline styles.... Time for instantiating style variable: ms Parameter dpi: Parameter metaFileURL: Parameter stylesFileURL: Parameter absoluteSourceDirRef: Parameter precedingChapterLevel1 : Parameter precedingChapterLevel2 : Parameter precedingChapterLevel3 : Parameter precedingChapterLevel4 : Parameter precedingChapterLevel5 : Parameter precedingChapterLevel6 : Parameter precedingChapterLevel7 : Parameter precedingChapterLevel8 : Parameter precedingChapterLevel9 : Parameter precedingChapterLevel10: PalmComputingPlatform true HandheldFriendly true HistoryListText Dateimanager : &date &time description StarPortal keywords starportal, staroffice, software Content-Type text/html; charset=iso-8859-1 left right center left right center #000000 #FFFFFF #000000 #FFFFFF The CSS style header method for setting styles text/css // { } *.OOo_defaults , , { margin-top:0cm; margin-bottom:0cm; } , , { } , INDX( @w(.hV-ii`(%xNq; common.xsl/xh-ii`(%~sX_U global_document.xsl/pZ-ii`(%~sX_U GLOBAL~1.XSL0p\-ii`(%w`[] main_html.xsl0pZ-ii`(%w`[] MAIN_H~1.XSL1hR-ii`(%yH_C palm.xsl2xb-ii`(%>|HD style_header.xsl3xd-ii%>|`\ style_inlined.xsl4xd-ii%>| style_mapping.xsl2pZ-ii`(%>|HD STYLE_~1.XSL3pZ-ii%>|`\ STYLE_~2.XSL4pZ-ii%>| STYLE_~3.XSL5hT-ii%{'}HG table.xsl6p`-ii%{'}pl table_cells.xsl7xd-ii%{'}0) table_columns.xsl8p^-ii%)} U table_rows.xsl6pZ-ii%{'}pl TABLE_~1.XSL7pZ-ii%{'}0) TABLE_~2.XSL8pZ-ii%)} U TABLE_~3.XSL float: right; float: left; align: left; align: right; align: center; padding: ; border-width:; border-style:; border-color:; border-width:; border-style:; border-color:; border-width:; border-style:; border-color:; border-top: ; border-bottom: ; border-left: ; border-right: ; width:; width:; height:; height:; width:; width:; :; font-family: ; font-style:italic; font-weight:bold; :; :; :; :; :; :; :; :; :; text-align:left ! important; text-align:right ! important; text-align: ! important; :; background-color:; background-color:; background-image:url(); background-repeat:repeat; background-repeat:no-repeat; :; text-decoration:line-through; text-decoration:underline; vertical-align:sub; vertical-align:sup; italic, bold, underline, align:left, align:right, align:center, strike, size::size, color:#FFFFFF, color:#000000, size::size, width::width, width::width; height::height; height::height; width::width; width::width; left right center maxRowLength: numberOfHiddenColumns: 1 #000000 2 0 page-break-inside:avoid Time for checking BorderStyle: ms --------------> table:table-cell has been entered with node value: table:number-columns-repeated: -- 1 NEW VALUE: column-position: -- +++++++++ starting cell writing +++++++++ number-columns-repeated: -- maxRowLength: -- column-position: -- +++++++++ cell repetition +++++++++ WriteTest -> If nothing between '-' write cell -- TABLE COLUMN is hidden! TABLE COLUMN is hidden! th td *****************************************'' element has been added! text-align:right; text-align:left; text-align:right; text-align:left;   text-align:right; text-align:left;   , ; true DebugInformation: For each 'column-style-entry' of the 'allColumnStyleEntries' variable the style-name is given out. In case of 'column-hidden-flag' attribute the text 'column is hidden' is given out. column is hidden = *************************'tr' element has been added! * @author Jan Schneider * @package Horde_MIME_Viewer */ class MIME_Viewer_ooo extends MIME_Viewer { /** * Render out the current data. * * @param array $params Any parameters the Viewer may need. * * @return string The rendered contents. */ function render($params = array()) { $use_xslt = Util::extensionExists('xslt') || function_exists('domxml_xslt_stylesheet_file'); if ($use_xslt) { $tmpdir = Util::createTempDir(true); } require_once 'Horde/Compress.php'; $xml_tags = array('text:p', 'table:table ', 'table:table-row', 'table:table-cell', 'table:number-columns-spanned='); $html_tags = array('p', 'table border="0" cellspacing="1" cellpadding="0" ', 'tr bgcolor="#cccccc"', 'td', 'colspan='); $zip = &Horde_Compress::singleton('zip'); $list = $zip->decompress($this->mime_part->getContents(), array('action' => HORDE_COMPRESS_ZIP_LIST)); foreach ($list as $key => $file) { if ($file['name'] == 'content.xml' || $file['name'] == 'styles.xml' || $file['name'] == 'meta.xml') { $content = $zip->decompress($this->mime_part->getContents(), array('action' => HORDE_COMPRESS_ZIP_DATA, 'info' => $list, 'key' => $key)); if ($use_xslt) { $fp = fopen($tmpdir . $file['name'], 'w'); fwrite($fp, $content); fclose($fp); } elseif ($file['name'] == 'content.xml') { $content = str_replace($xml_tags, $html_tags, $content); return $content; } } } if (!Util::extensionExists('xslt')) { return; } if (function_exists('domxml_xslt_stylesheet_file')) { // Use DOMXML $xslt = domxml_xslt_stylesheet_file(dirname(__FILE__) . '/ooo/main_html.xsl'); $dom = domxml_open_file($tmpdir . 'content.xml'); $result = @$xslt->process($dom, array('metaFileURL' => $tmpdir . 'meta.xml', 'stylesFileURL' => $tmpdir . 'styles.xml', 'disableJava' => true)); return String::convertCharset($xslt->result_dump_mem($result), 'UTF-8', NLS::getCharset()); } else { // Use XSLT $xslt = xslt_create(); $result = @xslt_process($xslt, $tmpdir . 'content.xml', dirname(__FILE__) . '/ooo/main_html.xsl', null, null, array('metaFileURL' => $tmpdir . 'meta.xml', 'stylesFileURL' => $tmpdir . 'styles.xml', 'disableJava' => true)); if (!$result) { $result = xslt_error($xslt); } xslt_free($xslt); return String::convertCharset($result, 'UTF-8', NLS::getCharset()); } } /** * Return the MIME content type of the rendered content. * * @return string The content type of the output. */ function getType() { return 'text/html; charset=' . NLS::getCharset(); } } * @package Horde_MIME_Viewer */ class MIME_Viewer_pdf extends MIME_Viewer { /** * Return the content-type. * * @return string The content-type of the output. */ function getType() { return 'application/pdf'; } } * @package Horde_MIME_Viewer */ class MIME_Viewer_php extends MIME_Viewer_source { /** * Renders out the contents. * * @param array $params Any parameters the Viewer may need. * * @return string The rendered contents. */ function render($params = array()) { ini_set('highlight.comment', 'comment'); ini_set('highlight.default', 'default'); ini_set('highlight.keyword', 'keyword'); ini_set('highlight.string', 'string'); ini_set('highlight.html', 'html'); $code = $this->mime_part->getContents(); if (strpos($code, 'lineNumber(str_replace('<?php ', '', highlight_string('lineNumber(highlight_string($code, true)); } // Educated guess at whether we are inline or not. if (headers_sent() || ob_get_length()) { return $results; } else { return Util::bufferOutput('require', $GLOBALS['registry']->get('templates', 'horde') . '/common-header.inc') . $results . Util::bufferOutput('require', $GLOBALS['registry']->get('templates', 'horde') . '/common-footer.inc'); } } /** * Add line numbers to a block of code. * * @param string $code The code to number. */ function lineNumber($code, $linebreak = "\n") { // Clean up. $code = preg_replace(array('/\s*/', '/\s*/', '/\s*<\/span>\s*<\/span>\s*<\/code>/', '/\s*<\/font>\s*<\/font>\s*<\/code>/'), '', $code); $code = str_replace(array(' ', '&', '
', '', ), array(' ', '&', "\n", '', ), $code); $code = trim($code); // Normalize newlines. $code = str_replace("\r", '', $code); $code = preg_replace('/\n\n\n+/', "\n\n", $code); $lines = explode("\n", $code); $results = array('
    '); $previous = false; foreach ($lines as $lineno => $line) { if (substr($line, 0, 7) == '') { $previous = false; $line = substr($line, 7); } if (empty($line)) { $line = ' '; } if ($previous) { $line = "" . $line; } // Save the previous style. if (strpos($line, '') { $previous = false; } elseif ($previous) { $line .= ''; } $results[] = '
  1. ' . $line . '
  2. '; } $results[] = '
'; return implode("\n", $results); } } * @author Michael Slusarz * @package Horde_MIME_Viewer */ class MIME_Viewer_plain extends MIME_Viewer { /** * Render out the contents. * * @param array $params Any parameters the Viewer may need. * * @return string The rendered contents. */ function render($params = array()) { require_once 'Horde/MIME/Contents.php'; $text = $this->mime_part->getContents(); /* Check for 'flowed' text data. */ $flowed = ($this->mime_part->getContentTypeParameter('format') == 'flowed'); if ($flowed) { $text = $this->_formatFlowed($text); } /* If calling as an attachment from view.php, we do not want to alter the text in any way with HTML. */ if (MIME_Contents::viewAsAttachment()) { return $text; } else { require_once 'Horde/Text/Filter.php'; return Text_Filter::filter($text, 'text2html', array('parselevel' => TEXT_HTML_MICRO, 'charset' => null, 'class' => null)); } } /** * Return the MIME content type of the rendered content. * * @return string The content type of the output. */ function getType() { require_once 'Horde/MIME/Contents.php'; return (MIME_Contents::viewAsAttachment()) ? $this->mime_part->getType(true) : 'text/html; charset=' . NLS::getCharset(); } /** * Format flowed text for HTML output. * * @param string $text The text to format. * @param integer $opt The optimal length to wrap. * @param integer $max The maximum length to wrap. 0 means don't wrap. * @param boolean $delsp Was text created with DelSp formatting? * * @return string The formatted text. */ function _formatFlowed($text, $opt = null, $max = null, $delsp = null) { require_once 'Text/Flowed.php'; $flowed = &new Text_Flowed($this->mime_part->replaceEOL($text, "\n"), $this->mime_part->getCharset()); if (!is_null($opt)) { $flowed->setOptLength($opt); } if (!is_null($max)) { $flowed->setMaxLength($max); } if (!is_null($delsp)) { $flowed->setDelSp($delsp); } return $flowed->toFixed(); } } * @author Michael Cochrane * @package Horde_MIME_Viewer */ class MIME_Viewer_rar extends MIME_Viewer { /** * Rar compression methods. * * @var array */ var $_methods = array( 0x30 => 'Store', 0x31 => 'Fastest', 0x32 => 'Fast', 0x33 => 'Normal', 0x34 => 'Good', 0x35 => 'Best' ); /** * Render out the currently set contents using rar. * * @param array $params Any parameters the Viewer may need. * * @return string The rendered contents. */ function render($params = array()) { $contents = $this->mime_part->getContents(); /* Make sure this is a valid rar file. */ if ($this->checkRarData($contents) === false) { return '
' . _("This does not appear to be a valid rar archive.") . '
'; } require_once 'Horde/Text.php'; $rarData = $this->getRarData($contents); $fileCount = count($rarData); $text = '' . htmlspecialchars(sprintf(_("Contents of \"%s\""), $this->mime_part->getName())) . ':' . "\n"; $text .= '
'; $text .= Text::htmlAllSpaces(_("Archive Name") . ': ' . $this->mime_part->getName()) . "\n"; $text .= Text::htmlAllSpaces(_("Archive File Size") . ': ' . strlen($contents) . ' bytes') . "\n"; $text .= Text::htmlAllSpaces(sprintf(ngettext("File Count: %d file", "File Count: %d files", $fileCount), $fileCount)); $text .= "\n\n"; $text .= Text::htmlAllSpaces( String::pad(_("File Name"), 50, ' ', STR_PAD_RIGHT) . String::pad(_("Attributes"), 10, ' ', STR_PAD_LEFT) . String::pad(_("Size"), 10, ' ', STR_PAD_LEFT) . String::pad(_("Modified Date"), 19, ' ', STR_PAD_LEFT) . String::pad(_("Method"), 10, ' ', STR_PAD_LEFT) . String::pad(_("Ratio"), 10, ' ', STR_PAD_LEFT) ) . "\n"; $text .= str_repeat('-', 109) . "\n"; foreach ($rarData as $val) { $ratio = (empty($val['size'])) ? 0 : 100 * ($val['csize'] / $val['size']); $text .= Text::htmlAllSpaces( String::pad($val['name'], 50, ' ', STR_PAD_RIGHT) . String::pad($val['attr'], 10, ' ', STR_PAD_LEFT) . String::pad($val['size'], 10, ' ', STR_PAD_LEFT) . String::pad(strftime("%d-%b-%Y %H:%M", $val['date']), 19, ' ', STR_PAD_LEFT) . String::pad($val['method'], 10, ' ', STR_PAD_LEFT) . String::pad(sprintf("%1.1f%%", $ratio), 10, ' ', STR_PAD_LEFT) ) . "\n"; } $text .= str_repeat('-', 106) . "\n"; $text .= '
'; return nl2br($text); } /** * Returns the MIME type of this part. * * @return string The MIME type of this part. */ function getType() { return 'text/html; charset=' . NLS::getCharset(); } /** * Checks to see if the data is a valid Rar archive. * * @param string &$data The rar archive data. * * @return boolean True if valid, false if invalid. */ function checkRarData(&$data) { $fileHeader = "\x52\x61\x72\x21\x1a\x07\x00"; if (strpos($data, $fileHeader) === false) { return false; } else { return true; } } /** * Get the list of files/data from the rar archive. * * @param string &$data The rar archive data. * * @return array KEY: Position in RAR archive * VALUES: 'attr' -- File attributes * 'date' -- File modification time * 'csize' -- Compressed file size * 'method' -- Compression method * 'name' -- Filename * 'size' -- Original file size */ function getRarData(&$data) { $return_array = array(); $blockStart = strpos($data, "\x52\x61\x72\x21\x1a\x07\x00"); $position = $blockStart + 7; while ($position < strlen($data)) { $head_crc = substr($data, $position + 0, 2); $head_type = ord(substr($data, $position + 2, 1)); $head_flags = unpack('vFlags', substr($data, $position + 3, 2)); $head_flags = $head_flags['Flags']; $head_size = unpack('vSize', substr($data, $position + 5, 2)); $head_size = $head_size['Size']; $position += 7; $head_size -= 7; switch ($head_type) { case 0x73: /* Archive header */ $position += $head_size; break; case 0x74: $file = array(); /* File Header */ $info = unpack('VPacked/VUnpacked/COS/VCRC32/VTime/CVersion/CMethod/vLength/vAttrib', substr($data, $position)); $file['name'] = substr($data, $position + 25, $info['Length']); $file['size'] = $info['Unpacked']; $file['csize'] = $info['Packed']; $file['date'] = mktime((($info['Time'] >> 11) & 0x1f), (($info['Time'] >> 5) & 0x3f), (($info['Time'] << 1) & 0x3e), (($info['Time'] >> 21) & 0x07), (($info['Time'] >> 16) & 0x1f), ((($info['Time'] >> 25) & 0x7f) + 80)); $file['method'] = $this->_methods[$info['Method']]; $file['attr'] = ''; $file['attr'] .= ($info['Attrib'] & 0x10) ? 'D' : '-'; $file['attr'] .= ($info['Attrib'] & 0x20) ? 'A' : '-'; $file['attr'] .= ($info['Attrib'] & 0x03) ? 'S' : '-'; $file['attr'] .= ($info['Attrib'] & 0x02) ? 'H' : '-'; $file['attr'] .= ($info['Attrib'] & 0x01) ? 'R' : '-'; $return_array[] = $file; $position += $head_size; $position += $info['Packed']; break; default: $position += $head_size; if (isset($add_size)) { $position += $add_size; } break; } } return $return_array; } } * @package Horde_MIME_Viewer */ class MIME_Viewer_report extends MIME_Viewer { /** * Stores the MIME_Viewer of the specified protocol. * * @var MIME_Viewer */ var $_viewer; /** * Render the multipart/report data. * * @param array $params An array of parameters needed. * * @return string The rendered data. */ function render($params = array()) { /* Get the appropriate MIME_Viewer for the protocol specified. */ if (!($this->_resolveViewer())) { return; } /* Render using the loaded MIME_Viewer object. */ return $this->_viewer->render($params); } /** * Returns the content-type of the Viewer used to view the part. * * @return string A content-type string. */ function getType() { /* Get the appropriate MIME_Viewer for the protocol specified. */ if (!($this->_resolveViewer())) { return 'application/octet-stream'; } else { return $this->_viewer->getType(); } } /** * Load a MIME_Viewer according to the report-type parameter stored * in the MIME_Part to render. If unsuccessful, try to load a generic * multipart MIME_Viewer. * * @access private * * @return boolean True on success, false on failure. */ function _resolveViewer() { $type = $viewer = null; if (empty($this->_viewer)) { if (($type = $this->mime_part->getContentTypeParameter('report-type'))) { $viewer = &MIME_Viewer::factory($this->mime_part, 'message/' . String::lower($type)); $type = $this->mime_part->getPrimaryType(); } else { /* If report-type is missing, the message is an improper * multipart/report message. Attempt to fall back to a * multipart/mixed viewer instead. */ $type = 'multipart'; } if (empty($viewer) || (String::lower(get_class($viewer)) == 'mime_viewer_default')) { if (!($viewer = &MIME_Viewer::factory($this->mime_part, $type . '/*'))) { return false; } } $this->_viewer = $viewer; } return true; } } * @package Horde_MIME_Viewer */ class MIME_Viewer_rfc822 extends MIME_Viewer { /** * Render out the currently set contents. * * @param array $params An array with any parameters needed. * * @return string The rendered text. */ function render($params = array()) { if (!$this->mime_part) { require_once 'Horde/MIME/Contents.php'; $contents = new MIME_Contents(new MIME_Part()); return $contents->formatStatusMsg(_("There was an error displaying this message part")); } $part =& Util::cloneObject($this->mime_part); $part->transferDecodeContents(); $text = $part->getContents(); if (!$text) { require_once 'Horde/MIME/Contents.php'; $contents = new MIME_Contents(new MIME_Part()); return $contents->formatStatusMsg(_("There was an error displaying this message part")); } else { return $text; } } /** * Render out attachment information. * * @param array $params An array with any parameters needed. * * @return string The rendered text in HTML. */ function renderAttachmentInfo($params = array()) { if (!$this->mime_part) { return ''; } /* Get the text of the part. Since we need to look for the end of * the headers by searching for the CRLFCRLF sequence, use * getCanonicalContents() to make sure we are getting the text with * CRLF's. */ $text = $this->mime_part->getCanonicalContents(); if (empty($text)) { return ''; } /* Search for the end of the header text (CRLFCRLF). */ $text = substr($text, 0, strpos($text, "\r\n\r\n")); /* Get the list of headers now. */ require_once 'Horde/MIME/Structure.php'; $headers = MIME_Structure::parseMIMEHeaders($text, true, true); $header_array = array( 'date' => _("Date"), 'from' => _("From"), 'to' => _("To"), 'cc' => _("Cc"), 'bcc' => _("Bcc"), 'reply-to' => _("Reply-To"), 'subject' => _("Subject") ); $header_output = array(); foreach ($header_array as $key => $val) { if (isset($headers[$key])) { if (is_array($headers[$key])) { $headers[$key] = $headers[$key][0]; } $header_output[] = '' . $val . ': ' . htmlspecialchars($headers[$key]); } } require_once 'Horde/Text/Filter.php'; return '
' . Text_Filter::filter(implode("
\n", $header_output), 'emails') . '
'; } /** * Return the MIME content type for the rendered data. * * @return string The content type of the data. */ function getType() { return 'text/plain; charset=' . NLS::getCharset(); } } " to * "<", converts CRLFs to SPACE, converts to a newline according to * local newline convention, removes everything between a command * and the next balancing command, and removes all other * formatting commands (all text enclosed in angle brackets). * * We implement the following tags: * , , , , , ,
, * , , , , , , * , , , , * * The following tags are implemented differently than described in the RFC * (due to limitations in HTML output): * - Output as centered, bold text. * - Output as centered, bold text. * - Output as paragraph break. * * The following tags are NOT implemented: * , , , , , * , * * $Horde: framework/MIME/MIME/Viewer/richtext.php,v 1.4.10.14 2009-01-06 15:23:21 jan Exp $ * * Copyright 2004-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. * * @author Michael Slusarz * @package Horde_MIME_Viewer */ class MIME_Viewer_richtext extends MIME_Viewer { /** * Render out the currently set contents in HTML format. * * @param array $params Any parameters the Viewer may need. * * @return string The rendered contents. */ function render($params = array()) { if (($text = $this->mime_part->getContents()) === false) { return false; } if (trim($text) == '') { return $text; } /* Use str_ireplace() if using PHP 5.0+. */ $has_str_ireplace = function_exists('str_ireplace'); /* We add space at the beginning and end of the string as it will * make some regular expression checks later much easier (so we * don't have to worry about start/end of line characters). */ $text = ' ' . $text . ' '; /* Remove everything between tags. */ $text = preg_replace('/.*<\/comment>/Uis', '', $text); /* Remove any unrecognized tags in the text. We don't need * in $tags since it doesn't do anything anyway. All tags * have already been removed. */ $tags = '
'; $text = strip_tags($text, $tags); /* becomes a '<'. CRLF becomes a SPACE. */ if ($has_str_ireplace) { $text = str_ireplace(array('', "\r\n"), array('<', ' '), $text); } else { $text = preg_replace(array('//i', "/\r\n/"), array('<', ' '), $text); } /* We try to protect against bad stuff here. */ $text = @htmlspecialchars($text, ENT_QUOTES, $this->mime_part->getCharset()); /* becomes a newline (
); * becomes a paragraph break (

). */ if ($has_str_ireplace) { $text = str_ireplace(array('<nl>', '<np>'), array('
', '

'), $text); } else { $text = preg_replace(array('/(?', '

'), $text); } /* Now convert the known tags to html. Try to remove any tag * parameters to stop people from trying to pull a fast one. */ $pattern = array( '/(?\1', '\1', '\1', '\1', '\1', '\1', '

\1
', '
\1
', '
\1
', '
\1
', '\1', '\1', '\1', '
\1

', '
\1

', '

\1

', '
\1
', ); $text = preg_replace($pattern, $replace, $text); /* Now we remove the leading/trailing space we added at the start. */ $text = substr($text, 1, -1); /* Wordwrap. */ $text = str_replace(array("\t", ' ', "\n "), array(' ', '  ', "\n "), $text); if ($text[0] == ' ') { $text = ' ' . substr($text, 1); } return '

' . nl2br($text) . '

'; } /** * Return the MIME content type of the rendered content. * * @return string The content type of the output. */ function getType() { return 'text/html; charset=' . NLS::getCharset(); } } * @package Horde_MIME_Viewer */ class MIME_Viewer_rpm extends MIME_Viewer { /** * Render out the RPM contents. * * @param array $params Any parameters the Viewer may need. * * @return string The rendered contents. */ function render($params = array()) { global $mime_drivers; /* Check to make sure the program actually exists. */ if (!file_exists($mime_drivers['horde']['rpm']['location'])) { return '
' . sprintf(_("The program used to view this data type (%s) was not found on the system."), $mime_drivers['horde']['rpm']['location']) . '
'; } $data = ''; $tmp_rpm = Horde::getTempFile('horde_rpm'); $fh = fopen($tmp_rpm, 'w'); fwrite($fh, $this->mime_part->getContents()); fclose($fh); $fh = popen($mime_drivers['horde']['rpm']['location'] . " -qip $tmp_rpm 2>&1", 'r'); while (($rc = fgets($fh, 8192))) { $data .= $rc; } pclose($fh); return '
' . htmlentities($data) . '
'; } /** * Return the MIME content type of the rendered content. * * @return string The content type of the output. */ function getType() { return 'text/html; charset=' . NLS::getCharset(); } } * * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * * @author Duck * @since Horde 3.2 * @package Horde_MIME_Viewer */ class MIME_Viewer_rtf extends MIME_Viewer { /** * Render out the current data using UnRTF. * * @param array $params Any parameters the viewer may need. * * @return string The rendered contents. */ function render($params = array()) { global $mime_drivers; /* Check to make sure the program actually exists. */ if (!file_exists($mime_drivers['horde']['rtf']['location'])) { return '
' . sprintf(_("The program used to view this data type (%s) was not found on the system."), $mime_drivers['horde']['rtf']['location']) . '
'; } $tmp_rtf = Horde::getTempFile('rtf'); $tmp_output = Horde::getTempFile('rtf'); $args = " $tmp_rtf > $tmp_output"; $fh = fopen($tmp_rtf, 'w'); fwrite($fh, $this->mime_part->getContents()); fclose($fh); exec($mime_drivers['horde']['rtf']['location'] . $args); if (!file_exists($tmp_output)) { return _("Unable to translate this RTF document"); } return file_get_contents($tmp_output); } /** * Return the MIME content type of the rendered content. * * @return string The content type of the output. */ function getType() { return 'text/html; charset=' . NLS::getCharset(); } } * @since Horde 3.0 * @package Horde_MIME_Viewer */ class MIME_Viewer_security extends MIME_Viewer { /** * Stores the MIME_Viewer of the specified security protocol. * * @var MIME_Viewer */ var $_viewer; /** * The $mime_part class variable has the information to render * out, encapsulated in a MIME_Part object. * * @param $params mixed The parameters (if any) to pass to the underlying * MIME_Viewer. * * @return string Rendering of the content. */ function render($params = array()) { /* Get the appropriate MIME_Viewer for the protocol specified. */ if (!($this->_resolveViewer())) { return; } /* Render using the loaded MIME_Viewer object. */ return $this->_viewer->render($params); } /** * Returns the content-type of the Viewer used to view the part. * * @return string A content-type string. */ function getType() { /* Get the appropriate MIME_Viewer for the protocol specified. */ if (!($this->_resolveViewer())) { return 'application/octet-stream'; } else { return $this->_viewer->getType(); } } /** * Load a MIME_Viewer according to the protocol parameter stored * in the MIME_Part to render. If unsuccessful, try to load a generic * multipart MIME_Viewer. * * @access private * * @return boolean True on success, false on failure. */ function _resolveViewer() { $viewer = null; if (empty($this->_viewer)) { $protocol = $this->mime_part->getContentTypeParameter('protocol'); if (empty($protocol)) { return false; } $viewer = &MIME_Viewer::factory($this->mime_part, $protocol); if (empty($viewer) || (String::lower(get_class($viewer)) == 'mime_viewer_default')) { $viewer = &MIME_Viewer::factory($this->mime_part, $this->mime_part->getPrimaryType() . '/*'); if (empty($viewer)) { return false; } } $this->_viewer = $viewer; } return true; } } * @since Horde 3.0 * @package Horde_MIME_Viewer */ class MIME_Viewer_simple extends MIME_Viewer { /** * Renders out the contents. * * @param array $params Any parameters the Viewer may need. * * @return string The rendered contents. */ function render($params = array()) { // Bug #8311: Unknown text parts should not be rendered inline. return MIME_Contents::viewAsAttachment() ? parent::render($params) : _("Can not display contents of text part inline."); } /** * Return the MIME type of the rendered content. * * @return string MIME-type of the output content. */ function getType() { return 'text/plain'; } } * @since Horde 3.2 * @package Horde_MIME_Viewer */ class MIME_Viewer_smil extends MIME_Viewer { /** * Handle for the XML parser object. * * @var resource */ var $_parser; /** * String buffer to hold the generated content * * @var string */ var $_content = ''; /** * Renders out the contents. * * @param array $params Any parameters the Viewer may need. * * @return string The rendered contents. */ function render($params = array()) { /* Create a new parser and set its default properties. */ $this->_parser = xml_parser_create(); xml_set_object($this->_parser, $this); xml_set_element_handler($this->_parser, '_startElement', '_endElement'); xml_set_character_data_handler($this->_parser, '_defaultHandler'); xml_parse($this->_parser, $this->mime_part->getContents(), true); return $this->_content; } /** * User-defined function callback for start elements. * * @access private * * @param object $parser Handle to the parser instance. * @param string $name The name of this XML element. * @param array $attrs List of this element's attributes. */ function _startElement($parser, $name, $attrs) { switch ($name) { case 'IMG': if (isset($attrs['SRC'])) { $this->_content .= ''; } break; } } /** * User-defined function callback for end elements. * * @access private * * @param object $parser Handle to the parser instance. * @param string $name The name of this XML element. */ function _endElement($parser, $name) { } /** * User-defined function callback for character data. * * @access private * * @param object $parser Handle to the parser instance. * @param string $data String of character data. */ function _defaultHandler($parser, $data) { $data = trim($data); if (!empty($data)) { $this->_content .= ' ' . htmlspecialchars($data); } } /** * Return the MIME content type of the rendered content. * * @return string The content type of the output. */ function getType() { return 'text/html; charset=' . NLS::getCharset(); } } * @package Horde_MIME_Viewer */ class MIME_Viewer_source extends MIME_Viewer { /** * Add line numbers to a block of code. * * @param string $code The code to number. */ function lineNumber($code, $linebreak = "\n") { $lines = substr_count($code, $linebreak) + 1; $html = '
'; for ($l = 1; $l <= $lines; $l++) { $html .= sprintf('%s
', $l, $l, $l) . "\n"; } return $html . '
' . $code . '
'; } /** * Return the MIME content type of the rendered content. * * @return string The content type of the output. */ function getType() { return 'text/html; charset=' . NLS::getCharset(); } } * @package Horde_MIME_Viewer */ class MIME_Viewer_srchighlite extends MIME_Viewer_source { /** * Render out the currently set contents using Source-highlight * * @param array $params Any parameters the viewer may need. * * @return string The rendered contents. */ function render($params = array()) { global $mime_drivers; /* Check to make sure the program actually exists. */ if (!file_exists($mime_drivers['horde']['srchighlite']['location'])) { return '
' . sprintf(_("The program used to view this data type (%s) was not found on the system."), $mime_drivers['horde']['srchighlite']['location']) . '
'; } /* Create temporary files for Webcpp. */ $tmpin = Horde::getTempFile('SrcIn'); $tmpout = Horde::getTempFile('SrcOut', false); /* Write the contents of our buffer to the temporary input file. */ $contents = $this->mime_part->getContents(); $fh = fopen($tmpin, 'wb'); fwrite($fh, $contents, strlen($contents)); fclose($fh); /* Determine the language from the mime type. */ $lang = ''; switch ($this->mime_part->getType()) { case 'text/x-java': $lang = 'java'; break; case 'text/x-csrc': case 'text/x-c++src': case 'text/cpp': $lang = 'cpp'; break; case 'application/x-perl': $lang = 'perl'; break; case 'application/x-php': case 'x-extension/phps': case 'x-extension/php3s': case 'application/x-httpd-php': case 'application/x-httpd-php3': case 'application/x-httpd-phps': $lang = 'php3'; break; case 'application/x-python': $lang = 'python'; break; // $lang = 'prolog'; // break; // $lang = 'flex'; // break; // $lang = 'changelog'; // break; // $lang = 'ruby'; // break; } /* Execute Source-Highlite. */ exec($mime_drivers['horde']['srchighlite']['location'] . " --src-lang $lang --out-format xhtml --input $tmpin --output $tmpout"); $results = file_get_contents($tmpout); unlink($tmpout); /* Educated Guess at whether we are inline or not. */ if (headers_sent() || ob_get_length()) { return $this->lineNumber($results); } else { return Util::bufferOutput('require', $GLOBALS['registry']->get('templates', 'horde') . '/common-header.inc') . $this->lineNumber($results) . Util::bufferOutput('require', $GLOBALS['registry']->get('templates', 'horde') . '/common-footer.inc'); } } } * @author Michael Cochrane * @package Horde_MIME_Viewer */ class MIME_Viewer_tgz extends MIME_Viewer { /** * Render out the currently set tar file contents. * * @param array $params Any parameters the Viewer may need. * * @return string The rendered contents. */ function render($params = array()) { $contents = $this->mime_part->getContents(); /* Only decompress gzipped files. */ $subtype = $this->mime_part->getSubType(); if (($subtype == 'x-compressed-tar') || ($subtype == 'tgz') || ($subtype == 'x-tgz') || ($subtype == 'gzip') || ($subtype == 'x-gzip') || ($subtype == 'x-gzip-compressed') || ($subtype == 'x-gtar')) { $gzip = &Horde_Compress::singleton('gzip'); $contents = $gzip->decompress($contents); if (empty($contents)) { return _("Unable to open compressed archive."); } elseif (is_a($contents, 'PEAR_Error')) { return $contents->getMessage(); } } if ($subtype == 'gzip' || $subtype == 'x-gzip' || $subtype == 'x-gzip-compressed') { global $conf; require_once 'Horde/MIME/Magic.php'; $mime_type = MIME_Magic::analyzeData($contents, isset($conf['mime']['magic_db']) ? $conf['mime']['magic_db'] : null); if (!$mime_type) { $mime_type = _("Unknown"); } return sprintf(_("Content type of compressed file: %s"), $mime_type); } /* Obtain the list of files/data in the tar file. */ $tar = Horde_Compress::factory('tar'); $tarData = $tar->decompress($contents); if (is_a($tarData, 'PEAR_Error')) { return $tarData->getMessage(); } $fileCount = count($tarData); $text = '' . htmlspecialchars(sprintf(_("Contents of \"%s\""), $this->mime_part->getName())) . ':' . "\n" . '
' . Text::htmlAllSpaces(_("Archive Name") . ': ' . $this->mime_part->getName()) . "\n" . Text::htmlAllSpaces(_("Archive File Size") . ': ' . strlen($contents) . ' bytes') . "\n" . Text::htmlAllSpaces(sprintf(ngettext("File Count: %d file", "File Count: %d files", $fileCount), $fileCount)) . "\n\n" . Text::htmlAllSpaces( str_pad(_("File Name"), 62, ' ', STR_PAD_RIGHT) . str_pad(_("Attributes"), 15, ' ', STR_PAD_LEFT) . str_pad(_("Size"), 10, ' ', STR_PAD_LEFT) . str_pad(_("Modified Date"), 19, ' ', STR_PAD_LEFT) ) . "\n" . str_repeat('-', 106) . "\n"; foreach ($tarData as $val) { $text .= Text::htmlAllSpaces( str_pad($val['name'], 62, ' ', STR_PAD_RIGHT) . str_pad($val['attr'], 15, ' ', STR_PAD_LEFT) . str_pad($val['size'], 10, ' ', STR_PAD_LEFT) . str_pad(strftime("%d-%b-%Y %H:%M", $val['date']), 19, ' ', STR_PAD_LEFT) ) . "\n"; } return nl2br($text . str_repeat('-', 106) . "\n" . '
'); } /** * Return the content-type * * @return string The content-type of the output. */ function getType() { return 'text/html; charset=' . NLS::getCharset(); } } * @author Michael Slusarz * @package Horde_MIME_Viewer */ class MIME_Viewer_tnef extends MIME_Viewer { /** * Render out the current tnef data. * * @param array $params Any parameters the viewer may need. * * @return string The rendered contents. */ function render($params = array()) { require_once 'Horde/Compress.php'; $tnef = &Horde_Compress::singleton('tnef'); $data = ''; $info = $tnef->decompress($this->mime_part->getContents()); if (empty($info) || is_a($info, 'PEAR_Error')) { $data .= ''; } else { $data .= ''; foreach ($info as $part) { $data .= ''; } } $data .= '
' . _("MS-TNEF Attachment contained no data.") . '
' . _("Name") . '' . _("Mime Type") . '
' . $part['name'] . '' . $part['type'] . '/' . $part['subtype'] . '
'; return $data; } /** * Return the MIME content type of the rendered content. * * @return string The content type of the output. */ function getType() { return 'text/html; charset=' . NLS::getCharset(); } } * @package Horde_MIME_Viewer */ class MIME_Viewer_vcard extends MIME_Viewer { /** * Render out the vcard contents. * * @param array $params Any parameters the Viewer may need. * * @return string The rendered contents. */ function render($params = null) { global $registry, $prefs, $notification; require_once 'Horde/iCalendar.php'; $app = false; $data = $this->mime_part->getContents(); $html = ''; $import_msg = null; $title = _("vCard"); $iCal = new Horde_iCalendar(); if (!$iCal->parsevCalendar($data, 'VCALENDAR', $this->mime_part->getCharset())) { $notification->push( _("There was an error reading the contact data."), 'horde.error'); } if (Util::getFormData('import') && Util::getFormData('source') && $registry->hasMethod('contacts/import')) { $source = Util::getFormData('source'); $count = 0; foreach ($iCal->getComponents() as $c) { if (is_a($c, 'Horde_iCalendar_vcard')) { $contacts = $registry->call('contacts/import', array($c, null, $source)); if (is_a($contacts, 'PEAR_Error')) { $notification->push( _("There was an error importing the contact data:") . ' ' . $contacts->getMessage(), 'horde.error'); continue; } $count++; } } $notification->push(sprintf(ngettext( "%d contact was successfully added to your address book.", "%d contacts were successfully added to your address book.", $count), $count), 'horde.success'); } $html .= ''; $i = 0; foreach ($iCal->getComponents() as $vc) { if ($i > 0) { $html .= ''; } ++$i; $html .= ''; $n = $vc->printableName(); if (!empty($n)) { $html .= $this->_row(_("Name"), $n); } $aliases = $vc->getAttributeValues('ALIAS'); if (!is_a($aliases, 'PEAR_Error')) { $html .= $this->_row(_("Alias"), implode("\n", $aliases)); } $birthdays = $vc->getAttributeValues('BDAY'); if (!is_a($birthdays, 'PEAR_Error')) { require_once 'Horde/Date.php'; $birthday = new Horde_Date($birthdays[0]); $html .= $this->_row( _("Birthday"), $birthday->strftime($prefs->getValue('date_format'))); } $photos = $vc->getAllAttributes('PHOTO'); foreach ($photos as $photo) { if (isset($photo['params']['VALUE']) && String::upper($photo['params']['VALUE']) == 'URI') { $html .= $this->_row(_("Photo"), '', false); } elseif (isset($photo['params']['ENCODING']) && String::upper($photo['params']['ENCODING']) == 'B' && isset($photo['params']['TYPE']) && ($GLOBALS['browser']->hasFeature('dataurl') === true || $GLOBALS['browser']->hasFeature('dataurl') >= strlen($photo['value']))) { $html .= $this->_row(_("Photo"), '', false); } } $labels = $vc->getAllAttributes('LABEL'); foreach ($labels as $label) { if (isset($label['params']['TYPE'])) { if (!is_array($label['params']['TYPE'])) { $label['params']['TYPE'] = array($label['params']['TYPE']); } } else { $label['params']['TYPE'] = array_keys($label['params']); } $types = array(); foreach ($label['params']['TYPE'] as $type) { switch(String::upper($type)) { case 'HOME': $types[] = _("Home Address"); break; case 'WORK': $types[] = _("Work Address"); break; case 'DOM': $types[] = _("Domestic Address"); break; case 'INTL': $types[] = _("International Address"); break; case 'POSTAL': $types[] = _("Postal Address"); break; case 'PARCEL': $types[] = _("Parcel Address"); break; case 'PREF': $types[] = _("Preferred Address"); break; } } if (!count($types)) { $types = array(_("Address")); } $html .= $this->_row(implode('/', $types), $label['value']); } $adrs = $vc->getAllAttributes('ADR'); foreach ($adrs as $item) { if (isset($item['params']['TYPE'])) { if (!is_array($item['params']['TYPE'])) { $item['params']['TYPE'] = array($item['params']['TYPE']); } } else { $item['params']['TYPE'] = array_keys($item['params']); } $address = $item['values']; $a = array(); if (isset($address[VCARD_ADR_STREET])) { $a[] = $address[VCARD_ADR_STREET]; } if (isset($address[VCARD_ADR_LOCALITY])) { $a[] = $address[VCARD_ADR_LOCALITY]; } if (isset($address[VCARD_ADR_REGION])) { $a[] = $address[VCARD_ADR_REGION]; } if (isset($address[VCARD_ADR_POSTCODE])) { $a[] = $address[VCARD_ADR_POSTCODE]; } if (isset($address[VCARD_ADR_COUNTRY])) { $a[] = $address[VCARD_ADR_COUNTRY]; } $types = array(); foreach ($item['params']['TYPE'] as $type) { switch(String::upper($type)) { case 'HOME': $types[] = _("Home Address"); break; case 'WORK': $types[] = _("Work Address"); break; case 'DOM': $types[] = _("Domestic Address"); break; case 'INTL': $types[] = _("International Address"); break; case 'POSTAL': $types[] = _("Postal Address"); break; case 'PARCEL': $types[] = _("Parcel Address"); break; case 'PREF': $types[] = _("Preferred Address"); break; } } if (!count($types)) { $types = array(_("Address")); } $html .= $this->_row(implode('/', $types), implode("\n", $a)); } $numbers = $vc->getAllAttributes('TEL'); foreach ($numbers as $number) { if (isset($number['params']['TYPE'])) { if (!is_array($number['params']['TYPE'])) { $number['params']['TYPE'] = array($number['params']['TYPE']); } foreach ($number['params']['TYPE'] as $type) { $number['params'][String::upper($type)] = true; } } if (isset($number['params']['FAX'])) { $html .= $this->_row(_("Fax"), $number['value']); } else { if (isset($number['params']['HOME'])) { $html .= $this->_row(_("Home Phone"), $number['value']); } elseif (isset($number['params']['WORK'])) { $html .= $this->_row(_("Work Phone"), $number['value']); } elseif (isset($number['params']['CELL'])) { $html .= $this->_row(_("Cell Phone"), $number['value']); } else { $html .= $this->_row(_("Phone"), $number['value']); } } } $addresses = $vc->getAllAttributes('EMAIL'); $emails = array(); foreach ($addresses as $address) { if (isset($address['params']['TYPE'])) { if (!is_array($address['params']['TYPE'])) { $address['params']['TYPE'] = array($address['params']['TYPE']); } foreach ($address['params']['TYPE'] as $type) { $address['params'][String::upper($type)] = true; } } $email = '' . htmlspecialchars($address['value']) . ''; if (isset($address['params']['PREF'])) { array_unshift($emails, $email); } else { $emails[] = $email; } } if (count($emails)) { $html .= $this->_row(_("Email"), implode("\n", $emails), false); } $title = $vc->getAttributeValues('TITLE'); if (!is_a($title, 'PEAR_Error')) { $html .= $this->_row(_("Title"), $title[0]); } $role = $vc->getAttributeValues('ROLE'); if (!is_a($role, 'PEAR_Error')) { $html .= $this->_row(_("Role"), $role[0]); } $org = $vc->getAttributeValues('ORG'); if (!is_a($org, 'PEAR_Error')) { $html .= $this->_row(_("Company"), $org[0]); if (isset($org[1])) { $html .= $this->_row(_("Department"), $org[1]); } } $notes = $vc->getAttributeValues('NOTE'); if (!is_a($notes, 'PEAR_Error')) { $html .= $this->_row(_("Notes"), $notes[0]); } $url = $vc->getAttributeValues('URL'); if (!is_a($url, 'PEAR_Error')) { $html .= $this->_row( _("URL"), '' . htmlspecialchars($url[0]) . '', false); } } if ($registry->hasMethod('contacts/import') && $registry->hasMethod('contacts/sources')) { $html .= ''; } $html .= '
 
'; $fullname = $vc->getAttributeDefault('FN', false); if ($fullname !== false) { $html .= htmlspecialchars($fullname); } $html .= '
' . Util::formInput(); foreach ($_GET as $key => $val) { $html .= ''; } $sources = $registry->call('contacts/sources', array(true)); if (count($sources) > 1) { $html .= '' . '' . '' . ''; } $html .= '
 
'; return Util::bufferOutput( 'include', $registry->get('templates', 'horde') . '/common-header.inc') . Util::bufferOutput(array($notification, 'notify'), array('listeners' => 'status')) . $html . Util::bufferOutput( 'include', $registry->get('templates', 'horde') . '/common-footer.inc'); } function _row($label, $value, $encode = true) { if ($encode) { $label = htmlspecialchars($label); $value = htmlspecialchars($value); } return '' . $label . '' . nl2br($value) . "\n"; } /** * Return the MIME content type of the rendered content. * * @return string The content type of the output. */ function getType() { return 'text/html; charset=' . NLS::getCharset(); } } * @package Horde_MIME_Viewer */ class MIME_Viewer_webcpp extends MIME_Viewer { /** * Render out the currently set contents using Web C Plus Plus. * * @param array $params Any parameters the Viewer may need. * * @return string The rendered contents. */ function render($params = array()) { global $mime_drivers; require_once 'Horde/MIME/Contents.php'; $attachment = MIME_Contents::viewAsAttachment(); /* Check to make sure the program actually exists. */ if (!file_exists($mime_drivers['horde']['webcpp']['location'])) { return '
' . sprintf(_("The program used to view this data type (%s) was not found on the system."), $mime_drivers['horde']['webcpp']['location']) . '
'; } /* Create temporary files for Webcpp. */ $tmpin = Horde::getTempFile('WebcppIn'); $tmpout = Horde::getTempFile('WebcppOut'); /* Write the contents of our buffer to the temporary input file. */ $contents = $this->mime_part->getContents(); $fh = fopen($tmpin, 'wb'); fwrite($fh, $contents, strlen($contents)); fclose($fh); /* Get the extension for the mime type. */ include_once 'Horde/MIME/Magic.php'; $ext = MIME_Magic::MIMEToExt($this->mime_part->getType()); /* Execute Web C Plus Plus. Specifying the in and out files didn't work for me but pipes did. */ exec($mime_drivers['horde']['webcpp']['location'] . " --pipe --pipe -x=$ext -l -a -t < $tmpin > $tmpout"); $results = file_get_contents($tmpout); /* If we are not displaying inline, all the formatting is already * done for us. */ if ($attachment) { /* The first 2 lines are the Content-Type line and a blank line * so we should remove them before outputting. */ return preg_replace("/.*\n.*\n/", '', $results, 1); } /* Extract the style sheet, removing any global body formatting * if we're displaying inline. */ $res = preg_split(';()|(
' . $body . '
'; } /** * Return the MIME content type of the rendered content. * * @return string The content type of the output. */ function getType() { return 'text/html; charset=' . NLS::getCharset(); } } * @since Horde 3.2 * @package Horde_MIME_Viewer */ class MIME_Viewer_wordperfect extends MIME_Viewer { /** * Render out the current data using wpd2html. * * @param array $params Any parameters the viewer may need. * * @return string The rendered contents. */ function render($params = array()) { global $mime_drivers; /* Check to make sure the program actually exists. */ if (!file_exists($mime_drivers['horde']['wordperfect']['location'])) { return '
' . sprintf(_("The program used to view this data type (%s) was not found on the system."), $mime_drivers['horde']['wordperfect']['location']) . '
'; } $tmp_wpd = Horde::getTempFile('wpd'); $tmp_output = Horde::getTempFile('wpd'); $args = " $tmp_wpd > $tmp_output"; $fh = fopen($tmp_wpd, 'w'); fwrite($fh, $this->mime_part->getContents()); fclose($fh); exec($mime_drivers['horde']['wordperfect']['location'] . $args); if (!file_exists($tmp_output)) { return _("Unable to translate this WordPerfect document"); } return file_get_contents($tmp_output); } /** * Return the MIME content type of the rendered content. * * @return string The content type of the output. */ function getType() { return 'text/html; charset=' . NLS::getCharset(); } } * @author Michael Cochrane * @package Horde_MIME_Viewer */ class MIME_Viewer_zip extends MIME_Viewer { /** * Render out the current zip contents. * * @param array $params Any parameters the Viewer may need. * * @return string The rendered contents. */ function render($params = array()) { return $this->_render($this->mime_part->getContents()); } /** * Output the file list. * * @access private * * @param string $contents The contents of the zip archive. * @param mixed $callback The callback function to use on the zipfile * information. * * @return string The file list. */ function _render($contents, $callback = null) { require_once 'Horde/Compress.php'; $zip = &Horde_Compress::factory('zip'); /* Make sure this is a valid zip file. */ if ($zip->checkZipData($contents) === false) { return '
' . _("This does not appear to be a valid zip file.")
                . '
'; } $zipInfo = $zip->decompress( $contents, array('action' => HORDE_COMPRESS_ZIP_LIST)); if (is_a($zipInfo, 'PEAR_Error')) { return $zipInfo->getMessage(); } $fileCount = count($zipInfo); /* Determine maximum file name length. */ $maxlen = 0; foreach ($zipInfo as $val) { $maxlen = max($maxlen, strlen($val['name'])); } require_once 'Horde/Text.php'; $text = '' . htmlspecialchars(sprintf(_("Contents of \"%s\""), $this->mime_part->getName())) . ':' . "\n" . '
' . Text::htmlAllSpaces( _("Archive Name") . ': ' . $this->mime_part->getName() . "\n" . _("Archive File Size") . ': ' . strlen($contents) . ' bytes' . "\n" . sprintf( ngettext("File Count: %d file", "File Count: %d files", $fileCount), $fileCount) . "\n\n" . String::pad(_("File Name"), $maxlen, ' ', STR_PAD_RIGHT) . String::pad(_("Attributes"), 10, ' ', STR_PAD_LEFT) . String::pad(_("Size"), 10, ' ', STR_PAD_LEFT) . String::pad(_("Modified Date"), 19, ' ', STR_PAD_LEFT) . String::pad(_("Method"), 10, ' ', STR_PAD_LEFT) . String::pad(_("CRC"), 10, ' ', STR_PAD_LEFT) . String::pad(_("Ratio"), 10, ' ', STR_PAD_LEFT) . "\n") . str_repeat('-', 69 + $maxlen) . "\n"; foreach ($zipInfo as $key => $val) { $ratio = (empty($val['size'])) ? 0 : 100 * ($val['csize'] / $val['size']); $val['name'] = String::pad($val['name'], $maxlen, ' ', STR_PAD_RIGHT); $val['attr'] = String::pad($val['attr'], 10, ' ', STR_PAD_LEFT); $val['size'] = String::pad($val['size'], 10, ' ', STR_PAD_LEFT); $val['date'] = String::pad(strftime("%d-%b-%Y %H:%M", $val['date']), 19, ' ', STR_PAD_LEFT); $val['method'] = String::pad($val['method'], 10, ' ', STR_PAD_LEFT); $val['crc'] = String::pad($val['crc'], 10, ' ', STR_PAD_LEFT); $val['ratio'] = String::pad(sprintf("%1.1f%%", $ratio), 10, ' ', STR_PAD_LEFT); $val = array_map(array('Text', 'htmlAllSpaces'), $val); if (!is_null($callback)) { $val = call_user_func($callback, $key, $val); } $text .= $val['name'] . $val['attr'] . $val['size'] . $val['date'] . $val['method'] . $val['crc'] . $val['ratio'] . "\n"; } $text .= str_repeat('-', 69 + $maxlen) . "\n" . '
'; return nl2br($text); } /** * Return the content-type * * @return string The content-type of the output. */ function getType() { return 'text/html; charset=' . NLS::getCharset(); } } * @package Horde_MIME_Viewer */ class MIME_Viewer { /** * The MIME_Part object to render. * * @var MIME_Part */ var $mime_part; /** * Configuration parameters. * * @var array */ var $_conf = array(); /** * getDriver cache. * * @var array */ var $_driverCache = array(); /** * Force viewing of a part inline, regardless of the Content-Disposition * of the MIME Part. * * @var boolean */ var $_forceinline = false; /** * Attempts to return a concrete MIME_Viewer_* object based on the * type of MIME_Part passed onto it. * * @param MIME_Part &$mime_part Reference to a MIME_Part object with the * information to be rendered. * @param string $mime_type Use this MIME type instead of the type * stored in the $mime_part. * * @return MIME_Viewer The MIME_Viewer object, or false on error. */ function &factory(&$mime_part, $mime_type = null) { $viewer = false; /* Check that we have a valid MIME_Part object */ if (!is_a($mime_part, 'MIME_Part')) { return $viewer; } /* Determine driver type from the MIME type */ if (empty($mime_type)) { $mime_type = $mime_part->getType(); if (empty($mime_type)) { return $viewer; } } /* Spawn the relevant driver, and return it (or false on failure) */ if (($ob = MIME_Viewer::includeDriver($mime_type))) { $class = (($ob->module == 'horde') ? '' : $ob->module . '_') . 'MIME_Viewer_' . $ob->driver; if (class_exists($class)) { $viewer = &new $class($mime_part, $GLOBALS['mime_drivers'][$ob->module][$ob->driver]); } } return $viewer; } /** * Include the code for the relevant driver. * * @param string $mime_type The Content-type of the part to be rendered. * * @return stdClass See MIME_Driver::getDriver(). */ function includeDriver($mime_type) { // TODO: BC - switch to require_once for Horde 4.0; don't need $config; // don't need to make sure the 2 variables are unset. static $config = false; global $registry; $app = $registry->getApp(); if (!$config) { $GLOBALS['mime_drivers'] = $GLOBALS['mime_drivers_map'] = array(); $result = Horde::loadConfiguration('mime_drivers.php', array('mime_drivers', 'mime_drivers_map'), 'horde'); if (!is_a($result, 'PEAR_Error')) { extract($result); } if ($app != 'horde') { $result = Horde::loadConfiguration('mime_drivers.php', array('mime_drivers', 'mime_drivers_map'), $app); if (!is_a($result, 'PEAR_Error')) { require_once 'Horde/Array.php'; if (isset($result['mime_drivers'])) { $mime_drivers = Horde_Array::array_merge_recursive_overwrite($mime_drivers, $result['mime_drivers']); } if (isset($result['mime_drivers_map'])) { $mime_drivers_map = Horde_Array::array_merge_recursive_overwrite($mime_drivers_map, $result['mime_drivers_map']); } } } $GLOBALS['mime_drivers'] = $mime_drivers; $GLOBALS['mime_drivers_map'] = $mime_drivers_map; $config = true; } /* Figure the correct driver for this MIME type. If there is no application-specific module, a general Horde one will attempt to be used. */ if (($ob = MIME_Viewer::getDriver($mime_type, $app))) { /* Include the class. */ require_once MIME_Viewer::resolveDriver($ob->driver, $ob->module); } return $ob; } /** * Constructor for MIME_Viewer * * @param MIME_Part &$mime_part Reference to a MIME_Part object with the * information to be rendered. */ function MIME_Viewer(&$mime_part, $conf = array()) { $this->mime_part = &$mime_part; $this->_conf = $conf; } /** * Sets the MIME_Part object for the class. * * @param MIME_Part &$mime_part Reference to a MIME_Part object with the * information to be rendered. */ function setMIMEPart(&$mime_part) { $this->mime_part = &$mime_part; } /** * Return the MIME type of the rendered content. This can be * overridden by the individual drivers, depending on what format * they output in. By default, it passes through the MIME type of * the object, or replaces custom extension types with * 'text/plain' to let the browser do a best-guess render. * * @return string MIME-type of the output content. */ function getType() { if ($this->mime_part->getPrimaryType() == 'x-extension') { return 'text/plain'; } else { return $this->mime_part->getType(true); } } /** * Return the rendered version of the object. * * Should be overridden by individual drivers to perform custom tasks. * The $mime_part class variable has the information to render, * encapsulated in a MIME_Part object. * * @param mixed $params Any optional parameters this driver needs at * runtime. * * @return string Rendered version of the object. */ function render($params = null) { return $this->mime_part->getContents(); } /** * Return text/html output used as alternative output when the fully * rendered object cannot (or should not) be displayed. For example, * this function should be used for MIME attachments that cannot be * viewed inline, where the user may be given options on how to view * the attachment. * Should be overridden by individual drivers to perform custom tasks. * The $mime_part class variable has the information to render, * encapsulated in a MIME_Part object. * * @param mixed $params Any optional parameters this driver needs at * runtime. * * @return string Text/html rendered information. */ function renderAttachmentInfo() { } /** * Can this driver render the the data inline? * * @return boolean True if the driver can display inline. */ function canDisplayInline() { if ($this->getConfigParam('inline')) { return true; } else { return false; } } /** * Given a driver and an application, this returns the fully * qualified filesystem path to the driver source file. * * @param string $driver Driver name. * @param string $app Application name. * * @return string Filesystem path of the driver/application queried. */ function resolveDriver($driver = 'default', $app = 'horde') { if ($app == 'horde') { return dirname(__FILE__) . '/Viewer/' . $driver . '.php'; } else { return $GLOBALS['registry']->applications[$app]['fileroot'] . '/lib/MIME/Viewer/' . $driver . '.php'; } } /** * Given an input MIME type and a module name, this function * resolves it into a specific output driver which can handle it. * * @param string $mimeType MIME type to resolve. * @param string $module Module in which to search for the driver. * * @return stdClass Object with the following items: *
     * 'driver'  --  Name of driver (e.g. 'enscript')
     * 'exact'   --  Was the driver and exact match? (true/false)
     * 'module'  --  The module containing driver (e.g. 'horde')
     * 
* Returns false if driver could not be found. */ function getDriver($mimeType, $module = 'horde') { global $mime_drivers, $mime_drivers_map; $cacheName = $mimeType . '|' . $module; if (isset($this) && isset($this->_driverCache[$cacheName])) { return $this->_driverCache[$cacheName]; } $driver = ''; $exactDriver = false; list($primary_type, ) = explode('/', $mimeType, 2); $allSub = $primary_type . '/*'; /* If the module doesn't exist in $mime_drivers_map, check for Horde viewers. */ if (!isset($mime_drivers_map[$module]) && $module != 'horde') { return MIME_Viewer::getDriver($mimeType, 'horde'); } $dr = &$mime_drivers[$module]; $map = &$mime_drivers_map[$module]; /* If an override exists for this MIME type, then use that */ if (isset($map['overrides'][$mimeType])) { $driver = $map['overrides'][$mimeType]; $exactDriver = true; } elseif (isset($map['overrides'][$allSub])) { $driver = $map['overrides'][$allSub]; $exactDriver = true; } elseif (isset($map['registered'])) { /* Iterate through the list of registered drivers, and see if this MIME type exists in the MIME types that they claim to handle. If the driver handles it, then assign it as the rendering driver. If we find a generic handler, keep iterating to see if we can find a specific handler. */ foreach ($map['registered'] as $val) { if (in_array($mimeType, $dr[$val]['handles'])) { $driver = $val; $exactDriver = true; break; } elseif (in_array($allSub, $dr[$val]['handles'])) { $driver = $val; } } } /* If this is an application specific module, and an exact match was not found, search for a Horde-wide specific driver. Only use the Horde-specific driver if it is NOT the 'default' driver AND the Horde driver is an exact match. */ if (!$exactDriver && $module != 'horde') { $ob = MIME_Viewer::getDriver($mimeType, 'horde'); if (empty($driver) || (($ob->driver != 'default') && $ob->exact)) { $driver = $ob->driver; $module = 'horde'; } } /* If the 'default' driver exists in this module, fall back to that. */ if (empty($driver) && @is_file(MIME_Viewer::resolveDriver('default', $module))) { $driver = 'default'; } if (empty($driver)) { $this->_driverCache[$cacheName] = false; return false; } else { $ob = new stdClass; $ob->driver = $driver; $ob->exact = $exactDriver; $ob->module = $module; if (isset($this)) { $this->_driverCache[$cacheName] = $ob; } return $ob; } } /** * Given a MIME type, this function will return an appropriate * icon. * * @param string $mimeType The MIME type that we need an icon for. * * @return string The URL to the appropriate icon. */ function getIcon($mimeType) { $app = $GLOBALS['registry']->getApp(); $ob = MIME_Viewer::_getIcon($mimeType, $app); if ($ob === null) { if ($app != 'horde') { $obHorde = MIME_Viewer::_getIcon($mimeType, 'horde'); return ($obHorde === null) ? null : $obHorde->url; } else { return null; } } elseif (($ob->match !== 0) && ($app != 'horde')) { $obHorde = MIME_Viewer::_getIcon($mimeType, 'horde'); if ($ob->match !== null && $ob->match <= $obHorde->match) { return $ob->url; } else { return $obHorde->url; } } else { return $ob->url; } } /** * Given an input MIME type and module, this function returns the * URL of an icon that can be associated with it * * @access private * * @param string $mimeType MIME type to get the icon for. * * @return stdClass url: URL to an icon, or null if none * could be found. * exact: How exact the match is. * 0 => 'exact', 1 => 'primary', * 2 => 'driver', 3 => 'default' * or null. */ function _getIcon($mimeType, $module = 'horde') { global $mime_drivers; $ob = MIME_Viewer::getDriver($mimeType, $module); if (!is_object($ob)) { return array(false, null); } $driver = $ob->driver; list($primary_type,) = explode('/', $mimeType, 2); $allSub = $primary_type . '/*'; $retOb = &new stdClass(); $retOb->match = null; $retOb->url = null; /* If the module doesn't exist in $mime_drivers, return now. */ if (!isset($mime_drivers[$module])) { return null; } $dr = &$mime_drivers[$module]; /* If a specific icon for this driver and mimetype is defined, then use that. */ if (isset($dr[$driver]['icons'])) { $icondr = &$mime_drivers[$module][$driver]['icons']; $iconList = array($mimeType => 0, $allSub => 1, 'default' => 2); foreach ($iconList as $key => $val) { if (isset($icondr[$key])) { $retOb->url = $icondr[$key]; $retOb->match = $val; break; } } } /* Try to use a default icon if none already obtained. */ if (is_null($retOb->url) && isset($dr['default'])) { $dr = &$mime_drivers[$module]['default']; if (isset($dr['icons']['default'])) { $retOb->url = $dr['default']['icons']['default']; $retOb->match = 3; } } if (!is_null($retOb->url)) { $retOb->url = $GLOBALS['registry']->getImageDir($module) . '/mime/' . $retOb->url; } return $retOb; } /** * Returns the character set used for the Viewer. * Should be overridden by individual drivers to perform custom tasks. * * @return string The character set used by this Viewer. */ function getCharset() { return $this->mime_part->getCharset(); } /** * Return a configuration parameter for the current viewer. * * @param string $param The parameter name. * * @return mixed The value of the parameter; returns null if the parameter * doesn't exist. */ function getConfigParam($param) { return (isset($this->_conf[$param])) ? $this->_conf[$param] : null; } /** * Should we force viewing of this MIME Part inline, regardless of the * Content-Disposition of the MIME Part? * * @return boolean Force viewing inline? */ function forceInlineView() { return $this->_forceinline; } } * @since Horde 1.3 * @package Horde_MIME */ class MIME { /** * A listing of the allowed MIME types. * * @var array */ var $mime_types = array( TYPETEXT => 'text', TYPEMULTIPART => 'multipart', TYPEMESSAGE => 'message', TYPEAPPLICATION => 'application', TYPEAUDIO => 'audio', TYPEIMAGE => 'image', TYPEVIDEO => 'video', TYPEMODEL => 'model', TYPEOTHER => 'other' ); /** * A listing of the allowed MIME encodings. * * @var array */ var $mime_encodings = array( ENC7BIT => '7bit', ENC8BIT => '8bit', ENCBINARY => 'binary', ENCBASE64 => 'base64', ENCQUOTEDPRINTABLE => 'quoted-printable', ENCOTHER => 'unknown' ); /** * Filter for RFC822. * * @var string */ var $rfc822_filter = "()<>@,;:\\\"[]\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177"; /** * Determines if a string contains 8-bit (non US-ASCII) characters. * * @param string $string The string to check. * @param string $charset The charset of the string. Defaults to * US-ASCII. Since Horde 3.2.2. * * @return boolean True if it does, false if it doesn't. */ function is8bit($string, $charset = null) { /* ISO-2022-JP is a 7bit charset, but it is an 8bit representation so * it needs to be entirely encoded. */ return is_string($string) && ((stristr('iso-2022-jp', $charset) && (strstr($string, "\x1b\$B"))) || preg_match('/[\x80-\xff]/', $string)); } /** * Encodes a string containing non-ASCII characters according to RFC 2047. * * @param string $text The text to encode. * @param string $charset The character set of the text. * * @return string The text, encoded only if it contains non-ASCII * characters. */ function encode($text, $charset = null) { if (is_null($charset)) { require_once 'Horde/NLS.php'; $charset = NLS::getCharset(); } $charset = String::lower($charset); $line = ''; /* Return if nothing needs to be encoded. */ if (($charset == 'us-ascii') || !MIME::is8bit($text, $charset)) { return $text; } /* Get the list of elements in the string. */ $size = preg_match_all('/([^\s]+)([\s]*)/', $text, $matches, PREG_SET_ORDER); foreach ($matches as $key => $val) { if (MIME::is8bit($val[1], $charset)) { if ((($key + 1) < $size) && MIME::is8bit($matches[$key + 1][1], $charset)) { $line .= MIME::_encode($val[1] . $val[2], $charset) . ' '; } else { $line .= MIME::_encode($val[1], $charset) . $val[2]; } } else { $line .= $val[1] . $val[2]; } } return rtrim($line); } /** * Internal recursive function to RFC 2047 encode a string. * * @access private * * @param string $text The text to encode. * @param string $charset The character set of the text. * * @return string The text, encoded only if it contains non-ASCII * characters. */ function _encode($text, $charset) { $encoded = trim(base64_encode($text)); $c_size = strlen($charset) + 7; if ((strlen($encoded) + $c_size) > 75) { $parts = explode("\r\n", rtrim(chunk_split($encoded, intval((75 - $c_size) / 4) * 4))); } else { $parts[] = $encoded; } $p_size = count($parts); $out = ''; foreach ($parts as $key => $val) { $out .= '=?' . $charset . '?b?' . $val . '?='; if ($p_size > $key + 1) { /* RFC 2047 [2] states that no encoded word can be more than * 75 characters long. If longer, you must split the word with * CRLF SPACE. */ $out .= "\r\n "; } } return $out; } /** * Encodes a line via quoted-printable encoding. * Wraps lines at 76 characters. * * @param string $text The text to encode. * @param string $eol The EOL sequence to use. * * @return string The quoted-printable encoded string. */ function quotedPrintableEncode($text, $eol) { $line = $output = ''; $curr_length = 0; /* We need to go character by character through the data. */ $length = strlen($text); for ($i = 0; $i < $length; ++$i) { $char = $text[$i]; /* If we have reached the end of the line, reset counters. */ if ($char == "\n") { $output .= $eol; $curr_length = 0; continue; } elseif ($char == "\r") { continue; } $ascii = ord($char); $char_len = 1; /* Spaces or tabs at the end of the line are NOT allowed. Also, * ASCII characters below 32 or above 126 AND 61 must be * encoded. */ if ((($ascii === 32) && ($i + 1 != $length) && (($text[$i + 1] == "\n") || ($text[$i + 1] == "\r"))) || (($ascii < 32) || ($ascii > 126) || ($ascii === 61))) { $char_len = 3; $char = '=' . String::upper(sprintf('%02s', dechex($ascii))); } /* Lines must be 76 characters or less. */ $curr_length += $char_len; if ($curr_length > 75) { $output .= '=' . $eol; $curr_length = $char_len; } $output .= $char; } return $output; } /** * Encodes a string containing email addresses according to RFC 2047. * * This differs from MIME::encode() because it keeps email addresses legal, * only encoding the personal information. * * @param string $addresses The email addresses to encode. * @param string $charset The character set of the text. * @param string $defserver The default domain to append to mailboxes. * * @return string The text, encoded only if it contains non-ascii * characters, or PEAR_Error on error. */ function encodeAddress($addresses, $charset = null, $defserver = null) { if (is_array($addresses)) { $addr_arr = $addresses; } else { /* parseAddressList() does not process the null entry * 'undisclosed-recipients:;' correctly. */ if (preg_match('/undisclosed-recipients:\s*;/i', trim($addresses))) { return $addresses; } require_once 'Mail/RFC822.php'; $parser = &new Mail_RFC822(); $addr_arr = $parser->parseAddressList($addresses, $defserver, true, false); if (is_a($addr_arr, 'PEAR_Error')) { return $addr_arr; } } $text = ''; if (is_array($addr_arr)) { foreach ($addr_arr as $addr) { // Check for groups. if (!empty($addr->groupname)) { $text .= MIME::encode($addr->groupname, $charset) . ': ' . MIME::encodeAddress($addr->addresses) . '; '; } else { if (empty($addr->personal)) { $personal = ''; } else { if ((substr($addr->personal, 0, 1) == '"') && (substr($addr->personal, -1) == '"')) { $addr->personal = stripslashes(substr($addr->personal, 1, -1)); } $personal = MIME::encode($addr->personal, $charset); } $text .= MIME::trimEmailAddress(MIME::rfc822WriteAddress($addr->mailbox, $addr->host, $personal)) . ', '; } } } return rtrim($text, ' ,'); } /** * Decodes an RFC 2047-encoded string. * * @param string $string The text to decode. * @param string $to_charset The charset that the text should be decoded * to. * * @return string The decoded text. */ function decode($string, $to_charset = null) { if (($pos = strpos($string, '=?')) === false) { return $string; } /* Take out any spaces between multiple encoded words. */ $string = preg_replace('|\?=\s+=\?|', '?==?', $string); /* Save any preceding text. */ $preceding = substr($string, 0, $pos); $search = substr($string, $pos + 2); $d1 = strpos($search, '?'); if ($d1 === false) { return $string; } $charset = substr($string, $pos + 2, $d1); $search = substr($search, $d1 + 1); $d2 = strpos($search, '?'); if ($d2 === false) { return $string; } $encoding = substr($search, 0, $d2); $search = substr($search, $d2 + 1); $end = strpos($search, '?='); if ($end === false) { $end = strlen($search); } $encoded_text = substr($search, 0, $end); $rest = substr($string, (strlen($preceding . $charset . $encoding . $encoded_text) + 6)); if (is_null($to_charset)) { require_once 'Horde/NLS.php'; $to_charset = NLS::getCharset(); } switch ($encoding) { case 'Q': case 'q': $encoded_text = str_replace('_', ' ', $encoded_text); $decoded = preg_replace('/=([0-9a-f]{2})/ie', 'chr(0x\1)', $encoded_text); $decoded = String::convertCharset($decoded, $charset, $to_charset); break; case 'B': case 'b': $decoded = base64_decode($encoded_text); $decoded = String::convertCharset($decoded, $charset, $to_charset); break; default: $decoded = '=?' . $charset . '?' . $encoding . '?' . $encoded_text . '?='; break; } return $preceding . $decoded . MIME::decode($rest, $to_charset); } /** * Decodes an RFC 2047-encoded address string. * * @param string $string The text to decode. * @param string $to_charset The charset that the text should be decoded * to. * * @return string The decoded text. */ function decodeAddrString($string, $to_charset = null) { $addr_list = array(); foreach (MIME::parseAddressList($string) as $ob) { $ob->personal = isset($ob->personal) ? MIME::decode($ob->personal, $to_charset) : ''; $addr_list[] = $ob; } return MIME::addrArray2String($addr_list); } /** * Encodes a string pursuant to RFC 2231. * * @param string $name The parameter name. * @param string $string The string to encode. * @param string $charset The charset the text should be encoded with. * @param string $lang The language to use when encoding. * * @return array The encoded parameter string. */ function encodeRFC2231($name, $string, $charset, $lang = null) { $encode = $wrap = false; $output = array(); if (MIME::is8bit($string, $charset)) { $string = String::lower($charset) . '\'' . (($lang === null) ? '' : String::lower($lang)) . '\'' . rawurlencode($string); $encode = true; } // 4 = '*', ';' $pre_len = strlen($name) + 2 + (($encode) ? 1 : 0); if (($pre_len + strlen($string)) > 76) { while ($string) { $chunk = 76 - $pre_len; $pos = min($chunk, strlen($string) - 1); if (($chunk == $pos) && ($pos > 2)) { for ($i = 0; $i <= 2; $i++) { if ($string[$pos-$i] == '%') { $pos -= $i + 1; break; } } } $lines[] = substr($string, 0, $pos + 1); $string = substr($string, $pos + 1); } $wrap = true; } else { $lines = array($string); } $i = 0; foreach ($lines as $val) { $output[] = $name . (($wrap) ? ('*' . $i++) : '') . (($encode) ? '*' : '') . '=' . ($encode ? '' : '"') . $val . ($encode ? '' : '"'); } return implode('; ', $output); } /** * Decodes an RFC 2231-encoded string. * * @param string $string The entire string to decode, including the * parameter name. * @param string $to_charset The charset the text should be decoded to. * * @return array The decoded text, or the original string if it was not * encoded. */ function decodeRFC2231($string, $to_charset = null) { if (($pos = strpos($string, '*')) === false) { return false; } if (!isset($to_charset)) { require_once 'Horde/NLS.php'; $to_charset = NLS::getCharset(); } $attribute = substr($string, 0, $pos); $charset = $lang = null; $output = ''; /* Get the character set and language used in the encoding, if * any. */ if (preg_match("/^[^=]+\*\=([^']*)'([^']*)'/", $string, $matches)) { $charset = $matches[1]; $lang = $matches[2]; $string = str_replace($charset . "'" . $lang . "'", '', $string); } $lines = preg_split('/\s*' . preg_quote($attribute) . '(?:\*\d)*/', $string); foreach ($lines as $line) { $pos = strpos($line, '*='); if ($pos === 0) { $line = substr($line, 2); $line = str_replace('_', '%20', $line); $line = str_replace('=', '%', $line); $output .= urldecode($line); } else { $line = substr($line, 1); $output .= $line; } } /* RFC 2231 uses quoted printable encoding. */ if (!is_null($charset)) { $output = String::convertCharset($output, $charset, $to_charset); } return array( 'attribute' => $attribute, 'value' => $output ); } /** * If an email address has no personal information, get rid of any angle * brackets (<>) around it. * * @param string $address The address to trim. * * @return string The trimmed address. */ function trimEmailAddress($address) { $address = trim($address); if ((substr($address, 0, 1) == '<') && (substr($address, -1) == '>')) { $address = substr($address, 1, -1); } return $address; } /** * Builds an RFC 822 compliant email address. * * @param string $mailbox Mailbox name. * @param string $host Domain name of mailbox's host. * @param string $personal Personal name phrase. * * @return string The correctly escaped and quoted * "$personal <$mailbox@$host>" string. */ function rfc822WriteAddress($mailbox, $host = null, $personal = '') { $address = ''; if (strlen($personal)) { $address .= MIME::_rfc822Encode($personal, 'personal'); $address .= ' <'; } if (!is_null($host)) { $address .= MIME::_rfc822Encode($mailbox, 'address'); if (substr($host, 0, 1) != '@') { $address .= '@' . $host; } } if (strlen($personal)) { $address .= '>'; } return $address; } /** * Explodes an RFC 2822 string, ignoring a delimiter if preceded * by a "\" character, or if the delimiter is inside single or * double quotes. * * @param string $string The RFC 822 string. * @param string $delimiters A string containing valid delimiters. * Defaults to ','. * * @return array The exploded string in an array. */ function rfc822Explode($string, $delimiters = ',') { $emails = array(); $pos = 0; $in_group = $in_quote = false; $prev = null; if (!strlen($string)) { return array($string); } $char = $string[0]; if ($char == '"') { $in_quote = true; } elseif ($char == ':') { $in_group = true; } elseif (strpos($delimiters, $char) !== false) { $emails[] = ''; $pos = 1; } for ($i = 1, $iMax = strlen($string); $i < $iMax; ++$i) { $char = $string[$i]; if ($char == '"') { if ($prev !== '\\') { $in_quote = !$in_quote; } } elseif ($in_group) { if ($char == ';') { $emails[] = substr($string, $pos, $i - $pos + 1); $pos = $i + 1; $in_group = false; } } elseif (!$in_quote) { if ($char == ':') { $in_group = true; } elseif (strpos($delimiters, $char) !== false && $prev !== '\\') { $emails[] = substr($string, $pos, $i - $pos); $pos = $i + 1; } } $prev = $char; } if ($pos != $i) { /* The string ended without a delimiter. */ $emails[] = substr($string, $pos, $i - $pos); } return $emails; } /** * Takes an address object, as returned by imap_header() for example, and * formats it as a string. * * Object format for the address "John Doe " is: *
     *   $object->personal = Personal name ("John Doe")
     *   $object->mailbox  = The user's mailbox ("john_doe")
     *   $object->host     = The host the mailbox is on ("example.com")
     * 
* * @param stdClass $ob The address object to be turned into a string. * @param mixed $filter A user@example.com style bare address to ignore. * Either single string or an array of strings. If * the address matches $filter, an empty string will * be returned. * * @return string The formatted address (Example: John Doe * ). */ function addrObject2String($ob, $filter = '') { /* If the personal name is set, decode it. */ $ob->personal = isset($ob->personal) ? MIME::decode($ob->personal) : ''; /* If both the mailbox and the host are empty, return an empty string. If we just let this case fall through, the call to MIME::rfc822WriteAddress() will end up return just a '@', which is undesirable. */ if (empty($ob->mailbox) && empty($ob->host)) { return ''; } /* Make sure these two variables have some sort of value. */ if (!isset($ob->mailbox)) { $ob->mailbox = ''; } elseif ($ob->mailbox == 'undisclosed-recipients') { return ''; } if (!isset($ob->host)) { $ob->host = ''; } /* Filter out unwanted addresses based on the $filter string. */ if ($filter) { if (!is_array($filter)) { $filter = array($filter); } foreach ($filter as $f) { if (strcasecmp($f, $ob->mailbox . '@' . $ob->host) == 0) { return ''; } } } /* Return the trimmed, formatted email address. */ return MIME::trimEmailAddress(MIME::rfc822WriteAddress($ob->mailbox, $ob->host, $ob->personal)); } /** * Takes an array of address objects, as returned by imap_headerinfo(), * for example, and passes each of them through MIME::addrObject2String(). * * @param array $addresses The array of address objects. * @param mixed $filter A user@example.com style bare address to * ignore. If any address matches $filter, it * will not be included in the final string. * * @return string All of the addresses in a comma-delimited string. * Returns the empty string on error/no addresses found. */ function addrArray2String($addresses, $filter = '') { $addrList = array(); if (!is_array($addresses)) { return ''; } foreach ($addresses as $addr) { $val = MIME::addrObject2String($addr, $filter); if (!empty($val)) { $bareAddr = String::lower(MIME::bareAddress($val)); if (!isset($addrList[$bareAddr])) { $addrList[$bareAddr] = $val; } } } if (empty($addrList)) { return ''; } else { return implode(', ', $addrList); } } /** * Returns the bare address. * * @param string $address The address string. * @param string $defserver The default domain to append to mailboxes. * @param boolean $multiple Should we return multiple results? * * @return mixed If $multiple is false, returns the mailbox@host e-mail * address. If $multiple is true, returns an array of * these addresses. */ function bareAddress($address, $defserver = null, $multiple = false) { $addressList = array(); $from = MIME::parseAddressList($address, $defserver); if (is_a($from, 'PEAR_Error')) { return $multiple ? array() : ''; } foreach ($from as $entry) { if (isset($entry->mailbox) && $entry->mailbox != 'undisclosed-recipients' && $entry->mailbox != 'UNEXPECTED_DATA_AFTER_ADDRESS') { if (isset($entry->host)) { $addressList[] = $entry->mailbox . '@' . $entry->host; } else { $addressList[] = $entry->mailbox; } } } return $multiple ? $addressList : array_pop($addressList); } /** * Parses a list of email addresses into its parts. * * Works with and without the imap extension being available and parses * distribution lists as well. * * @since Horde 3.2 * @see http://www.php.net/imap_rfc822_parse_adrlist * * @param string $address The address string. * @param string $defserver The default domain to append to mailboxes. * @param boolean $validate Whether to validate the address(es). * * @return array A list of objects with the possible properties 'mailbox', * 'host', 'personal', 'adl', and 'comment'. */ function parseAddressList($address, $defserver = null, $validate = false) { if (preg_match('/undisclosed-recipients:\s*;/i', trim($address))) { return array(); } /* Use built-in IMAP function only if available and if not parsing * distribution lists because it doesn't parse distribution lists * properly. */ if (!$validate && strpos($address, ':') === false && Util::extensionExists('imap')) { return imap_rfc822_parse_adrlist($address, $defserver); } else { static $parser; if (!isset($parser)) { require_once 'Mail/RFC822.php'; $parser = new Mail_RFC822(); } $ret = $parser->parseAddressList($address, $defserver, false, $validate); return is_a($ret, 'PEAR_Error') ? array() : $ret; } } /** * Quotes and escapes the given string if necessary using rules contained * in RFC 2822 [3.2.5]. * * @access private * * @param string $str The string to be quoted and escaped. * @param string $type Either 'address' or 'personal'. * * @return string The correctly quoted and escaped string. */ function _rfc822Encode($str, $type = 'address') { // Excluded (in ASCII): 0-8, 10-31, 34, 40-41, 44, 58-60, 62, 64, // 91-93, 127 $filter = "\0\1\2\3\4\5\6\7\10\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\"(),:;<>@[\\]\177"; switch ($type) { case 'address': // RFC 2822 [3.4.1]: (HTAB, SPACE) not allowed in address $filter .= "\11\40"; break; case 'personal': // RFC 2822 [3.4]: Period not allowed in display name $filter .= '.'; break; default: // BC: $filter was passed in explicitly $filter = $type; } // Strip double quotes if they are around the string already. // If quoted, we know that the contents are already escaped, so // unescape now. if (substr($str, 0, 1) == '"' && substr($str, -1) == '"') { $str = stripslashes(substr($str, 1, -1)); } if (strcspn($str, $filter) != strlen($str)) { return '"' . addcslashes($str, '\\"') . '"'; } else { return $str; } } /** * Returns the MIME type for the given input. * * @param mixed $input Either the MIME code or type string. * @param integer $format If MIME_CODE, return code. * If MIME_STRING, returns lowercase string. * * @return mixed See above. */ function type($input, $format = null) { return MIME::_getCode($input, $format, 'mime_types'); } /** * Returns the MIME encoding for the given input. * * @param mixed $input Either the MIME code or encoding string. * @param integer $format If MIME_CODE, return code. * If MIME_STRING, returns lowercase string. * If not set, returns the opposite value. * * @return mixed See above. */ function encoding($input, $format = null) { return MIME::_getCode($input, $format, 'mime_encodings'); } /** * Retrieves MIME encoding/type data from the internal arrays. * * @access private * * @param mixed $input Either the MIME code or encoding string. * @param string $format If MIME_CODE, returns code. * If MIME_STRING, returns lowercase string. * If null, returns the oppposite value. * @param string $type The name of the internal array. * * @return mixed See above. */ function _getCode($input, $format, $type) { $numeric = is_numeric($input); if (!$numeric) { $input = String::lower($input); } switch ($format) { case MIME_CODE: if ($numeric) return $input; break; case MIME_STRING: if (!$numeric) return $input; break; } $vars = get_class_vars('MIME'); if ($numeric) { if (isset($vars[$type][$input])) { return $vars[$type][$input]; } } else { if (($search = array_search($input, $vars[$type]))) { return $search; } } return null; } /** * Generates a Message-ID string conforming to RFC 2822 [3.6.4] and the * standards outlined in 'draft-ietf-usefor-message-id-01.txt'. * * @param string A message ID string. */ function generateMessageID() { return '<' . date('YmdHis') . '.' . substr(str_pad(base_convert(microtime(), 10, 36), 16, uniqid(mt_rand()), STR_PAD_LEFT), -16) . '@' . $_SERVER['SERVER_NAME'] . '>'; } /** * Adds proper linebreaks to a header string. * RFC 2822 says headers SHOULD only be 78 characters a line, but also * says that a header line MUST not be more than 998 characters. * * @param string $header The header name. * @param string $text The text of the header field. * @param string $eol The EOL string to use. * * @return string The header text, with linebreaks inserted. */ function wrapHeaders($header, $text, $eol = "\r\n") { $header = rtrim($header); $text = rtrim($text); /* Remove any existing linebreaks. */ $text = $header . ': ' . preg_replace("/\r?\n\s?/", ' ', $text); $eollength = strlen($eol); $header_lower = strtolower($header); if (($header_lower != 'content-type') && ($header_lower != 'content-disposition')) { /* Wrap the line. */ $line = wordwrap($text, 75, $eol . ' '); /* Make sure there are no empty lines. */ $line = preg_replace('/' . $eol . ' ' . $eol . ' /', '/' . $eol . " /", $line); return substr($line, strlen($header) + 2); } /* Split the line by the RFC parameter separator ';'. */ $params = preg_split("/\s*;\s*/", $text); $line = ''; $length = 1000 - $eollength; $paramcount = count($params); reset($params); while (list($count, $val) = each($params)) { /* If longer than RFC allows, then simply chop off the excess. */ $moreparams = (($count + 1) != $paramcount); $maxlength = $length - (!empty($line) ? 1 : 0) - (($moreparams) ? 1 : 0); if (strlen($val) > $maxlength) { $val = substr($val, 0, $maxlength); /* If we have an opening quote, add a closing quote after * chopping the rest of the text. */ if (strpos($val, '"') !== false) { $val = substr($val, 0, -1); $val .= '"'; } } if (!empty($line)) { $line .= ' '; } $line .= $val . (($moreparams) ? ';' : '') . $eol; } return substr($line, strlen($header) + 2, ($eollength * -1)); } } * @since Horde 3.0 * @package Horde_Mobile */ class Horde_Mobile_Renderer_html extends Horde_Mobile_Renderer { /** * Properly encode characters for output to an HTML browser. * * @param string $input Characters to encode. * * @return string The encoded text. */ function escape($input) { return @htmlspecialchars($input, ENT_COMPAT, NLS::getCharset()); } /** * Creates the page in the appropriate markup. Depending on the * clients browser type pure HTML, handheldfriendly AvantGo HTML, * i-mode cHTML, or MML is created. * * @param Horde_Mobile $deck The deck to render. */ function render($deck) { if ($deck->_debug) { header('Content-Type: text/plain; charset=' . NLS::getCharset()); } else { header('Content-Type: text/html; charset=' . NLS::getCharset()); } header('Vary: Accept-Language'); if (!$this->isBrowser('mml')) { echo "\n"; } echo !empty($GLOBALS['language']) ? '' : ''; echo ''; if ($this->isBrowser('avantgo')) { echo ''; } printf("%s\n", $this->escape($deck->get('title'))); if ($deck->_simulator) { // Use simulator (mobile theme) stylesheet. echo Horde::stylesheetLink('horde', 'mobile'); } echo ''; if ($deck->_simulator) { echo "

\n"; // Create default device simulator table layout with // central CSS layout. echo "\n"; echo "\n"; echo "\n"; echo "
 
 \n"; } $divstyle = ''; if ($this->hasQuirk('scroll_tds') && $deck->_simulator) { // Make content of table element scrollable (Horde_Mobile // simulator). $divstyle = ' class="simdev"'; } echo ''; if (($cnt = count($deck->_cards)) !== 0) { $i = 0; foreach ($deck->_cards as $card) { if ($i != 0) { echo '
'; } $this->_renderCard($card); $i++; } } else { foreach ($deck->_elements as $page_element) { $this->renderElement($page_element); } } echo ''; if ($deck->_simulator) { // Display lower part of Horde_Mobile default device // simulator. echo '
 
 
'; } echo ''; } function _renderCard($card) { $name = $card->get('name') ? ' name="' . $this->escape($card->get('name')) . '"' : ''; printf('%s', $name, $card->get('title')); if (count($card->_softkeys)) { foreach ($card->_softkeys as $key) { echo ' | ' . $this->escape($key['label']) . ''; } } // Render all tags. foreach ($card->_elements as $page_element) { $this->renderElement($page_element); } } function _renderLink($link) { if ($link->get('title') && !$this->isBrowser('avantgo') && !$this->isBrowser('imode') && !$this->isBrowser('mml')) { $title_option = sprintf(' onmouseover="self.status=\'%s\';return true;"', $this->escape($link->get('title'))); } else { $title_option = ''; } $accesskey_option = ''; if ($link->get('accesskey')) { if ($this->isBrowser('imode')) { $accesskey_option = sprintf(' accesskey="%d"', $link->get('accesskey')); } elseif ($this->isBrowser('mml')) { $accesskey_option = sprintf(' directkey="%d"', $link->get('accesskey')); } } printf('%s', str_replace('&amp;', '&', $this->escape($link->get('url'))), $title_option, $accesskey_option, $this->escape($link->get('label'))); } function _renderLinkset($linkset) { if (count($linkset->_elements)) { echo '
    '; foreach ($linkset->_elements as $val) { echo '
  1. '; $this->_renderLink($val); echo '
  2. '; } echo '
'; } } function _renderText($element) { foreach ($element->_attributes as $attribute) { echo '<' . $attribute . '>'; } if ($element->get('linebreaks')) { echo nl2br($this->escape($element->get('text'))); } else { echo $this->escape($element->get('text')); } $attributes = array_reverse($element->_attributes); foreach ($attributes as $attribute) { echo ''; } } function _renderImage($image) { $attributes = ''; foreach ($image->_attributes as $attribute => $value) { $attributes .= sprintf(' %s="%s"', $attribute, $value); } printf('', $image->_src, $attributes); } function _renderForm($form) { printf('
', $form->get('url'), $form->get('method')); parent::_renderForm($form); echo '
'; } function _renderInput($input) { $type = 'type="' . $input->get('type') . '"'; $size = $input->get('size') ? sprintf('size="%d"', $input->get('size')) : ''; $maxlength = $input->get('maxlength') ? sprintf('maxlength="%d"', $input->get('maxlength')) : ''; if ($this->isBrowser('imode')) { $mode = sprintf(' istyle="%d"', $input->get('mode')); } elseif ($this->isBrowser('mml')) { $mode = $this->_getMode($input->get('mode')); } else { $mode = ''; } // Create HTML input. printf('%s ', $this->escape($input->get('label')), $type, $this->escape($input->get('name')), $this->escape($input->get('value')), $size, $maxlength, $mode); } function _renderTextarea($textarea) { if ($this->isBrowser('imode')) { $mode = sprintf(' istyle="%d"', $this->mode); } elseif ($this->isBrowser('mml')) { $mode = $this->_getMode($this->mode); } else { $mode = ''; } printf('%s
', $this->escape($textarea->get('label')), $textarea->get('name'), $textarea->get('rows'), $textarea->get('cols'), $mode, $textarea->get('value')); } function _renderSelect($select) { $name = $this->escape($select->get('name')); echo ''; } function _renderRadio($radio) { foreach ($radio->_buttons as $val) { $sel = ($val['value'] == $radio->_value) ? ' checked="checked"' : ''; printf(' %s
', $radio->get('name'), $sel, $val['value'], $this->escape($val['label'])); } } function _renderCheckbox($checkbox) { $state = $checkbox->isChecked() ? ' checked="checked"' : ''; printf('
', $checkbox->get('name'), $state, $checkbox->get('value'), $this->escape($checkbox->get('label'))); } function _renderSubmit($submit) { $name = !empty($submit->_name) ? ' name="' . $submit->_name . '"' : ''; printf('
', $name, $this->escape($submit->_label)); } function _renderHidden($hidden) { printf('', $hidden->get('name'), $hidden->get('value')); } function _renderDl($dl) { echo '
'; parent::_renderDl($dl); // Terminate Dl. if ($this->isBrowser('mml')) { // MML has problems with the clear attribute. echo '

'; } else { echo '
'; } } function _renderTable($table) { $border = $table->get('border'); $padding = $table->get('padding'); $spacing = $table->get('spacing'); echo ''; parent::_renderTable($table); // Terminate table. if ($this->isBrowser('mml')) { echo '
'; } else { // MML has problems with the clear attribute. echo '
'; } } function _renderPhone($phone) { if ($this->isBrowser('imode')) { // Create phoneto: link for i-Mode. printf('

%s

', $phone->get('number'), $phone->get('label')); } elseif ($this->isBrowser('mml')) { // Create tel: link for MML. printf('

%s

', $phone->get('number'), $phone->get('label')); } else { // Display phone number as plain text. printf('

%s

', $phone->get('label')); } } function _renderRule($rule) { $width = $rule->get('width'); $size = $rule->get('size'); echo '\n"; } function _getMode($mode) { switch ($mode) { case 'katakana': return ' mode="katakana"'; case 'hiragana': return ' mode="hiragana"'; case 'numeric': return ' mode="numeric"'; case 'alpha': default: return ' mode="alphabet"'; } } } * @since Horde 3.0 * @package Horde_Mobile */ class Horde_Mobile_Renderer_wml extends Horde_Mobile_Renderer { /** * Properly encode characters for output to a WML device. * * @param string $input Characters to encode. * * @return string The encoded text. */ function escape($input) { // Encode entities. $output = @htmlspecialchars($input, ENT_COMPAT, NLS::getCharset()); // Escape $ character in WML. $output = str_replace('$', '$$', $output); // Generate UTF-8. $output = String::convertCharset($output, NLS::getCharset(), 'utf-8'); return $output; } /** * Creates the page in WML, allowing for different WML browser quirks. * * @param Horde_Mobile $deck The deck to render. */ function render(&$deck) { if ($deck->_debug) { header('Content-Type: text/plain; charset=utf-8'); } else { header('Content-Type: text/vnd.wap.wml; charset=utf-8'); } echo "\n"; if ($this->hasQuirk('ow_gui_1.3')) { echo ''; } else { echo ''; } echo ''; if (count($deck->_cards)) { foreach ($deck->_cards as $card) { $this->_renderCard($card); } } else { $title = $deck->get('title') ? ' title="' . $this->escape($deck->get('title')) . '"' : ''; printf('', $title); // Render all tags. foreach ($deck->_elements as $page_element) { $this->renderElement($page_element); } echo ''; } // End the WML page. echo ''; } function _renderCard(&$card) { $name = $card->get('name') ? ' id="' . $this->escape($card->get('name')) . '"' : ''; $title = $card->get('title') ? ' title="' . $this->escape($card->get('title')) . '"' : ''; printf('', $name, $title); // Initialize WML variables with their default values. if (!is_null($card->_form)) { echo ''; $defaults = $card->_form->getDefaults(); foreach ($defaults as $d) { printf('', $d['name'], $this->escape($d['value'])); } echo ''; } if (count($card->_softkeys)) { if (count($card->_softkeys) == 1) { // If there is only one softkey, make it of type // 'options' so that it always shows up on the right, // instead of having to share the left softkey with // active links, making it much harder to get to. $type = 'options'; } else { $type = 'accept'; } foreach ($card->_softkeys as $key) { echo ''; } } // Render all tags. foreach ($card->_elements as $page_element) { $this->renderElement($page_element); } echo ''; } function _renderLink(&$link) { $title_option = $link->get('title') ? sprintf(' title="%s"', $this->escape($link->get('title'))) : ''; printf('%s', $title_option, str_replace('&amp;', '&', $this->escape($link->get('url'))), $this->escape($link->get('label'))); } function _renderLinkset(&$linkset) { if (count($linkset->_elements)) { echo '

'; if ($this->isBrowser('up')) { echo ''; } else { foreach ($linkset->_elements as $val) { $this->_renderLink($val); echo '
'; } } echo '

'; } } function _renderText(&$element) { foreach ($element->_attributes as $attribute) { echo '<' . $attribute . '>'; } if ($element->get('linebreaks')) { echo nl2br($this->escape($element->get('text'))); } else { echo $this->escape($element->get('text')); } $attributes = array_reverse($element->_attributes); foreach ($attributes as $attribute) { echo ''; } } function _renderImage(&$image) { $attributes = ''; foreach ($image->_attributes as $attribute => $value) { $attributes .= sprintf(' %s="%s"', $attribute, $value); } printf('', $image->_src, $attributes); } function _renderInput(&$input) { $type = ' type="' . $input->get('type') . '"'; $size = $input->get('size') ? sprintf(' size="%d"', $input->get('size')) : ''; $maxlength = $input->get('maxlength') ? sprintf(' maxlength="%d"', $input->get('maxlength')) : ''; printf('%s', $this->escape($input->get('label')), $input->get('format'), $type, $this->escape($input->get('name')), $this->escape($input->get('value')), $size, $maxlength); } function _renderTextarea(&$textarea) { printf('%s', $this->escape($textarea->get('label')), $textarea->get('name'), $textarea->get('value')); } function _renderSelect(&$select) { if ($label = $select->get('label')) { echo $this->escape($label) . ' '; } if ($this->hasQuirk('ow_gui_1.3')) { switch ($select->get('type')) { case 'spin': $type_option = 'type="spin"'; break; case 'popup': default: $type_option = 'type="popup"'; break; } echo ''; } $htmlchars = $select->get('htmlchars'); foreach ($select->_options as $val) { $label = $htmlchars ? $val['label'] : $this->escape($val['label']); echo ''; } echo ''; } function _renderRadio(&$radio) { if ($this->hasQuirk('ow_gui_1.3')) { // Openwave GUI extensions for WML 1.3 printf('', $radio->get('name')); } foreach ($radio->_buttons as $val) { printf('', $val['value'], $this->escape($val['label'])); } echo ''; } function _renderCheckbox(&$checkbox) { printf('', $checkbox->get('value'), $this->escape($checkbox->get('label'))); } function _renderSubmit(&$submit) { if ($this->hasQuirk('ow_gui_1.3')) { // Create sequence for Openwave GUI // extensions WML 1.3. printf('', $this->escape($submit->get('label'))); $tag = 'do'; } else { // Create sequence in normal WML. printf('%s', $this->escape($submit->get('label')), $this->escape($submit->get('label'))); $tag = 'anchor'; } if ($submit->_form->get('method') == 'post') { printf('', Horde::url($submit->_form->get('url'))); // Value for this submit element, only if non-empty name. if ($submit->get('name')) { printf('', $submit->get('name'), $this->escape($submit->get('label'))); } $defaults = $submit->_form->getDefaults(); foreach ($defaults as $d) { if (array_key_exists('hidden', $d)) { printf('', $d['name'], $this->escape($d['value'])); } else { printf('', $d['name'], $d['name']); } } } else { // Start with the value for this submit element. $query_string = $submit->get('name') . '=' . $this->escape($submit->get('label')) . '&'; $getvars = $submit->_form->getGetVars(); foreach ($getvars as $val) { $query_string .= $val . '=$(_' . $val . ')&'; } if (substr($query_string, -5) == '&') { $query_string = substr($query_string, 0, strlen($query_string) - 5); } printf('', $submit->_form->get('url'), $query_string); } echo ""; } function _renderTable(&$table) { // Count maximum number of columns in table. $max = 0; foreach ($table->_rows as $row) { $max = max($max, $row->getColumnCount()); } printf('

', $max); parent::_renderTable($table); // Terminate table. echo '

'; } function _renderPhone(&$phone) { $title = $phone->get('title'); $title_option = ($title ? sprintf(' title="%s"', $this->escape($title)) : ''); printf('%s', $title_option, str_replace('+', '%2B', $phone->get('number')), $phone->get('label')); } function _renderRule(&$rule) { if ($this->hasQuirk('ow_gui_1.3')) { // WAP device accepts Openwave GUI extensions for WML 1.3 $width = $rule->get('width'); $size = $rule->get('size'); echo ''; } else { // WAP device does not understand
tags. // ==> draw some number of hyphens to create a rule echo '----------
'; } } } * @since Horde 3.0 * @package Horde_Mobile */ class Horde_Mobile_Renderer extends Horde_Mobile { var $_browser; function Horde_Mobile_Renderer($browser = null) { if (is_null($browser)) { $this->_browser = new Browser(); } else { $this->_browser = $browser; } } function isBrowser($agent) { return $this->_browser->isBrowser($agent); } function hasQuirk($quirk) { return $this->_browser->hasQuirk($quirk); } /** * Render any Horde_Mobile_element object. Looks for the * appropriate rendering function in the renderer; if there isn't * one, we ignore this element. * * @param Horde_Mobile_element $element The element to render. */ function renderElement(&$element) { $func = '_render' . ucfirst(str_replace('horde_mobile_', '', strtolower(get_class($element)))); if (method_exists($this, $func)) { $this->$func($element); } } function _renderBlock(&$block) { if (count($block->_elements)) { echo '

'; foreach ($block->_elements as $blockElement) { $this->renderElement($blockElement); } echo "

\n"; } } function _renderForm(&$form) { foreach ($form->_elements as $formElement) { $this->renderElement($formElement); } } function _renderDl(&$dl) { foreach ($dl->_dts as $dt) { $this->_renderDt($dt); } } function _renderDt(&$dt) { $i = 0; foreach ($dt->_dds as $dd) { echo $out = ($i == 0) ? '
' : '
'; // Call create function for each ddelement that is a // Horde_Mobile object. if (!is_null($dd)) { $this->renderElement($dd); } echo $out = ($i++ == 0) ? '' : '
'; } } function _renderTable(&$table) { foreach ($table->_rows as $row) { $this->_renderRow($row); } } function _renderRow(&$row) { echo ''; foreach ($row->_columns as $column) { echo ''; // Call create function for each cellelement that is a // Horde_Mobile object. if (!is_null($column)) { $this->renderElement($column); } echo ''; } echo "\n"; } /** * Attempts to return a concrete Horde_Mobile_Renderer instance * based on $type. * * @param string $type The kind of markup (html, hdml, wml) we want to * generate. * @param Browser $browser The Browser object to use. * @param array $params A hash containing any options for the renderer. * * @return Horde_Mobile_Renderer The newly created concrete * Horde_Mobile_Renderer instance, or a * PEAR_Error object on an error. */ function &factory($type, $browser = null, $params = array()) { $type = basename($type); $class = 'Horde_Mobile_Renderer_' . $type; if (!class_exists($class)) { include_once 'Horde/Mobile/Renderer/' . $type . '.php'; } if (class_exists($class)) { $renderer = new $class($browser, $params); } else { $renderer = PEAR::raiseError('Class definition of ' . $class . ' not found.'); } return $renderer; } /** * Attempts to return a concrete Horde_Mobile_Renderer instance * based on $type. It will only create a new instance if no * renderer with the same parameters currently exists. * * @param string $type The kind of markup (html, hdml, wml) we want to * generate. * @param Browser $browser The Browser object to use. * @param array $params A hash containing any options for the renderer. * * @return Horde_Mobile_Renderer The newly created concrete * Horde_Mobile_Renderer instance, or a * PEAR_Error object on an error. */ function &singleton($type, $browser = null, $params = array()) { static $instances = array(); $signature = md5(serialize(array($type, $browser, $params))); if (!isset($instances[$signature])) { $instances[$signature] = &Horde_Mobile_Renderer::factory($type, $browser, $params); } return $instances[$signature]; } } _name = $name; $this->_title = $title; } function &add(&$element) { if (!is_a($element, 'Horde_Mobile_element')) { $error = PEAR::raiseError('Invalid element.'); return $error; } elseif (is_a($element, 'Horde_Mobile_text') || is_a($element, 'Horde_Mobile_image') || is_a($element, 'Horde_Mobile_link') || is_a($element, 'Horde_Mobile_phone') || is_a($element, 'Horde_Mobile_rule')) { $block = &new Horde_Mobile_block($element); $this->_elements[] = &$block; } elseif (is_a($element, 'Horde_Mobile_block')) { $this->_elements[] = &$element; } elseif (is_a($element, 'Horde_Mobile_form')) { if (!empty($this->_form)) { $error = PEAR::raiseError('Cards may only contain one Form element.'); return $error; } $this->_elements[] = &$element; $this->_form = &$element; } elseif (is_a($element, 'Horde_Mobile_linkset')) { if ($this->_linksetAdded) { $error = PEAR::raiseError('Cards may only contain one Linkset element.'); return $error; } $this->_elements[] = &$element; $this->_linksetAdded = true; } else { $error = PEAR::raiseError('This element must be inside an appropriate container element.'); return $error; } return $element; } function softkey($url, $label) { $this->_softkeys[] = array('url' => $url, 'label' => $label); } } /** * Horde_Mobile:: * * Horde API for generating Mobile content. Includes numerous utility * functions, generalized element classes, and renderers for markup * languages including WML, HDML, and CHTML. * * This class is the top level class of all Horde_Mobile classes. Your * page should consist of exactly one Horde_Mobile object. Appropriate * markup - Imode, WML, HDML, etc. - is generated by the appropriate * renderer object * * Do not overstuff Horde_Mobile objects. Remember that a lot of WAP * clients cannot handle more than about 1400 bytes of compiled data. * * Examples: * * $myPage = new Horde_Mobile(); * $myPage = new Horde_Mobile('My WAP page'); * $myPage = new Horde_Mobile('', 'center'); * * // More stuff * * $myPage->add($myText); * * // More items * * $myPage->render(); * * $Horde: framework/Mobile/Mobile.php,v 1.32.10.14 2009-01-06 15:23:25 jan Exp $ * * Copyright 2002-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. * * @author Chuck Hagenbuch * @since Horde 3.0 * @package Horde_Mobile */ class Horde_Mobile extends Horde_Mobile_card { var $_title; var $_elements = array(); var $_cards = array(); var $_debug = false; // Decide whether the simulator device is to be used. Only affects // HTML browser output. var $_simulator = false; /** * Constructor * * @param string $title If a string is provided here, it will be displayed * in the HTML title bar, respectively somewhere on * the WAP display. Using a title you will normally * have to spend one of your few lines on your WAP * display. Consider that some WAP phones/SDK's and * handheld devices don't display the title at all. * @param string $agent If specified, use instead of HTTP_USER_AGENT. */ function Horde_Mobile($title = null, $agent = null) { if (!is_null($title)) { $this->_title = $title; } require_once 'Horde/Browser.php'; $browser = &Browser::singleton($agent); if ($browser->hasFeature('html')) { $ml = 'html'; } elseif ($browser->hasFeature('wml')) { $ml = 'wml'; } else { $ml = 'html'; } require_once dirname(__FILE__) . '/Mobile/Renderer.php'; $this->_renderer = &Horde_Mobile_Renderer::singleton($ml, $browser); } function &add(&$element) { if (is_a($element, 'Horde_Mobile_card')) { if (count($this->_elements)) { $error = PEAR::raiseError('You cannot mix Horde_Mobile_cards and other elements at the deck level.'); return $error; } $this->_usingCards = true; $this->_cards[] = &$element; return $element; } else { if (count($this->_cards)) { $error = PEAR::raiseError('You cannot mix Horde_Mobile_cards and other elements at the deck level.'); return $error; } return parent::add($element); } } /** * Activates the built-in device simulator on bigscreen browsers. * The device simulator is only fully-functional in Internet * Explorer, because the layout requires a scrollable table * element. Other browsers will fail to show content on pages * longer than a single screen. */ function useSimulator() { $this->_simulator = true; } /** * Creates the page in the appropriate markup language. Depending * on the renderer type, HTML (pure HTML, handheldfriendly AvantGo * HTML, i-mode cHTML, MML), WML or HDML code is created. */ function display() { $this->_renderer->render($this); } } /** * @package Horde_Mobile */ class Horde_Mobile_element { /** * @TODO Remove in Horde 4.0 */ function Horde_Mobile_element() { } function get($attribute) { $attr = '_' . $attribute; if (isset($this->$attr)) { return $this->$attr; } else { return null; } } function set($attribute, $value) { $attr = '_' . $attribute; $this->$attr = $value; } } /** * @package Horde_Mobile */ class Horde_Mobile_formElement extends Horde_Mobile_element { var $_name; var $_value; var $_label; var $_size; var $_maxlength; var $_type; var $_format; var $_mode; /** * Set input mode/istyle for japanese MML/i-mode devices. * * @param string $mode Input mode, one of: * 'alpha' (default) * 'katakana' * 'hiragana' * 'numeric' */ function setMode($mode) { $this->_mode = $mode; // Map the mode into an appropriate format string, used for // WML and HDML. If a format string was provided earlier, it // will be overwritten. switch ($mode) { case 'hiragana': case 'katakana': $this->_format = '*M'; break; case 'alpha': $this->_format = '*m'; break; case 'numeric': $this->_format = '*N'; break; } } } /** * This class defines a form with various possible input elements. The * input elements have to be defined as separate objects and are * linked to the form with a special "add" function. One Horde_Mobile * object can contain only one Horde_Mobile_form object. * * Examples: * * $myPage = new Horde_Mobile(...); * * $myForm = new Horde_Mobile_form("/mynextpage.wml"); * $myText = new Horde_Mobile_text(...); * $myForm->add($myText); * $myInput = new Horde_Mobile_input(...); * $myForm->add($myInput); * $mySubmit = new Horde_Mobile_submit(...); * $myForm->add($mySubmit); * * $myPage->add($myForm); * * $myPage->render(); * * @see Horde_Mobile_text * @see Horde_Mobile_image * @see Horde_Mobile_table * @see Horde_Mobile_dl * @see Horde_Mobile_input * @see Horde_Mobile_textarea * @see Horde_Mobile_select * @see Horde_Mobile_radio * @see Horde_Mobile_checkbox * @see Horde_Mobile_submit * @see Horde_Mobile_rule * * @package Horde_Mobile */ class Horde_Mobile_form extends Horde_Mobile_element { var $_url; var $_method; var $_elements = array(); /** * Constructor * * @param string $url Address where the user input is sent to. * @param string $method 'post' (default) or 'get'. * @param boolean $session Preserve the sesion id in the form? Defaults to true. */ function Horde_Mobile_form($url, $method = 'post', $session = true) { $this->_url = $url; $this->_method = $method; if ($session && !array_key_exists(session_name(), $_COOKIE)) { $this->add(new Horde_Mobile_hidden(session_name(), session_id())); } } function &add(&$formElement) { if (is_a($formElement, 'Horde_Mobile_submit')) { $formElement->_form = &$this; $block = &new Horde_Mobile_block($formElement); $this->_elements[] = &$block; } elseif (is_a($formElement, 'Horde_Mobile_hidden') || is_a($formElement, 'Horde_Mobile_block')) { $this->_elements[] = &$formElement; } elseif (is_a($formElement, 'Horde_Mobile_formElement') || is_a($formElement, 'Horde_Mobile_text') || is_a($formElement, 'Horde_Mobile_rule') || is_a($formElement, 'Horde_Mobile_image')) { $block = &new Horde_Mobile_block($formElement); $this->_elements[] = &$block; } else { $error = PEAR::raiseError('Specified element cannot be inside a form.'); return $error; } return $formElement; } function getDefaults() { $defaults = array(); foreach ($this->_elements as $val) { switch (strtolower(get_class($val))) { case 'horde_mobile_hidden': $defaults[] = array('name' => $val->get('name'), 'value' => $val->get('value'), 'hidden' => true); break; case 'horde_mobile_block': foreach ($val->_elements as $bval) { switch (strtolower(get_class($bval))) { case 'horde_mobile_checkbox': if ($bval->isChecked()) { $defaults[] = array('name' => $bval->get('name'), 'value' => $bval->get('value')); } break; case 'horde_mobile_input': case 'horde_mobile_textarea': case 'horde_mobile_select': case 'horde_mobile_radio': $defaults[] = array('name' => $bval->get('name'), 'value' => $bval->get('value')); break; case 'horde_mobile_hidden': $defaults[] = array('name' => $bval->get('name'), 'value' => $bval->get('value'), 'hidden' => true); break; case 'horde_mobile_table': foreach ($bval->_rows as $row) { foreach ($row->_columns as $col) { switch (strtolower(get_class($col))) { case 'horde_mobile_checkbox': if ($col->isChecked()) { $defaults[] = array('name' => $bval->get('name'), 'value' => $bval->get('value')); } break; case 'horde_mobile_input': case 'horde_mobile_textarea': case 'horde_mobile_select': case 'horde_mobile_radio': $defaults[] = array('name' => $bval->get('name'), 'value' => $bval->get('value')); break; case 'horde_mobile_hidden': $defaults[] = array('name' => $bval->get('name'), 'value' => $bval->get('value'), 'hidden' => true); break; } } } break; case 'horde_mobile_dl': foreach ($bval->_dts as $dt) { foreach ($dt->_dds as $dd) { switch (strtolower(get_class($dd))) { case 'horde_mobile_checkbox': if ($dd->isChecked()) { $defaults[] = array('name' => $bval->get('name'), 'value' => $bval->get('value')); } break; case 'horde_mobile_input': case 'horde_mobile_textarea': case 'horde_mobile_select': case 'horde_mobile_radio': $defaults[] = array('name' => $bval->get('name'), 'value' => $bval->get('value')); break; case 'horde_mobile_hidden': $defaults[] = array('name' => $bval->get('name'), 'value' => $bval->get('value')); break; } } } break; } } break; } } return $defaults; } function getGetVars() { // Determine all elements that have to be submitted. $getvars = array(); foreach ($this->_elements as $val) { switch (strtolower(get_class($val))) { case 'horde_mobile_block': foreach ($val->_elements as $bval) { switch (strtolower(get_class($bval))) { case 'horde_mobile_input': case 'horde_mobile_hidden': case 'horde_mobile_textarea': case 'horde_mobile_select': case 'horde_mobile_checkbox': case 'horde_mobile_radio': $getvars[] = $bval->get('name'); break; case 'horde_mobile_table': foreach ($bval->_rows as $row) { foreach ($row->_columns as $col) { switch (strtolower(get_class($col))) { case 'horde_mobile_input': case 'horde_mobile_hidden': case 'horde_mobile_textarea': case 'horde_mobile_select': case 'horde_mobile_checkbox': case 'horde_mobile_radio': $getvars[] = $col->get('name'); break; } } } break; case 'horde_mobile_dl': foreach ($bval->_dts as $dt) { foreach ($dt->_dds as $dd) { switch (strtolower(get_class($dd))) { case 'horde_mobile_input': case 'horde_mobile_hidden': case 'horde_mobile_textarea': case 'horde_mobile_select': case 'horde_mobile_checkbox': case 'horde_mobile_radio': $getvars[] = $dd->get('name'); break; } } } break; } } break; } } return $getvars; } } /** * This class holds text-level elements for use in Horde_Mobile or * Horde_Mobile_form objects. * * Examples: * * $block = new Horde_Mobile_block("Hello World"); * $text = new Horde_Mobile_text("Welcome to Horde_Mobile", 'b'); * $block->add($text); * * @see Horde_Mobile * @see Horde_Mobile_form * * @package Horde_Mobile */ class Horde_Mobile_block extends Horde_Mobile_element { var $_elements = array(); /** * Constructor. * * @param mixed $elements Any elements (a single one or an array) to fill this block with. */ function Horde_Mobile_block(&$elements) { if (!is_null($elements)) { if (!is_array($elements)) { $this->add($elements); } else { foreach ($elements as $element) { if ($this->allows($element)) { $this->_elements[] = $element; } } } } } function &add(&$element) { if (!is_a($element, 'Horde_Mobile_element')) { $error = PEAR::raiseError('Invalid element.'); } elseif ($this->allows($element)) { $this->_elements[] = &$element; return $element; } else { $error = PEAR::raiseError('The element is not allowed inside a block.'); } return $error; } function allows($element) { return (is_a($element, 'Horde_Mobile_text') || is_a($element, 'Horde_Mobile_table') || is_a($element, 'Horde_Mobile_dl') || is_a($element, 'Horde_Mobile_image') || is_a($element, 'Horde_Mobile_formElement') || is_a($element, 'Horde_Mobile_link') || is_a($element, 'Horde_Mobile_linkset') || is_a($element, 'Horde_Mobile_phone') || is_a($element, 'Horde_Mobile_rule')); } } /** * This class inserts plain text into a Horde_Mobile_block or a * Horde_Mobile_row object. * * Examples: * * $myText1 = new Horde_Mobile_text("Hello World"); * $myText2 = new Horde_Mobile_text("Welcome to Horde_Mobile", 'b'); * $myText3 = new Horde_Mobile_text("Good Morning", array('b', 'big')); * * @see Horde_Mobile_block * @see Horde_Mobile_row * * @package Horde_Mobile */ class Horde_Mobile_text extends Horde_Mobile_element { var $_text = ''; var $_attributes = array(); var $_linebreaks = false; /** * Constructor * @param string $text The text content of the element. * @param array $attributes Text attributes. Any of: * 'b' * 'u' * 'i' * 'big' * 'small' */ function Horde_Mobile_text($text, $attributes = array()) { $this->_text = $text; if (!is_array($attributes)) { $attributes = array($attributes); } $this->_attributes = $attributes; } } /** * This class allows to insert bitmap images into a Horde_Mobile_block, * Horde_Mobile_form or Horde_Mobile_row object. * * Examples: * * $image = new Horde_Mobile_image('/path/to/image.wbmp', * array('height' => 100, 'width' => 100)); * * @see Horde_Mobile_block * @see Horde_Mobile_form * @see Horde_Mobile_row * * @package Horde_Mobile */ class Horde_Mobile_image extends Horde_Mobile_element { var $_src = ''; var $_attributes = array(); /** * Constructor * @param string $src The source location of the image. * @param array $attributes Image attributes. Any of: * 'align' * 'alt' * 'height' * 'hspace' * 'vspace' * 'width' * 'class' * 'id' */ function Horde_Mobile_image($src, $attributes = array()) { $this->_src = $src; $this->_attributes = $attributes; } } /** * This class allows to insert tables into a Horde_Mobile or * Horde_Mobile_form object. * * Examples: * * $myTable = new Horde_Mobile_table(); * * $row1 = new Horde_Mobile_row(); * $row1->add($image1); * $row1->add($text1); * $myTable->add($row1); * * $row2 = new Horde_Mobile_row(); * $row2->add($image2); * $row2->add($text2); * $myTable->add($row2); * * $myDeck->add($myTable); * * @see Horde_Mobile * @see Horde_Mobile_form * @see Horde_Mobile_row * * @package Horde_Mobile */ class Horde_Mobile_table extends Horde_Mobile_block { var $_rows = array(); var $_border = null; var $_padding = null; var $_spacing = null; /** * Constructor. Need because Horde_Mobile_block requires a reference * parameter and PHP 4 does not allow default arguments. * * @TODO Remove in Horde 4.0 */ function Horde_Mobile_table() { } /** * Adds a Horde_Mobile_row object to Horde_Mobile_table. * * @param Horde_Mobile_row $row The row object to add. */ function &add(&$row) { if (!is_a($row, 'Horde_Mobile_row')) { $error = PEAR::raiseError('Rows must be Horde_Mobile_row objects.'); return $error; } $this->_rows[] = &$row; return $row; } } /** * This class defines the rows that a Horde_Mobile_table object * consists of. * * Examples: * * $image1 = new Horde_Mobile_image("my_image.wbmp", "my_image.png", ":-)"); * $text1 = new Horde_Mobile_text("my text"); * $row1 = new Horde_Mobile_row(); * $row1->add($image1); * $row1->add(); * $row1->add($text1); * * @see Horde_Mobile_table * @see Horde_Mobile_text * @see Horde_Mobile_image * @see Horde_Mobile_link * * @package Horde_Mobile */ class Horde_Mobile_row extends Horde_Mobile_element { var $_columns = array(); /** * Adds a column element to a Horde_Mobile_row object. * * @param Horde_Mobile_element $cellElement Can be a Horde_Mobile_text * object, a Horde_Mobile_image * object, a Horde_Mobile_link * object or null. The latter * results in an empty cell. */ function &add($cellElement = null) { if (is_object($cellElement)) { if (!is_a($cellElement, 'Horde_Mobile_text') && !is_a($cellElement, 'Horde_Mobile_link') && !is_a($cellElement, 'Horde_Mobile_image')) { $error = PEAR::raiseError('Table cells can only contain text, links, or images.'); return $error; } $this->_columns[] = &$cellElement; return $cellElement; } elseif (!is_null($cellElement)) { $t = &new Horde_Mobile_text($cellElement); $this->_columns[] = &$t; return $t; } else { $this->_columns[] = &$cellElement; return $cellElement; } } function getColumnCount() { return count($this->_columns); } } /** * This class allows to insert definition lists into a Horde_Mobile or * Horde_Mobile_form object. * * Examples: * * $myDl = new Horde_Mobile_dl(); * * $dt1 = new Horde_Mobile_dt(); * $dt1->add($image1); * $dt1->add($text1); * $myDl->add($dt1); * * $dt2 = new Horde_Mobile_dt(); * $dt2->add($image2); * $dt2->add($text2); * $myDl->add($dt2); * * $myDeck->add($myDl); * * @see Horde_Mobile * @see Horde_Mobile_form * @see Horde_Mobile_dt * * @package Horde_Mobile */ class Horde_Mobile_dl extends Horde_Mobile_block { var $_dts = array(); /** * Constructor. Need because Horde_Mobile_block requires a reference * parameter and PHP 4 does not allow default arguments. * * @TODO Remove in Horde 4.0 */ function Horde_Mobile_dl() { } /** * Adds a Horde_Mobile_dt object to Horde_Mobile_dl. * * @param Horde_Mobile_dt $dt The dl object to add. */ function &add(&$dt) { if (!is_a($dt, 'Horde_Mobile_dt')) { $error = PEAR::raiseError('Must be Horde_Mobile_dt objects.'); return $error; } $this->_dts[] = &$dt; return $dt; } } /** * This class defines the terms of a Horde_Mobile_dl object. * * Examples: * * $image1 = new Horde_Mobile_image("my_image.wbmp", "my_image.png", ":-)"); * $text1 = new Horde_Mobile_text("my text"); * $dt1 = new Horde_Mobile_dt(); * $dt1->add($image1); * $dt1->add(); * $dt1->add($text1); * * @see Horde_Mobile_dl * @see Horde_Mobile_text * @see Horde_Mobile_image * @see Horde_Mobile_link * * @package Horde_Mobile */ class Horde_Mobile_dt extends Horde_Mobile_element { var $_dds = array(); /** * Adds a definition term element to a Horde_Mobile_dt object. * * @param Horde_Mobile_Element $ddElement Can be a Horde_Mobile_text * object, a Horde_Mobile_image * object, a Horde_Mobile_link * object or null. The latter * results in an empty dd. * * @return Horde_Mobile_Element */ function &add($ddElement = null) { if (is_object($ddElement)) { if (!is_a($ddElement, 'Horde_Mobile_text') && !is_a($ddElement, 'Horde_Mobile_link') && !is_a($ddElement, 'Horde_Mobile_image')) { $error = PEAR::raiseError('Description can only contain text, links or images.'); return $error; } $this->_dds[]= &$ddElement; return $ddElement; } elseif (!is_null($ddElement)) { $t = &new Horde_Mobile_text($ddElement); $this->_dds[] = &$t; return $t; } else { $this->_dds[] = &$ddElement; return $ddElement; } } function getDdsCount() { return count($this->_dds); } } /** * This class provides a text input field in a Horde_Mobile_form object. * * Examples: * * $myInput1 = new Horde_Mobile_input('cid', '', 'Customer ID'); * * $myInput2 = new Horde_Mobile_input('cid', '', 'Customer ID', '*N'); * $myInput2->set_size(6); * $myInput2->set_maxlength(6); * * $myInput3 = new Horde_Mobile_input('pw', '', 'Password', '*N'); * $myInput3->set_size(8); * $myInput3->set_maxlength(8); * $myInput3->set_type('password'); * * @see Horde_Mobile_form * * @package Horde_Mobile */ class Horde_Mobile_input extends Horde_Mobile_formElement { /** * Constructor * * @param string $name Variable in which the input is sent to the * destination URL. * @param string $value Initial value that will be presented in the * input field. * @param string $label Describes your input field on the surfer's * screen/display. * @param string $format Input format code according to the WAP standard. * Allows the WAP user client e.g. to input only * digits and no characters. On an HTML generated * page this format has no significance. */ function Horde_Mobile_input($name, $value, $label = '', $format = '*M') { $this->_name = $name; $this->_value = $value; $this->_label = $label; $this->_format = $format; $this->_type = 'text'; $this->_mode = 'alpha'; } } /** * This class provides an input textarea in a Horde_Mobile_form object. * * Examples: * * $myArea1 = new Horde_Mobile_textarea('fb', '', 'Feedback'); * $myArea2 = new Horde_Mobile_textarea('msg', 'Enter message here ...', 'Message', 40, 5); * * @see Horde_Mobile_form * * @package Horde_Mobile */ class Horde_Mobile_textarea extends Horde_Mobile_formElement { var $_rows; var $_cols; /** * Constructor. * * @param string $name Variable in which the input is sent to the * destination URL. * @param string $value Initial value that will be presented in the * textarea. * @param string $label Describes your textarea on the surfer's * screen/display. * @param integer $rows Rows. * @param integer $cols Columns. */ function Horde_Mobile_textarea($name, $value, $label, $rows = 3, $cols = 16) { $this->_name = $name; $this->_value = $value; $this->_label = $label; $this->_rows = $rows; $this->_cols = $cols; $this->_mode = 'alpha'; } } /** * This class provides a select element in a Horde_Mobile_form object. * It allows to create optimized WML for WAP devices which are capable * to interprete the Openwave GUI extensions for WML 1.3. All other * WML devices receive WML 1.1 compatible markup code, which is quite * similar to the markup code created by the Horde_Mobile_radio class. * * Examples: * * $mySelect = new Horde_Mobile_select('color'); * $mySelect->add('Blue', 'b'); * $mySelect->add('Red', 'r', true); * $mySelect->add('Yellow', 'y'); * * @see Horde_Mobile_form * * @package Horde_Mobile */ class Horde_Mobile_select extends Horde_Mobile_formElement { var $_type; var $_options = array(); var $_htmlchars = true; /** * Constructor * * @param string $name Variable in which the information about the * selected option is sent to the destination URL. * @param string $type Type of select area: * 'popup': popup the whole selection list * 'spin': rotate options on a WAP device screen (OW * 1.3 GUI only). * @param string $label Describes your input field on the surfer's * screen/display. * * @param string $htmlchars Are the options already encoded for output? */ function Horde_Mobile_select($name, $type = 'popup', $label = '', $htmlchars = false) { $this->_name = $name; $this->_type = $type; $this->_label = $label; $this->_value = null; $this->_htmlchars = $htmlchars; } /** * Adds one option to a Horde_Mobile_select object. * * @param string $label Describes the option on the surfer's * screen/display. * @param string $value Value sent in the "name" variable, if this * is the option selected. * @param boolean $is_selected Allowed values are true or false. */ function add($label, $value, $is_selected = false) { $this->_options[] = array('label' => $label, 'value' => $value); if (is_null($this->_value) || $is_selected) { $this->_value = $value; } } } /** * This class provides a radio button element in a Horde_Mobile_form object. * * Examples: * * $myRadio = new Horde_Mobile_radio('country'); * $myRadio->add('Finland', 'F'); * $myRadio->add('Germany', 'G', true); * $myRadio->add('Sweden', 'S'); * * @see Horde_Mobile_form * * @package Horde_Mobile */ class Horde_Mobile_radio extends Horde_Mobile_formElement { var $_buttons = array(); /** * Constructor * * @param string $name Variable in which the information about the pressed button * is sent to the destination URL. */ function Horde_Mobile_radio($name) { $this->_name = $name; $this->_value = null; } /** * Adds one radio button to a Horde_Mobile_radio object. * * @param string $label Describes the radiobutton on the surfer's * screen/display. * @param string $value Value sent in the "name" variable, if this * button is selected. * @param boolean $is_checked Allowed values are true or false. */ function add($label, $value, $is_checked = false) { $this->_buttons[] = array('label' => $label, 'value' => $value); if (!$this->_value || ($is_checked)) { $this->_value = $value; } } } /** * This class provides a single checkbox element in a Horde_Mobile_form object. * * Examples: * * $myCheckbox = new Horde_Mobile_checkbox('agmt', 'yes', 'I agree'); * $myCheckbox = new Horde_Mobile_checkbox('agmt', 'yes', 'I agree', false); * $myCheckbox = new Horde_Mobile_checkbox('agmt', 'yes', 'I agree', true); * * @note The first and second examples are identical. * * @see Horde_Mobile_form * * @package Horde_Mobile */ class Horde_Mobile_checkbox extends Horde_Mobile_formElement { var $_checked; /** * Constructor * * @param string $name Variable in which "value" is sent to the * destination URL, if the box is checked. * @param string $value See name. * @param string $label Describes the checkbox on the surfer's * screen/display. * @param boolean $checked Allowed values are true or false. */ function Horde_Mobile_checkbox($name, $value, $label, $checked = false) { $this->_name = $name; $this->_value = $value; $this->_label = $label; $this->_checked = $checked; } function isChecked() { return $this->_checked; } } /** * This class provides hidden elements in Horde_Mobile_form objects. * * Examples: * * $hidden = new Horde_Mobile_hidden('internal_reference', '08154711'); * * @see Horde_Mobile_form * * @package Horde_Mobile */ class Horde_Mobile_hidden extends Horde_Mobile_formElement { /** * Constructor * * @param string $name Variable in which $value is sent to the destination URL. * @param string $value See name. */ function Horde_Mobile_hidden($name, $value) { $this->_name = $name; $this->_value = $value; } } /** * This class provides a submit button for a Horde_Mobile_form object. One * Horde_Mobile_form object can contain only one Horde_Mobile_submit object. * * Examples: * $mySubmit = &new Horde_Mobile_submit('Submit'); * $mySubmit = &new Horde_Mobile_submit('Submit', 'user_pressed'); * * @see Horde_Mobile_form * * @package Horde_Mobile */ class Horde_Mobile_submit extends Horde_Mobile_formElement { var $_form; /** * Constructor * * @param string $label What's written on the button. * @param string $name Variable in which "label" is sent to the * destination URL. */ function Horde_Mobile_submit($label, $name = '') { $this->_label = $label; $this->_name = $name; } } /** * This class provides a link in a Horde_Mobile, Horde_Mobile_linkset or * Horde_Mobile_table object. * * Examples: * * $myPage = new Horde_Mobile(...); * * $myLink = new Horde_Mobile_link('Continue', '/mynextpage.wml'); * $myPage->add($myLink); * * @see Horde_Mobile * @see Horde_Mobile_linkset * @see Horde_Mobile_table * * @package Horde_Mobile */ class Horde_Mobile_link extends Horde_Mobile_element { var $_label; var $_url; var $_title; var $_accesskey; /** * Constructor * * @param string $label Describes the link on the surfer's screen/display. * @param string $url Next destination address. MUST be valid XML (& instead of &, etc.). * @param string $title If a string is provided here, it will be displayed * in the HTML browser status bar during * "MouseOver", respectively somewhere on the WAP * display. In order to work well with a broad range * of user agents, keep your title under 6 * characters. */ function Horde_Mobile_link($label, $url, $title = '') { $this->_label = $label; $this->_url = $url; $this->_title = $title; // No accesskey assigned by default; can be assigned later // from a Horde_Mobile_linkset object if required. $this->_accesskey = null; } } /** * This class provides a phone number in a Horde_Mobile object. If supported by * their mobile device, users can establish a voice connection to the * specified number. * * Examples: * * $myPhone = &new Horde_Mobile_phone('123-45678', 'CALL'); * $myPage->add($myPhone); * * @see Horde_Mobile * * @package Horde_Mobile */ class Horde_Mobile_phone extends Horde_Mobile_element { var $_label; var $_number; var $_title; /** * Constructor * * @param string $phone_number Phone number to dial. * @param string $title If a string is provided here, the call * button on a WAP/HDML device will be * enabled. In order to work well with * a broad range of user agents, keep your * title under 6 characters. */ function Horde_Mobile_phone($phone_number, $title = '') { $this->_label = $phone_number; $this->_number = preg_replace('|\D|', '', $phone_number); $this->_title = $title; } } /** * This class defines a set of links. The links have to be defined as * separate Horde_Mobile_link objects and are attached to the linkset * with a special "add" function. For WAP devices browser-dependent * WML code will be created. On all UP-browser-based WAP devices * linksets allow easier navigation through WML decks by using the * "onpick" WML option and therefore are improving the "usability" of * an application. Instead of painfully navigating through the links * "sports->football->results->today" the mobile user e.g. can press * "2431" on the keypad to enter his favorite deck. For all other WAP * devices normal tags are created. One Horde_Mobile object can * contain only one linkset object. * * Examples: * * $myPage = new Horde_Mobile(...); * * $myLinkset = new Horde_Mobile_linkset(); * $myLink1 = new Horde_Mobile_link("Phonebook", "/wap/phonebook.wml"); * $myLinkset->add($myLink1); * $myLink2 = new Horde_Mobile_link("DateBook", "/wap/datebook.wml"); * $myLinkset->add($myLink2); * * $myPage->add($myLinkset); * * $myPage->render(); * * @see Horde_Mobile_link * @see Horde_Mobile * * @package Horde_Mobile */ class Horde_Mobile_linkset extends Horde_Mobile_element { var $_elements; /** * Adds a Horde_Mobile_link object to Horde_Mobile_linkset. * * @param Horde_Mobile_link $link The link object to add. * * @see Horde_Mobile_link */ function &add(&$link) { if (!is_a($link, 'Horde_Mobile_link')) { $error = PEAR::raiseError('Links must be Horde_Mobile_link objects.'); return $error; } $this->_elements[] = &$link; $link->set('accesskey', count($this->_elements)); return $link; } } /** * This class will cause a horizontal rule to be drawn across the screen. You * can use it to separate text paragraphs in Horde_Mobile or Horde_Mobile_form * objects. * * Examples: * * $myDefaultRule = new Horde_Mobile_rule(); * $mySpecialRule = new Horde_Mobile_rule('60%', 4); * * $myPage->add($myDefaultRule); * * $myPage->add($mySpecialRule); * * @see Horde_Mobile * @see Horde_Mobile_form * * @package Horde_Mobile */ class Horde_Mobile_rule extends Horde_Mobile_element { var $_width; var $_size; /** * Constructor * * @param integer $width Percentage of screen width or absolute value in * number of pixels (e.g. "50%", 100). * @param integer $size Height of the line to be drawn in pixels. */ function Horde_Mobile_rule($width = '', $size = '') { $this->_width = $width; $this->_size = $size; } } 'A', 'af' => 'AFG', 'ag' => 'AG', 'al' => 'AL', 'ad' => 'AND', 'ao' => 'ANG', 'am' => 'ARM', 'au' => 'AUS', 'az' => 'AZ', 'be' => 'B', 'bd' => 'BD', 'bb' => 'BDS', 'bf' => 'BF', 'bg' => 'BG', 'bz' => 'BH', 'bt' => 'BHT', 'ba' => 'BIH', 'bo' => 'BOL', 'br' => 'BR', 'bh' => 'BRN', 'bn' => 'BRU', 'bs' => 'BS', 'bi' => 'BU', 'vg' => 'BVI', 'by' => 'BY', 'bz' => 'BZ', 'cu' => 'C', 'cm' => 'CAM', 'cd' => 'CD', 'ca' => 'CDN', 'ch' => 'CH', 'ci' => 'CI', 'lk' => 'CL', 'co' => 'CO', 'km' => 'COM', 'cr' => 'CR', 'cv' => 'CV', 'cy' => 'CY', 'cz' => 'CZ', 'de' => 'D', 'dj' => 'DJI', 'dk' => 'DK', 'do' => 'DOM', 'kp' => 'DVRK', 'bj' => 'DY', 'dz' => 'DZ', 'es' => 'E', 'ke' => 'EAK', 'tz' => 'EAT', 'ug' => 'EAU', 'ec' => 'EC', 'er' => 'ER', 'sv' => 'ES', 'ee' => 'EST', 'eg' => 'ET', 'et' => 'ETH', 'fr' => 'F', 'fi' => 'FIN', 'fj' => 'FJI', 'li' => 'FL', 'fo' => 'FO', 'fm' => 'FSM', 'ga' => 'G', 'gb' => 'GB', 'gi' => 'GBZ', 'gt' => 'GCA', 'ge' => 'GE', 'gh' => 'GH', 'gq' => 'GQ', 'gr' => 'GR', 'gy' => 'GUY', 'hu' => 'H', 'hk' => 'HK', 'hn' => 'HN', 'hr' => 'HR', 'it' => 'I', 'il' => 'IL', 'in' => 'IND', 'ir' => 'IR', 'ie' => 'IRL', 'iq' => 'IRQ', 'is' => 'IS', 'jp' => 'J', 'jm' => 'JA', 'jo' => 'JOR', 'kh' => 'K', 'ki' => 'KIR', 'gl' => 'KN', 'kg' => 'KS', 'sa' => 'KSA', 'kw' => 'KWT', 'kz' => 'KZ', 'lu' => 'L', 'la' => 'LAO', 'ly' => 'LAR', 'lr' => 'LB', 'ls' => 'LS', 'lt' => 'LT', 'lv' => 'LV', 'mt' => 'M', 'ma' => 'MA', 'my' => 'MAL', 'mc' => 'MC', 'md' => 'MD', 'mx' => 'MEX', 'mn' => 'MGL', 'mh' => 'MH', 'mk' => 'MK', 'mz' => 'MOC', 'mu' => 'MS', 'mv' => 'MV', 'mw' => 'MW', 'mm' => 'MYA', 'no' => 'N', 'an' => 'NA', 'nl' => 'NL', 'nz' => 'NZ', 'om' => 'OM', 'pt' => 'P', 'pa' => 'PA', 'pw' => 'PAL', 'pe' => 'PE', 'pk' => 'PK', 'pl' => 'PL', 'pg' => 'PNG', 'cn' => 'PRC', 'ps' => 'PS', 'py' => 'PY', 'qa' => 'Q', 'ar' => 'RA', 'bw' => 'RB', 'tw' => 'RC', 'cf' => 'RCA', 'cd' => 'RCB', 'cl' => 'RCH', 're' => 'RE', 'gn' => 'RG', 'ht' => 'RH', 'id' => 'RI', 'mr' => 'RIM', 'lb' => 'RL', 'mg' => 'RM', 'ml' => 'RMM', 'ne' => 'RN', 'ro' => 'RO', 'kr' => 'ROK', 'uy' => 'ROU', 'ph' => 'RP', 'sm' => 'RSM', 'tg' => 'RT', 'ru' => 'RUS', 'rw' => 'RWA', 'se' => 'S', 'cs' => 'SCG', 'kn' => 'SCN', 'sz' => 'SD', 'sg' => 'SGP', 'sk' => 'SK', 'si' => 'SLO', 'sr' => 'SME', 'sn' => 'SN', 'sb' => 'SOL', 'so' => 'SP', 'st' => 'STP', 'sd' => 'SUD', 'sc' => 'SY', 'sy' => 'SYR', 'td' => 'TCH', 'th' => 'THA', 'tj' => 'TJ', 'tl' => 'TL', 'tm' => 'TM', 'tn' => 'TN', 'to' => 'TO', 'tr' => 'TR', 'tt' => 'TT', 'tv' => 'TUV', 'ua' => 'UA', 'ae' => 'UAE', 'us' => 'USA', 'uz' => 'UZ', 'va' => 'V', 'vn' => 'VN', 'vu' => 'VU', 'GM' => 'WAG', 'sl' => 'WAL', 'ng' => 'WAN', 'dm' => 'WD', 'gd' => 'WG', 'lc' => 'WL', 'ws' => 'WS', 'vc' => 'WV', 'ye' => 'YAR', 've' => 'YV', 'zm' => 'Z', 'za' => 'ZA', 'zw' => 'ZW' );