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à * @since Horde 3.0 * @package Horde_Cache */ class Horde_Cache_zps4 extends Horde_Cache { /** * Attempts to retrieve a piece of cached data and return it to the caller. * * @param string $key Cache key to fetch. * @param integer $lifetime Lifetime of the key in seconds. * * @return mixed Cached data, or false if none was found. */ function get($key, $lifetime = 1) { $key = $this->_params['prefix'] . $key; return output_cache_get($key, $lifetime); } /** * Attempts to store an object to the cache. * * @param string $key Cache key (identifier). * @param mixed $data Data to store in the cache. * @param integer $lifetime Data lifetime. @since Horde 3.2 [Not used] * * @return boolean True on success, false on failure. */ function set($key, $data, $lifetime = null) { $key = $this->_params['prefix'] . $key; output_cache_put($key, $data); return true; } /** * Checks if a given key exists in the cache, valid for the given lifetime. * * @param string $key Cache key to check. * @param integer $lifetime Lifetime of the key in seconds. * * @return boolean Existance. */ function exists($key, $lifetime = 1) { $key = $this->_params['prefix'] . $key; $exists = output_cache_exists($key, $lifetime); output_cache_stop(); return $exists; } /** * Expire any existing data for the given key. * * @param string $key Cache key to expire. * * @return boolean Success or failure. */ function expire($key) { $key = $this->_params['prefix'] . $key; return output_cache_remove_key($key); } } * @author Chuck Hagenbuch * @since Horde 1.3 * @package Horde_Cache */ class Horde_Cache { /** * Cache parameters. * * @var array */ var $_params = array(); /** * Construct a new Horde_Cache object. * * @param array $params Parameter array. */ function Horde_Cache($params = array()) { if (!isset($params['lifetime'])) { $params['lifetime'] = isset($GLOBALS['conf']['cache']['default_lifetime']) ? $GLOBALS['conf']['cache']['default_lifetime'] : 86400; } if (empty($params['prefix'])) { $params['prefix'] = ($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : $_SERVER['HOSTNAME']; } $this->_params = $params; } /** * Attempts to retrieve a cached object and return it to the * caller. * * @abstract * * @param string $key Object ID to query. * @param integer $lifetime Lifetime of the object in seconds. * * @return mixed Cached data, or false if none was found. */ function get($key, $lifetime = 1) { return false; } /** * Attempts to store an object in the cache. * * @abstract * * @param string $key Object ID used as the caching key. * @param mixed $data Data to store in the cache. * @param integer $lifetime Object lifetime - i.e. the time before the * data becomes available for garbage * collection. If null use the default Horde GC * time. If 0 will not be GC'd. * @since Horde 3.2 * * @return boolean True on success, false on failure. */ function set($key, $data, $lifetime = null) { return true; } /** * Attempts to directly output a cached object. * * @abstract * * @param string $key Object ID to query. * @param integer $lifetime Lifetime of the object in seconds. * * @return boolean True if output or false if no object was found. */ function output($key, $lifetime = 1) { $data = $this->get($key, $lifetime); if ($data === false) { return false; } else { echo $data; return true; } } /** * Checks if a given key exists in the cache, valid for the given * lifetime. * * @abstract * * @param string $key Cache key to check. * @param integer $lifetime Lifetime of the key in seconds. * * @return boolean Existance. */ function exists($key, $lifetime = 1) { return false; } /** * Expire any existing data for the given key. * * @abstract * * @param string $key Cache key to expire. * * @return boolean Success or failure. */ function expire($key) { return true; } /** * Determine the default lifetime for data. * * @private * @since Horde 3.2 * * @param mixed $lifetime The lifetime to use or null for default. */ function _getLifetime($lifetime) { return is_null($lifetime) ? $this->_params['lifetime'] : $lifetime; } /** * Attempts to return a concrete Horde_Cache instance based on $driver. * * @param mixed $driver The type of concrete Horde_Cache subclass to * return. If $driver is an array, then we will look * in $driver[0]/lib/Cache/ 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_Cache The newly created concrete Horde_Cache instance, or * false on error. */ function factory($driver, $params = array()) { if (is_array($driver)) { $app = $driver[0]; $driver = $driver[1]; } $driver = basename($driver); if (empty($driver) || $driver == 'none') { return new Horde_Cache($params); } if (!empty($app)) { include_once $app . '/lib/Cache/' . $driver . '.php'; } elseif (file_exists(dirname(__FILE__) . '/Cache/' . $driver . '.php')) { include_once dirname(__FILE__) . '/Cache/' . $driver . '.php'; } else { include_once 'Horde/Cache/' . $driver . '.php'; } $class = 'Horde_Cache_' . $driver; if (class_exists($class)) { $cache = new $class($params); } else { $cache = PEAR::raiseError('Class definition of ' . $class . ' not found.'); } return $cache; } /** * Attempts to return a reference to a concrete Horde_Cache * instance based on $driver. It will only create a new instance * if no Horde_Cache instance with the same parameters currently * exists. * * This should be used if multiple cache backends (and, thus, * multiple Horde_Cache instances) are required. * * This method must be invoked as: * $var = &Horde_Cache::singleton() * * @param mixed $driver The type of concrete Horde_Cache subclass to * return. If $driver is an array, then we will look * in $driver[0]/lib/Cache/ 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_Cache The concrete Horde_Cache reference, or PEAR_Error. */ function &singleton($driver, $params = array()) { static $instances = array(); $signature = serialize(array($driver, $params)); if (empty($instances[$signature])) { $instances[$signature] = Horde_Cache::factory($driver, $params); } return $instances[$signature]; } } * @since Horde 2.2 * @package Horde_Cipher */ class Horde_Cipher_BlockMode_cbc extends Horde_Cipher_BlockMode { /** * Encrypt a string. * * @param Horde_Cipher $cipher Cipher algorithm to use for * encryption. * @param string $plaintext The data to encrypt. * * @return The encrypted data. */ function encrypt(&$cipher, $plaintext) { $encrypted = ''; $blocksize = $cipher->getBlockSize(); $previousCipher = $this->_iv; $jMax = strlen($plaintext); for ($j = 0; $j < $jMax; $j += $blocksize) { $plain = substr($plaintext, $j, $blocksize); if (strlen($plain) < $blocksize) { // pad the block with \0's if it's not long enough $plain = str_pad($plain, 8, "\0"); } $plain = $plain ^ $previousCipher; $previousCipher = $cipher->encryptBlock($plain); $encrypted .= $previousCipher; } return $encrypted; } /** * Decrypt a string. * * @param Horde_Cipher $cipher Cipher algorithm to use for * decryption. * @param string $ciphertext The data to decrypt. * * @return The decrypted data. */ function decrypt(&$cipher, $ciphertext) { $decrypted = ''; $blocksize = $cipher->getBlockSize(); $previousCipher = $this->_iv; $jMax = strlen($ciphertext); for ($j = 0; $j < $jMax; $j += $blocksize) { $plain = substr($ciphertext, $j, $blocksize); $decrypted .= $cipher->decryptBlock($plain) ^ $previousCipher; $previousCipher = $plain; } // Remove trailing \0's used to pad the last block. while (substr($decrypted, -1, 1) == "\0") { $decrypted = substr($decrypted, 0, -1); } return $decrypted; } } * @since Horde 2.2 * @package Horde_Cipher */ class Horde_Cipher_BlockMode_cfb64 extends Horde_Cipher_BlockMode { /** * Encrypt a string. * * @param Horde_Cipher $cipher Cipher algorithm to use for * encryption. * @param string $plaintext The data to encrypt. * * @return The encrypted data. */ function encrypt(&$cipher, $plaintext) { $encrypted = ''; $n = 0; $jMax = strlen($plaintext); for ($j = 0; $j < $jMax; $j++) { if ($n == 0) { $this->_iv = $cipher->encryptBlock($this->_iv); } $c = $plaintext[$j] ^ $this->_iv[$n]; $this->_iv = substr($this->_iv, 0, $n) . $c . substr($this->_iv, $n + 1); $encrypted .= $c; $n = (++$n) & 0x07; } return $encrypted; } /** * Decrypt a string. * * @param Horde_Cipher $cipher Cipher algorithm to use for * decryption. * @param string $ciphertext The data to decrypt. * * @return The decrypted data. */ function decrypt(&$cipher, $ciphertext) { $decrypted = ''; $n = 0; $jMax = strlen($ciphertext); for ($j = 0; $j < $jMax; $j++) { if ($n == 0) { $this->_iv = $cipher->encryptBlock($this->_iv); } $c = $ciphertext[$j] ^ $this->_iv[$n]; $this->_iv = substr($this->_iv, 0, $n) . substr($ciphertext, $j, 1) . substr($this->_iv, $n + 1); $decrypted .= $c; $n = (++$n) & 0x07; } // Remove trailing \0's used to pad the last block. while (substr($decrypted, -1, 1) == "\0") { $decrypted = substr($decrypted, 0, -1); } return $decrypted; } } * @since Horde 2.2 * @package Horde_Cipher */ class Horde_Cipher_BlockMode_ecb extends Horde_Cipher_BlockMode { /** * Encrypt a string. * * @param Horde_Cipher $cipher Cipher algorithm to use for * encryption. * @param string $plaintext The data to encrypt. * * @return The encrypted data. */ function encrypt(&$cipher, $plaintext) { $encrypted = ''; $blocksize = $cipher->getBlockSize(); $jMax = strlen($plaintext); for ($j = 0; $j < $jMax; $j += $blocksize) { $plain = substr($plaintext, $j, $blocksize); if (strlen($plain) < $blocksize) { // pad the block with \0's if it's not long enough $plain = str_pad($plain, 8, "\0"); } $encrypted .= $cipher->encryptBlock($plain); } return $encrypted; } /** * Decrypt a string. * * @param Horde_Cipher $cipher Cipher algorithm to use for * decryption. * @param string $ciphertext The data to decrypt. * * @return The decrypted data. */ function decrypt(&$cipher, $ciphertext) { $decrypted = ''; $blocksize = $cipher->getBlockSize(); $jMax = strlen($ciphertext); for ($j = 0; $j < $jMax; $j += $blocksize) { $plain = substr($ciphertext, $j, $blocksize); $decrypted .= $cipher->decryptBlock($plain); } // Remove trailing \0's used to pad the last block. while (substr($decrypted, -1, 1) == "\0") { $decrypted = substr($decrypted, 0, -1); } return $decrypted; } } * @since Horde 2.2 * @package Horde_Cipher */ class Horde_Cipher_BlockMode_ofb64 extends Horde_Cipher_BlockMode { /** * Encrypt a string. * * @param Horde_Cipher $cipher Cipher algorithm to use for * encryption. * @param string $plaintext The data to encrypt. * * @return The encrypted data. */ function encrypt(&$cipher, $plaintext) { $encrypted = ''; $n = 0; $jMax = strlen($plaintext); for ($j = 0; $j < $jMax; $j++) { if ($n == 0) { $this->_iv = $cipher->encryptBlock($this->_iv); } $c = $plaintext[$j] ^ $this->_iv[$n]; $encrypted .= $c; $n = (++$n) & 0x07; } return $encrypted; } /** * Decrypt a string. * * @param Horde_Cipher $cipher Cipher algorithm to use for * decryption. * @param string $ciphertext The data to decrypt. * * @return The decrypted data. */ function decrypt(&$cipher, $ciphertext) { $decrypted = ''; $n = 0; $jMax = strlen($ciphertext); for ($j = 0; $j < $jMax; $j++) { if ($n == 0) { $this->_iv = $cipher->encryptBlock($this->_iv); } $c = $ciphertext[$j] ^ $this->_iv[$n]; $decrypted .= $c; $n = (++$n) & 0x07; } return $decrypted; } } * @since Horde 2.2 * @package Horde_Cipher */ class Horde_Cipher_BlockMode { /** * The initialization vector. * * @var string */ var $_iv = "\0\0\0\0\0\0\0\0"; /** * Attempts to return a concrete Horde_Cipher_BlockMode instance based on * $mode. * * @param string $mode The type of concrete Horde_Cipher_BlockMode * subclass to return. * @param array $params A hash containing any additional parameters a * subclass might need. * * @return Horde_Cipher_BlockMode The newly created concrete * CipherBlockMode instance, or PEAR_Error * on error. */ function &factory($mode, $params = null) { $mode = basename($mode); $class = 'Horde_Cipher_BlockMode_' . $mode; if (!class_exists($class)) { include_once 'Horde/Cipher/BlockMode/' . $mode . '.php'; } if (class_exists($class)) { $blockmode = new $class($params); } else { $blockmode = PEAR::raiseError('Class definition of ' . $class . ' not found.'); } return $blockmode; } /** * Set the IV * * @param string $iv The new IV. */ function setIV($iv) { $this->_iv = $iv; } } * @since Horde 2.2 * @package Horde_Cipher */ class Horde_Cipher_blowfish extends Horde_Cipher { /** * Pi Array * * @var array */ var $p = array( 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0, 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, 0x9216D5D9, 0x8979FB1B); /** * S Box (s1) * * @var array */ var $s1 = array( 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99, 0x24A19947, 0xB3916CF7, 0x0801F2E2, 0x858EFC16, 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 0x0D95748F, 0x728EB658, 0x718BCD58, 0x82154AEE, 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013, 0xC5D1B023, 0x286085F0, 0xCA417918, 0xB8DB38EF, 0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E, 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60, 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, 0x55CA396A, 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE, 0xA15486AF, 0x7C72E993, 0xB3EE1411, 0x636FBC2A, 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, 0xAFD6BA33, 0x6C24CF5C, 0x7A325381, 0x28958677, 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193, 0x61D809CC, 0xFB21A991, 0x487CAC60, 0x5DEC8032, 0xEF845D5D, 0xE98575B1, 0xDC262302, 0xEB651B88, 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239, 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, 0x21C66842, 0xF6E96C9A, 0x670C9C61, 0xABD388F0, 0x6A51A0D2, 0xD8542F68, 0x960FA728, 0xAB5133A3, 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, 0xA1F1651D, 0x39AF0176, 0x66CA593E, 0x82430E88, 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE, 0xE06F75D8, 0x85C12073, 0x401A449F, 0x56C16AA6, 0x4ED3AA62, 0x363F7706, 0x1BFEDF72, 0x429B023D, 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B, 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, 0xE3FE501A, 0xB6794C3B, 0x976CE0BD, 0x04C006BA, 0xC1A94FB6, 0x409F60C4, 0x5E5C9EC2, 0x196A2463, 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, 0x6DFC511F, 0x9B30952C, 0xCC814544, 0xAF5EBD09, 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3, 0xC0CBA857, 0x45C8740F, 0xD20B5F39, 0xB9D3FBDB, 0x5579C0BD, 0x1A60320A, 0xD6A100C6, 0x402C7279, 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8, 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, 0x323DB5FA, 0xFD238760, 0x53317B48, 0x3E00DF82, 0x9E5C57BB, 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB, 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, 0x695B27B0, 0xBBCA58C8, 0xE1FFA35D, 0xB8F011A0, 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B, 0x9A53E479, 0xB6F84565, 0xD28E49BC, 0x4BFB9790, 0xE1DDF2DA, 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8, 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4, 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, 0xD08ED1D0, 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7, 0x8FF6E2FB, 0xF2122B64, 0x8888B812, 0x900DF01C, 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, 0x2F2F2218, 0xBE0E1777, 0xEA752DFE, 0x8B021FA1, 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299, 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, 0xD2ADA8D9, 0x165FA266, 0x80957705, 0x93CC7314, 0x211A1477, 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF, 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, 0x00250E2D, 0x2071B35E, 0x226800BB, 0x57B8E0AF, 0x2464369B, 0xF009B91E, 0x5563911D, 0x59DFA6AA, 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, 0x83260376, 0x6295CFA9, 0x11C81968, 0x4E734A41, 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915, 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, 0x81E67400, 0x08BA6FB5, 0x571BE91F, 0xF296EC6B, 0x2A0DD915, 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664, 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A); /** * S Box (s2) * * @var array */ var $s2 = array( 0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, 0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266, 0xECAA8C71, 0x699A17FF, 0x5664526C, 0xC2B19EE1, 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, 0x3F54989A, 0x5B429D65, 0x6B8FE4D6, 0x99F73FD6, 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1, 0x4CDD2086, 0x8470EB26, 0x6382E9C6, 0x021ECC5E, 0x09686B3F, 0x3EBAEFC9, 0x3C971814, 0x6B6A70A1, 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737, 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, 0xB03ADA37, 0xF0500C0D, 0xF01C1F04, 0x0200B3FF, 0xAE0CF51A, 0x3CB574B2, 0x25837A58, 0xDC0921BD, 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, 0x3AE5E581, 0x37C2DADC, 0xC8B57634, 0x9AF3DDA7, 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41, 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, 0x183EB331, 0x4E548B38, 0x4F6DB908, 0x6F420D03, 0xF60A04BF, 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF, 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, 0x5512721F, 0x2E6B7124, 0x501ADDE6, 0x9F84CD87, 0x7A584718, 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C, 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, 0xEF1C1847, 0x3215D908, 0xDD433B37, 0x24C2BA16, 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD, 0x71DFF89E, 0x10314E55, 0x81AC77D6, 0x5F11199B, 0x043556F1, 0xD7A3C76B, 0x3C11183B, 0x5924A509, 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E, 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, 0x771FE71C, 0x4E3D06FA, 0x2965DCB9, 0x99E71D0F, 0x803E89D6, 0x5266C825, 0x2E4CC978, 0x9C10B36A, 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, 0xF2F74EA7, 0x361D2B3D, 0x1939260F, 0x19C27960, 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66, 0xE3BC4595, 0xA67BC883, 0xB17F37D1, 0x018CFF28, 0xC332DDEF, 0xBE6C5AA5, 0x65582185, 0x68AB9802, 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84, 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, 0x13CCA830, 0xEB61BD96, 0x0334FE1E, 0xAA0363CF, 0xB5735C90, 0x4C70A239, 0xD59E9E0B, 0xCBAADE14, 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, 0x648B1EAF, 0x19BDF0CA, 0xA02369B9, 0x655ABB50, 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7, 0x9B540B19, 0x875FA099, 0x95F7997E, 0x623D7DA8, 0xF837889A, 0x97E32D77, 0x11ED935F, 0x16681281, 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99, 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, 0xCDB30AEB, 0x532E3054, 0x8FD948E4, 0x6DBC3128, 0x58EBF2EF, 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73, 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, 0x45EEE2B6, 0xA3AAABEA, 0xDB6C4F15, 0xFACB4FD0, 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105, 0xD81E799E, 0x86854DC7, 0xE44B476A, 0x3D816250, 0xCF62A1F2, 0x5B8D2646, 0xFC8883A0, 0xC1C7B6A3, 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285, 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, 0x58428D2A, 0x0C55F5EA, 0x1DADF43E, 0x233F7061, 0x3372F092, 0x8D937E41, 0xD65FECF1, 0x6C223BDB, 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, 0xA6078084, 0x19F8509E, 0xE8EFD855, 0x61D99735, 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC, 0x9E447A2E, 0xC3453484, 0xFDD56705, 0x0E1E9EC9, 0xDB73DBD3, 0x105588CD, 0x675FDA79, 0xE3674340, 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20, 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7); /** * S Box (s3) * * @var array */ var $s3 = array( 0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, 0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068, 0xD4082471, 0x3320F46A, 0x43B7D4B7, 0x500061AF, 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, 0x4D95FC1D, 0x96B591AF, 0x70F4DDD3, 0x66A02F45, 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504, 0x96EB27B3, 0x55FD3941, 0xDA2547E6, 0xABCA0A9A, 0x28507825, 0x530429F4, 0x0A2C86DA, 0xE9B66DFB, 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE, 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, 0xAACE1E7C, 0xD3375FEC, 0xCE78A399, 0x406B2A42, 0x20FE9E35, 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B, 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, 0x3A6EFA74, 0xDD5B4332, 0x6841E7F7, 0xCA7820FB, 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527, 0x55533A3A, 0x20838D87, 0xFE6BA9B7, 0xD096954B, 0x55A867BC, 0xA1159A58, 0xCCA92963, 0x99E1DB33, 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C, 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, 0x95C11548, 0xE4C66D22, 0x48C1133F, 0xC70F86DC, 0x07F9C9EE, 0x41041F0F, 0x404779A4, 0x5D886E17, 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, 0x257B7834, 0x602A9C60, 0xDFF8E8A3, 0x1F636C1B, 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115, 0x6B2395E0, 0x333E92E1, 0x3B240B62, 0xEEBEB922, 0x85B2A20E, 0xE6BA0D99, 0xDE720C8C, 0x2DA2F728, 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0, 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, 0x0A476341, 0x992EFF74, 0x3A6F6EAB, 0xF4F8FD37, 0xA812DC60, 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D, 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, 0xF1290DC7, 0xCC00FFA3, 0xB5390F92, 0x690FED0B, 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3, 0xBB132F88, 0x515BAD24, 0x7B9479BF, 0x763BD6EB, 0x37392EB3, 0xCC115979, 0x8026E297, 0xF42E312D, 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C, 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, 0x1A6B1018, 0x11CAEDFA, 0x3D25BDD8, 0xE2E1C3C9, 0x44421659, 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A, 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, 0x9DBC8057, 0xF0F7C086, 0x60787BF8, 0x6003604D, 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC, 0x83426B33, 0xF01EAB71, 0xB0804187, 0x3C005E5F, 0x77A057BE, 0xBDE8AE24, 0x55464299, 0xBF582E61, 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2, 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, 0x7AEB2661, 0x8B1DDF84, 0x846A0E79, 0x915F95E2, 0x466E598E, 0x20B45770, 0x8CD55591, 0xC902DE4C, 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, 0xB77F19B6, 0xE0A9DC09, 0x662D09A1, 0xC4324633, 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10, 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, 0x2868F169, 0xDCB7DA83, 0x573906FE, 0xA1E2CE9B, 0x4FCD7F52, 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027, 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, 0xF0177A28, 0xC0F586E0, 0x006058AA, 0x30DC7D62, 0x11E69ED7, 0x2338EA63, 0x53C2DD94, 0xC2C21634, 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, 0x6F05E409, 0x4B7C0188, 0x39720A3D, 0x7C927C24, 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC, 0xED545578, 0x08FCA5B5, 0xD83D7CD3, 0x4DAD0FC4, 0x1E50EF5E, 0xB161E6F8, 0xA28514D9, 0x6C51133C, 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837, 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0); /** * S Box (s4) * * @var array */ var $s4 = array( 0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, 0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE, 0xD5118E9D, 0xBF0F7315, 0xD62D1C7E, 0xC700C47B, 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, 0x5748AB2F, 0xBC946E79, 0xC6A376D2, 0x6549C2C8, 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6, 0x2939BBDB, 0xA9BA4650, 0xAC9526E8, 0xBE5EE304, 0xA1FAD5F0, 0x6A2D519A, 0x63EF8CE2, 0x9A86EE22, 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4, 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, 0x2826A2F9, 0xA73A3AE1, 0x4BA99586, 0xEF5562E9, 0xC72FEFD3, 0xF752F7DA, 0x3F046F69, 0x77FA0A59, 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, 0xE990FD5A, 0x9E34D797, 0x2CF0B7D9, 0x022B8B51, 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28, 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, 0x5A88F54C, 0xE029AC71, 0xE019A5E6, 0x47B0ACFD, 0xED93FA9B, 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28, 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, 0x15056DD4, 0x88F46DBA, 0x03A16125, 0x0564F0BD, 0xC3EB9E15, 0x3C9057A2, 0x97271AEC, 0xA93A072A, 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, 0x7533D928, 0xB155FDF5, 0x03563482, 0x8ABA3CBB, 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F, 0x4DE81751, 0x3830DC8E, 0x379D5862, 0x9320F991, 0xEA7A90C2, 0xFB3E7BCE, 0x5121CE64, 0x774FBE32, 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680, 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, 0xB39A460A, 0x6445C0DD, 0x586CDECF, 0x1C20C8AE, 0x5BBEF7DD, 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB, 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, 0x72EACEA8, 0xFA6484BB, 0x8D6612AE, 0xBF3C6F47, 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370, 0x740E0D8D, 0xE75B1357, 0xF8721671, 0xAF537D5D, 0x4040CB08, 0x4EB4E2CC, 0x34D2466A, 0x0115AF84, 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048, 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, 0x611560B1, 0xE7933FDC, 0xBB3A792B, 0x344525BD, 0xA08839E1, 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9, 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, 0x1A908749, 0xD44FBD9A, 0xD0DADECB, 0xD50ADA38, 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F, 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, 0x27D9459C, 0xBF97222C, 0x15E6FC2A, 0x0F91FC71, 0x9B941525, 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1, 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, 0xE0EC6E0E, 0x1698DB3B, 0x4C98A0BE, 0x3278E964, 0x9F1F9532, 0xE0D392DF, 0xD3A0342B, 0x8971F21E, 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, 0xDF359F8D, 0x9B992F2E, 0xE60B6F47, 0x0FE3F11D, 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F, 0x1618B166, 0xFD2C1D05, 0x848FD2C5, 0xF6FB2299, 0xF523F357, 0xA6327623, 0x93A83531, 0x56CCCD02, 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC, 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, 0xE6C6C7BD, 0x327A140A, 0x45E1D006, 0xC3F27B9A, 0xC9AA53FD, 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6, 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, 0x53113EC0, 0x1640E3D3, 0x38ABBD60, 0x2547ADF0, 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060, 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, 0x4CF9AA7E, 0x1948C25C, 0x02FB8A8C, 0x01C36AE4, 0xD6EBE1F9, 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F, 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6); /** * The number of rounds to do * * @var integer */ var $_rounds = 16; /** * Set the key to be used for en/decryption. * * @param string $key The key to use. */ function setKey($key) { $key = $this->_formatKey($key); $keyLen = count($key); if ($keyLen == 0) { return false; } $keyPos = 0; $keyXor = 0; $iMax = count($this->p); for ($i = 0; $i < $iMax; $i++) { for ($t = 0; $t < 4; $t++) { $keyXor = ($keyXor << 8) | (($key[$keyPos]) & 0x0ff); if (++$keyPos == $keyLen) { $keyPos = 0; } } $this->p[$i] = $this->p[$i] ^ $keyXor; } $encZero = array('L' => 0, 'R' => 0); for ($i = 0; $i + 1 < $iMax; $i += 2) { $encZero = $this->_encryptBlock($encZero['L'], $encZero['R']); $this->p[$i] = $encZero['L']; $this->p[$i + 1] = $encZero['R']; } $iMax = count($this->s1); for ($i = 0; $i < $iMax; $i += 2) { $encZero = $this->_encryptBlock($encZero['L'], $encZero['R']); $this->s1[$i] = $encZero['L']; $this->s1[$i + 1] = $encZero['R']; } $iMax = count($this->s2); for ($i = 0; $i < $iMax; $i += 2) { $encZero = $this->_encryptBlock($encZero['L'], $encZero['R']); $this->s2[$i] = $encZero['L']; $this->s2[$i + 1] = $encZero['R']; } $iMax = count($this->s3); for ($i = 0; $i < $iMax; $i += 2) { $encZero = $this->_encryptBlock($encZero['L'], $encZero['R']); $this->s3[$i] = $encZero['L']; $this->s3[$i + 1] = $encZero['R']; } $iMax = count($this->s4); for ($i = 0; $i < $iMax; $i += 2) { $encZero = $this->_encryptBlock($encZero['L'], $encZero['R']); $this->s4[$i] = $encZero['L']; $this->s4[$i + 1] = $encZero['R']; } } /** * Return the size of the blocks that this cipher needs. * * @return integer The number of characters per block. */ function getBlockSize() { return 8; } /** * Encrypt a block of data. * * @param string $block The data to encrypt. * @param string $key The key to use. * * @return string The encrypted output. */ function encryptBlock($block, $key = null) { if (!is_null($key)) { $this->setKey($key); } list($L, $R) = array_values(unpack('N*', $block)); $parts = $this->_encryptBlock($L, $R); return pack('NN', $parts['L'], $parts['R']); } /** * Encrypt left and right halves of a block of data. * * @access private * * @param integer $L Left half of the data. * @param integer $R Right half of the data. * * @return array A hash, with keys 'L' and 'R', and the encrypted data as * the values. */ function _encryptBlock($L, $R) { $L ^= $this->p[0]; $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[1]; $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[2]; $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[3]; $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[4]; $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[5]; $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[6]; $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[7]; $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[8]; $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[9]; $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[10]; $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[11]; $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[12]; $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[13]; $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[14]; $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[15]; $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[16]; $R ^= $this->p[17]; return array('L' => $R, 'R' => $L); } /** * Decrypt a block of data. * * @param string $block The data to decrypt. * @param string $key The key to use. * * @return string The decrypted output. */ function decryptBlock($block, $key = null) { if (!is_null($key)) { $this->setKey($key); } list($L, $R) = array_values(unpack('N*', $block)); $L ^= $this->p[17]; $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[16]; $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[15]; $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[14]; $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[13]; $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[12]; $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[11]; $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[10]; $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[9]; $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[8]; $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[7]; $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[6]; $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[5]; $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[4]; $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[3]; $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[2]; $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[1]; $decrypted = pack("NN", $R ^ $this->p[0], $L); return $decrypted; } /** * Converts a text key into an array. * * @param string $key Key in text format. * * @return array The key in array format. */ function _formatKey($key) { return array_values(unpack('C*', $key)); } } * @since Horde 2.2 * @package Horde_Cipher */ class Horde_Cipher_cast128 extends Horde_Cipher { /** * S box (s1) * * @var array */ var $s1 = array( 0x30fb40d4, 0x9fa0ff0b, 0x6beccd2f, 0x3f258c7a, 0x1e213f2f, 0x9c004dd3, 0x6003e540, 0xcf9fc949, 0xbfd4af27, 0x88bbbdb5, 0xe2034090, 0x98d09675, 0x6e63a0e0, 0x15c361d2, 0xc2e7661d, 0x22d4ff8e, 0x28683b6f, 0xc07fd059, 0xff2379c8, 0x775f50e2, 0x43c340d3, 0xdf2f8656, 0x887ca41a, 0xa2d2bd2d, 0xa1c9e0d6, 0x346c4819, 0x61b76d87, 0x22540f2f, 0x2abe32e1, 0xaa54166b, 0x22568e3a, 0xa2d341d0, 0x66db40c8, 0xa784392f, 0x004dff2f, 0x2db9d2de, 0x97943fac, 0x4a97c1d8, 0x527644b7, 0xb5f437a7, 0xb82cbaef, 0xd751d159, 0x6ff7f0ed, 0x5a097a1f, 0x827b68d0, 0x90ecf52e, 0x22b0c054, 0xbc8e5935, 0x4b6d2f7f, 0x50bb64a2, 0xd2664910, 0xbee5812d, 0xb7332290, 0xe93b159f, 0xb48ee411, 0x4bff345d, 0xfd45c240, 0xad31973f, 0xc4f6d02e, 0x55fc8165, 0xd5b1caad, 0xa1ac2dae, 0xa2d4b76d, 0xc19b0c50, 0x882240f2, 0x0c6e4f38, 0xa4e4bfd7, 0x4f5ba272, 0x564c1d2f, 0xc59c5319, 0xb949e354, 0xb04669fe, 0xb1b6ab8a, 0xc71358dd, 0x6385c545, 0x110f935d, 0x57538ad5, 0x6a390493, 0xe63d37e0, 0x2a54f6b3, 0x3a787d5f, 0x6276a0b5, 0x19a6fcdf, 0x7a42206a, 0x29f9d4d5, 0xf61b1891, 0xbb72275e, 0xaa508167, 0x38901091, 0xc6b505eb, 0x84c7cb8c, 0x2ad75a0f, 0x874a1427, 0xa2d1936b, 0x2ad286af, 0xaa56d291, 0xd7894360, 0x425c750d, 0x93b39e26, 0x187184c9, 0x6c00b32d, 0x73e2bb14, 0xa0bebc3c, 0x54623779, 0x64459eab, 0x3f328b82, 0x7718cf82, 0x59a2cea6, 0x04ee002e, 0x89fe78e6, 0x3fab0950, 0x325ff6c2, 0x81383f05, 0x6963c5c8, 0x76cb5ad6, 0xd49974c9, 0xca180dcf, 0x380782d5, 0xc7fa5cf6, 0x8ac31511, 0x35e79e13, 0x47da91d0, 0xf40f9086, 0xa7e2419e, 0x31366241, 0x051ef495, 0xaa573b04, 0x4a805d8d, 0x548300d0, 0x00322a3c, 0xbf64cddf, 0xba57a68e, 0x75c6372b, 0x50afd341, 0xa7c13275, 0x915a0bf5, 0x6b54bfab, 0x2b0b1426, 0xab4cc9d7, 0x449ccd82, 0xf7fbf265, 0xab85c5f3, 0x1b55db94, 0xaad4e324, 0xcfa4bd3f, 0x2deaa3e2, 0x9e204d02, 0xc8bd25ac, 0xeadf55b3, 0xd5bd9e98, 0xe31231b2, 0x2ad5ad6c, 0x954329de, 0xadbe4528, 0xd8710f69, 0xaa51c90f, 0xaa786bf6, 0x22513f1e, 0xaa51a79b, 0x2ad344cc, 0x7b5a41f0, 0xd37cfbad, 0x1b069505, 0x41ece491, 0xb4c332e6, 0x032268d4, 0xc9600acc, 0xce387e6d, 0xbf6bb16c, 0x6a70fb78, 0x0d03d9c9, 0xd4df39de, 0xe01063da, 0x4736f464, 0x5ad328d8, 0xb347cc96, 0x75bb0fc3, 0x98511bfb, 0x4ffbcc35, 0xb58bcf6a, 0xe11f0abc, 0xbfc5fe4a, 0xa70aec10, 0xac39570a, 0x3f04442f, 0x6188b153, 0xe0397a2e, 0x5727cb79, 0x9ceb418f, 0x1cacd68d, 0x2ad37c96, 0x0175cb9d, 0xc69dff09, 0xc75b65f0, 0xd9db40d8, 0xec0e7779, 0x4744ead4, 0xb11c3274, 0xdd24cb9e, 0x7e1c54bd, 0xf01144f9, 0xd2240eb1, 0x9675b3fd, 0xa3ac3755, 0xd47c27af, 0x51c85f4d, 0x56907596, 0xa5bb15e6, 0x580304f0, 0xca042cf1, 0x011a37ea, 0x8dbfaadb, 0x35ba3e4a, 0x3526ffa0, 0xc37b4d09, 0xbc306ed9, 0x98a52666, 0x5648f725, 0xff5e569d, 0x0ced63d0, 0x7c63b2cf, 0x700b45e1, 0xd5ea50f1, 0x85a92872, 0xaf1fbda7, 0xd4234870, 0xa7870bf3, 0x2d3b4d79, 0x42e04198, 0x0cd0ede7, 0x26470db8, 0xf881814c, 0x474d6ad7, 0x7c0c5e5c, 0xd1231959, 0x381b7298, 0xf5d2f4db, 0xab838653, 0x6e2f1e23, 0x83719c9e, 0xbd91e046, 0x9a56456e, 0xdc39200c, 0x20c8c571, 0x962bda1c, 0xe1e696ff, 0xb141ab08, 0x7cca89b9, 0x1a69e783, 0x02cc4843, 0xa2f7c579, 0x429ef47d, 0x427b169c, 0x5ac9f049, 0xdd8f0f00, 0x5c8165bf); /** * S box (s2) * * @var array */ var $s2 = array( 0x1f201094, 0xef0ba75b, 0x69e3cf7e, 0x393f4380, 0xfe61cf7a, 0xeec5207a, 0x55889c94, 0x72fc0651, 0xada7ef79, 0x4e1d7235, 0xd55a63ce, 0xde0436ba, 0x99c430ef, 0x5f0c0794, 0x18dcdb7d, 0xa1d6eff3, 0xa0b52f7b, 0x59e83605, 0xee15b094, 0xe9ffd909, 0xdc440086, 0xef944459, 0xba83ccb3, 0xe0c3cdfb, 0xd1da4181, 0x3b092ab1, 0xf997f1c1, 0xa5e6cf7b, 0x01420ddb, 0xe4e7ef5b, 0x25a1ff41, 0xe180f806, 0x1fc41080, 0x179bee7a, 0xd37ac6a9, 0xfe5830a4, 0x98de8b7f, 0x77e83f4e, 0x79929269, 0x24fa9f7b, 0xe113c85b, 0xacc40083, 0xd7503525, 0xf7ea615f, 0x62143154, 0x0d554b63, 0x5d681121, 0xc866c359, 0x3d63cf73, 0xcee234c0, 0xd4d87e87, 0x5c672b21, 0x071f6181, 0x39f7627f, 0x361e3084, 0xe4eb573b, 0x602f64a4, 0xd63acd9c, 0x1bbc4635, 0x9e81032d, 0x2701f50c, 0x99847ab4, 0xa0e3df79, 0xba6cf38c, 0x10843094, 0x2537a95e, 0xf46f6ffe, 0xa1ff3b1f, 0x208cfb6a, 0x8f458c74, 0xd9e0a227, 0x4ec73a34, 0xfc884f69, 0x3e4de8df, 0xef0e0088, 0x3559648d, 0x8a45388c, 0x1d804366, 0x721d9bfd, 0xa58684bb, 0xe8256333, 0x844e8212, 0x128d8098, 0xfed33fb4, 0xce280ae1, 0x27e19ba5, 0xd5a6c252, 0xe49754bd, 0xc5d655dd, 0xeb667064, 0x77840b4d, 0xa1b6a801, 0x84db26a9, 0xe0b56714, 0x21f043b7, 0xe5d05860, 0x54f03084, 0x066ff472, 0xa31aa153, 0xdadc4755, 0xb5625dbf, 0x68561be6, 0x83ca6b94, 0x2d6ed23b, 0xeccf01db, 0xa6d3d0ba, 0xb6803d5c, 0xaf77a709, 0x33b4a34c, 0x397bc8d6, 0x5ee22b95, 0x5f0e5304, 0x81ed6f61, 0x20e74364, 0xb45e1378, 0xde18639b, 0x881ca122, 0xb96726d1, 0x8049a7e8, 0x22b7da7b, 0x5e552d25, 0x5272d237, 0x79d2951c, 0xc60d894c, 0x488cb402, 0x1ba4fe5b, 0xa4b09f6b, 0x1ca815cf, 0xa20c3005, 0x8871df63, 0xb9de2fcb, 0x0cc6c9e9, 0x0beeff53, 0xe3214517, 0xb4542835, 0x9f63293c, 0xee41e729, 0x6e1d2d7c, 0x50045286, 0x1e6685f3, 0xf33401c6, 0x30a22c95, 0x31a70850, 0x60930f13, 0x73f98417, 0xa1269859, 0xec645c44, 0x52c877a9, 0xcdff33a6, 0xa02b1741, 0x7cbad9a2, 0x2180036f, 0x50d99c08, 0xcb3f4861, 0xc26bd765, 0x64a3f6ab, 0x80342676, 0x25a75e7b, 0xe4e6d1fc, 0x20c710e6, 0xcdf0b680, 0x17844d3b, 0x31eef84d, 0x7e0824e4, 0x2ccb49eb, 0x846a3bae, 0x8ff77888, 0xee5d60f6, 0x7af75673, 0x2fdd5cdb, 0xa11631c1, 0x30f66f43, 0xb3faec54, 0x157fd7fa, 0xef8579cc, 0xd152de58, 0xdb2ffd5e, 0x8f32ce19, 0x306af97a, 0x02f03ef8, 0x99319ad5, 0xc242fa0f, 0xa7e3ebb0, 0xc68e4906, 0xb8da230c, 0x80823028, 0xdcdef3c8, 0xd35fb171, 0x088a1bc8, 0xbec0c560, 0x61a3c9e8, 0xbca8f54d, 0xc72feffa, 0x22822e99, 0x82c570b4, 0xd8d94e89, 0x8b1c34bc, 0x301e16e6, 0x273be979, 0xb0ffeaa6, 0x61d9b8c6, 0x00b24869, 0xb7ffce3f, 0x08dc283b, 0x43daf65a, 0xf7e19798, 0x7619b72f, 0x8f1c9ba4, 0xdc8637a0, 0x16a7d3b1, 0x9fc393b7, 0xa7136eeb, 0xc6bcc63e, 0x1a513742, 0xef6828bc, 0x520365d6, 0x2d6a77ab, 0x3527ed4b, 0x821fd216, 0x095c6e2e, 0xdb92f2fb, 0x5eea29cb, 0x145892f5, 0x91584f7f, 0x5483697b, 0x2667a8cc, 0x85196048, 0x8c4bacea, 0x833860d4, 0x0d23e0f9, 0x6c387e8a, 0x0ae6d249, 0xb284600c, 0xd835731d, 0xdcb1c647, 0xac4c56ea, 0x3ebd81b3, 0x230eabb0, 0x6438bc87, 0xf0b5b1fa, 0x8f5ea2b3, 0xfc184642, 0x0a036b7a, 0x4fb089bd, 0x649da589, 0xa345415e, 0x5c038323, 0x3e5d3bb9, 0x43d79572, 0x7e6dd07c, 0x06dfdf1e, 0x6c6cc4ef, 0x7160a539, 0x73bfbe70, 0x83877605, 0x4523ecf1); /** * S box (s3) * * @var array */ var $s3 = array( 0x8defc240, 0x25fa5d9f, 0xeb903dbf, 0xe810c907, 0x47607fff, 0x369fe44b, 0x8c1fc644, 0xaececa90, 0xbeb1f9bf, 0xeefbcaea, 0xe8cf1950, 0x51df07ae, 0x920e8806, 0xf0ad0548, 0xe13c8d83, 0x927010d5, 0x11107d9f, 0x07647db9, 0xb2e3e4d4, 0x3d4f285e, 0xb9afa820, 0xfade82e0, 0xa067268b, 0x8272792e, 0x553fb2c0, 0x489ae22b, 0xd4ef9794, 0x125e3fbc, 0x21fffcee, 0x825b1bfd, 0x9255c5ed, 0x1257a240, 0x4e1a8302, 0xbae07fff, 0x528246e7, 0x8e57140e, 0x3373f7bf, 0x8c9f8188, 0xa6fc4ee8, 0xc982b5a5, 0xa8c01db7, 0x579fc264, 0x67094f31, 0xf2bd3f5f, 0x40fff7c1, 0x1fb78dfc, 0x8e6bd2c1, 0x437be59b, 0x99b03dbf, 0xb5dbc64b, 0x638dc0e6, 0x55819d99, 0xa197c81c, 0x4a012d6e, 0xc5884a28, 0xccc36f71, 0xb843c213, 0x6c0743f1, 0x8309893c, 0x0feddd5f, 0x2f7fe850, 0xd7c07f7e, 0x02507fbf, 0x5afb9a04, 0xa747d2d0, 0x1651192e, 0xaf70bf3e, 0x58c31380, 0x5f98302e, 0x727cc3c4, 0x0a0fb402, 0x0f7fef82, 0x8c96fdad, 0x5d2c2aae, 0x8ee99a49, 0x50da88b8, 0x8427f4a0, 0x1eac5790, 0x796fb449, 0x8252dc15, 0xefbd7d9b, 0xa672597d, 0xada840d8, 0x45f54504, 0xfa5d7403, 0xe83ec305, 0x4f91751a, 0x925669c2, 0x23efe941, 0xa903f12e, 0x60270df2, 0x0276e4b6, 0x94fd6574, 0x927985b2, 0x8276dbcb, 0x02778176, 0xf8af918d, 0x4e48f79e, 0x8f616ddf, 0xe29d840e, 0x842f7d83, 0x340ce5c8, 0x96bbb682, 0x93b4b148, 0xef303cab, 0x984faf28, 0x779faf9b, 0x92dc560d, 0x224d1e20, 0x8437aa88, 0x7d29dc96, 0x2756d3dc, 0x8b907cee, 0xb51fd240, 0xe7c07ce3, 0xe566b4a1, 0xc3e9615e, 0x3cf8209d, 0x6094d1e3, 0xcd9ca341, 0x5c76460e, 0x00ea983b, 0xd4d67881, 0xfd47572c, 0xf76cedd9, 0xbda8229c, 0x127dadaa, 0x438a074e, 0x1f97c090, 0x081bdb8a, 0x93a07ebe, 0xb938ca15, 0x97b03cff, 0x3dc2c0f8, 0x8d1ab2ec, 0x64380e51, 0x68cc7bfb, 0xd90f2788, 0x12490181, 0x5de5ffd4, 0xdd7ef86a, 0x76a2e214, 0xb9a40368, 0x925d958f, 0x4b39fffa, 0xba39aee9, 0xa4ffd30b, 0xfaf7933b, 0x6d498623, 0x193cbcfa, 0x27627545, 0x825cf47a, 0x61bd8ba0, 0xd11e42d1, 0xcead04f4, 0x127ea392, 0x10428db7, 0x8272a972, 0x9270c4a8, 0x127de50b, 0x285ba1c8, 0x3c62f44f, 0x35c0eaa5, 0xe805d231, 0x428929fb, 0xb4fcdf82, 0x4fb66a53, 0x0e7dc15b, 0x1f081fab, 0x108618ae, 0xfcfd086d, 0xf9ff2889, 0x694bcc11, 0x236a5cae, 0x12deca4d, 0x2c3f8cc5, 0xd2d02dfe, 0xf8ef5896, 0xe4cf52da, 0x95155b67, 0x494a488c, 0xb9b6a80c, 0x5c8f82bc, 0x89d36b45, 0x3a609437, 0xec00c9a9, 0x44715253, 0x0a874b49, 0xd773bc40, 0x7c34671c, 0x02717ef6, 0x4feb5536, 0xa2d02fff, 0xd2bf60c4, 0xd43f03c0, 0x50b4ef6d, 0x07478cd1, 0x006e1888, 0xa2e53f55, 0xb9e6d4bc, 0xa2048016, 0x97573833, 0xd7207d67, 0xde0f8f3d, 0x72f87b33, 0xabcc4f33, 0x7688c55d, 0x7b00a6b0, 0x947b0001, 0x570075d2, 0xf9bb88f8, 0x8942019e, 0x4264a5ff, 0x856302e0, 0x72dbd92b, 0xee971b69, 0x6ea22fde, 0x5f08ae2b, 0xaf7a616d, 0xe5c98767, 0xcf1febd2, 0x61efc8c2, 0xf1ac2571, 0xcc8239c2, 0x67214cb8, 0xb1e583d1, 0xb7dc3e62, 0x7f10bdce, 0xf90a5c38, 0x0ff0443d, 0x606e6dc6, 0x60543a49, 0x5727c148, 0x2be98a1d, 0x8ab41738, 0x20e1be24, 0xaf96da0f, 0x68458425, 0x99833be5, 0x600d457d, 0x282f9350, 0x8334b362, 0xd91d1120, 0x2b6d8da0, 0x642b1e31, 0x9c305a00, 0x52bce688, 0x1b03588a, 0xf7baefd5, 0x4142ed9c, 0xa4315c11, 0x83323ec5, 0xdfef4636, 0xa133c501, 0xe9d3531c, 0xee353783); /** * S box (s4) * * @var array */ var $s4 = array( 0x9db30420, 0x1fb6e9de, 0xa7be7bef, 0xd273a298, 0x4a4f7bdb, 0x64ad8c57, 0x85510443, 0xfa020ed1, 0x7e287aff, 0xe60fb663, 0x095f35a1, 0x79ebf120, 0xfd059d43, 0x6497b7b1, 0xf3641f63, 0x241e4adf, 0x28147f5f, 0x4fa2b8cd, 0xc9430040, 0x0cc32220, 0xfdd30b30, 0xc0a5374f, 0x1d2d00d9, 0x24147b15, 0xee4d111a, 0x0fca5167, 0x71ff904c, 0x2d195ffe, 0x1a05645f, 0x0c13fefe, 0x081b08ca, 0x05170121, 0x80530100, 0xe83e5efe, 0xac9af4f8, 0x7fe72701, 0xd2b8ee5f, 0x06df4261, 0xbb9e9b8a, 0x7293ea25, 0xce84ffdf, 0xf5718801, 0x3dd64b04, 0xa26f263b, 0x7ed48400, 0x547eebe6, 0x446d4ca0, 0x6cf3d6f5, 0x2649abdf, 0xaea0c7f5, 0x36338cc1, 0x503f7e93, 0xd3772061, 0x11b638e1, 0x72500e03, 0xf80eb2bb, 0xabe0502e, 0xec8d77de, 0x57971e81, 0xe14f6746, 0xc9335400, 0x6920318f, 0x081dbb99, 0xffc304a5, 0x4d351805, 0x7f3d5ce3, 0xa6c866c6, 0x5d5bcca9, 0xdaec6fea, 0x9f926f91, 0x9f46222f, 0x3991467d, 0xa5bf6d8e, 0x1143c44f, 0x43958302, 0xd0214eeb, 0x022083b8, 0x3fb6180c, 0x18f8931e, 0x281658e6, 0x26486e3e, 0x8bd78a70, 0x7477e4c1, 0xb506e07c, 0xf32d0a25, 0x79098b02, 0xe4eabb81, 0x28123b23, 0x69dead38, 0x1574ca16, 0xdf871b62, 0x211c40b7, 0xa51a9ef9, 0x0014377b, 0x041e8ac8, 0x09114003, 0xbd59e4d2, 0xe3d156d5, 0x4fe876d5, 0x2f91a340, 0x557be8de, 0x00eae4a7, 0x0ce5c2ec, 0x4db4bba6, 0xe756bdff, 0xdd3369ac, 0xec17b035, 0x06572327, 0x99afc8b0, 0x56c8c391, 0x6b65811c, 0x5e146119, 0x6e85cb75, 0xbe07c002, 0xc2325577, 0x893ff4ec, 0x5bbfc92d, 0xd0ec3b25, 0xb7801ab7, 0x8d6d3b24, 0x20c763ef, 0xc366a5fc, 0x9c382880, 0x0ace3205, 0xaac9548a, 0xeca1d7c7, 0x041afa32, 0x1d16625a, 0x6701902c, 0x9b757a54, 0x31d477f7, 0x9126b031, 0x36cc6fdb, 0xc70b8b46, 0xd9e66a48, 0x56e55a79, 0x026a4ceb, 0x52437eff, 0x2f8f76b4, 0x0df980a5, 0x8674cde3, 0xedda04eb, 0x17a9be04, 0x2c18f4df, 0xb7747f9d, 0xab2af7b4, 0xefc34d20, 0x2e096b7c, 0x1741a254, 0xe5b6a035, 0x213d42f6, 0x2c1c7c26, 0x61c2f50f, 0x6552daf9, 0xd2c231f8, 0x25130f69, 0xd8167fa2, 0x0418f2c8, 0x001a96a6, 0x0d1526ab, 0x63315c21, 0x5e0a72ec, 0x49bafefd, 0x187908d9, 0x8d0dbd86, 0x311170a7, 0x3e9b640c, 0xcc3e10d7, 0xd5cad3b6, 0x0caec388, 0xf73001e1, 0x6c728aff, 0x71eae2a1, 0x1f9af36e, 0xcfcbd12f, 0xc1de8417, 0xac07be6b, 0xcb44a1d8, 0x8b9b0f56, 0x013988c3, 0xb1c52fca, 0xb4be31cd, 0xd8782806, 0x12a3a4e2, 0x6f7de532, 0x58fd7eb6, 0xd01ee900, 0x24adffc2, 0xf4990fc5, 0x9711aac5, 0x001d7b95, 0x82e5e7d2, 0x109873f6, 0x00613096, 0xc32d9521, 0xada121ff, 0x29908415, 0x7fbb977f, 0xaf9eb3db, 0x29c9ed2a, 0x5ce2a465, 0xa730f32c, 0xd0aa3fe8, 0x8a5cc091, 0xd49e2ce7, 0x0ce454a9, 0xd60acd86, 0x015f1919, 0x77079103, 0xdea03af6, 0x78a8565e, 0xdee356df, 0x21f05cbe, 0x8b75e387, 0xb3c50651, 0xb8a5c3ef, 0xd8eeb6d2, 0xe523be77, 0xc2154529, 0x2f69efdf, 0xafe67afb, 0xf470c4b2, 0xf3e0eb5b, 0xd6cc9876, 0x39e4460c, 0x1fda8538, 0x1987832f, 0xca007367, 0xa99144f8, 0x296b299e, 0x492fc295, 0x9266beab, 0xb5676e69, 0x9bd3ddda, 0xdf7e052f, 0xdb25701c, 0x1b5e51ee, 0xf65324e6, 0x6afce36c, 0x0316cc04, 0x8644213e, 0xb7dc59d0, 0x7965291f, 0xccd6fd43, 0x41823979, 0x932bcdf6, 0xb657c34d, 0x4edfd282, 0x7ae5290c, 0x3cb9536b, 0x851e20fe, 0x9833557e, 0x13ecf0b0, 0xd3ffb372, 0x3f85c5c1, 0x0aef7ed2); /** * S box (s5) * * @var array */ var $s5 = array( 0x7ec90c04, 0x2c6e74b9, 0x9b0e66df, 0xa6337911, 0xb86a7fff, 0x1dd358f5, 0x44dd9d44, 0x1731167f, 0x08fbf1fa, 0xe7f511cc, 0xd2051b00, 0x735aba00, 0x2ab722d8, 0x386381cb, 0xacf6243a, 0x69befd7a, 0xe6a2e77f, 0xf0c720cd, 0xc4494816, 0xccf5c180, 0x38851640, 0x15b0a848, 0xe68b18cb, 0x4caadeff, 0x5f480a01, 0x0412b2aa, 0x259814fc, 0x41d0efe2, 0x4e40b48d, 0x248eb6fb, 0x8dba1cfe, 0x41a99b02, 0x1a550a04, 0xba8f65cb, 0x7251f4e7, 0x95a51725, 0xc106ecd7, 0x97a5980a, 0xc539b9aa, 0x4d79fe6a, 0xf2f3f763, 0x68af8040, 0xed0c9e56, 0x11b4958b, 0xe1eb5a88, 0x8709e6b0, 0xd7e07156, 0x4e29fea7, 0x6366e52d, 0x02d1c000, 0xc4ac8e05, 0x9377f571, 0x0c05372a, 0x578535f2, 0x2261be02, 0xd642a0c9, 0xdf13a280, 0x74b55bd2, 0x682199c0, 0xd421e5ec, 0x53fb3ce8, 0xc8adedb3, 0x28a87fc9, 0x3d959981, 0x5c1ff900, 0xfe38d399, 0x0c4eff0b, 0x062407ea, 0xaa2f4fb1, 0x4fb96976, 0x90c79505, 0xb0a8a774, 0xef55a1ff, 0xe59ca2c2, 0xa6b62d27, 0xe66a4263, 0xdf65001f, 0x0ec50966, 0xdfdd55bc, 0x29de0655, 0x911e739a, 0x17af8975, 0x32c7911c, 0x89f89468, 0x0d01e980, 0x524755f4, 0x03b63cc9, 0x0cc844b2, 0xbcf3f0aa, 0x87ac36e9, 0xe53a7426, 0x01b3d82b, 0x1a9e7449, 0x64ee2d7e, 0xcddbb1da, 0x01c94910, 0xb868bf80, 0x0d26f3fd, 0x9342ede7, 0x04a5c284, 0x636737b6, 0x50f5b616, 0xf24766e3, 0x8eca36c1, 0x136e05db, 0xfef18391, 0xfb887a37, 0xd6e7f7d4, 0xc7fb7dc9, 0x3063fcdf, 0xb6f589de, 0xec2941da, 0x26e46695, 0xb7566419, 0xf654efc5, 0xd08d58b7, 0x48925401, 0xc1bacb7f, 0xe5ff550f, 0xb6083049, 0x5bb5d0e8, 0x87d72e5a, 0xab6a6ee1, 0x223a66ce, 0xc62bf3cd, 0x9e0885f9, 0x68cb3e47, 0x086c010f, 0xa21de820, 0xd18b69de, 0xf3f65777, 0xfa02c3f6, 0x407edac3, 0xcbb3d550, 0x1793084d, 0xb0d70eba, 0x0ab378d5, 0xd951fb0c, 0xded7da56, 0x4124bbe4, 0x94ca0b56, 0x0f5755d1, 0xe0e1e56e, 0x6184b5be, 0x580a249f, 0x94f74bc0, 0xe327888e, 0x9f7b5561, 0xc3dc0280, 0x05687715, 0x646c6bd7, 0x44904db3, 0x66b4f0a3, 0xc0f1648a, 0x697ed5af, 0x49e92ff6, 0x309e374f, 0x2cb6356a, 0x85808573, 0x4991f840, 0x76f0ae02, 0x083be84d, 0x28421c9a, 0x44489406, 0x736e4cb8, 0xc1092910, 0x8bc95fc6, 0x7d869cf4, 0x134f616f, 0x2e77118d, 0xb31b2be1, 0xaa90b472, 0x3ca5d717, 0x7d161bba, 0x9cad9010, 0xaf462ba2, 0x9fe459d2, 0x45d34559, 0xd9f2da13, 0xdbc65487, 0xf3e4f94e, 0x176d486f, 0x097c13ea, 0x631da5c7, 0x445f7382, 0x175683f4, 0xcdc66a97, 0x70be0288, 0xb3cdcf72, 0x6e5dd2f3, 0x20936079, 0x459b80a5, 0xbe60e2db, 0xa9c23101, 0xeba5315c, 0x224e42f2, 0x1c5c1572, 0xf6721b2c, 0x1ad2fff3, 0x8c25404e, 0x324ed72f, 0x4067b7fd, 0x0523138e, 0x5ca3bc78, 0xdc0fd66e, 0x75922283, 0x784d6b17, 0x58ebb16e, 0x44094f85, 0x3f481d87, 0xfcfeae7b, 0x77b5ff76, 0x8c2302bf, 0xaaf47556, 0x5f46b02a, 0x2b092801, 0x3d38f5f7, 0x0ca81f36, 0x52af4a8a, 0x66d5e7c0, 0xdf3b0874, 0x95055110, 0x1b5ad7a8, 0xf61ed5ad, 0x6cf6e479, 0x20758184, 0xd0cefa65, 0x88f7be58, 0x4a046826, 0x0ff6f8f3, 0xa09c7f70, 0x5346aba0, 0x5ce96c28, 0xe176eda3, 0x6bac307f, 0x376829d2, 0x85360fa9, 0x17e3fe2a, 0x24b79767, 0xf5a96b20, 0xd6cd2595, 0x68ff1ebf, 0x7555442c, 0xf19f06be, 0xf9e0659a, 0xeeb9491d, 0x34010718, 0xbb30cab8, 0xe822fe15, 0x88570983, 0x750e6249, 0xda627e55, 0x5e76ffa8, 0xb1534546, 0x6d47de08, 0xefe9e7d4); /** * S box (s6) * * @var array */ var $s6 = array( 0xf6fa8f9d, 0x2cac6ce1, 0x4ca34867, 0xe2337f7c, 0x95db08e7, 0x016843b4, 0xeced5cbc, 0x325553ac, 0xbf9f0960, 0xdfa1e2ed, 0x83f0579d, 0x63ed86b9, 0x1ab6a6b8, 0xde5ebe39, 0xf38ff732, 0x8989b138, 0x33f14961, 0xc01937bd, 0xf506c6da, 0xe4625e7e, 0xa308ea99, 0x4e23e33c, 0x79cbd7cc, 0x48a14367, 0xa3149619, 0xfec94bd5, 0xa114174a, 0xeaa01866, 0xa084db2d, 0x09a8486f, 0xa888614a, 0x2900af98, 0x01665991, 0xe1992863, 0xc8f30c60, 0x2e78ef3c, 0xd0d51932, 0xcf0fec14, 0xf7ca07d2, 0xd0a82072, 0xfd41197e, 0x9305a6b0, 0xe86be3da, 0x74bed3cd, 0x372da53c, 0x4c7f4448, 0xdab5d440, 0x6dba0ec3, 0x083919a7, 0x9fbaeed9, 0x49dbcfb0, 0x4e670c53, 0x5c3d9c01, 0x64bdb941, 0x2c0e636a, 0xba7dd9cd, 0xea6f7388, 0xe70bc762, 0x35f29adb, 0x5c4cdd8d, 0xf0d48d8c, 0xb88153e2, 0x08a19866, 0x1ae2eac8, 0x284caf89, 0xaa928223, 0x9334be53, 0x3b3a21bf, 0x16434be3, 0x9aea3906, 0xefe8c36e, 0xf890cdd9, 0x80226dae, 0xc340a4a3, 0xdf7e9c09, 0xa694a807, 0x5b7c5ecc, 0x221db3a6, 0x9a69a02f, 0x68818a54, 0xceb2296f, 0x53c0843a, 0xfe893655, 0x25bfe68a, 0xb4628abc, 0xcf222ebf, 0x25ac6f48, 0xa9a99387, 0x53bddb65, 0xe76ffbe7, 0xe967fd78, 0x0ba93563, 0x8e342bc1, 0xe8a11be9, 0x4980740d, 0xc8087dfc, 0x8de4bf99, 0xa11101a0, 0x7fd37975, 0xda5a26c0, 0xe81f994f, 0x9528cd89, 0xfd339fed, 0xb87834bf, 0x5f04456d, 0x22258698, 0xc9c4c83b, 0x2dc156be, 0x4f628daa, 0x57f55ec5, 0xe2220abe, 0xd2916ebf, 0x4ec75b95, 0x24f2c3c0, 0x42d15d99, 0xcd0d7fa0, 0x7b6e27ff, 0xa8dc8af0, 0x7345c106, 0xf41e232f, 0x35162386, 0xe6ea8926, 0x3333b094, 0x157ec6f2, 0x372b74af, 0x692573e4, 0xe9a9d848, 0xf3160289, 0x3a62ef1d, 0xa787e238, 0xf3a5f676, 0x74364853, 0x20951063, 0x4576698d, 0xb6fad407, 0x592af950, 0x36f73523, 0x4cfb6e87, 0x7da4cec0, 0x6c152daa, 0xcb0396a8, 0xc50dfe5d, 0xfcd707ab, 0x0921c42f, 0x89dff0bb, 0x5fe2be78, 0x448f4f33, 0x754613c9, 0x2b05d08d, 0x48b9d585, 0xdc049441, 0xc8098f9b, 0x7dede786, 0xc39a3373, 0x42410005, 0x6a091751, 0x0ef3c8a6, 0x890072d6, 0x28207682, 0xa9a9f7be, 0xbf32679d, 0xd45b5b75, 0xb353fd00, 0xcbb0e358, 0x830f220a, 0x1f8fb214, 0xd372cf08, 0xcc3c4a13, 0x8cf63166, 0x061c87be, 0x88c98f88, 0x6062e397, 0x47cf8e7a, 0xb6c85283, 0x3cc2acfb, 0x3fc06976, 0x4e8f0252, 0x64d8314d, 0xda3870e3, 0x1e665459, 0xc10908f0, 0x513021a5, 0x6c5b68b7, 0x822f8aa0, 0x3007cd3e, 0x74719eef, 0xdc872681, 0x073340d4, 0x7e432fd9, 0x0c5ec241, 0x8809286c, 0xf592d891, 0x08a930f6, 0x957ef305, 0xb7fbffbd, 0xc266e96f, 0x6fe4ac98, 0xb173ecc0, 0xbc60b42a, 0x953498da, 0xfba1ae12, 0x2d4bd736, 0x0f25faab, 0xa4f3fceb, 0xe2969123, 0x257f0c3d, 0x9348af49, 0x361400bc, 0xe8816f4a, 0x3814f200, 0xa3f94043, 0x9c7a54c2, 0xbc704f57, 0xda41e7f9, 0xc25ad33a, 0x54f4a084, 0xb17f5505, 0x59357cbe, 0xedbd15c8, 0x7f97c5ab, 0xba5ac7b5, 0xb6f6deaf, 0x3a479c3a, 0x5302da25, 0x653d7e6a, 0x54268d49, 0x51a477ea, 0x5017d55b, 0xd7d25d88, 0x44136c76, 0x0404a8c8, 0xb8e5a121, 0xb81a928a, 0x60ed5869, 0x97c55b96, 0xeaec991b, 0x29935913, 0x01fdb7f1, 0x088e8dfa, 0x9ab6f6f5, 0x3b4cbf9f, 0x4a5de3ab, 0xe6051d35, 0xa0e1d855, 0xd36b4cf1, 0xf544edeb, 0xb0e93524, 0xbebb8fbd, 0xa2d762cf, 0x49c92f54, 0x38b5f331, 0x7128a454, 0x48392905, 0xa65b1db8, 0x851c97bd, 0xd675cf2f); /** * S box (s7) * * @var array */ var $s7 = array( 0x85e04019, 0x332bf567, 0x662dbfff, 0xcfc65693, 0x2a8d7f6f, 0xab9bc912, 0xde6008a1, 0x2028da1f, 0x0227bce7, 0x4d642916, 0x18fac300, 0x50f18b82, 0x2cb2cb11, 0xb232e75c, 0x4b3695f2, 0xb28707de, 0xa05fbcf6, 0xcd4181e9, 0xe150210c, 0xe24ef1bd, 0xb168c381, 0xfde4e789, 0x5c79b0d8, 0x1e8bfd43, 0x4d495001, 0x38be4341, 0x913cee1d, 0x92a79c3f, 0x089766be, 0xbaeeadf4, 0x1286becf, 0xb6eacb19, 0x2660c200, 0x7565bde4, 0x64241f7a, 0x8248dca9, 0xc3b3ad66, 0x28136086, 0x0bd8dfa8, 0x356d1cf2, 0x107789be, 0xb3b2e9ce, 0x0502aa8f, 0x0bc0351e, 0x166bf52a, 0xeb12ff82, 0xe3486911, 0xd34d7516, 0x4e7b3aff, 0x5f43671b, 0x9cf6e037, 0x4981ac83, 0x334266ce, 0x8c9341b7, 0xd0d854c0, 0xcb3a6c88, 0x47bc2829, 0x4725ba37, 0xa66ad22b, 0x7ad61f1e, 0x0c5cbafa, 0x4437f107, 0xb6e79962, 0x42d2d816, 0x0a961288, 0xe1a5c06e, 0x13749e67, 0x72fc081a, 0xb1d139f7, 0xf9583745, 0xcf19df58, 0xbec3f756, 0xc06eba30, 0x07211b24, 0x45c28829, 0xc95e317f, 0xbc8ec511, 0x38bc46e9, 0xc6e6fa14, 0xbae8584a, 0xad4ebc46, 0x468f508b, 0x7829435f, 0xf124183b, 0x821dba9f, 0xaff60ff4, 0xea2c4e6d, 0x16e39264, 0x92544a8b, 0x009b4fc3, 0xaba68ced, 0x9ac96f78, 0x06a5b79a, 0xb2856e6e, 0x1aec3ca9, 0xbe838688, 0x0e0804e9, 0x55f1be56, 0xe7e5363b, 0xb3a1f25d, 0xf7debb85, 0x61fe033c, 0x16746233, 0x3c034c28, 0xda6d0c74, 0x79aac56c, 0x3ce4e1ad, 0x51f0c802, 0x98f8f35a, 0x1626a49f, 0xeed82b29, 0x1d382fe3, 0x0c4fb99a, 0xbb325778, 0x3ec6d97b, 0x6e77a6a9, 0xcb658b5c, 0xd45230c7, 0x2bd1408b, 0x60c03eb7, 0xb9068d78, 0xa33754f4, 0xf430c87d, 0xc8a71302, 0xb96d8c32, 0xebd4e7be, 0xbe8b9d2d, 0x7979fb06, 0xe7225308, 0x8b75cf77, 0x11ef8da4, 0xe083c858, 0x8d6b786f, 0x5a6317a6, 0xfa5cf7a0, 0x5dda0033, 0xf28ebfb0, 0xf5b9c310, 0xa0eac280, 0x08b9767a, 0xa3d9d2b0, 0x79d34217, 0x021a718d, 0x9ac6336a, 0x2711fd60, 0x438050e3, 0x069908a8, 0x3d7fedc4, 0x826d2bef, 0x4eeb8476, 0x488dcf25, 0x36c9d566, 0x28e74e41, 0xc2610aca, 0x3d49a9cf, 0xbae3b9df, 0xb65f8de6, 0x92aeaf64, 0x3ac7d5e6, 0x9ea80509, 0xf22b017d, 0xa4173f70, 0xdd1e16c3, 0x15e0d7f9, 0x50b1b887, 0x2b9f4fd5, 0x625aba82, 0x6a017962, 0x2ec01b9c, 0x15488aa9, 0xd716e740, 0x40055a2c, 0x93d29a22, 0xe32dbf9a, 0x058745b9, 0x3453dc1e, 0xd699296e, 0x496cff6f, 0x1c9f4986, 0xdfe2ed07, 0xb87242d1, 0x19de7eae, 0x053e561a, 0x15ad6f8c, 0x66626c1c, 0x7154c24c, 0xea082b2a, 0x93eb2939, 0x17dcb0f0, 0x58d4f2ae, 0x9ea294fb, 0x52cf564c, 0x9883fe66, 0x2ec40581, 0x763953c3, 0x01d6692e, 0xd3a0c108, 0xa1e7160e, 0xe4f2dfa6, 0x693ed285, 0x74904698, 0x4c2b0edd, 0x4f757656, 0x5d393378, 0xa132234f, 0x3d321c5d, 0xc3f5e194, 0x4b269301, 0xc79f022f, 0x3c997e7e, 0x5e4f9504, 0x3ffafbbd, 0x76f7ad0e, 0x296693f4, 0x3d1fce6f, 0xc61e45be, 0xd3b5ab34, 0xf72bf9b7, 0x1b0434c0, 0x4e72b567, 0x5592a33d, 0xb5229301, 0xcfd2a87f, 0x60aeb767, 0x1814386b, 0x30bcc33d, 0x38a0c07d, 0xfd1606f2, 0xc363519b, 0x589dd390, 0x5479f8e6, 0x1cb8d647, 0x97fd61a9, 0xea7759f4, 0x2d57539d, 0x569a58cf, 0xe84e63ad, 0x462e1b78, 0x6580f87e, 0xf3817914, 0x91da55f4, 0x40a230f3, 0xd1988f35, 0xb6e318d2, 0x3ffa50bc, 0x3d40f021, 0xc3c0bdae, 0x4958c24c, 0x518f36b2, 0x84b1d370, 0x0fedce83, 0x878ddada, 0xf2a279c7, 0x94e01be8, 0x90716f4b, 0x954b8aa3); /** * S box (s8) * * @var array */ var $s8 = array( 0xe216300d, 0xbbddfffc, 0xa7ebdabd, 0x35648095, 0x7789f8b7, 0xe6c1121b, 0x0e241600, 0x052ce8b5, 0x11a9cfb0, 0xe5952f11, 0xece7990a, 0x9386d174, 0x2a42931c, 0x76e38111, 0xb12def3a, 0x37ddddfc, 0xde9adeb1, 0x0a0cc32c, 0xbe197029, 0x84a00940, 0xbb243a0f, 0xb4d137cf, 0xb44e79f0, 0x049eedfd, 0x0b15a15d, 0x480d3168, 0x8bbbde5a, 0x669ded42, 0xc7ece831, 0x3f8f95e7, 0x72df191b, 0x7580330d, 0x94074251, 0x5c7dcdfa, 0xabbe6d63, 0xaa402164, 0xb301d40a, 0x02e7d1ca, 0x53571dae, 0x7a3182a2, 0x12a8ddec, 0xfdaa335d, 0x176f43e8, 0x71fb46d4, 0x38129022, 0xce949ad4, 0xb84769ad, 0x965bd862, 0x82f3d055, 0x66fb9767, 0x15b80b4e, 0x1d5b47a0, 0x4cfde06f, 0xc28ec4b8, 0x57e8726e, 0x647a78fc, 0x99865d44, 0x608bd593, 0x6c200e03, 0x39dc5ff6, 0x5d0b00a3, 0xae63aff2, 0x7e8bd632, 0x70108c0c, 0xbbd35049, 0x2998df04, 0x980cf42a, 0x9b6df491, 0x9e7edd53, 0x06918548, 0x58cb7e07, 0x3b74ef2e, 0x522fffb1, 0xd24708cc, 0x1c7e27cd, 0xa4eb215b, 0x3cf1d2e2, 0x19b47a38, 0x424f7618, 0x35856039, 0x9d17dee7, 0x27eb35e6, 0xc9aff67b, 0x36baf5b8, 0x09c467cd, 0xc18910b1, 0xe11dbf7b, 0x06cd1af8, 0x7170c608, 0x2d5e3354, 0xd4de495a, 0x64c6d006, 0xbcc0c62c, 0x3dd00db3, 0x708f8f34, 0x77d51b42, 0x264f620f, 0x24b8d2bf, 0x15c1b79e, 0x46a52564, 0xf8d7e54e, 0x3e378160, 0x7895cda5, 0x859c15a5, 0xe6459788, 0xc37bc75f, 0xdb07ba0c, 0x0676a3ab, 0x7f229b1e, 0x31842e7b, 0x24259fd7, 0xf8bef472, 0x835ffcb8, 0x6df4c1f2, 0x96f5b195, 0xfd0af0fc, 0xb0fe134c, 0xe2506d3d, 0x4f9b12ea, 0xf215f225, 0xa223736f, 0x9fb4c428, 0x25d04979, 0x34c713f8, 0xc4618187, 0xea7a6e98, 0x7cd16efc, 0x1436876c, 0xf1544107, 0xbedeee14, 0x56e9af27, 0xa04aa441, 0x3cf7c899, 0x92ecbae6, 0xdd67016d, 0x151682eb, 0xa842eedf, 0xfdba60b4, 0xf1907b75, 0x20e3030f, 0x24d8c29e, 0xe139673b, 0xefa63fb8, 0x71873054, 0xb6f2cf3b, 0x9f326442, 0xcb15a4cc, 0xb01a4504, 0xf1e47d8d, 0x844a1be5, 0xbae7dfdc, 0x42cbda70, 0xcd7dae0a, 0x57e85b7a, 0xd53f5af6, 0x20cf4d8c, 0xcea4d428, 0x79d130a4, 0x3486ebfb, 0x33d3cddc, 0x77853b53, 0x37effcb5, 0xc5068778, 0xe580b3e6, 0x4e68b8f4, 0xc5c8b37e, 0x0d809ea2, 0x398feb7c, 0x132a4f94, 0x43b7950e, 0x2fee7d1c, 0x223613bd, 0xdd06caa2, 0x37df932b, 0xc4248289, 0xacf3ebc3, 0x5715f6b7, 0xef3478dd, 0xf267616f, 0xc148cbe4, 0x9052815e, 0x5e410fab, 0xb48a2465, 0x2eda7fa4, 0xe87b40e4, 0xe98ea084, 0x5889e9e1, 0xefd390fc, 0xdd07d35b, 0xdb485694, 0x38d7e5b2, 0x57720101, 0x730edebc, 0x5b643113, 0x94917e4f, 0x503c2fba, 0x646f1282, 0x7523d24a, 0xe0779695, 0xf9c17a8f, 0x7a5b2121, 0xd187b896, 0x29263a4d, 0xba510cdf, 0x81f47c9f, 0xad1163ed, 0xea7b5965, 0x1a00726e, 0x11403092, 0x00da6d77, 0x4a0cdd61, 0xad1f4603, 0x605bdfb0, 0x9eedc364, 0x22ebe6a8, 0xcee7d28a, 0xa0e736a0, 0x5564a6b9, 0x10853209, 0xc7eb8f37, 0x2de705ca, 0x8951570f, 0xdf09822b, 0xbd691a6c, 0xaa12e4f2, 0x87451c0f, 0xe0f6a27a, 0x3ada4819, 0x4cf1764f, 0x0d771c2b, 0x67cdb156, 0x350d8384, 0x5938fa0f, 0x42399ef3, 0x36997b07, 0x0e84093d, 0x4aa93e61, 0x8360d87b, 0x1fa98b0c, 0x1149382c, 0xe97625a5, 0x0614d1b7, 0x0e25244b, 0x0c768347, 0x589e8d82, 0x0d2059d1, 0xa466bb1e, 0xf8da0a82, 0x04f19130, 0xba6e4ec0, 0x99265164, 0x1ee7230d, 0x50b2ad80, 0xeaee6801, 0x8db2a283, 0xea8bf59e); /** * Masking Keys * * @var array */ var $_Km = array(); /** * Rotate Keys * * @var array */ var $_Kr = array(); /** * The number of rounds to do * * @var integer */ var $_rounds = 16; /** * Set the key to be used for en/decryption. * * @param string $key The key to use. */ function setKey($key) { if (!is_null($key)) { $this->_rounds = (strlen($key) < 11) ? 12 : 16; $key = $this->_formatKey($key); $keySchedule = $this->_keySchedule($key); } } /** * Return the size of the blocks that this cipher needs. * * @return integer The number of characters per block. */ function getBlockSize() { return 8; } /** * Encrypt a block of data. * * @param string $block The data to encrypt. * @param string $key The key to use. * * @return string The encrypted output. */ function encryptBlock($block, $key = null) { if (!is_null($key)) { $this->_rounds = (strlen($key) < 11) ? 12 : 16; $key = $this->_formatKey($key); $keySchedule = $this->_keySchedule($key); } $L = $this->_formatData(substr($block, 0, 4)); $R = $this->_formatData(substr($block, 4, 4)); $L ^= $this->f1($R, $this->_Km[1], $this->_Kr[1]); $R ^= $this->f2($L, $this->_Km[2], $this->_Kr[2]); $L ^= $this->f3($R, $this->_Km[3], $this->_Kr[3]); $R ^= $this->f1($L, $this->_Km[4], $this->_Kr[4]); $L ^= $this->f2($R, $this->_Km[5], $this->_Kr[5]); $R ^= $this->f3($L, $this->_Km[6], $this->_Kr[6]); $L ^= $this->f1($R, $this->_Km[7], $this->_Kr[7]); $R ^= $this->f2($L, $this->_Km[8], $this->_Kr[8]); $L ^= $this->f3($R, $this->_Km[9], $this->_Kr[9]); $R ^= $this->f1($L, $this->_Km[10], $this->_Kr[10]); $L ^= $this->f2($R, $this->_Km[11], $this->_Kr[11]); $R ^= $this->f3($L, $this->_Km[12], $this->_Kr[12]); if ($this->_rounds > 12) { $L ^= $this->f1($R, $this->_Km[13], $this->_Kr[13]); $R ^= $this->f2($L, $this->_Km[14], $this->_Kr[14]); $L ^= $this->f3($R, $this->_Km[15], $this->_Kr[15]); $R ^= $this->f1($L, $this->_Km[16], $this->_Kr[16]); } $encrypted = pack("N", $R) . pack("N", $L); return $encrypted; } /** * Decrypt a block of data. * * @param string $block The data to decrypt. * @param string $key The key to use. * * @return string The decrypted output. */ function decryptBlock($block, $key = null) { if (!is_null($key)) { $this->_rounds = (strlen($key) < 11) ? 12 : 16; $key = $this->_formatKey($key); $keySchedule = $this->_keySchedule($key); } $R = $this->_formatData(substr($block, 0, 4)); $L = $this->_formatData(substr($block, 4, 4)); if ($this->_rounds > 12) { $R ^= $this->f1($L, $this->_Km[16], $this->_Kr[16]); $L ^= $this->f3($R, $this->_Km[15], $this->_Kr[15]); $R ^= $this->f2($L, $this->_Km[14], $this->_Kr[14]); $L ^= $this->f1($R, $this->_Km[13], $this->_Kr[13]); } $R ^= $this->f3($L, $this->_Km[12], $this->_Kr[12]); $L ^= $this->f2($R, $this->_Km[11], $this->_Kr[11]); $R ^= $this->f1($L, $this->_Km[10], $this->_Kr[10]); $L ^= $this->f3($R, $this->_Km[9], $this->_Kr[9]); $R ^= $this->f2($L, $this->_Km[8], $this->_Kr[8]); $L ^= $this->f1($R, $this->_Km[7], $this->_Kr[7]); $R ^= $this->f3($L, $this->_Km[6], $this->_Kr[6]); $L ^= $this->f2($R, $this->_Km[5], $this->_Kr[5]); $R ^= $this->f1($L, $this->_Km[4], $this->_Kr[4]); $L ^= $this->f3($R, $this->_Km[3], $this->_Kr[3]); $R ^= $this->f2($L, $this->_Km[2], $this->_Kr[2]); $L ^= $this->f1($R, $this->_Km[1], $this->_Kr[1]); $decrypted = pack("N", $L) . pack("N", $R); return $decrypted; } /** * f1() */ function f1($d, $Km, $Kr) { $I = $Km + $d; /* Nasty Fix as * $I << $Kr | $I >> (32 - $Kr) * Doesn't always give the correct answer */ $bin = str_pad(decbin($I), 32, '0', STR_PAD_LEFT); $I = bindec(substr($bin, $Kr) . substr($bin, 0, $Kr)); $f = $this->s1[($I >> 24) & 0xFF] ^ $this->s2[($I >> 16) & 0xFF]; $f = $f - $this->s3[($I >> 8) & 0xFF]; $f = $f + $this->s4[$I & 0xFF]; return $f; } /** * f2() */ function f2($d, $Km, $Kr) { $I = $Km ^ $d; /* Nasty Fix as * $I << $Kr | $I >> (32 - $Kr) * Doesn't always give the correct answer */ $bin = str_pad(decbin($I), 32, '0', STR_PAD_LEFT); $I = bindec(substr($bin, $Kr) . substr($bin, 0, $Kr)); $f = $this->s1[($I >> 24) & 0xFF] - $this->s2[($I >> 16) & 0xFF]; $f = $f + $this->s3[($I >> 8) & 0xFF]; $f = $f ^ $this->s4[$I & 0xFF]; return $f; } /** * f3() */ function f3($d, $Km, $Kr) { $I = 0xFFFFFFFF & ($Km - $d); /* Nasty Fix as * $I << $Kr | $I >> (32 - $Kr) * Doesn't always give the correct answer */ $bin = str_pad(decbin($I), 32, '0', STR_PAD_LEFT); $I = bindec(substr($bin, $Kr) . substr($bin, 0, $Kr)); $f = ($this->s1[($I >> 24) & 0xFF] + $this->s2[($I >> 16) & 0xFF]); $f = $f ^ $this->s3[($I >> 8) & 0xFF]; $f = 0xFFFFFFFF & ($f - $this->s4[$I & 0xFF]); return $f; } /** * Convert a string into a array of the ordinates of its characters. * * @param string $data Data to convert. * * @return array The converted data. */ function _formatData($data) { return $this->_combine4(unpack('C*', $data), 0x1); } /** * Converts a text key into an array padded with \0's to a length of 128 * bits. * * @param string $key The key to format. * * @return array The padded key. */ function _formatKey($key) { $res = array_values(unpack('C*', $key)); for ($i = count($res); $i < 16; $i++) { $res[$i] = 0; } return $res; } /** * Create the complete key shedule. * * @param array $key The key to use. */ function _keySchedule($key) { $x0x1x2x3 = $this->_combine4($key, 0x0); $x4x5x6x7 = $this->_combine4($key, 0x4); $x8x9xAxB = $this->_combine4($key, 0x8); $xCxDxExF = $this->_combine4($key, 0xC); $b = $this->_split($x0x1x2x3); $x0 = $b[0]; $x1 = $b[1]; $x2 = $b[2]; $x3 = $b[3]; $b = $this->_split($x4x5x6x7); $x4 = $b[0]; $x5 = $b[1]; $x6 = $b[2]; $x7 = $b[3]; $b = $this->_split($x8x9xAxB); $x8 = $b[0]; $x9 = $b[1]; $xA = $b[2]; $xB = $b[3]; $b = $this->_split($xCxDxExF); $xC = $b[0]; $xD = $b[1]; $xE = $b[2]; $xF = $b[3]; $z0z1z2z3 = $x0x1x2x3 ^ $this->s5[$xD] ^ $this->s6[$xF] ^ $this->s7[$xC] ^ $this->s8[$xE] ^ $this->s7[$x8]; $b = $this->_split($z0z1z2z3); $z0 = $b[0]; $z1 = $b[1]; $z2 = $b[2]; $z3 = $b[3]; $z4z5z6z7 = $x8x9xAxB ^ $this->s5[$z0] ^ $this->s6[$z2] ^ $this->s7[$z1] ^ $this->s8[$z3] ^ $this->s8[$xA]; $b = $this->_split($z4z5z6z7); $z4 = $b[0]; $z5 = $b[1]; $z6 = $b[2]; $z7 = $b[3]; $z8z9zAzB = $xCxDxExF ^ $this->s5[$z7] ^ $this->s6[$z6] ^ $this->s7[$z5] ^ $this->s8[$z4] ^ $this->s5[$x9]; $b = $this->_split($z8z9zAzB); $z8 = $b[0]; $z9 = $b[1]; $zA = $b[2]; $zB = $b[3]; $zCzDzEzF = $x4x5x6x7 ^ $this->s5[$zA] ^ $this->s6[$z9] ^ $this->s7[$zB] ^ $this->s8[$z8] ^ $this->s6[$xB]; $b = $this->_split($zCzDzEzF); $zC = $b[0]; $zD = $b[1]; $zE = $b[2]; $zF = $b[3]; $this->_Km[1] = $this->s5[$z8] ^ $this->s6[$z9] ^ $this->s7[$z7] ^ $this->s8[$z6] ^ $this->s5[$z2]; $this->_Km[2] = $this->s5[$zA] ^ $this->s6[$zB] ^ $this->s7[$z5] ^ $this->s8[$z4] ^ $this->s6[$z6]; $this->_Km[3] = $this->s5[$zC] ^ $this->s6[$zD] ^ $this->s7[$z3] ^ $this->s8[$z2] ^ $this->s7[$z9]; $this->_Km[4] = $this->s5[$zE] ^ $this->s6[$zF] ^ $this->s7[$z1] ^ $this->s8[$z0] ^ $this->s8[$zC]; $x0x1x2x3 = $z8z9zAzB ^ $this->s5[$z5] ^ $this->s6[$z7] ^ $this->s7[$z4] ^ $this->s8[$z6] ^ $this->s7[$z0]; $b = $this->_split($x0x1x2x3); $x0 = $b[0]; $x1 = $b[1]; $x2 = $b[2]; $x3 = $b[3]; $x4x5x6x7 = $z0z1z2z3 ^ $this->s5[$x0] ^ $this->s6[$x2] ^ $this->s7[$x1] ^ $this->s8[$x3] ^ $this->s8[$z2]; $b = $this->_split($x4x5x6x7); $x4 = $b[0]; $x5 = $b[1]; $x6 = $b[2]; $x7 = $b[3]; $x8x9xAxB = $z4z5z6z7 ^ $this->s5[$x7] ^ $this->s6[$x6] ^ $this->s7[$x5] ^ $this->s8[$x4] ^ $this->s5[$z1]; $b = $this->_split($x8x9xAxB); $x8 = $b[0]; $x9 = $b[1]; $xA = $b[2]; $xB = $b[3]; $xCxDxExF = $zCzDzEzF ^ $this->s5[$xA] ^ $this->s6[$x9] ^ $this->s7[$xB] ^ $this->s8[$x8] ^ $this->s6[$z3]; $b = $this->_split($xCxDxExF); $xC = $b[0]; $xD = $b[1]; $xE = $b[2]; $xF = $b[3]; $this->_Km[5] = $this->s5[$x3] ^ $this->s6[$x2] ^ $this->s7[$xC] ^ $this->s8[$xD] ^ $this->s5[$x8]; $this->_Km[6] = $this->s5[$x1] ^ $this->s6[$x0] ^ $this->s7[$xE] ^ $this->s8[$xF] ^ $this->s6[$xD]; $this->_Km[7] = $this->s5[$x7] ^ $this->s6[$x6] ^ $this->s7[$x8] ^ $this->s8[$x9] ^ $this->s7[$x3]; $this->_Km[8] = $this->s5[$x5] ^ $this->s6[$x4] ^ $this->s7[$xA] ^ $this->s8[$xB] ^ $this->s8[$x7]; $z0z1z2z3 = $x0x1x2x3 ^ $this->s5[$xD] ^ $this->s6[$xF] ^ $this->s7[$xC] ^ $this->s8[$xE] ^ $this->s7[$x8]; $b = $this->_split($z0z1z2z3); $z0 = $b[0]; $z1 = $b[1]; $z2 = $b[2]; $z3 = $b[3]; $z4z5z6z7 = $x8x9xAxB ^ $this->s5[$z0] ^ $this->s6[$z2] ^ $this->s7[$z1] ^ $this->s8[$z3] ^ $this->s8[$xA]; $b = $this->_split($z4z5z6z7); $z4 = $b[0]; $z5 = $b[1]; $z6 = $b[2]; $z7 = $b[3]; $z8z9zAzB = $xCxDxExF ^ $this->s5[$z7] ^ $this->s6[$z6] ^ $this->s7[$z5] ^ $this->s8[$z4] ^ $this->s5[$x9]; $b = $this->_split($z8z9zAzB); $z8 = $b[0]; $z9 = $b[1]; $zA = $b[2]; $zB = $b[3]; $zCzDzEzF = $x4x5x6x7 ^ $this->s5[$zA] ^ $this->s6[$z9] ^ $this->s7[$zB] ^ $this->s8[$z8] ^ $this->s6[$xB]; $b = $this->_split($zCzDzEzF); $zC = $b[0]; $zD = $b[1]; $zE = $b[2]; $zF = $b[3]; $this->_Km[9] = $this->s5[$z3] ^ $this->s6[$z2] ^ $this->s7[$zC] ^ $this->s8[$zD] ^ $this->s5[$z9]; $this->_Km[10] = $this->s5[$z1] ^ $this->s6[$z0] ^ $this->s7[$zE] ^ $this->s8[$zF] ^ $this->s6[$zC]; $this->_Km[11] = $this->s5[$z7] ^ $this->s6[$z6] ^ $this->s7[$z8] ^ $this->s8[$z9] ^ $this->s7[$z2]; $this->_Km[12] = $this->s5[$z5] ^ $this->s6[$z4] ^ $this->s7[$zA] ^ $this->s8[$zB] ^ $this->s8[$z6]; $x0x1x2x3 = $z8z9zAzB ^ $this->s5[$z5] ^ $this->s6[$z7] ^ $this->s7[$z4] ^ $this->s8[$z6] ^ $this->s7[$z0]; $b = $this->_split($x0x1x2x3); $x0 = $b[0]; $x1 = $b[1]; $x2 = $b[2]; $x3 = $b[3]; $x4x5x6x7 = $z0z1z2z3 ^ $this->s5[$x0] ^ $this->s6[$x2] ^ $this->s7[$x1] ^ $this->s8[$x3] ^ $this->s8[$z2]; $b = $this->_split($x4x5x6x7); $x4 = $b[0]; $x5 = $b[1]; $x6 = $b[2]; $x7 = $b[3]; $x8x9xAxB = $z4z5z6z7 ^ $this->s5[$x7] ^ $this->s6[$x6] ^ $this->s7[$x5] ^ $this->s8[$x4] ^ $this->s5[$z1]; $b = $this->_split($x8x9xAxB); $x8 = $b[0]; $x9 = $b[1]; $xA = $b[2]; $xB = $b[3]; $xCxDxExF = $zCzDzEzF ^ $this->s5[$xA] ^ $this->s6[$x9] ^ $this->s7[$xB] ^ $this->s8[$x8] ^ $this->s6[$z3]; $b = $this->_split($xCxDxExF); $xC = $b[0]; $xD = $b[1]; $xE = $b[2]; $xF = $b[3]; $this->_Km[13] = $this->s5[$x8] ^ $this->s6[$x9] ^ $this->s7[$x7] ^ $this->s8[$x6] ^ $this->s5[$x3]; $this->_Km[14] = $this->s5[$xA] ^ $this->s6[$xB] ^ $this->s7[$x5] ^ $this->s8[$x4] ^ $this->s6[$x7]; $this->_Km[15] = $this->s5[$xC] ^ $this->s6[$xD] ^ $this->s7[$x3] ^ $this->s8[$x2] ^ $this->s7[$x8]; $this->_Km[16] = $this->s5[$xE] ^ $this->s6[$xF] ^ $this->s7[$x1] ^ $this->s8[$x0] ^ $this->s8[$xD]; $z0z1z2z3 = $x0x1x2x3 ^ $this->s5[$xD] ^ $this->s6[$xF] ^ $this->s7[$xC] ^ $this->s8[$xE] ^ $this->s7[$x8]; $b = $this->_split($z0z1z2z3); $z0 = $b[0]; $z1 = $b[1]; $z2 = $b[2]; $z3 = $b[3]; $z4z5z6z7 = $x8x9xAxB ^ $this->s5[$z0] ^ $this->s6[$z2] ^ $this->s7[$z1] ^ $this->s8[$z3] ^ $this->s8[$xA]; $b = $this->_split($z4z5z6z7); $z4 = $b[0]; $z5 = $b[1]; $z6 = $b[2]; $z7 = $b[3]; $z8z9zAzB = $xCxDxExF ^ $this->s5[$z7] ^ $this->s6[$z6] ^ $this->s7[$z5] ^ $this->s8[$z4] ^ $this->s5[$x9]; $b = $this->_split($z8z9zAzB); $z8 = $b[0]; $z9 = $b[1]; $zA = $b[2]; $zB = $b[3]; $zCzDzEzF = $x4x5x6x7 ^ $this->s5[$zA] ^ $this->s6[$z9] ^ $this->s7[$zB] ^ $this->s8[$z8] ^ $this->s6[$xB]; $b = $this->_split($zCzDzEzF); $zC = $b[0]; $zD = $b[1]; $zE = $b[2]; $zF = $b[3]; $this->_Kr[1] = 0x1F & ($this->s5[$z8] ^ $this->s6[$z9] ^ $this->s7[$z7] ^ $this->s8[$z6] ^ $this->s5[$z2]); $this->_Kr[2] = 0x1F & ($this->s5[$zA] ^ $this->s6[$zB] ^ $this->s7[$z5] ^ $this->s8[$z4] ^ $this->s6[$z6]); $this->_Kr[3] = 0x1F & ($this->s5[$zC] ^ $this->s6[$zD] ^ $this->s7[$z3] ^ $this->s8[$z2] ^ $this->s7[$z9]); $this->_Kr[4] = 0x1F & ($this->s5[$zE] ^ $this->s6[$zF] ^ $this->s7[$z1] ^ $this->s8[$z0] ^ $this->s8[$zC]); $x0x1x2x3 = $z8z9zAzB ^ $this->s5[$z5] ^ $this->s6[$z7] ^ $this->s7[$z4] ^ $this->s8[$z6] ^ $this->s7[$z0]; $b = $this->_split($x0x1x2x3); $x0 = $b[0]; $x1 = $b[1]; $x2 = $b[2]; $x3 = $b[3]; $x4x5x6x7 = $z0z1z2z3 ^ $this->s5[$x0] ^ $this->s6[$x2] ^ $this->s7[$x1] ^ $this->s8[$x3] ^ $this->s8[$z2]; $b = $this->_split($x4x5x6x7); $x4 = $b[0]; $x5 = $b[1]; $x6 = $b[2]; $x7 = $b[3]; $x8x9xAxB = $z4z5z6z7 ^ $this->s5[$x7] ^ $this->s6[$x6] ^ $this->s7[$x5] ^ $this->s8[$x4] ^ $this->s5[$z1]; $b = $this->_split($x8x9xAxB); $x8 = $b[0]; $x9 = $b[1]; $xA = $b[2]; $xB = $b[3]; $xCxDxExF = $zCzDzEzF ^ $this->s5[$xA] ^ $this->s6[$x9] ^ $this->s7[$xB] ^ $this->s8[$x8] ^ $this->s6[$z3]; $b = $this->_split($xCxDxExF); $xC = $b[0]; $xD = $b[1]; $xE = $b[2]; $xF = $b[3]; $this->_Kr[5] = 0x1F & ($this->s5[$x3] ^ $this->s6[$x2] ^ $this->s7[$xC] ^ $this->s8[$xD] ^ $this->s5[$x8]); $this->_Kr[6] = 0x1F & ($this->s5[$x1] ^ $this->s6[$x0] ^ $this->s7[$xE] ^ $this->s8[$xF] ^ $this->s6[$xD]); $this->_Kr[7] = 0x1F & ($this->s5[$x7] ^ $this->s6[$x6] ^ $this->s7[$x8] ^ $this->s8[$x9] ^ $this->s7[$x3]); $this->_Kr[8] = 0x1F & ($this->s5[$x5] ^ $this->s6[$x4] ^ $this->s7[$xA] ^ $this->s8[$xB] ^ $this->s8[$x7]); $z0z1z2z3 = $x0x1x2x3 ^ $this->s5[$xD] ^ $this->s6[$xF] ^ $this->s7[$xC] ^ $this->s8[$xE] ^ $this->s7[$x8]; $b = $this->_split($z0z1z2z3); $z0 = $b[0]; $z1 = $b[1]; $z2 = $b[2]; $z3 = $b[3]; $z4z5z6z7 = $x8x9xAxB ^ $this->s5[$z0] ^ $this->s6[$z2] ^ $this->s7[$z1] ^ $this->s8[$z3] ^ $this->s8[$xA]; $b = $this->_split($z4z5z6z7); $z4 = $b[0]; $z5 = $b[1]; $z6 = $b[2]; $z7 = $b[3]; $z8z9zAzB = $xCxDxExF ^ $this->s5[$z7] ^ $this->s6[$z6] ^ $this->s7[$z5] ^ $this->s8[$z4] ^ $this->s5[$x9]; $b = $this->_split($z8z9zAzB); $z8 = $b[0]; $z9 = $b[1]; $zA = $b[2]; $zB = $b[3]; $zCzDzEzF = $x4x5x6x7 ^ $this->s5[$zA] ^ $this->s6[$z9] ^ $this->s7[$zB] ^ $this->s8[$z8] ^ $this->s6[$xB]; $b = $this->_split($zCzDzEzF); $zC = $b[0]; $zD = $b[1]; $zE = $b[2]; $zF = $b[3]; $this->_Kr[9] = 0x1F & ($this->s5[$z3] ^ $this->s6[$z2] ^ $this->s7[$zC] ^ $this->s8[$zD] ^ $this->s5[$z9]); $this->_Kr[10] = 0x1F & ($this->s5[$z1] ^ $this->s6[$z0] ^ $this->s7[$zE] ^ $this->s8[$zF] ^ $this->s6[$zC]); $this->_Kr[11] = 0x1F & ($this->s5[$z7] ^ $this->s6[$z6] ^ $this->s7[$z8] ^ $this->s8[$z9] ^ $this->s7[$z2]); $this->_Kr[12] = 0x1F & ($this->s5[$z5] ^ $this->s6[$z4] ^ $this->s7[$zA] ^ $this->s8[$zB] ^ $this->s8[$z6]); $x0x1x2x3 = $z8z9zAzB ^ $this->s5[$z5] ^ $this->s6[$z7] ^ $this->s7[$z4] ^ $this->s8[$z6] ^ $this->s7[$z0]; $b = $this->_split($x0x1x2x3); $x0 = $b[0]; $x1 = $b[1]; $x2 = $b[2]; $x3 = $b[3]; $x4x5x6x7 = $z0z1z2z3 ^ $this->s5[$x0] ^ $this->s6[$x2] ^ $this->s7[$x1] ^ $this->s8[$x3] ^ $this->s8[$z2]; $b = $this->_split($x4x5x6x7); $x4 = $b[0]; $x5 = $b[1]; $x6 = $b[2]; $x7 = $b[3]; $x8x9xAxB = $z4z5z6z7 ^ $this->s5[$x7] ^ $this->s6[$x6] ^ $this->s7[$x5] ^ $this->s8[$x4] ^ $this->s5[$z1]; $b = $this->_split($x8x9xAxB); $x8 = $b[0]; $x9 = $b[1]; $xA = $b[2]; $xB = $b[3]; $xCxDxExF = $zCzDzEzF ^ $this->s5[$xA] ^ $this->s6[$x9] ^ $this->s7[$xB] ^ $this->s8[$x8] ^ $this->s6[$z3]; $b = $this->_split($xCxDxExF); $xC = $b[0]; $xD = $b[1]; $xE = $b[2]; $xF = $b[3]; $this->_Kr[13] = 0x1F & ($this->s5[$x8] ^ $this->s6[$x9] ^ $this->s7[$x7] ^ $this->s8[$x6] ^ $this->s5[$x3]); $this->_Kr[14] = 0x1F & ($this->s5[$xA] ^ $this->s6[$xB] ^ $this->s7[$x5] ^ $this->s8[$x4] ^ $this->s6[$x7]); $this->_Kr[15] = 0x1F & ($this->s5[$xC] ^ $this->s6[$xD] ^ $this->s7[$x3] ^ $this->s8[$x2] ^ $this->s7[$x8]); $this->_Kr[16] = 0x1F & ($this->s5[$xE] ^ $this->s6[$xF] ^ $this->s7[$x1] ^ $this->s8[$x0] ^ $this->s8[$xD]); } /** * Unpack the 32 bit value into an array of four 8 bit values. * * @param integer $x 32 bit value. * * @return array Four 8 bit values MS to LS. */ function _split($x) { return array(($x >> 24) & 0xFF, ($x >> 16) & 0xFF, ($x >> 8) & 0xFF, $x & 0xFF); } /** * Pack four 8 bit values into one 32 bit value. * * @param array $key Array of 8 bit values. * @param integer $start Start position in array to get 4 values from. * * @return integer 32 bit value. */ function _combine4($key, $start = 0) { $res = 0; if (array_key_exists($start, $key)) { $res |= ($key[$start]) << 24; } if (array_key_exists($start + 1, $key)) { $res |= ($key[$start + 1]) << 16; } if (array_key_exists($start + 2, $key)) { $res |= ($key[$start + 2]) << 8; } if (array_key_exists($start + 3, $key)) { $res |= ($key[$start + 3]); } return $res; } } * @since Horde 3.0 * @package Horde_Cipher */ class Horde_Cipher_des extends Horde_Cipher { /** * Initial Permutation * * @var array */ var $ip = array( 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 ); /** * Final Permutation IP^-1 * * @var array */ var $fp = array( 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25 ); /** * E Bit Selection Table * * @var array */ var $e = array( 32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1 ); /** * S boxes * * @var array */ var $_s = array( /* S1 */ 1 => array( 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 ), /* S2 */ 2 => array( 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9, ), /* S3 */ 3 => array( 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12, ), /* S4 */ 4 => array( 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14, ), /* S5 */ 5 => array( 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3, ), /* S6 */ 6 => array( 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13, ), /* S7 */ 7 => array( 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12, ), /* S8 */ 8 => array( 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 ) ); /** * Primitive function * * @var array */ var $p = array( 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25 ); /** * Permuted Choice Table * * @var array */ var $pc1 = array( 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4 ); /** * Number left rotations of pc1 * * @var array */ var $shifts = array( 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 ); /** * Permuted Choice Table 2 * * @var array */ var $pc2 = array( 14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32 ); /** * Key Schedule * * @var array */ var $_Ks = array(); /** * Set the key to be used for en/decryption. * * @param string $key The key to use. */ function setKey($key) { if (!is_null($key)) { $this->_Ks = $this->_keySchedule($key); } } /** * Return the size of the blocks that this cipher needs. * * @return integer The number of characters per block. */ function getBlockSize() { return 8; } /** * Encrypt a block of data. * * @param string $block The data to encrypt. * @param string $key The key to use. * * @return string The encrypted output. */ function encryptBlock($block, $key = null) { if (!is_null($key)) { $this->_Ks = $this->_keySchedule($key); } $block = $this->_initialPerm($block); $L = substr($block, 0, 4); $R = substr($block, 4, 4); for ($i = 1; $i <= 16; $i++ ) { $R_prev = $R; $L_prev = $L; $L = $R; $R = $L_prev ^ $this->_f($R_prev, $i); } $block = $R . $L; $block = $this->_finalPerm($block); return $block; } /** * Decrypt a block of data. * * @param string $block The data to decrypt. * @param string $key The key to use. * * @return string The decrypted output. */ function decryptBlock($block, $key = null) { $block = $this->_initialPerm($block); if (!is_null($key)) { $this->_Ks = $this->_keySchedule($key); } $L = substr($block, 0, 4); $R = substr($block, 4, 4); for ($i = 16; $i >= 1; $i-- ) { $R_prev = $R; $L_prev = $L; $L = $R_prev; $R = $L_prev ^ $this->_f($R_prev, $i); } $block = $R . $L; $block = $this->_finalPerm($block); return $block; } /** * Put an input string through an initial permutation * * @param string $input Input string. * * @return string Permutated string. */ function _initialPerm($input) { // TODO: Some stylie bitwise thing instead. $input_bin = ''; for ($i = 0; $i < 8; $i++) { $input_bin .= str_pad(decbin(ord($input[$i])), 8, '0', STR_PAD_LEFT); } $output_bin = ''; foreach ($this->ip as $offset) { $output_bin .= $input_bin[$offset - 1]; } $output = ''; for ($i = 0; $i < 8; $i++) { $output .= chr(bindec(substr($output_bin, 8 * $i, 8))); } return $output; } /** * Put an input string through a final permutation. * * @param string $input Input string. * * @return string Permutated string. */ function _finalPerm($input) { // TODO: Some stylie bitwise thing instead. $input_bin = ''; for ($i = 0; $i < 8; $i++) { $input_bin .= str_pad(decbin(ord($input[$i])), 8, '0', STR_PAD_LEFT); } $output_bin = ''; foreach ($this->fp as $offset) { $output_bin .= $input_bin[$offset - 1]; } $output = ''; for ($i = 0; $i < 8; $i++) { $output .= chr(bindec(substr($output_bin, 8 * $i, 8))); } return $output; } /** * f() The permutation function. * * @param string $input Input string. * @param integer $round The round. * * @return string The output string. */ function _f($input, $round) { // TODO: Some stylie bitwise thing instead. $key = $this->_Ks[$round]; $input_bin = ''; for ($i = 0; $i < 4; $i++) { $input_bin .= str_pad(decbin(ord($input[$i])), 8, '0', STR_PAD_LEFT); } $expanded_bin = ''; foreach ($this->e as $offset) { $expanded_bin .= $input_bin[$offset - 1]; } $expanded = array(); for ($i = 0; $i < 8; $i++) { $expanded[$i] = bindec('00' . substr($expanded_bin, $i * 6, 6)) ^ $key[$i]; } $combined_bin = ''; for ($i = 0; $i < 8; $i++) { $s_index = (($expanded[$i] & 0x20) >> 4) | ($expanded[$i] & 0x01); $s_index = 16 * $s_index + (($expanded[$i] & 0x1E) >> 1); $val = $this->_s[$i + 1][$s_index]; $combined_bin .= str_pad(decbin($val), 4, '0', STR_PAD_LEFT); } $output_bin = ''; foreach ($this->p as $offset) { $output_bin .= $combined_bin[$offset - 1]; } $output = ''; for ($i = 0; $i < 4; $i++) { $output .= chr(bindec(substr($output_bin, $i * 8, 8))); } return $output; } /** * Create the complete key shedule. * * @param string $key The key to use. * * @return array Key schedule. */ function _keySchedule($key) { $key = str_pad($key, 8, "\0"); $ks = array(); $key_bin = ''; for ($i = 0; $i < 8; $i++) { $key_bin .= str_pad(decbin(ord($key[$i])), 8, '0', STR_PAD_LEFT); } $c = ''; $d = ''; for ($i = 0; $i < 28; $i++) { $c .= $key_bin[$this->pc1[$i] - 1]; $d .= $key_bin[$this->pc1[28 + $i] - 1]; } for ($i = 0; $i < 16; $i++) { $c = substr($c, $this->shifts[$i]) . substr($c, 0, $this->shifts[$i]); $d = substr($d, $this->shifts[$i]) . substr($d, 0, $this->shifts[$i]); $cd = $c . $d; $permutated_bin = ''; foreach ($this->pc2 as $offset) { $permutated_bin .= $cd[$offset - 1]; } for ($j = 0; $j < 8; $j++) { $ks[$i + 1][] = bindec('00' . substr($permutated_bin, $j * 6, 6)); } } return $ks; } } INDX( u(hT*4*(*4*(*4*(*4*( BlockModep\ii*4*(C b BlockMode.phphR*4*(*4*(*4*(*4*(BLOCKM~1pZii*4*(C b BLOCKM~1.PHPpZii*4*(C `_ blowfish.phphXii*4*(j; b cast128.php`Pii*4*(+ 0. des.php`Pii*4*(0  C rc2.php`Pii*4*(0 ! rc4.php * http://www.mirrors.wiretapped.net/security/cryptography/ * algorithms/rc2/comments/gutman-960211 * * $Horde: framework/Cipher/Cipher/rc2.php,v 1.9.12.11 2009-01-06 15:22:57 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 Mike Cochrane * @since Horde 2.2 * @package Horde_Cipher */ class Horde_Cipher_rc2 extends Horde_Cipher { /** * Permutations array * * @var array */ var $_perm = array( 0xD9, 0x78, 0xF9, 0xC4, 0x19, 0xDD, 0xB5, 0xED, 0x28, 0xE9, 0xFD, 0x79, 0x4A, 0xA0, 0xD8, 0x9D, 0xC6, 0x7E, 0x37, 0x83, 0x2B, 0x76, 0x53, 0x8E, 0x62, 0x4C, 0x64, 0x88, 0x44, 0x8B, 0xFB, 0xA2, 0x17, 0x9A, 0x59, 0xF5, 0x87, 0xB3, 0x4F, 0x13, 0x61, 0x45, 0x6D, 0x8D, 0x09, 0x81, 0x7D, 0x32, 0xBD, 0x8F, 0x40, 0xEB, 0x86, 0xB7, 0x7B, 0x0B, 0xF0, 0x95, 0x21, 0x22, 0x5C, 0x6B, 0x4E, 0x82, 0x54, 0xD6, 0x65, 0x93, 0xCE, 0x60, 0xB2, 0x1C, 0x73, 0x56, 0xC0, 0x14, 0xA7, 0x8C, 0xF1, 0xDC, 0x12, 0x75, 0xCA, 0x1F, 0x3B, 0xBE, 0xE4, 0xD1, 0x42, 0x3D, 0xD4, 0x30, 0xA3, 0x3C, 0xB6, 0x26, 0x6F, 0xBF, 0x0E, 0xDA, 0x46, 0x69, 0x07, 0x57, 0x27, 0xF2, 0x1D, 0x9B, 0xBC, 0x94, 0x43, 0x03, 0xF8, 0x11, 0xC7, 0xF6, 0x90, 0xEF, 0x3E, 0xE7, 0x06, 0xC3, 0xD5, 0x2F, 0xC8, 0x66, 0x1E, 0xD7, 0x08, 0xE8, 0xEA, 0xDE, 0x80, 0x52, 0xEE, 0xF7, 0x84, 0xAA, 0x72, 0xAC, 0x35, 0x4D, 0x6A, 0x2A, 0x96, 0x1A, 0xD2, 0x71, 0x5A, 0x15, 0x49, 0x74, 0x4B, 0x9F, 0xD0, 0x5E, 0x04, 0x18, 0xA4, 0xEC, 0xC2, 0xE0, 0x41, 0x6E, 0x0F, 0x51, 0xCB, 0xCC, 0x24, 0x91, 0xAF, 0x50, 0xA1, 0xF4, 0x70, 0x39, 0x99, 0x7C, 0x3A, 0x85, 0x23, 0xB8, 0xB4, 0x7A, 0xFC, 0x02, 0x36, 0x5B, 0x25, 0x55, 0x97, 0x31, 0x2D, 0x5D, 0xFA, 0x98, 0xE3, 0x8A, 0x92, 0xAE, 0x05, 0xDF, 0x29, 0x10, 0x67, 0x6C, 0xBA, 0xC9, 0xD3, 0x00, 0xE6, 0xCF, 0xE1, 0x9E, 0xA8, 0x2C, 0x63, 0x16, 0x01, 0x3F, 0x58, 0xE2, 0x89, 0xA9, 0x0D, 0x38, 0x34, 0x1B, 0xAB, 0x33, 0xFF, 0xB0, 0xBB, 0x48, 0x0C, 0x5F, 0xB9, 0xB1, 0xCD, 0x2E, 0xC5, 0xF3, 0xDB, 0x47, 0xE5, 0xA5, 0x9C, 0x77, 0x0A, 0xA6, 0x20, 0x68, 0xFE, 0x7F, 0xC1, 0xAD ); /** * Array to hold the key schedule * * @var array */ var $_keySchedule = array(); /** * Set the key to be used for en/decryption. * * @param string $key The key to use. */ function setKey($key) { $key = array_values(unpack('C*', $key)); $bits = 1024; /* Expand input key to 128 bytes */ $len = count($key); $last = $key[$len - 1]; for ($i = $len; $i < 128; $i++) { $last = $this->_perm[($key[$i - $len] + $last) & 0xFF]; $key[$i] = $last; } /* Phase 2 - reduce effective key size to "bits" */ if ($len != 8) { $len = $len * 8; } $key[128 - $len] = $this->_perm[$key[128 - $len] & 0xFF]; for ($i = 127 - $len; $i >= 0; $i--) { $key[$i] = $this->_perm[$key[$i + $len] ^ $key[$i + 1]]; } /* Phase 3 - convert to 16 bit values */ for ($i = 63; $i >= 0; $i--) { $this->_keySchedule[$i] = ($key[$i * 2 + 1] << 8 | $key[$i * 2]) & 0xFFFF; } } /** * Return the size of the blocks that this cipher needs. * * @return integer The number of characters per block. */ function getBlockSize() { return 8; } /** * Encrypt a block of data. * * @param string $block The data to encrypt. * @param string $key The key to use. * * @return string The encrypted output. */ function encryptBlock($block, $key = null) { if (!is_null($key)) { $this->setKey($key); } $plain = unpack('v*', $block); for ($i = 0; $i < 16; $i++) { $plain[1] += ($plain[2] & ~$plain[4]) + ($plain[3] & $plain[4]) + $this->_keySchedule[4 * $i + 0]; $bin = str_pad(decbin(0xFFFF & $plain[1]), 32, '0', STR_PAD_LEFT); $plain[1] = bindec($bin . substr($bin, 16, 1)); $plain[2] += ($plain[3] & ~$plain[1]) + ($plain[4] & $plain[1]) + $this->_keySchedule[4 * $i + 1]; $bin = str_pad(decbin(0xFFFF & $plain[2]), 32, '0', STR_PAD_LEFT); $plain[2] = bindec($bin . substr($bin, 16, 2)); $plain[3] += ($plain[4] & ~$plain[2]) + ($plain[1] & $plain[2]) + $this->_keySchedule[4 * $i + 2]; $bin = str_pad(decbin(0xFFFF & $plain[3]), 16, '0', STR_PAD_LEFT); $plain[3] = bindec($bin . substr($bin, 0, 3)); $plain[4] += ($plain[1] & ~$plain[3]) + ($plain[2] & $plain[3]) + $this->_keySchedule[4 * $i + 3]; $bin = str_pad(decbin(0xFFFF & $plain[4]), 16, '0', STR_PAD_LEFT); $plain[4] = bindec($bin . substr($bin, 0, 5)); if ($i == 4 || $i == 10) { $plain[1] += $this->_keySchedule[$plain[4] & 0x3F]; $plain[2] += $this->_keySchedule[$plain[1] & 0x3F]; $plain[3] += $this->_keySchedule[$plain[2] & 0x3F]; $plain[4] += $this->_keySchedule[$plain[3] & 0x3F]; } } $encrypted = pack("v*", $plain[1], $plain[2], $plain[3], $plain[4]); return $encrypted; } /** * Decrypt a block of data. * * @param string $block The data to decrypt. * @param string $key The key to use. * * @return string The decrypted output. */ function decryptBlock($block, $key = null) { if (!is_null($key)) { $this->setKey($key); } $cipher = unpack('v*', $block); for ($i = 15; $i >= 0; $i--) { $bin = str_pad(decbin(0xFFFF & $cipher[4]), 16, '0', STR_PAD_LEFT); $cipher[4] = bindec(substr($bin, -21, 21) . substr($bin, 0, 11)); $cipher[4] -= ($cipher[1] & ~$cipher[3]) + ($cipher[2] & $cipher[3]) + $this->_keySchedule[4 * $i + 3]; $bin = str_pad(decbin(0xFFFF & $cipher[3]), 16, '0', STR_PAD_LEFT); $cipher[3] = bindec(substr($bin, -19, 19) . substr($bin, 0, 13)); $cipher[3] -= ($cipher[4] & ~$cipher[2]) + ($cipher[1] & $cipher[2]) + $this->_keySchedule[4 * $i + 2]; $bin = str_pad(decbin(0xFFFF & $cipher[2]), 16, '0', STR_PAD_LEFT); $cipher[2] = bindec(substr($bin, -18, 18) . substr($bin, 0, 14)); $cipher[2] -= ($cipher[3] & ~$cipher[1]) + ($cipher[4] & $cipher[1]) + $this->_keySchedule[4 * $i + 1]; $bin = str_pad(decbin(0xFFFF & $cipher[1]), 16, '0', STR_PAD_LEFT); $cipher[1] = bindec(substr($bin, -17, 17) . substr($bin, 0, 15)); $cipher[1] -= ($cipher[2] & ~$cipher[4]) + ($cipher[3] & $cipher[4]) + $this->_keySchedule[4 * $i + 0]; if ($i == 5 || $i == 11) { $cipher[4] -= $this->_keySchedule[$cipher[3] & 0x3F]; $cipher[3] -= $this->_keySchedule[$cipher[2] & 0x3F]; $cipher[2] -= $this->_keySchedule[$cipher[1] & 0x3F]; $cipher[1] -= $this->_keySchedule[$cipher[4] & 0x3F]; } } return pack("v*", $cipher[1], $cipher[2], $cipher[3], $cipher[4]); } } * @since Horde 2.2 * @package Horde_Cipher */ class Horde_Cipher_rc4 extends Horde_Cipher { /** * Pointer to a PEAR Crypt_RC4 object * * @var Crypt_RC4 */ var $_cipher; /** * Constructor */ function Horde_Cipher_rc4($params = null) { require_once 'Crypt/Rc4.php'; $this->_cipher = &new Crypt_Rc4(); } /** * Set the key to be used for en/decryption. * * @param string $key The key to use. */ function setKey($key) { $this->_cipher->setKey($key); } /** * Return the size of the blocks that this cipher needs. * * @return integer The number of characters per block. */ function getBlockSize() { return 8; } /** * Encrypt a block of data. * * @param string $block The data to encrypt. * @param string $key The key to use. * * @return string The encrypted output. */ function encryptBlock($block, $key = null) { if (!is_null($key)) { $this->setKey($key); } // Make a copy of the cipher as it destroys itself during a crypt $cipher = $this->_cipher; $cipher->crypt($block); return $block; } /** * Decrypt a block of data. * * @param string $block The data to decrypt. * @param string $key The key to use. * * @return string The decrypted output. */ function decryptBlock($block, $key = null) { if (!is_null($key)) { $this->setKey($key); } // Make a copy of the cipher as it destroys itself during a // crypt. $cipher = $this->_cipher; $cipher->decrypt($block); return $block; } } * @since Horde 2.2 * @package Horde_Cipher */ class Horde_Cipher { /** * The block mode for the cipher chaining * * @var string */ var $_blockMode = 'cbc'; /** * The initialization vector * * @var string */ var $_iv = null; /** * Set the block mode for cipher chaining. * * @param string $blockMode The new blockmode. */ function setBlockMode($blockMode) { $this->_blockMode = $blockMode; } /** * Set the IV. * * @param string $iv The new IV. */ function setIV($iv) { $this->_iv = $iv; } /** * Encrypt a string. * * @param string $plaintext The data to encrypt. * * @return string The encrypted data. */ function encrypt($plaintext) { require_once dirname(__FILE__) . '/Cipher/BlockMode.php'; $blockMode = &Horde_Cipher_BlockMode::factory($this->_blockMode); if (!is_null($this->_iv)) { $blockMode->setIV($this->_iv); } return $blockMode->encrypt($this, $plaintext); } /** * Decrypt a string. * * @param string $ciphertext The data to decrypt. * * @return string The decrypted data. */ function decrypt($ciphertext) { require_once dirname(__FILE__) . '/Cipher/BlockMode.php'; $blockMode = &Horde_Cipher_BlockMode::factory($this->_blockMode); if (!is_null($this->_iv)) { $blockMode->setIV($this->_iv); } return $blockMode->decrypt($this, $ciphertext); } /** * Attempts to return a concrete Horde_Cipher instance. * * @param string $cipher The type of concrete Horde_Cipher subclass to * return. * @param array $params A hash containing any additional parameters a * subclass might need. * * @return Horde_Cipher The newly created concrete Horde_Cipher instance, * or PEAR_Error on error. */ function &factory($cipher, $params = null) { $driver = basename($cipher); $class = 'Horde_Cipher_' . $driver; if (!class_exists($class)) { include_once 'Horde/Cipher/' . $cipher . '.php'; } if (class_exists($class)) { $cipher = new $class($params); } else { $cipher = PEAR::raiseError('Class definition of ' . $class . ' not found.'); } return $cipher; } } * @author Jan Schneider * @since Horde 3.0 * @package Horde_CLI */ class Horde_CLI { /** * Are we running on a console? * * @var boolean */ var $_console; /** * The newline string to use. * * @var string */ var $_newline; /** * The string to use for clearing the screen. * * @var string */ var $_clearscreen = ''; /** * The indent string to use. * * @var string */ var $_indent; /** * The string to mark the beginning of bold text. * * @var string */ var $_bold_start = ''; /** * The string to mark the end of bold text. * * @var string */ var $_bold_end = ''; /** * The strings to mark the beginning of coloured text. */ var $_red_start = ''; var $_green_start = ''; var $_yellow_start = ''; var $_blue_start = ''; /** * The strings to mark the end of coloured text. */ var $_red_end = ''; var $_green_end = ''; var $_yellow_end = ''; var $_blue_end = ''; /** * Terminal foreground color codes. Not used yet. * @var array */ var $_terminalForegrounds = array( 'normal' => "\x1B[0m", 'black' => "\x1B[0m", 'bold' => "\x1b[1m", 'red' => "\x1B[31m", 'green' => "\x1B[32m", 'brown' => "\x1B[33m", 'blue' => "\x1B[34m", 'magenta' => "\x1B[35m", 'cyan' => "\x1B[36m", 'lightgray' => "\x1B[37m", 'white' => "\x1B[1m\x1B[37m", 'darkgray' => "\x1B[1m\x1B[0m", 'lightred' => "\x1B[1m\x1B[31m", 'lightgreen' => "\x1B[1m\x1B[32m", 'yellow' => "\x1B[1m\x1B[33m", 'lightblue' => "\x1B[1m\x1B[34m", 'lightmagenta' => "\x1B[1m\x1B[35m", 'lightcyan' => "\x1B[1m\x1B[36m", ); /** * Terminal background color codes. Not used yet. * @var array */ var $_terminalBackgrounds = array( 'normal' => "\x1B[0m", 'black' => "\x1B[0m", 'red' => "\x1B[41m", 'green' => "\x1B[42m", 'brown' => "\x1B[43m", 'blue' => "\x1B[44m", 'magenta' => "\x1B[45m", 'cyan' => "\x1B[46m", 'lightgray' => "\x1B[47m", ); /** * Returns a single instance of the Horde_CLI class. */ function &singleton() { static $instance; if (!isset($instance)) { $instance = new Horde_CLI(); } return $instance; } /** * Detect the current environment (web server or console) and sets * internal values accordingly. * * The constructor must not be called after init(). Either use the * singleton() method to retrieve a Horde_CLI object, or don't call init() * statically. */ function Horde_CLI() { $this->_console = $this->runningFromCLI(); if ($this->_console) { $this->_newline = "\n"; $this->_indent = ' '; $term = getenv('TERM'); if ($term) { if (preg_match('/^(xterm|vt220|linux)/', $term)) { $this->_clearscreen = "\x1b[2J\x1b[H"; $this->_bold_start = "\x1b[1m"; $this->_red_start = "\x1b[01;31m"; $this->_green_start = "\x1b[01;32m"; $this->_yellow_start = "\x1b[01;33m"; $this->_blue_start = "\x1b[01;34m"; $this->_bold_end = $this->_red_end = $this->_green_end = $this->_yellow_end = $this->_blue_end = "\x1b[0m"; } elseif (preg_match('/^vt100/', $term)) { $this->_clearscreen = "\x1b[2J\x1b[H"; $this->_bold_start = "\x1b[1m"; $this->_bold_end = "\x1b[0m"; } } } else { $this->_newline = '
'; $this->_indent = str_repeat(' ', 4); $this->_bold_start = ''; $this->_bold_end = ''; $this->_red_start = ''; $this->_green_start = ''; $this->_yellow_start = ''; $this->_blue_start = ''; $this->_red_end = $this->_green_end = $this->_yellow_end = $this->_blue_end = ''; } if ($this->_console) { register_shutdown_function(array($this, '_shutdown')); } } /** * Prints $text on a single line. * * @param string $text The text to print. * @param boolean $pre If true the linebreak is printed before * the text instead of after it. */ function writeln($text = '', $pre = false) { if ($pre) { echo $this->_newline . $text; } else { echo $text . $this->_newline; } } /** * Clears the entire screen, if possible. * * @since Horde 3.2 */ function clearScreen() { echo $this->_clearscreen; } /** * Returns the indented string. * * @param string $text The text to indent. */ function indent($text) { return $this->_indent . $text; } /** * Returns a bold version of $text. * * @param string $text The text to bold. */ function bold($text) { return $this->_bold_start . $text . $this->_bold_end; } /** * Returns a red version of $text. * * @param string $text The text to print in red. */ function red($text) { return $this->_red_start . $text . $this->_red_end; } /** * Returns a green version of $text. * * @param string $text The text to print in green. */ function green($text) { return $this->_green_start . $text . $this->_green_end; } /** * Returns a blue version of $text. * * @param string $text The text to print in blue. */ function blue($text) { return $this->_blue_start . $text . $this->_blue_end; } /** * Returns a yellow version of $text. * * @param string $text The text to print in yellow. */ function yellow($text) { return $this->_yellow_start . $text . $this->_yellow_end; } /** * Displays a message. * * @param string $event The message string. * @param string $type The type of message: 'cli.error', 'cli.warning', * 'cli.success', or 'cli.message'. */ function message($message, $type = 'cli.message') { $message = wordwrap(str_replace("\n", "\n ", $message), 68, "\n ", true); switch ($type) { case 'cli.error': $type_message = $this->red('[ ERROR! ] '); break; case 'cli.warning': $type_message = $this->yellow('[ WARN ] '); break; case 'cli.success': $type_message = $this->green('[ OK ] '); break; case 'cli.message': $type_message = $this->blue('[ INFO ] '); break; } $this->writeln($type_message . $message); } /** * Displays a fatal error message. * * @param string $error The error text to display. */ function fatal($error) { $this->writeln($this->red('====================')); $this->writeln(); $this->writeln($this->red(_("Fatal Error:"))); $this->writeln($this->red($error)); $this->writeln(); $this->writeln($this->red('====================')); exit(1); } /** * Prompts for a user response. * * @todo Horde 4: switch $choices and $default * * @param string $prompt The message to display when prompting the user. * @param array $choices The choices available to the user or null for a * text input. * @param string $default The default value if no value specified. * * @return mixed The user's response to the prompt. */ function prompt($prompt, $choices = null, $default = null) { if ($default !== null) { $prompt .= ' [' . $default . ']'; } // Main event loop to capture top level command. while (true) { // Print out the prompt message. $this->writeln($prompt . ' ', !is_array($choices)); if (is_array($choices) && !empty($choices)) { foreach ($choices as $key => $choice) { $this->writeln($this->indent('(' . $this->bold($key) . ') ' . $choice)); } $this->writeln(_("Type your choice: "), true); @ob_flush(); // Get the user choice. $response = trim(fgets(STDIN)); if ($response === '' && $default !== null) { $response = $default; } if (isset($choices[$response])) { return $response; } else { $this->writeln($this->red(sprintf(_("\"%s\" is not a valid choice."), $response))); } } else { @ob_flush(); $response = trim(fgets(STDIN)); if ($response === '' && $default !== null) { $response = $default; } return $response; } } return true; } /** * Reads everything that is sent through standard input and returns it * as a single string. * * @return string The contents of the standard input. */ function readStdin() { $in = ''; while (!feof(STDIN)) { $in .= fgets(STDIN, 1024); } return $in; } /** * CLI scripts shouldn't timeout, so try to set the time limit to * none. Also initialize a few variables in $_SERVER that aren't present * from the CLI. * * You must not call init() statically before calling the constructor. * Either use the singleton() method to retrieve a Horde_CLI object after * calling init(), or don't call init() statically. * * @static */ function init() { /* Run constructor now because it requires $_SERVER['SERVER_NAME'] to * be empty if called with a CGI SAPI. */ $cli = &Horde_CLI::singleton(); @set_time_limit(0); ob_implicit_flush(true); ini_set('html_errors', false); $_SERVER['HTTP_HOST'] = '127.0.0.1'; $_SERVER['SERVER_NAME'] = '127.0.0.1'; $_SERVER['SERVER_PORT'] = ''; $_SERVER['REMOTE_ADDR'] = ''; $_SERVER['PHP_SELF'] = isset($argv) ? $argv[0] : ''; if (!defined('STDIN')) { define('STDIN', fopen('php://stdin', 'r')); } if (!defined('STDOUT')) { define('STDOUT', fopen('php://stdout', 'r')); } if (!defined('STDERR')) { define('STDERR', fopen('php://stderr', 'r')); } } /** * Make sure we're being called from the command line, and not via * the web. * * @static * * @return boolean True if we are, false otherwise. */ function runningFromCLI() { if (PHP_SAPI == 'cli') { return true; } else { return (PHP_SAPI == 'cgi' || PHP_SAPI == 'cgi-fcgi') && empty($_SERVER['SERVER_NAME']); } } /** * Destroys any session on script end. */ function _shutdown() { @session_destroy(); } } * http://uruds.gateway.bg/zeos/ * * $Horde: framework/Compress/Compress/dbx.php,v 1.3.12.13 2009-01-06 15:22:59 jan Exp $ * * Copyright 2003-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 Jan Schneider * @since Horde 3.0 * @package Horde_Compress */ class Horde_Compress_dbx extends Horde_Compress { /** * TODO * * @var array */ var $_mails = array(); /** * TODO * * @var array */ var $_tmp = array(); /** * Decompresses a DBX file and gets information from it. * * @param string $data The dbx file data. * @param array $params Any * * @return mixed The requested data. */ function decompress(&$data, $params = null) { $this->_mails = array(); $this->_tmp = array(); $position = 0xC4; $header_info = unpack('Lposition/LDataLength/nHeaderLength/nFlagCount', substr($data, $position, 12)); $position += 12; // Go to the first table offest and process it. if ($header_info['position'] > 0) { $position = 0x30; $buf = unpack('Lposition', substr($data, $position, 4)); $position = $buf['position']; $result = $this->_readIndex($data, $position); if (is_a($result, 'PEAR_Error')) { return $result; } } return $this->_mails; } /** * Returns a null-terminated string from the specified data. * * @access private * * @param string &$buf TODO * @param integer $pos TODO * * @return string TODO */ function _readString(&$buf, $pos) { if ($len = strpos(substr($buf, $pos), chr(0))) { return substr($buf, $pos, $len); } return ''; } /** * TODO * * @access private * * @param string &$data TODO * @param integer $position TODO * * @return string TODO */ function _readMessage(&$data, $position) { $msg = ''; $part = 0; if ($position > 0) { $IndexItemsCount = array_pop(unpack('S', substr($data, 0xC4, 4))); if ($IndexItemsCount > 0) { while ($position < strlen($data)) { $part++; $s = substr($data, $position, 528); if (strlen($s) == 0) { break; } $msg_item = unpack('LFilePos/LUnknown/LItemSize/LNextItem/a512Content', $s); if ($msg_item['FilePos'] != $position) { return PEAR::raiseError(_("Invalid file format")); } $position += 528; $msg .= substr($msg_item['Content'], 0, $msg_item['ItemSize']); $position = $msg_item['NextItem']; if ($position == 0) { break; } } } } return $msg; } /** * TODO * * @access private * * @param string &$data TODO * @param integer $position TODO * * @return array TODO */ function _readMessageInfo(&$data, $position) { $message_info = array(); $msg_header = unpack('Lposition/LDataLength/SHeaderLength/SFlagCount', substr($data, $position, 12)); if ($msg_header['position'] != $position) { return PEAR::raiseError(_("Invalid file format")); } $position += 12; $message_info['HeaderPosition'] = $msg_header['position']; $flags = $msg_header['FlagCount'] & 0xFF; $DataSize = $msg_header['DataLength'] - $flags * 4; $size = 4 * $flags; $FlagsBuffer = substr($data, $position, $size); $position += $size; $size = $DataSize; $DataBuffer = substr($data, $position, $size); $position += $size; $message_info = array(); $flag_array = array( 0x1 => 'MsgFlags', 0x2 => 'Sent', 0x4 => 'position', 0x7 => 'MessageID', 0x8 => 'Subject', 0x9 => 'From_reply', 0xA => 'References', 0xB => 'Newsgroup', 0xD => 'From', 0xE => 'Reply_To', 0x12 => 'Received', 0x13 => 'Receipt', 0x1A => 'Account', 0x1B => 'AccountID', 0x80 => 'Msg', 0x81 => 'MsgFlags', 0x84 => 'position', 0x91 => 'size', ); /* Process flags */ for ($i = 0; $i < $flags; $i++) { $pos = 0; $f = array_pop(unpack('L', substr($FlagsBuffer, $i * 4, 4))); $mask = $f & 0xFF; switch ($mask) { case 0x1: $pos = $pos + ($f >> 8); $message_info['MsgFlags'] = array_pop(unpack('C', substr($DataBuffer, $pos++, 1))); $message_info['MsgFlags'] += array_pop(unpack('C', substr($DataBuffer, $pos++, 1))) * 256; $message_info['MsgFlags'] += array_pop(unpack('C', substr($DataBuffer, $pos, 1))) * 65536; break; case 0x2: case 0x4: $pos += array_pop(unpack('L', substr($FlagsBuffer, $i * 4, 4))) >> 8; $message_info[$flag_array[$mask]] = array_pop(unpack('L', substr($DataBuffer, $pos, 4))); break; case 0x7: case 0x8: case 0x9: case 0xA: case 0xB: case 0xD: case 0xE: case 0x13: case 0x1A: $pos += array_pop(unpack('L', substr($FlagsBuffer, $i * 4, 4))) >> 8; $message_info[$flag_array[$mask]] = $this->_readString($DataBuffer, $pos); break; case 0x12: $pos += array_pop(unpack('L', substr($FlagsBuffer, $i * 4, 4))) >> 8; $message_info['Received'] = array_pop(unpack('L', substr($DataBuffer, $pos, 4))); break; case 0x1B: $pos += array_pop(unpack('L', substr($FlagsBuffer, $i * 4, 4))) >> 8; $message_info['AccountID'] = intval($this->_readString($DataBuffer, $pos)); break; case 0x80: case 0x81: case 0x84: case 0x91: $message_info[$flag_array[$mask]] = array_pop(unpack('L', substr($FlagsBuffer, $i * 4, 4))) >> 8; break; } } return $message_info; } /** * TODO * * @access private * * @param string &$data TODO * @param integer $position TODO */ function _readIndex(&$data, $position) { $index_header = unpack('LFilePos/LUnknown1/LPrevIndex/LNextIndex/LCount/LUnknown', substr($data, $position, 24)); if ($index_header['FilePos'] != $position) { return PEAR::raiseError(_("Invalid file format")); } // Push it into list of processed items. $this->_tmp[$position] = true; if (($index_header['NextIndex'] > 0) && empty($this->_tmp[$index_header['NextIndex']])) { $this->_readIndex($data, $index_header['NextIndex']); } if (($index_header['PrevIndex'] > 0) && empty($this->_tmp[$index_header['PrevIndex']])) { $this->_readIndex($data, $index_header['PrevIndex']); } $position += 24; $icount = $index_header['Count'] >> 8; if ($icount > 0) { $buf = substr($data, $position, 12 * $icount); for ($i = 0; $i < $icount; $i++) { $hdr_buf = substr($buf, $i * 12, 12); $IndexItem = unpack('LHeaderPos/LChildIndex/LUnknown', $hdr_buf); if ($IndexItem['HeaderPos'] > 0) { $mail['info'] = $this->_readMessageInfo($data, $IndexItem['HeaderPos']); $mail['content'] = $this->_readMessage($data, $mail['info']['position']); $this->_mails[] = $mail; } if (($IndexItem['ChildIndex'] > 0) && empty($this->_tmp[$IndexItem['ChildIndex']])) { $this->_readIndex($fp, $IndexItem['ChildIndex']); } } } } } * @author Michael Slusarz * @since Horde 3.0 * @package Horde_Compress */ class Horde_Compress_gzip extends Horde_Compress { /** * Gzip file flags. * * @var array */ var $_flags = array( 'FTEXT' => 0x01, 'FHCRC' => 0x02, 'FEXTRA' => 0x04, 'FNAME' => 0x08, 'FCOMMENT' => 0x10 ); /** * Decompress a gzip file and get information from it. * * @param string &$data The tar file data. * @param array $params The parameter array (Unused). * * @return string The uncompressed data. */ function decompress(&$data, $params = array()) { /* If gzip is not compiled into PHP, return now. */ if (!Util::extensionExists('zlib')) { return PEAR::raiseError(_("This server can't uncompress zip and gzip files.")); } /* Gzipped File - decompress it first. */ $position = 0; $info = @unpack('CCM/CFLG/VTime/CXFL/COS', substr($data, $position + 2)); if (!$info) { return PEAR::raiseError(_("Unable to decompress data.")); } $position += 10; if ($info['FLG'] & $this->_flags['FEXTRA']) { $XLEN = unpack('vLength', substr($data, $position + 0, 2)); $XLEN = $XLEN['Length']; $position += $XLEN + 2; } if ($info['FLG'] & $this->_flags['FNAME']) { $filenamePos = strpos($data, "\x0", $position); $filename = substr($data, $position, $filenamePos - $position); $position = $filenamePos + 1; } if ($info['FLG'] & $this->_flags['FCOMMENT']) { $commentPos = strpos($data, "\x0", $position); $comment = substr($data, $position, $commentPos - $position); $position = $commentPos + 1; } if ($info['FLG'] & $this->_flags['FHCRC']) { $hcrc = unpack('vCRC', substr($data, $position + 0, 2)); $hcrc = $hcrc['CRC']; $position += 2; } $result = @gzinflate(substr($data, $position, strlen($data) - $position)); if (empty($result)) { return PEAR::raiseError(_("Unable to decompress data.")); } return $result; } } * @author Michael Slusarz * @since Horde 3.0 * @package Horde_Compress */ class Horde_Compress_tar extends Horde_Compress { /** * Tar file types. * * @var array */ var $_types = array( 0x0 => 'Unix file', 0x30 => 'File', 0x31 => 'Link', 0x32 => 'Symbolic link', 0x33 => 'Character special file', 0x34 => 'Block special file', 0x35 => 'Directory', 0x36 => 'FIFO special file', 0x37 => 'Contiguous file' ); /** * Tar file flags. * * @var array */ var $_flags = array( 'FTEXT' => 0x01, 'FHCRC' => 0x02, 'FEXTRA' => 0x04, 'FNAME' => 0x08, 'FCOMMENT' => 0x10 ); /** * Decompress a tar file and get information from it. * * @param string &$data The tar file data. * @param array $params The parameter array (Unused). * * @return array The requested data or PEAR_Error on error. *
     * KEY: Position in the array
     * VALUES: 'attr'  --  File attributes
     *         'data'  --  Raw file contents
     *         'date'  --  File modification time
     *         'name'  --  Filename
     *         'size'  --  Original file size
     *         'type'  --  File type
     * 
*/ function decompress(&$data, $params = array()) { $position = 0; $return_array = array(); while ($position < strlen($data)) { $info = @unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/Ctypeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor", substr($data, $position)); if (!$info) { return PEAR::raiseError(_("Unable to decompress data.")); } $position += 512; $contents = substr($data, $position, octdec($info['size'])); $position += ceil(octdec($info['size']) / 512) * 512; if ($info['filename']) { $file = array( 'attr' => null, 'data' => null, 'date' => octdec($info['mtime']), 'name' => trim($info['filename']), 'size' => octdec($info['size']), 'type' => isset($this->_types[$info['typeflag']]) ? $this->_types[$info['typeflag']] : null ); if (($info['typeflag'] == 0) || ($info['typeflag'] == 0x30) || ($info['typeflag'] == 0x35)) { /* File or folder. */ $file['data'] = $contents; $mode = hexdec(substr($info['mode'], 4, 3)); $file['attr'] = (($info['typeflag'] == 0x35) ? 'd' : '-') . (($mode & 0x400) ? 'r' : '-') . (($mode & 0x200) ? 'w' : '-') . (($mode & 0x100) ? 'x' : '-') . (($mode & 0x040) ? 'r' : '-') . (($mode & 0x020) ? 'w' : '-') . (($mode & 0x010) ? 'x' : '-') . (($mode & 0x004) ? 'r' : '-') . (($mode & 0x002) ? 'w' : '-') . (($mode & 0x001) ? 'x' : '-'); } else { /* Some other type. */ } $return_array[] = $file; } } return $return_array; } } * Original design by: * Thomas Boll , Mark Simpson * * $Horde: framework/Compress/Compress/tnef.php,v 1.6.12.13 2009-01-06 15:22:59 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 Jan Schneider * @author Michael Slusarz * @since Horde 3.0 * @package Horde_Compress */ class Horde_Compress_tnef extends Horde_Compress { /** * Decompress the data. * * @param string $data The data to decompress. * @param array $params An array of arguments needed to decompress the * data. * * @return mixed The decompressed data. * Returns a PEAR_Error object on error. */ function decompress($data, $params = array()) { $out = array(); if ($this->_geti($data, 32) == TNEF_SIGNATURE) { $this->_geti($data, 16); while (strlen($data) > 0) { switch ($this->_geti($data, 8)) { case TNEF_LVL_MESSAGE: $this->_decodeMessage($data); break; case TNEF_LVL_ATTACHMENT: $this->_decodeAttachment($data, $out); break; } } } return array_reverse($out); } /** * TODO * * @access private * * @param string &$data The data string. * @param integer $bits How many bits to retrieve. * * @return TODO */ function _getx(&$data, $bits) { $value = null; if (strlen($data) >= $bits) { $value = substr($data, 0, $bits); $data = substr_replace($data, '', 0, $bits); } return $value; } /** * TODO * * @access private * * @param string &$data The data string. * @param integer $bits How many bits to retrieve. * * @return TODO */ function _geti(&$data, $bits) { $bytes = $bits / 8; $value = null; if (strlen($data) >= $bytes) { $value = ord($data[0]); if ($bytes >= 2) { $value += (ord($data[1]) << 8); } if ($bytes >= 4) { $value += (ord($data[2]) << 16) + (ord($data[3]) << 24); } $data = substr_replace($data, '', 0, $bytes); } return $value; } /** * TODO * * @access private * * @param string &$data The data string. * @param string $attribute TODO */ function _decodeAttribute(&$data, $attribute) { /* Data. */ $this->_getx($data, $this->_geti($data, 32)); /* Checksum. */ $this->_geti($data, 16); } /** * TODO * * @access private * * @param string $data The data string. * @param array &$attachment_data TODO */ function _extractMapiAttributes($data, &$attachment_data) { /* Number of attributes. */ $number = $this->_geti($data, 32); while ((strlen($data) > 0) && $number--) { $have_mval = false; $num_mval = 1; $named_id = $value = null; $attr_type = $this->_geti($data, 16); $attr_name = $this->_geti($data, 16); if (($attr_type & TNEF_MAPI_MV_FLAG) != 0) { $have_mval = true; $attr_type = $attr_type & ~TNEF_MAPI_MV_FLAG; } if (($attr_name >= 0x8000) && ($attr_name < 0xFFFE)) { $this->_getx($data, 16); $named_type = $this->_geti($data, 32); switch ($named_type) { case TNEF_MAPI_NAMED_TYPE_ID: $named_id = $this->_geti($data, 32); $attr_name = $named_id; break; case TNEF_MAPI_NAMED_TYPE_STRING: $attr_name = 0x9999; $idlen = $this->_geti($data, 32); $datalen = $idlen + ((4 - ($idlen % 4)) % 4); $named_id = substr($this->_getx($data, $datalen), 0, $idlen); break; } } if ($have_mval) { $num_mval = $this->_geti($data, 32); } switch ($attr_type) { case TNEF_MAPI_SHORT: $value = $this->_geti($data, 16); break; case TNEF_MAPI_INT: case TNEF_MAPI_BOOLEAN: for ($i = 0; $i < $num_mval; $i++) { $value = $this->_geti($data, 32); } break; case TNEF_MAPI_FLOAT: case TNEF_MAPI_ERROR: $value = $this->_getx($data, 4); break; case TNEF_MAPI_DOUBLE: case TNEF_MAPI_APPTIME: case TNEF_MAPI_CURRENCY: case TNEF_MAPI_INT8BYTE: case TNEF_MAPI_SYSTIME: $value = $this->_getx($data, 8); break; case TNEF_MAPI_STRING: case TNEF_MAPI_UNICODE_STRING: case TNEF_MAPI_BINARY: case TNEF_MAPI_OBJECT: $num_vals = ($have_mval) ? $num_mval : $this->_geti($data, 32); for ($i = 0; $i < $num_vals; $i++) { $length = $this->_geti($data, 32); /* Pad to next 4 byte boundary. */ $datalen = $length + ((4 - ($length % 4)) % 4); if ($attr_type == TNEF_MAPI_STRING) { $length -= 1; } /* Read and truncate to length. */ $value = substr($this->_getx($data, $datalen), 0, $length); } break; } /* Store any interesting attributes. */ switch ($attr_name) { case TNEF_MAPI_ATTACH_LONG_FILENAME: /* Used in preference to AFILENAME value. */ $attachment_data[0]['name'] = preg_replace('/.*[\/](.*)$/', '\1', $value); $attachment_data[0]['name'] = str_replace("\0", '', $attachment_data[0]['name']); break; case TNEF_MAPI_ATTACH_MIME_TAG: /* Is this ever set, and what is format? */ $attachment_data[0]['type'] = preg_replace('/^(.*)\/.*/', '\1', $value); $attachment_data[0]['subtype'] = preg_replace('/.*\/(.*)$/', '\1', $value); $attachment_data[0]['subtype'] = str_replace("\0", '', $attachment_data[0]['subtype']); break; } } } /** * TODO * * @access private * * @param string &$data The data string. */ function _decodeMessage(&$data) { $this->_decodeAttribute($data, $this->_geti($data, 32)); } /** * TODO * * @access private * * @param string &$data The data string. * @param array &$attachment_data TODO */ function _decodeAttachment(&$data, &$attachment_data) { $attribute = $this->_geti($data, 32); switch ($attribute) { case TNEF_ARENDDATA: /* Marks start of new attachment. */ $this->_getx($data, $this->_geti($data, 32)); /* Checksum */ $this->_geti($data, 16); /* Add a new default data block to hold details of this attachment. Reverse order is easier to handle later! */ array_unshift($attachment_data, array('type' => 'application', 'subtype' => 'octet-stream', 'name' => 'unknown', 'stream' => '')); break; case TNEF_AFILENAME: /* Strip path. */ $attachment_data[0]['name'] = preg_replace('/.*[\/](.*)$/', '\1', $this->_getx($data, $this->_geti($data, 32))); $attachment_data[0]['name'] = str_replace("\0", '', $attachment_data[0]['name']); /* Checksum */ $this->_geti($data, 16); break; case TNEF_ATTACHDATA: /* The attachment itself. */ $length = $this->_geti($data, 32); $attachment_data[0]['size'] = $length; $attachment_data[0]['stream'] = $this->_getx($data, $length); /* Checksum */ $this->_geti($data, 16); break; case TNEF_AMAPIATTRS: $length = $this->_geti($data, 32); $value = $this->_getx($data, $length); /* Checksum */ $this->_geti($data, 16); $this->_extractMapiAttributes($value, $attachment_data); break; default: $this->_decodeAttribute($data, $attribute); } } } * http://www.zend.com/codex.php?id=535&single=1 * * Deins125 * http://www.zend.com/codex.php?id=470&single=1 * * The ZIP compression date code is partially based on code from * Peter Listiak * * Copyright 2000-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 * @author Michael Cochrane * @author Michael Slusarz * @package Horde_Compress */ /** For decompress(), return a list of files/information about the zipfile. */ define('HORDE_COMPRESS_ZIP_LIST', 1); /** For decompress(), return the data for an individual file in the zipfile. */ define('HORDE_COMPRESS_ZIP_DATA', 2); /** * The Horde_Compress_zip class allows ZIP files to be created and read. * * @author Chuck Hagenbuch * @author Michael Cochrane * @author Michael Slusarz * @since Horde 3.0 * @package Horde_Compress */ class Horde_Compress_zip extends Horde_Compress { /** * ZIP compression methods. * * @var array */ var $_methods = array( 0x0 => 'None', 0x1 => 'Shrunk', 0x2 => 'Super Fast', 0x3 => 'Fast', 0x4 => 'Normal', 0x5 => 'Maximum', 0x6 => 'Imploded', 0x8 => 'Deflated' ); /** * Beginning of central directory record. * * @var string */ var $_ctrlDirHeader = "\x50\x4b\x01\x02"; /** * End of central directory record. * * @var string */ var $_ctrlDirEnd = "\x50\x4b\x05\x06\x00\x00\x00\x00"; /** * Beginning of file contents. * * @var string */ var $_fileHeader = "\x50\x4b\x03\x04"; /** * Create a ZIP compressed file from an array of file data. * * @param array $data The data to compress. *
     * Requires an array of arrays - each subarray should contain the
     * following fields:
     * 'data' (string)   --  The data to compress.
     * 'name' (string)   --  The pathname to the file.
     * 'time' (integer)  --  [optional] The timestamp to use for the file.
     * 
* @param array $params The parameter array (unused). * * @return string The ZIP file. */ function compress($data, $params = array()) { $contents = ''; $ctrldir = array(); reset($data); while (list(, $val) = each($data)) { $this->_addToZIPFile($val, $contents, $ctrldir); } return $this->_createZIPFile($contents, $ctrldir); } /** * Decompress a ZIP file and get information from it. * * @param string $data The zipfile data. * @param array $params The parameter array. *
     * The following parameters are REQUIRED:
     * 'action' (integer)  =>  The action to take on the data.  Either
     *                         HORDE_COMPRESS_ZIP_LIST or
     *                         HORDE_COMPRESS_ZIP_DATA.
     *
     * The following parameters are REQUIRED for HORDE_COMPRESS_ZIP_DATA also:
     * 'info' (array)   =>  The zipfile list.
     * 'key' (integer)  =>  The position of the file in the archive list.
     * 
* * @return mixed The requested data. */ function decompress($data, $params) { if (isset($params['action'])) { if ($params['action'] == HORDE_COMPRESS_ZIP_LIST) { return $this->_getZipInfo($data); } elseif ($params['action'] == HORDE_COMPRESS_ZIP_DATA) { // TODO: Check for parameters. return $this->_getZipData($data, $params['info'], $params['key']); } else { return PEAR::raiseError(_("Incorrect action code given."), 'horde.error'); } } return PEAR::raiseError(_("You must specify what action to perform."), 'horde.error'); } /** * Get the list of files/data from the zip archive. * * @access private * * @param string $data The zipfile data. * * @return array KEY: Position in zipfile * VALUES: 'attr' -- File attributes * 'crc' -- CRC checksum * 'csize' -- Compressed file size * 'date' -- File modification time * 'name' -- Filename * 'method' -- Compression method * 'size' -- Original file size * 'type' -- File type */ function _getZipInfo($data) { $entries = array(); /* Get details from Central directory structure. */ $fhStart = strpos($data, $this->_ctrlDirHeader); do { if (strlen($data) < $fhStart + 31) { return PEAR::raiseError(_("Invalid ZIP data")); } $info = unpack('vMethod/VTime/VCRC32/VCompressed/VUncompressed/vLength', substr($data, $fhStart + 10, 20)); $name = substr($data, $fhStart + 46, $info['Length']); $entries[$name] = array( 'attr' => null, 'crc' => sprintf("%08s", dechex($info['CRC32'])), 'csize' => $info['Compressed'], 'date' => null, '_dataStart' => null, 'name' => $name, 'method' => $this->_methods[$info['Method']], '_method' => $info['Method'], 'size' => $info['Uncompressed'], 'type' => null ); $entries[$name]['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) + 1980)); if (strlen($data) < $fhStart + 43) { return PEAR::raiseError(_("Invalid ZIP data")); } $info = unpack('vInternal/VExternal', substr($data, $fhStart + 36, 6)); $entries[$name]['type'] = ($info['Internal'] & 0x01) ? 'text' : 'binary'; $entries[$name]['attr'] = (($info['External'] & 0x10) ? 'D' : '-') . (($info['External'] & 0x20) ? 'A' : '-') . (($info['External'] & 0x03) ? 'S' : '-') . (($info['External'] & 0x02) ? 'H' : '-') . (($info['External'] & 0x01) ? 'R' : '-'); } while (($fhStart = strpos($data, $this->_ctrlDirHeader, $fhStart + 46)) !== false); /* Get details from local file header. */ $fhStart = strpos($data, $this->_fileHeader); do { if (strlen($data) < $fhStart + 34) { return PEAR::raiseError(_("Invalid ZIP data")); } $info = unpack('vMethod/VTime/VCRC32/VCompressed/VUncompressed/vLength/vExtraLength', substr($data, $fhStart + 8, 25)); $name = substr($data, $fhStart + 30, $info['Length']); $entries[$name]['_dataStart'] = $fhStart + 30 + $info['Length'] + $info['ExtraLength']; } while (strlen($data) > $fhStart + 30 + $info['Length'] && ($fhStart = strpos($data, $this->_fileHeader, $fhStart + 30 + $info['Length'])) !== false); return array_values($entries); } /** * Returns the data for a specific archived file. * * @access private * * @param string $data The zip archive contents. * @param array $info The information array from _getZipInfo(). * @param integer $key The position of the file in the archive. * * @return string The file data. */ function _getZipData($data, $info, $key) { if (($info[$key]['_method'] == 0x8) && Util::extensionExists('zlib')) { /* If the file has been deflated, and zlib is installed, then inflate the data again. */ return @gzinflate(substr($data, $info[$key]['_dataStart'], $info[$key]['csize'])); } elseif ($info[$key]['_method'] == 0x0) { /* Files that aren't compressed. */ return substr($data, $info[$key]['_dataStart'], $info[$key]['csize']); } return ''; } /** * Checks to see if the data is a valid ZIP file. * * @param string $data The ZIP file data. * * @return boolean True if valid, false if invalid. */ function checkZipData($data) { return (strpos($data, $this->_fileHeader) !== false); } /** * Converts a UNIX timestamp to a 4-byte DOS date and time format * (date in high 2-bytes, time in low 2-bytes allowing magnitude * comparison). * * @access private * * @param integer $unixtime The current UNIX timestamp. * * @return integer The current date in a 4-byte DOS format. */ function _unix2DOSTime($unixtime = null) { $timearray = (is_null($unixtime)) ? getdate() : getdate($unixtime); if ($timearray['year'] < 1980) { $timearray['year'] = 1980; $timearray['mon'] = 1; $timearray['mday'] = 1; $timearray['hours'] = 0; $timearray['minutes'] = 0; $timearray['seconds'] = 0; } return (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) | ($timearray['hours'] << 11) | ($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1); } /** * Adds a "file" to the ZIP archive. * * @access private * * @param array $file See Horde_Compress_zip::createZipFile(). * @param string &$contents The zip data. * @param array &$ctrldir An array of central directory information. * * @return array The updated value of $ctrldir. */ function _addToZIPFile($file, &$contents, &$ctrldir) { $data = $file['data']; $name = str_replace('\\', '/', $file['name']); /* See if time/date information has been provided. */ $ftime = (isset($file['time'])) ? $file['time'] : null; /* Get the hex time. */ $dtime = dechex($this->_unix2DosTime($ftime)); $hexdtime = chr(hexdec($dtime[6] . $dtime[7])) . chr(hexdec($dtime[4] . $dtime[5])) . chr(hexdec($dtime[2] . $dtime[3])) . chr(hexdec($dtime[0] . $dtime[1])); /* "Local file header" segment. */ $unc_len = strlen($data); $crc = crc32($data); $zdata = gzcompress($data); $zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); $c_len = strlen($zdata); $old_offset = strlen($contents); /* Common data for the two entries. */ $common = "\x14\x00" . /* Version needed to extract. */ "\x00\x00" . /* General purpose bit flag. */ "\x08\x00" . /* Compression method. */ $hexdtime . /* Last modification time/date. */ pack('V', $crc) . /* CRC 32 information. */ pack('V', $c_len) . /* Compressed filesize. */ pack('V', $unc_len) . /* Uncompressed filesize. */ pack('v', strlen($name)) . /* Length of filename. */ pack('v', 0); /* Extra field length. */ /* Add this entry to zip data. */ $contents .= $this->_fileHeader . /* Begin creating the ZIP data. */ $common . /* Common data. */ $name . /* File name. */ $zdata; /* "File data" segment. */ /* Add to central directory record. */ $cdrec = $this->_ctrlDirHeader . "\x00\x00" . /* Version made by. */ $common . /* Common data. */ pack('v', 0) . /* File comment length. */ pack('v', 0) . /* Disk number start. */ pack('v', 0) . /* Internal file attributes. */ pack('V', 32) . /* External file attributes - 'archive' bit set. */ pack('V', $old_offset) . /* Relative offset of local header. */ $name; /* File name. */ // Save to central directory array. */ $ctrldir[] = $cdrec; } /** * Creates the ZIP file. * Official ZIP file format: http://www.pkware.com/appnote.txt * * @access private * * @return string The ZIP file. */ function _createZIPFile($contents, $ctrlDir) { $dir = implode('', $ctrlDir); return $contents . $dir . $this->_ctrlDirEnd . /* Total # of entries "on this disk". */ pack('v', count($ctrlDir)) . /* Total # of entries overall. */ pack('v', count($ctrlDir)) . /* Size of central directory. */ pack('V', strlen($dir)) . /* Offset to start of central dir. */ pack('V', strlen($contents)) . /* ZIP file comment length. */ "\x00\x00"; } } * @since Horde 3.0 * @package Horde_Compress */ class Horde_Compress { /** * Attempts to return a concrete Horde_Compress instance based on $driver. * * @param mixed $driver The type of concrete Horde_Compress subclass to * return. If $driver is an array, then we will look * in $driver[0]/lib/Compress/ for the subclass * implementation named $driver[1].php. * @param array $params A hash containing any additional configuration or * parameters a subclass might need. * * @return Horde_Compress The newly created concrete Horde_Compress * instance, or false on an error. */ function &factory($driver, $params = array()) { if (is_array($driver)) { list($app, $driver) = $driver; } $driver = basename($driver); $class = 'Horde_Compress_' . $driver; if (!class_exists($class)) { if (!empty($app)) { include_once $app . '/lib/Compress/' . $driver . '.php'; } else { include_once 'Horde/Compress/' . $driver . '.php'; } } if (class_exists($class)) { $compress = new $class($params); } else { $compress = false; } return $compress; } /** * Attempts to return a reference to a concrete Horde_Compress instance * based on $driver. It will only create a new instance if no * Horde_Compress instance with the same parameters currently exists. * * This method must be invoked as: * $var = &Horde_Compress::singleton(); * * @param mixed $driver See Horde_Compress::factory(). * @param array $params See Horde_Compress::factory(). * * @return Horde_Compress The concrete Horde_Compress reference, or false * on an error. */ function &singleton($driver, $params = array()) { static $instances = array(); $signature = md5(serialize(array($driver, $params))); if (!isset($instances[$signature])) { $instances[$signature] = &Horde_Compress::factory($driver, $params); } return $instances[$signature]; } /** * Constructor. * * @param array $params Parameter array. */ function Horde_Compress($params = array()) { } /** * Compress the data. * * @param string $data The data to compress. * @param array $params An array of arguments needed to compress the data. * * @return mixed The compressed data. * Returns PEAR_Error object on error. */ function compress($data, $params = array()) { return PEAR::raiseError('Unsupported'); } /** * Decompress the data. * * @param string $data The data to decompress. * @param array $params An array of arguments needed to decompress the * data. * * @return array The decompressed data. * Returns PEAR_Error object on error. */ function decompress($data, $params = array()) { return PEAR::raiseError('Unsupported'); } } * @since Horde 3.0 * @package Horde_Framework */ class Horde_Config { /** * The name of the configured application. * * @var string */ var $_app; /** * The XML tree of the configuration file traversed to an * associative array. * * @var array */ var $_xmlConfigTree = null; /** * The content of the generated configuration file. * * @var string */ var $_phpConfig; /** * The content of the old configuration file. * * @var string */ var $_oldConfig; /** * The manual configuration in front of the generated configuration. * * @var string */ var $_preConfig; /** * The manual configuration after the generated configuration. * * @var string */ var $_postConfig; /** * The current $conf array of the configured application. * * @var array */ var $_currentConfig = array(); /** * The CVS version tag of the conf.xml file which will be copied into the * conf.php file. * * @var string */ var $_versionTag = ''; /** * The line marking the begin of the generated configuration. * * @var string */ var $_configBegin = "/* CONFIG START. DO NOT CHANGE ANYTHING IN OR AFTER THIS LINE. */\n"; /** * The line marking the end of the generated configuration. * * @var string */ var $_configEnd = "/* CONFIG END. DO NOT CHANGE ANYTHING IN OR BEFORE THIS LINE. */\n"; /** * Constructor. * * @param string $app The name of the application to be configured. */ function Horde_Config($app) { $this->_app = $app; } /** * Reads the application's conf.xml file and builds an associative array * from its XML tree. * * @param array $custom_conf Any settings that shall be included in the * generated configuration. * * @return array An associative array representing the configuration tree. */ function readXMLConfig($custom_conf = null) { if (is_null($this->_xmlConfigTree) || $custom_conf) { require_once 'Horde/Text.php'; global $registry; $path = $registry->get('fileroot', $this->_app) . '/config'; if ($custom_conf) { $this->_currentConfig = $custom_conf; } else { /* Fetch the current conf.php contents. */ @eval($this->getPHPConfig()); if (isset($conf)) { $this->_currentConfig = $conf; } } /* Load the DOM object. */ $doc = Horde_DOM_Document::factory(array('filename' => $path . '/conf.xml')); /* Check if there is a CVS version tag and store it. */ $node = $doc->first_child(); while (!empty($node)) { if ($node->type == XML_COMMENT_NODE) { if (preg_match('/\$.*?conf\.xml,v .*? .*\$/', $node->node_value(), $match)) { $this->_versionTag = $match[0] . "\n"; break; } } $node = $node->next_sibling(); } /* Parse the config file. */ $this->_xmlConfigTree = array(); $root = $doc->root(); if ($root->has_child_nodes()) { $this->_parseLevel($this->_xmlConfigTree, $root->child_nodes(), ''); } } return $this->_xmlConfigTree; } /** * Returns the file content of the current configuration file. * * @return string The unparsed configuration file content. */ function getPHPConfig() { if (is_null($this->_oldConfig)) { global $registry; $path = $registry->get('fileroot', $this->_app) . '/config'; if (file_exists($path . '/conf.php')) { $this->_oldConfig = file_get_contents($path . '/conf.php'); if (!empty($this->_oldConfig)) { $this->_oldConfig = preg_replace('/<\?php\n?/', '', $this->_oldConfig); $pos = strpos($this->_oldConfig, $this->_configBegin); if ($pos !== false) { $this->_preConfig = substr($this->_oldConfig, 0, $pos); $this->_oldConfig = substr($this->_oldConfig, $pos); } $pos = strpos($this->_oldConfig, $this->_configEnd); if ($pos !== false) { $this->_postConfig = substr($this->_oldConfig, $pos + strlen($this->_configEnd)); $this->_oldConfig = substr($this->_oldConfig, 0, $pos); } } } else { $this->_oldConfig = ''; } } return $this->_oldConfig; } /** * Generates the content of the application's configuration file. * * @param Variables $formvars The processed configuration form data. * @param array $custom_conf Any settings that shall be included in the * generated configuration. * * @return string The content of the generated configuration file. */ function generatePHPConfig($formvars, $custom_conf = null) { $this->readXMLConfig($custom_conf); $this->getPHPConfig(); $this->_phpConfig = "_phpConfig .= $this->_preConfig; $this->_phpConfig .= $this->_configBegin; if (!empty($this->_versionTag)) { $this->_phpConfig .= '// ' . $this->_versionTag; } $this->_generatePHPConfig($this->_xmlConfigTree, '', $formvars); $this->_phpConfig .= $this->_configEnd; $this->_phpConfig .= $this->_postConfig; return $this->_phpConfig; } /** * Generates the configuration file items for a part of the configuration * tree. * * @access private * * @param array $section An associative array containing the part of the * traversed XML configuration tree that should be * processed. * @param string $prefix A configuration prefix determining the current * position inside the configuration file. This * prefix will be translated to keys of the $conf * array in the generated configuration file. * @param Variables $formvars The processed configuration form data. */ function _generatePHPConfig($section, $prefix, $formvars) { if (!is_array($section)) { return; } foreach ($section as $name => $configitem) { $prefixedname = empty($prefix) ? $name : $prefix . '|' . $name; $configname = str_replace('|', '__', $prefixedname); $quote = !isset($configitem['quote']) || $configitem['quote'] !== false; if ($configitem == 'placeholder') { $this->_phpConfig .= '$conf[\'' . str_replace('|', '\'][\'', $prefix) . "'] = array();\n"; } elseif (isset($configitem['switch'])) { $val = $formvars->getExists($configname, $wasset); if (!$wasset) { $val = isset($configitem['default']) ? $configitem['default'] : null; } if (isset($configitem['switch'][$val])) { $value = $val; if ($quote && $value != 'true' && $value != 'false') { $value = "'" . $value . "'"; } $this->_generatePHPConfig($configitem['switch'][$val]['fields'], $prefix, $formvars); } } elseif (isset($configitem['_type'])) { $val = $formvars->getExists($configname, $wasset); if (!$wasset) { $val = isset($configitem['default']) ? $configitem['default'] : null; } $type = $configitem['_type']; switch ($type) { case 'multienum': if (is_array($val)) { $encvals = array(); foreach ($val as $v) { $encvals[] = $this->_quote($v); } $arrayval = "'" . implode('\', \'', $encvals) . "'"; if ($arrayval == "''") { $arrayval = ''; } } else { $arrayval = ''; } $value = 'array(' . $arrayval . ')'; break; case 'boolean': if (is_bool($val)) { $value = $val ? 'true' : 'false'; } else { $value = ($val == 'on') ? 'true' : 'false'; } break; case 'stringlist': $values = explode(',', $val); if (!is_array($values)) { $value = "array('" . $this->_quote(trim($values)) . "')"; } else { $encvals = array(); foreach ($values as $v) { $encvals[] = $this->_quote(trim($v)); } $arrayval = "'" . implode('\', \'', $encvals) . "'"; if ($arrayval == "''") { $arrayval = ''; } $value = 'array(' . $arrayval . ')'; } break; case 'int': if ($val !== '') { $value = (int)$val; } break; case 'octal': $value = sprintf('0%o', octdec($val)); break; case 'header': case 'description': break; default: if ($val != '') { $value = $val; if ($quote && $value != 'true' && $value != 'false') { $value = "'" . $this->_quote($value) . "'"; } } break; } } else { $this->_generatePHPConfig($configitem, $prefixedname, $formvars); } if (isset($value)) { $this->_phpConfig .= '$conf[\'' . str_replace('__', '\'][\'', $configname) . '\'] = ' . $value . ";\n"; } unset($value); } } /** * Parses one level of the configuration XML tree into the associative * array containing the traversed configuration tree. * * @access private * * @param array &$conf The already existing array where the processed * XML tree portion should be appended to. * @param array $children An array containing the XML nodes of the level * that should be parsed. * @param string $ctx A string representing the current position * (context prefix) inside the configuration XML * file. */ function _parseLevel(&$conf, $children, $ctx) { require_once 'Horde/Text/Filter.php'; foreach ($children as $node) { if ($node->type != XML_ELEMENT_NODE) { continue; } $name = $node->get_attribute('name'); $desc = Text_Filter::filter($node->get_attribute('desc'), 'linkurls', array('callback' => 'Horde::externalUrl')); $required = !($node->get_attribute('required') == 'false'); $quote = !($node->get_attribute('quote') == 'false'); if (!empty($ctx)) { $curctx = $ctx . '|' . $name; } else { $curctx = $name; } switch ($node->tagname) { case 'configdescription': if (empty($name)) { $name = md5(uniqid(mt_rand(), true)); } $conf[$name] = array('_type' => 'description', 'desc' => Text_Filter::filter($this->_default($curctx, $this->_getNodeOnlyText($node)), 'linkurls', array('callback' => 'Horde::externalUrl'))); break; case 'configheader': if (empty($name)) { $name = md5(uniqid(mt_rand(), true)); } $conf[$name] = array('_type' => 'header', 'desc' => $this->_default($curctx, $this->_getNodeOnlyText($node))); break; case 'configswitch': $values = $this->_getSwitchValues($node, $ctx); if ($quote) { list($default, $isDefault) = $this->__default($curctx, $this->_getNodeOnlyText($node)); } else { list($default, $isDefault) = $this->__defaultRaw($curctx, $this->_getNodeOnlyText($node)); } if ($default === '') { $default = key($values); } if (is_bool($default)) { $default = $default ? 'true' : 'false'; } $conf[$name] = array('desc' => $desc, 'switch' => $values, 'default' => $default, 'is_default' => $isDefault); break; case 'configenum': $values = $this->_getEnumValues($node); if ($quote) { list($default, $isDefault) = $this->__default($curctx, $this->_getNodeOnlyText($node)); } else { list($default, $isDefault) = $this->__defaultRaw($curctx, $this->_getNodeOnlyText($node)); } if ($default === '') { $default = key($values); } if (is_bool($default)) { $default = $default ? 'true' : 'false'; } $conf[$name] = array('_type' => 'enum', 'required' => $required, 'quote' => $quote, 'values' => $values, 'desc' => $desc, 'default' => $default, 'is_default' => $isDefault); break; case 'configlist': list($default, $isDefault) = $this->__default($curctx, null); if ($default === null) { $default = $this->_getNodeOnlyText($node); } elseif (is_array($default)) { $default = implode(', ', $default); } $conf[$name] = array('_type' => 'stringlist', 'required' => $required, 'desc' => $desc, 'default' => $default, 'is_default' => $isDefault); break; case 'configmultienum': $values = $this->_getEnumValues($node); require_once 'Horde/Array.php'; list($default, $isDefault) = $this->__default($curctx, explode(',', $this->_getNodeOnlyText($node))); $conf[$name] = array('_type' => 'multienum', 'required' => $required, 'values' => $values, 'desc' => $desc, 'default' => Horde_Array::valuesToKeys($default), 'is_default' => $isDefault); break; case 'configpassword': $conf[$name] = array('_type' => 'password', 'required' => $required, 'desc' => $desc, 'default' => $this->_default($curctx, $this->_getNodeOnlyText($node)), 'is_default' => $this->_isDefault($curctx, $this->_getNodeOnlyText($node))); break; case 'configstring': $conf[$name] = array('_type' => 'text', 'required' => $required, 'desc' => $desc, 'default' => $this->_default($curctx, $this->_getNodeOnlyText($node)), 'is_default' => $this->_isDefault($curctx, $this->_getNodeOnlyText($node))); if ($conf[$name]['default'] === false) { $conf[$name]['default'] = 'false'; } elseif ($conf[$name]['default'] === true) { $conf[$name]['default'] = 'true'; } break; case 'configboolean': $default = $this->_getNodeOnlyText($node); if (empty($default) || $default === 'false') { $default = false; } else { $default = true; } $conf[$name] = array('_type' => 'boolean', 'required' => $required, 'desc' => $desc, 'default' => $this->_default($curctx, $default), 'is_default' => $this->_isDefault($curctx, $default)); break; case 'configinteger': $values = $this->_getEnumValues($node); $conf[$name] = array('_type' => 'int', 'required' => $required, 'values' => $values, 'desc' => $desc, 'default' => $this->_default($curctx, $this->_getNodeOnlyText($node)), 'is_default' => $this->_isDefault($curctx, $this->_getNodeOnlyText($node))); if ($node->get_attribute('octal') == 'true' && $conf[$name]['default'] != '') { $conf[$name]['_type'] = 'octal'; $conf[$name]['default'] = sprintf('0%o', $this->_default($curctx, octdec($this->_getNodeOnlyText($node)))); } break; case 'configphp': $conf[$name] = array('_type' => 'php', 'required' => $required, 'quote' => false, 'desc' => $desc, 'default' => $this->_defaultRaw($curctx, $this->_getNodeOnlyText($node)), 'is_default' => $this->_isDefaultRaw($curctx, $this->_getNodeOnlyText($node))); break; case 'configsecret': $conf[$name] = array('_type' => 'text', 'required' => true, 'desc' => $desc, 'default' => $this->_default($curctx, sha1(uniqid(mt_rand(), true))), 'is_default' => $this->_isDefault($curctx, $this->_getNodeOnlyText($node))); break; case 'configsql': $conf[$node->get_attribute('switchname')] = $this->_configSQL($ctx, $node); break; case 'configvfs': $conf[$node->get_attribute('switchname')] = $this->_configVFS($ctx, $node); break; case 'configsection': $conf[$name] = array(); $cur = &$conf[$name]; if ($node->has_child_nodes()) { $this->_parseLevel($cur, $node->child_nodes(), $curctx); } break; case 'configtab': $key = md5(uniqid(mt_rand(), true)); $conf[$key] = array('tab' => $name, 'desc' => $desc); if ($node->has_child_nodes()) { $this->_parseLevel($conf, $node->child_nodes(), $ctx); } break; case 'configplaceholder': $conf[md5(uniqid(mt_rand(), true))] = 'placeholder'; break; default: $conf[$name] = array(); $cur = &$conf[$name]; if ($node->has_child_nodes()) { $this->_parseLevel($cur, $node->child_nodes(), $curctx); } break; } } } /** * Returns the configuration tree for an SQL backend configuration to * replace a tag. * Subnodes will be parsed and added to both the Horde defaults and the * Custom configuration parts. * * @access private * * @param string $ctx The context of the tag. * @param DomNode $node The DomNode representation of the * tag. * @param string $switchname If DomNode is not set, the value of the * tag's switchname attribute. * * @return array An associative array with the SQL configuration tree. */ function _configSQL($ctx, $node = null, $switchname = 'driverconfig') { $persistent = array( '_type' => 'boolean', 'required' => false, 'desc' => 'Request persistent connections?', 'default' => $this->_default($ctx . '|persistent', false)); $hostspec = array( '_type' => 'text', 'required' => true, 'desc' => 'Database server/host', 'default' => $this->_default($ctx . '|hostspec', '')); $username = array( '_type' => 'text', 'required' => true, 'desc' => 'Username to connect to the database as', 'default' => $this->_default($ctx . '|username', '')); $password = array( '_type' => 'text', 'required' => false, 'desc' => 'Password to connect with', 'default' => $this->_default($ctx . '|password', '')); $database = array( '_type' => 'text', 'required' => true, 'desc' => 'Database name to use', 'default' => $this->_default($ctx . '|database', '')); $socket = array( '_type' => 'text', 'required' => false, 'desc' => 'Location of UNIX socket', 'default' => $this->_default($ctx . '|socket', '')); $port = array( '_type' => 'int', 'required' => false, 'desc' => 'Port the DB is running on, if non-standard', 'default' => $this->_default($ctx . '|port', null)); $protocol = array( 'desc' => 'How should we connect to the database?', 'default' => $this->_default($ctx . '|protocol', 'unix'), 'switch' => array( 'unix' => array( 'desc' => 'UNIX Sockets', 'fields' => array( 'socket' => $socket)), 'tcp' => array( 'desc' => 'TCP/IP', 'fields' => array( 'hostspec' => $hostspec, 'port' => $port)))); $mysql_protocol = $protocol; $mysql_protocol['switch']['tcp']['fields']['port']['default'] = $this->_default($ctx . '|port', 3306); $charset = array( '_type' => 'text', 'required' => true, 'desc' => 'Internally used charset', 'default' => $this->_default($ctx . '|charset', 'utf-8')); $ssl = array( '_type' => 'boolean', 'required' => false, 'desc' => 'Use SSL to connect to the server?', 'default' => $this->_default($ctx . '|ssl', false)); $ca = array( '_type' => 'text', 'required' => false, 'desc' => 'Certification Authority to use for SSL connections', 'default' => $this->_default($ctx . '|ca', '')); $oci8_fields = array( 'persistent' => $persistent, 'username' => $username, 'password' => $password); if (function_exists('oci_connect')) { $oci8_fields['database'] = array( '_type' => 'text', 'required' => true, 'desc' => 'Database name or Easy Connect parameter', 'default' => $this->_default($ctx . '|database', 'horde')); } else { $oci8_fields['hostspec'] = array( '_type' => 'text', 'required' => true, 'desc' => 'Database name or Easy Connect parameter', 'default' => $this->_default($ctx . '|hostspec', 'horde')); } $oci8_fields['charset'] = $charset; $read_hostspec = array( '_type' => 'text', 'required' => true, 'desc' => 'Read database server/host', 'default' => $this->_default($ctx . '|read|hostspec', '')); $read_port = array( '_type' => 'int', 'required' => false, 'desc' => 'Port the read DB is running on, if non-standard', 'default' => $this->_default($ctx . '|read|port', null)); $splitread = array( '_type' => 'boolean', 'required' => false, 'desc' => 'Split reads to a different server?', 'default' => $this->_default($ctx . '|splitread', 'false'), 'switch' => array( 'false' => array( 'desc' => 'Disabled', 'fields' => array()), 'true' => array( 'desc' => 'Enabled', 'fields' => array( 'read' => array( 'persistent' => $persistent, 'username' => $username, 'password' => $password, 'protocol' => $mysql_protocol, 'database' => $database, 'charset' => $charset))))); $custom_fields = array( 'required' => true, 'desc' => 'What database backend should we use?', 'default' => $this->_default($ctx . '|phptype', 'false'), 'switch' => array( 'false' => array( 'desc' => '[None]', 'fields' => array()), 'dbase' => array( 'desc' => 'dBase', 'fields' => array( 'database' => array( '_type' => 'text', 'required' => true, 'desc' => 'Absolute path to the database file', 'default' => $this->_default($ctx . '|database', '')), 'mode' => array( '_type' => 'enum', 'desc' => 'The mode to open the file with', 'values' => array( 0 => 'Read Only', 2 => 'Read Write'), 'default' => $this->_default($ctx . '|mode', 2)), 'charset' => $charset)), 'ibase' => array( 'desc' => 'Firebird/InterBase', 'fields' => array( 'dbsyntax' => array( '_type' => 'enum', 'desc' => 'The database syntax variant to use', 'required' => false, 'values' => array( 'ibase' => 'InterBase', 'firebird' => 'Firebird'), 'default' => $this->_default($ctx . '|dbsyntax', 'firebird')), 'persistent' => $persistent, 'hostspec' => $hostspec, 'username' => $username, 'password' => $password, 'database' => $database, 'buffers' => array( '_type' => 'int', 'desc' => 'The number of database buffers to allocate', 'required' => false, 'default' => $this->_default($ctx . '|buffers', null)), 'dialect' => array( '_type' => 'int', 'desc' => 'The default SQL dialect for any statement executed within a connection.', 'required' => false, 'default' => $this->_default($ctx . '|dialect', null)), 'role' => array( '_type' => 'text', 'desc' => 'Role', 'required' => false, 'default' => $this->_default($ctx . '|role', null)), 'charset' => $charset)), 'fbsql' => array( 'desc' => 'Frontbase', 'fields' => array( 'persistent' => $persistent, 'hostspec' => $hostspec, 'username' => $username, 'password' => $password, 'database' => $database, 'charset' => $charset)), 'ifx' => array( 'desc' => 'Informix', 'fields' => array( 'persistent' => $persistent, 'username' => $username, 'password' => $password, 'database' => $database, 'charset' => $charset)), 'msql' => array( 'desc' => 'mSQL', 'fields' => array( 'persistent' => $persistent, 'hostspec' => $hostspec, 'username' => $username, 'password' => $password, 'port' => $port, 'database' => $database, 'charset' => $charset)), 'mssql' => array( 'desc' => 'MS SQL Server', 'fields' => array( 'persistent' => $persistent, 'hostspec' => $hostspec, 'username' => $username, 'password' => $password, 'port' => $port, 'database' => $database, 'charset' => $charset)), 'mysql' => array( 'desc' => 'MySQL', 'fields' => array( 'persistent' => $persistent, 'username' => $username, 'password' => $password, 'protocol' => $mysql_protocol, 'database' => $database, 'charset' => $charset, 'ssl' => $ssl, 'ca' => $ca, 'splitread' => $splitread)), 'mysqli' => array( 'desc' => 'MySQL (mysqli)', 'fields' => array( 'username' => $username, 'password' => $password, 'protocol' => $mysql_protocol, 'database' => $database, 'charset' => $charset, 'splitread' => $splitread, 'ssl' => $ssl, 'ca' => $ca )), 'oci8' => array( 'desc' => 'Oracle', 'fields' => $oci8_fields), 'odbc' => array( 'desc' => 'ODBC', 'fields' => array( 'persistent' => $persistent, 'username' => $username, 'password' => $password, 'hostspec' => array( '_type' => 'text', 'desc' => 'DSN', 'default' => $this->_default($ctx . '|hostspec', '')), 'dbsyntax' => array( '_type' => 'enum', 'desc' => 'The database syntax variant to use', 'required' => false, 'values' => array( 'sql92' => 'SQL92', 'access' => 'Access', 'db2' => 'DB2', 'solid' => 'Solid', 'navision' => 'Navision', 'mssql' => 'MS SQL Server', 'sybase' => 'Sybase', 'mysql' => 'MySQL', 'mysqli' => 'MySQL (mysqli)', ), 'default' => $this->_default($ctx . '|dbsyntax', 'sql92')), 'cursor' => array( '_type' => 'enum', 'desc' => 'Cursor type', 'quote' => false, 'required' => false, 'values' => array( 'null' => 'None', 'SQL_CUR_DEFAULT' => 'Default', 'SQL_CUR_USE_DRIVER' => 'Use Driver', 'SQL_CUR_USE_ODBC' => 'Use ODBC', 'SQL_CUR_USE_IF_NEEDED' => 'Use If Needed'), 'default' => $this->_default($ctx . '|cursor', null)), 'charset' => $charset)), 'pgsql' => array( 'desc' => 'PostgreSQL', 'fields' => array( 'persistent' => $persistent, 'username' => $username, 'password' => $password, 'protocol' => $protocol, 'database' => $database, 'charset' => $charset)), 'sqlite' => array( 'desc' => 'SQLite', 'fields' => array( 'database' => array( '_type' => 'text', 'required' => true, 'desc' => 'Absolute path to the database file', 'default' => $this->_default($ctx . '|database', '')), 'mode' => array( '_type' => 'text', 'desc' => 'The mode to open the file with', 'default' => $this->_default($ctx . '|mode', '0644')), 'charset' => $charset)), 'sybase' => array( 'desc' => 'Sybase', 'fields' => array( 'persistent' => $persistent, 'hostspec' => $hostspec, 'username' => $username, 'password' => $password, 'database' => $database, 'appname' => array( '_type' => 'text', 'desc' => 'Application Name', 'required' => false, 'default' => $this->_default($ctx . '|appname', '')), 'charset' => $charset)))); if (isset($node) && $node->get_attribute('baseconfig') == 'true') { return $custom_fields; } list($default, $isDefault) = $this->__default($ctx . '|' . (isset($node) ? $node->get_attribute('switchname') : $switchname), 'horde'); $config = array( 'desc' => 'Driver configuration', 'default' => $default, 'is_default' => $isDefault, 'switch' => array( 'horde' => array( 'desc' => 'Horde defaults', 'fields' => array()), 'custom' => array( 'desc' => 'Custom parameters', 'fields' => array( 'phptype' => $custom_fields)))); if (isset($node) && $node->has_child_nodes()) { $cur = array(); $this->_parseLevel($cur, $node->child_nodes(), $ctx); $config['switch']['horde']['fields'] = array_merge($config['switch']['horde']['fields'], $cur); $config['switch']['custom']['fields'] = array_merge($config['switch']['custom']['fields'], $cur); } return $config; } /** * Returns the configuration tree for a VFS backend configuration to * replace a tag. * Subnodes will be parsed and added to both the Horde defaults and the * Custom configuration parts. * * @access private * * @param string $ctx The context of the tag. * @param DomNode $node The DomNode representation of the * tag. * * @return array An associative array with the VFS configuration tree. */ function _configVFS($ctx, $node) { $sql = $this->_configSQL($ctx . '|params'); $default = $node->get_attribute('default'); $default = (empty($default)) ? 'none' : $default; list($default, $isDefault) = $this->__default($ctx . '|' . $node->get_attribute('switchname'), $default); $config = array( 'desc' => 'What VFS driver should we use?', 'default' => $default, 'is_default' => $isDefault, 'switch' => array( 'none' => array( 'desc' => 'None', 'fields' => array()), 'file' => array( 'desc' => 'Files on the local system', 'fields' => array( 'params' => array( 'vfsroot' => array( '_type' => 'text', 'desc' => 'Where on the real filesystem should Horde use as root of the virtual filesystem?', 'default' => $this->_default($ctx . '|params|vfsroot', '/tmp'))))), 'sql' => array( 'desc' => 'SQL database', 'fields' => array( 'params' => array( 'driverconfig' => $sql))))); if (isset($node) && $node->get_attribute('baseconfig') != 'true') { $config['switch']['horde'] = array( 'desc' => 'Horde defaults', 'fields' => array()); } $cases = $this->_getSwitchValues($node, $ctx . '|params'); foreach ($cases as $case => $fields) { if (isset($config['switch'][$case])) { $config['switch'][$case]['fields']['params'] = array_merge($config['switch'][$case]['fields']['params'], $fields['fields']); } } return $config; } /** * Returns a certain value from the current configuration array or * a default value, if not found. * * @access private * * @param string $ctx A string representing the key of the * configuration array to return. * @param mixed $default The default value to return if the key wasn't * found. * * @return mixed Either the value of the configuration array's requested * key or the default value if the key wasn't found. */ function _default($ctx, $default) { list ($ptr,) = $this->__default($ctx, $default); return $ptr; } /** * Returns whether a certain value from the current configuration array * exists or a default value will be used. * * @access private * * @param string $ctx A string representing the key of the * configuration array to return. * @param mixed $default The default value to return if the key wasn't * found. * * @return boolean Whether the default value will be used. */ function _isDefault($ctx, $default) { list (,$isDefault) = $this->__default($ctx, $default); return $isDefault; } /** * Returns a certain value from the current configuration array or a * default value, if not found, and which of the values have been * returned. * * @access private * * @param string $ctx A string representing the key of the * configuration array to return. * @param mixed $default The default value to return if the key wasn't * found. * * @return array First element: either the value of the configuration * array's requested key or the default value if the key * wasn't found. * Second element: whether the returned value was the * default value. */ function __default($ctx, $default) { $ctx = explode('|', $ctx); $ptr = $this->_currentConfig; for ($i = 0; $i < count($ctx); $i++) { if (!isset($ptr[$ctx[$i]])) { return array($default, true); } else { $ptr = $ptr[$ctx[$i]]; } } if (is_string($ptr)) { $ptr = String::convertCharset($ptr, 'iso-8859-1'); } return array($ptr, false); } /** * Returns a certain value from the current configuration file or * a default value, if not found. * It does NOT return the actual value, but the PHP expression as used * in the configuration file. * * @access private * * @param string $ctx A string representing the key of the * configuration array to return. * @param mixed $default The default value to return if the key wasn't * found. * * @return mixed Either the value of the configuration file's requested * key or the default value if the key wasn't found. */ function _defaultRaw($ctx, $default) { list ($ptr,) = $this->__defaultRaw($ctx, $default); return $ptr; } /** * Returns whether a certain value from the current configuration array * exists or a default value will be used. * * @access private * * @param string $ctx A string representing the key of the * configuration array to return. * @param mixed $default The default value to return if the key wasn't * found. * * @return boolean Whether the default value will be used. */ function _isDefaultRaw($ctx, $default) { list (,$isDefault) = $this->__defaultRaw($ctx, $default); return $isDefault; } /** * Returns a certain value from the current configuration file or * a default value, if not found, and which of the values have been * returned. * * It does NOT return the actual value, but the PHP expression as used * in the configuration file. * * @access private * * @param string $ctx A string representing the key of the * configuration array to return. * @param mixed $default The default value to return if the key wasn't * found. * * @return array First element: either the value of the configuration * array's requested key or the default value if the key * wasn't found. * Second element: whether the returned value was the * default value. */ function __defaultRaw($ctx, $default) { $ctx = explode('|', $ctx); $pattern = '/^\$conf\[\'' . implode("'\]\['", $ctx) . '\'\] = (.*);\r?$/m'; if (preg_match($pattern, $this->getPHPConfig(), $matches)) { return array($matches[1], false); } return array($default, true); } /** * Returns the content of all text node children of the specified node. * * @access private * * @param DomNode $node A DomNode object whose text node children to return. * * @return string The concatenated values of all text nodes. */ function _getNodeOnlyText($node) { $text = ''; if (!$node->has_child_nodes()) { return $node->get_content(); } foreach ($node->child_nodes() as $tnode) { if ($tnode->type == XML_TEXT_NODE) { $text .= $tnode->content; } } return trim($text); } /** * Returns an associative array containing all possible values of the * specified tag. * * The keys contain the actual enum values while the values contain their * corresponding descriptions. * * @access private * * @param DomNode $node The DomNode representation of the tag * whose values should be returned. * * @return array An associative array with all possible enum values. */ function _getEnumValues($node) { $values = array(); if (!$node->has_child_nodes()) { return array(); } foreach ($node->child_nodes() as $vnode) { if ($vnode->type == XML_ELEMENT_NODE && $vnode->tagname == 'values') { if (!$vnode->has_child_nodes()) { return array(); } foreach ($vnode->child_nodes() as $value) { if ($value->type == XML_ELEMENT_NODE) { if ($value->tagname == 'configspecial') { return $this->_handleSpecials($value); } elseif ($value->tagname == 'value') { $text = $value->get_content(); $desc = $value->get_attribute('desc'); if (!empty($desc)) { $values[$text] = $desc; } else { $values[$text] = $text; } } } } } } return $values; } /** * Returns a multidimensional associative array representing the specified * tag. * * @access private * * @param DomNode &$node The DomNode representation of the * tag to process. * * @return array An associative array representing the node. */ function _getSwitchValues(&$node, $curctx) { if (!$node->has_child_nodes()) { return array(); } $values = array(); foreach ($node->child_nodes() as $case) { if ($case->type == XML_ELEMENT_NODE) { $name = $case->get_attribute('name'); $values[$name] = array(); $values[$name]['desc'] = $case->get_attribute('desc'); $values[$name]['fields'] = array(); if ($case->has_child_nodes()) { $this->_parseLevel($values[$name]['fields'], $case->child_nodes(), $curctx); } } } return $values; } /** * Returns an associative array containing the possible values of a * tag as used inside of enum configurations. * * @access private * * @param DomNode $node The DomNode representation of the * tag. * * @return array An associative array with the possible values. */ function _handleSpecials($node) { switch ($node->get_attribute('name')) { case 'list-horde-apps': global $registry; require_once 'Horde/Array.php'; $apps = Horde_Array::valuesToKeys($registry->listApps(array('hidden', 'notoolbar', 'active'))); asort($apps); return $apps; break; case 'list-horde-languages': return array_map(create_function('$val', 'return preg_replace(array("/&#x([0-9a-f]{4});/ie", "/(&[^;]+;)/e"), array("String::convertCharset(pack(\"H*\", \"$1\"), \"ucs-2\", \"' . NLS::getCharset() . '\")", "String::convertCharset(html_entity_decode(\"$1\", ENT_COMPAT, \"iso-8859-1\"), \"iso-8859-1\", \"' . NLS::getCharset() . '\")"), $val);'), $GLOBALS['nls']['languages']); break; case 'list-blocks': require_once 'Horde/Block/Collection.php'; $collection = &Horde_Block_Collection::singleton('portal'); return $collection->getBlocksList(); case 'list-client-fields': global $registry; $f = array(); if ($registry->hasMethod('clients/getClientSource')) { $addressbook = $registry->call('clients/getClientSource'); $fields = $registry->call('clients/clientFields', array($addressbook)); if (is_a($fields, 'PEAR_Error')) { $fields = $registry->call('clients/fields', array($addressbook)); } if (!is_a($fields, 'PEAR_Error')) { foreach ($fields as $field) { $f[$field['name']] = $field['label']; } } } return $f; break; case 'list-contact-sources': global $registry; $res = $registry->call('contacts/sources'); return $res; break; } return array(); } /** * Returns the specified string with escaped single quotes * * @access private * * @param string $string A string to escape. * * @return string The specified string with single quotes being escaped. */ function _quote($string) { return str_replace("'", "\'", $string); } } /** * A Horde_Form:: form that implements a user interface for the config * system. * * @author Chuck Hagenbuch * @since Horde 3.0 * @package Horde_Framework */ class ConfigForm extends Horde_Form { /** * Don't use form tokens for the configuration form - while * generating configuration info, things like the Token system * might not work correctly. This saves some headaches. * * @var boolean */ var $_useFormToken = false; /** * Contains the Horde_Config object that this form represents. * * @var Horde_Config */ var $_xmlConfig; /** * Contains the Variables object of this form. * * @var Variables */ var $_vars; /** * Constructor. * * @param Variables &$vars The Variables object of this form. * @param string $app The name of the application that this * configuration form is for. */ function ConfigForm(&$vars, $app) { parent::Horde_Form($vars); $this->_xmlConfig = new Horde_Config($app); $this->_vars = &$vars; $config = $this->_xmlConfig->readXMLConfig(); $this->addHidden('', 'app', 'text', true); $this->_buildVariables($config); } /** * Builds the form based on the specified level of the configuration tree. * * @access private * * @param array $config The portion of the configuration tree for that * the form fields should be created. * @param string $prefix A string representing the current position * inside the configuration tree. */ function _buildVariables($config, $prefix = '') { if (!is_array($config)) { return; } foreach ($config as $name => $configitem) { $prefixedname = empty($prefix) ? $name : $prefix . '|' . $name; $varname = str_replace('|', '__', $prefixedname); if ($configitem == 'placeholder') { continue; } elseif (isset($configitem['tab'])) { $this->setSection($configitem['tab'], $configitem['desc']); } elseif (isset($configitem['switch'])) { $selected = $this->_vars->getExists($varname, $wasset); $var_params = array(); $select_option = true; if (is_bool($configitem['default'])) { $configitem['default'] = $configitem['default'] ? 'true' : 'false'; } foreach ($configitem['switch'] as $option => $case) { $var_params[$option] = $case['desc']; if ($option == $configitem['default']) { $select_option = false; if (!$wasset) { $selected = $option; } } } $name = '$conf[' . implode('][', explode('|', $prefixedname)) . ']'; $desc = $configitem['desc']; $v = &$this->addVariable($name, $varname, 'enum', true, false, $desc, array($var_params, $select_option)); if (array_key_exists('default', $configitem)) { $v->setDefault($configitem['default']); } if (!empty($configitem['is_default'])) { $v->_new = true; } $v_action = &Horde_Form_Action::factory('reload'); $v->setAction($v_action); if (isset($selected) && isset($configitem['switch'][$selected])) { $this->_buildVariables($configitem['switch'][$selected]['fields'], $prefix); } } elseif (isset($configitem['_type'])) { $required = (isset($configitem['required'])) ? $configitem['required'] : true; $type = $configitem['_type']; // FIXME: multienum fields can well be required, meaning that // you need to select at least one entry. Changing this before // Horde 4.0 would break a lot of configuration files though. if ($type == 'multienum' || $type == 'header' || $type == 'description') { $required = false; } if ($type == 'multienum' || $type == 'enum') { $var_params = array($configitem['values']); } else { $var_params = array(); } if ($type == 'header' || $type == 'description') { $name = $configitem['desc']; $desc = null; } else { $name = '$conf[' . implode('][', explode('|', $prefixedname)) . ']'; $desc = $configitem['desc']; if ($type == 'php') { $type = 'text'; $desc .= "\nEnter a valid PHP expression."; } } $v = &$this->addVariable($name, $varname, $type, $required, false, $desc, $var_params); if (isset($configitem['default'])) { $v->setDefault($configitem['default']); } if (!empty($configitem['is_default'])) { $v->_new = true; } } else { $this->_buildVariables($configitem, $prefixedname); } } } } * @since Horde 3.0 * @package Horde_Crypt */ class Horde_Crypt_pgp extends Horde_Crypt { /** * Strings in armor header lines used to distinguish between the different * types of PGP decryption/encryption. * * @var array */ var $_armor = array( 'MESSAGE' => PGP_ARMOR_MESSAGE, 'SIGNED MESSAGE' => PGP_ARMOR_SIGNED_MESSAGE, 'PUBLIC KEY BLOCK' => PGP_ARMOR_PUBLIC_KEY, 'PRIVATE KEY BLOCK' => PGP_ARMOR_PRIVATE_KEY, 'SIGNATURE' => PGP_ARMOR_SIGNATURE ); /** * The list of PGP hash algorithms (from RFC 3156). * * @var array */ var $_hashAlg = array( 1 => 'pgp-md5', 2 => 'pgp-sha1', 3 => 'pgp-ripemd160', 5 => 'pgp-md2', 6 => 'pgp-tiger192', 7 => 'pgp-haval-5-160', 8 => 'pgp-sha256', 9 => 'pgp-sha384', 10 => 'pgp-sha512', 11 => 'pgp-sha224', ); /** * GnuPG program location/common options. * * @var array */ var $_gnupg; /** * Filename of the temporary public keyring. * * @var string */ var $_publicKeyring; /** * Filename of the temporary private keyring. * * @var string */ var $_privateKeyring; /** * The existence of this property indicates that multiple recipient * encryption is available. * * @since Horde 3.1 * * @deprecated * * @var boolean */ var $multipleRecipientEncryption = true; /** * Constructor. * * @param array $params Parameter array containing the path to the GnuPG * binary (key = 'program') and to a temporary * directory. */ function Horde_Crypt_pgp($params = array()) { $this->_tempdir = Util::createTempDir(true, $params['temp']); if (empty($params['program'])) { Horde::fatal(PEAR::raiseError(_("The location of the GnuPG binary must be given to the Crypt_pgp:: class.")), __FILE__, __LINE__); } /* Store the location of GnuPG and set common options. */ $this->_gnupg = array( $params['program'], '--no-tty', '--no-secmem-warning', '--no-options', '--no-default-keyring', '--yes', '--homedir ' . $this->_tempdir ); if (strncasecmp(PHP_OS, 'WIN', 3)) { array_unshift($this->_gnupg, 'LANG= ;'); } } /** * Generates a personal Public/Private keypair combination. * * @param string $realname The name to use for the key. * @param string $email The email to use for the key. * @param string $passphrase The passphrase to use for the key. * @param string $comment The comment to use for the key. * @param integer $keylength The keylength to use for the key. * * @return array An array consisting of the public key and the private * key, or PEAR_Error on error. *
     * Return array:
     * Key            Value
     * --------------------------
     * 'public'   =>  Public Key
     * 'private'  =>  Private Key
     * 
*/ function generateKey($realname, $email, $passphrase, $comment = '', $keylength = 1024) { /* Check for secure connection. */ $secure_check = $this->requireSecureConnection(); if (is_a($secure_check, 'PEAR_Error')) { return $secure_check; } /* Create temp files to hold the generated keys. */ $pub_file = $this->_createTempFile('horde-pgp'); $secret_file = $this->_createTempFile('horde-pgp'); /* Create the config file necessary for GnuPG to run in batch mode. */ /* TODO: Sanitize input, More user customizable? */ $input = array(); $input[] = '%pubring ' . $pub_file; $input[] = '%secring ' . $secret_file; $input[] = 'Key-Type: DSA'; $input[] = 'Key-Length: 1024'; $input[] = 'Subkey-Type: ELG-E'; $input[] = 'Subkey-Length: ' . $keylength; $input[] = 'Name-Real: ' . $realname; if (!empty($comment)) { $input[] = 'Name-Comment: ' . $comment; } $input[] = 'Name-Email: ' . $email; $input[] = 'Expire-Date: 0'; $input[] = 'Passphrase: ' . $passphrase; $input[] = '%commit'; /* Run through gpg binary. */ $cmdline = array( '--gen-key', '--batch', '--armor' ); $result = $this->_callGpg($cmdline, 'w', $input, true, true); /* Get the keys from the temp files. */ $public_key = file($pub_file); $secret_key = file($secret_file); /* If either key is empty, something went wrong. */ if (empty($public_key) || empty($secret_key)) { $msg = _("Public/Private keypair not generated successfully."); if (!empty($result->stderr)) { $msg .= ' ' . _("Returned error message:") . ' ' . $result->stderr; } return PEAR::raiseError($msg, 'horde.error'); } return array('public' => $public_key, 'private' => $secret_key); } /** * Returns information on a PGP data block. * * @param string $pgpdata The PGP data block. * * @return array An array with information on the PGP data block. If an * element is not present in the data block, it will * likewise not be set in the array. *
     * Array Format:
     * -------------
     * [public_key]/[secret_key] => Array
     *   (
     *     [created] => Key creation - UNIX timestamp
     *     [expires] => Key expiration - UNIX timestamp (0 = never expires)
     *     [size]    => Size of the key in bits
     *   )
     *
     * [fingerprint] => Fingerprint of the PGP data (if available)
     *                  16-bit hex value (DEPRECATED)
     * [keyid] => Key ID of the PGP data (if available)
     *            16-bit hex value (as of Horde 3.2)
     *
     * [signature] => Array (
     *     [id{n}/'_SIGNATURE'] => Array (
     *         [name]        => Full Name
     *         [comment]     => Comment
     *         [email]       => E-mail Address
     *         [fingerprint] => 16-bit hex value (DEPRECATED)
     *         [keyid]       => 16-bit hex value (as of Horde 3.2)
     *         [created]     => Signature creation - UNIX timestamp
     *         [expires]     => Signature expiration - UNIX timestamp
     *         [micalg]      => The hash used to create the signature
     *         [sig_{hex}]   => Array [details of a sig verifying the ID] (
     *             [created]     => Signature creation - UNIX timestamp
     *             [expires]     => Signature expiration - UNIX timestamp
     *             [fingerprint] => 16-bit hex value (DEPRECATED)
     *             [keyid]       => 16-bit hex value (as of Horde 3.2)
     *             [micalg]      => The hash used to create the signature
     *         )
     *     )
     * )
     * 
* * Each user ID will be stored in the array 'signature' and have data * associated with it, including an array for information on each * signature that has signed that UID. Signatures not associated with a * UID (e.g. revocation signatures and sub keys) will be stored under the * special keyword '_SIGNATURE'. */ function pgpPacketInformation($pgpdata) { $data_array = array(); $keyid = ''; $header = null; $input = $this->_createTempFile('horde-pgp'); $sig_id = $uid_idx = 0; /* Store message in temporary file. */ $fp = fopen($input, 'w+'); fputs($fp, $pgpdata); fclose($fp); $cmdline = array( '--list-packets', $input ); $result = $this->_callGpg($cmdline, 'r'); foreach (explode("\n", $result->stdout) as $line) { /* Headers are prefaced with a ':' as the first character on the line. */ if (strpos($line, ':') === 0) { $lowerLine = String::lower($line); /* If we have a key (rather than a signature block), get the key's ID */ if (strpos($lowerLine, ':public key packet:') !== false || strpos($lowerLine, ':secret key packet:') !== false) { $cmdline = array( '--with-colons', $input ); $data = $this->_callGpg($cmdline, 'r'); if (preg_match("/(sec|pub):.*:.*:.*:([A-F0-9]{16}):/", $data->stdout, $matches)) { $keyid = $matches[2]; } } if (strpos($lowerLine, ':public key packet:') !== false) { $header = 'public_key'; } elseif (strpos($lowerLine, ':secret key packet:') !== false) { $header = 'secret_key'; } elseif (strpos($lowerLine, ':user id packet:') !== false) { $uid_idx++; $line = preg_replace_callback('/\\\\x([0-9a-f]{2})/', array($this, '_pgpPacketInformationHelper'), $line); if (preg_match("/\"([^\<]+)\<([^\>]+)\>\"/", $line, $matches)) { $header = 'id' . $uid_idx; if (preg_match('/([^\(]+)\((.+)\)$/', trim($matches[1]), $comment_matches)) { $data_array['signature'][$header]['name'] = trim($comment_matches[1]); $data_array['signature'][$header]['comment'] = $comment_matches[2]; } else { $data_array['signature'][$header]['name'] = trim($matches[1]); $data_array['signature'][$header]['comment'] = ''; } $data_array['signature'][$header]['email'] = $matches[2]; $data_array['signature'][$header]['keyid'] = $keyid; // TODO: Remove in Horde 4 $data_array['signature'][$header]['fingerprint'] = $keyid; } } elseif (strpos($lowerLine, ':signature packet:') !== false) { if (empty($header) || empty($uid_idx)) { $header = '_SIGNATURE'; } if (preg_match("/keyid\s+([0-9A-F]+)/i", $line, $matches)) { $sig_id = $matches[1]; $data_array['signature'][$header]['sig_' . $sig_id]['keyid'] = $matches[1]; $data_array['keyid'] = $matches[1]; // TODO: Remove these 2 entries in Horde 4 $data_array['signature'][$header]['sig_' . $sig_id]['fingerprint'] = $matches[1]; $data_array['fingerprint'] = $matches[1]; } } elseif (strpos($lowerLine, ':literal data packet:') !== false) { $header = 'literal'; } elseif (strpos($lowerLine, ':encrypted data packet:') !== false) { $header = 'encrypted'; } else { $header = null; } } else { if ($header == 'secret_key' || $header == 'public_key') { if (preg_match("/created\s+(\d+),\s+expires\s+(\d+)/i", $line, $matches)) { $data_array[$header]['created'] = $matches[1]; $data_array[$header]['expires'] = $matches[2]; } elseif (preg_match("/\s+[sp]key\[0\]:\s+\[(\d+)/i", $line, $matches)) { $data_array[$header]['size'] = $matches[1]; } } elseif ($header == 'literal' || $header == 'encrypted') { $data_array[$header] = true; } elseif ($header) { if (preg_match("/version\s+\d+,\s+created\s+(\d+)/i", $line, $matches)) { $data_array['signature'][$header]['sig_' . $sig_id]['created'] = $matches[1]; } elseif (isset($data_array['signature'][$header]['sig_' . $sig_id]['created']) && preg_match('/expires after (\d+y\d+d\d+h\d+m)\)$/', $line, $matches)) { $expires = $matches[1]; preg_match('/^(\d+)y(\d+)d(\d+)h(\d+)m$/', $expires, $matches); list(, $years, $days, $hours, $minutes) = $matches; $data_array['signature'][$header]['sig_' . $sig_id]['expires'] = strtotime('+ ' . $years . ' years + ' . $days . ' days + ' . $hours . ' hours + ' . $minutes . ' minutes', $data_array['signature'][$header]['sig_' . $sig_id]['created']); } elseif (preg_match("/digest algo\s+(\d{1})/", $line, $matches)) { $micalg = $this->_hashAlg[$matches[1]]; $data_array['signature'][$header]['sig_' . $sig_id]['micalg'] = $micalg; if ($header == '_SIGNATURE') { /* Likely a signature block, not a key. */ $data_array['signature']['_SIGNATURE']['micalg'] = $micalg; } if ($sig_id == $keyid) { /* Self signing signature - we can assume * the micalg value from this signature is * that for the key */ $data_array['signature']['_SIGNATURE']['micalg'] = $micalg; $data_array['signature'][$header]['micalg'] = $micalg; } } } } } $keyid && $data_array['keyid'] = $keyid; // TODO: Remove for Horde 4 $keyid && $data_array['fingerprint'] = $keyid; return $data_array; } function _pgpPacketInformationHelper($a) { return chr(hexdec($a[1])); } /** * Returns human readable information on a PGP key. * * @param string $pgpdata The PGP data block. * * @return string Tabular information on the PGP key. */ function pgpPrettyKey($pgpdata) { $msg = ''; $packet_info = $this->pgpPacketInformation($pgpdata); $fingerprints = $this->getFingerprintsFromKey($pgpdata); if (!empty($packet_info['signature'])) { /* Making the property names the same width for all * localizations .*/ $leftrow = array(_("Name"), _("Key Type"), _("Key Creation"), _("Expiration Date"), _("Key Length"), _("Comment"), _("E-Mail"), _("Hash-Algorithm"), _("Key ID"), _("Key Fingerprint")); $leftwidth = array_map('strlen', $leftrow); $maxwidth = max($leftwidth) + 2; array_walk($leftrow, array($this, '_pgpPrettyKeyFormatter'), $maxwidth); foreach (array_keys($packet_info['signature']) as $uid_idx) { if ($uid_idx == '_SIGNATURE') { continue; } $key_info = $this->pgpPacketSignatureByUidIndex($pgpdata, $uid_idx); if (!empty($key_info['keyid'])) { $key_info['keyid'] = $this->_getKeyIDString($key_info['keyid']); } else { $key_info['keyid'] = null; } $fingerprint = isset($fingerprints[$key_info['keyid']]) ? $fingerprints[$key_info['keyid']] : null; $msg .= $leftrow[0] . (isset($key_info['name']) ? stripcslashes($key_info['name']) : '') . "\n" . $leftrow[1] . (($key_info['key_type'] == 'public_key') ? _("Public Key") : _("Private Key")) . "\n" . $leftrow[2] . strftime("%D", $key_info['key_created']) . "\n" . $leftrow[3] . (empty($key_info['key_expires']) ? '[' . _("Never") . ']' : strftime("%D", $key_info['key_expires'])) . "\n" . $leftrow[4] . $key_info['key_size'] . " Bytes\n" . $leftrow[5] . (empty($key_info['comment']) ? '[' . _("None") . ']' : $key_info['comment']) . "\n" . $leftrow[6] . (empty($key_info['email']) ? '[' . _("None") . ']' : $key_info['email']) . "\n" . $leftrow[7] . (empty($key_info['micalg']) ? '[' . _("Unknown") . ']' : $key_info['micalg']) . "\n" . $leftrow[8] . (empty($key_info['keyid']) ? '[' . _("Unknown") . ']' : $key_info['keyid']) . "\n" . $leftrow[9] . (empty($fingerprint) ? '[' . _("Unknown") . ']' : $fingerprint) . "\n\n"; } } return $msg; } function _pgpPrettyKeyFormatter(&$s, $k, $m) { $s .= ':' . str_repeat(' ', $m - String::length($s)); } function _getKeyIDString($keyid) { /* Get the 8 character key ID string. */ if (strpos($keyid, '0x') === 0) { $keyid = substr($keyid, 2); } if (strlen($keyid) > 8) { $keyid = substr($keyid, -8); } return '0x' . $keyid; } /** * Returns only information on the first ID that matches the email address * input. * * @param string $pgpdata The PGP data block. * @param string $email An e-mail address. * * @return array An array with information on the PGP data block. If an * element is not present in the data block, it will * likewise not be set in the array. *
     * Array Fields:
     * -------------
     * key_created  =>  Key creation - UNIX timestamp
     * key_expires  =>  Key expiration - UNIX timestamp (0 = never expires)
     * key_size     =>  Size of the key in bits
     * key_type     =>  The key type (public_key or secret_key)
     * name         =>  Full Name
     * comment      =>  Comment
     * email        =>  E-mail Address
     * fingerprint  =>  16-bit hex value (DEPRECATED)
     * keyid        =>  16-bit hex value
     * created      =>  Signature creation - UNIX timestamp
     * micalg       =>  The hash used to create the signature
     * 
*/ function pgpPacketSignature($pgpdata, $email) { $data = $this->pgpPacketInformation($pgpdata); $key_type = null; $return_array = array(); /* Check that [signature] key exists. */ if (!isset($data['signature'])) { return $return_array; } /* Store the signature information now. */ if (($email == '_SIGNATURE') && isset($data['signature']['_SIGNATURE'])) { foreach ($data['signature'][$email] as $key => $value) { $return_array[$key] = $value; } } else { $uid_idx = 1; while (isset($data['signature']['id' . $uid_idx])) { if ($data['signature']['id' . $uid_idx]['email'] == $email) { foreach ($data['signature']['id' . $uid_idx] as $key => $val) { $return_array[$key] = $val; } break; } $uid_idx++; } } return $this->_pgpPacketSignature($data, $return_array); } /** * Returns information on a PGP signature embedded in PGP data. Similar * to pgpPacketSignature(), but returns information by unique User ID * Index (format id{n} where n is an integer of 1 or greater). * * @param string $pgpdata See pgpPacketSignature(). * @param string $uid_idx The UID index. * * @return array See pgpPacketSignature(). */ function pgpPacketSignatureByUidIndex($pgpdata, $uid_idx) { $data = $this->pgpPacketInformation($pgpdata); $key_type = null; $return_array = array(); /* Search for the UID index. */ if (!isset($data['signature']) || !isset($data['signature'][$uid_idx])) { return $return_array; } /* Store the signature information now. */ foreach ($data['signature'][$uid_idx] as $key => $value) { $return_array[$key] = $value; } return $this->_pgpPacketSignature($data, $return_array); } /** * Adds some data to the pgpPacketSignature*() function array. * * @access private * * @param array $data See pgpPacketSignature(). * @param array $retarray The return array. * * @return array The return array. */ function _pgpPacketSignature($data, $retarray) { /* If empty, return now. */ if (empty($retarray)) { return $retarray; } $key_type = null; /* Store any public/private key information. */ if (isset($data['public_key'])) { $key_type = 'public_key'; } elseif (isset($data['secret_key'])) { $key_type = 'secret_key'; } if ($key_type) { $retarray['key_type'] = $key_type; if (isset($data[$key_type]['created'])) { $retarray['key_created'] = $data[$key_type]['created']; } if (isset($data[$key_type]['expires'])) { $retarray['key_expires'] = $data[$key_type]['expires']; } if (isset($data[$key_type]['size'])) { $retarray['key_size'] = $data[$key_type]['size']; } } return $retarray; } /** * Returns the short fingerprint (Key ID) of the key used to sign a block * of PGP data. * * @deprecated Use getSignersKeyID() instead. * @todo Remove for Horde 4 * * @param string $text The PGP signed text block. * * @return string The short fingerprint of the key used to sign $text. */ function getSignersFingerprint($text) { return $this->getSignersKeyID($text); } /** * Returns the key ID of the key used to sign a block of PGP data. * * @since Horde 3.2 * * @param string $text The PGP signed text block. * * @return string The key ID of the key used to sign $text. */ function getSignersKeyID($text) { $keyid = null; $input = $this->_createTempFile('horde-pgp'); $fp = fopen($input, 'w+'); fputs($fp, $text); fclose($fp); $cmdline = array( '--verify', $input ); $result = $this->_callGpg($cmdline, 'r', null, true, true); if (preg_match('/gpg:\sSignature\smade.*ID\s+([A-F0-9]{8})\s+/', $result->stderr, $matches)) { $keyid = $matches[1]; } return $keyid; } /** * Verify a passphrase for a given public/private keypair. * * @param string $public_key The user's PGP public key. * @param string $private_key The user's PGP private key. * @param string $passphrase The user's passphrase. * * @return boolean Returns true on valid passphrase, false on invalid * passphrase, and PEAR_Error on error. */ function verifyPassphrase($public_key, $private_key, $passphrase) { /* Check for secure connection. */ $secure_check = $this->requireSecureConnection(); if (is_a($secure_check, 'PEAR_Error')) { return $secure_check; } /* Encrypt a test message. */ $result = $this->encrypt('Test', array('type' => 'message', 'pubkey' => $public_key)); if (is_a($result, 'PEAR_Error')) { return false; } /* Try to decrypt the message. */ $result = $this->decrypt($result, array('type' => 'message', 'pubkey' => $public_key, 'privkey' => $private_key, 'passphrase' => $passphrase)); if (is_a($result, 'PEAR_Error')) { return false; } return true; } /** * Parses a message into text and PGP components. * * @param string $text The text to parse. * * @return array An array with the parsed text, returned in blocks of * text corresponding to their actual order. *
     * Return array:
     * Key         Value
     * -------------------------------------------------
     * 'type'  =>  The type of data contained in block.
     *             Valid types are defined at the top of this class
     *             (the PGP_ARMOR_* constants).
     * 'data'  =>  The actual data for each section.
     * 
*/ function parsePGPData($text) { $data = array(); $buffer = explode("\n", $text); /* Set $temp_array to be of type PGP_ARMOR_TEXT. */ $temp_array = array(); $temp_array['type'] = PGP_ARMOR_TEXT; foreach ($buffer as $value) { if (preg_match('/^-----(BEGIN|END) PGP ([^-]+)-----\s*$/', $value, $matches)) { if (isset($temp_array['data'])) { $data[] = $temp_array; } unset($temp_array); $temp_array = array(); if ($matches[1] === 'BEGIN') { $temp_array['type'] = $this->_armor[$matches[2]]; $temp_array['data'][] = $value; } elseif ($matches[1] === 'END') { $temp_array['type'] = PGP_ARMOR_TEXT; $data[count($data) - 1]['data'][] = $value; } } else { $temp_array['data'][] = $value; } } if (isset($temp_array['data'])) { $data[] = $temp_array; } return $data; } /** * Returns a PGP public key from a public keyserver. * * @param string $keyid The key ID of the PGP key. * @param string $server The keyserver to use. * @param float $timeout The keyserver timeout. * @param string $address The email address of the PGP key. @since * Horde 3.2. * * @return string The PGP public key, or PEAR_Error on error. */ function getPublicKeyserver($keyid, $server = PGP_KEYSERVER_PUBLIC, $timeout = PGP_KEYSERVER_TIMEOUT, $address = null) { if (empty($keyid) && !empty($address)) { $keyid = $this->getKeyID($address, $server, $timeout); if (is_a($keyid, 'PEAR_Error')) { return $keyid; } } /* Connect to the public keyserver. */ $uri = '/pks/lookup?op=get&search=' . $this->_getKeyIDString($keyid); $output = $this->_connectKeyserver('GET', $server, $uri, '', $timeout); if (is_a($output, 'PEAR_Error')) { return $output; } /* Strip HTML Tags from output. */ if (($start = strstr($output, '-----BEGIN'))) { $length = strpos($start, '-----END') + 34; return substr($start, 0, $length); } else { return PEAR::raiseError(_("Could not obtain public key from the keyserver."), 'horde.error'); } } /** * Sends a PGP public key to a public keyserver. * * @param string $pubkey The PGP public key * @param string $server The keyserver to use. * @param float $timeout The keyserver timeout. * * @return PEAR_Error PEAR_Error on error/failure. */ function putPublicKeyserver($pubkey, $server = PGP_KEYSERVER_PUBLIC, $timeout = PGP_KEYSERVER_TIMEOUT) { /* Get the key ID of the public key. */ $info = $this->pgpPacketInformation($pubkey); /* See if the public key already exists on the keyserver. */ if (!is_a($this->getPublicKeyserver($info['keyid'], $server, $timeout), 'PEAR_Error')) { return PEAR::raiseError(_("Key already exists on the public keyserver."), 'horde.warning'); } /* Connect to the public keyserver. _connectKeyserver() * returns a PEAR_Error object on error and the output text on * success. */ $pubkey = 'keytext=' . urlencode(rtrim($pubkey)); $cmd = array( 'Host: ' . $server . ':11371', 'User-Agent: Horde Application Framework 3.2', 'Content-Type: application/x-www-form-urlencoded', 'Content-Length: ' . strlen($pubkey), 'Connection: close', '', $pubkey ); $result = $this->_connectKeyserver('POST', $server, '/pks/add', implode("\r\n", $cmd), $timeout); if (is_a($result, 'PEAR_Error')) { return $result; } } /** * Returns the first matching key ID for an email address from a * public keyserver. * * @since Horde 3.2 * * @param string $address The email address of the PGP key. * @param string $server The keyserver to use. * @param float $timeout The keyserver timeout. * * @return string The PGP key ID, or PEAR_Error on error. */ function getKeyID($address, $server = PGP_KEYSERVER_PUBLIC, $timeout = PGP_KEYSERVER_TIMEOUT) { /* Connect to the public keyserver. */ $uri = '/pks/lookup?op=index&options=mr&search=' . urlencode($address); $output = $this->_connectKeyserver('GET', $server, $uri, '', $timeout); if (is_a($output, 'PEAR_Error')) { return $output; } if (($start = strstr($output, '-----BEGIN PGP PUBLIC KEY BLOCK'))) { /* The server returned the matching key immediately. */ $length = strpos($start, '-----END PGP PUBLIC KEY BLOCK') + 34; $info = $this->pgpPacketInformation(substr($start, 0, $length)); if (!empty($info['keyid']) && (empty($info['public_key']['expires']) || $info['public_key']['expires'] > time())) { return $info['keyid']; } } elseif (strpos($output, 'pub:') !== false) { $output = explode("\n", $output); $keyids = array(); foreach ($output as $line) { if (substr($line, 0, 4) == 'pub:') { $line = explode(':', $line); /* Ignore invalid lines and expired keys. */ if (count($line) != 7 || (!empty($line[5]) && $line[5] <= time())) { continue; } $keyids[$line[4]] = $line[1]; } } /* Sort by timestamp to use the newest key. */ if (count($keyids)) { ksort($keyids); return array_pop($keyids); } } return PEAR::raiseError(_("Could not obtain public key from the keyserver.")); } /** * Get the fingerprints from a key block. * * @param string $pgpdata The PGP data block. * * @return array The fingerprints in $pgpdata indexed by key id. */ function getFingerprintsFromKey($pgpdata) { $fingerprints = array(); /* Store the key in a temporary keyring. */ $keyring = $this->_putInKeyring($pgpdata); /* Options for the GPG binary. */ $cmdline = array( '--fingerprint', $keyring, ); $result = $this->_callGpg($cmdline, 'r'); if (!$result || !$result->stdout) { return $fingerprints; } /* Parse fingerprints and key ids from output. */ $lines = explode("\n", $result->stdout); $keyid = null; foreach ($lines as $line) { if (preg_match('/pub\s+\w+\/(\w{8})/', $line, $matches)) { $keyid = '0x' . $matches[1]; } elseif ($keyid && preg_match('/^\s+[\s\w]+=\s*([\w\s]+)$/m', $line, $matches)) { $fingerprints[$keyid] = trim($matches[1]); $keyid = null; } } return $fingerprints; } /** * Connects to a public key server via HKP (Horrowitz Keyserver Protocol). * * @access private * * @param string $method POST, GET, etc. * @param string $server The keyserver to use. * @param string $uri The URI to access (relative to the server). * @param string $command The PGP command to run. * @param float $timeout The timeout value. * * @return string The text from standard output on success, or PEAR_Error * on error/failure. */ function _connectKeyserver($method, $server, $resource, $command, $timeout) { $connRefuse = 0; $output = ''; $port = '11371'; if (!empty($GLOBALS['conf']['http']['proxy']['proxy_host'])) { $resource = 'http://' . $server . ':' . $port . $resource; $server = $GLOBALS['conf']['http']['proxy']['proxy_host']; if (!empty($GLOBALS['conf']['http']['proxy']['proxy_port'])) { $port = $GLOBALS['conf']['http']['proxy']['proxy_port']; } else { $port = 80; } } $command = $method . ' ' . $resource . ' HTTP/1.0' . ($command ? "\r\n" . $command : ''); /* Attempt to get the key from the keyserver. */ do { $connError = false; $errno = $errstr = null; /* The HKP server is located on port 11371. */ $fp = @fsockopen($server, $port, $errno, $errstr, $timeout); if (!$fp) { $connError = true; } else { fputs($fp, $command . "\n\n"); while (!feof($fp)) { $output .= fgets($fp, 1024); } fclose($fp); } if ($connError) { if (++$connRefuse === PGP_KEYSERVER_REFUSE) { if ($errno == 0) { $output = PEAR::raiseError(_("Connection refused to the public keyserver."), 'horde.error'); } else { $output = PEAR::raiseError(sprintf(_("Connection refused to the public keyserver. Reason: %s (%s)"), String::convertCharset($errstr, NLS::getExternalCharset()), $errno), 'horde.error'); } break; } } } while ($connError); return $output; } /** * Encrypts text using PGP. * * @param string $text The text to be PGP encrypted. * @param array $params The parameters needed for encryption. * See the individual _encrypt*() functions for the * parameter requirements. * * @return string The encrypted message, or PEAR_Error on error. */ function encrypt($text, $params = array()) { if (isset($params['type'])) { if ($params['type'] === 'message') { return $this->_encryptMessage($text, $params); } elseif ($params['type'] === 'signature') { return $this->_encryptSignature($text, $params); } } } /** * Decrypts text using PGP. * * @param string $text The text to be PGP decrypted. * @param array $params The parameters needed for decryption. * See the individual _decrypt*() functions for the * parameter requirements. * * @return string The decrypted message, or PEAR_Error on error. */ function decrypt($text, $params = array()) { if (isset($params['type'])) { if ($params['type'] === 'message') { return $this->_decryptMessage($text, $params); } elseif (($params['type'] === 'signature') || ($params['type'] === 'detached-signature')) { return $this->_decryptSignature($text, $params); } } } /** * Returns whether a text has been encrypted symmetrically. * * @since Horde 3.2 * * @param string $text The PGP encrypted text. * * @return boolean True if the text is symmetricallly encrypted. */ function encryptedSymmetrically($text) { $cmdline = array( '--decrypt', '--batch' ); $result = $this->_callGpg($cmdline, 'w', $text, true, true, true); return strpos($result->stderr, 'gpg: encrypted with 1 passphrase') !== false; } /** * Creates a temporary gpg keyring. * * @access private * * @param string $type The type of key to analyze. Either 'public' * (Default) or 'private' * * @return string Command line keystring option to use with gpg program. */ function _createKeyring($type = 'public') { $type = String::lower($type); if ($type === 'public') { if (empty($this->_publicKeyring)) { $this->_publicKeyring = $this->_createTempFile('horde-pgp'); } return '--keyring ' . $this->_publicKeyring; } elseif ($type === 'private') { if (empty($this->_privateKeyring)) { $this->_privateKeyring = $this->_createTempFile('horde-pgp'); } return '--secret-keyring ' . $this->_privateKeyring; } } /** * Adds PGP keys to the keyring. * * @access private * * @param mixed $keys A single key or an array of key(s) to add to the * keyring. * @param string $type The type of key(s) to add. Either 'public' * (Default) or 'private' * * @return string Command line keystring option to use with gpg program. */ function _putInKeyring($keys = array(), $type = 'public') { $type = String::lower($type); if (!is_array($keys)) { $keys = array($keys); } /* Create the keyrings if they don't already exist. */ $keyring = $this->_createKeyring($type); /* Store the key(s) in the keyring. */ $cmdline = array( '--allow-secret-key-import', '--fast-import', $keyring ); $this->_callGpg($cmdline, 'w', array_values($keys)); return $keyring; } /** * Encrypts a message in PGP format using a public key. * * @access private * * @param string $text The text to be encrypted. * @param array $params The parameters needed for encryption. *
     * Parameters:
     * ===========
     * 'type'       => 'message' (REQUIRED)
     * 'symmetric'  => Whether to use symmetric instead of asymmetric
     *                 encryption (defaults to false)
     * 'recips'     => An array with the e-mail address of the recipient as
     *                 the key and that person's public key as the value.
     *                 (REQUIRED if 'symmetric' is false)
     * 'passphrase' => The passphrase for the symmetric encryption (REQUIRED if
     *                 'symmetric' is true)
     * 'pubkey'     => PGP public key. (Optional) (DEPRECATED)
     * 'email'      => E-mail address of recipient. If not present, or not
     *                 found in the public key, the first e-mail address found
     *                 in the key will be used instead. (Optional) (DEPRECATED)
     * 
* * @return string The encrypted message, or PEAR_Error on error. */ function _encryptMessage($text, $params) { $email = null; if (empty($params['symmetric']) && !isset($params['recips'])) { /* Check for required parameters. */ if (!isset($params['pubkey'])) { return PEAR::raiseError(_("A public PGP key is required to encrypt a message."), 'horde.error'); } /* Get information on the key. */ if (isset($params['email'])) { $key_info = $this->pgpPacketSignature($params['pubkey'], $params['email']); if (!empty($key_info)) { $email = $key_info['email']; } } /* If we have no email address at this point, use the first email address found in the public key. */ if (empty($email)) { $key_info = $this->pgpPacketInformation($params['pubkey']); if (isset($key_info['signature']['id1']['email'])) { $email = $key_info['signature']['id1']['email']; } else { return PEAR::raiseError(_("Could not determine the recipient's e-mail address."), 'horde.error'); } } $params['recips'] = array($email => $params['pubkey']); } /* Create temp files for input. */ $input = $this->_createTempFile('horde-pgp'); $fp = fopen($input, 'w+'); fputs($fp, $text); fclose($fp); /* Build command line. */ $cmdline = array( '--armor', '--batch', '--always-trust' ); if (empty($params['symmetric'])) { /* Store public key in temporary keyring. */ $keyring = $this->_putInKeyring(array_values($params['recips'])); $cmdline[] = $keyring; $cmdline[] = '--encrypt'; foreach (array_keys($params['recips']) as $val) { $cmdline[] = '--recipient ' . $val; } } else { $cmdline[] = '--symmetric'; $cmdline[] = '--passphrase-fd 0'; } $cmdline[] = $input; /* Encrypt the document. */ $result = $this->_callGpg($cmdline, 'w', empty($params['symmetric']) ? null : $params['passphrase'], true, true); if (empty($result->output)) { $error = preg_replace('/\n.*/', '', $result->stderr); return PEAR::raiseError(_("Could not PGP encrypt message: ") . $error, 'horde.error'); } return $result->output; } /** * Signs a message in PGP format using a private key. * * @access private * * @param string $text The text to be signed. * @param array $params The parameters needed for signing. *
     * Parameters:
     * ===========
     * 'type'        =>  'signature' (REQUIRED)
     * 'pubkey'      =>  PGP public key. (REQUIRED)
     * 'privkey'     =>  PGP private key. (REQUIRED)
     * 'passphrase'  =>  Passphrase for PGP Key. (REQUIRED)
     * 'sigtype'     =>  Determine the signature type to use. (Optional)
     *                   'cleartext'  --  Make a clear text signature
     *                   'detach'     --  Make a detached signature (DEFAULT)
     * 
* * @return string The signed message, or PEAR_Error on error. */ function _encryptSignature($text, $params) { /* Check for secure connection. */ $secure_check = $this->requireSecureConnection(); if (is_a($secure_check, 'PEAR_Error')) { return $secure_check; } /* Check for required parameters. */ if (!isset($params['pubkey']) || !isset($params['privkey']) || !isset($params['passphrase'])) { return PEAR::raiseError(_("A public PGP key, private PGP key, and passphrase are required to sign a message."), 'horde.error'); } /* Create temp files for input. */ $input = $this->_createTempFile('horde-pgp'); /* Encryption requires both keyrings. */ $pub_keyring = $this->_putInKeyring(array($params['pubkey'])); $sec_keyring = $this->_putInKeyring(array($params['privkey']), 'private'); /* Store message in temporary file. */ $fp = fopen($input, 'w+'); fputs($fp, $text); fclose($fp); /* Determine the signature type to use. */ $cmdline = array(); if (isset($params['sigtype']) && $params['sigtype'] == 'cleartext') { $sign_type = '--clearsign'; } else { $sign_type = '--detach-sign'; } /* Additional GPG options. */ $cmdline += array( '--armor', '--batch', '--passphrase-fd 0', $sec_keyring, $pub_keyring, $sign_type, $input ); /* Sign the document. */ $result = $this->_callGpg($cmdline, 'w', $params['passphrase'], true, true); if (empty($result->output)) { $error = preg_replace('/\n.*/', '', $result->stderr); return PEAR::raiseError(_("Could not PGP sign message: ") . $error, 'horde.error'); } else { return $result->output; } } /** * Decrypts an PGP encrypted message using a private/public keypair and a * passhprase. * * @access private * * @param string $text The text to be decrypted. * @param array $params The parameters needed for decryption. *
     * Parameters:
     * ===========
     * 'type'        =>  'message' (REQUIRED)
     * 'pubkey'      =>  PGP public key. (REQUIRED for asymmetric encryption)
     * 'privkey'     =>  PGP private key. (REQUIRED for asymmetric encryption)
     * 'passphrase'  =>  Passphrase for PGP Key. (REQUIRED)
     * 
* * @return stdClass An object with the following properties, or PEAR_Error * on error: *
     * 'message'     -  The decrypted message.
     * 'sig_result'  -  The result of the signature test.
     * 
*/ function _decryptMessage($text, $params) { /* Check for secure connection. */ $secure_check = $this->requireSecureConnection(); if (is_a($secure_check, 'PEAR_Error')) { return $secure_check; } $good_sig_flag = false; /* Check for required parameters. */ if (!isset($params['passphrase']) && empty($params['no_passphrase'])) { return PEAR::raiseError(_("A passphrase is required to decrypt a message."), 'horde.error'); } /* Create temp files. */ $input = $this->_createTempFile('horde-pgp'); /* Store message in file. */ $fp = fopen($input, 'w+'); fputs($fp, $text); fclose($fp); /* Build command line. */ $cmdline = array( '--always-trust', '--armor', '--batch' ); if (empty($param['no_passphrase'])) { $cmdline[] = '--passphrase-fd 0'; } if (!empty($params['pubkey']) && !empty($params['privkey'])) { /* Decryption requires both keyrings. */ $pub_keyring = $this->_putInKeyring(array($params['pubkey'])); $sec_keyring = $this->_putInKeyring(array($params['privkey']), 'private'); $cmdline[] = $sec_keyring; $cmdline[] = $pub_keyring; } $cmdline[] = '--decrypt'; $cmdline[] = $input; /* Decrypt the document now. */ if (empty($params['no_passphrase'])) { $result = $this->_callGpg($cmdline, 'w', $params['passphrase'], true, true); } else { $result = $this->_callGpg($cmdline, 'r', null, true, true); } if (empty($result->output)) { $error = preg_replace('/\n.*/', '', $result->stderr); return PEAR::raiseError(_("Could not decrypt PGP data: ") . $error, 'horde.error'); } /* Create the return object. */ $ob = new stdClass; $ob->message = $result->output; /* Check the PGP signature. */ $sig_check = $this->_checkSignatureResult($result->stderr); if (is_a($sig_check, 'PEAR_Error')) { $ob->sig_result = $sig_check; } else { $ob->sig_result = ($sig_check) ? $result->stderr : ''; } return $ob; } /** * Decrypts an PGP signed message using a public key. * * @access private * * @param string $text The text to be verified. * @param array $params The parameters needed for verification. *
     * Parameters:
     * ===========
     * 'type'       =>  'signature' or 'detached-signature' (REQUIRED)
     * 'pubkey'     =>  PGP public key. (REQUIRED)
     * 'signature'  =>  PGP signature block. (REQUIRED for detached signature)
     * 
* * @return string The verification message from gpg. If no signature, * returns empty string, and PEAR_Error on error. */ function _decryptSignature($text, $params) { /* Check for required parameters. */ if (!isset($params['pubkey'])) { return PEAR::raiseError(_("A public PGP key is required to verify a signed message."), 'horde.error'); } if (($params['type'] === 'detached-signature') && !isset($params['signature'])) { return PEAR::raiseError(_("The detached PGP signature block is required to verify the signed message."), 'horde.error'); } $good_sig_flag = 0; /* Create temp files for input. */ $input = $this->_createTempFile('horde-pgp'); /* Store public key in temporary keyring. */ $keyring = $this->_putInKeyring($params['pubkey']); /* Store the message in a temporary file. */ $fp = fopen($input, 'w+'); fputs($fp, $text); fclose($fp); /* Options for the GPG binary. */ $cmdline = array( '--armor', '--always-trust', '--batch', '--charset ' . NLS::getCharset(), $keyring, '--verify' ); /* Extra stuff to do if we are using a detached signature. */ if ($params['type'] === 'detached-signature') { $sigfile = $this->_createTempFile('horde-pgp'); $cmdline[] = $sigfile . ' ' . $input; $fp = fopen($sigfile, 'w+'); fputs($fp, $params['signature']); fclose($fp); } else { $cmdline[] = $input; } /* Verify the signature. We need to catch standard error output, * since this is where the signature information is sent. */ $result = $this->_callGpg($cmdline, 'r', null, true, true); $sig_result = $this->_checkSignatureResult($result->stderr); if (is_a($sig_result, 'PEAR_Error')) { return $sig_result; } else { return ($sig_result) ? $result->stderr : ''; } } /** * Checks signature result from the GnuPG binary. * * @access private * * @param string $result The signature result. * * @return boolean True if signature is good. */ function _checkSignatureResult($result) { /* Good signature: * gpg: Good signature from "blah blah blah (Comment)" * Bad signature: * gpg: BAD signature from "blah blah blah (Comment)" */ if (strpos($result, 'gpg: BAD signature') !== false) { return PEAR::raiseError($result, 'horde.error'); } elseif (strpos($result, 'gpg: Good signature') !== false) { return true; } else { return false; } } /** * Signs a MIME_Part using PGP. * * @param MIME_Part $mime_part The MIME_Part object to sign. * @param array $params The parameters required for signing. * @see _encryptSignature(). * * @return MIME_Part A MIME_Part object that is signed according to RFC * 2015/3156, or PEAR_Error on error. */ function signMIMEPart($mime_part, $params = array()) { include_once 'Horde/MIME/Part.php'; $params = array_merge($params, array('type' => 'signature', 'sigtype' => 'detach')); /* RFC 2015 Requirements for a PGP signed message: * + Content-Type params 'micalg' & 'protocol' are REQUIRED. * + The digitally signed message MUST be constrained to 7 bits. * + The MIME headers MUST be a part of the signed data. */ $mime_part->strict7bit(true); $msg_sign = $this->encrypt($mime_part->toCanonicalString(), $params); if (is_a($msg_sign, 'PEAR_Error')) { return $msg_sign; } /* Add the PGP signature. */ $charset = NLS::getEmailCharset(); $pgp_sign = new MIME_Part('application/pgp-signature', $msg_sign, $charset, 'inline'); $pgp_sign->setDescription(String::convertCharset(_("PGP Digital Signature"), NLS::getCharset(), $charset)); /* Get the algorithim information from the signature. Since we are * analyzing a signature packet, we need to use the special keyword * '_SIGNATURE' - see Horde_Crypt_pgp. */ $sig_info = $this->pgpPacketSignature($msg_sign, '_SIGNATURE'); /* Setup the multipart MIME Part. */ $part = new MIME_Part('multipart/signed'); $part->setContents('This message is in MIME format and has been PGP signed.' . "\n"); $part->addPart($mime_part); $part->addPart($pgp_sign); $part->setContentTypeParameter('protocol', 'application/pgp-signature'); $part->setContentTypeParameter('micalg', $sig_info['micalg']); return $part; } /** * Encrypts a MIME_Part using PGP. * * @param MIME_Part $mime_part The MIME_Part object to encrypt. * @param array $params The parameters required for encryption. * @see _encryptMessage(). * * @return MIME_Part A MIME_Part object that is encrypted according to RFC * 2015/3156, or PEAR_Error on error. */ function encryptMIMEPart($mime_part, $params = array()) { include_once 'Horde/MIME/Part.php'; $params = array_merge($params, array('type' => 'message')); $signenc_body = $mime_part->toCanonicalString(); $message_encrypt = $this->encrypt($signenc_body, $params); if (is_a($message_encrypt, 'PEAR_Error')) { return $message_encrypt; } /* Set up MIME Structure according to RFC 2015. */ $charset = NLS::getEmailCharset(); $part = new MIME_Part('multipart/encrypted', null, $charset); $part->setContents('This message is in MIME format and has been PGP encrypted.' . "\n"); $part->addPart(new MIME_Part('application/pgp-encrypted', "Version: 1\n", null)); $part->addPart(new MIME_Part('application/octet-stream', $message_encrypt, null, 'inline')); $part->setContentTypeParameter('protocol', 'application/pgp-encrypted'); $part->setDescription(String::convertCharset(_("PGP Encrypted Data"), NLS::getCharset(), $charset)); return $part; } /** * Signs and encrypts a MIME_Part using PGP. * * @param MIME_Part $mime_part The MIME_Part object to sign and encrypt. * @param array $sign_params The parameters required for signing. * @see _encryptSignature(). * @param array $encrypt_params The parameters required for encryption. * @see _encryptMessage(). * * @return MIME_Part A MIME_Part object that is signed and encrypted * according to RFC 2015/3156, or PEAR_Error on error. */ function signAndEncryptMIMEPart($mime_part, $sign_params = array(), $encrypt_params = array()) { include_once 'Horde/MIME/Part.php'; /* RFC 2015 requires that the entire signed message be encrypted. We * need to explicitly call using Horde_Crypt_pgp:: because we don't * know whether a subclass has extended these methods. */ $part = $this->signMIMEPart($mime_part, $sign_params); if (is_a($part, 'PEAR_Error')) { return $part; } $part = $this->encryptMIMEPart($part, $encrypt_params); if (is_a($part, 'PEAR_Error')) { return $part; } $part->setContents('This message is in MIME format and has been PGP signed and encrypted.' . "\n"); $charset = NLS::getEmailCharset(); $part->setCharset($charset); $part->setDescription(String::convertCharset(_("PGP Signed/Encrypted Data"), NLS::getCharset(), $charset)); return $part; } /** * Generates a MIME_Part object, in accordance with RFC 2015/3156, that * contains a public key. * * @param string $key The public key. * * @return MIME_Part A MIME_Part object that contains the public key. */ function publicKeyMIMEPart($key) { include_once 'Horde/MIME/Part.php'; $charset = NLS::getEmailCharset(); $part = new MIME_Part('application/pgp-keys', $key, $charset); $part->setDescription(String::convertCharset(_("PGP Public Key"), NLS::getCharset(), $charset)); return $part; } /** * Function that handles interfacing with the GnuPG binary. * * @access private * * @param array $options Options and commands to pass to GnuPG. * @param string $mode 'r' to read from stdout, 'w' to write to stdin. * @param array $input Input to write to stdin. * @param boolean $output If true, collect and store output in object returned. * @param boolean $stderr If true, collect and store stderr in object returned. * @param boolean $verbose If true, run GnuPG with quiet flag. * * @return stdClass Class with members output, stderr, and stdout. */ function _callGpg($options, $mode, $input = array(), $output = false, $stderr = false, $verbose = false) { $data = new stdClass; $data->output = null; $data->stderr = null; $data->stdout = null; /* Verbose output? */ if (!$verbose) { array_unshift($options, '--quiet'); } /* Create temp files for output. */ if ($output) { $output_file = $this->_createTempFile('horde-pgp', false); array_unshift($options, '--output ' . $output_file); /* Do we need standard error output? */ if ($stderr) { $stderr_file = $this->_createTempFile('horde-pgp', false); $options[] = '2> ' . $stderr_file; } } /* Silence errors if not requested. */ if (!$output || !$stderr) { $options[] = '2> /dev/null'; } /* Build the command line string now. */ $cmdline = implode(' ', array_merge($this->_gnupg, $options)); if ($mode == 'w') { $fp = popen($cmdline, 'w'); $win32 = !strncasecmp(PHP_OS, 'WIN', 3); if (!is_array($input)) { $input = array($input); } foreach ($input as $line) { if ($win32 && (strpos($line, "\x0d\x0a") !== false)) { $chunks = explode("\x0d\x0a", $line); foreach ($chunks as $chunk) { fputs($fp, $chunk . "\n"); } } else { fputs($fp, $line . "\n"); } } } elseif ($mode == 'r') { $fp = popen($cmdline, 'r'); while (!feof($fp)) { $data->stdout .= fgets($fp, 1024); } } pclose($fp); if ($output) { $data->output = file_get_contents($output_file); unlink($output_file); if ($stderr) { $data->stderr = file_get_contents($stderr_file); unlink($stderr_file); } } return $data; } /** * Generates a revocation certificate. * * @since Horde 3.2 * * @param string $key The private key. * @param string $email The email to use for the key. * @param string $passphrase The passphrase to use for the key. * * @return string The revocation certificate, or PEAR_Error on error. */ function generateRevocation($key, $email, $passphrase) { $keyring = $this->_putInKeyring($key, 'private'); /* Prepare the canned answers. */ $input = array(); $input[] = 'y'; // Really generate a revocation certificate $input[] = '0'; // Refuse to specify a reason $input[] = ''; // Empty comment $input[] = 'y'; // Confirm empty comment if (!empty($passphrase)) { $input[] = $passphrase; } /* Run through gpg binary. */ $cmdline = array( $keyring, '--command-fd 0', '--gen-revoke' . ' ' . $email, ); $results = $this->_callGpg($cmdline, 'w', $input, true); /* If the key is empty, something went wrong. */ if (empty($results->output)) { return PEAR::raiseError(_("Revocation key not generated successfully."), 'horde.error'); } return $results->output; } } * @package Horde_Crypt */ class Horde_Crypt_smime extends Horde_Crypt { /** * Object Identifers to name array. * * @var array */ var $_oids = array( '2.5.4.3' => 'CommonName', '2.5.4.4' => 'Surname', '2.5.4.6' => 'Country', '2.5.4.7' => 'Location', '2.5.4.8' => 'StateOrProvince', '2.5.4.9' => 'StreetAddress', '2.5.4.10' => 'Organisation', '2.5.4.11' => 'OrganisationalUnit', '2.5.4.12' => 'Title', '2.5.4.20' => 'TelephoneNumber', '2.5.4.42' => 'GivenName', '2.5.29.14' => 'id-ce-subjectKeyIdentifier', '2.5.29.14' => 'id-ce-subjectKeyIdentifier', '2.5.29.15' => 'id-ce-keyUsage', '2.5.29.17' => 'id-ce-subjectAltName', '2.5.29.19' => 'id-ce-basicConstraints', '2.5.29.31' => 'id-ce-CRLDistributionPoints', '2.5.29.32' => 'id-ce-certificatePolicies', '2.5.29.35' => 'id-ce-authorityKeyIdentifier', '2.5.29.37' => 'id-ce-extKeyUsage', '1.2.840.113549.1.9.1' => 'Email', '1.2.840.113549.1.1.1' => 'RSAEncryption', '1.2.840.113549.1.1.2' => 'md2WithRSAEncryption', '1.2.840.113549.1.1.4' => 'md5withRSAEncryption', '1.2.840.113549.1.1.5' => 'SHA-1WithRSAEncryption', '1.2.840.10040.4.3' => 'id-dsa-with-sha-1', '1.3.6.1.5.5.7.3.2' => 'id_kp_clientAuth', '2.16.840.1.113730.1.1' => 'netscape-cert-type', '2.16.840.1.113730.1.2' => 'netscape-base-url', '2.16.840.1.113730.1.3' => 'netscape-revocation-url', '2.16.840.1.113730.1.4' => 'netscape-ca-revocation-url', '2.16.840.1.113730.1.7' => 'netscape-cert-renewal-url', '2.16.840.1.113730.1.8' => 'netscape-ca-policy-url', '2.16.840.1.113730.1.12' => 'netscape-ssl-server-name', '2.16.840.1.113730.1.13' => 'netscape-comment', ); /** * Constructor. * * @param array $params Parameter array. * 'temp' => Location of temporary directory. */ function Horde_Crypt_smime($params) { $this->_tempdir = $params['temp']; } /** * Verify a passphrase for a given private key. * * @param string $private_key The user's private key. * @param string $passphrase The user's passphrase. * * @return boolean Returns true on valid passphrase, false on invalid * passphrase. * Returns PEAR_Error on error. */ function verifyPassphrase($private_key, $passphrase) { if (is_null($passphrase)) { $res = openssl_pkey_get_private($private_key); } else { $res = openssl_pkey_get_private($private_key, $passphrase); } return is_resource($res); } /** * Encrypt text using S/MIME. * * @param string $text The text to be encrypted. * @param array $params The parameters needed for encryption. * See the individual _encrypt*() functions for * the parameter requirements. * * @return string The encrypted message. * Returns PEAR_Error object on error. */ function encrypt($text, $params = array()) { /* Check for availability of OpenSSL PHP extension. */ $openssl = $this->checkForOpenSSL(); if (is_a($openssl, 'PEAR_Error')) { return $openssl; } if (isset($params['type'])) { if ($params['type'] === 'message') { return $this->_encryptMessage($text, $params); } elseif ($params['type'] === 'signature') { return $this->_encryptSignature($text, $params); } } } /** * Decrypt text via S/MIME. * * @param string $text The text to be smime decrypted. * @param array $params The parameters needed for decryption. * See the individual _decrypt*() functions for * the parameter requirements. * * @return string The decrypted message. * Returns PEAR_Error object on error. */ function decrypt($text, $params = array()) { /* Check for availability of OpenSSL PHP extension. */ $openssl = $this->checkForOpenSSL(); if (is_a($openssl, 'PEAR_Error')) { return $openssl; } if (isset($params['type'])) { if ($params['type'] === 'message') { return $this->_decryptMessage($text, $params); } elseif (($params['type'] === 'signature') || ($params['type'] === 'detached-signature')) { return $this->_decryptSignature($text, $params); } } } /** * Verify a signature using via S/MIME. * * @param string $text The multipart/signed data to be verified. * @param mixed $certs Either a single or array of root certificates. * * @return stdClass Object with the following elements: * 'result' -> Returns true on success; * PEAR_Error object on error. * 'cert' -> The certificate of the signer stored * in the message (in PEM format). * 'email' -> The email of the signing person. */ function verify($text, $certs) { /* Check for availability of OpenSSL PHP extension. */ $openssl = $this->checkForOpenSSL(); if (is_a($openssl, 'PEAR_Error')) { return $openssl; } /* Create temp files for input/output. */ $input = $this->_createTempFile('horde-smime'); $output = $this->_createTempFile('horde-smime'); /* Write text to file */ $fp = fopen($input, 'w+'); fwrite($fp, $text); fclose($fp); $root_certs = array(); if (!is_array($certs)) { $certs = array($certs); } foreach ($certs as $file) { if (file_exists($file)) { $root_certs[] = $file; } } $ob = new stdClass; if (!empty($root_certs)) { $result = openssl_pkcs7_verify($input, 0, $output, $root_certs); /* Message verified */ if ($result === true) { $ob->result = true; $ob->cert = file_get_contents($output); $ob->email = $this->getEmailFromKey($ob->cert); return $ob; } } /* Try again without verfying the signer's cert */ $result = openssl_pkcs7_verify($input, PKCS7_NOVERIFY, $output); if ($result === true) { $ob->result = PEAR::raiseError(_("Message Verified Successfully but the signer's certificate could not be verified."), 'horde.warning'); } elseif ($result == -1) { $ob->result = PEAR::raiseError(_("Verification failed - an unknown error has occurred."), 'horde.error'); } else { $ob->result = PEAR::raiseError(_("Verification failed - this message may have been tampered with."), 'horde.error'); } $ob->cert = file_get_contents($output); $ob->email = $this->getEmailFromKey($ob->cert); return $ob; } /** * Extract the contents from signed S/MIME data. * * @param string $data The signed S/MIME data. * @param string $sslpath The path to the OpenSSL binary. * * @return string The contents embedded in the signed data. * Returns PEAR_Error on error. */ function extractSignedContents($data, $sslpath) { /* Check for availability of OpenSSL PHP extension. */ $openssl = $this->checkForOpenSSL(); if (is_a($openssl, 'PEAR_Error')) { return $openssl; } /* Create temp files for input/output. */ $input = $this->_createTempFile('horde-smime'); $output = $this->_createTempFile('horde-smime'); /* Write text to file. */ $fp = fopen($input, 'w'); fwrite($fp, $data); fclose($fp); unset($data); exec($sslpath . ' smime -verify -noverify -nochain -in ' . $input . ' -out ' . $output); $ret = file_get_contents($output); return $ret ? $ret : PEAR::raiseError(_("OpenSSL error: Could not extract data from signed S/MIME part."), 'horde.error'); } /** * Sign a MIME_Part using S/MIME. * * @param MIME_Part $mime_part The MIME_Part object to sign. * @param array $params The parameters required for signing. * * @return MIME_Part A MIME_Part object that is signed. * Returns PEAR_Error object on error. */ function signMIMEPart($mime_part, $params) { require_once 'Horde/MIME/Part.php'; require_once 'Horde/MIME/Structure.php'; /* Sign the part as a message */ $message = $this->encrypt($mime_part->toCanonicalString(), $params); /* Break the result into its components */ $mime_message = MIME_Structure::parseTextMIMEMessage($message); $smime_sign = $mime_message->getPart(2); $smime_sign->setDescription(_("S/MIME Cryptographic Signature")); $smime_sign->transferDecodeContents(); $smime_sign->setTransferEncoding('base64'); $smime_part = new MIME_Part('multipart/signed'); $smime_part->setContents('This is a cryptographically signed message in MIME format.' . "\n"); $smime_part->addPart($mime_part); $smime_part->addPart($smime_sign); $smime_part->setContentTypeParameter('protocol', 'application/pkcs7-signature'); $smime_part->setContentTypeParameter('micalg', 'sha1'); return $smime_part; } /** * Encrypt a MIME_Part using S/MIME. * * @param MIME_Part $mime_part The MIME_Part object to encrypt. * @param array $params The parameters required for encryption. * * @return MIME_Part A MIME_Part object that is encrypted. * Returns PEAR_Error on error. */ function encryptMIMEPart($mime_part, $params = array()) { require_once 'Horde/MIME/Part.php'; require_once 'Horde/MIME/Structure.php'; /* Sign the part as a message */ $message = $this->encrypt($mime_part->toCanonicalString(), $params); if (is_a($message, 'PEAR_Error')) { return $message; } /* Get charset for mime part description. */ $charset = NLS::getEmailCharset(); /* Break the result into its components */ $mime_message = MIME_Structure::parseTextMIMEMessage($message); $smime_part = $mime_message->getBasePart(); $smime_part->setCharset($charset); $smime_part->setDescription(String::convertCharset(_("S/MIME Encrypted Message"), NLS::getCharset(), $charset)); $smime_part->transferDecodeContents(); $smime_part->setTransferEncoding('base64'); $smime_part->setDisposition('inline'); /* By default, encrypt() produces a message with type * 'application/x-pkcs7-mime' and no 'smime-type' parameter. Per * RFC 2311, the more correct MIME type is 'application/pkcs7-mime' * and the smime-type should be 'enveloped-data'. */ $smime_part->setType('application/pkcs7-mime'); $smime_part->setContentTypeParameter('smime-type', 'enveloped-data'); return $smime_part; } /** * Encrypt a message in S/MIME format using a public key. * * @access private * * @param string $text The text to be encrypted. * @param array $params The parameters needed for encryption. *
     * Parameters:
     * ===========
     * 'type'    =>  'message' (REQUIRED)
     * 'pubkey'  =>  public key. (REQUIRED)
     * 'email'   =>  E-mail address of recipient. If not present, or not found
     *               in the public key, the first e-mail address found in the
     *               key will be used instead. (Optional)
     * 
* * @return string The encrypted message. * Return PEAR_Error object on error. */ function _encryptMessage($text, $params) { $email = null; /* Check for required parameters. */ if (!isset($params['pubkey'])) { return PEAR::raiseError(_("A public S/MIME key is required to encrypt a message."), 'horde.error'); } /* Create temp files for input/output. */ $input = $this->_createTempFile('horde-smime'); $output = $this->_createTempFile('horde-smime'); /* Store message in file. */ $fp1 = fopen($input, 'w+'); fputs($fp1, $text); fclose($fp1); if (isset($params['email'])) { $email = $params['email']; } else { $email = $this->getEmailFromKey($params['pubkey']); if (is_null($email)) { return PEAR::raiseError(_("Could not determine the recipient's e-mail address."), 'horde.error'); } } /* Encrypt the document. */ if (openssl_pkcs7_encrypt($input, $output, $params['pubkey'], array('To' => $email))) { $result = file_get_contents($output); if (!empty($result)) { return $this->_fixContentType($result, 'encrypt'); } } return PEAR::raiseError(_("Could not S/MIME encrypt message."), 'horde.error'); } /** * Sign a message in S/MIME format using a private key. * * @access private * * @param string $text The text to be signed. * @param array $params The parameters needed for signing. *
     * Parameters:
     * ===========
     * 'certs'       =>  Additional signing certs (Optional)
     * 'passphrase'  =>  Passphrase for key (REQUIRED)
     * 'privkey'     =>  Private key (REQUIRED)
     * 'pubkey'      =>  Public key (REQUIRED)
     * 'sigtype'     =>  Determine the signature type to use. (Optional)
     *                   'cleartext'  --  Make a clear text signature
     *                   'detach'     --  Make a detached signature (DEFAULT)
     * 'type'        =>  'signature' (REQUIRED)
     * 
* * @return string The signed message. * Return PEAR_Error object on error. */ function _encryptSignature($text, $params) { /* Check for secure connection. */ $secure_check = $this->requireSecureConnection(); if (is_a($secure_check, 'PEAR_Error')) { return $secure_check; } /* Check for required parameters. */ if (!isset($params['pubkey']) || !isset($params['privkey']) || !array_key_exists('passphrase', $params)) { return PEAR::raiseError(_("A public S/MIME key, private S/MIME key, and passphrase are required to sign a message."), 'horde.error'); } /* Create temp files for input/output/certificates. */ $input = $this->_createTempFile('horde-smime'); $output = $this->_createTempFile('horde-smime'); $certs = $this->_createTempFile('horde-smime'); /* Store message in temporary file. */ $fp = fopen($input, 'w+'); fputs($fp, $text); fclose($fp); /* Store additional certs in temporary file. */ if (!empty($params['certs'])) { $fp = fopen($certs, 'w+'); fputs($fp, $params['certs']); fclose($fp); } /* Determine the signature type to use. */ if (isset($params['sigtype']) && ($params['sigtype'] == 'cleartext')) { $flags = PKCS7_TEXT; } else { $flags = PKCS7_DETACHED; } $privkey = (is_null($params['passphrase'])) ? $params['privkey'] : array($params['privkey'], $params['passphrase']); if (empty($params['certs'])) { $res = openssl_pkcs7_sign($input, $output, $params['pubkey'], $privkey, array(), $flags); } else { $res = openssl_pkcs7_sign($input, $output, $params['pubkey'], $privkey, array(), $flags, $certs); } if (!$res) { return PEAR::raiseError(_("Could not S/MIME sign message."), 'horde.error'); } $data = file_get_contents($output); return $this->_fixContentType($data, 'signature'); } /** * Decrypt an S/MIME encrypted message using a private/public keypair * and a passhprase. * * @access private * * @param string $text The text to be decrypted. * @param array $params The parameters needed for decryption. *
     * Parameters:
     * ===========
     * 'type'        =>  'message' (REQUIRED)
     * 'pubkey'      =>  public key. (REQUIRED)
     * 'privkey'     =>  private key. (REQUIRED)
     * 'passphrase'  =>  Passphrase for Key. (REQUIRED)
     * 
* * @return string The decrypted message. * Returns PEAR_Error object on error. */ function _decryptMessage($text, $params) { /* Check for secure connection. */ $secure_check = $this->requireSecureConnection(); if (is_a($secure_check, 'PEAR_Error')) { return $secure_check; } /* Check for required parameters. */ if (!isset($params['pubkey']) || !isset($params['privkey']) || !array_key_exists('passphrase', $params)) { return PEAR::raiseError(_("A public S/MIME key, private S/MIME key, and passphrase are required to decrypt a message."), 'horde.error'); } /* Create temp files for input/output. */ $input = $this->_createTempFile('horde-smime'); $output = $this->_createTempFile('horde-smime'); /* Store message in file. */ $fp = fopen($input, 'w+'); fputs($fp, trim($text)); fclose($fp); $privkey = (is_null($params['passphrase'])) ? $params['privkey'] : array($params['privkey'], $params['passphrase']); if (openssl_pkcs7_decrypt($input, $output, $params['pubkey'], $privkey)) { return file_get_contents($output); } return PEAR::raiseError(_("Could not decrypt S/MIME data."), 'horde.error'); } /** * Sign and Encrypt a MIME_Part using S/MIME. * * @param MIME_Part $mime_part The MIME_Part object to sign and encrypt. * @param array $sign_params The parameters required for signing. See * _encryptSignature(). * @param array $encrypt_params The parameters required for encryption. * See _encryptMessage(). * * @return MIME_Part A MIME_Part object that is signed and encrypted. * Returns PEAR_Error on error. */ function signAndEncryptMIMEPart($mime_part, $sign_params = array(), $encrypt_params = array()) { include_once 'Horde/MIME/Part.php'; $part = $this->signMIMEPart($mime_part, $sign_params); if (is_a($part, 'PEAR_Error')) { return $part; } $part = $this->encryptMIMEPart($part, $encrypt_params); if (is_a($part, 'PEAR_Error')) { return $part; } return $part; } /** * Convert a PEM format certificate to readable HTML version * * @param string $cert PEM format certificate * * @return string HTML detailing the certificate. */ function certToHTML($cert) { /* Common Fields */ $fieldnames = array( 'Email' => _("Email Address"), 'CommonName' => _("Common Name"), 'Organisation' => _("Organisation"), 'OrganisationalUnit' => _("Organisational Unit"), 'Country' => _("Country"), 'StateOrProvince' => _("State or Province"), 'Location' => _("Location"), 'StreetAddress' => _("Street Address"), 'TelephoneNumber' => _("Telephone Number"), 'Surname' => _("Surname"), 'GivenName' => _("Given Name") ); /* Netscape Extensions */ $fieldnames += array( 'netscape-cert-type' => _("Netscape certificate type"), 'netscape-base-url' => _("Netscape Base URL"), 'netscape-revocation-url' => _("Netscape Revocation URL"), 'netscape-ca-revocation-url' => _("Netscape CA Revocation URL"), 'netscape-cert-renewal-url' => _("Netscape Renewal URL"), 'netscape-ca-policy-url' => _("Netscape CA policy URL"), 'netscape-ssl-server-name' => _("Netscape SSL server name"), 'netscape-comment' => _("Netscape certificate comment") ); /* X590v3 Extensions */ $fieldnames += array( 'id-ce-extKeyUsage' => _("X509v3 Extended Key Usage"), 'id-ce-basicConstraints' => _("X509v3 Basic Constraints"), 'id-ce-subjectAltName' => _("X509v3 Subject Alternative Name"), 'id-ce-subjectKeyIdentifier' => _("X509v3 Subject Key Identifier"), 'id-ce-certificatePolicies' => _("Certificate Policies"), 'id-ce-CRLDistributionPoints' => _("CRL Distribution Points"), 'id-ce-keyUsage' => _("Key Usage") ); $cert_details = $this->parseCert($cert); if (!is_array($cert_details)) { return '
' . _("Unable to extract certificate details") . '
'; } $certificate = $cert_details['certificate']; $text = '
';

        /* Subject (a/k/a Certificate Owner) */
        if (isset($certificate['subject'])) {
            $text .= "" . _("Certificate Owner") . ":\n";

            foreach ($certificate['subject'] as $key => $value) {
                if (isset($fieldnames[$key])) {
                    $text .= sprintf("  %s: %s\n", $fieldnames[$key], $value);
                } else {
                    $text .= sprintf("  *%s: %s\n", $key, $value);
                }
            }
            $text .= "\n";
        }

        /* Issuer */
        if (isset($certificate['issuer'])) {
            $text .= "" . _("Issuer") . ":\n";

            foreach ($certificate['issuer'] as $key => $value) {
                if (isset($fieldnames[$key])) {
                    $text .= sprintf("  %s: %s\n", $fieldnames[$key], $value);
                } else {
                    $text .= sprintf("  *%s: %s\n", $key, $value);
                }
            }
            $text .= "\n";
        }

        /* Dates  */
        $text .= "" . _("Validity") . ":\n";
        $text .= sprintf("  %s: %s\n", _("Not Before"), strftime("%x %X", $certificate['validity']['notbefore']));
        $text .= sprintf("  %s: %s\n", _("Not After"), strftime("%x %X", $certificate['validity']['notafter']));
        $text .= "\n";

        /* Certificate Owner - Public Key Info */
        $text .= "" . _("Public Key Info") . ":\n";
        $text .= sprintf("  %s: %s\n", _("Public Key Algorithm"), $certificate['subjectPublicKeyInfo']['algorithm']);
        if ($certificate['subjectPublicKeyInfo']['algorithm'] == 'rsaEncryption') {
            if (Util::extensionExists('bcmath')) {
                $modulus = $certificate['subjectPublicKeyInfo']['subjectPublicKey']['modulus'];
                $modulus_hex = '';
                while ($modulus != '0') {
                    $modulus_hex = dechex(bcmod($modulus, '16')) . $modulus_hex;
                    $modulus = bcdiv($modulus, '16', 0);
                }

                if ((strlen($modulus_hex) > 64) &&
                    (strlen($modulus_hex) < 128)) {
                    str_pad($modulus_hex, 128, '0', STR_PAD_RIGHT);
                } elseif ((strlen($modulus_hex) > 128) &&
                          (strlen($modulus_hex) < 256)) {
                    str_pad($modulus_hex, 256, '0', STR_PAD_RIGHT);
                }

                $text .= "  " . sprintf(_("RSA Public Key (%d bit)"), strlen($modulus_hex) * 4) . ":\n";

                $modulus_str = '';
                for ($i = 0; $i < strlen($modulus_hex); $i += 2) {
                    if (($i % 32) == 0) {
                        $modulus_str .= "\n      ";
                    }
                    $modulus_str .= substr($modulus_hex, $i, 2) . ':';
                }

                $text .= sprintf("    %s: %s\n", _("Modulus"), $modulus_str);
            }

            $text .= sprintf("    %s: %s\n", _("Exponent"), $certificate['subjectPublicKeyInfo']['subjectPublicKey']['publicExponent']);
        }
        $text .= "\n";

        /* X509v3 extensions */
        if (isset($certificate['extensions'])) {
            $text .= "" . _("X509v3 extensions") . ":\n";

            foreach ($certificate['extensions'] as $key => $value) {
                if (is_array($value)) {
                    $value = _("Unsupported Extension");
                }
                if (isset($fieldnames[$key])) {
                    $text .= sprintf("  %s:\n    %s\n", $fieldnames[$key], wordwrap($value, 40, "\n    "));
                } else {
                    $text .= sprintf("  %s:\n    %s\n", $key, wordwrap($value, 60, "\n    "));
                }
            }

            $text .= "\n";
        }

        /* Certificate Details */
        $text .= "" . _("Certificate Details") . ":\n";
        $text .= sprintf("  %s: %d\n", _("Version"), $certificate['version']);
        $text .= sprintf("  %s: %d\n", _("Serial Number"), $certificate['serialNumber']);

        foreach ($cert_details['fingerprints'] as $hash => $fingerprint) {
            $label = sprintf(_("%s Fingerprint"), String::upper($hash));
            $fingerprint_str = '';
            for ($i = 0; $i < strlen($fingerprint); $i += 2) {
                $fingerprint_str .= substr($fingerprint, $i, 2) . ':';
            }
            $text .= sprintf("  %s:\n      %s\n", $label, $fingerprint_str);
        }
        $text .= sprintf("  %s: %s\n", _("Signature Algorithm"), $cert_details['signatureAlgorithm']);
        $text .= sprintf("  %s:", _("Signature"));

        $sig_str = '';
        for ($i = 0; $i < strlen($cert_details['signature']); $i++) {
            if (($i % 16) == 0) {
                $sig_str .= "\n      ";
            }
            $sig_str .= sprintf("%02x:", ord($cert_details['signature'][$i]));
        }

        return $text . $sig_str . "\n
"; } /** * Extract the contents of a PEM format certificate to an array. * * @param string $cert PEM format certificate * * @return array Array containing all extractable information about * the certificate. */ function parseCert($cert) { $cert_split = preg_split('/(-----((BEGIN)|(END)) CERTIFICATE-----)/', $cert); if (!isset($cert_split[1])) { $raw_cert = base64_decode($cert); } else { $raw_cert = base64_decode($cert_split[1]); } $cert_data = $this->_parseASN($raw_cert); if (!is_array($cert_data) || ($cert_data[0] == 'UNKNOWN') || ($cert_data[1][0] == 'UNKNOWN') || /* Bug #8751: Check for required number of fields. The ASN * parsing code doesn't seem to be able to handle v1 data - it * combines the version and serial number fields. * openssl_x509_parse() works, but doesn't have a stable API. * Since v1 is such an old standard anyway, best just to abort * here. */ !isset($cert_data[1][0][1][6])) { return false; } $cert_details = array(); $cert_details['fingerprints']['md5'] = md5($raw_cert); $cert_details['fingerprints']['sha1'] = sha1($raw_cert); $cert_details['certificate']['extensions'] = array(); $cert_details['certificate']['version'] = $cert_data[1][0][1][0][1] + 1; $cert_details['certificate']['serialNumber'] = $cert_data[1][0][1][1][1]; $cert_details['certificate']['signature'] = $cert_data[1][0][1][2][1][0][1]; $cert_details['certificate']['issuer'] = $cert_data[1][0][1][3][1]; $cert_details['certificate']['validity'] = $cert_data[1][0][1][4][1]; $cert_details['certificate']['subject'] = @$cert_data[1][0][1][5][1]; $cert_details['certificate']['subjectPublicKeyInfo'] = $cert_data[1][0][1][6][1]; $cert_details['signatureAlgorithm'] = $cert_data[1][1][1][0][1]; $cert_details['signature'] = $cert_data[1][2][1]; // issuer $issuer = array(); foreach ($cert_details['certificate']['issuer'] as $value) { $issuer[$value[1][1][0][1]] = $value[1][1][1][1]; } $cert_details['certificate']['issuer'] = $issuer; // subject $subject = array(); foreach ($cert_details['certificate']['subject'] as $value) { $subject[$value[1][1][0][1]] = $value[1][1][1][1]; } $cert_details['certificate']['subject'] = $subject; // validity $vals = $cert_details['certificate']['validity']; $cert_details['certificate']['validity'] = array(); $cert_details['certificate']['validity']['notbefore'] = $vals[0][1]; $cert_details['certificate']['validity']['notafter'] = $vals[1][1]; foreach ($cert_details['certificate']['validity'] as $key => $val) { $year = substr($val, 0, 2); $month = substr($val, 2, 2); $day = substr($val, 4, 2); $hour = substr($val, 6, 2); $minute = substr($val, 8, 2); if (($val[11] == '-') || ($val[9] == '+')) { // handle time zone offset here $seconds = 0; } elseif (String::upper($val[11]) == 'Z') { $seconds = 0; } else { $seconds = substr($val, 10, 2); if (($val[11] == '-') || ($val[9] == '+')) { // handle time zone offset here } } $cert_details['certificate']['validity'][$key] = mktime ($hour, $minute, $seconds, $month, $day, $year); } // Split the Public Key into components. $subjectPublicKeyInfo = array(); $subjectPublicKeyInfo['algorithm'] = $cert_details['certificate']['subjectPublicKeyInfo'][0][1][0][1]; if ($subjectPublicKeyInfo['algorithm'] == 'rsaEncryption') { $subjectPublicKey = $this->_parseASN($cert_details['certificate']['subjectPublicKeyInfo'][1][1]); $subjectPublicKeyInfo['subjectPublicKey']['modulus'] = $subjectPublicKey[1][0][1]; $subjectPublicKeyInfo['subjectPublicKey']['publicExponent'] = $subjectPublicKey[1][1][1]; } $cert_details['certificate']['subjectPublicKeyInfo'] = $subjectPublicKeyInfo; if (isset($cert_data[1][0][1][7]) && is_array($cert_data[1][0][1][7][1])) { foreach ($cert_data[1][0][1][7][1] as $ext) { $oid = $ext[1][0][1]; $cert_details['certificate']['extensions'][$oid] = $ext[1][1]; } } $i = 9; while (isset($cert_data[1][0][1][$i]) && is_array($cert_data[1][0][1][$i][1])) { $oid = $cert_data[1][0][1][$i][1][0][1]; $cert_details['certificate']['extensions'][$oid] = $cert_data[1][0][1][$i][1][1]; ++$i; } foreach ($cert_details['certificate']['extensions'] as $oid => $val) { switch ($oid) { case 'netscape-base-url': case 'netscape-revocation-url': case 'netscape-ca-revocation-url': case 'netscape-cert-renewal-url': case 'netscape-ca-policy-url': case 'netscape-ssl-server-name': case 'netscape-comment': $val = $this->_parseASN($val[1]); $cert_details['certificate']['extensions'][$oid] = $val[1]; break; case 'id-ce-subjectAltName': $val = $this->_parseASN($val[1]); $cert_details['certificate']['extensions'][$oid] = ''; foreach ($val[1] as $name) { if (!empty($cert_details['certificate']['extensions'][$oid])) { $cert_details['certificate']['extensions'][$oid] .= ', '; } $cert_details['certificate']['extensions'][$oid] .= $name[1]; } break; case 'netscape-cert-type': $val = $this->_parseASN($val[1]); $val = ord($val[1]); $newVal = ''; if ($val & 0x80) { $newVal .= empty($newVal) ? 'SSL client' : ', SSL client'; } if ($val & 0x40) { $newVal .= empty($newVal) ? 'SSL server' : ', SSL server'; } if ($val & 0x20) { $newVal .= empty($newVal) ? 'S/MIME' : ', S/MIME'; } if ($val & 0x10) { $newVal .= empty($newVal) ? 'Object Signing' : ', Object Signing'; } if ($val & 0x04) { $newVal .= empty($newVal) ? 'SSL CA' : ', SSL CA'; } if ($val & 0x02) { $newVal .= empty($newVal) ? 'S/MIME CA' : ', S/MIME CA'; } if ($val & 0x01) { $newVal .= empty($newVal) ? 'Object Signing CA' : ', Object Signing CA'; } $cert_details['certificate']['extensions'][$oid] = $newVal; break; case 'id-ce-extKeyUsage': $val = $this->_parseASN($val[1]); $val = $val[1]; $newVal = ''; if ($val[0][1] != 'sequence') { $val = array($val); } else { $val = $val[1][1]; } foreach ($val as $usage) { if ($usage[1] == 'id_kp_clientAuth') { $newVal .= empty($newVal) ? 'TLS Web Client Authentication' : ', TLS Web Client Authentication'; } else { $newVal .= empty($newVal) ? $usage[1] : ', ' . $usage[1]; } } $cert_details['certificate']['extensions'][$oid] = $newVal; break; case 'id-ce-subjectKeyIdentifier': $val = $this->_parseASN($val[1]); $val = $val[1]; $newVal = ''; for ($i = 0; $i < strlen($val); $i++) { $newVal .= sprintf("%02x:", ord($val[$i])); } $cert_details['certificate']['extensions'][$oid] = $newVal; break; case 'id-ce-authorityKeyIdentifier': $val = $this->_parseASN($val[1]); if ($val[0] == 'string') { $val = $val[1]; $newVal = ''; for ($i = 0; $i < strlen($val); $i++) { $newVal .= sprintf("%02x:", ord($val[$i])); } $cert_details['certificate']['extensions'][$oid] = $newVal; } else { $cert_details['certificate']['extensions'][$oid] = _("Unsupported Extension"); } break; case 'id-ce-basicConstraints': case 'default': $cert_details['certificate']['extensions'][$oid] = _("Unsupported Extension"); break; } } return $cert_details; } /** * Attempt to parse ASN.1 formated data. * * @access private * * @param string $data ASN.1 formated data * * @return array Array contained the extracted values. */ function _parseASN($data) { $result = array(); while (strlen($data) > 1) { $class = ord($data[0]); switch ($class) { case 0x30: // Sequence $len = ord($data[1]); $bytes = 0; if ($len & 0x80) { $bytes = $len & 0x0f; $len = 0; for ($i = 0; $i < $bytes; $i++) { $len = ($len << 8) | ord($data[$i + 2]); } } $sequence_data = substr($data, 2 + $bytes, $len); $data = substr($data, 2 + $bytes + $len); $values = $this->_parseASN($sequence_data); if (!is_array($values) || is_string($values[0])) { $values = array($values); } $sequence_values = array(); $i = 0; foreach ($values as $val) { if ($val[0] == 'extension') { $sequence_values['extensions'][] = $val; } else { $sequence_values[$i++] = $val; } } $result[] = array('sequence', $sequence_values); break; case 0x31: // Set of $len = ord($data[1]); $bytes = 0; if ($len & 0x80) { $bytes = $len & 0x0f; $len = 0; for ($i = 0; $i < $bytes; $i++) { $len = ($len << 8) | ord($data[$i + 2]); } } $sequence_data = substr($data, 2 + $bytes, $len); $data = substr($data, 2 + $bytes + $len); $result[] = array('set', $this->_parseASN($sequence_data)); break; case 0x01: // Boolean type $boolean_value = (ord($data[2]) == 0xff); $data = substr($data, 3); $result[] = array('boolean', $boolean_value); break; case 0x02: // Integer type $len = ord($data[1]); $bytes = 0; if ($len & 0x80) { $bytes = $len & 0x0f; $len = 0; for ($i = 0; $i < $bytes; $i++) { $len = ($len << 8) | ord($data[$i + 2]); } } $integer_data = substr($data, 2 + $bytes, $len); $data = substr($data, 2 + $bytes + $len); $value = 0; if ($len <= 4) { /* Method works fine for small integers */ for ($i = 0; $i < strlen($integer_data); $i++) { $value = ($value << 8) | ord($integer_data[$i]); } } else { /* Method works for arbitrary length integers */ if (Util::extensionExists('bcmath')) { for ($i = 0; $i < strlen($integer_data); $i++) { $value = bcadd(bcmul($value, 256), ord($integer_data[$i])); } } else { $value = -1; } } $result[] = array('integer(' . $len . ')', $value); break; case 0x03: // Bitstring type $len = ord($data[1]); $bytes = 0; if ($len & 0x80) { $bytes = $len & 0x0f; $len = 0; for ($i = 0; $i < $bytes; $i++) { $len = ($len << 8) | ord($data[$i + 2]); } } $bitstring_data = substr($data, 3 + $bytes, $len); $data = substr($data, 2 + $bytes + $len); $result[] = array('bit string', $bitstring_data); break; case 0x04: // Octetstring type $len = ord($data[1]); $bytes = 0; if ($len & 0x80) { $bytes = $len & 0x0f; $len = 0; for ($i = 0; $i < $bytes; $i++) { $len = ($len << 8) | ord($data[$i + 2]); } } $octectstring_data = substr($data, 2 + $bytes, $len); $data = substr($data, 2 + $bytes + $len); $result[] = array('octet string', $octectstring_data); break; case 0x05: // Null type $data = substr($data, 2); $result[] = array('null', null); break; case 0x06: // Object identifier type $len = ord($data[1]); $bytes = 0; if ($len & 0x80) { $bytes = $len & 0x0f; $len = 0; for ($i = 0; $i < $bytes; $i++) { $len = ($len << 8) | ord($data[$i + 2]); } } $oid_data = substr($data, 2 + $bytes, $len); $data = substr($data, 2 + $bytes + $len); // Unpack the OID $plain = floor(ord($oid_data[0]) / 40); $plain .= '.' . ord($oid_data[0]) % 40; $value = 0; $i = 1; while ($i < strlen($oid_data)) { $value = $value << 7; $value = $value | (ord($oid_data[$i]) & 0x7f); if (!(ord($oid_data[$i]) & 0x80)) { $plain .= '.' . $value; $value = 0; } $i++; } if (isset($this->_oids[$plain])) { $result[] = array('oid', $this->_oids[$plain]); } else { $result[] = array('oid', $plain); } break; case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x81: case 0x80: // Character string type $len = ord($data[1]); $bytes = 0; if ($len & 0x80) { $bytes = $len & 0x0f; $len = 0; for ($i = 0; $i < $bytes; $i++) { $len = ($len << 8) | ord($data[$i + 2]); } } $string_data = substr($data, 2 + $bytes, $len); $data = substr($data, 2 + $bytes + $len); $result[] = array('string', $string_data); break; case 0x17: // Time types $len = ord($data[1]); $bytes = 0; if ($len & 0x80) { $bytes = $len & 0x0f; $len = 0; for ($i = 0; $i < $bytes; $i++) { $len = ($len << 8) | ord($data[$i + 2]); } } $time_data = substr($data, 2 + $bytes, $len); $data = substr($data, 2 + $bytes + $len); $result[] = array('utctime', $time_data); break; case 0x82: // X509v3 extensions? $len = ord($data[1]); $bytes = 0; if ($len & 0x80) { $bytes = $len & 0x0f; $len = 0; for ($i = 0; $i < $bytes; $i++) { $len = ($len << 8) | ord($data[$i + 2]); } } $sequence_data = substr($data, 2 + $bytes, $len); $data = substr($data, 2 + $bytes + $len); $result[] = array('extension', 'X509v3 extensions'); $result[] = $this->_parseASN($sequence_data); break; case 0xa0: case 0xa3: // Extensions $extension_data = substr($data, 0, 2); $data = substr($data, 2); $result[] = array('extension', dechex($extension_data)); break; case 0xe6: $extension_data = substr($data, 0, 1); $data = substr($data, 1); $result[] = array('extension', dechex($extension_data)); break; case 0xa1: $extension_data = substr($data, 0, 1); $data = substr($data, 6); $result[] = array('extension', dechex($extension_data)); break; default: // Unknown $result[] = array('UNKNOWN', dechex($data)); $data = ''; break; } } return (count($result) > 1) ? $result : array_pop($result); } /** * Decrypt an S?MIME signed message using a public key. * * @access private * * @param string $text The text to be verified. * @param array $params The parameters needed for verification. * * @return string The verification message. * Returns PEAR_Error object on error. */ function _decryptSignature($text, $params) { return PEAR::raiseError('_decryptSignature() ' . _("not yet implemented")); } /** * Check for the presence of the OpenSSL extension to PHP. * * @return boolean Returns true if the openssl extension is available. * Returns a PEAR_Error if not. */ function checkForOpenSSL() { if (!Util::extensionExists('openssl')) { return PEAR::raiseError(_("The openssl module is required for the Horde_Crypt_smime:: class.")); } } /** * Extract the email address from a public key. * * @param string $key The public key. * * @return mixed Returns the first email address found, or null if * there are none. */ function getEmailFromKey($key) { $key_info = openssl_x509_parse($key); if (!is_array($key_info)) { return null; } if (isset($key_info['subject'])) { if (isset($key_info['subject']['Email'])) { return $key_info['subject']['Email']; } elseif (isset($key_info['subject']['emailAddress'])) { return $key_info['subject']['emailAddress']; } } // Check subjectAltName per http://www.ietf.org/rfc/rfc3850.txt if (isset($key_info['extensions']['subjectAltName'])) { $names = preg_split('/\s*,\s*/', $key_info['extensions']['subjectAltName'], -1, PREG_SPLIT_NO_EMPTY); foreach ($names as $name) { if (strpos($name, ':') === false) { continue; } list($kind, $value) = explode(':', $name, 2); if (String::lower($kind) == 'email') { return $value; } } } return null; } /** * Convert a PKCS 12 encrypted certificate package into a private key, * public key, and any additional keys. * * @param string $text The PKCS 12 data. * @param array $params The parameters needed for parsing. *
     * Parameters:
     * ===========
     * 'sslpath' => The path to the OpenSSL binary. (REQUIRED)
     * 'password' => The password to use to decrypt the data. (Optional)
     * 'newpassword' => The password to use to encrypt the private key.
     *                  (Optional)
     * 
* * @return stdClass An object. * 'private' - The private key in PEM format. * 'public' - The public key in PEM format. * 'certs' - An array of additional certs. * Returns PEAR_Error on error. */ function parsePKCS12Data($pkcs12, $params) { /* Check for availability of OpenSSL PHP extension. */ $openssl = $this->checkForOpenSSL(); if (is_a($openssl, 'PEAR_Error')) { return $openssl; } if (!isset($params['sslpath'])) { return PEAR::raiseError(_("No path to the OpenSSL binary provided. The OpenSSL binary is necessary to work with PKCS 12 data."), 'horde.error'); } $sslpath = escapeshellcmd($params['sslpath']); /* Create temp files for input/output. */ $input = $this->_createTempFile('horde-smime'); $output = $this->_createTempFile('horde-smime'); $ob = new stdClass; /* Write text to file */ $fp = fopen($input, 'w+'); fwrite($fp, $pkcs12); fclose($fp); /* Extract the private key from the file first. */ $cmdline = $sslpath . ' pkcs12 -in ' . $input . ' -out ' . $output . ' -nocerts'; if (isset($params['password'])) { $cmdline .= ' -passin stdin'; if (!empty($params['newpassword'])) { $cmdline .= ' -passout stdin'; } else { $cmdline .= ' -nodes'; } $fd = popen($cmdline, 'w'); fwrite($fd, $params['password'] . "\n"); if (!empty($params['newpassword'])) { fwrite($fd, $params['newpassword'] . "\n"); } pclose($fd); } else { $cmdline .= ' -nodes'; exec($cmdline); } $ob->private = trim(file_get_contents($output)); if (empty($ob->private)) { return PEAR::raiseError(_("Password incorrect"), 'horde.error'); } /* Extract the client public key next. */ $cmdline = $sslpath . ' pkcs12 -in ' . $input . ' -out ' . $output . ' -nokeys -clcerts'; if (isset($params['password'])) { $cmdline .= ' -passin stdin'; $fd = popen($cmdline, 'w'); fwrite($fd, $params['password'] . "\n"); pclose($fd); } else { exec($cmdline); } $ob->public = trim(file_get_contents($output)); /* Extract the CA public key next. */ $cmdline = $sslpath . ' pkcs12 -in ' . $input . ' -out ' . $output . ' -nokeys -cacerts'; if (isset($params['password'])) { $cmdline .= ' -passin stdin'; $fd = popen($cmdline, 'w'); fwrite($fd, $params['password'] . "\n"); pclose($fd); } else { exec($cmdline); } $ob->certs = trim(file_get_contents($output)); return $ob; } /** * The Content-Type parameters PHP's openssl_pkcs7_* functions return are * deprecated. Fix these headers to the correct ones (see RFC 2311). * * @access private * * @param string $text The PKCS7 data. * @param string $type Is this 'message' or 'signature' data? * * @return string The PKCS7 data with the correct Content-Type parameter. */ function _fixContentType($text, $type) { if ($type == 'message') { $from = 'application/x-pkcs7-mime'; $to = 'application/pkcs7-mime'; } else { $from = 'application/x-pkcs7-signature'; $to = 'application/pkcs7-signature'; } return str_replace('Content-Type: ' . $from, 'Content-Type: ' . $to, $text); } } * @since Horde 3.0 * @package Horde_Crypt */ class Horde_Crypt { /** * The temporary directory to use. * * @var string */ var $_tempdir; /** * Attempts to return a concrete Horde_Crypt instance based on $driver. * * @param mixed $driver The type of concrete Horde_Crypt subclass to * return. If $driver is an array, then we will look * in $driver[0]/lib/Crypt/ for the subclass * implementation named $driver[1].php. * @param array $params A hash containing any additional configuration or * parameters a subclass might need. * * @return Horde_Crypt The newly created concrete Horde_Crypt instance, or * false on an error. */ function factory($driver, $params = array()) { if (is_array($driver)) { list($app, $driver) = $driver; } /* Return a base Horde_Crypt object if no driver is specified. */ $driver = basename($driver); if (empty($driver) || (strcmp($driver, 'none') == 0)) { return new Horde_Crypt(); } $class = 'Horde_Crypt_' . $driver; if (!class_exists($class)) { if (!empty($app)) { include_once $GLOBALS['registry']->get('fileroot', $app) . '/lib/Crypt/' . $driver . '.php'; } else { include_once 'Horde/Crypt/' . $driver . '.php'; } } if (class_exists($class)) { $crypt = new $class($params); } else { $crypt = PEAR::raiseError('Class definition of ' . $class . ' not found.'); } return $crypt; } /** * Attempts to return a reference to a concrete Horde_Crypt instance * based on $driver. It will only create a new instance if no * Horde_Crypt instance with the same parameters currently exists. * * This should be used if multiple crypto backends (and, thus, * multiple Horde_Crypt instances) are required. * * This method must be invoked as: $var = &Horde_Crypt::singleton() * * @param mixed $driver The type of concrete Horde_Crypt subclass to * return. If $driver is an array, then we will look * in $driver[0]/lib/Crypt/ 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_Crypt The concrete Horde_Crypt reference, or false on an * error. */ function &singleton($driver, $params = array()) { static $instances = array(); $signature = serialize(array($driver, $params)); if (!isset($instances[$signature])) { $instances[$signature] = Horde_Crypt::factory($driver, $params); } return $instances[$signature]; } /** * Outputs error message if we are not using a secure connection. * * @return PEAR_Error Returns a PEAR_Error object if there is no secure * connection. */ function requireSecureConnection() { if ($GLOBALS['browser']->usingSSLConnection()) { return; } if (!empty($GLOBALS['conf']['safe_ips'])) { if (reset($GLOBALS['conf']['safe_ips']) == '*') { return; } /* $_SERVER['HTTP_X_FORWARDED_FOR'] is user data and not * reliable. We dont' consult it for safe IPs. We also * have to assume that if it is present, the user is * coming through a proxy server. If so, we don't count * any non-SSL connection as safe, no matter the source * IP. */ if (!isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $remote_addr = $_SERVER['REMOTE_ADDR']; foreach ($GLOBALS['conf']['safe_ips'] as $safe_ip) { $safe_ip = preg_replace('/(\.0)*$/', '', $safe_ip); if (strpos($remote_addr, $safe_ip) === 0) { return; } } } } return PEAR::raiseError(_("The encryption features require a secure web connection.")); } /** * Encrypt the requested data. * This method should be provided by all classes that extend Horde_Crypt. * * @param string $data The data to encrypt. * @param array $params An array of arguments needed to encrypt the data. * * @return array The encrypted data. */ function encrypt($data, $params = array()) { return $data; } /** * Decrypt the requested data. * This method should be provided by all classes that extend Horde_Crypt. * * @param string $data The data to decrypt. * @param array $params An array of arguments needed to decrypt the data. * * @return array The decrypted data. */ function decrypt($data, $params = array()) { return $data; } /** * Create a temporary file that will be deleted at the end of this * process. * * @access private * * @param string $descrip Description string to use in filename. * @param boolean $delete Delete the file automatically? * * @return string Filename of a temporary file. */ function _createTempFile($descrip = 'horde-crypt', $delete = true) { return Util::getTempFile($descrip, $delete, $this->_tempdir, true); } } * @author Chuck Hagenbuch * @since Horde 1.3 * @package Horde_Data */ class Horde_Data_csv extends Horde_Data { var $_extension = 'csv'; var $_contentType = 'text/comma-separated-values'; /** * Tries to discover the CSV file's parameters. * * @param string $filename The name of the file to investigate. * * @return array An associative array with the following possible keys: *
     * 'sep':    The field separator
     * 'quote':  The quoting character
     * 'fields': The number of fields (columns)
     * 
*/ function discoverFormat($filename) { return File_CSV::discoverFormat($filename); } /** * Imports and parses a CSV file. * * @param string $filename The name of the file to parse. * @param boolean $header Does the first line contain the field/column * names? * @param string $sep The field/column separator. * @param string $quote The quoting character. * @param integer $fields The number or fields/columns. * @param string $charset The file's charset. @since Horde 3.1. * @param string $crlf The file's linefeed characters. @since Horde 3.1. * * @return array A two-dimensional array of all imported data rows. If * $header was true the rows are associative arrays with the * field/column names as the keys. */ function importFile($filename, $header = false, $sep = '', $quote = '', $fields = null, $import_mapping = array(), $charset = null, $crlf = null) { /* File_CSV is a bit picky at what parameters it expects. */ $conf = array(); if ($fields) { $conf['fields'] = $fields; } else { return array(); } if (!empty($quote)) { $conf['quote'] = $quote; } if (empty($sep)) { $conf['sep'] = ','; } else { $conf['sep'] = $sep; } if (!empty($crlf)) { $conf['crlf'] = $crlf; } /* Strip and keep the first line if it contains the field * names. */ if ($header) { $head = File_CSV::read($filename, $conf); if (is_a($head, 'PEAR_Error')) { return $head; } if (!empty($charset)) { $head = String::convertCharset($head, $charset, NLS::getCharset()); } } $data = array(); while ($line = File_CSV::read($filename, $conf)) { if (is_a($line, 'PEAR_Error')) { return $line; } if (!empty($charset)) { $line = String::convertCharset($line, $charset, NLS::getCharset()); } if (!isset($head)) { $data[] = $line; } else { $newline = array(); for ($i = 0; $i < count($head); $i++) { if (isset($import_mapping[$head[$i]])) { $head[$i] = $import_mapping[$head[$i]]; } $cell = $line[$i]; $cell = preg_replace("/\"\"/", "\"", $cell); $newline[$head[$i]] = empty($cell) ? '' : $cell; } $data[] = $newline; } } $fp = File_CSV::getPointer($filename, $conf); if ($fp && !is_a($fp, 'PEAR_Error')) { rewind($fp); } $this->_warnings = File_CSV::warning(); return $data; } /** * Builds a CSV file from a given data structure and returns it as a * string. * * @param array $data A two-dimensional array containing the data set. * @param boolean $header If true, the rows of $data are associative * arrays with field names as their keys. * * @return string The CSV data. */ function exportData($data, $header = false, $export_mapping = array()) { if (!is_array($data) || count($data) == 0) { return ''; } $export = ''; $eol = "\n"; $head = array_keys(current($data)); if ($header) { foreach ($head as $key) { if (!empty($key)) { if (isset($export_mapping[$key])) { $key = $export_mapping[$key]; } $export .= '"' . $key . '"'; } $export .= ','; } $export = substr($export, 0, -1) . $eol; } foreach ($data as $row) { foreach ($head as $key) { $cell = $row[$key]; if (!empty($cell) || $cell === 0) { $export .= '"' . $cell . '"'; } $export .= ','; } $export = substr($export, 0, -1) . $eol; } return $export; } /** * Builds a CSV file from a given data structure and triggers its * download. It DOES NOT exit the current script but only outputs the * correct headers and data. * * @param string $filename The name of the file to be downloaded. * @param array $data A two-dimensional array containing the data * set. * @param boolean $header If true, the rows of $data are associative * arrays with field names as their keys. */ function exportFile($filename, $data, $header = false, $export_mapping = array()) { $export = $this->exportData($data, $header, $export_mapping); $GLOBALS['browser']->downloadHeaders($filename, 'application/csv', false, strlen($export)); echo $export; } /** * Takes all necessary actions for the given import step, parameters and * form values and returns the next necessary step. * * @param integer $action The current step. One of the IMPORT_* constants. * @param array $param An associative array containing needed * parameters for the current step. * * @return mixed Either the next step as an integer constant or imported * data set after the final step. */ function nextStep($action, $param = array()) { switch ($action) { case IMPORT_FILE: $next_step = parent::nextStep($action, $param); if (is_a($next_step, 'PEAR_Error')) { return $next_step; } /* Move uploaded file so that we can read it again in the next step after the user gave some format details. */ $file_name = Horde::getTempFile('import', false); if (!move_uploaded_file($_FILES['import_file']['tmp_name'], $file_name)) { return PEAR::raiseError(_("The uploaded file could not be saved.")); } $_SESSION['import_data']['file_name'] = $file_name; /* Try to discover the file format ourselves. */ $conf = $this->discoverFormat($file_name); if (!$conf) { $conf = array('sep' => ','); } $_SESSION['import_data'] = array_merge($_SESSION['import_data'], $conf); /* Check if charset was specified. */ $_SESSION['import_data']['charset'] = Util::getFormData('charset'); /* Read the file's first two lines to show them to the user. */ $_SESSION['import_data']['first_lines'] = ''; $fp = @fopen($file_name, 'r'); if ($fp) { $line_no = 1; while ($line_no < 3 && $line = fgets($fp)) { if (!empty($_SESSION['import_data']['charset'])) { $line = String::convertCharset($line, $_SESSION['import_data']['charset'], NLS::getCharset()); } $newline = String::length($line) > 100 ? "\n" : ''; $_SESSION['import_data']['first_lines'] .= substr($line, 0, 100) . $newline; $line_no++; } } return IMPORT_CSV; case IMPORT_CSV: $_SESSION['import_data']['header'] = Util::getFormData('header'); $import_mapping = array(); if (isset($param['import_mapping'])) { $import_mapping = $param['import_mapping']; } $import_data = $this->importFile($_SESSION['import_data']['file_name'], $_SESSION['import_data']['header'], Util::getFormData('sep'), Util::getFormData('quote'), Util::getFormData('fields'), $import_mapping, $_SESSION['import_data']['charset'], $_SESSION['import_data']['crlf']); $_SESSION['import_data']['data'] = $import_data; unset($_SESSION['import_data']['map']); return IMPORT_MAPPED; default: return parent::nextStep($action, $param); } } } * @author Karsten Fourmont * @package Horde_Data * @since Horde 3.0 */ class Horde_Data_icalendar extends Horde_Data_imc { } * @package Horde_Data * @since Horde 3.0 */ class Horde_Data_imc extends Horde_Data { var $_iCal = false; function importData($text) { $this->_iCal = new Horde_iCalendar(); if (!$this->_iCal->parsevCalendar($text)) { return PEAR::raiseError(_("There was an error importing the iCalendar data.")); } return $this->_iCal->getComponents(); } /** * Builds an iCalendar file from a given data structure and * returns it as a string. * * @param array $data An array containing Horde_iCalendar_vevent * objects * @param string $method The iTip method to use. * * @return string The iCalendar data. */ function exportData($data, $method = 'REQUEST') { $this->_iCal = new Horde_iCalendar(); $this->_iCal->setAttribute('METHOD', $method); foreach ($data as $event) { $this->_iCal->addComponent($event); } return $this->_iCal->exportvCalendar(); } /** * Builds an iCalendar file from a given data structure and * triggers its download. It DOES NOT exit the current script but * only outputs the correct headers and data. * * @param string $filename The name of the file to be downloaded. * @param array $data An array containing Horde_iCalendar_vevents */ function exportFile($filename, $data) { $export = $this->exportData($data); $GLOBALS['browser']->downloadHeaders($filename, 'text/calendar', false, strlen($export)); echo $export; } /** * Takes all necessary actions for the given import step, * parameters and form values and returns the next necessary step. * * @param integer $action The current step. One of the IMPORT_* constants. * @param array $param An associative array containing needed * parameters for the current step. * @return mixed Either the next step as an integer constant or imported * data set after the final step. */ function nextStep($action, $param = array()) { switch ($action) { case IMPORT_FILE: $next_step = parent::nextStep($action, $param); if (is_a($next_step, 'PEAR_Error')) { return $next_step; } $import_data = $this->importFile($_FILES['import_file']['tmp_name']); if (is_a($import_data, 'PEAR_Error')) { return $import_data; } return $this->_iCal->getComponents(); break; default: return parent::nextStep($action, $param); break; } } } * @author Chuck Hagenbuch * @since Horde 1.3 * @package Horde_Data */ class Horde_Data_tsv extends Horde_Data { var $_extension = 'tsv'; var $_contentType = 'text/tab-separated-values'; /** * Convert data file contents to list of data records. * * @param string $contents Data file contents. * @param boolean $header True if a header row is present. * @param string $delimiter Field delimiter. * * @return array List of data records. */ function importData($contents, $header = false, $delimiter = "\t") { if ($_SESSION['import_data']['format'] == 'pine') { $contents = preg_replace('/\n +/', '', $contents); } $contents = explode("\n", $contents); $data = array(); if ($header) { $head = explode($delimiter, array_shift($contents)); } foreach ($contents as $line) { if (trim($line) == '') { continue; } $line = explode($delimiter, $line); if (!isset($head)) { $data[] = $line; } else { $newline = array(); for ($i = 0; $i < count($head); $i++) { $newline[$head[$i]] = empty($line[$i]) ? '' : $line[$i]; } $data[] = $newline; } } return $data; } /** * Builds a TSV file from a given data structure and returns it as a * string. * * @param array $data A two-dimensional array containing the data set. * @param boolean $header If true, the rows of $data are associative * arrays with field names as their keys. * * @return string The TSV data. */ function exportData($data, $header = false) { if (!is_array($data) || count($data) == 0) { return ''; } $export = ''; $head = array_keys(current($data)); if ($header) { $export = implode("\t", $head) . "\n"; } foreach ($data as $row) { foreach ($head as $key) { $cell = $row[$key]; if (!empty($cell) || $cell === 0) { $export .= $cell; } $export .= "\t"; } $export = substr($export, 0, -1) . "\n"; } return $export; } /** * Builds a TSV file from a given data structure and triggers its download. * It DOES NOT exit the current script but only outputs the correct headers * and data. * * @param string $filename The name of the file to be downloaded. * @param array $data A two-dimensional array containing the data * set. * @param boolean $header If true, the rows of $data are associative * arrays with field names as their keys. */ function exportFile($filename, $data, $header = false) { $export = $this->exportData($data, $header); $GLOBALS['browser']->downloadHeaders($filename, 'text/tab-separated-values', false, strlen($export)); echo $export; } /** * Takes all necessary actions for the given import step, parameters and * form values and returns the next necessary step. * * @param integer $action The current step. One of the IMPORT_* constants. * @param array $param An associative array containing needed * parameters for the current step. * * @return mixed Either the next step as an integer constant or imported * data set after the final step. */ function nextStep($action, $param = array()) { switch ($action) { case IMPORT_FILE: $next_step = parent::nextStep($action, $param); if (is_a($next_step, 'PEAR_Error')) { return $next_step; } if ($_SESSION['import_data']['format'] == 'mulberry' || $_SESSION['import_data']['format'] == 'pine') { $_SESSION['import_data']['data'] = $this->importFile($_FILES['import_file']['tmp_name']); $format = $_SESSION['import_data']['format']; if ($format == 'mulberry') { $appKeys = array('alias', 'name', 'email', 'company', 'workAddress', 'workPhone', 'homePhone', 'fax', 'notes'); $dataKeys = array(0, 1, 2, 3, 4, 5, 6, 7, 9); } elseif ($format == 'pine') { $appKeys = array('alias', 'name', 'email', 'notes'); $dataKeys = array(0, 1, 2, 4); } foreach ($appKeys as $key => $app) { $map[$dataKeys[$key]] = $app; } $data = array(); foreach ($_SESSION['import_data']['data'] as $row) { $hash = array(); if ($format == 'mulberry') { if (preg_match("/^Grp:/", $row[0]) || empty($row[1])) { continue; } $row[1] = preg_replace('/^([^,"]+),\s*(.*)$/', '$2 $1', $row[1]); foreach ($dataKeys as $key) { if (array_key_exists($key, $row)) { $hash[$key] = stripslashes(preg_replace('/\\\\r/', "\n", $row[$key])); } } } elseif ($format == 'pine') { if (count($row) < 3 || preg_match("/^#DELETED/", $row[0]) || preg_match("/[()]/", $row[2])) { continue; } $row[1] = preg_replace('/^([^,"]+),\s*(.*)$/', '$2 $1', $row[1]); /* Address can be a full RFC822 address */ require_once 'Horde/MIME.php'; $addr_arr = MIME::parseAddressList($row[2]); if (is_a($addr_arr, 'PEAR_Error') || empty($addr_arr[0]->mailbox)) { continue; } $row[2] = $addr_arr[0]->mailbox . '@' . $addr_arr[0]->host; if (empty($row[1]) && !empty($addr_arr[0]->personal)) { $row[1] = $addr_arr[0]->personal; } foreach ($dataKeys as $key) { if (array_key_exists($key, $row)) { $hash[$key] = $row[$key]; } } } $data[] = $hash; } $_SESSION['import_data']['data'] = $data; $_SESSION['import_data']['map'] = $map; $ret = $this->nextStep(IMPORT_DATA, $param); return $ret; } /* Move uploaded file so that we can read it again in the next step after the user gave some format details. */ $uploaded = Browser::wasFileUploaded('import_file', _("TSV file")); if (is_a($uploaded, 'PEAR_Error')) { return PEAR::raiseError($uploaded->getMessage()); } $file_name = Horde::getTempFile('import', false); if (!move_uploaded_file($_FILES['import_file']['tmp_name'], $file_name)) { return PEAR::raiseError(_("The uploaded file could not be saved.")); } $_SESSION['import_data']['file_name'] = $file_name; /* Read the file's first two lines to show them to the user. */ $_SESSION['import_data']['first_lines'] = ''; $fp = @fopen($file_name, 'r'); if ($fp) { $line_no = 1; while ($line_no < 3 && $line = fgets($fp)) { $newline = String::length($line) > 100 ? "\n" : ''; $_SESSION['import_data']['first_lines'] .= substr($line, 0, 100) . $newline; $line_no++; } } return IMPORT_TSV; break; case IMPORT_TSV: $_SESSION['import_data']['header'] = Util::getFormData('header'); $import_data = $this->importFile($_SESSION['import_data']['file_name'], $_SESSION['import_data']['header']); $_SESSION['import_data']['data'] = $import_data; unset($_SESSION['import_data']['map']); return IMPORT_MAPPED; break; default: return parent::nextStep($action, $param); break; } } } INDX( v(P`Pii6(~ (& csv.phpp\ii6(2z  icalendar.phppZii6(2z  ICALEN~1.PHP`Pii6( " imc.phpp^ii6(&qϵ  outlookcsv.phppZii6(&qϵ  OUTLOO~1.PHP`Pii6(ֻ ($ tsv.phphTii6(޼ p vcard.phphTii6(tI  vnote.phphTii6(tI  vtodo.php * @since Horde 3.0 * @package Horde_Data */ class Horde_Data_vcard extends Horde_Data_imc { /** * Exports vcalendar data as a string. Unlike vEvent, vCard data * is not enclosed in BEGIN|END:vCalendar. * * @param array $data An array containing Horde_iCalendar_vcard * objects. * @param string $method The iTip method to use. * * @return string The iCalendar data. */ function exportData($data, $method = 'REQUEST') { $s = ''; foreach ($data as $vcard) { $s.= $vcard->exportvCalendar(); } return $s; } } * @author Chuck Hagenbuch * @package Horde_Data * @since Horde 3.0 */ class Horde_Data_vnote extends Horde_Data_imc { /** * Exports vcalendar data as a string. Unlike vEvent, vNote data * is not enclosed in BEGIN|END:vCalendar. * * @param array $data An array containing Horde_iCalendar_vnote * objects. * @param string $method The iTip method to use. * * @return string The iCalendar data. */ function exportData($data, $method = 'REQUEST') { global $prefs; $this->_iCal = &new Horde_iCalendar(); $this->_iCal->setAttribute('METHOD', $method); $s = ''; foreach ($data as $event) { $s.= $event->exportvCalendar(); } return $s; } } * @author Chuck Hagenbuch * @package Horde_Data * @since Horde 3.0 */ class Horde_Data_vtodo extends Horde_Data_imc { } * @author Chuck Hagenbuch * @since Horde 1.3 * @package Horde_Data */ class Horde_Data extends PEAR { /** * File extension. * * @var string */ var $_extension; /** * MIME content type. * * @var string */ var $_contentType = 'text/plain'; /** * A list of warnings raised during the last operation. * * @var array * @since Horde 3.1 */ var $_warnings = array(); /** * Stub to import passed data. */ function importData() { } /** * Stub to return exported data. */ function exportData() { } /** * Stub to import a file. */ function importFile($filename, $header = false) { $data = file_get_contents($filename); return $this->importData($data, $header); } /** * Stub to export data to a file. */ function exportFile() { } /** * Tries to determine the expected newline character based on the * platform information passed by the browser's agent header. * * @return string The guessed expected newline characters, either \n, \r * or \r\n. */ function getNewline() { require_once 'Horde/Browser.php'; $browser = &Browser::singleton(); switch ($browser->getPlatform()) { case 'win': return "\r\n"; case 'mac': return "\r"; case 'unix': default: return "\n"; } } /** * Returns the full filename including the basename and extension. * * @param string $basename Basename for the file. * * @return string The file name. */ function getFilename($basename) { return $basename . '.' . $this->_extension; } /** * Returns the content type. * * @return string The content type. */ function getContentType() { return $this->_contentType; } /** * Returns a list of warnings that have been raised during the last * operation. * * @since Horde 3.1 * * @return array A (possibly empty) list of warnings. */ function warnings() { return $this->_warnings; } /** * Attempts to return a concrete Horde_Data instance based on $format. * * @param mixed $format The type of concrete Horde_Data subclass to * return. If $format is an array, then we will look * in $format[0]/lib/Data/ for the subclass * implementation named $format[1].php. * * @return Horde_Data The newly created concrete Horde_Data instance, or * false on an error. */ function &factory($format) { if (is_array($format)) { $app = $format[0]; $format = $format[1]; } $format = basename($format); if (empty($format) || (strcmp($format, 'none') == 0)) { $data =& new Horde_Data(); return $data; } if (!empty($app)) { require_once $GLOBALS['registry']->get('fileroot', $app) . '/lib/Data/' . $format . '.php'; } else { require_once 'Horde/Data/' . $format . '.php'; } $class = 'Horde_Data_' . $format; if (class_exists($class)) { $data =& new $class(); } else { $data = PEAR::raiseError('Class definition of ' . $class . ' not found.'); } return $data; } /** * Attempts to return a reference to a concrete Horde_Data instance * based on $format. It will only create a new instance if no Horde_Data * instance with the same parameters currently exists. * * This should be used if multiple data sources (and, thus, multiple * Horde_Data instances) are required. * * This method must be invoked as: $var = &Horde_Data::singleton() * * @param string $format The type of concrete Horde_Data subclass to * return. * * @return Horde_Data The concrete Horde_Data reference, or false on an * error. */ function &singleton($format) { static $instances = array(); $signature = serialize($format); if (!isset($instances[$signature])) { $instances[$signature] = &Horde_Data::factory($format); } return $instances[$signature]; } /** * Maps a date/time string to an associative array. * * The method signature has changed in Horde 3.1.3. * * @access private * * @param string $date The date. * @param string $type One of 'date', 'time' or 'datetime'. * @param array $params Two-dimensional array with additional information * about the formatting. Possible keys are: * - delimiter - The character that seperates the * different date/time parts. * - format - If 'ampm' and $date contains a time we * assume that it is in AM/PM format. * - order - If $type is 'datetime' the order of the * day and time parts: -1 (timestamp), 0 * (day/time), 1 (time/day). * @param integer $key The key to use for $params. * * @return string The date or time in ISO format. */ function mapDate($date, $type, $params, $key) { switch ($type) { case 'date': case 'monthday': case 'monthdayyear': $dates = explode($params['delimiter'][$key], $date); if (count($dates) != 3) { return $date; } $index = array_flip(explode('/', $params['format'][$key])); return sprintf('%d-%02d-%02d', $dates[$index['year']], $dates[$index['month']], $dates[$index['mday']]); case 'time': $dates = explode($params['delimiter'][$key], $date); if (count($dates) < 2 || count($dates) > 3) { return $date; } if ($params['format'][$key] == 'ampm') { if (strpos(strtolower($dates[count($dates)-1]), 'pm') !== false) { if ($dates[0] !== '12') { $dates[0] += 12; } } elseif ($dates[0] == '12') { $dates[0] = '0'; } $dates[count($dates) - 1] = sprintf('%02d', $dates[count($dates)-1]); } return $dates[0] . ':' . $dates[1] . (count($dates) == 3 ? (':' . $dates[2]) : ':00'); case 'datetime': switch ($params['order'][$key]) { case -1: return (string)(int)$date == $date ? date('Y-m-d H:i:s', $date) : $date; case 0: list($day, $time) = explode(' ', $date, 2); break; case 1: list($time, $day) = explode(' ', $date, 2); break; } $date = $this->mapDate($day, 'date', array('delimiter' => $params['day_delimiter'], 'format' => $params['day_format']), $key); $time = $this->mapDate($time, 'time', array('delimiter' => $params['time_delimiter'], 'format' => $params['time_format']), $key); return $date . ' ' . $time; } } /** * Takes all necessary actions for the given import step, parameters and * form values and returns the next necessary step. * * @param integer $action The current step. One of the IMPORT_* constants. * @param array $param An associative array containing needed * parameters for the current step. * * @return mixed Either the next step as an integer constant or imported * data set after the final step. */ function nextStep($action, $param = array()) { /* First step. */ if (is_null($action)) { $_SESSION['import_data'] = array(); return IMPORT_FILE; } switch ($action) { case IMPORT_FILE: /* Sanitize uploaded file. */ $import_format = Util::getFormData('import_format'); $check_upload = Browser::wasFileUploaded('import_file', $param['file_types'][$import_format]); if (is_a($check_upload, 'PEAR_Error')) { return $check_upload; } if ($_FILES['import_file']['size'] <= 0) { return PEAR::raiseError(_("The file contained no data.")); } $_SESSION['import_data']['format'] = $import_format; break; case IMPORT_MAPPED: $dataKeys = Util::getFormData('dataKeys', ''); $appKeys = Util::getFormData('appKeys', ''); if (empty($dataKeys) || empty($appKeys)) { global $registry; return PEAR::raiseError(sprintf(_("You didn't map any fields from the imported file to the corresponding fields in %s."), $registry->get('name'))); } $dataKeys = explode("\t", $dataKeys); $appKeys = explode("\t", $appKeys); $map = array(); $dates = array(); foreach ($appKeys as $key => $app) { $map[$dataKeys[$key]] = $app; if (isset($param['time_fields']) && isset($param['time_fields'][$app])) { $dates[$dataKeys[$key]]['type'] = $param['time_fields'][$app]; $dates[$dataKeys[$key]]['values'] = array(); $i = 0; /* Build an example array of up to 10 date/time fields. */ while ($i < count($_SESSION['import_data']['data']) && count($dates[$dataKeys[$key]]['values']) < 10) { if (!empty($_SESSION['import_data']['data'][$i][$dataKeys[$key]])) { $dates[$dataKeys[$key]]['values'][] = $_SESSION['import_data']['data'][$i][$dataKeys[$key]]; } $i++; } } } $_SESSION['import_data']['map'] = $map; if (count($dates) > 0) { foreach ($dates as $key => $data) { if (count($data['values'])) { $_SESSION['import_data']['dates'] = $dates; return IMPORT_DATETIME; } } } return $this->nextStep(IMPORT_DATA, $param); case IMPORT_DATETIME: case IMPORT_DATA: if ($action == IMPORT_DATETIME) { $params = array('delimiter' => Util::getFormData('delimiter'), 'format' => Util::getFormData('format'), 'order' => Util::getFormData('order'), 'day_delimiter' => Util::getFormData('day_delimiter'), 'day_format' => Util::getFormData('day_format'), 'time_delimiter' => Util::getFormData('time_delimiter'), 'time_format' => Util::getFormData('time_format')); } if (!isset($_SESSION['import_data']['data'])) { return PEAR::raiseError(_("The uploaded data was lost since the previous step.")); } /* Build the result data set as an associative array. */ $data = array(); foreach ($_SESSION['import_data']['data'] as $row) { $data_row = array(); foreach ($row as $key => $val) { if (isset($_SESSION['import_data']['map'][$key])) { $mapped_key = $_SESSION['import_data']['map'][$key]; if ($action == IMPORT_DATETIME && !empty($val) && isset($param['time_fields']) && isset($param['time_fields'][$mapped_key])) { $val = $this->mapDate($val, $param['time_fields'][$mapped_key], $params, $key); } $data_row[$_SESSION['import_data']['map'][$key]] = $val; } } $data[] = $data_row; } return $data; } } /** * Cleans the session data up and removes any uploaded and moved * files. If a function called "_cleanup()" exists, this gets * called too. * * @return mixed If _cleanup() was called, the return value of this call. * This should be the value of the first import step. */ function cleanup() { if (isset($_SESSION['import_data']['file_name'])) { @unlink($_SESSION['import_data']['file_name']); } $_SESSION['import_data'] = array(); if (function_exists('_cleanup')) { return _cleanup(); } } } * @author Chuck Hagenbuch * @since Horde 3.0 * @package Horde_DataTree */ class DataTree_null extends DataTree { /** * Cache of attributes for any objects created during this page request. * * @var array */ var $_attributeCache = array(); /** * Cache of data for any objects created during this page request. * * @var array */ var $_dataCache = array(); /** * Load (a subset of) the datatree into the $_data array. Part of the * DataTree API that must be overridden by subclasses. * * @param string $root Which portion of the tree to load. Defaults to * all of it. * @param boolean $reload Re-load already loaded values? * * @return mixed True on success or a PEAR_Error on failure. * * @access private */ function _load($root = null, $reload = false) { } /** * Load a specific object identified by its unique ID ($id), and * its parents, into the $_data array. * * @param integer $cid The unique ID of the object to load. * * @return mixed True on success or a PEAR_Error on failure. * * @access private */ function _loadById($cid) { } /** * Check for existance of an object in a backend-specific manner. * * @param string $object_name Object name to check for. * * @return boolean True if the object exists, false otherwise. */ function _exists($object_name) { return false; } /** * Look up a datatree id by name. * * @param string $name * * @return integer DataTree id */ function _getId($name) { return null; } /** * Look up a datatree name by id. * * @param integer $id * * @return string DataTree name */ function _getName($id) { return null; } /** * Get a tree sorted by the specified attribute name and/or key. * * @since Horde 3.1 * * @param string $root Which portion of the tree to sort. * Defaults to all of it. * @param boolean $loadTree Sort the tree starting at $root, or just the * requested level and direct parents? * Defaults to single level. * @param array $sortby_name Attribute name to use for sorting. * @param array $sortby_key Attribute key to use for sorting. * @param array $direction Sort direction: * 0 - ascending * 1 - descending * * @return array TODO */ function getSortedTree($root, $loadTree = false, $sortby_name = null, $sortby_key = null, $direction = 0) { return array(); } /** * Add an object. Part of the DataTree API that must be * overridden by subclasses. * * @param mixed $fullname The object to add (string or DataTreeObject). */ function add($object) { if (is_a($object, 'DataTreeObject')) { $fullname = $object->getName(); $order = $object->order; } else { $fullname = $object; $order = null; } $id = md5(mt_rand()); if (strpos($fullname, ':') !== false) { $parts = explode(':', $fullname); $name = array_pop($parts); $parent = implode(':', $parts); $pid = $this->getId($parent); if (is_a($pid, 'PEAR_Error')) { $this->add($parent); } } else { $pid = DATATREE_ROOT; } if (parent::exists($fullname)) { return PEAR::raiseError('Already exists'); } $added = parent::_add($fullname, $id, $pid, $order); if (is_a($added, 'PEAR_Error')) { return $added; } return $this->updateData($object); } /** * Change order of the children of an object. * * @param string $parents The parent id string path. * @param mixed $order A specific new order position or an array * containing the new positions for the given * $parents object. * @param integer $cid If provided indicates insertion of a new child * to the object, and will be used to avoid * incrementing it when shifting up all other * children's order. If not provided indicates * deletion, hence shift all other positions down * one. */ function reorder($parents, $order = null, $cid = null) { if (is_array($order) && !empty($order)) { // Multi update. $this->_reorder($pid, $order); } } /** * Explicitly set the order for a datatree object. * * @param integer $id The datatree object id to change. * @param integer $order The new order. */ function setOrder($id, $order) { } /** * Removes an object. * * @param mixed $object The object to remove. * @param boolean $force Force removal of every child object? */ function remove($object, $force = false) { } /** * Remove one or more objects by id. This function does *not* do * the validation, reordering, etc. that remove() does. If you * need to check for children, re-do ordering, etc., then you must * remove() objects one-by-one. This is for code that knows it's * dealing with single (non-parented) objects and needs to delete * a batch of them quickly. * * @param array $ids The objects to remove. */ function removeByIds($ids) { } /** * Remove one or more objects by name. This function does *not* do * the validation, reordering, etc. that remove() does. If you * need to check for children, re-do ordering, etc., then you must * remove() objects one-by-one. This is for code that knows it's * dealing with single (non-parented) objects and needs to delete * a batch of them quickly. * * @param array $names The objects to remove. */ function removeByNames($names) { } /** * Move an object to a new parent. * * @param mixed $object The object to move. * @param string $newparent The new parent object. Defaults to the root. */ function move($object, $newparent = null) { } /** * Change an object's name. * * @param mixed $old_object The old object. * @param string $new_object_name The new object name. */ function rename($old_object, $new_object_name) { } /** * Retrieve data for an object from the datatree_data field. * * @param integer $cid The object id to fetch, or an array of object ids. */ function getData($cid) { return isset($this->_dataCache[$cid]) ? $this->_dataCache[$cid] : array(); } /** * Retrieve data for an object. * * @param integer $cid The object id to fetch. */ function getAttributes($cid) { if (is_array($cid)) { $data = array(); foreach ($cid as $id) { if (isset($this->_attributeCache[$id])) { $data[$id] = $this->_attributeCache[$id]; } } return $data; } else { return isset($this->_attributeCache[$cid]) ? $this->_attributeCache[$cid] : array(); } } /** * Returns the number of objects matching a set of attribute * criteria. * * @see buildAttributeQuery() * * @param array $criteria The array of criteria. * @param string $parent The parent node to start searching from. * @param boolean $allLevels Return all levels, or just the direct * children of $parent? Defaults to all levels. * @param string $restrict Only return attributes with the same * attribute_name or attribute_id. */ function countByAttributes($criteria, $parent = DATATREE_ROOT, $allLevels = true, $restrict = 'name') { if (!count($criteria)) { return 0; } return count($this->_attributeCache); } /** * Returns a set of object ids based on a set of attribute criteria. * * @see buildAttributeQuery() * * @param array $criteria The array of criteria. * @param string $parent The parent node to start searching from. * @param boolean $allLevels Return all levels, or just the direct * children of $parent? Defaults to all levels. * @param string $restrict Only return attributes with the same * attribute_name or attribute_id. * @param integer $from The object to start to fetching * @param integer $count The number of objects to fetch * @param string $sortby_name Attribute name to use for sorting. * @param string $sortby_key Attribute key to use for sorting. * @param integer $direction Sort direction: * 0 - ascending * 1 - descending */ function getByAttributes($criteria, $parent = DATATREE_ROOT, $allLevels = true, $restrict = 'name', $from = 0, $count = 0, $sortby_name = null, $sortby_key = null, $direction = 0) { if (!count($criteria)) { return PEAR::raiseError('no criteria'); } $cids = array(); foreach (array_keys($this->_attributeCache) as $cid) { $cids[$cid] = null; } return $cids; } /** * Sorts IDs by attribute values. IDs without attributes will be * added to the end of the sorted list. * * @param array $unordered_ids Array of ids to sort. * @param array $sortby_name Attribute name to use for sorting. * @param array $sortby_key Attribute key to use for sorting. * @param array $direction Sort direction: * 0 - ascending * 1 - descending * * @return array Sorted ids. */ function sortByAttributes($unordered_ids, $sortby_name = null, $sortby_key = null, $direction = 0) { return $unordered_ids; } /** * Returns a list of all of the available values of the given * attribute name/key combination. Either attribute_name or * attribute_key MUST be supplied, and both MAY be supplied. * * @param string $attribute_name The name of the attribute. * @param string $attribute_key The key value of the attribute. * @param string $parent The parent node to start searching from. * @param boolean $allLevels Return all levels, or just the direct * children of $parent? * * @return array An array of all of the available values. */ function getAttributeValues($attribute_name = null, $attribute_key = null, $parent = DATATREE_ROOT, $allLevels = true) { return array(); } /** * Update the data in an object. Does not change the object's * parent or name, just serialized data. * * @param string $object The object. */ function updateData($object) { if (!is_a($object, 'DataTreeObject')) { return true; } $cid = $this->getId($object->getName()); if (is_a($cid, 'PEAR_Error')) { return $cid; } // We handle data differently if we can map it to // attributes. if (method_exists($object, '_toAttributes')) { $this->_attributeCache[$cid] = $object->_toAttributes(); } else { $this->_dataCache[$cid] = $object->getData(); } return true; } /** * Init the object. * * @return boolean True. */ function _init() { return true; } } * 'phptype' The database type (ie. 'pgsql', 'mysql', etc.). * 'charset' The charset used by the database. * * Optional parameters:
 *   'table'        The name of the data table in 'database'.
 *                  DEFAULT: 'horde_datatree'
* * Required by some database implementations:
 *   'database'     The name of the database.
 *   'username'     The username with which to connect to the database.
 *   'password'     The password associated with 'username'.
 *   'hostspec'     The hostname of the database server.
 *   'protocol'     The communication protocol ('tcp', 'unix', etc.).
 *   'options'      Additional options to pass to the database.
 *   'port'         The port on which to connect to the database.
 *   'tty'          The TTY on which to connect to the database.
* * 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 DataTree system is in * scripts/sql/horde_datatree.sql. * * $Horde: framework/DataTree/DataTree/sql.php,v 1.156.2.46 2010-10-21 20:02:46 jan Exp $ * * Copyright 1999-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 Stephane Huther * @author Chuck Hagenbuch * @author Jan Schneider * @since Horde 2.1 * @package Horde_DataTree */ class DataTree_sql extends DataTree { /** * Handle for the current database connection, used for reading. * * @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 is not required. * * @var DB */ var $_write_db; /** * The number of copies of the horde_datatree_attributes table * that we need to join on in the current query. * * @var integer */ var $_tableCount = 1; /** * Returns a list of all groups (root nodes) of the data tree. * * @return array The the group IDs */ function getGroups() { $query = 'SELECT DISTINCT group_uid FROM ' . $this->_params['table']; Horde::logMessage('SQL Query by DataTree_sql::getGroups(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); return $this->_db->getCol($query); } /** * Loads (a subset of) the datatree into the $_data array. * * @access private * * @param string $root Which portion of the tree to load. * Defaults to all of it. * @param boolean $loadTree Load a tree starting at $root, or just the * requested level and direct parents? * Defaults to single level. * @param boolean $reload Re-load already loaded values? * @param string $sortby_name Attribute name to use for sorting. * @param string $sortby_key Attribute key to use for sorting. * @param integer $direction Sort direction: * 0 - ascending * 1 - descending * * @return mixed True on success or a PEAR_Error on failure. */ function _load($root = DATATREE_ROOT, $loadTree = false, $reload = false, $sortby_name = null, $sortby_key = null, $direction = 0) { /* Do NOT use DataTree::exists() here; that would cause an infinite * loop. */ if (!$reload && (in_array($root, $this->_nameMap) || (count($this->_data) && $root == DATATREE_ROOT)) || (!is_null($this->_sortHash) && isset($this->_data[$root]['sorter'][$this->_sortHash]))) { return true; } $query = $this->_buildLoadQuery($root, $loadTree, DATATREE_BUILD_SELECT, $sortby_name, $sortby_key, $direction); if (empty($query)) { return true; } Horde::logMessage('SQL Query by DataTree_sql::_load(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); $data = $this->_db->getAll($query); if (is_a($data, 'PEAR_Error')) { return $data; } return $this->set($data, $this->_params['charset']); } /** * Counts (a subset of) the datatree which would be loaded into the $_data * array if _load() is called with the same value of $root. * * @access private * * @param string $root Which portion of the tree to load. Defaults to all * of it. * * @return integer Number of objects */ function _count($root = DATATREE_ROOT) { $query = $this->_buildLoadQuery($root, true, DATATREE_BUILD_COUNT); if (empty($query)) { return 0; } Horde::logMessage('SQL Query by DataTree_sql::_count(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); return (int)$this->_db->getOne($query); } /** * Loads (a subset of) the datatree into the $_data array. * * @access private * * @param string $root Which portion of the tree to load. * Defaults to all of it. * @param boolean $loadTree Load a tree starting at $root, or just the * requested level and direct parents? * Defaults to single level. * @param integer $operation Type of query to build * @param string $sortby_name Attribute name to use for sorting. * @param string $sortby_key Attribute key to use for sorting. * @param integer $direction Sort direction: * 0 - ascending * 1 - descending * * @return mixed True on success or a PEAR_Error on failure. */ function _buildLoadQuery($root = DATATREE_ROOT, $loadTree = false, $operation = DATATREE_BUILD_SELECT, $sortby_name = null, $sortby_key = null, $direction = 0) { $sorted = false; $where = sprintf('c.group_uid = %s ', $this->_db->quote($this->_params['group'])); if (!empty($root) && $root != DATATREE_ROOT) { $parent_where = $this->_buildParentIds($root, $loadTree, 'c.'); if (empty($parent_where)) { return ''; } elseif (!is_a($parent_where, 'PEAR_Error')) { $where = sprintf('%s AND (%s)', $where, $parent_where); } } if (!is_null($sortby_name)) { $where = sprintf('%s AND a.attribute_name = %s ', $where, $this->_db->quote($sortby_name)); $sorted = true; } if (!is_null($sortby_key)) { $where = sprintf('%s AND a.attribute_key = %s ', $where, $this->_db->quote($sortby_key)); $sorted = true; } switch ($operation) { case DATATREE_BUILD_COUNT: $what = 'COUNT(*)'; break; default: $what = 'c.datatree_id, c.datatree_name, c.datatree_parents, c.datatree_order'; break; } if ($sorted) { $query = sprintf('SELECT %s FROM %s c LEFT JOIN %s a ON (c.datatree_id = a.datatree_id OR c.datatree_name=%s) '. 'WHERE %s GROUP BY c.datatree_id, c.datatree_name, c.datatree_parents, c.datatree_order ORDER BY a.attribute_value %s', $what, $this->_params['table'], $this->_params['table_attributes'], $this->_db->quote($root), $where, ($direction == 1) ? 'DESC' : 'ASC'); } else { $query = sprintf('SELECT %s FROM %s c WHERE %s', $what, $this->_params['table'], $where); } return $query; } /** * Builds parent ID string for selecting trees. * * @access private * * @param string $root Which portion of the tree to load. * @param boolean $loadTree Load a tree starting at $root, or just the * requested level and direct parents? * Defaults to single level. * @param string $join_name Table join name * * @return string Id list. */ function _buildParentIds($root, $loadTree = false, $join_name = '') { if (strpos($root, ':') !== false) { $parts = explode(':', $root); $root = array_pop($parts); } $root = (string)$root; $query = 'SELECT datatree_id, datatree_parents' . ' FROM ' . $this->_params['table'] . ' WHERE datatree_name = ? AND group_uid = ?' . ' ORDER BY datatree_id'; $values = array($root, $this->_params['group']); Horde::logMessage('SQL Query by DataTree_sql::_buildParentIds(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $root = $this->_db->getAssoc($query, false, $values); if (is_a($root, 'PEAR_Error') || !count($root)) { return ''; } $where = ''; $first_time = true; foreach ($root as $object_id => $object_parents) { $pstring = $object_parents . ':' . $object_id . '%'; $pquery = ''; if (!empty($object_parents)) { $ids = substr($object_parents, 1); $pquery = ' OR ' . $join_name . 'datatree_id IN (' . str_replace(':', ', ', $ids) . ')'; } if ($loadTree) { $pquery .= ' OR ' . $join_name . 'datatree_parents = ' . $this->_db->quote(substr($pstring, 0, -1)); } if (!$first_time) { $where .= ' OR '; } $where .= sprintf($join_name . 'datatree_parents LIKE %s OR ' . $join_name . 'datatree_id = %s%s', $this->_db->quote($pstring), $object_id, $pquery); $first_time = false; } return $where; } /** * Loads a set of objects identified by their unique IDs, and their * parents, into the $_data array. * * @access private * * @param mixed $cids The unique ID of the object to load, or an array of * object ids. * * @return mixed True on success or a PEAR_Error on failure. */ function _loadById($cids) { /* Make sure we have an array. */ if (!is_array($cids)) { $cids = array((int)$cids); } else { array_walk($cids, 'intval'); } /* Bail out now if there's nothing to load. */ if (!count($cids)) { return true; } /* Don't load any that are already loaded. Also, make sure that * everything in the $ids array that we are building is an integer. */ $ids = array(); foreach ($cids as $cid) { /* Do NOT use DataTree::exists() here; that would cause an * infinite loop. */ if (!isset($this->_data[$cid])) { $ids[] = (int)$cid; } } /* If there are none left to load, return. */ if (!count($ids)) { return true; } $in = array_search(DATATREE_ROOT, $ids) === false ? sprintf('datatree_id IN (%s) AND ', implode(', ', $ids)) : ''; $query = sprintf('SELECT datatree_id, datatree_parents FROM %s' . ' WHERE %sgroup_uid = %s' . ' ORDER BY datatree_id', $this->_params['table'], $in, $this->_db->quote($this->_params['group'])); Horde::logMessage('SQL Query by DataTree_sql::_loadById(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); $parents = $this->_db->getAssoc($query); if (is_a($parents, 'PEAR_Error')) { return $parents; } if (empty($parents)) { return PEAR::raiseError(_("Object not found."), null, null, null, 'DataTree ids ' . implode(', ', $ids) . ' not found.'); } $ids = array(); foreach ($parents as $cid => $parent) { $ids[(int)$cid] = (int)$cid; $pids = explode(':', substr($parent, 1)); foreach ($pids as $pid) { $pid = (int)$pid; if (!isset($this->_data[$pid])) { $ids[$pid] = $pid; } } } /* If $ids is empty, we have nothing to load. */ if (!count($ids)) { return true; } $query = 'SELECT datatree_id, datatree_name, datatree_parents, datatree_order' . ' FROM ' . $this->_params['table'] . ' WHERE datatree_id IN (?' . str_repeat(', ?', count($ids) - 1) . ')' . ' AND group_uid = ? ORDER BY datatree_id'; $values = array_merge($ids, array($this->_params['group'])); Horde::logMessage('SQL Query by DataTree_sql::_loadById(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $data = $this->_db->getAll($query, $values); if (is_a($data, 'PEAR_Error')) { return $data; } return $this->set($data, $this->_params['charset']); } /** * Check for existance of an object in a backend-specific manner. * * @param string $object_name Object name to check for. * * @return boolean True if the object exists, false otherwise. */ function _exists($object_name) { $query = 'SELECT datatree_id FROM ' . $this->_params['table'] . ' WHERE group_uid = ? AND datatree_name = ? AND datatree_parents = ?'; $object_name = String::convertCharset($object_name, NLS::getCharset(), $this->_params['charset']); $object_names = explode(':', $object_name); $object_parents = ''; foreach ($object_names as $name) { $values = array($this->_params['group'], $name, $object_parents); Horde::logMessage('SQL Query by DataTree_sql::_exists(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_db->getOne($query, $values); if (is_a($result, 'PEAR_Error') || !$result) { return false; } $object_parents .= ':' . $result; } return true; } /** * Look up a datatree id by name. * * @param string $name * * @return integer DataTree id */ function _getId($name) { $query = 'SELECT datatree_id FROM ' . $this->_params['table'] . ' WHERE group_uid = ? AND datatree_name = ?' . ' AND datatree_parents = ?'; $ids = array(); $name = String::convertCharset($name, NLS::getCharset(), $this->_params['charset']); $parts = explode(':', $name); foreach ($parts as $part) { $result = $this->_db->getOne($query, array($this->_params['group'], $part, count($ids) ? ':' . implode(':', $ids) : '')); if (is_a($result, 'PEAR_Error') || !$result) { return null; } else { $ids[] = $result; } } return (int)array_pop($ids); } /** * Look up a datatree name by id. * * @param integer $id * * @return string DataTree name */ function _getName($id) { $query = 'SELECT datatree_name FROM ' . $this->_params['table'] . ' WHERE group_uid = ? AND datatree_id = ?'; $values = array($this->_params['group'], (int)$id); Horde::logMessage('SQL Query by DataTree_sql::_getName(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $name = $this->_db->getOne($query, $values); if (is_a($name, 'PEAR_Error')) { return null; } else { $name = String::convertCharset($name, $this->_params['charset'], NLS::getCharset()); // Get the parent names, if any. $parent = $this->getParentById($id); if ($parent && !is_a($parent, 'PEAR_Error') && $parent != DATATREE_ROOT) { return $this->getName($parent) . ':' . $name; } else { return $name; } } } /** * Returns a tree sorted by the specified attribute name and/or key. * * @since Horde 3.1 * * @param string $root Which portion of the tree to sort. * Defaults to all of it. * @param boolean $loadTree Sort the tree starting at $root, or just the * requested level and direct parents? * Defaults to single level. * @param string $sortby_name Attribute name to use for sorting. * @param string $sortby_key Attribute key to use for sorting. * @param integer $direction Sort direction: * 0 - ascending * 1 - descending * * @return array TODO */ function getSortedTree($root, $loadTree = false, $sortby_name = null, $sortby_key = null, $direction = 0) { $query = $this->_buildLoadQuery($root, $loadTree, DATATREE_BUILD_SELECT, $sortby_name, $sortby_key, $direction); if (empty($query)) { return array(); } return $this->_db->getAll($query); } /** * Adds an object. * * @param mixed $object The object to add (string or * DataTreeObject). * @param boolean $id_as_name Whether the object ID is to be used as * object name. Used in situations where * there is no available unique input for * object name. */ function add($object, $id_as_name = false) { $attributes = false; if (is_a($object, 'DataTreeObject')) { $fullname = $object->getName(); $order = $object->order; /* We handle data differently if we can map it to the * horde_datatree_attributes table. */ if (method_exists($object, '_toAttributes')) { $data = ''; $ser = null; /* Set a flag for later so that we know to insert the * attribute rows. */ $attributes = true; } else { require_once 'Horde/Serialize.php'; $ser = SERIALIZE_UTF7_BASIC; $data = Horde_Serialize::serialize($object->getData(), $ser, NLS::getCharset()); } } else { $fullname = $object; $order = null; $data = ''; $ser = null; } /* Get the next unique ID. */ $id = $this->_write_db->nextId($this->_params['table']); if (is_a($id, 'PEAR_Error')) { Horde::logMessage($id, __FILE__, __LINE__, PEAR_LOG_ERR); return $id; } if (strpos($fullname, ':') !== false) { $parts = explode(':', $fullname); $parents = ''; $pstring = ''; if ($id_as_name) { /* Requested use of ID as name, so discard current name. */ array_pop($parts); /* Set name to ID. */ $name = $id; /* Modify fullname to reflect new name. */ $fullname = implode(':', $parts) . ':' . $id; if (is_a($object, 'DataTreeObject')) { $object->setName($fullname); } else { $object = $fullname; } } else { $name = array_pop($parts); } foreach ($parts as $par) { $pstring .= (empty($pstring) ? '' : ':') . $par; $pid = $this->getId($pstring); if (is_a($pid, 'PEAR_Error')) { /* Auto-create parents. */ $pid = $this->add($pstring); if (is_a($pid, 'PEAR_Error')) { return $pid; } } $parents .= ':' . $pid; } } else { if ($id_as_name) { /* Requested use of ID as name, set fullname and name to ID. */ $fullname = $id; $name = $id; if (is_a($object, 'DataTreeObject')) { $object->setName($fullname); } else { $object = $fullname; } } else { $name = $fullname; } $parents = ''; $pid = DATATREE_ROOT; } if (parent::exists($fullname)) { return PEAR::raiseError(sprintf(_("\"%s\" already exists"), $fullname)); } $query = 'INSERT INTO ' . $this->_params['table'] . ' (datatree_id, group_uid, datatree_name, datatree_order,' . ' datatree_data, user_uid, datatree_serialized,' . ' datatree_parents)' . ' VALUES (?, ?, ?, ?, ?, ?, ?, ?)'; $values = array((int)$id, $this->_params['group'], String::convertCharset($name, NLS::getCharset(), $this->_params['charset']), is_null($order) ? NULL : (int)$order, $data, (string)Auth::getAuth(), (int)$ser, $parents); Horde::logMessage('SQL Query by DataTree_sql::add(): ' . $query . ', ' . var_export($values, true), __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 $result; } $reorder = $this->reorder($parents, $order, $id); if (is_a($reorder, 'PEAR_Error')) { Horde::logMessage($reorder, __FILE__, __LINE__, PEAR_LOG_ERR); return $reorder; } $result = parent::_add($fullname, $id, $pid, $order); if (is_a($result, 'PEAR_Error')) { return $result; } /* If we succesfully inserted the object and it supports * being mapped to the attributes table, do that now: */ if (!empty($attributes)) { $result = $this->updateData($object); if (is_a($result, 'PEAR_Error')) { return $result; } } return $id; } /** * Changes the order of the children of an object. * * @param string $parent The full id path of the parent object. * @param mixed $order If an array it specifies the new positions for * all child objects. * If an integer and $cid is specified, the position * where the child specified by $cid is inserted. If * $cid is not specified, the position gets deleted, * causing the following positions to shift up. * @param integer $cid See $order. */ function reorder($parent, $order = null, $cid = null) { if (!$parent || is_a($parent, 'PEAR_Error')) { // Abort immediately if the parent string is empty; we // cannot safely reorder all top-level elements. return; } $pquery = ''; if (!is_array($order) && !is_null($order)) { /* Single update (add/del). */ if (is_null($cid)) { /* No object id given so shuffle down. */ $direction = '-'; } else { /* We have an object id so shuffle up. */ $direction = '+'; /* Leaving the newly inserted object alone. */ $pquery = sprintf(' AND datatree_id != %s', (int)$cid); } $query = sprintf('UPDATE %s SET datatree_order = datatree_order %s 1 WHERE group_uid = %s AND datatree_parents = %s AND datatree_order >= %s', $this->_params['table'], $direction, $this->_write_db->quote($this->_params['group']), $this->_write_db->quote($parent), is_null($order) ? 'NULL' : (int)$order) . $pquery; Horde::logMessage('SQL Query by DataTree_sql::reorder(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_write_db->query($query); } elseif (is_array($order)) { /* Multi update. */ $query = 'SELECT COUNT(datatree_id)' . ' FROM ' . $this->_params['table'] . ' WHERE group_uid = ? AND datatree_parents = ?' . ' GROUP BY datatree_parents'; $values = array($this->_params['group'], $parent); Horde::logMessage('SQL Query by DataTree_sql::reorder(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_db->getOne($query, $values); if (is_a($result, 'PEAR_Error')) { return $result; } elseif (count($order) != $result) { return PEAR::raiseError(_("Cannot reorder, number of entries supplied for reorder does not match number stored.")); } $o_key = 0; foreach ($order as $o_cid) { $query = 'UPDATE ' . $this->_params['table'] . ' SET datatree_order = ? WHERE datatree_id = ?'; $values = array($o_key, is_null($o_cid) ? NULL : (int)$o_cid); Horde::logMessage('SQL Query by DataTree_sql::reorder(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_write_db->query($query, $values); if (is_a($result, 'PEAR_Error')) { return $result; } $o_key++; } $pid = $this->getId($parent); /* Re-order our cache. */ return $this->_reorder($pid, $order); } } /** * Explicitly set the order for a datatree object. * * @param integer $id The datatree object id to change. * @param integer $order The new order. */ function setOrder($id, $order) { $query = 'UPDATE ' . $this->_params['table'] . ' SET datatree_order = ? WHERE datatree_id = ?'; $values = array(is_null($order) ? NULL : (int)$order, (int)$id); Horde::logMessage('SQL Query by DataTree_sql::setOrder(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); return $this->_write_db->query($query, $values); } /** * Removes an object. * * @param mixed $object The object to remove. * @param boolean $force Force removal of every child object? */ function remove($object, $force = false) { $id = $this->getId($object); if (is_a($id, 'PEAR_Error')) { return $id; } $order = $this->getOrder($object); $query = 'SELECT datatree_id FROM ' . $this->_params['table'] . ' WHERE group_uid = ? AND datatree_parents LIKE ?' . ' ORDER BY datatree_id'; $values = array($this->_params['group'], '%:' . (int)$id . ''); Horde::logMessage('SQL Query by DataTree_sql::remove(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $children = $this->_db->getAll($query, $values, DB_FETCHMODE_ASSOC); if (count($children)) { if ($force) { foreach ($children as $child) { $cat = $this->getName($child['datatree_id']); $result = $this->remove($cat, true); if (is_a($result, 'PEAR_Error')) { return $result; } } } else { return PEAR::raiseError(sprintf(_("Cannot remove, %d children exist."), count($children))); } } /* Remove attributes for this object. */ $query = 'DELETE FROM ' . $this->_params['table_attributes'] . ' WHERE datatree_id = ?'; $values = array((int)$id); Horde::logMessage('SQL Query by DataTree_sql::remove(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_write_db->query($query, $values); if (is_a($result, 'PEAR_Error')) { return $result; } $query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE datatree_id = ?'; $values = array((int)$id); Horde::logMessage('SQL Query by DataTree_sql::remove(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_write_db->query($query, $values); if (is_a($result, 'PEAR_Error')) { return $result; } $parents = $this->getParentIdString($object); if (is_a($parents, 'PEAR_Error')) { return $parents; } $reorder = $this->reorder($parents, $order); if (is_a($reorder, 'PEAR_Error')) { return $reorder; } return is_a(parent::remove($object), 'PEAR_Error') ? $id : true; } /** * Removes one or more objects by id. * * This function does *not* do the validation, reordering, etc. that * remove() does. If you need to check for children, re-do ordering, etc., * then you must remove() objects one-by-one. This is for code that knows * it's dealing with single (non-parented) objects and needs to delete a * batch of them quickly. * * @param array $ids The objects to remove. */ function removeByIds($ids) { /* Sanitize input. */ if (!is_array($ids)) { $ids = array((int)$ids); } else { array_walk($ids, 'intval'); } /* Removing zero objects always succeeds. */ if (!$ids) { return true; } /* Remove attributes for $ids. */ $query = 'DELETE FROM ' . $this->_params['table_attributes'] . ' WHERE datatree_id IN (?' . str_repeat(', ?', count($ids) - 1) . ')'; $values = $ids; Horde::logMessage('SQL Query by DataTree_sql::removeByIds(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_write_db->query($query, $values); if (is_a($result, 'PEAR_Error')) { return $result; } $query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE datatree_id IN (?' . str_repeat(', ?', count($ids) - 1) . ')'; $values = $ids; Horde::logMessage('SQL Query by DataTree_sql::removeByIds(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); return $this->_write_db->query($query, $values); } /** * Removes one or more objects by name. * * This function does *not* do the validation, reordering, etc. that * remove() does. If you need to check for children, re-do ordering, etc., * then you must remove() objects one-by-one. This is for code that knows * it's dealing with single (non-parented) objects and needs to delete a * batch of them quickly. * * @param array $names The objects to remove. */ function removeByNames($names) { if (!is_array($names)) { $names = array($names); } /* Removing zero objects always succeeds. */ if (!$names) { return true; } $query = 'SELECT datatree_id FROM ' . $this->_params['table'] . ' WHERE datatree_name IN (?' . str_repeat(', ?', count($names) - 1) . ')'; $values = $names; Horde::logMessage('SQL Query by DataTree_sql::removeByNames(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $ids = $this->_db->getCol($query, 0, $values); if (is_a($ids, 'PEAR_Error')) { return $ids; } return $this->removeByIds($ids); } /** * Move an object to a new parent. * * @param mixed $object The object to move. * @param string $newparent The new parent object. Defaults to the root. */ function move($object, $newparent = null) { $old_parent_path = $this->getParentIdString($object); $result = parent::move($object, $newparent); if (is_a($result, 'PEAR_Error')) { return $result; } $id = $this->getId($object); $new_parent_path = $this->getParentIdString($object); /* Fetch the object being moved and all of its children, since * we also need to update their parent paths to avoid creating * orphans. */ $query = 'SELECT datatree_id, datatree_parents' . ' FROM ' . $this->_params['table'] . ' WHERE datatree_parents = ? OR datatree_parents LIKE ?' . ' OR datatree_id = ?'; $values = array($old_parent_path . ':' . $id, $old_parent_path . ':' . $id . ':%', (int)$id); Horde::logMessage('SQL Query by DataTree_sql::move(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $rowset = $this->_db->query($query, $values); if (is_a($rowset, 'PEAR_Error')) { return $rowset; } /* Update each object, replacing the old parent path with the * new one. */ while ($row = $rowset->fetchRow(DB_FETCHMODE_ASSOC)) { if (is_a($row, 'PEAR_Error')) { return $row; } $oquery = ''; if ($row['datatree_id'] == $id) { $oquery = ', datatree_order = 0 '; } /* Do str_replace() only if this is not a first level * object. */ if (!empty($row['datatree_parents'])) { $ppath = str_replace($old_parent_path, $new_parent_path, $row['datatree_parents']); } else { $ppath = $new_parent_path; } $query = sprintf('UPDATE %s SET datatree_parents = %s' . $oquery . ' WHERE datatree_id = %s', $this->_params['table'], $this->_write_db->quote($ppath), (int)$row['datatree_id']); Horde::logMessage('SQL Query by DataTree_sql::move(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_write_db->query($query); if (is_a($result, 'PEAR_Error')) { return $result; } } $order = $this->getOrder($object); /* Shuffle down the old order positions. */ $reorder = $this->reorder($old_parent_path, $order); /* Shuffle up the new order positions. */ $reorder = $this->reorder($new_parent_path, 0, $id); return true; } /** * Change an object's name. * * @param mixed $old_object The old object. * @param string $new_object_name The new object name. */ function rename($old_object, $new_object_name) { /* Do the cache renaming first */ $result = parent::rename($old_object, $new_object_name); if (is_a($result, 'PEAR_Error')) { return $result; } /* Get the object id and set up the sql query. */ $id = $this->getId($old_object); $query = 'UPDATE ' . $this->_params['table'] . ' SET datatree_name = ? WHERE datatree_id = ?'; $values = array(String::convertCharset($new_object_name, NLS::getCharset(), $this->_params['charset']), (int)$id); Horde::logMessage('SQL Query by DataTree_sql::rename(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_write_db->query($query, $values); return is_a($result, 'PEAR_Error') ? $result : true; } /** * Retrieves data for an object from the datatree_data field. * * @param integer $cid The object id to fetch, or an array of object ids. */ function getData($cid) { require_once 'Horde/Serialize.php'; if (is_array($cid)) { if (!count($cid)) { return array(); } $query = sprintf('SELECT datatree_id, datatree_data, datatree_serialized FROM %s WHERE datatree_id IN (%s)', $this->_params['table'], implode(', ', $cid)); Horde::logMessage('SQL Query by DataTree_sql::getData(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_db->getAssoc($query); if (is_a($result, 'PEAR_Error')) { Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); return $result; } $data = array(); foreach ($result as $id => $row) { $data[$id] = Horde_Serialize::unserialize($row[0], $row[1], NLS::getCharset()); /* Convert old data to the new format. */ if ($row[1] == SERIALIZE_BASIC) { $data[$id] = String::convertCharset($data[$id], NLS::getCharset(true)); } $data[$id] = (is_null($data[$id]) || !is_array($data[$id])) ? array() : $data[$id]; } return $data; } else { $query = 'SELECT datatree_data, datatree_serialized' . ' FROM ' . $this->_params['table'] . ' WHERE datatree_id = ?'; $values = array((int)$cid); Horde::logMessage('SQL Query by DataTree_sql::getData(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $row = $this->_db->getRow($query, $values, DB_FETCHMODE_ASSOC); $data = Horde_Serialize::unserialize($row['datatree_data'], $row['datatree_serialized'], NLS::getCharset()); /* Convert old data to the new format. */ if ($row['datatree_serialized'] == SERIALIZE_BASIC) { $data = String::convertCharset($data, NLS::getCharset(true)); } return (is_null($data) || !is_array($data)) ? array() : $data; } } /** * Retrieves data for an object from the horde_datatree_attributes table. * * @param integer|array $cid The object id to fetch, or an array of * object ids. * @param array $keys The attributes keys to fetch. * * @return array A hash of attributes, or a multi-level hash of object * ids => their attributes. */ function getAttributes($cid, $keys = false) { if (empty($cid)) { return array(); } if ($keys) { $filter = sprintf(' AND attribute_key IN (\'%s\')', implode("', '", $keys)); } else { $filter = ''; } if (is_array($cid)) { $query = sprintf('SELECT datatree_id, attribute_name AS name, attribute_key AS "key", attribute_value AS value FROM %s WHERE datatree_id IN (%s)%s', $this->_params['table_attributes'], implode(', ', $cid), $filter); Horde::logMessage('SQL Query by DataTree_sql::getAttributes(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); $rows = $this->_db->getAll($query, DB_FETCHMODE_ASSOC); if (is_a($rows, 'PEAR_Error')) { return $rows; } $data = array(); foreach ($rows as $row) { if (empty($data[$row['datatree_id']])) { $data[$row['datatree_id']] = array(); } $data[$row['datatree_id']][] = array('name' => $row['name'], 'key' => $row['key'], 'value' => String::convertCharset($row['value'], $this->_params['charset'], NLS::getCharset())); } return $data; } else { $query = sprintf('SELECT attribute_name AS name, attribute_key AS "key", attribute_value AS value FROM %s WHERE datatree_id = %s%s', $this->_params['table_attributes'], (int)$cid, $filter); Horde::logMessage('SQL Query by DataTree_sql::getAttributes(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); $rows = $this->_db->getAll($query, DB_FETCHMODE_ASSOC); for ($i = 0; $i < count($rows); $i++) { $rows[$i]['value'] = String::convertCharset($rows[$i]['value'], $this->_params['charset'], NLS::getCharset()); } return $rows; } } /** * Returns the number of objects matching a set of attribute criteria. * * @see buildAttributeQuery() * * @param array $criteria The array of criteria. * @param string $parent The parent node to start searching from. * @param boolean $allLevels Return all levels, or just the direct * children of $parent? Defaults to all levels. * @param string $restrict Only return attributes with the same * attribute_name or attribute_id. */ function countByAttributes($criteria, $parent = DATATREE_ROOT, $allLevels = true, $restrict = 'name') { if (!count($criteria)) { return 0; } $aq = $this->buildAttributeQuery($criteria, $parent, $allLevels, $restrict, DATATREE_BUILD_COUNT); if (is_a($aq, 'PEAR_Error')) { return $aq; } list($query, $values) = $aq; Horde::logMessage('SQL Query by DataTree_sql::countByAttributes(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_db->query($query, $values); if (is_a($result, 'PEAR_Error')) { Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); return $result; } $row = $result->fetchRow(); if (is_a($row, 'PEAR_Error')) { Horde::logMessage($row, __FILE__, __LINE__, PEAR_LOG_ERR); return $row; } return $row[0]; } /** * Returns a set of object ids based on a set of attribute criteria. * * @see buildAttributeQuery() * * @param array $criteria The array of criteria. * @param string $parent The parent node to start searching from. * @param boolean $allLevels Return all levels, or just the direct * children of $parent? Defaults to all levels. * @param string $restrict Only return attributes with the same * attribute_name or attribute_id. * @param integer $from The object to start to fetching * @param integer $count The number of objects to fetch * @param string $sortby_name Attribute name to use for sorting. * @param string $sortby_key Attribute key to use for sorting. * @param integer $direction Sort direction: * 0 - ascending * 1 - descending */ function getByAttributes($criteria, $parent = DATATREE_ROOT, $allLevels = true, $restrict = 'name', $from = 0, $count = 0, $sortby_name = null, $sortby_key = null, $direction = 0) { if (!count($criteria)) { return PEAR::raiseError('no criteria'); } // If there are top-level OR criteria, process one at a time // and return any results as soon as they're found...but only if // there is no LIMIT requested. if ($count == 0 && $from == 0) { foreach ($criteria as $key => $vals) { if ($key == 'OR') { $rows = array(); $num_or_statements = count($criteria[$key]); for ($i = 0; $i < $num_or_statements; $i++) { $criteria_or = $criteria['OR'][$i]; list($query, $values) = $this->buildAttributeQuery( $criteria_or, $parent, $allLevels, $restrict, DATATREE_BUILD_SELECT, $sortby_name, $sortby_key, $direction); if ($count) { $query = $this->_db->modifyLimitQuery($query, $from, $count); } Horde::logMessage('SQL Query by DataTree_sql::getByAttributes(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_db->query($query, $values); if (is_a($result, 'PEAR_Error')) { Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); return $result; } while ($row = $result->fetchRow()) { $rows[$row[0]] = String::convertCharset($row[1], $this->_params['charset']); } } return $rows; } } } // Process AND or other complex queries. $aq = $this->buildAttributeQuery($criteria, $parent, $allLevels, $restrict, DATATREE_BUILD_SELECT, $sortby_name, $sortby_key, $direction); if (is_a($aq, 'PEAR_Error')) { return $aq; } list($query, $values) = $aq; if ($count) { $query = $this->_db->modifyLimitQuery($query, $from, $count); } Horde::logMessage('SQL Query by DataTree_sql::getByAttributes(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_db->query($query, $values); if (is_a($result, 'PEAR_Error')) { Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); return $result; } $rows = array(); while ($row = $result->fetchRow()) { $rows[$row[0]] = String::convertCharset($row[1], $this->_params['charset']); } return $rows; } /** * Sorts IDs by attribute values. IDs without attributes will be added to * the end of the sorted list. * * @param array $unordered_ids Array of ids to sort. * @param array $sortby_name Attribute name to use for sorting. * @param array $sortby_key Attribute key to use for sorting. * @param array $direction Sort direction: * 0 - ascending * 1 - descending * * @return array Sorted ids. */ function sortByAttributes($unordered_ids, $sortby_name = null, $sortby_key = null, $direction = 0) { /* Select ids ordered by attribute value. */ $where = ''; if (!is_null($sortby_name)) { $where = sprintf(' AND attribute_name = %s ', $this->_db->quote($sortby_name)); } if (!is_null($sortby_key)) { $where = sprintf('%s AND attribute_key = %s ', $where, $this->_db->quote($sortby_key)); } $query = sprintf('SELECT datatree_id FROM %s WHERE datatree_id IN (%s) %s ORDER BY attribute_value %s', $this->_params['table_attributes'], implode(',', $unordered_ids), $where, ($direction == 1) ? 'DESC' : 'ASC'); Horde::logMessage('SQL Query by DataTree_sql::sortByAttributes(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); $ordered_ids = $this->_db->getCol($query); /* Make sure that some ids didn't get lost because has no such * attribute name/key. Append them to the end. */ if (count($ordered_ids) != count($unordered_ids)) { $ordered_ids = array_unique(array_merge($ordered_ids, $unordered_ids)); } return $ordered_ids; } /** * Returns the number of all of the available values matching the * given criteria. Either attribute_name or attribute_key MUST be * supplied, and both MAY be supplied. * * @since Horde 3.2 * * @see buildAttributeQuery() * * @param array $criteria The array of criteria. * @param string $parent The parent node to start searching from. * @param boolean $allLevels Return all levels, or just the direct * children of $parent? Defaults to all levels. * @param string $restrict Only return attributes with the same * attribute_name or attribute_id. * @param string $attribute_name The name of the attribute. * @param string $attribute_key The key value of the attribute. */ function countValuesByAttributes($criteria, $parent = DATATREE_ROOT, $allLevels = true, $restrict = 'name', $key = null, $name = null) { if (!count($criteria)) { return PEAR::raiseError('no criteria'); } $aq = $this->buildAttributeQuery($criteria, $parent, $allLevels, $restrict, DATATREE_BUILD_VALUES_COUNT); $aq[0] .= ' AND a.datatree_id = c.datatree_id'; if ($key !== null) { $aq[0] .= ' AND a.attribute_key = ?'; $aq[1][] = $key; } if ($name !== null) { $aq[0] .= ' AND a.attribute_name = ?'; $aq[1][] = $name; } return $this->_db->getOne($aq[0], $aq[1]); } /** * Returns a list of all of the available values of the given criteria * Either attribute_name or attribute_key MUST be * supplied, and both MAY be supplied. * * @since Horde 3.2 * * @see buildAttributeQuery() * * @param array $criteria The array of criteria. * @param string $parent The parent node to start searching from. * @param boolean $allLevels Return all levels, or just the direct * children of $parent? Defaults to all levels. * @param string $restrict Only return attributes with the same * attribute_name or attribute_id. * @param integer $from The object to start to fetching * @param integer $count The number of objects to fetch * @param string $sortby_name Attribute name to use for sorting. * @param string $sortby_key Attribute key to use for sorting. * @param integer $direction Sort direction: * 0 - ascending * 1 - descending * @param string $attribute_name The name of the attribute. * @param string $attribute_key The key value of the attribute. */ function getValuesByAttributes($criteria, $parent = DATATREE_ROOT, $allLevels = true, $restrict = 'name', $from = 0, $count = 0, $sortby_name = null, $sortby_key = null, $direction = 0, $key = null, $name = null) { if (!count($criteria)) { return PEAR::raiseError('no criteria'); } $aq = $this->buildAttributeQuery($criteria, $parent, $allLevels, $restrict, DATATREE_BUILD_VALUES, $sortby_name, $sortby_key, $direction); $aq[0] .= ' AND a.datatree_id = c.datatree_id'; if ($key !== null) { $aq[0] .= ' AND a.attribute_key = ?'; $aq[1][] = $key; } if ($name !== null) { $aq[0] .= ' AND a.attribute_name = ?'; $aq[1][] = $name; } if ($count) { $aq[0] = $this->_db->modifyLimitQuery($aq[0], $from, $count); } return $this->_db->getCol($aq[0], 0, $aq[1]); } /** * Returns a list of all of the available values of the given attribute * name/key combination. Either attribute_name or attribute_key MUST be * supplied, and both MAY be supplied. * * @param string $attribute_name The name of the attribute. * @param string $attribute_key The key value of the attribute. * @param string $parent The parent node to start searching from. * @param boolean $allLevels Return all levels, or just the direct * children of $parent? Defaults to all * levels. * * @return array An array of all of the available values. */ function getAttributeValues($attribute_name = null, $attribute_key = null, $parent = DATATREE_ROOT, $allLevels = true) { // Build the name/key filter. $where = ''; if (!is_null($attribute_name)) { $where .= 'a.attribute_name = ' . $this->_db->quote($attribute_name); } if (!is_null($attribute_key)) { if ($where) { $where .= ' AND '; } $where .= 'a.attribute_key = ' . $this->_db->quote($attribute_key); } // Return if we have no criteria. if (!$where) { return PEAR::raiseError('no criteria'); } // Add filtering by parent, and for one or all levels. $levelQuery = ''; if ($parent != DATATREE_ROOT) { $parts = explode(':', $parent); $parents = ''; $pstring = ''; foreach ($parts as $part) { $pstring .= (empty($pstring) ? '' : ':') . $part; $pid = $this->getId($pstring); if (is_a($pid, 'PEAR_Error')) { return $pid; } $parents .= ':' . $pid; } if ($allLevels) { $levelQuery = sprintf('AND (datatree_parents = %s OR datatree_parents LIKE %s)', $this->_db->quote($parents), $this->_db->quote($parents . ':%')); } else { $levelQuery = sprintf('AND datatree_parents = %s', $this->_db->quote($parents)); } } elseif (!$allLevels) { $levelQuery = "AND datatree_parents = ''"; } // Build the FROM/JOIN clauses. $joins = 'LEFT JOIN ' . $this->_params['table'] . ' c ON a.datatree_id = c.datatree_id'; $query = sprintf('SELECT DISTINCT a.attribute_value FROM %s a %s WHERE c.group_uid = %s AND %s %s', $this->_params['table_attributes'], $joins, $this->_db->quote($this->_params['group']), $where, $levelQuery); Horde::logMessage('SQL Query by DataTree_sql::getAttributeValues(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); $rows = $this->_db->getCol($query); if (is_a($rows, 'PEAR_Error')) { Horde::logMessage($rows, __FILE__, __LINE__, PEAR_LOG_ERR); } return $rows; } /** * Builds an attribute query. Here is an example $criteria array: * * * $criteria['OR'] = array( * array('AND' => array( * array('field' => 'name', * 'op' => '=', * 'test' => 'foo'), * array('field' => 'key', * 'op' => '=', * 'test' => 'abc'))), * array('AND' => array( * array('field' => 'name', * 'op' => '=', * 'test' => 'bar'), * array('field' => 'key', * 'op' => '=', * 'test' => 'xyz')))); * * * This would fetch all object ids where attribute name is "foo" AND key * is "abc", OR "bar" AND "xyz". * * @param array $criteria The array of criteria. * @param string $parent The parent node to start searching from. * @param boolean $allLevels Return all levels, or just the direct * children of $parent? Defaults to all levels. * @param string $restrict Only return attributes with the same * attribute_name or attribute_id. * @param integer $operation Type of query to build * @param string $sortby_name Attribute name to use for sorting. * @param string $sortby_key Attribute key to use for sorting. * @param integer $direction Sort direction: * 0 - ascending * 1 - descending * * @return array An SQL query and a list of values suitable for binding * as an array. */ function buildAttributeQuery($criteria, $parent = DATATREE_ROOT, $allLevels = true, $restrict = 'name', $operation = DATATREE_BUILD_SELECT, $sortby_name = null, $sortby_key = null, $direction = 0) { if (!count($criteria)) { return array('', array()); } /* Build the query. */ $this->_tableCount = 1; $query = ''; $values = array(); foreach ($criteria as $key => $vals) { if ($key == 'OR' || $key == 'AND') { if (!empty($query)) { $query .= ' ' . $key . ' '; } $binds = $this->_buildAttributeQuery($key, $vals); $query .= '(' . $binds[0] . ')'; $values += $binds[1]; } } // Add filtering by parent, and for one or all levels. $levelQuery = ''; $levelValues = array(); if ($parent != DATATREE_ROOT) { $parts = explode(':', $parent); $parents = ''; $pstring = ''; foreach ($parts as $part) { $pstring .= (empty($pstring) ? '' : ':') . $part; $pid = $this->getId($pstring); if (is_a($pid, 'PEAR_Error')) { return $pid; } $parents .= ':' . $pid; } if ($allLevels) { $levelQuery = 'AND (datatree_parents = ? OR datatree_parents LIKE ?)'; $levelValues = array($parents, $parents . ':%'); } else { $levelQuery = 'AND datatree_parents = ?'; $levelValues = array($parents); } } elseif (!$allLevels) { $levelQuery = "AND datatree_parents = ''"; } // Build the FROM/JOIN clauses. $joins = array(); $pairs = array(); for ($i = 1; $i <= $this->_tableCount; $i++) { $joins[] = 'LEFT JOIN ' . $this->_params['table_attributes'] . ' a' . $i . ' ON a' . $i . '.datatree_id = c.datatree_id'; if ($i != 1) { if ($restrict == 'name') { $pairs[] = 'AND a1.attribute_name = a' . $i . '.attribute_name'; } elseif ($restrict == 'id') { $pairs[] = 'AND a1.datatree_id = a' . $i . '.datatree_id'; } } } // Override sorting. $sort = array(); if (!is_null($sortby_name) || !is_null($sortby_key)) { $order_table = 'a' . $i; $joins[] = 'LEFT JOIN ' . $this->_params['table_attributes'] . ' ' . $order_table . ' ON ' . $order_table . '.datatree_id = c.datatree_id'; if (!is_null($sortby_name)) { $pairs[] = sprintf('AND %s.attribute_name = ? ', $order_table); $sort[] = $sortby_name; } if (!is_null($sortby_key)) { $pairs[] = sprintf('AND %s.attribute_key = ? ', $order_table); $sort[] = $sortby_key; } $order = sprintf('%s.attribute_value %s', $order_table, ($direction == 1) ? 'DESC' : 'ASC'); $group_by = 'c.datatree_id, c.datatree_name, c.datatree_order, ' . $order_table . '.attribute_value'; } else { $order = 'c.datatree_order, c.datatree_name, c.datatree_id'; $group_by = 'c.datatree_id, c.datatree_name, c.datatree_order'; } $joins = implode(' ', $joins); $pairs = implode(' ', $pairs); switch ($operation) { case DATATREE_BUILD_VALUES_COUNT: $what = 'COUNT(DISTINCT(a.attribute_value))'; $from = ' ' . $this->_params['table_attributes'] . ' a, ' . $this->_params['table']; $tail = ''; break; case DATATREE_BUILD_VALUES: $what = 'DISTINCT(a.attribute_value)'; $from = ' ' . $this->_params['table_attributes'] . ' a, ' . $this->_params['table']; $tail = ''; break; case DATATREE_BUILD_COUNT: $what = 'COUNT(DISTINCT c.datatree_id)'; $from = $this->_params['table']; $tail = ''; break; default: $what = 'c.datatree_id, c.datatree_name'; $from = $this->_params['table']; $tail = sprintf('GROUP BY %s ORDER BY %s', $group_by, $order); break; } return array(sprintf('SELECT %s FROM %s c %s WHERE c.group_uid = ? AND %s %s %s %s', $what, $from, $joins, $query, $levelQuery, $pairs, $tail), array_merge(array($this->_params['group']), $values, $levelValues, $sort)); } /** * Builds a piece of an attribute query. * * @param string $glue The glue to join the criteria (OR/AND). * @param array $criteria The array of criteria. * @param boolean $join Should we join on a clean * horde_datatree_attributes table? Defaults to * false. * * @return array An SQL fragment and a list of values suitable for binding * as an array. */ function _buildAttributeQuery($glue, $criteria, $join = false) { require_once 'Horde/SQL.php'; // Initialize the clause that we're building. $clause = ''; $values = array(); // Get the table alias to use for this set of criteria. $alias = $this->_getAlias($join); foreach ($criteria as $key => $vals) { if (!empty($clause)) { $clause .= ' ' . $glue . ' '; } if (!empty($vals['OR']) || !empty($vals['AND'])) { $binds = $this->_buildAttributeQuery($glue, $vals); $clause .= '(' . $binds[0] . ')'; $values = array_merge($values, $binds[1]); } elseif (!empty($vals['JOIN'])) { $binds = $this->_buildAttributeQuery($glue, $vals['JOIN'], true); $clause .= $binds[0]; $values = array_merge($values, $binds[1]); } else { if (isset($vals['field'])) { // All of the attribute_* fields are text, so make // sure we send strings to the database. if (is_array($vals['test'])) { for ($i = 0, $iC = count($vals['test']); $i < $iC; ++$i) { $vals['test'][$i] = (string)$vals['test'][$i]; } } else { $vals['test'] = (string)$vals['test']; } $binds = Horde_SQL::buildClause($this->_db, $alias . '.attribute_' . $vals['field'], $vals['op'], $vals['test'], true); $clause .= $binds[0]; $values = array_merge($values, $binds[1]); } else { $binds = $this->_buildAttributeQuery($key, $vals); $clause .= $binds[0]; $values = array_merge($values, $binds[1]); } } } return array($clause, $values); } /** * Get an alias to horde_datatree_attributes, incrementing it if * necessary. * * @param boolean $increment Increment the alias count? Defaults to no. */ function _getAlias($increment = false) { static $seen = array(); if ($increment && !empty($seen[$this->_tableCount])) { $this->_tableCount++; } $seen[$this->_tableCount] = true; return 'a' . $this->_tableCount; } /** * Update the data in an object. Does not change the object's * parent or name, just serialized data or attributes. * * @param DataTree $object A DataTree object. */ function updateData($object) { if (!is_a($object, 'DataTreeObject')) { /* Nothing to do for non objects. */ return true; } /* Get the object id. */ $id = $this->getId($object->getName()); if (is_a($id, 'PEAR_Error')) { return $id; } /* See if we can break the object out to datatree_attributes table. */ if (method_exists($object, '_toAttributes')) { /* If we can, clear out the datatree_data field to make sure it * doesn't get picked up by getData(). Intentionally don't check * for errors here in case datatree_data goes away in the * future. */ $query = 'UPDATE ' . $this->_params['table'] . ' SET datatree_data = ? WHERE datatree_id = ?'; $values = array(NULL, (int)$id); Horde::logMessage('SQL Query by DataTree_sql::updateData(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $this->_write_db->query($query, $values); /* Start a transaction. */ $this->_write_db->autoCommit(false); /* Delete old attributes. */ $query = 'DELETE FROM ' . $this->_params['table_attributes'] . ' WHERE datatree_id = ?'; $values = array((int)$id); Horde::logMessage('SQL Query by DataTree_sql::updateData(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_write_db->query($query, $values); if (is_a($result, 'PEAR_Error')) { $this->_write_db->rollback(); $this->_write_db->autoCommit(true); return $result; } /* Get the new attribute set, and insert each into the DB. If * anything fails in here, rollback the transaction, return the * relevant error, and bail out. */ $attributes = $object->_toAttributes(); $query = 'INSERT INTO ' . $this->_params['table_attributes'] . ' (datatree_id, attribute_name, attribute_key, attribute_value)' . ' VALUES (?, ?, ?, ?)'; $statement = $this->_write_db->prepare($query); foreach ($attributes as $attr) { $values = array((int)$id, $attr['name'], $attr['key'], String::convertCharset($attr['value'], NLS::getCharset(), $this->_params['charset'])); Horde::logMessage('SQL Query by DataTree_sql::updateData(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_write_db->execute($statement, $values); if (is_a($result, 'PEAR_Error')) { $this->_write_db->rollback(); $this->_write_db->autoCommit(true); return $result; } } /* Commit the transaction, and turn autocommit back on. */ $result = $this->_write_db->commit(); $this->_write_db->autoCommit(true); return is_a($result, 'PEAR_Error') ? $result : true; } else { /* Write to the datatree_data field. */ require_once 'Horde/Serialize.php'; $ser = SERIALIZE_UTF7_BASIC; $data = Horde_Serialize::serialize($object->getData(), $ser, NLS::getCharset()); $query = 'UPDATE ' . $this->_params['table'] . ' SET datatree_data = ?, datatree_serialized = ?' . ' WHERE datatree_id = ?'; $values = array($data, (int)$ser, (int)$id); Horde::logMessage('SQL Query by DataTree_sql::updateData(): ' . $query . ', ' . var_export($values, true), __FILE__, __LINE__, PEAR_LOG_DEBUG); $result = $this->_write_db->query($query, $values); return is_a($result, 'PEAR_Error') ? $result : true; } } /** * Attempts to open a connection to the SQL server. * * @return boolean True. */ function _init() { Horde::assertDriverConfig($this->_params, 'sql', array('phptype', 'charset'), 'DataTree SQL'); $default = array( 'database' => '', 'username' => '', 'password' => '', 'hostspec' => '', 'table' => 'horde_datatree', 'table_attributes' => 'horde_datatree_attributes', ); $this->_params = array_merge($default, $this->_params); /* Connect to the SQL server using the supplied parameters. */ 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')) { return $this->_write_db; } // Set DB portability options. $portability = DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS; if ($this->_write_db->phptype == 'mssql') { $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')) { return $this->_db; } // Set DB portability options $portability = DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS; if ($this->_db->phptype == 'mssql') { $portability |= DB_PORTABILITY_RTRIM; } $this->_db->setOption('portability', $portability); } else { /* Default to the same DB handle for reads. */ $this->_db = $this->_write_db; } return true; } } * 'group' -- Define each group of objects we want to build. * * Copyright 1999-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 Stephane Huther * @author Chuck Hagenbuch * @package Horde_DataTree */ class DataTree { /** * Array of all data: indexed by id. The format is: * array(id => 'name' => name, 'parent' => parent). * * @var array */ var $_data = array(); /** * A hash that can be used to map a full object name * (parent:child:object) to that object's unique ID. * * @var array */ var $_nameMap = array(); /** * Actual attribute sorting hash. * * @var array */ var $_sortHash = null; /** * Hash containing connection parameters. * * @var array */ var $_params = array(); /** * Constructor. * * @param array $params A hash containing any additional configuration or * connection parameters a subclass might need. * We always need 'group', a string that defines the * prefix for each set of hierarchical data. */ function DataTree($params = array()) { $this->_params = $params; } /** * Returns a parameter of this DataTree instance. * * @param string $param The parameter to return. * * @return mixed The parameter's value or null if it doesn't exist. */ function getParam($param) { return isset($this->_params[$param]) ? $this->_params[$param] : null; } /** * Removes an object. * * @param string $object The object to remove. * @param boolean $force Force removal of every child object? * * @return TODO */ function remove($object, $force = false) { if (is_a($object, 'DataTreeObject')) { $object = $object->getName(); } if (!$this->exists($object)) { return PEAR::raiseError($object . ' does not exist'); } $children = $this->getNumberOfChildren($object); if ($children) { /* TODO: remove children if $force == true */ return PEAR::raiseError(sprintf(ngettext("Cannot remove, %d child exists.", "Cannot remove, %d children exist.", count($children)), count($children))); } $id = $this->getId($object); $pid = $this->getParent($object); $order = $this->_data[$id]['order']; unset($this->_data[$id], $this->_nameMap[$id]); // Shift down the order positions. $this->_reorder($pid, $order); return $id; } /** * Removes all DataTree objects owned by a certain user. * * @abstract * * @param string $user A user name. * * @return TODO */ function removeUserData($user) { return PEAR::raiseError('not supported'); } /** * Move an object to a new parent. * * @param mixed $object The object to move. * @param string $newparent The new parent object. Defaults to the root. * * @return mixed True on success, PEAR_Error on error. */ function move($object, $newparent = null) { $cid = $this->getId($object); if (is_a($cid, 'PEAR_Error')) { return PEAR::raiseError(sprintf('Object to move does not exist: %s', $cid->getMessage())); } if (!is_null($newparent)) { $pid = $this->getId($newparent); if (is_a($pid, 'PEAR_Error')) { return PEAR::raiseError(sprintf('New parent does not exist: %s', $pid->getMessage())); } } else { $pid = DATATREE_ROOT; } $this->_data[$cid]['parent'] = $pid; return true; } /** * Change an object's name. * * @param mixed $old_object The old object. * @param string $new_object_name The new object name. * * @return mixed True on success, PEAR_Error on error. */ function rename($old_object, $new_object_name) { /* Check whether the object exists at all */ if (!$this->exists($old_object)) { return PEAR::raiseError($old_object . ' does not exist'); } /* Check for duplicates - get parent and create new object * name */ $parent = $this->getName($this->getParent($old_object)); if ($this->exists($parent . ':' . $new_object_name)) { return PEAR::raiseError('Duplicate name ' . $new_object_name); } /* Replace the old name with the new one in the cache */ $old_object_id = $this->getId($old_object); $this->_data[$old_object_id]['name'] = $new_object_name; return true; } /** * Changes the order of the children of an object. * * @abstract * * @param string $parent The full id path of the parent object. * @param mixed $order If an array it specifies the new positions for * all child objects. * If an integer and $cid is specified, the position * where the child specified by $cid is inserted. If * $cid is not specified, the position gets deleted, * causing the following positions to shift up. * @param integer $cid See $order. * * @return TODO */ function reorder($parents, $order = null, $cid = null) { return PEAR::raiseError('not supported'); } /** * Change order of children of an object. * * @param string $pid The parent object id string path. * @param mixed $order Specific new order position or an array containing * the new positions for the given parent. * @param integer $cid If provided indicates insertion of a new child to * the parent to avoid incrementing it when * shifting up all other children's order. If not * provided indicates deletion, so shift all other * positions down one. */ function _reorder($pid, $order = null, $cid = null) { if (!is_array($order) && !is_null($order)) { // Single update (add/del). if (is_null($cid)) { // No id given so shuffle down. foreach ($this->_data as $c_key => $c_val) { if ($this->_data[$c_key]['parent'] == $pid && $this->_data[$c_key]['order'] > $order) { --$this->_data[$c_key]['order']; } } } else { // We have an id so shuffle up. foreach ($this->_data as $c_key => $c_val) { if ($c_key != $cid && $this->_data[$c_key]['parent'] == $pid && $this->_data[$c_key]['order'] >= $order) { ++$this->_data[$c_key]['order']; } } } } elseif (is_array($order) && count($order)) { // Multi update. foreach ($order as $order_position => $cid) { $this->_data[$cid]['order'] = $order_position; } } } /** * Explicitly set the order for a datatree object. * * @abstract * * @param integer $id The datatree object id to change. * @param integer $order The new order. * * @return TODO */ function setOrder($id, $order) { return PEAR::raiseError('not supported'); } /** * Dynamically determines the object class. * * @param array $attributes The set of attributes that contain the class * information. Defaults to DataTreeObject. * * @return TODO */ function _defineObjectClass($attributes) { $class = 'DataTreeObject'; if (!is_array($attributes)) { return $class; } foreach ($attributes as $attr) { if ($attr['name'] == 'DataTree') { switch ($attr['key']) { case 'objectClass': $class = $attr['value']; break; case 'objectType': $result = explode('/', $attr['value']); $class = $GLOBALS['registry']->callByPackage($result[0], 'defineClass', array('type' => $result[1])); break; } } } return $class; } /** * Returns a DataTreeObject (or subclass) object of the data in the * object defined by $object. * * @param string $object The object to fetch: 'parent:sub-parent:name'. * @param string $class Subclass of DataTreeObject to use. Defaults to * DataTreeObject. Null forces the driver to look * into the attributes table to determine the * subclass to use. If none is found it uses * DataTreeObject. * * @return TODO */ function &getObject($object, $class = 'DataTreeObject') { if (empty($object)) { $error = PEAR::raiseError('No object requested.'); return $error; } $this->_load($object); if (!$this->exists($object)) { $error = PEAR::raiseError($object . ' not found.'); return $error; } return $this->_getObject($this->getId($object), $object, $class); } /** * Returns a DataTreeObject (or subclass) object of the data in the * object with the ID $id. * * @param integer $id An object id. * @param string $class Subclass of DataTreeObject to use. Defaults to * DataTreeObject. Null forces the driver to look * into the attributes table to determine the * subclass to use. If none is found it uses * DataTreeObject. * * @return TODO */ function &getObjectById($id, $class = 'DataTreeObject') { if (empty($id)) { $object = PEAR::raiseError('No id requested.'); return $object; } $result = $this->_loadById($id); if (is_a($result, 'PEAR_Error')) { return $result; } return $this->_getObject($id, $this->getName($id), $class); } /** * Helper function for getObject() and getObjectById(). * * @access private */ function &_getObject($id, $name, $class) { $use_attributes = is_null($class) || is_callable(array($class, '_fromAttributes')); if ($use_attributes) { $attributes = $this->getAttributes($id); if (is_a($attributes, 'PEAR_Error')) { return $attributes; } if (is_null($class)) { $class = $this->_defineObjectClass($attributes); } } if (!class_exists($class)) { $error = PEAR::raiseError($class . ' not found.'); return $error; } $dataOb = new $class($name); $dataOb->setDataTree($this); /* If the class has a _fromAttributes method, load data from * the attributes backend. */ if ($use_attributes) { $dataOb->_fromAttributes($attributes); } else { /* Otherwise load it from the old data storage field. */ $dataOb->setData($this->getData($id)); } $dataOb->setOrder($this->getOrder($name)); return $dataOb; } /** * Returns an array of DataTreeObject (or subclass) objects * corresponding to the objects in $ids, with the object * names as the keys of the array. * * @param array $ids An array of object ids. * @param string $class Subclass of DataTreeObject to use. Defaults to * DataTreeObject. Null forces the driver to look * into the attributes table to determine the * subclass to use. If none is found it uses * DataTreeObject. * * @return TODO */ function &getObjects($ids, $class = 'DataTreeObject') { $result = $this->_loadById($ids); if (is_a($result, 'PEAR_Error')) { return $result; } $defineClass = is_null($class); $attributes = $defineClass || is_callable(array($class, '_fromAttributes')); if ($attributes) { $data = $this->getAttributes($ids); } else { $data = $this->getData($ids); } $objects = array(); foreach ($ids as $id) { $name = $this->getName($id); if (!empty($name) && !empty($data[$id])) { if ($defineClass) { $class = $this->_defineObjectClass($data[$id]); } if (!class_exists($class)) { return PEAR::raiseError($class . ' not found.'); } $objects[$name] = new $class($name); $objects[$name]->setDataTree($this); if ($attributes) { $objects[$name]->_fromAttributes($data[$id]); } else { $objects[$name]->setData($data[$id]); } $objects[$name]->setOrder($this->getOrder($name)); } } return $objects; } /** * Export a list of objects. * * @param constant $format Format of the export * @param string $startleaf The name of the leaf from which we start * the export tree. * @param boolean $reload Re-load the requested chunk? Defaults to * false (only what is currently loaded). * @param string $rootname The label to use for the root element. * Defaults to DATATREE_ROOT. * @param integer $maxdepth The maximum number of levels to return. * Defaults to DATATREE_ROOT, which is no * limit. * @param boolean $loadTree Load a tree starting at $root, or just the * requested level and direct parents? * Defaults to single level. * @param string $sortby_name Attribute name to use for sorting. * @param string $sortby_key Attribute key to use for sorting. * @param integer $direction Sort direction: * 0 - ascending * 1 - descending * * @return mixed The tree representation of the objects, or a PEAR_Error * on failure. */ function get($format, $startleaf = DATATREE_ROOT, $reload = false, $rootname = DATATREE_ROOT, $maxdepth = -1, $loadTree = false, $sortby_name = null, $sortby_key = null, $direction = 0) { $out = array(); /* Set sorting hash */ if (!is_null($sortby_name)) { $this->_sortHash = DataTree::sortHash($startleaf, $sortby_name, $sortby_key, $direction); } $this->_load($startleaf, $loadTree, $reload, $sortby_name, $sortby_key, $direction); switch ($format) { case DATATREE_FORMAT_TREE: $startid = $this->getId($startleaf, $maxdepth); if (is_a($startid, 'PEAR_Error')) { return $startid; } $this->_extractAllLevelTree($out, $startid, $maxdepth); break; case DATATREE_FORMAT_FLAT: $startid = $this->getId($startleaf); if (is_a($startid, 'PEAR_Error')) { return $startid; } $this->_extractAllLevelList($out, $startid, $maxdepth); if (!empty($out[DATATREE_ROOT])) { $out[DATATREE_ROOT] = $rootname; } break; default: return PEAR::raiseError('Not supported'); } if (!is_null($this->_sortHash)) { /* Reset sorting hash. */ $this->_sortHash = null; /* Reverse since the attribute sorting combined with tree up-ward * sorting produces a reversed object order. */ $out = array_reverse($out, true); } return $out; } /** * Counts objects. * * @param string $startleaf The name of the leaf from which we start * counting. * * @return integer The number of the objects below $startleaf. */ function count($startleaf = DATATREE_ROOT) { return $this->_count($startleaf); } /** * Create attribute sort hash * * @since Horde 3.1 * * @param string $root The name of the leaf from which we start * the export tree. * @param string $sortby_name Attribute name to use for sorting. * @param string $sortby_key Attribute key to use for sorting. * @param integer $direction Sort direction: * 0 - ascending * 1 - descending * * @return string The sort hash. */ function sortHash($root, $sortby_name = null, $sortby_key = null, $direction = 0) { return sprintf('%s-%s-%s-%s', $root, $sortby_name, $sortby_key, $direction); } /** * Export a list of objects just like get() above, but uses an * object id to fetch the list of objects. * * @param constant $format Format of the export. * @param string $startleaf The id of the leaf from which we start the * export tree. * @param boolean $reload Reload the requested chunk? Defaults to * false (only what is currently loaded). * @param string $rootname The label to use for the root element. * Defaults to DATATREE_ROOT. * @param integer $maxdepth The maximum number of levels to return * Defaults to -1, which is no limit. * * @return mixed The tree representation of the objects, or a PEAR_Error * on failure. */ function getById($format, $startleaf = DATATREE_ROOT, $reload = false, $rootname = DATATREE_ROOT, $maxdepth = -1) { $this->_loadById($startleaf); $out = array(); switch ($format) { case DATATREE_FORMAT_TREE: $this->_extractAllLevelTree($out, $startleaf, $maxdepth); break; case DATATREE_FORMAT_FLAT: $this->_extractAllLevelList($out, $startleaf, $maxdepth); if (!empty($out[DATATREE_ROOT])) { $out[DATATREE_ROOT] = $rootname; } break; default: return PEAR::raiseError('Not supported'); } return $out; } /** * Returns a list of all groups (root nodes) of the data tree. * * @abstract * * @return mixed The group IDs or PEAR_Error on error. */ function getGroups() { return PEAR::raiseError('not supported'); } /** * Retrieve data for an object from the datatree_data field. * * @abstract * * @param integer $cid The object id to fetch, or an array of object ids. * * @return TODO */ function getData($cid) { return PEAR::raiseError('not supported'); } /** * Import a list of objects. Used by drivers to populate the internal * $_data array. * * @param array $data The data to import. * @param string $charset The charset to convert the object name from. * * @return TODO */ function set($data, $charset = null) { $cids = array(); foreach ($data as $id => $cat) { if (!is_null($charset)) { $cat[1] = String::convertCharset($cat[1], $charset); } $cids[$cat[0]] = $cat[1]; $cparents[$cat[0]] = $cat[2]; $corders[$cat[0]] = $cat[3]; $sorders[$cat[0]] = $id; } foreach ($cids as $id => $name) { $this->_data[$id]['name'] = $name; $this->_data[$id]['order'] = $corders[$id]; if (!is_null($this->_sortHash)) { $this->_data[$id]['sorter'][$this->_sortHash] = $sorders[$id]; } if (!empty($cparents[$id])) { $parents = explode(':', substr($cparents[$id], 1)); $par = $parents[count($parents) - 1]; $this->_data[$id]['parent'] = $par; if (!empty($this->_nameMap[$par])) { // If we've already loaded the direct parent of // this object, use that to find the full name. $this->_nameMap[$id] = $this->_nameMap[$par] . ':' . $name; } else { // Otherwise, run through parents one by one to // build it up. $this->_nameMap[$id] = ''; foreach ($parents as $parID) { if (!empty($cids[$parID])) { $this->_nameMap[$id] .= ':' . $cids[$parID]; } } $this->_nameMap[$id] = substr($this->_nameMap[$id], 1) . ':' . $name; } } else { $this->_data[$id]['parent'] = DATATREE_ROOT; $this->_nameMap[$id] = $name; } } return true; } /** * Extract one level of data for a parent leaf, sorted first by * their order and then by name. This function is a way to get a * collection of $leaf's children. * * @param string $leaf Name of the parent from which to start. * * @return array TODO */ function _extractOneLevel($leaf = DATATREE_ROOT) { $out = array(); foreach ($this->_data as $id => $vals) { if ($vals['parent'] == $leaf) { $out[$id] = $vals; } } uasort($out, array($this, (is_null($this->_sortHash)) ? '_cmp' : '_cmpSorted')); return $out; } /** * Extract all levels of data, starting from a given parent * leaf in the datatree. * * @access private * * @note If nothing is returned that means there is no child, but * don't forget to add the parent if any subsequent operations are * required! * * @param array $out This is an iterating function, so $out is * passed by reference to contain the result. * @param string $parent The name of the parent from which to begin. * @param integer $maxdepth Max of levels of depth to check. * * @return TODO */ function _extractAllLevelTree(&$out, $parent = DATATREE_ROOT, $maxdepth = -1) { if ($maxdepth == 0) { return false; } $out[$parent] = true; $k = $this->_extractOneLevel($parent); foreach (array_keys($k) as $object) { if (!is_array($out[$parent])) { $out[$parent] = array(); } $out[$parent][$object] = true; $this->_extractAllLevelTree($out[$parent], $object, $maxdepth - 1); } } /** * Extract all levels of data, starting from any parent in * the tree. * * Returned array format: array(parent => array(child => true)) * * @access private * * @param array $out This is an iterating function, so $out is * passed by reference to contain the result. * @param string $parent The name of the parent from which to begin. * @param integer $maxdepth Max number of levels of depth to check. * * @return TODO */ function _extractAllLevelList(&$out, $parent = DATATREE_ROOT, $maxdepth = -1) { if ($maxdepth == 0) { return false; } // This is redundant most of the time, so make sure we need to // do it. if (empty($out[$parent])) { $out[$parent] = $this->getName($parent); } foreach (array_keys($this->_extractOneLevel($parent)) as $object) { $out[$object] = $this->getName($object); $this->_extractAllLevelList($out, $object, $maxdepth - 1); } } /** * Returns a child's direct parent ID. * * @param mixed $child Either the object, an array containing the * path elements, or the object name for which * to look up the parent's ID. * * @return mixed The unique ID of the parent or PEAR_Error on error. */ function getParent($child) { if (is_a($child, 'DataTreeObject')) { $child = $child->getName(); } $id = $this->getId($child); if (is_a($id, 'PEAR_Error')) { return $id; } return $this->getParentById($id); } /** * Get a $child's direct parent ID. * * @param integer $childId Get the parent of this object. * * @return mixed The unique ID of the parent or PEAR_Error on error. */ function getParentById($childId) { $this->_loadById($childId); return isset($this->_data[$childId]) ? $this->_data[$childId]['parent'] : PEAR::raiseError($childId . ' not found'); } /** * Get a list of parents all the way up to the root object for * $child. * * @param mixed $child The name of the child * @param boolean $getids If true, return parent IDs; otherwise, return * names. * * @return mixed [child] [parent] in a tree format or PEAR_Error. */ function getParents($child, $getids = false) { $pid = $this->getParent($child); if (is_a($pid, 'PEAR_Error')) { return PEAR::raiseError('Parents not found: ' . $pid->getMessage()); } $pname = $this->getName($pid); $parents = ($getids) ? array($pid => true) : array($pname => true); if ($pid != DATATREE_ROOT) { if ($getids) { $parents[$pid] = $this->getParents($pname, $getids); } else { $parents[$pname] = $this->getParents($pname, $getids); } } return $parents; } /** * Get a list of parents all the way up to the root object for * $child. * * @param integer $childId The id of the child. * @param array $parents The array, as we build it up. * * @return array A flat list of all of the parents of $child, * hashed in $id => $name format. */ function getParentList($childId, $parents = array()) { $pid = $this->getParentById($childId); if (is_a($pid, 'PEAR_Error')) { return PEAR::raiseError('Parents not found: ' . $pid->getMessage()); } if ($pid != DATATREE_ROOT) { $parents[$pid] = $this->getName($pid); $parents = $this->getParentList($pid, $parents); } return $parents; } /** * Get a parent ID string (id:cid format) for the specified object. * * @param mixed $object The object to return a parent string for. * * @return string|PEAR_Error The ID "path" to the parent object or * PEAR_Error on failure. */ function getParentIdString($object) { $ptree = $this->getParents($object, true); if (is_a($ptree, 'PEAR_Error')) { return $ptree; } $pids = ''; while ((list($id, $parent) = each($ptree)) && is_array($parent)) { $pids = ':' . $id . $pids; $ptree = $parent; } return $pids; } /** * Get the number of children an object has, only counting immediate * children, not grandchildren, etc. * * @param mixed $parent Either the object or the name for which to count * the children, defaults to the root * (DATATREE_ROOT). * * @return integer */ function getNumberOfChildren($parent = DATATREE_ROOT) { if (is_a($parent, 'DataTreeObject')) { $parent = $parent->getName(); } $this->_load($parent); $out = $this->_extractOneLevel($this->getId($parent)); return is_array($out) ? count($out) : 0; } /** * Check if an object exists or not. The root element DATATREE_ROOT always * exists. * * @param mixed $object The name of the object. * * @return boolean True if the object exists, false otherwise. */ function exists($object) { if (empty($object)) { return false; } if (is_a($object, 'DataTreeObject')) { $object = $object->getName(); } elseif (is_array($object)) { $object = implode(':', $object); } if ($object == DATATREE_ROOT) { return true; } if (array_search($object, $this->_nameMap) !== false) { return true; } // Consult the backend directly. return $this->_exists($object); } /** * Get the name of an object from its id. * * @param integer $id The id for which to look up the name. * * @return string TODO */ function getName($id) { /* If no id or if id is a PEAR error, return null. */ if (empty($id) || is_a($id, 'PEAR_Error')) { return null; } /* If checking name of root, return DATATREE_ROOT. */ if ($id == DATATREE_ROOT) { return DATATREE_ROOT; } /* If found in the name map, return the name. */ if (isset($this->_nameMap[$id])) { return $this->_nameMap[$id]; } /* Not found in name map, consult the backend. */ return $this->_getName($id); } /** * Get the id of an object from its name. * * @param mixed $name Either the object, an array containing the * path elements, or the object name for which * to look up the id. * * @return string */ function getId($name) { /* Check if $name is not a string. */ if (is_a($name, 'DataTreeObject')) { /* DataTreeObject, get the string name. */ $name = $name->getName(); } elseif (is_array($name)) { /* Path array, implode to get the string name. */ $name = implode(':', $name); } /* If checking id of root, return DATATREE_ROOT. */ if ($name == DATATREE_ROOT) { return DATATREE_ROOT; } /* Flip the name map to look up the id using the name as key. */ if (($id = array_search($name, $this->_nameMap)) !== false) { return $id; } /* Not found in name map, consult the backend. */ $id = $this->_getId($name); if (is_null($id)) { return PEAR::raiseError($name . ' does not exist'); } return $id; } /** * Get the order position of an object. * * @param mixed $child Either the object or the name. * * @return mixed The object's order position or a PEAR error on failure. */ function getOrder($child) { if (is_a($child, 'DataTreeObject')) { $child = $child->getName(); } $id = $this->getId($child); if (is_a($id, 'PEAR_Error')) { return $id; } $this->_loadById($id); return isset($this->_data[$id]['order']) ? $this->_data[$id]['order'] : null; } /** * Replace all occurences of ':' in an object name with '.'. * * @param string $name The name of the object. * * @return string The encoded name. */ function encodeName($name) { return str_replace(':', '.', $name); } /** * Get the short name of an object, returns only the last portion of the * full name. For display purposes only. * * @static * * @param string $name The name of the object. * * @return string The object's short name. */ function getShortName($name) { /* If there are several components to the name, explode and get the * last one, otherwise just return the name. */ if (strpos($name, ':') !== false) { $name = explode(':', $name); $name = array_pop($name); } return $name; } /** * Returns a tree sorted by the specified attribute name and/or key. * * @abstract * * @since Horde 3.1 * * @param string $root Which portion of the tree to sort. * Defaults to all of it. * @param boolean $loadTree Sort the tree starting at $root, or just the * requested level and direct parents? * Defaults to single level. * @param string $sortby_name Attribute name to use for sorting. * @param string $sortby_key Attribute key to use for sorting. * @param integer $direction Sort direction: * 0 - ascending * 1 - descending * * @return array TODO */ function getSortedTree($root, $loadTree = false, $sortby_name = null, $sortby_key = null, $direction = 0) { return PEAR::raiseError('not supported'); } /** * Adds an object. * * @abstract * * @param mixed $object The object to add (string or * DataTreeObject). * @param boolean $id_as_name True or false to indicate if object ID is to * be used as object name. Used in situations * where there is no available unique input for * object name. * * @return TODO */ function add($object, $id_as_name = false) { return PEAR::raiseError('not supported'); } /** * Add an object. * * @private * * @param string $name The short object name. * @param integer $id The new object's unique ID. * @param integer $pid The unique ID of the object's parent. * @param integer $order The ordering data for the object. * * @access protected * * @return TODO */ function _add($name, $id, $pid, $order = '') { $this->_data[$id] = array('name' => $name, 'parent' => $pid, 'order' => $order); $this->_nameMap[$id] = $name; /* Shift along the order positions. */ $this->_reorder($pid, $order, $id); return true; } /** * Retrieve data for an object from the horde_datatree_attributes * table. * * @abstract * * @param integer | array $cid The object id to fetch, * or an array of object ids. * * @return array A hash of attributes, or a multi-level hash * of object ids => their attributes. */ function getAttributes($cid) { return PEAR::raiseError('not supported'); } /** * Returns the number of objects matching a set of attribute criteria. * * @abstract * * @see buildAttributeQuery() * * @param array $criteria The array of criteria. * @param string $parent The parent node to start searching from. * @param boolean $allLevels Return all levels, or just the direct * children of $parent? Defaults to all levels. * @param string $restrict Only return attributes with the same * attribute_name or attribute_id. * * @return TODO */ function countByAttributes($criteria, $parent = DATATREE_ROOT, $allLevels = true, $restrict = 'name') { return PEAR::raiseError('not supported'); } /** * Returns a set of object ids based on a set of attribute criteria. * * @abstract * * @see buildAttributeQuery() * * @param array $criteria The array of criteria. * @param string $parent The parent node to start searching from. * @param boolean $allLevels Return all levels, or just the direct * children of $parent? Defaults to all levels. * @param string $restrict Only return attributes with the same * attribute_name or attribute_id. * @param integer $from The object to start to fetching * @param integer $count The number of objects to fetch * @param string $sortby_name Attribute name to use for sorting. * @param string $sortby_key Attribute key to use for sorting. * @param integer $direction Sort direction: * 0 - ascending * 1 - descending * * @return TODO */ function getByAttributes($criteria, $parent = DATATREE_ROOT, $allLevels = true, $restrict = 'name', $from = 0, $count = 0, $sortby_name = null, $sortby_key = null, $direction = 0) { return PEAR::raiseError('not supported'); } /** * Sorts IDs by attribute values. IDs without attributes will be added to * the end of the sorted list. * * @abstract * * @param array $unordered_ids Array of ids to sort. * @param array $sortby_name Attribute name to use for sorting. * @param array $sortby_key Attribute key to use for sorting. * @param array $direction Sort direction: * 0 - ascending * 1 - descending * * @return array Sorted ids. */ function sortByAttributes($unordered_ids, $sortby_name = null, $sortby_key = null, $direction = 0) { return PEAR::raiseError('not supported'); } /** * Update the data in an object. Does not change the object's * parent or name, just serialized data or attributes. * * @abstract * * @param DataTree $object A DataTree object. * * @return TODO */ function updateData($object) { return PEAR::raiseError('not supported'); } /** * Sort two objects by their order field, and if that is the same, * alphabetically (case insensitive) by name. * * You never call this function; it's used in uasort() calls. Do * NOT use usort(); you'll lose key => value associations. * * @private * * @param array $a The first object * @param array $b The second object * * @return integer 1 if $a should be first, * -1 if $b should be first, * 0 if they are entirely equal. */ function _cmp($a, $b) { if ($a['order'] > $b['order']) { return 1; } elseif ($a['order'] < $b['order']) { return -1; } else { return strcasecmp($a['name'], $b['name']); } } /** * Sorts two objects by their sorter hash field. * * You never call this function; it's used in uasort() calls. Do NOT use * usort(); you'll lose key => value associations. * * @since Horde 3.1 * * @private * * @param array $a The first object * @param array $b The second object * * @return integer 1 if $a should be first, * -1 if $b should be first, * 0 if they are entirely equal. */ function _cmpSorted($a, $b) { return intval($a['sorter'][$this->_sortHash] < $b['sorter'][$this->_sortHash]); } /** * Attempts to return a concrete DataTree instance based on $driver. * * @param mixed $driver The type of concrete DataTree 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/DataTree/ for * the subclass implementation named $driver[1].php. * @param array $params A hash containing any additional configuration or * connection parameters a subclass might need. * Here, we need 'group' = a string that defines * top-level groups of objects. * * @return DataTree The newly created concrete DataTree instance, or false * on an error. */ function &factory($driver, $params = null) { $driver = basename($driver); if (is_null($params)) { $params = Horde::getDriverConfig('datatree', $driver); } if (empty($driver)) { $driver = 'null'; } include_once 'Horde/DataTree/' . $driver . '.php'; $class = 'DataTree_' . $driver; if (class_exists($class)) { $dt = new $class($params); $result = $dt->_init(); if (is_a($result, 'PEAR_Error')) { include_once 'Horde/DataTree/null.php'; $dt = new DataTree_null($params); } } else { $dt = PEAR::raiseError('Class definition of ' . $class . ' not found.'); } return $dt; } /** * Attempts to return a reference to a concrete DataTree instance based on * $driver. * * It will only create a new instance if no DataTree instance with the same * parameters currently exists. * * This should be used if multiple DataTree sources (and, thus, multiple * DataTree instances) are required. * * This method must be invoked as: $var = &DataTree::singleton(); * * @param mixed $driver Type of concrete DataTree subclass to return, * based on storage driver ($driver). The code is * dynamically included. If $driver is an array, then * look in $driver[0]/lib/DataTree/ for subclass * implementation named $driver[1].php. * @param array $params A hash containing any additional configuration or * connection parameters a subclass might need. * * @return DataTree The concrete DataTree reference, or false on an error. */ function &singleton($driver, $params = null) { static $instances = array(); if (is_null($params)) { $params = Horde::getDriverConfig('datatree', $driver); } $signature = serialize(array($driver, $params)); if (!isset($instances[$signature])) { $instances[$signature] = &DataTree::factory($driver, $params); } return $instances[$signature]; } } /** * Class that can be extended to save arbitrary information as part of a stored * object. * * @author Stephane Huther * @author Chuck Hagenbuch * @since Horde 2.1 * @package Horde_DataTree */ class DataTreeObject { /** * This object's DataTree instance. * * @var DataTree */ var $datatree; /** * Key-value hash that will be serialized. * * @see getData() * @var array */ var $data = array(); /** * The unique name of this object. * These names have the same requirements as other object names - they must * be unique, etc. * * @var string */ var $name; /** * If this object has ordering data, store it here. * * @var integer */ var $order = null; /** * DataTreeObject constructor. * Just sets the $name parameter. * * @param string $name The object name. */ function DataTreeObject($name) { $this->setName($name); } /** * Sets the {@link DataTree} instance used to retrieve this object. * * @param DataTree $datatree A {@link DataTree} instance. */ function setDataTree(&$datatree) { $this->datatree = &$datatree; } /** * Gets the name of this object. * * @return string The object name. */ function getName() { return $this->name; } /** * Sets the name of this object. * * NOTE: Use with caution. This may throw out of sync the cached datatree * tables if not used properly. * * @param string $name The name to set this object's name to. */ function setName($name) { $this->name = $name; } /** * Gets the short name of this object. * For display purposes only. * * @return string The object's short name. */ function getShortName() { return DataTree::getShortName($this->name); } /** * Gets the ID of this object. * * @return string The object's ID. */ function getId() { return $this->datatree->getId($this); } /** * Gets the data array. * * @return array The internal data array. */ function getData() { return $this->data; } /** * Sets the data array. * * @param array The data array to store internally. */ function setData($data) { $this->data = $data; } /** * Sets the order of this object in its object collection. * * @param integer $order */ function setOrder($order) { $this->order = $order; } /** * Returns this object's parent. * * @param string $class Subclass of DataTreeObject to use. Defaults to * DataTreeObject. Null forces the driver to look * into the attributes table to determine the * subclass to use. If none is found it uses * DataTreeObject. * * @return DataTreeObject This object's parent */ function &getParent($class = 'DataTreeObject') { $id = $this->datatree->getParent($this); if (is_a($id, 'PEAR_Error')) { return $id; } return $this->datatree->getObjectById($id, $class); } /** * Returns a child of this object. * * @param string $name The child's name. * @param boolean $autocreate If true and no child with the given name * exists, one gets created. */ function &getChild($name, $autocreate = true) { $name = $this->getShortName() . ':' . $name; /* If the child shouldn't get created, we don't check for its * existance to return the "not found" error of * getObject(). */ if (!$autocreate || $this->datatree->exists($name)) { $child = &$this->datatree->getObject($name); } else { $child = new DataTreeObject($name); $child->setDataTree($this->datatree); $this->datatree->add($child); } return $child; } /** * Saves any changes to this object to the backend permanently. New objects * are added instead. * * @return boolean|PEAR_Error PEAR_Error on failure. */ function save() { if ($this->datatree->exists($this)) { return $this->datatree->updateData($this); } else { return $this->datatree->add($this); } } /** * Delete this object from the backend permanently. * * @return boolean|PEAR_Error PEAR_Error on failure. */ function delete() { return $this->datatree->remove($this); } /** * 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) { return isset($this->data[$attribute]) ? $this->data[$attribute] : null; } /** * Sets one of the attributes of the object. * * @param string $attribute The attribute to set. * @param mixed $value The value for $attribute. */ function set($attribute, $value) { $this->data[$attribute] = $value; } } * @since Horde 3.2 * @package Horde_Date */ class Horde_Date_Recurrence { /** * The start time of the event. * * @var Horde_Date */ var $start; /** * The end date of the recurrence interval. * * @var Horde_Date */ var $recurEnd = null; /** * The number of recurrences. * * @var integer */ var $recurCount = null; /** * The type of recurrence this event follows. HORDE_DATE_RECUR_* constant. * * @var integer */ var $recurType = HORDE_DATE_RECUR_NONE; /** * The length of time between recurrences. The time unit depends on the * recurrence type. * * @var integer */ var $recurInterval = 1; /** * Any additional recurrence data. * * @var integer */ var $recurData = null; /** * All the exceptions from recurrence for this event. * * @var array */ var $exceptions = array(); /** * All the dates this recurrence has been marked as completed. * * @var array */ var $completions = array(); /** * Constructor. * * @param Horde_Date $start Start of the recurring event. */ function Horde_Date_Recurrence($start) { $this->start = new Horde_Date($start); } /** * Checks if this event recurs on a given day of the week. * * @param integer $dayMask A mask consisting of HORDE_DATE_MASK_* * constants specifying the day(s) to check. * * @return boolean True if this event recurs on the given day(s). */ function recurOnDay($dayMask) { return ($this->recurData & $dayMask); } /** * Specifies the days this event recurs on. * * @param integer $dayMask A mask consisting of HORDE_DATE_MASK_* * constants specifying the day(s) to recur on. */ function setRecurOnDay($dayMask) { $this->recurData = $dayMask; } /** * Returns the days this event recurs on. * * @return integer A mask consisting of HORDE_DATE_MASK_* constants * specifying the day(s) this event recurs on. */ function getRecurOnDays() { return $this->recurData; } /** * Returns whether this event has a specific recurrence type. * * @param integer $recurrence HORDE_DATE_RECUR_* constant of the * recurrence type to check for. * * @return boolean True if the event has the specified recurrence type. */ function hasRecurType($recurrence) { return ($recurrence == $this->recurType); } /** * Sets a recurrence type for this event. * * @param integer $recurrence A HORDE_DATE_RECUR_* constant. */ function setRecurType($recurrence) { $this->recurType = $recurrence; } /** * Returns recurrence type of this event. * * @return integer A HORDE_DATE_RECUR_* constant. */ function getRecurType() { return $this->recurType; } /** * Returns a description of this event's recurring type. * * @return string Human readable recurring type. */ function getRecurName() { switch ($this->getRecurType()) { case HORDE_DATE_RECUR_NONE: return _("No recurrence"); case HORDE_DATE_RECUR_DAILY: return _("Daily"); case HORDE_DATE_RECUR_WEEKLY: return _("Weekly"); case HORDE_DATE_RECUR_MONTHLY_DATE: case HORDE_DATE_RECUR_MONTHLY_WEEKDAY: return _("Monthly"); case HORDE_DATE_RECUR_YEARLY_DATE: case HORDE_DATE_RECUR_YEARLY_DAY: case HORDE_DATE_RECUR_YEARLY_WEEKDAY: return _("Yearly"); } } /** * Sets the length of time between recurrences of this event. * * @param integer $interval The time between recurrences. */ function setRecurInterval($interval) { if ($interval > 0) { $this->recurInterval = $interval; } } /** * Retrieves the length of time between recurrences of this event. * * @return integer The number of seconds between recurrences. */ function getRecurInterval() { return $this->recurInterval; } /** * Sets the number of recurrences of this event. * * @param integer $count The number of recurrences. */ function setRecurCount($count) { if ($count > 0) { $this->recurCount = (int)$count; // Recurrence counts and end dates are mutually exclusive. $this->recurEnd = null; } else { $this->recurCount = null; } } /** * Retrieves the number of recurrences of this event. * * @return integer The number recurrences. */ function getRecurCount() { return $this->recurCount; } /** * Returns whether this event has a recurrence with a fixed count. * * @return boolean True if this recurrence has a fixed count. */ function hasRecurCount() { return isset($this->recurCount); } /** * Sets the start date of the recurrence interval. * * @param Horde_Date $start The recurrence start. */ function setRecurStart($start) { $this->start = new Horde_Date($start); } /** * Retrieves the start date of the recurrence interval. * * @return Horde_Date The recurrence start. */ function getRecurStart() { return $this->start; } /** * Sets the end date of the recurrence interval. * * @param Horde_Date $end The recurrence end. */ function setRecurEnd($end) { if (!empty($end)) { // Recurrence counts and end dates are mutually exclusive. $this->recurCount = null; } $this->recurEnd = new Horde_Date($end); } /** * Retrieves the end date of the recurrence interval. * * @return Horde_Date The recurrence end. */ function getRecurEnd() { return $this->recurEnd; } /** * Returns whether this event has a recurrence end. * * @return boolean True if this recurrence ends. */ function hasRecurEnd() { return isset($this->recurEnd) && isset($this->recurEnd->year) && $this->recurEnd->year != 9999; } /** * Finds the next recurrence of this event that's after $afterDate. * * @param Horde_Date $afterDate Return events after this date. * * @return Horde_Date|boolean The date of the next recurrence or false * if the event does not recur after * $afterDate. */ function nextRecurrence($afterDate) { $after = new Horde_Date($afterDate); $after->correct(); if ($this->start->compareDateTime($after) >= 0) { return new Horde_Date($this->start); } if ($this->recurInterval == 0) { return false; } switch ($this->getRecurType()) { case HORDE_DATE_RECUR_DAILY: $diff = Date_Calc::dateDiff($this->start->mday, $this->start->month, $this->start->year, $after->mday, $after->month, $after->year); $recur = ceil($diff / $this->recurInterval); if ($this->recurCount && $recur >= $this->recurCount) { return false; } $recur *= $this->recurInterval; $next = new Horde_Date($this->start); list($next->mday, $next->month, $next->year) = explode('/', Date_Calc::daysToDate(Date_Calc::dateToDays($next->mday, $next->month, $next->year) + $recur, '%e/%m/%Y')); if ((!$this->hasRecurEnd() || $next->compareDateTime($this->recurEnd) <= 0) && $next->compareDateTime($after) >= 0) { return new Horde_Date($next); } break; case HORDE_DATE_RECUR_WEEKLY: if (empty($this->recurData)) { return false; } list($start_week->mday, $start_week->month, $start_week->year) = explode('/', Date_Calc::beginOfWeek($this->start->mday, $this->start->month, $this->start->year, '%e/%m/%Y')); $start_week->hour = $this->start->hour; $start_week->min = $this->start->min; $start_week->sec = $this->start->sec; list($after_week->mday, $after_week->month, $after_week->year) = explode('/', Date_Calc::beginOfWeek($after->mday, $after->month, $after->year, '%e/%m/%Y')); $after_week_end = new Horde_Date($after_week); $after_week_end->mday += 7; $after_week_end->correct(); $diff = Date_Calc::dateDiff($start_week->mday, $start_week->month, $start_week->year, $after_week->mday, $after_week->month, $after_week->year); $interval = $this->recurInterval * 7; $repeats = floor($diff / $interval); if ($diff % $interval < 7) { $recur = $diff; } else { /** * If the after_week is not in the first week interval the * search needs to skip ahead a complete interval. The way it is * calculated here means that an event that occurs every second * week on Monday and Wednesday with the event actually starting * on Tuesday or Wednesday will only have one incidence in the * first week. */ $recur = $interval * ($repeats + 1); } if ($this->hasRecurCount()) { $recurrences = 0; /** * Correct the number of recurrences by the number of events * that lay between the start of the start week and the * recurrence start. */ $next = new Horde_Date($start_week); while ($next->compareDateTime($this->start) < 0) { if ($this->recurOnDay((int)pow(2, $next->dayOfWeek()))) { $recurrences--; } ++$next->mday; $next->correct(); } if ($repeats > 0) { $weekdays = $this->recurData; $total_recurrences_per_week = 0; while ($weekdays > 0) { if ($weekdays % 2) { $total_recurrences_per_week++; } $weekdays = ($weekdays - ($weekdays % 2)) / 2; } $recurrences += $total_recurrences_per_week * $repeats; } } $next = $start_week; list($next->mday, $next->month, $next->year) = explode('/', Date_Calc::daysToDate(Date_Calc::dateToDays($next->mday, $next->month, $next->year) + $recur, '%e/%m/%Y')); $next = new Horde_Date($next); while ($next->compareDateTime($after) < 0 && $next->compareDateTime($after_week_end) < 0) { if ($this->hasRecurCount() && $next->compareDateTime($after) < 0 && $this->recurOnDay((int)pow(2, $next->dayOfWeek()))) { $recurrences++; } ++$next->mday; $next->correct(); } if ($this->hasRecurCount() && $recurrences >= $this->recurCount) { return false; } if (!$this->hasRecurEnd() || $next->compareDateTime($this->recurEnd) <= 0) { if ($next->compareDateTime($after_week_end) >= 0) { return $this->nextRecurrence($after_week_end); } while (!$this->recurOnDay((int)pow(2, $next->dayOfWeek())) && $next->compareDateTime($after_week_end) < 0) { ++$next->mday; $next->correct(); } if (!$this->hasRecurEnd() || $next->compareDateTime($this->recurEnd) <= 0) { if ($next->compareDateTime($after_week_end) >= 0) { return $this->nextRecurrence($after_week_end); } else { return $next; } } } break; case HORDE_DATE_RECUR_MONTHLY_DATE: $start = new Horde_Date($this->start); if ($after->compareDateTime($start) < 0) { $after = $start; } // If we're starting past this month's recurrence of the event, // look in the next month on the day the event recurs. if ($after->mday > $start->mday) { ++$after->month; $after->mday = $start->mday; $after->correct(); } // Adjust $start to be the first match. $offset = ($after->month - $start->month) + ($after->year - $start->year) * 12; $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; if ($this->recurCount && ($offset / $this->recurInterval) >= $this->recurCount) { return false; } $start->month += $offset; $count = $offset / $this->recurInterval; do { if ($this->recurCount && $count++ >= $this->recurCount) { return false; } // Don't correct for day overflow; we just skip February 30th, // for example. $start->correct(HORDE_DATE_MASK_MONTH); // Bail if we've gone past the end of recurrence. if ($this->hasRecurEnd() && $this->recurEnd->compareDateTime($start) < 0) { return false; } if ($start->isValid()) { return $start; } // If the interval is 12, and the date isn't valid, then we // need to see if February 29th is an option. If not, then the // event will _never_ recur, and we need to stop checking to // avoid an infinite loop. if ($this->recurInterval == 12 && ($start->month != 2 || $start->mday > 29)) { return false; } // Add the recurrence interval. $start->month += $this->recurInterval; } while (true); break; case HORDE_DATE_RECUR_MONTHLY_WEEKDAY: // Start with the start date of the event. $estart = new Horde_Date($this->start); // What day of the week, and week of the month, do we recur on? $nth = ceil($this->start->mday / 7); $weekday = $estart->dayOfWeek(); // Adjust $estart to be the first candidate. $offset = ($after->month - $estart->month) + ($after->year - $estart->year) * 12; $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; // Adjust our working date until it's after $after. $estart->month += $offset - $this->recurInterval; $count = $offset / $this->recurInterval; do { if ($this->recurCount && $count++ >= $this->recurCount) { return false; } $estart->month += $this->recurInterval; $estart->correct(); $next = new Horde_Date($estart); $next->setNthWeekday($weekday, $nth); if ($next->compareDateTime($after) < 0) { // We haven't made it past $after yet, try again. continue; } if ($this->hasRecurEnd() && $next->compareDateTime($this->recurEnd) > 0) { // We've gone past the end of recurrence; we can give up // now. return false; } // We have a candidate to return. break; } while (true); return $next; case HORDE_DATE_RECUR_YEARLY_DATE: // Start with the start date of the event. $estart = new Horde_Date($this->start); if ($after->month > $estart->month || ($after->month == $estart->month && $after->mday > $estart->mday)) { ++$after->year; $after->month = $estart->month; $after->mday = $estart->mday; } // Seperate case here for February 29th if ($estart->month == 2 && $estart->mday == 29) { while (!Horde_Date::isLeapYear($after->year)) { ++$after->year; } } // Adjust $estart to be the first candidate. $offset = $after->year - $estart->year; if ($offset > 0) { $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; $estart->year += $offset; } // We've gone past the end of recurrence; give up. if ($this->recurCount && $offset >= $this->recurCount) { return false; } if ($this->hasRecurEnd() && $this->recurEnd->compareDateTime($estart) < 0) { return false; } return $estart; case HORDE_DATE_RECUR_YEARLY_DAY: // Check count first. $dayofyear = $this->start->dayOfYear(); $count = ($after->year - $this->start->year) / $this->recurInterval + 1; if ($this->recurCount && ($count > $this->recurCount || ($count == $this->recurCount && $after->dayOfYear() > $dayofyear))) { return false; } // Start with a rough interval. $estart = new Horde_Date($this->start); $estart->year += floor($count - 1) * $this->recurInterval; // Now add the difference to the required day of year. $estart->mday += $dayofyear - $estart->dayOfYear(); $estart->correct(); // Add an interval if the estimation was wrong. if ($estart->compareDate($after) < 0) { $estart->year += $this->recurInterval; $estart->mday += $dayofyear - $estart->dayOfYear(); $estart->correct(); } // We've gone past the end of recurrence; give up. if ($this->hasRecurEnd() && $this->recurEnd->compareDateTime($estart) < 0) { return false; } return $estart; case HORDE_DATE_RECUR_YEARLY_WEEKDAY: // Start with the start date of the event. $estart = new Horde_Date($this->start); // What day of the week, and week of the month, do we recur on? $nth = ceil($this->start->mday / 7); $weekday = $estart->dayOfWeek(); // Adjust $estart to be the first candidate. $offset = floor(($after->year - $estart->year + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; // Adjust our working date until it's after $after. $estart->year += $offset - $this->recurInterval; $count = $offset / $this->recurInterval; do { if ($this->recurCount && $count++ >= $this->recurCount) { return false; } $estart->year += $this->recurInterval; $estart->correct(); $next = new Horde_Date($estart); $next->setNthWeekday($weekday, $nth); if ($next->compareDateTime($after) < 0) { // We haven't made it past $after yet, try again. continue; } if ($this->hasRecurEnd() && $next->compareDateTime($this->recurEnd) > 0) { // We've gone past the end of recurrence; we can give up // now. return false; } // We have a candidate to return. break; } while (true); return $next; } // We didn't find anything, the recurType was bad, or something else // went wrong - return false. return false; } /** * Returns whether this event has any date that matches the recurrence * rules and is not an exception. * * @return boolean True if an active recurrence exists. */ function hasActiveRecurrence() { if (!$this->hasRecurEnd()) { return true; } $next = $this->nextRecurrence(new Horde_Date($this->start)); while (is_object($next)) { if (!$this->hasException($next->year, $next->month, $next->mday) && !$this->hasCompletion($next->year, $next->month, $next->mday)) { return true; } $next = $this->nextRecurrence(array('year' => $next->year, 'month' => $next->month, 'mday' => $next->mday + 1, 'hour' => $next->hour, 'min' => $next->min, 'sec' => $next->sec)); } return false; } /** * Returns the next active recurrence. * * @param Horde_Date $afterDate Return events after this date. * * @return Horde_Date|boolean The date of the next active * recurrence or false if the event * has no active recurrence after * $afterDate. */ function nextActiveRecurrence($afterDate) { $next = $this->nextRecurrence($afterDate); while (is_object($next)) { if (!$this->hasException($next->year, $next->month, $next->mday) && !$this->hasCompletion($next->year, $next->month, $next->mday)) { return $next; } $next->mday++; $next = $this->nextRecurrence($next); } return false; } /** * Adds an exception to a recurring event. * * @param integer $year The year of the execption. * @param integer $month The month of the execption. * @param integer $mday The day of the month of the exception. */ function addException($year, $month, $mday) { $this->exceptions[] = sprintf('%04d%02d%02d', $year, $month, $mday); } /** * Deletes an exception from a recurring event. * * @param integer $year The year of the execption. * @param integer $month The month of the execption. * @param integer $mday The day of the month of the exception. */ function deleteException($year, $month, $mday) { $key = array_search(sprintf('%04d%02d%02d', $year, $month, $mday), $this->exceptions); if ($key !== false) { unset($this->exceptions[$key]); } } /** * Checks if an exception exists for a given reccurence of an event. * * @param integer $year The year of the reucrance. * @param integer $month The month of the reucrance. * @param integer $mday The day of the month of the reucrance. * * @return boolean True if an exception exists for the given date. */ function hasException($year, $month, $mday) { return in_array(sprintf('%04d%02d%02d', $year, $month, $mday), $this->getExceptions()); } /** * Retrieves all the exceptions for this event. * * @return array Array containing the dates of all the exceptions in * YYYYMMDD form. */ function getExceptions() { return $this->exceptions; } /** * Adds a completion to a recurring event. * * @param integer $year The year of the execption. * @param integer $month The month of the execption. * @param integer $mday The day of the month of the completion. */ function addCompletion($year, $month, $mday) { $this->completions[] = sprintf('%04d%02d%02d', $year, $month, $mday); } /** * Deletes a completion from a recurring event. * * @param integer $year The year of the execption. * @param integer $month The month of the execption. * @param integer $mday The day of the month of the completion. */ function deleteCompletion($year, $month, $mday) { $key = array_search(sprintf('%04d%02d%02d', $year, $month, $mday), $this->completions); if ($key !== false) { unset($this->completions[$key]); } } /** * Checks if a completion exists for a given reccurence of an event. * * @param integer $year The year of the reucrance. * @param integer $month The month of the recurrance. * @param integer $mday The day of the month of the recurrance. * * @return boolean True if a completion exists for the given date. */ function hasCompletion($year, $month, $mday) { return in_array(sprintf('%04d%02d%02d', $year, $month, $mday), $this->getCompletions()); } /** * Retrieves all the completions for this event. * * @return array Array containing the dates of all the completions in * YYYYMMDD form. */ function getCompletions() { return $this->completions; } /** * Parses a vCalendar 1.0 recurrence rule. * * @link http://www.imc.org/pdi/vcal-10.txt * @link http://www.shuchow.com/vCalAddendum.html * * @param string $rrule A vCalendar 1.0 conform RRULE value. */ function fromRRule10($rrule) { if (!$rrule) { return; } if (!preg_match('/([A-Z]+)(\d+)?(.*)/', $rrule, $matches)) { // No recurrence data - event does not recur. $this->setRecurType(HORDE_DATE_RECUR_NONE); } // Always default the recurInterval to 1. $this->setRecurInterval(!empty($matches[2]) ? $matches[2] : 1); $remainder = trim($matches[3]); switch ($matches[1]) { case 'D': $this->setRecurType(HORDE_DATE_RECUR_DAILY); break; case 'W': $this->setRecurType(HORDE_DATE_RECUR_WEEKLY); if (!empty($remainder)) { $maskdays = array('SU' => HORDE_DATE_MASK_SUNDAY, 'MO' => HORDE_DATE_MASK_MONDAY, 'TU' => HORDE_DATE_MASK_TUESDAY, 'WE' => HORDE_DATE_MASK_WEDNESDAY, 'TH' => HORDE_DATE_MASK_THURSDAY, 'FR' => HORDE_DATE_MASK_FRIDAY, 'SA' => HORDE_DATE_MASK_SATURDAY); $mask = 0; while (preg_match('/^ ?[A-Z]{2} ?/', $remainder, $matches)) { $day = trim($matches[0]); $remainder = substr($remainder, strlen($matches[0])); $mask |= $maskdays[$day]; } $this->setRecurOnDay($mask); } else { // Recur on the day of the week of the original recurrence. $maskdays = array(HORDE_DATE_SUNDAY => HORDE_DATE_MASK_SUNDAY, HORDE_DATE_MONDAY => HORDE_DATE_MASK_MONDAY, HORDE_DATE_TUESDAY => HORDE_DATE_MASK_TUESDAY, HORDE_DATE_WEDNESDAY => HORDE_DATE_MASK_WEDNESDAY, HORDE_DATE_THURSDAY => HORDE_DATE_MASK_THURSDAY, HORDE_DATE_FRIDAY => HORDE_DATE_MASK_FRIDAY, HORDE_DATE_SATURDAY => HORDE_DATE_MASK_SATURDAY); $this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]); } break; case 'MP': $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_WEEKDAY); break; case 'MD': $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_DATE); break; case 'YM': $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DATE); break; case 'YD': $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DAY); break; } // We don't support modifiers at the moment, strip them. while ($remainder && !preg_match('/^(#\d+|\d{8})($| |T\d{6})/', $remainder)) { $remainder = substr($remainder, 1); } if (!empty($remainder)) { if (strpos($remainder, '#') === 0) { $this->setRecurCount(substr($remainder, 1)); } else { list($year, $month, $mday) = sscanf($remainder, '%04d%02d%02d'); $this->setRecurEnd(new Horde_Date(array('year' => $year, 'month' => $month, 'mday' => $mday))); } } } /** * Creates a vCalendar 1.0 recurrence rule. * * @link http://www.imc.org/pdi/vcal-10.txt * @link http://www.shuchow.com/vCalAddendum.html * * @param Horde_iCalendar $calendar A Horde_iCalendar object instance. * * @return string A vCalendar 1.0 conform RRULE value. */ function toRRule10($calendar) { switch ($this->recurType) { case HORDE_DATE_RECUR_NONE: return ''; case HORDE_DATE_RECUR_DAILY: $rrule = 'D' . $this->recurInterval; break; case HORDE_DATE_RECUR_WEEKLY: $rrule = 'W' . $this->recurInterval; $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); for ($i = 0; $i <= 7 ; ++$i) { if ($this->recurOnDay(pow(2, $i))) { $rrule .= ' ' . $vcaldays[$i]; } } break; case HORDE_DATE_RECUR_MONTHLY_DATE: $rrule = 'MD' . $this->recurInterval . ' ' . trim($this->start->mday); break; case HORDE_DATE_RECUR_MONTHLY_WEEKDAY: $nth_weekday = (int)($this->start->mday / 7); if (($this->start->mday % 7) > 0) { $nth_weekday++; } $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); $rrule = 'MP' . $this->recurInterval . ' ' . $nth_weekday . '+ ' . $vcaldays[$this->start->dayOfWeek()]; break; case HORDE_DATE_RECUR_YEARLY_DATE: $rrule = 'YM' . $this->recurInterval . ' ' . trim($this->start->month); break; case HORDE_DATE_RECUR_YEARLY_DAY: $rrule = 'YD' . $this->recurInterval . ' ' . $this->start->dayOfYear(); break; default: return ''; } if ($this->hasRecurEnd()) { $recurEnd = new Horde_Date($this->recurEnd); $recurEnd->mday++; return $rrule . ' ' . $calendar->_exportDateTime($recurEnd); } return $rrule . ' #' . (int)$this->getRecurCount(); } /** * Parses an iCalendar 2.0 recurrence rule. * * @link http://rfc.net/rfc2445.html#s4.3.10 * @link http://rfc.net/rfc2445.html#s4.8.5 * @link http://www.shuchow.com/vCalAddendum.html * * @param string $rrule An iCalendar 2.0 conform RRULE value. */ function fromRRule20($rrule) { // Parse the recurrence rule into keys and values. $rdata = array(); $parts = explode(';', $rrule); foreach ($parts as $part) { list($key, $value) = explode('=', $part, 2); $rdata[String::upper($key)] = $value; } if (isset($rdata['FREQ'])) { // Always default the recurInterval to 1. $this->setRecurInterval(isset($rdata['INTERVAL']) ? $rdata['INTERVAL'] : 1); switch (String::upper($rdata['FREQ'])) { case 'DAILY': $this->setRecurType(HORDE_DATE_RECUR_DAILY); break; case 'WEEKLY': $this->setRecurType(HORDE_DATE_RECUR_WEEKLY); if (isset($rdata['BYDAY'])) { $maskdays = array('SU' => HORDE_DATE_MASK_SUNDAY, 'MO' => HORDE_DATE_MASK_MONDAY, 'TU' => HORDE_DATE_MASK_TUESDAY, 'WE' => HORDE_DATE_MASK_WEDNESDAY, 'TH' => HORDE_DATE_MASK_THURSDAY, 'FR' => HORDE_DATE_MASK_FRIDAY, 'SA' => HORDE_DATE_MASK_SATURDAY); $days = explode(',', $rdata['BYDAY']); $mask = 0; foreach ($days as $day) { $mask |= $maskdays[$day]; } $this->setRecurOnDay($mask); } else { // Recur on the day of the week of the original // recurrence. $maskdays = array( HORDE_DATE_SUNDAY => HORDE_DATE_MASK_SUNDAY, HORDE_DATE_MONDAY => HORDE_DATE_MASK_MONDAY, HORDE_DATE_TUESDAY => HORDE_DATE_MASK_TUESDAY, HORDE_DATE_WEDNESDAY => HORDE_DATE_MASK_WEDNESDAY, HORDE_DATE_THURSDAY => HORDE_DATE_MASK_THURSDAY, HORDE_DATE_FRIDAY => HORDE_DATE_MASK_FRIDAY, HORDE_DATE_SATURDAY => HORDE_DATE_MASK_SATURDAY); $this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]); } break; case 'MONTHLY': if (isset($rdata['BYDAY'])) { $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_WEEKDAY); } else { $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_DATE); } break; case 'YEARLY': if (isset($rdata['BYYEARDAY'])) { $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DAY); } elseif (isset($rdata['BYDAY'])) { $this->setRecurType(HORDE_DATE_RECUR_YEARLY_WEEKDAY); } else { $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DATE); } break; } if (isset($rdata['UNTIL'])) { list($year, $month, $mday) = sscanf($rdata['UNTIL'], '%04d%02d%02d'); $this->setRecurEnd(new Horde_Date(array('year' => $year, 'month' => $month, 'mday' => $mday))); } if (isset($rdata['COUNT'])) { $this->setRecurCount($rdata['COUNT']); } } else { // No recurrence data - event does not recur. $this->setRecurType(HORDE_DATE_RECUR_NONE); } } /** * Creates an iCalendar 2.0 recurrence rule. * * @link http://rfc.net/rfc2445.html#s4.3.10 * @link http://rfc.net/rfc2445.html#s4.8.5 * @link http://www.shuchow.com/vCalAddendum.html * * @param Horde_iCalendar $calendar A Horde_iCalendar object instance. * * @return string An iCalendar 2.0 conform RRULE value. */ function toRRule20($calendar) { switch ($this->recurType) { case HORDE_DATE_RECUR_NONE: return ''; case HORDE_DATE_RECUR_DAILY: $rrule = 'FREQ=DAILY;INTERVAL=' . $this->recurInterval; break; case HORDE_DATE_RECUR_WEEKLY: $rrule = 'FREQ=WEEKLY;INTERVAL=' . $this->recurInterval . ';BYDAY='; $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); for ($i = $flag = 0; $i <= 7 ; ++$i) { if ($this->recurOnDay(pow(2, $i))) { if ($flag) { $rrule .= ','; } $rrule .= $vcaldays[$i]; $flag = true; } } break; case HORDE_DATE_RECUR_MONTHLY_DATE: $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval; break; case HORDE_DATE_RECUR_MONTHLY_WEEKDAY: $nth_weekday = (int)($this->start->mday / 7); if (($this->start->mday % 7) > 0) { $nth_weekday++; } $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval . ';BYDAY=' . $nth_weekday . $vcaldays[$this->start->dayOfWeek()]; break; case HORDE_DATE_RECUR_YEARLY_DATE: $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval; break; case HORDE_DATE_RECUR_YEARLY_DAY: $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval . ';BYYEARDAY=' . $this->start->dayOfYear(); break; case HORDE_DATE_RECUR_YEARLY_WEEKDAY: $nth_weekday = (int)($this->start->mday / 7); if (($this->start->mday % 7) > 0) { $nth_weekday++; } $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval . ';BYDAY=' . $nth_weekday . $vcaldays[$this->start->dayOfWeek()] . ';BYMONTH=' . $this->start->month; break; } if ($this->hasRecurEnd()) { $recurEnd = new Horde_Date($this->recurEnd); $recurEnd->mday++; $rrule .= ';UNTIL=' . $calendar->_exportDateTime($recurEnd); } if ($count = $this->getRecurCount()) { $rrule .= ';COUNT=' . $count; } return $rrule; } /** * Parses the recurrence data from a hash. * * @param array $hash The hash to convert. * * @return boolean True if the hash seemed valid, false otherwise. */ function fromHash($hash) { if (!isset($hash['interval']) || !isset($hash['interval']) || !isset($hash['range-type'])) { $this->setRecurType(HORDE_DATE_RECUR_NONE); return false; } $this->setRecurInterval((int) $hash['interval']); $parse_day = false; $set_daymask = false; $update_month = false; $update_daynumber = false; $update_weekday = false; $nth_weekday = -1; switch ($hash['cycle']) { case 'daily': $this->setRecurType(HORDE_DATE_RECUR_DAILY); break; case 'weekly': $this->setRecurType(HORDE_DATE_RECUR_WEEKLY); $parse_day = true; $set_daymask = true; break; case 'monthly': if (!isset($hash['daynumber'])) { $this->setRecurType(HORDE_DATE_RECUR_NONE); return false; } switch ($hash['type']) { case 'daynumber': $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_DATE); $update_daynumber = true; break; case 'weekday': $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_WEEKDAY); $nth_weekday = (int) $hash['daynumber']; $hash['daynumber'] = 1; $parse_day = true; $update_daynumber = true; $update_weekday = true; break; } break; case 'yearly': if (!isset($hash['type'])) { $this->setRecurType(HORDE_DATE_RECUR_NONE); return false; } switch ($hash['type']) { case 'monthday': $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DATE); $update_month = true; $update_daynumber = true; break; case 'yearday': if (!isset($hash['month'])) { $this->setRecurType(HORDE_DATE_RECUR_NONE); return false; } $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DAY); // Start counting days in January. $hash['month'] = 'january'; $update_month = true; $update_daynumber = true; break; case 'weekday': if (!isset($hash['daynumber'])) { $this->setRecurType(HORDE_DATE_RECUR_NONE); return false; } $this->setRecurType(HORDE_DATE_RECUR_YEARLY_WEEKDAY); $nth_weekday = (int) $hash['daynumber']; $hash['daynumber'] = 1; $parse_day = true; $update_month = true; $update_daynumber = true; $update_weekday = true; break; } } switch ($hash['range-type']) { case 'number': if (!isset($hash['range'])) { $this->setRecurType(HORDE_DATE_RECUR_NONE); return false; } $this->setRecurCount((int) $hash['range']); break; case 'date': $recur_end = new Horde_Date($hash['range']); $recur_end->hour = 23; $recur_end->min = 59; $recur_end->sec = 59; $this->setRecurEnd($recur_end); break; } // Need to parse ? $last_found_day = -1; if ($parse_day) { if (!isset($hash['day'])) { $this->setRecurType(HORDE_DATE_RECUR_NONE); return false; } $mask = 0; $bits = array( 'monday' => HORDE_DATE_MASK_MONDAY, 'tuesday' => HORDE_DATE_MASK_TUESDAY, 'wednesday' => HORDE_DATE_MASK_WEDNESDAY, 'thursday' => HORDE_DATE_MASK_THURSDAY, 'friday' => HORDE_DATE_MASK_FRIDAY, 'saturday' => HORDE_DATE_MASK_SATURDAY, 'sunday' => HORDE_DATE_MASK_SUNDAY, ); $days = array( 'monday' => HORDE_DATE_MONDAY, 'tuesday' => HORDE_DATE_TUESDAY, 'wednesday' => HORDE_DATE_WEDNESDAY, 'thursday' => HORDE_DATE_THURSDAY, 'friday' => HORDE_DATE_FRIDAY, 'saturday' => HORDE_DATE_SATURDAY, 'sunday' => HORDE_DATE_SUNDAY, ); foreach ($hash['day'] as $day) { // Validity check. if (empty($day) || !isset($bits[$day])) { continue; } $mask |= $bits[$day]; $last_found_day = $days[$day]; } if ($set_daymask) { $this->setRecurOnDay($mask); } } if ($update_month || $update_daynumber || $update_weekday) { if ($update_month) { $month2number = array( 'january' => 1, 'february' => 2, 'march' => 3, 'april' => 4, 'may' => 5, 'june' => 6, 'july' => 7, 'august' => 8, 'september' => 9, 'october' => 10, 'november' => 11, 'december' => 12, ); if (isset($month2number[$hash['month']])) { $this->start->month = $month2number[$hash['month']]; } } if ($update_daynumber) { if (!isset($hash['daynumber'])) { $this->setRecurType(HORDE_DATE_RECUR_NONE); return false; } $this->start->mday = $hash['daynumber']; } if ($update_weekday) { $this->start->setNthWeekday($last_found_day, $nth_weekday); } $this->start->correct(); } // Exceptions. if (isset($hash['exceptions'])) { $this->exceptions = $hash['exceptions']; } if (isset($hash['completions'])) { $this->completions = $hash['completions']; } return true; } /** * Export this object into a hash. * * @return array The recurrence hash. */ function toHash() { if ($this->getRecurType() == HORDE_DATE_RECUR_NONE) { return array(); } $day2number = array( 0 => 'sunday', 1 => 'monday', 2 => 'tuesday', 3 => 'wednesday', 4 => 'thursday', 5 => 'friday', 6 => 'saturday' ); $month2number = array( 1 => 'january', 2 => 'february', 3 => 'march', 4 => 'april', 5 => 'may', 6 => 'june', 7 => 'july', 8 => 'august', 9 => 'september', 10 => 'october', 11 => 'november', 12 => 'december' ); $hash = array('interval' => $this->getRecurInterval()); $start = $this->getRecurStart(); switch ($this->getRecurType()) { case HORDE_DATE_RECUR_DAILY: $hash['cycle'] = 'daily'; break; case HORDE_DATE_RECUR_WEEKLY: $hash['cycle'] = 'weekly'; $bits = array( 'monday' => HORDE_DATE_MASK_MONDAY, 'tuesday' => HORDE_DATE_MASK_TUESDAY, 'wednesday' => HORDE_DATE_MASK_WEDNESDAY, 'thursday' => HORDE_DATE_MASK_THURSDAY, 'friday' => HORDE_DATE_MASK_FRIDAY, 'saturday' => HORDE_DATE_MASK_SATURDAY, 'sunday' => HORDE_DATE_MASK_SUNDAY, ); $days = array(); foreach($bits as $name => $bit) { if ($this->recurOnDay($bit)) { $days[] = $name; } } $hash['day'] = $days; break; case HORDE_DATE_RECUR_MONTHLY_DATE: $hash['cycle'] = 'monthly'; $hash['type'] = 'daynumber'; $hash['daynumber'] = $start->mday; break; case HORDE_DATE_RECUR_MONTHLY_WEEKDAY: $hash['cycle'] = 'monthly'; $hash['type'] = 'weekday'; $hash['daynumber'] = $start->weekOfMonth(); $hash['day'] = array ($day2number[$start->dayOfWeek()]); break; case HORDE_DATE_RECUR_YEARLY_DATE: $hash['cycle'] = 'yearly'; $hash['type'] = 'monthday'; $hash['daynumber'] = $start->mday; $hash['month'] = $month2number[$start->month]; break; case HORDE_DATE_RECUR_YEARLY_DAY: $hash['cycle'] = 'yearly'; $hash['type'] = 'yearday'; $hash['daynumber'] = $start->dayOfYear(); break; case HORDE_DATE_RECUR_YEARLY_WEEKDAY: $hash['cycle'] = 'yearly'; $hash['type'] = 'weekday'; $hash['daynumber'] = $start->weekOfMonth(); $hash['day'] = array ($day2number[$start->dayOfWeek()]); $hash['month'] = $month2number[$start->month]; } if ($this->hasRecurCount()) { $hash['range-type'] = 'number'; $hash['range'] = $this->getRecurCount(); } elseif ($this->hasRecurEnd()) { $date = $this->getRecurEnd(); $hash['range-type'] = 'date'; $hash['range'] = $date->datestamp(); } else { $hash['range-type'] = 'none'; $hash['range'] = ''; } // Recurrence exceptions $hash['exceptions'] = $this->exceptions; $hash['completions'] = $this->completions; return $hash; } } _supportedSpecs .= 'bBpxX'; } if (is_array($date) || is_object($date)) { foreach ($date as $key => $val) { if (in_array($key, array('year', 'month', 'mday', 'hour', 'min', 'sec'))) { $this->$key = (int)$val; } } // If $date['day'] is present and numeric we may have been passed // a Horde_Form_datetime array. if (is_array($date) && isset($date['day']) && is_numeric($date['day'])) { $this->mday = (int)$date['day']; } // 'minute' key also from Horde_Form_datetime if (is_array($date) && isset($date['minute'])) { $this->min = $date['minute']; } } elseif (!is_null($date)) { // Match YYYY-MM-DD HH:MM:SS, YYYYMMDDHHMMSS and YYYYMMDD'T'HHMMSS'Z'. if (preg_match('/(\d{4})-?(\d{2})-?(\d{2})T? ?(\d{2}):?(\d{2}):?(\d{2})Z?/', $date, $parts)) { $this->year = (int)$parts[1]; $this->month = (int)$parts[2]; $this->mday = (int)$parts[3]; $this->hour = (int)$parts[4]; $this->min = (int)$parts[5]; $this->sec = (int)$parts[6]; } else { // Try as a timestamp. $parts = @getdate($date); if ($parts) { $this->year = $parts['year']; $this->month = $parts['mon']; $this->mday = $parts['mday']; $this->hour = $parts['hours']; $this->min = $parts['minutes']; $this->sec = $parts['seconds']; } } } } /** * @static */ function isLeapYear($year) { if (strlen($year) != 4 || preg_match('/\D/', $year)) { return false; } return (($year % 4 == 0 && $year % 100 != 0) || $year % 400 == 0); } /** * Returns the day of the year (1-366) that corresponds to the * first day of the given week. * * TODO: with PHP 5.1+, see http://derickrethans.nl/calculating_start_and_end_dates_of_a_week.php * * @param integer $week The week of the year to find the first day of. * @param integer $year The year to calculate for. * * @return integer The day of the year of the first day of the given week. */ function firstDayOfWeek($week, $year) { $jan1 = new Horde_Date(array('year' => $year, 'month' => 1, 'mday' => 1)); $start = $jan1->dayOfWeek(); if ($start > HORDE_DATE_THURSDAY) { $start -= 7; } return (($week * 7) - (7 + $start)) + 1; } /** * @static */ function daysInMonth($month, $year) { if ($month == 2) { if (Horde_Date::isLeapYear($year)) { return 29; } else { return 28; } } elseif ($month == 4 || $month == 6 || $month == 9 || $month == 11) { return 30; } else { return 31; } } /** * Return the day of the week (0 = Sunday, 6 = Saturday) of this * object's date. * * @return integer The day of the week. */ function dayOfWeek() { if ($this->month > 2) { $month = $this->month - 2; $year = $this->year; } else { $month = $this->month + 10; $year = $this->year - 1; } $day = (floor((13 * $month - 1) / 5) + $this->mday + ($year % 100) + floor(($year % 100) / 4) + floor(($year / 100) / 4) - 2 * floor($year / 100) + 77); return (int)($day - 7 * floor($day / 7)); } /** * Returns the day number of the year (1 to 365/366). * * @return integer The day of the year. */ function dayOfYear() { $monthTotals = array(0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334); $dayOfYear = $this->mday + $monthTotals[$this->month - 1]; if (Horde_Date::isLeapYear($this->year) && $this->month > 2) { ++$dayOfYear; } return $dayOfYear; } /** * Returns the week of the month. * * @since Horde 3.2 * * @return integer The week number. */ function weekOfMonth() { return ceil($this->mday / 7); } /** * Returns the week of the year, first Monday is first day of first week. * * @return integer The week number. */ function weekOfYear() { return $this->format('W'); } /** * Return the number of weeks in the given year (52 or 53). * * @static * * @param integer $year The year to count the number of weeks in. * * @return integer $numWeeks The number of weeks in $year. */ function weeksInYear($year) { // Find the last Thursday of the year. $day = 31; $date = new Horde_Date(array('year' => $year, 'month' => 12, 'mday' => $day, 'hour' => 0, 'min' => 0, 'sec' => 0)); while ($date->dayOfWeek() != HORDE_DATE_THURSDAY) { --$date->mday; } return $date->weekOfYear(); } /** * Set the date of this object to the $nth weekday of $weekday. * * @param integer $weekday The day of the week (0 = Sunday, etc). * @param integer $nth The $nth $weekday to set to (defaults to 1). */ function setNthWeekday($weekday, $nth = 1) { if ($weekday < HORDE_DATE_SUNDAY || $weekday > HORDE_DATE_SATURDAY) { return false; } $this->mday = 1; $first = $this->dayOfWeek(); if ($weekday < $first) { $this->mday = 8 + $weekday - $first; } else { $this->mday = $weekday - $first + 1; } $this->mday += 7 * $nth - 7; $this->correct(); return true; } function dump($prefix = '') { echo ($prefix ? $prefix . ': ' : '') . $this->year . '-' . $this->month . '-' . $this->mday . "
\n"; } /** * Is the date currently represented by this object a valid date? * * @return boolean Validity, counting leap years, etc. */ function isValid() { if ($this->year < 0 || $this->year > 9999) { return false; } return checkdate($this->month, $this->mday, $this->year); } /** * Correct any over- or underflows in any of the date's members. * * @param integer $mask We may not want to correct some overflows. */ function correct($mask = HORDE_DATE_MASK_ALLPARTS) { if ($mask & HORDE_DATE_MASK_SECOND) { while ($this->sec < 0) { --$this->min; $this->sec += 60; } while ($this->sec > 59) { ++$this->min; $this->sec -= 60; } } if ($mask & HORDE_DATE_MASK_MINUTE) { while ($this->min < 0) { --$this->hour; $this->min += 60; } while ($this->min > 59) { ++$this->hour; $this->min -= 60; } } if ($mask & HORDE_DATE_MASK_HOUR) { while ($this->hour < 0) { --$this->mday; $this->hour += 24; } while ($this->hour > 23) { ++$this->mday; $this->hour -= 24; } } if ($mask & HORDE_DATE_MASK_MONTH) { while ($this->month > 12) { ++$this->year; $this->month -= 12; } while ($this->month < 1) { --$this->year; $this->month += 12; } } if ($mask & HORDE_DATE_MASK_DAY) { while ($this->mday > Horde_Date::daysInMonth($this->month, $this->year)) { $this->mday -= Horde_Date::daysInMonth($this->month, $this->year); ++$this->month; $this->correct(HORDE_DATE_MASK_MONTH); } while ($this->mday < 1) { --$this->month; $this->correct(HORDE_DATE_MASK_MONTH); $this->mday += Horde_Date::daysInMonth($this->month, $this->year); } } } /** * Compare this date to another date object to see which one is * greater (later). Assumes that the dates are in the same * timezone. * * @param mixed $date The date to compare to. * * @return integer == 0 if the dates are equal * >= 1 if this date is greater (later) * <= -1 if the other date is greater (later) */ function compareDate($date) { if (!is_a($date, 'Horde_Date')) { $date = new Horde_Date($date); } if ($this->year != $date->year) { return $this->year - $date->year; } if ($this->month != $date->month) { return $this->month - $date->month; } return $this->mday - $date->mday; } /** * Compare this to another date object by time, to see which one * is greater (later). Assumes that the dates are in the same * timezone. * * @param mixed $date The date to compare to. * * @return integer == 0 if the dates are equal * >= 1 if this date is greater (later) * <= -1 if the other date is greater (later) */ function compareTime($date) { if (!is_a($date, 'Horde_Date')) { $date = new Horde_Date($date); } if ($this->hour != $date->hour) { return $this->hour - $date->hour; } if ($this->min != $date->min) { return $this->min - $date->min; } return $this->sec - $date->sec; } /** * Compare this to another date object, including times, to see * which one is greater (later). Assumes that the dates are in the * same timezone. * * @param mixed $date The date to compare to. * * @return integer == 0 if the dates are equal * >= 1 if this date is greater (later) * <= -1 if the other date is greater (later) */ function compareDateTime($date) { if (!is_a($date, 'Horde_Date')) { $date = new Horde_Date($date); } if ($diff = $this->compareDate($date)) { return $diff; } return $this->compareTime($date); } /** * Get the time offset for local time zone. * * @param boolean $colon Place a colon between hours and minutes? * * @return string Timezone offset as a string in the format +HH:MM. */ function tzOffset($colon = true) { $secs = $this->format('Z'); if ($secs < 0) { $sign = '-'; $secs = -$secs; } else { $sign = '+'; } $colon = $colon ? ':' : ''; $mins = intval(($secs + 30) / 60); return sprintf('%s%02d%s%02d', $sign, $mins / 60, $colon, $mins % 60); } /** * Return the unix timestamp representation of this date. * * @return integer A unix timestamp. */ function timestamp() { if (class_exists('DateTime')) { return $this->format('U'); } else { return Horde_Date::_mktime($this->hour, $this->min, $this->sec, $this->month, $this->mday, $this->year); } } /** * Return the unix timestamp representation of this date, 12:00am. * * @return integer A unix timestamp. */ function datestamp() { if (class_exists('DateTime')) { $this->_setTimeZone(); $dt = new DateTime(); $dt->setDate($this->year, $this->month, $this->mday); $dt->setTime(0, 0, 0); return $dt->format('U'); } else { return Horde_Date::_mktime(0, 0, 0, $this->month, $this->mday, $this->year); } } /** * Format time using the specifiers available in date() or in the DateTime * class' format() method. * * @since Horde 3.3 * * @param string $format * * @return string Formatted time. */ function format($format) { if (class_exists('DateTime')) { $this->_setTimeZone(); $dt = new DateTime(); $dt->setDate($this->year, $this->month, $this->mday); $dt->setTime($this->hour, $this->min, $this->sec); return $dt->format($format); } else { return date($format, $this->timestamp()); } } /** * Format time in ISO-8601 format. Works correctly since Horde 3.2. * * @return string Date and time in ISO-8601 format. */ function iso8601DateTime() { return $this->rfc3339DateTime() . $this->tzOffset(); } /** * Format time in RFC 2822 format. * * @return string Date and time in RFC 2822 format. */ function rfc2822DateTime() { return $this->format('D, j M Y H:i:s') . ' ' . $this->tzOffset(false); } /** * Format time in RFC 3339 format. * * @since Horde 3.1 * * @return string Date and time in RFC 3339 format. The seconds part has * been added with Horde 3.2. */ function rfc3339DateTime() { return $this->format('Y-m-d\TH:i:s'); } /** * Format time in the format we use for SQL queries. * * @since Horde 3.3.6 * * @return string Date and time in SQL format. */ function sqlDateTime() { return $this->format('Y-m-d H:i:s'); } /** * Format time to standard 'ctime' format. * * @return string Date and time. */ function cTime() { return $this->format('D M j H:i:s Y'); } /** * Format date and time using strftime() format. * * @since Horde 3.1 * * @return string strftime() formatted date and time. */ function strftime($format) { if (preg_match('/%[^' . $this->_supportedSpecs . ']/', $format)) { return strftime($format, $this->timestamp()); } else { return $this->_strftime($format); } } /** * Format date and time using a limited set of the strftime() format. * * @return string strftime() formatted date and time. */ function _strftime($format) { if (preg_match('/%[bBpxX]/', $format)) { require_once 'Horde/NLS.php'; } return preg_replace( array('/%b/e', '/%B/e', '/%C/e', '/%d/e', '/%D/e', '/%e/e', '/%H/e', '/%I/e', '/%m/e', '/%M/e', '/%n/', '/%p/e', '/%R/e', '/%S/e', '/%t/', '/%T/e', '/%x/e', '/%X/e', '/%y/e', '/%Y/', '/%%/'), array('$this->_strftime(NLS::getLangInfo(constant(\'ABMON_\' . (int)$this->month)))', '$this->_strftime(NLS::getLangInfo(constant(\'MON_\' . (int)$this->month)))', '(int)($this->year / 100)', 'sprintf(\'%02d\', $this->mday)', '$this->_strftime(\'%m/%d/%y\')', 'sprintf(\'%2d\', $this->mday)', 'sprintf(\'%02d\', $this->hour)', 'sprintf(\'%02d\', $this->hour == 0 ? 12 : ($this->hour > 12 ? $this->hour - 12 : $this->hour))', 'sprintf(\'%02d\', $this->month)', 'sprintf(\'%02d\', $this->min)', "\n", '$this->_strftime(NLS::getLangInfo($this->hour < 12 ? AM_STR : PM_STR))', '$this->_strftime(\'%H:%M\')', 'sprintf(\'%02d\', $this->sec)', "\t", '$this->_strftime(\'%H:%M:%S\')', '$this->_strftime(NLS::getLangInfo(D_FMT))', '$this->_strftime(NLS::getLangInfo(T_FMT))', 'substr(sprintf(\'%04d\', $this->year), -2)', (int)$this->year, '%'), $format); } /** * mktime() implementation that supports dates outside of 1970-2038, * from http://phplens.com/phpeverywhere/adodb_date_library. * * @TODO remove in Horde 4 * * This does NOT work with pre-1970 daylight saving times. * * @static */ function _mktime($hr, $min, $sec, $mon = false, $day = false, $year = false, $is_dst = false, $is_gmt = false) { if ($mon === false) { return $is_gmt ? @gmmktime($hr, $min, $sec) : @mktime($hr, $min, $sec); } if ($year > 1901 && $year < 2038 && ($year >= 1970 || version_compare(PHP_VERSION, '5.0.0', '>='))) { return $is_gmt ? @gmmktime($hr, $min, $sec, $mon, $day, $year) : @mktime($hr, $min, $sec, $mon, $day, $year); } $gmt_different = $is_gmt ? 0 : (mktime(0, 0, 0, 1, 2, 1970, 0) - gmmktime(0, 0, 0, 1, 2, 1970, 0)); $mon = intval($mon); $day = intval($day); $year = intval($year); if ($mon > 12) { $y = floor($mon / 12); $year += $y; $mon -= $y * 12; } elseif ($mon < 1) { $y = ceil((1 - $mon) / 12); $year -= $y; $mon += $y * 12; } $_day_power = 86400; $_hour_power = 3600; $_min_power = 60; $_month_table_normal = array('', 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); $_month_table_leaf = array('', 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); $_total_date = 0; if ($year >= 1970) { for ($a = 1970; $a <= $year; $a++) { $leaf = Horde_Date::isLeapYear($a); if ($leaf == true) { $loop_table = $_month_table_leaf; $_add_date = 366; } else { $loop_table = $_month_table_normal; $_add_date = 365; } if ($a < $year) { $_total_date += $_add_date; } else { for ($b = 1; $b < $mon; $b++) { $_total_date += $loop_table[$b]; } } } return ($_total_date + $day - 1) * $_day_power + $hr * $_hour_power + $min * $_min_power + $sec + $gmt_different; } for ($a = 1969 ; $a >= $year; $a--) { $leaf = Horde_Date::isLeapYear($a); if ($leaf == true) { $loop_table = $_month_table_leaf; $_add_date = 366; } else { $loop_table = $_month_table_normal; $_add_date = 365; } if ($a > $year) { $_total_date += $_add_date; } else { for ($b = 12; $b > $mon; $b--) { $_total_date += $loop_table[$b]; } } } $_total_date += $loop_table[$mon] - $day; $_day_time = $hr * $_hour_power + $min * $_min_power + $sec; $_day_time = $_day_power - $_day_time; $ret = -($_total_date * $_day_power + $_day_time - $gmt_different); if ($ret < -12220185600) { // If earlier than 5 Oct 1582 - gregorian correction. return $ret + 10 * 86400; } elseif ($ret < -12219321600) { // If in limbo, reset to 15 Oct 1582. return -12219321600; } else { return $ret; } } /** * Sets the current timezone, if available. */ function _setTimeZone() { $tz = getenv('TZ'); if (!empty($tz)) { date_default_timezone_set($tz); } } } * @author Michael J. Rubinsky * @since Horde 3.2 * @package Horde_DOM */ /** PEAR */ require_once 'PEAR.php'; /** Validate against the DTD */ define('HORDE_DOM_LOAD_VALIDATE', 1); /** Recover from load errors */ define('HORDE_DOM_LOAD_RECOVER', 2); /** Remove redundant whitespace */ define('HORDE_DOM_LOAD_REMOVE_BLANKS', 4); /** Substitute XML entities */ define('HORDE_DOM_LOAD_SUBSTITUTE_ENTITIES', 8); class Horde_DOM_Document extends Horde_DOM_Node { /** * Creates an appropriate object based on the version of PHP that is * running and the requested xml source. This function should be passed an * array containing either 'filename' => $filename | 'xml' => $xmlstring * depending on the source of the XML document. * * You can pass an optional 'options' parameter to enable extra * features like DTD validation or removal of whitespaces. * For a list of available features see the HORDE_DOM_LOAD defines. * Multiple options are added by bitwise OR. * * @param array $params Array containing either 'filename' | 'xml' keys. * You can specify an optional 'options' key. * * @return mixed PHP 4 domxml document | Horde_DOM_Document | PEAR_Error */ function factory($params = array()) { if (!isset($params['options'])) { $params['options'] = 0; } if (version_compare(PHP_VERSION, '5', '>=')) { // PHP 5 with Horde_DOM. Let Horde_DOM determine // if we are a file or string. $doc = new Horde_DOM_Document($params); if (isset($doc->error)) { return $doc->error; } return $doc; } // Load mode if ($params['options'] & HORDE_DOM_LOAD_VALIDATE) { $options = DOMXML_LOAD_VALIDATING; } elseif ($params['options'] & HORDE_DOM_LOAD_RECOVER) { $options = DOMXML_LOAD_RECOVERING; } else { $options = DOMXML_LOAD_PARSING; } // Load options if ($params['options'] & HORDE_DOM_LOAD_REMOVE_BLANKS) { $options |= DOMXML_LOAD_DONT_KEEP_BLANKS; } if ($params['options'] & HORDE_DOM_LOAD_SUBSTITUTE_ENTITIES) { $options |= DOMXML_LOAD_SUBSTITUTE_ENTITIES; } if (isset($params['filename'])) { if (function_exists('domxml_open_file')) { // PHP 4 with domxml and XML file return domxml_open_file($params['filename'], $options); } } elseif (isset($params['xml'])) { if (function_exists('domxml_open_mem')) { // PHP 4 with domxml and XML string. $result = @domxml_open_mem($params['xml'], $options); if (!$result) { return PEAR::raiseError('Failed loading XML document.'); } return $result; } } elseif (function_exists('domxml_new_doc')) { // PHP 4 creating a blank doc. return domxml_new_doc('1.0'); } // No DOM support - raise error. return PEAR::raiseError('DOM support not present.'); } /** * Constructor. Determine if we are trying to load a file or xml string * based on the parameters. * * @param array $params Array with key 'filename' | 'xml' */ function Horde_DOM_Document($params = array()) { $this->node = new DOMDocument(); // Load mode if ($params['options'] & HORDE_DOM_LOAD_VALIDATE) { $this->node->validateOnParse = true; } elseif ($params['options'] & HORDE_DOM_LOAD_RECOVER) { $this->node->recover = true; } // Load options if ($params['options'] & HORDE_DOM_LOAD_REMOVE_BLANKS) { $this->node->preserveWhiteSpace = false; } if ($params['options'] & HORDE_DOM_LOAD_SUBSTITUTE_ENTITIES) { $this->node->substituteEntities = true; } if (isset($params['xml'])) { $result = @$this->node->loadXML($params['xml']); if (!$result) { $this->error = PEAR::raiseError('Failed loading XML document.'); return; } } elseif (isset($params['filename'])) { $this->node->load($params['filename']); } $this->document = $this; } /** * Return the root document element. */ function root() { return new Horde_DOM_Element($this->node->documentElement, $this); } /** * Return the document element. */ function document_element() { return $this->root(); } /** * Return the node represented by the requested tagname. * * @param string $name The tagname requested. * * @return array The nodes matching the tag name */ function get_elements_by_tagname($name) { $list = $this->node->getElementsByTagName($name); $nodes = array(); $i = 0; if (isset($list)) { while ($node = $list->item($i)) { $nodes[] = $this->_newDOMElement($node, $this); ++$i; } return $nodes; } } /** * Return the document as a text string. * * @param bool $format Specifies whether the output should be * neatly formatted, or not * @param string $encoding Sets the encoding attribute in line * * * @return string The xml document as a string */ function dump_mem($format = false, $encoding = false) { $format0 = $this->node->formatOutput; $this->node->formatOutput = $format; $encoding0 = $this->node->encoding; if ($encoding) { $this->node->encoding=$encoding; } $dump = $this->node->saveXML(); $this->node->formatOutput = $format0; // UTF-8 is the default encoding for XML. if ($encoding) { $this->node->encoding = $encoding0 == '' ? 'UTF-8' : $encoding0; } return $dump; } /** * Create a new element for this document * * @param string $name Name of the new element * * @return Horde_DOM_Element New element */ function create_element($name) { return new Horde_DOM_Element($this->node->createElement($name), $this); } /** * Create a new text node for this document * * @param string $content The content of the text element * * @return Horde_DOM_Node New node */ function create_text_node($content) { return new Horde_DOM_Text($this->node->createTextNode($content), $this); } /** * Create a new attribute for this document * * @param string $name The name of the attribute * @param string $value The value of the attribute * * @return Horde_DOM_Attribute New attribute */ function create_attribute($name, $value) { $attr = $this->node->createAttribute($name); $attr->value = $value; return new Horde_DOM_Attribute($attr, $this); } function xpath_new_context() { return Horde_DOM_XPath::factory($this->node); } } /** * @package Horde_DOM */ class Horde_DOM_Node { var $node; var $document; /** * Wrap a DOMNode into the Horde_DOM_Node class. * * @param DOMNode $node The DOMXML node * @param Horde_DOM_Document $document The parent document * * @return Horde_DOM_Node The wrapped node */ function Horde_DOM_Node($domNode, $domDocument = null) { $this->node = $domNode; $this->document = $domDocument; } function __get($name) { switch ($name) { case 'type': return $this->node->nodeType; case 'tagname': return $this->node->tagName; case 'content': return $this->node->textContent; default: return false; } } /** * Set the content of this node. * * @param string $text The new content of this node. * * @return DOMNode The modified node. */ function set_content($text) { return $this->node->appendChild($this->node->ownerDocument->createTextNode($text)); } /** * Return the type of this node. * * @return integer Type of this node. */ function node_type() { return $this->node->nodeType; } function child_nodes() { $nodes = array(); $nodeList = $this->node->childNodes; if (isset($nodeList)) { $i = 0; while ($node = $nodeList->item($i)) { $nodes[] = $this->_newDOMElement($node, $this->document); ++$i; } } return $nodes; } /** * Return the attributes of this node. * * @return array Attributes of this node. */ function attributes() { $attributes = array(); $attrList = $this->node->attributes; if (isset($attrList)) { $i = 0; while ($attribute = $attrList->item($i)) { $attributes[] = new Horde_DOM_Attribute($attribute, $this->document); ++$i; } } return $attributes; } function first_child() { return $this->_newDOMElement($this->node->firstChild, $this->document); } /** * Return the content of this node. * * @return string Text content of this node. */ function get_content() { return $this->node->textContent; } function has_child_nodes() { return $this->node->hasChildNodes(); } function next_sibling() { if ($this->node->nextSibling === null) { return null; } return $this->_newDOMElement($this->node->nextSibling, $this->document); } function node_value() { return $this->node->nodeValue; } function node_name() { if ($this->node->nodeType == XML_ELEMENT_NODE) { return $this->node->localName; } else { return $this->node->nodeName; } } function clone_node() { return new Horde_DOM_Node($this->node->cloneNode()); } /** * Append a new node * * @param Horde_DOM_Node $newnode The child to be added * * @return Horde_DOM_Node The resulting node */ function append_child($newnode) { return $this->_newDOMElement($this->node->appendChild($this->_importNode($newnode)), $this->document); } /** * Remove a child node * * @param Horde_DOM_Node $oldchild The child to be removed * * @return Horde_DOM_Node The resulting node */ function remove_child($oldchild) { return $this->_newDOMElement($this->node->removeChild($oldchild->node), $this->document); } /** * Return a node of this class type. * * @param DOMNode $node The DOMXML node * @param Horde_DOM_Document $document The parent document * * @return Horde_DOM_Node The wrapped node */ function _newDOMElement($node, $document) { if ($node == null) { return null; } switch ($node->nodeType) { case XML_ELEMENT_NODE: return new Horde_DOM_Element($node, $document); case XML_TEXT_NODE: return new Horde_DOM_Text($node, $document); case XML_ATTRIBUTE_NODE: return new Horde_DOM_Attribute($node, $document); // case XML_PI_NODE: // return new Horde_DOM_ProcessingInstruction($node, $document); default: return new Horde_DOM_Node($node, $document); } } /** * Private function to import DOMNode from another DOMDocument. * * @param Horde_DOM_Node $newnode The node to be imported * * @return Horde_DOM_Node The wrapped node */ function _importNode($newnode) { if ($this->document === $newnode->document) { return $newnode->node; } else { return $this->document->node->importNode($newnode->node, true); } } } /** * @package Horde_DOM */ class Horde_DOM_Element extends Horde_DOM_Node { /** * Get the value of specified attribute. * * @param string $name Name of the attribute * * @return string Indicates if the attribute was set. */ function get_attribute($name) { return $this->node->getAttribute($name); } /** * Determine if an attribute of this name is present on the node. * * @param string $name Name of the attribute * * @return bool Indicates if such an attribute is present. */ function has_attribute($name) { return $this->node->hasAttribute($name); } /** * Set the specified attribute on this node. * * @param string $name Name of the attribute * @param string $value The new value of this attribute. * * @return mixed Indicates if the attribute was set. */ function set_attribute($name, $value) { return $this->node->setAttribute($name, $value); } /** * Return the node represented by the requested tagname. * * @param string $name The tagname requested. * * @return array The nodes matching the tag name */ function get_elements_by_tagname($name) { $list = $this->node->getElementsByTagName($name); $nodes = array(); $i = 0; if (isset($list)) { while ($node = $list->item($i)) { $nodes[] = $this->_newDOMElement($node, $this->document); $i++; } } return $nodes; } } /** * @package Horde_DOM */ class Horde_DOM_Attribute extends Horde_DOM_Node { /** * Return a node of this class type. * * @param DOMNode $node The DOMXML node * @param Horde_DOM_Document $document The parent document * * @return Horde_DOM_Attribute The wrapped attribute */ function _newDOMElement($node, $document) { return new Horde_DOM_Attribute($node, $document); } } /** * @package Horde_DOM */ class Horde_DOM_Text extends Horde_DOM_Node { function __get($name) { if ($name == 'tagname') { return '#text'; } else { return parent::__get($name); } } function tagname() { return '#text'; } /** * Set the content of this node. * * @param string $text The new content of this node. */ function set_content($text) { $this->node->nodeValue = $text; } } /** * @package Horde_DOM */ class Horde_DOM_XPath { /** * @var DOMXPath */ var $_xpath; function factory($dom) { if (version_compare(PHP_VERSION, '5', '>=')) { // PHP 5 with Horde_DOM. return new Horde_DOM_XPath($dom); } return $dom->xpath_new_context(); } function Horde_DOM_XPath($dom) { $this->_xpath = new DOMXPath($dom); } function xpath_register_ns($prefix, $uri) { $this->_xpath->registerNamespace($prefix, $uri); } function xpath_eval($expression, $context = null) { if (is_null($context)) { $nodeset = $this->_xpath->query($expression); } else { $nodeset = $this->_xpath->query($expression, $context); } $result = new stdClass(); $result->nodeset = array(); for ($i = 0; $i < $nodeset->length; $i++) { $result->nodeset[] = new Horde_DOM_Element($nodeset->item($i)); } return $result; } } * @author Jan Schneider * @since Horde 3.2 * @package Horde_Editor */ class Horde_Editor_fckeditor extends Horde_Editor { /** * Constructor. * * @param array $params The following configuration parameters: *
     * 'id' - The ID of the text area to turn into an editor.
     * 'no_notify' - Don't output JS code via notification library. Code will
     *               be stored for access via getJS().
     * 
*/ function Horde_Editor_fckeditor($params = array()) { $fck_path = $GLOBALS['registry']->get('webroot', 'horde') . '/services/editor/fckeditor/'; $js = "var oFCKeditor = new FCKeditor('" . $params['id'] . "'); oFCKeditor.BasePath = '" . $fck_path . "';"; if (!empty($params['no_notify'])) { $this->_js = ''; } else { Horde::addScriptFile('prototype.js', 'horde', true); $GLOBALS['notification']->push('Event.observe(window, \'load\', function() {' . $js . ' oFCKeditor.ReplaceTextarea();});', 'javascript'); $GLOBALS['notification']->push($fck_path . 'fckeditor.js', 'javascript-file'); } } } * * 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 Nuno Loureiro * @author Jan Schneider * @author Ryan Miller * @since Horde 3.2 * @package Horde_Editor */ class Horde_Editor_tinymce extends Horde_Editor { /** * Constructor. * * @param array $params The following configuration parameters: *
     * 'buttons' - The list of buttons to show. An array of strings - each
     *             string should contain the list of buttons to displayed on
     *             that row.
     * 'config' - Config params to pass to tinymce.
     * 'id' - The ID of the text area to turn into an editor.
     * 'no_autoload' - Don't load tinymce by default on pageload.
     * 'no_notify' - Don't output JS code via notification library. Code will
     *               be stored for access via getJS().
     * 
*/ function Horde_Editor_tinymce($params = array()) { $params['config'] = isset($params['config']) ? $params['config'] : array(); if (!empty($params['buttons'])) { $params['config']['theme'] = 'advanced'; for ($i = 0, $ilength = count($params['buttons']); $i <= $ilength; ++$i) { $params['config']['theme_advanced_buttons' . ($i + 1)] = ($i == $ilength) ? '' : $params['buttons'][$i]; } } if (empty($params['no_autoload'])) { $params['config'] = array_merge($params['config'], array('elements' => $params['id'], 'mode' => 'exact')); } else { $params['config'] = array_merge($params['config'], array('mode' => 'none')); } $p = array(); foreach ($params['config'] as $config => $value) { if (is_bool($value)) { $value = ($value) ? 'true' : 'false'; } else { $value = '\'' . addslashes($value) . '\''; } $p[] = $config . ':' . $value . ''; } $js = 'tinyMCE.init({' . implode(',', $p) . '});'; $mce_path = '/services/editor/tinymce/tiny_mce.js'; if (!empty($params['no_notify'])) { $mce_path = $GLOBALS['registry']->get('webroot', 'horde') . $mce_path; $this->_js = ''; } else { Horde::addScriptFile($mce_path, 'horde', true); $GLOBALS['notification']->push($js, 'javascript'); } } } * @author Jan Schneider * @author Roel Gloudemans * @since Horde 3.2 * @package Horde_Editor */ class Horde_Editor_xinha extends Horde_Editor { /** * Constructor. * * @param array $params The following configuration parameters: *
     * 'config' - An array of additional config items. Values will be quoted
     *            unless they begin with the string '@raw@' in which case
     *            they will be output as-is.
     * 'hidebuttons' - A list of buttons to hide.
     * 'id' - The ID of the text area to turn into an editor.
     * 'lang' - The language to use. (Default: en)
     * 'loadnotify' - Display notification graphic when loading (Default: no)
     * 'no_autoload' - Don't load xinha by default on pageload.
     * 'no_notify' - Don't output JS code via notification library. Code will
     *               be stored for access via getJS().
     * 'noplugins' - A list of plugins to specifically never load.
     * 'plugins' - Any plugins to load in addition to the plugins_stored in
     *             'editor_plugins'.
     * 'relativelinks' - TODO
     * 'textarea' - Turn all textareas on page into editors? (Default: no)
     * 
*/ function Horde_Editor_xinha($params = array()) { $language = 'en'; if (!empty($params['lang'])) { $language = $params['lang']; } elseif (isset($GLOBALS['language'])) { $language = explode('_', $GLOBALS['language']); if (count($language) > 1) { $country = String::lower($language[1]); if ($country == $language[0]) { $language = $language[0]; } else { $language = $language[0] . '_' . $country; } } else { $language = $language[0]; } } if (($language != 'pt_br') && ($pos = strpos($language, '_'))) { $language = substr($language, 0, $pos); } $xinha_path = $GLOBALS['registry']->get('webroot', 'horde') . '/services/editor/xinha/'; $js = 'var _editor_url = \'' . $xinha_path . '\',' . '_editor_lang = \'' . $language . '\',' . '_editors,' . 'xinha_init = function() { '; // Loading plugins. $plugins = @unserialize($GLOBALS['prefs']->getValue('editor_plugins')); if (!$plugins) { $plugins = array(); } $key = array_search('AnselImage', $plugins); if (($key !== false) && !$GLOBALS['registry']->hasMethod('images/listGalleries')) { unset($plugins[$key]); } if (!empty($params['plugins'])) { $plugins += $params['plugins']; } if (!empty($params['noplugins'])) { $plugins = array_diff($plugins, $params['noplugins']); } $js .= 'var xinha_plugins = '; if (empty($plugins)) { $js .= '[];'; } else { $js .= '[\'' . implode('\',\'', array_unique($plugins)) . '\'];'; } $js .= 'if (!Xinha.loadPlugins(xinha_plugins, xinha_init)) return; '; $js .= 'var xinha_editors = '; if (!empty($params['id'])) { $js .= '[\'' . $params['id'] . '\'];'; } elseif (!empty($params['textarea'])) { $js .= 'document.getElementsByTagName(\'TEXTAREA\');'; } else { $js .= '[];'; } $js .= 'var xinha_config = new Xinha.Config();' . 'xinha_config.debug = false;'; if (!empty($params['hidebuttons'])) { $js .= 'xinha_config.hideSomeButtons(\' ' . implode(' ', $params['hidebuttons']) . ' \');'; } if (!empty($params['loadnotify'])) { $params['config']['showLoading'] = true; } if (!empty($params['relativelinks'])) { $myserver = Horde::url('', true); $params['config'] = array_merge($params['config'], array('stripBaseHref' => true, 'stripSelfNamedAnchors' => true, 'baseHref' => substr($myserver, 0, strpos($myserver, '/', 8)))); } if (!empty($params['config'])) { foreach ($params['config'] as $config => $value) { $js .= 'xinha_config.' . $config . ' = '; if (is_bool($value)) { $js .= ($value) ? 'true;' : 'false;'; } elseif (strpos($value, '@raw@') === 0) { $js .= substr($value, 5) . ';'; } else { $js .= '\'' . addslashes($value) . '\';'; } } } $js .= '_editors = Xinha.makeEditors(xinha_editors, xinha_config, xinha_plugins);' . 'Xinha.startEditors(_editors); };'; if (empty($params['no_autoload'])) { Horde::addScriptFile('prototype.js', 'horde', true); $js .= 'Event.observe(window, \'load\', xinha_init);'; } if (!empty($params['no_notify'])) { $this->_js = ''; } else { $GLOBALS['notification']->push($js, 'javascript'); $GLOBALS['notification']->push($xinha_path . 'XinhaCore.js', 'javascript-file'); } } } * @since Horde 3.0 * @package Horde_Editor */ class Horde_Editor { /** * Javascript code to init the editor. * * @var string */ var $_js = ''; /** * Attempts to return a concrete Horde_Editor instance based on $driver. * * @param mixed $driver The type of concrete Horde_Editor subclass to * return. If $driver is an array, then we will look * in $driver[0]/lib/Driver/ 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_Editor The newly created concrete Horde_Editor instance, * or false 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')) { $editor = new Horde_Editor($params); return $editor; } /* Transparent backwards compatible upgrade to Xinha, * htmlarea's replacement. */ if ($driver == 'htmlarea') { $driver = 'xinha'; } $class = 'Horde_Editor_' . $driver; if (!class_exists($class)) { if (!empty($app)) { include_once $GLOBALS['registry']->get('fileroot', $app) . '/lib/Editor/' . $driver . '.php'; } else { include_once 'Horde/Editor/' . $driver . '.php'; } } if (class_exists($class)) { if (is_null($params) && class_exists('Horde')) { $params = Horde::getDriverConfig('editor', $driver); } $editor = new $class($params); } else { $editor = PEAR::raiseError('Class definition of ' . $class . ' not found.'); } return $editor; } /** * Attempts to return a reference to a concrete Horde_Editor * instance based on $driver. It will only create a new instance * if no Horde_Editor instance with the same parameters currently * exists. * * This should be used if multiple cache backends (and, thus, * multiple Horde_Editor instances) are required. * * This method must be invoked as: * $var = &Horde_Editor::singleton() * * @param mixed $driver The type of concrete Horde_Editor subclass to * return. If $driver is an array, then we will look * in $driver[0]/lib/Editor/ 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_Editor The concrete Horde_Editor reference, or false on * error. */ function &singleton($driver, $params = null) { static $instances = array(); if (is_null($params) && class_exists('Horde')) { $params = Horde::getDriverConfig('cache', $driver); } $signature = serialize(array($driver, $params)); if (!array_key_exists($signature, $instances)) { $instances[$signature] = &Horde_Editor::factory($driver, $params); } return $instances[$signature]; } /** * Returns the JS code needed to instantiate the editor. * * @since Horde 3.2 * * @return string Javascript code. */ function getJS() { return $this->_js; } /** * List the available editors. * Can be called statically: Horde_Editor::availableEditors(); * * @since Horde 3.2 * * @return array List of available editors. */ function availableEditors() { $eds = array(); $d = dir(dirname(__FILE__) . '/Editor'); while (false !== ($entry = $d->read())) { if (preg_match('/\.php$/', $entry)) { $eds[] = basename($entry, '.php'); } } return $eds; } } * elements). * * @var string */ protected $_entryElementName = 'entry'; /** * The default namespace for Atom feeds. * * @var string */ protected $_defaultNamespace = 'atom'; /** * The XML string for an "empty" Atom feed. * * @var string */ protected $_emptyXml = ''; /** * Set up the $_entries alias. */ public function __wakeup() { parent::__wakeup(); // Cache the individual feed elements so they don't need to be // searched for on every operation. $this->_entries = array(); foreach ($this->_element->childNodes as $child) { if ($child->localName == $this->_entryElementName) { $this->_entries[] = $child; } } } /** * Easy access to tags keyed by "rel" attributes. * * If $elt->link() is called with no arguments, we will attempt to return * the value of the tag(s) like all other method-syntax attribute * access. If an argument is passed to link(), however, then we will return * the "href" value of the first tag that has a "rel" attribute * matching $rel: * * $elt->link(): returns the value of the link tag. * $elt->link('self'): returns the href from the first in the entry. * * @param string $rel The "rel" attribute to look for. * @return mixed */ public function link($rel = null) { if ($rel === null) { return parent::__call('link', null); } // Index link tags by their "rel" attribute. $links = parent::__get('link'); if (!is_array($links)) { if ($links instanceof Horde_Xml_Element) { $links = array($links); } else { return $links; } } foreach ($links as $link) { if (empty($link['rel'])) { continue; } if ($rel == $link['rel']) { return $link['href']; } } return null; } /** * Make accessing some individual elements of the feed easier. * * Special accessors 'entry' and 'entries' are provided so that if you wish * to iterate over an Atom feed's entries, you can do so using foreach * ($feed->entries as $entry) or foreach ($feed->entry as $entry). * * @param string $var The property to access. * @return mixed */ public function __get($var) { switch ($var) { case 'entry': case 'entries': return $this; default: return parent::__get($var); } } } _uri = $uri; try { parent::__construct($xml); } catch (Horde_Xml_Element_Exception $e) { throw new Horde_Feed_Exception('Unable to load feed: ' . $e->getMessage()); } } /** * Handle null or array values for $this->_element by initializing * with $this->_emptyXml, and importing the array with * Horde_Xml_Element::fromArray() if necessary. * * @see Horde_Xml_Element::__wakeup * @see Horde_Xml_Element::fromArray */ public function __wakeup() { // If we've been passed an array, we'll store it for importing // after initializing with the default "empty" feed XML. $importArray = null; if (is_null($this->_element)) { $this->_element = $this->_emptyXml; } elseif (is_array($this->_element)) { $importArray = $this->_element; $this->_element = $this->_emptyXml; } parent::__wakeup(); if (!is_null($importArray)) { $this->fromArray($importArray); } } /** * Get the number of entries in this feed object. * * @return integer Entry count. */ public function count() { return count($this->_entries); } /** * Required by the Iterator interface. * * @internal */ public function rewind() { $this->_entryIndex = 0; } /** * Required by the Iterator interface. * * @internal * * @return mixed The current row, or null if no rows. */ public function current() { return new $this->_entryClassName( $this->_entries[$this->_entryIndex]); } /** * Required by the Iterator interface. * * @internal * * @return mixed The current row number (starts at 0), or null if no rows */ public function key() { return $this->_entryIndex; } /** * Required by the Iterator interface. * * @internal * * @return mixed The next row, or null if no more rows. */ public function next() { ++$this->_entryIndex; } /** * Required by the Iterator interface. * * @internal * * @return boolean Whether the iteration is valid */ public function valid() { return (0 <= $this->_entryIndex && $this->_entryIndex < $this->count()); } } * @license http://opensource.org/licenses/bsd-license.php BSD * @category Horde * @package Horde_Feed */ /** * Blogroll feed list class * * This is not a generic OPML implementation, but one focused on lists of feeds, * i.e. blogrolls. See http://en.wikipedia.org/wiki/OPML for more information on * OPML. * * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD * @category Horde * @package Horde_Feed */ class Horde_Feed_Blogroll extends Horde_Feed_Base { /** * The classname for individual feed elements. * @var string */ protected $_entryClassName = 'Horde_Feed_Entry_Blogroll'; /** * The element name for individual feed elements (Atom elements). * @var string */ protected $_entryElementName = 'outline'; /** * The default namespace for blogrolls. * @var string */ protected $_defaultNamespace = ''; /** * The XML string for an "empty" Blogroll. * @var string */ protected $_emptyXml = ''; /** * Set up the $_entries alias. */ public function __wakeup() { parent::__wakeup(); // Cache the individual outline elements so they don't need to be // searched for on every operation. $this->_entries = array(); foreach ($this->_element->getElementsByTagName($this->_entryElementName) as $child) { if ($child->attributes->getNamedItem('xmlUrl')) { $this->_entries[] = $child; } } } /** * Make accessing some individual elements of the feed easier. * * @param string $var The property to access. * @return mixed */ public function __get($var) { switch ($var) { case 'body': case 'outline': return $this; case 'title': return $this->head->title; default: return parent::__get($var); } } } '; /** * Name of the XML element for Atom entries. Subclasses can * override this to something other than "entry" if necessary. * * @var string */ protected $_entryElementName = 'entry'; /** * Delete an atom entry. * * Delete tries to delete this entry from its feed. If the entry * does not contain a link rel="edit", we throw an error (either * the entry does not yet exist or this is not an editable * feed). If we have a link rel="edit", we do the empty-body * HTTP DELETE to that URI and check for a response of 2xx. * Usually the response would be 204 No Content, but the Atom * Publishing Protocol permits it to be 200 OK. * * @throws Horde_Feed_Exception If an error occurs, an Horde_Feed_Exception will * be thrown. */ public function delete() { // Look for link rel="edit" in the entry object. $deleteUri = $this->link('edit'); if (!$deleteUri) { throw new Horde_Feed_Exception('Cannot delete entry; no link rel="edit" is present.'); } // DELETE $client = Horde_Feed::getHttpClient(); $client->uri = $deleteUri; $response = $client->delete(); if (!($response->code >= 200 && $response->code <= 299)) { throw new Horde_Feed_Exception('Expected response code 2xx, got ' . $response->code); } return true; } /** * Save a new or updated Atom entry. * * Save is used to either create new entries or to save changes to * existing ones. If we have a link rel="edit", we are changing * an existing entry. In this case we re-serialize the entry and * PUT it to the edit URI, checking for a 200 OK result. * * For posting new entries, you must specify the $postUri * parameter to save() to tell the object where to post itself. * We use $postUri and POST the serialized entry there, checking * for a 201 Created response. If the insert is successful, we * then parse the response from the POST to get any values that * the server has generated: an id, an updated time, and its new * link rel="edit". * * @param string $postUri Location to POST for creating new * entries. * * @throws Horde_Feed_Exception If an error occurs, a Horde_Feed_Exception will * be thrown. */ public function save($postUri = null) { if ($this->id()) { // If id is set, look for link rel="edit" in the // entry object and PUT. $editUri = $this->link('edit'); if (!$editUri) { throw new Horde_Feed_Exception('Cannot edit entry; no link rel="edit" is present.'); } $client = Horde_Feed::getHttpClient(); $client->uri = $editUri; $client->setHeaders('Content-Type', 'application/atom+xml'); $response = $client->put($this->saveXml()); if ($response->code !== 200) { throw new Horde_Feed_Exception('Expected response code 200, got ' . $response->code); } } else { if ($postUri === null) { throw new Horde_Feed_Exception('PostURI must be specified to save new entries.'); } $client = Horde_Feed::getHttpClient(); $client->uri = $postUri; $client->setHeaders('Content-Type', 'application/atom+xml'); $response = $client->post($this->saveXml()); if ($response->code !== 201) { throw new Horde_Feed_Exception('Expected response code 201, got ' . $response->code); } } // Update internal properties using the response body. $body = $response->getBody(); $newEntry = @DOMDocument::loadXML($body); if (!$newEntry) { throw new Horde_Feed_Exception('XML cannot be parsed: ', error_get_last()); } $newEntry = $newEntry->getElementsByTagName($this->_entryElementName)->item(0); if (!$newEntry) { throw new Horde_Feed_Exception('No root element found in server response:' . "\n\n" . $body); } if ($this->_element->parentNode) { $oldElement = $this->_element; $this->_element = $oldElement->ownerDocument->importNode($newEntry, true); $oldElement->parentNode->replaceChild($this->_element, $oldElement); } else { $this->_element = $newEntry; } } /** * Easy access to tags keyed by "rel" attributes. * * If $elt->link() is called with no arguments, we will attempt to * return the value of the tag(s) like all other * method-syntax attribute access. If an argument is passed to * link(), however, then we will return the "href" value of the * first tag that has a "rel" attribute matching $rel: * * $elt->link(): returns the value of the link tag. * $elt->link('self'): returns the href from the first in the entry. * * @param string $rel The "rel" attribute to look for. * @return mixed */ public function link($rel = null) { if ($rel === null) { return parent::__call('link', null); } // index link tags by their "rel" attribute. $links = parent::__get('link'); if (!is_array($links)) { if ($links instanceof Horde_Xml_Element) { $links = array($links); } else { return $links; } } foreach ($links as $link) { if (empty($link['rel'])) { continue; } if ($rel == $link['rel']) { return $link['href']; } } return null; } } _element by initializing * with $this->_emptyXml, and importing the array with * Horde_Xml_Element::fromArray() if necessary. * * @see Horde_Xml_Element::__wakeup * @see Horde_Xml_Element::fromArray */ public function __wakeup() { // If we've been passed an array, we'll store it for importing // after initializing with the default "empty" feed XML. $importArray = null; if (is_null($this->_element)) { $this->_element = $this->_emptyXml; } elseif (is_array($this->_element)) { $importArray = $this->_element; $this->_element = $this->_emptyXml; } parent::__wakeup(); if (!is_null($importArray)) { $this->fromArray($importArray); } } } * @license http://opensource.org/licenses/bsd-license.php BSD * @category Horde * @package Horde_Feed */ /** * Concrete class for working with Blogroll elements. * * @author Chuck Hagenbuch * @license http://opensource.org/licenses/bsd-license.php BSD * @category Horde * @package Horde_Feed */ class Horde_Feed_Entry_Blogroll extends Horde_Feed_Entry_Base { /** * The XML string for an "empty" outline element. * * @var string */ protected $_emptyXml = ''; /** * Name of the XML element for Atom entries. Subclasses can * override this to something other than "entry" if necessary. * * @var string */ protected $_entryElementName = 'outline'; /** * Get a Horde_Feed object for the feed described by this outline element. * * @return Horde_Feed_Base */ public function getFeed() { if (!$this['xmlUrl']) { throw new Horde_Feed_Exception('No XML URL in element'); } return Horde_Feed::readUri($this['xmlUrl']); } /** * Add child elements and attributes to this element from a simple key => * value hash. Because feed list outline elements only use attributes, this * overrides Horde_Xml_Element#fromArray to set attributes whether the * #Attribute syntax is used or not. * * @see Horde_Xml_Element#fromArray * * @param $array Hash to import into this element. */ public function fromArray($array) { foreach ($array as $key => $value) { $attribute = $key; if (substr($attribute, 0, 1) == '#') { $attribute = substr($attribute, 1); } $this[$attribute] = $value; } } /** * Make accessing some individual elements of the feed easier. * * @param string $var The property to access. * @return mixed */ public function __get($var) { return $this->offsetGet($var); } /** * Map variable sets onto the underlying entry representation. * * @param string $var The property to change. * @param string $val The property's new value. */ public function __set($var, $val) { return $this->offsetSet($var, $val); } } '; } file = $code_or_lasterror['file']; $this->line = $code_or_lasterror['line']; $code = $code_or_lasterror['type']; } else { $code = $code_or_lasterror; } parent::__construct($message, $code); } } s). * * @var string */ protected $_entryElementName = 'item'; /** * The default namespace for RSS channels. * * @var string */ protected $_defaultNamespace = 'rss'; /** * The XML string for an "empty" RSS feed. * * @var string */ protected $_emptyXml = ''; /** * Set up the $_entries alias. */ public function __wakeup() { parent::__wakeup(); // Cache the individual feed elements so they don't need to be // searched for on every operation. $this->_entries = array(); foreach ($this->_element->childNodes as $child) { if ($child->localName == $this->_entryElementName) { $this->_entries[] = $child; } } // Brute-force search for $_entryElementName if we haven't // found any so far. if (!count($this->_entries)) { foreach ($this->_element->getElementsByTagName($this->_entryElementName) as $child) { $this->_entries[] = $child; } } } /** * Make accessing some individual elements of the channel easier. * * Special accessors 'item' and 'items' are provided so that if * you wish to iterate over an RSS channel's items, you can do so * using foreach ($channel->items as $item) or foreach * ($channel->item as $item). * * @param string $var The property to access. * @return mixed */ public function __get($var) { switch ($var) { case 'item': // fall through to the next case case 'items': return $this; default: return parent::__get($var); } } } INDX( K7v(hRii:(   Atom.phphRii:(̨  Base.phppZii:(C  Blogroll.php`L:(:(:(:(Entryp\ii:( D Exception.phppZii:( D EXCEPT~1.PHP`Pii:("  Rss.php of an // Atom feed. if ($feed = $doc->getElementsByTagName('feed')->item(0)) { // Return an Atom feed. return new Horde_Feed_Atom($feed, $uri); } elseif ($entry = $doc->getElementsByTagName('entry')->item(0)) { // Return an Atom single-entry feed. $feeddoc = new DOMDocument($doc->version, $doc->actualEncoding); $feed = $feeddoc->appendChild($feeddoc->createElement('feed')); $feed->appendChild($feeddoc->importNode($entry, true)); return new Horde_Feed_Atom($feed, $uri); } // Try to find the base feed element of an RSS feed. if ($channel = $doc->getElementsByTagName('channel')->item(0)) { // Return an RSS feed. return new Horde_Feed_Rss($channel, $uri); } // Try to find an outline element of an OPML blogroll. if ($outline = $doc->getElementsByTagName('outline')->item(0)) { // Return a blogroll feed. return new Horde_Feed_Blogroll($doc->documentElement, $uri); } // $string does not appear to be a valid feed of the supported // types. throw new Horde_Feed_Exception('Invalid or unsupported feed format: ' . substr($doc->saveXML(), 0, 80) . '...'); } /** * Reads a feed represented by $string. * * @param string $string The XML content of the feed. * @param string $uri The feed's URI location, if known. * * @throws Horde_Feed_Exception * * @return Horde_Feed_Base */ public static function read($string, $uri = null) { // Load the feed as a DOMDocument object. $doc = new DOMDocument; if (!@$doc->loadXML($string)) { throw new Horde_Feed_Exception('DOMDocument cannot parse XML: ', error_get_last()); } return self::create($doc); } /** * Read a feed located at $uri * * @param string $uri The URI to fetch the feed from. * * @throws Horde_Feed_Exception * * @return Horde_Feed_Base */ public static function readUri($uri) { $client = self::getHttpClient(); try { $response = $client->GET($uri); } catch (Horde_Http_Client_Exception $e) { throw new Horde_Feed_Exception('Error reading feed: ' . $e->getMessage()); } if ($response->code != 200) { throw new Horde_Feed_Exception('Unable to read feed, got response code ' . $response->code); } $feed = $response->getBody(); return self::read($feed, $uri); } /** * Read a feed from $filename * * @param string $filename The location of the feed file on an accessible * filesystem or through an available stream wrapper. * * @throws Horde_Feed_Exception * * @return Horde_Feed_Base */ public static function readFile($filename) { $doc = new DOMDocument; if (!@$doc->load($filename)) { throw new Horde_Feed_Exception('File could not be read or parsed: ', error_get_last()); } return self::create($doc); } } * $params = array( * 'target' => '[name of element this is conditional on]', * 'enabled' => 'true' | 'false', * 'values' => array([target values to check]) * ); * * * So $params = array('foo', 'true', array(1, 2)) will enable the field this * action is attached to if the value of 'foo' is 1 or 2, and disable it * otherwise. * * $Horde: framework/Form/Form/Action/ConditionalEnable.php,v 1.3.2.3 2009-01-06 15:23:07 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 Matt Kynaston * @package Horde_Form */ class Horde_Form_Action_ConditionalEnable extends Horde_Form_Action { var $_trigger = array('onload'); function getActionScript(&$form, $renderer, $varname) { Horde::addScriptFile('form_helpers.js', 'horde', true); $form_name = $form->getName(); $target = $this->_params['target']; $enabled = $this->_params['enabled']; if (!is_string($enabled)) { $enabled = ($enabled) ? 'true' : 'false'; } $vals = $this->_params['values']; $vals = (is_array($vals)) ? $vals : array($vals); $args = "'$varname', $enabled, '" . implode("','", $vals) . "'"; return "if (addEvent(document.getElementById('$form_name').$target, 'onchange', \"checkEnabled(this, $args);\")) { " . " checkEnabled(document.getElementById('$form_name').$varname, $args); };"; } } * @package Horde_Form */ class Horde_Form_Action_ConditionalSetValue extends Horde_Form_Action { /** * Which JS events should trigger this action? * * @var array */ var $_trigger = array('onchange', 'onload'); function getActionScript($form, $renderer, $varname) { return 'map(\'' . $renderer->_genID($varname, false) . "', '" . $renderer->_genID($this->getTarget(), false) . '\');'; } function setValues(&$vars, $sourceVal, $arrayVal = false) { $map = $this->_params['map']; $target = $this->getTarget(); if ($arrayVal) { $i = 0; if (is_array($sourceVal)) { foreach ($sourceVal as $val) { if (!empty($map[$val])) { $vars->set($target, $map[$val], $i); } $i++; } } } else { if (!empty($map[$sourceVal])) { $vars->set($target, $map[$sourceVal]); } } } function printJavaScript() { $this->_printJavaScriptStart(); $map = $this->_params['map']; ?> var _map = [ 0) { echo ', '; } echo '"' . $val . '"'; $i++; }?>]; function map(sourceId, targetId) { var newval; var source = document.getElementById(sourceId); var element = document.getElementById(targetId); if (element) { if (_map[source.selectedIndex]) { newval = _map[source.selectedIndex]; replace = true; } else { newval = ''; replace = false; for (i = 0; i < _map.length; i++) { if (element.value == _map[i]) { replace = true; break; } } } if (replace) { element.value = newval; } } }_printJavaScriptEnd(); } } * $params = array( * 'target' => '[name of element this is conditional on]', * 'enabled' => 'true' | 'false', * 'values' => array([target values to check]) * ); * * * So $params = array('foo', 'true', array(1, 2)) will enable the field this * action is attached to if the value of 'foo' is 1 or 2, and disable it * otherwise. * * $Horde: framework/Form/Form/Action/conditional_enable.php,v 1.4.10.7 2009-01-06 15:23:07 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 Matt Kynaston * @package Horde_Form */ class Horde_Form_Action_conditional_enable extends Horde_Form_Action { var $_trigger = array('onload'); function getActionScript(&$form, $renderer, $varname) { Horde::addScriptFile('form_helpers.js', 'horde', true); $form_name = $form->getName(); $target = $this->_params['target']; $enabled = $this->_params['enabled']; if (!is_string($enabled)) { $enabled = ($enabled) ? 'true' : 'false'; } $vals = $this->_params['values']; $vals = (is_array($vals)) ? $vals : array($vals); $args = "'$varname', $enabled, '" . implode("','", $vals) . "'"; return "if (addEvent(document.getElementById('$form_name').$target, 'onchange', \"checkEnabled(this, $args);\")) { " . " checkEnabled(document.getElementById('$form_name').$varname, $args); };"; } } INDX( QJv(xHliiF=(oE ( ConditionalEnable.phppiiF=(oE  ConditionalSetValue.phpniiF=(6 C , conditional_enable.phpriiF=(6 C  conditional_setvalue.phppZiiF=(oE ( CONDIT~1.PHPpZiiF=(oE  CONDIT~2.PHPpZiiF=(6 C , CONDIT~3.PHPpZiiF=(6 C  CONDIT~4.PPhViiF=(mҷ ; reload.phpxbiiF=( T ( setcursorpos.phppZiiF=( T ( SETCUR~1.PHPhViiF=(S  submit.phpp^iiF=((V  sum_fields.phppZiiF=((V  SUM_FI~1.PHPp`iiF=(  updatefield.phppZiiF=(  UPDATE~1.PHP * @package Horde_Form */ class Horde_Form_Action_conditional_setvalue extends Horde_Form_Action { /** * Which JS events should trigger this action? * * @var array */ var $_trigger = array('onchange', 'onload'); function getActionScript($form, $renderer, $varname) { return 'map(\'' . $renderer->_genID($varname, false) . "', '" . $renderer->_genID($this->getTarget(), false) . '\');'; } function setValues(&$vars, $sourceVal, $arrayVal = false) { $map = $this->_params['map']; $target = $this->getTarget(); if ($arrayVal) { $i = 0; if (is_array($sourceVal)) { foreach ($sourceVal as $val) { if (!empty($map[$val])) { $vars->set($target, $map[$val], $i); } $i++; } } } else { if (!empty($map[$sourceVal])) { $vars->set($target, $map[$sourceVal]); } } } function printJavaScript() { $this->_printJavaScriptStart(); $map = $this->_params['map']; ?> var _map = [ 0) { echo ', '; } echo '"' . $val . '"'; $i++; }?>]; function map(sourceId, targetId) { var newval; var source = document.getElementById(sourceId); var element = document.getElementById(targetId); if (element) { if (_map[source.selectedIndex]) { newval = _map[source.selectedIndex]; replace = true; } else { newval = ''; replace = false; for (i = 0; i < _map.length; i++) { if (element.value == _map[i]) { replace = true; break; } } } if (replace) { element.value = newval; } } }_printJavaScriptEnd(); } } * @package Horde_Form */ class Horde_Form_Action_reload extends Horde_Form_Action { var $_trigger = array('onchange'); function getActionScript($form, $renderer, $varname) { Horde::addScriptFile('prototype.js', 'horde', true); Horde::addScriptFile('effects.js', 'horde', true); Horde::addScriptFile('redbox.js', 'horde', true); return 'if (this.value) { document.' . $form->getName() . '.formname.value=\'\'; RedBox.loading(); document.' . $form->getName() . '.submit() }'; } } * @since Horde 3.2 * @package Horde_Form */ class Horde_Form_Action_setcursorpos extends Horde_Form_Action { var $_trigger = array('onload'); function getActionScript(&$form, $renderer, $varname) { Horde::addScriptFile('form_helpers.js', 'horde', true); $pos = implode(',', $this->_params); return 'form_setCursorPosition(document.forms[\'' . htmlspecialchars($form->getName()) . '\'].elements[\'' . htmlspecialchars($varname) . '\'].id, ' . $pos . ');'; } } * @package Horde_Form */ class Horde_Form_Action_submit extends Horde_Form_Action { var $_trigger = array('onchange'); function getActionScript($form, $renderer, $varname) { Horde::addScriptFile('prototype.js', 'horde', true); Horde::addScriptFile('effects.js', 'horde', true); Horde::addScriptFile('redbox.js', 'horde', true); return 'RedBox.loading(); document.' . $form->getName() . '.submit()'; } } * @package Horde_Form */ class Horde_Form_Action_sum_fields extends Horde_Form_Action { var $_trigger = array('onload'); function getActionScript(&$form, $renderer, $varname) { Horde::addScriptFile('form_helpers.js', 'horde', true); $form_name = $form->getName(); $fields = "'" . implode("','", $this->_params) . "'"; $js = array(); $js[] = sprintf('document.forms[\'%s\'].elements[\'%s\'].disabled = true;', $form_name, $varname); foreach ($this->_params as $field) { $js[] = sprintf("addEvent(document.forms['%1\$s'].elements['%2\$s'], \"onchange\", \"sumFields(document.forms['%1\$s'], '%3\$s', %4\$s);\");", $form_name, $field, $varname, $fields); } return implode("\n", $js); } } * @package Horde_Form */ class Horde_Form_Action_updatefield extends Horde_Form_Action { var $_trigger = array('onchange', 'onload', 'onkeyup'); function getActionScript(&$form, &$renderer, $varname) { return 'updateField' . $this->id() . '();'; } function setValues(&$vars, $sourceVal, $arrayVal = false) { } function printJavaScript() { $this->_printJavaScriptStart(); $pieces = explode('%s', $this->_params['format']); $fields = $this->_params['fields']; $val_first = (substr($this->_params['format'], 0, 2) == '%s'); if ($val_first) { array_shift($pieces); } if (substr($this->_params['format'], -2) == '%s') { array_pop($pieces); } $args = array(); if ($val_first) { $args[] = "document.getElementById('" . array_shift($fields) . "').value"; } while (count($pieces)) { $args[] = "'" . array_shift($pieces) . "'"; $args[] = "document.getElementById('" . array_shift($fields) . "').value"; } ?> // Updater for getTarget() ?>. function updateFieldid() ?>() { var target = document.getElementById('getTarget() ?>'); if (target) { target.value = ().replace(/(^ +| +$)/, '').replace(/ +/g, ' '); } }_printJavaScriptEnd(); } } * @package Horde_Form */ class Horde_Form_Action { var $_id; var $_params; var $_trigger = null; function Horde_Form_Action($params = null) { $this->_params = $params; $this->_id = md5(mt_rand()); } function getTrigger() { return $this->_trigger; } function id() { return $this->_id; } function getActionScript($form, $renderer, $varname) { return ''; } function printJavaScript() { } function _printJavaScriptStart() { echo ''; } function getTarget() { return isset($this->_params['target']) ? $this->_params['target'] : null; } function setValues(&$vars, $sourceVal, $index = null, $arrayVal = false) { } /** * Attempts to return a concrete Horde_Form_Action instance * based on $form. * * @param mixed $action The type of concrete Horde_Form_Action subclass * to return. If $action is an array, then we will look * in $action[0]/lib/Form/Action/ for the subclass * implementation named $action[1].php. * @param array $params A hash containing any additional configuration a * form might need. * * @return Horde_Form_Action The concrete Horde_Form_Action reference, or * false on an error. */ function &factory($action, $params = null) { if (is_array($action)) { $app = $action[0]; $action = $action[1]; } $action = basename($action); $class = 'Horde_Form_Action_' . $action; if (!class_exists($class)) { if (!empty($app)) { include_once $GLOBALS['registry']->get('fileroot', $app) . '/lib/Form/Action/' . $action . '.php'; } else { include_once 'Horde/Form/Action/' . $action . '.php'; } } if (class_exists($class)) { $instance = new $class($params); } else { $instance = PEAR::raiseError('Class definition of ' . $class . ' not found.'); } return $instance; } /** * Attempts to return a reference to a concrete * Horde_Form_Action instance based on $action. It will only * create a new instance if no Horde_Form_Action instance with * the same parameters currently exists. * * This should be used if multiple types of form renderers (and, * thus, multiple Horde_Form_Action instances) are required. * * This method must be invoked as: $var = * &Horde_Form_Action::singleton() * * @param mixed $action The type of concrete Horde_Form_Action subclass to return. * The code is dynamically included. If $action is an array, * then we will look in $action[0]/lib/Form/Action/ for * the subclass implementation named $action[1].php. * @param array $params A hash containing any additional configuration a * form might need. * * @return Horde_Form_Action The concrete Horde_Form_Action reference, or * false on an error. */ function &singleton($action, $params = null) { static $instances = array(); $signature = serialize(array($action, $params)); if (!isset($instances[$signature])) { $instances[$signature] = &Horde_Form_Action::factory($action, $params); } return $instances[$signature]; } } * * 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 Robert E. Coyle * @package Horde_Form */ class Horde_Form_Renderer { var $_name; var $_requiredLegend = false; var $_requiredMarker = '*'; var $_helpMarker = '?'; var $_showHeader = true; var $_cols = 2; var $_varRenderer = null; var $_firstField = null; var $_stripedRows = true; /** * Does the title of the form contain HTML? If so, you are responsible for * doing any needed escaping/sanitization yourself. Otherwise the title * will be run through htmlspecialchars() before being output. * * @var boolean */ var $_encodeTitle = true; /** * Width of the attributes column. * * @access private * @var string */ var $_attrColumnWidth = '15%'; /** * Construct a new Horde_Form_Renderer::. * * @param array $params This is a hash of renderer-specific parameters. * Possible keys: * 'varrenderer_driver': specifies the driver * parameter to Horde_UI_VarRenderer::factory(). * 'encode_title': @see $_encodeTitle */ function Horde_Form_Renderer($params = array()) { global $registry; if (isset($registry) && is_a($registry, 'Registry')) { /* Registry available, so use a pretty image. */ $this->_requiredMarker = Horde::img('required.png', '*', '', $registry->getImageDir('horde')); } else { /* No registry available, use something plain. */ $this->_requiredMarker = '*'; } if (isset($params['encode_title'])) { $this->encodeTitle($params['encode_title']); } $driver = 'html'; if (isset($params['varrenderer_driver'])) { $driver = $params['varrenderer_driver']; } require_once 'Horde/UI/VarRenderer.php'; $this->_varRenderer = &Horde_UI_VarRenderer::factory($driver, $params); } function showHeader($bool) { $this->_showHeader = $bool; } /** * Sets or returns whether the form title should be encoded with * htmlspecialchars(). * * @param boolean $encode If true, the form title gets encoded. If false * the title can contain HTML, but the class user * is responsible to encode any special characters. * * @return boolean Whether the form title should be encoded. */ function encodeTitle($encode = null) { if (!is_null($encode)) { $this->_encodeTitle = $encode; } return $this->_encodeTitle = $encode; } /** * @deprecated */ function setAttrColumnWidth($width) { } function open($action, $method, $name, $enctype = null) { $this->_name = $name; $name = htmlspecialchars($name); $action = htmlspecialchars($action); $method = htmlspecialchars($method); echo "
\n"; Util::pformInput(); } function beginActive($name, $extra = null) { $this->_renderBeginActive($name, $extra); } function beginInactive($name, $extra = null) { $this->_renderBeginInactive($name, $extra); } function _renderSectionTabs(&$form) { /* If javascript is not available, do not render tabs. */ if (!$GLOBALS['browser']->hasFeature('javascript')) { return; } $open_section = $form->getOpenSection(); /* Add the javascript for the toggling the sections. */ Horde::addScriptFile('form_sections.js', 'horde', true); echo ''; /* Loop through the sections and print out a tab for each. */ echo "
    \n"; foreach ($form->_sections as $section => $val) { $class = ($section == $open_section) ? ' class="activeTab"' : ''; $js = sprintf('onclick="sections_%s.toggle(\'%s\'); return false;"', $form->getName(), $section); printf('%s%s ' . "\n", $class, htmlspecialchars($form->getName() . '_tab_' . $section), $js, $form->getSectionImage($section), $form->getSectionDesc($section)); } echo "

\n"; } function _renderSectionBegin(&$form, $section) { // Stripe alternate rows if that option is turned on. if ($this->_stripedRows && class_exists('Horde')) { Horde::addScriptFile('stripe.js', 'horde', true); $class = ' class="striped"'; } else { $class = ''; } $open_section = $form->getOpenSection(); if (empty($open_section)) { $open_section = '__base'; } printf('
', htmlspecialchars($form->getName() . '_section_' . $section), ($open_section == $section ? 'block' : 'none'), $class); } function _renderSectionEnd() { echo '
'; } function end() { $this->_renderEnd(); } function close($focus = true) { echo "
\n"; if ($focus && !empty($this->_firstField)) { echo ' '; } } function listFormVars(&$form) { $variables = &$form->getVariables(true, true); $vars = array(); if ($variables) { foreach ($variables as $var) { if (is_object($var)) { if (!$var->isReadonly()) { $vars[$var->getVarName()] = 1; } } else { $vars[$var] = 1; } } } require_once 'Horde/NLS.php'; echo ''; } function renderFormActive(&$form, &$vars) { $this->_renderForm($form, $vars, true); } function renderFormInactive(&$form, &$vars) { $this->_renderForm($form, $vars, false); } function _renderForm(&$form, &$vars, $active) { /* If help is present 3 columns are needed. */ $this->_cols = $form->hasHelp() ? 3 : 2; $variables = &$form->getVariables(false); /* Check for a form token error. */ if (($tokenError = $form->getError('_formToken')) !== null) { echo '

' . htmlspecialchars($tokenError) . '

'; } /* Check for a form secret error. */ if (($secretError = $form->getError('_formSecret')) !== null) { echo '

' . htmlspecialchars($secretError) . '

'; } if (count($form->_sections)) { $this->_renderSectionTabs($form); } $error_section = null; foreach ($variables as $section_id => $section) { $this->_renderSectionBegin($form, $section_id); foreach ($section as $var) { $type = $var->getTypeName(); switch ($type) { case 'header': $this->_renderHeader($var->getHumanName(), $form->getError($var->getVarName())); break; case 'description': $this->_renderDescription($var->getHumanName()); break; case 'spacer': $this->_renderSpacer(); break; default: $isInput = ($active && !$var->isReadonly()); $format = $isInput ? 'Input' : 'Display'; $begin = "_renderVar${format}Begin"; $end = "_renderVar${format}End"; $this->$begin($form, $var, $vars); echo $this->_varRenderer->render($form, $var, $vars, $isInput); $this->$end($form, $var, $vars); /* Print any javascript if actions present. */ if ($var->hasAction()) { $var->_action->printJavaScript(); } /* Keep first field. */ if ($active && empty($this->_firstField) && !$var->isReadonly() && !$var->isHidden()) { $this->_firstField = $var->getVarName(); } /* Keep section with first error. */ if (is_null($error_section) && $form->getError($var)) { $error_section = $section_id; } } } $this->_renderSectionEnd(); } if (!is_null($error_section) && $form->_sections) { echo '"; } } function submit($submit = null, $reset = false) { if (is_null($submit) || empty($submit)) { $submit = _("Submit"); } if ($reset === true) { $reset = _("Reset"); } $this->_renderSubmit($submit, $reset); } /** * Implementation specific begin function. */ function _renderBeginActive($name, $extra) { echo '
'; if ($this->_showHeader) { $this->_sectionHeader($name, $extra); } if ($this->_requiredLegend) { echo '' . $this->_requiredMarker . ' = ' . _("Required Field"); } } /** * Implementation specific begin function. */ function _renderBeginInactive($name, $extra) { echo '
'; if ($this->_showHeader) { $this->_sectionHeader($name, $extra); } } /** * Implementation specific end function. */ function _renderEnd() { echo '
' . $this->_varRenderer->renderEnd(); } function _renderHeader($header, $error = '') { ?>

 
getError($var); $isvalid = empty($message); echo "\n"; printf(' %s%s%s%s' . "\n", empty($this->_attrColumnWidth) ? '' : ' width="' . $this->_attrColumnWidth . '"', $isvalid ? '' : '', $var->isRequired() ? '' . $this->_requiredMarker . ' ' : '', $var->getHumanName(), $isvalid ? '' : '
' . $message . '
'); printf(' ', ((!$var->hasHelp() && $form->hasHelp()) ? ' colspan="2"' : ''), ($var->isDisabled() ? ' class="form-disabled"' : '')); } function _renderVarInputEnd(&$form, &$var, &$vars) { /* Display any description for the field. */ if ($var->hasDescription()) { echo '
' . $var->getDescription(); } /* Display any help for the field. */ if ($var->hasHelp()) { global $registry; if (isset($registry) && is_a($registry, 'Registry')) { $link = Help::link($GLOBALS['registry']->getApp(), $var->getHelp()); } else { $link = '' . $this->_helpMarker . ''; } echo "\n $link "; } echo "\n\n"; } // Implementation specifics -- display variables. function _renderVarDisplayBegin(&$form, &$var, &$vars) { $message = $form->getError($var); $isvalid = empty($message); echo "\n"; printf(' %s%s%s' . "\n", empty($this->_attrColumnWidth) ? '' : ' width="' . $this->_attrColumnWidth . '"', $isvalid ? '' : '', $var->getHumanName(), $isvalid ? '' : '
' . $message . '
'); echo ' '; } function _renderVarDisplayEnd(&$form, &$var, &$vars) { if ($var->hasHelp()) { echo ' '; } echo "\n\n"; } function _sectionHeader($title, $extra = '') { if (strlen($title)) { echo '
'; if (!empty($extra)) { echo '' . $extra . ''; } echo $this->_encodeTitle ? htmlspecialchars($title) : $title; echo '
'; } } /** * Attempts to return a concrete Horde_Form_Renderer instance based on * $renderer. * * @param mixed $renderer The type of concrete Horde_Form_Renderer * subclass to return. The code is dynamically * included. If $renderer is an array, then we will * look in $renderer[0]/lib/Form/Renderer/ for the * subclass implementation named $renderer[1].php. * @param array $params A hash containing any additional configuration a * form might need. * * @return Horde_Form_Renderer The concrete Horde_Form_Renderer reference, * or false on an error. */ function factory($renderer = '', $params = null) { if (is_array($renderer)) { $app = $renderer[0]; $renderer = $renderer[1]; } /* Return a base Horde_Form_Renderer object if no driver is * specified. */ $renderer = basename($renderer); if (!empty($renderer) && $renderer != 'none') { $class = 'Horde_Form_Renderer_' . $renderer; } else { $class = 'Horde_Form_Renderer'; } if (!class_exists($class)) { if (!empty($app)) { include_once $GLOBALS['registry']->get('fileroot', $app) . '/lib/Form/Renderer/' . $renderer . '.php'; } else { include_once 'Horde/Form/Renderer/' . $renderer . '.php'; } } if (class_exists($class)) { return new $class($params); } else { return PEAR::raiseError('Class definition of ' . $class . ' not found.'); } } /** * Attempts to return a reference to a concrete Horde_Form_Renderer * instance based on $renderer. It will only create a new instance if no * Horde_Form_Renderer instance with the same parameters currently exists. * * This should be used if multiple types of form renderers (and, * thus, multiple Horde_Form_Renderer instances) are required. * * This method must be invoked as: $var = &Horde_Form_Renderer::singleton() * * @param mixed $renderer The type of concrete Horde_Form_Renderer * subclass to return. The code is dynamically * included. If $renderer is an array, then we will * look in $renderer[0]/lib/Form/Renderer/ for the * subclass implementation named $renderer[1].php. * @param array $params A hash containing any additional configuration a * form might need. * * @return Horde_Form_Renderer The concrete Horde_Form_Renderer reference, * or false on an error. */ function &singleton($renderer, $params = null) { static $instances = array(); $signature = serialize(array($renderer, $params)); if (!isset($instances[$signature])) { $instances[$signature] = Horde_Form_Renderer::factory($renderer, $params); } return $instances[$signature]; } } _values = $values; $this->_header = $header; } function isValid(&$var, &$vars, $value, &$message) { if (count($this->_values) == 0 || count($value) == 0) { return true; } foreach ($value as $item) { if (!isset($this->_values[$item])) { $error = true; break; } } if (!isset($error)) { return true; } $message = _("Invalid data submitted."); return false; } function getHeader() { return $this->_header; } function getValues() { return $this->_values; } /** * Return info about field type. */ function about() { return array( 'name' => _("Table Set"), 'params' => array( 'values' => array('label' => _("Values"), 'type' => 'stringlist'), 'header' => array('label' => _("Headers"), 'type' => 'stringlist')), ); } } * 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 Robert E. Coyle * @author Chuck Hagenbuch * @since Horde 3.0 * @package Horde_Form */ class Horde_Form { var $_name = ''; var $_title = ''; var $_extra = ''; var $_vars; var $_submit = array(); var $_reset = false; var $_errors = array(); var $_submitted = null; var $_sections = array(); var $_open_section = null; var $_currentSection = array(); var $_variables = array(); var $_hiddenVariables = array(); var $_useFormToken = true; var $_autofilled = false; var $_enctype = null; var $_help = false; function Horde_Form(&$vars, $title = '', $name = null) { if (empty($name)) { $name = String::lower(get_class($this)); } $this->_vars = &$vars; $this->_title = $title; $this->_name = $name; } function __construct($vars, $title = '', $name = null) { $this->Horde_Form($vars, $title, $name); } function &singleton($form, &$vars, $title = '', $name = null) { static $instances = array(); $signature = serialize(array($form, $vars, $title, $name)); if (!isset($instances[$signature])) { if (class_exists($form)) { $instances[$signature] = new $form($vars, $title, $name); } else { $instances[$signature] = new Horde_Form($vars, $title, $name); } } return $instances[$signature]; } function setVars(&$vars) { $this->_vars = &$vars; } function getTitle() { return $this->_title; } function setTitle($title) { $this->_title = $title; } function getExtra() { return $this->_extra; } function setExtra($extra) { $this->_extra = $extra; } function getName() { return $this->_name; } /** * Sets or gets whether the form should be verified by tokens. * Tokens are used to verify that a form is only submitted once. * * @param boolean $token If specified, sets whether to use form tokens. * * @return boolean Whether form tokens are being used. */ function useToken($token = null) { if (!is_null($token)) { $this->_useFormToken = $token; } return $this->_useFormToken; } /** * Get the renderer for this form, either a custom renderer or the * standard one. * * To use a custom form renderer, your form class needs to * override this function: * * function &getRenderer() * { * $r = new CustomFormRenderer(); * return $r; * } * * * ... where CustomFormRenderer is the classname of the custom * renderer class, which should extend Horde_Form_Renderer. * * @param array $params A hash of renderer-specific parameters. * * @return object Horde_Form_Renderer The form renderer. */ function &getRenderer($params = array()) { require_once 'Horde/Form/Renderer.php'; $renderer = new Horde_Form_Renderer($params); return $renderer; } function &getType($type, $params = array()) { $type_class = 'Horde_Form_Type_' . $type; if (!class_exists($type_class)) { require_once 'PEAR.php'; Horde::fatal(PEAR::raiseError(sprintf('Nonexistant class "%s" for field type "%s"', $type_class, $type)), __FILE__, __LINE__); } $type_ob = new $type_class(); if (!$params) { $params = array(); } call_user_func_array(array(&$type_ob, 'init'), $params); return $type_ob; } function setSection($section = '', $desc = '', $image = '', $expanded = true) { $this->_currentSection = $section; if (!count($this->_sections) && !$this->getOpenSection()) { $this->setOpenSection($section); } $this->_sections[$section]['desc'] = $desc; $this->_sections[$section]['expanded'] = $expanded; $this->_sections[$section]['image'] = $image; } function getSectionDesc($section) { return $this->_sections[$section]['desc']; } function getSectionImage($section) { return $this->_sections[$section]['image']; } function setOpenSection($section) { $this->_vars->set('__formOpenSection', $section); } function getOpenSection() { return $this->_vars->get('__formOpenSection'); } function getSectionExpandedState($section, $boolean = false) { if ($boolean) { /* Only the boolean value is required. */ return $this->_sections[$section]['expanded']; } /* Need to return the values for use in styles. */ if ($this->_sections[$section]['expanded']) { return 'block'; } else { return 'none'; } } /** * TODO */ function &addVariable($humanName, $varName, $type, $required, $readonly = false, $description = null, $params = array()) { return $this->insertVariableBefore(null, $humanName, $varName, $type, $required, $readonly, $description, $params); } /** * TODO */ function &insertVariableBefore($before, $humanName, $varName, $type, $required, $readonly = false, $description = null, $params = array()) { $type = &$this->getType($type, $params); $var = new Horde_Form_Variable($humanName, $varName, $type, $required, $readonly, $description); /* Set the form object reference in the var. */ $var->setFormOb($this); if ($var->getTypeName() == 'enum' && !strlen($type->getPrompt()) && count($var->getValues()) == 1) { $vals = array_keys($var->getValues()); $this->_vars->add($var->varName, $vals[0]); $var->_autofilled = true; } elseif ($var->getTypeName() == 'file' || $var->getTypeName() == 'image') { $this->_enctype = 'multipart/form-data'; } if (empty($this->_currentSection)) { $this->_currentSection = '__base'; } if (is_null($before)) { $this->_variables[$this->_currentSection][] = &$var; } else { $num = 0; while (isset($this->_variables[$this->_currentSection][$num]) && $this->_variables[$this->_currentSection][$num]->getVarName() != $before) { $num++; } if (!isset($this->_variables[$this->_currentSection][$num])) { $this->_variables[$this->_currentSection][] = &$var; } else { $this->_variables[$this->_currentSection] = array_merge( array_slice($this->_variables[$this->_currentSection], 0, $num), array(&$var), array_slice($this->_variables[$this->_currentSection], $num)); } } return $var; } /** * Removes a variable from the form. * * As only variables can be passed by reference, you need to call this * method this way if want to pass a variable name: * * $form->removeVariable($var = 'varname'); * * * @param Horde_Form_Variable|string $var Either the variable's name or * the variable to remove from the * form. * * @return boolean True if the variable was found (and deleted). */ function removeVariable(&$var) { foreach (array_keys($this->_variables) as $section) { foreach (array_keys($this->_variables[$section]) as $i) { if ((is_a($var, 'Horde_Form_Variable') && $this->_variables[$section][$i] === $var) || ($this->_variables[$section][$i]->getVarName() == $var)) { // Slice out the variable to be removed. $this->_variables[$this->_currentSection] = array_merge( array_slice($this->_variables[$this->_currentSection], 0, $i), array_slice($this->_variables[$this->_currentSection], $i + 1)); return true; } } } return false; } /** * TODO */ function &addHidden($humanName, $varName, $type, $required, $readonly = false, $description = null, $params = array()) { $type = &$this->getType($type, $params); $var = new Horde_Form_Variable($humanName, $varName, $type, $required, $readonly, $description); $var->hide(); $this->_hiddenVariables[] = &$var; return $var; } function &getVariables($flat = true, $withHidden = false) { if ($flat) { $vars = array(); foreach ($this->_variables as $section) { foreach ($section as $var) { $vars[] = $var; } } if ($withHidden) { foreach ($this->_hiddenVariables as $var) { $vars[] = $var; } } return $vars; } else { return $this->_variables; } } function setButtons($submit, $reset = false) { if ($submit === true || is_null($submit) || empty($submit)) { /* Default to 'Submit'. */ $submit = array(_("Submit")); } elseif (!is_array($submit)) { /* Default to array if not passed. */ $submit = array($submit); } /* Only if $reset is strictly true insert default 'Reset'. */ if ($reset === true) { $reset = _("Reset"); } $this->_submit = $submit; $this->_reset = $reset; } function appendButtons($submit) { if (!is_array($submit)) { $submit = array($submit); } $this->_submit = array_merge($this->_submit, $submit); } function preserveVarByPost(&$vars, $varname, $alt_varname = '') { $value = $vars->getExists($varname, $wasset); /* If an alternate name is given under which to preserve use that. */ if ($alt_varname) { $varname = $alt_varname; } if ($wasset) { $this->_preserveVarByPost($varname, $value); } } /** * @access private */ function _preserveVarByPost($varname, $value) { if (is_array($value)) { foreach ($value as $id => $val) { $this->_preserveVarByPost($varname . '[' . $id . ']', $val); } } else { $varname = htmlspecialchars($varname); $value = htmlspecialchars($value); printf('' . "\n", $varname, $value); } } function open(&$renderer, &$vars, $action, $method = 'get', $enctype = null) { if (is_null($enctype) && !is_null($this->_enctype)) { $enctype = $this->_enctype; } $renderer->open($action, $method, $this->_name, $enctype); $renderer->listFormVars($this); if (!empty($this->_name)) { $this->_preserveVarByPost('formname', $this->_name); } if ($this->_useFormToken) { require_once 'Horde/Token.php'; $token = Horde_Token::generateId($this->_name); $_SESSION['horde_form_secrets'][$token] = true; $this->_preserveVarByPost($this->_name . '_formToken', $token); } /* Loop through vars and check for any special cases to preserve. */ $variables = $this->getVariables(); foreach ($variables as $var) { /* Preserve value if change has to be tracked. */ if ($var->getOption('trackchange')) { $varname = $var->getVarName(); $this->preserveVarByPost($vars, $varname, '__old_' . $varname); } } foreach ($this->_hiddenVariables as $var) { $this->preserveVarByPost($vars, $var->getVarName()); } } function close($renderer) { $renderer->close(); } /** * Renders the form for editing. * * @param Horde_Form_Renderer $renderer A renderer instance, optional * since Horde 3.2. * @param Variables $vars A Variables instance, optional * since Horde 3.2. * @param string $action The form action (url). * @param string $method The form method, usually either * 'get' or 'post'. * @param string $enctype The form encoding type. Determined * automatically if null. * @param boolean $focus Focus the first form field? */ function renderActive($renderer = null, $vars = null, $action = '', $method = 'get', $enctype = null, $focus = true) { if (is_null($renderer)) { $renderer = $this->getRenderer(); } if (is_null($vars)) { $vars = $this->_vars; } if (is_null($enctype) && !is_null($this->_enctype)) { $enctype = $this->_enctype; } $renderer->open($action, $method, $this->getName(), $enctype); $renderer->listFormVars($this); if (!empty($this->_name)) { $this->_preserveVarByPost('formname', $this->_name); } if ($this->_useFormToken) { require_once 'Horde/Token.php'; $token = Horde_Token::generateId($this->_name); $_SESSION['horde_form_secrets'][$token] = true; $this->_preserveVarByPost($this->_name . '_formToken', $token); } if (count($this->_sections)) { $this->_preserveVarByPost('__formOpenSection', $this->getOpenSection()); } /* Loop through vars and check for any special cases to * preserve. */ $variables = $this->getVariables(); foreach ($variables as $var) { /* Preserve value if change has to be tracked. */ if ($var->getOption('trackchange')) { $varname = $var->getVarName(); $this->preserveVarByPost($vars, $varname, '__old_' . $varname); } } foreach ($this->_hiddenVariables as $var) { $this->preserveVarByPost($vars, $var->getVarName()); } $renderer->beginActive($this->getTitle(), $this->getExtra()); $renderer->renderFormActive($this, $vars); $renderer->submit($this->_submit, $this->_reset); $renderer->end(); $renderer->close($focus); } /** * Renders the form for displaying. * * @param Horde_Form_Renderer $renderer A renderer instance, optional * since Horde 3.2. * @param Variables $vars A Variables instance, optional * since Horde 3.2. */ function renderInactive($renderer = null, $vars = null) { if (is_null($renderer)) { $renderer = $this->getRenderer(); } if (is_null($vars)) { $vars = $this->_vars; } $renderer->_name = $this->_name; $renderer->beginInactive($this->getTitle(), $this->getExtra()); $renderer->renderFormInactive($this, $vars); $renderer->end(); } function preserve($vars) { if ($this->_useFormToken) { require_once 'Horde/Token.php'; $token = Horde_Token::generateId($this->_name); $_SESSION['horde_form_secrets'][$token] = true; $this->_preserveVarByPost($this->_name . '_formToken', $token); } $variables = $this->getVariables(); foreach ($variables as $var) { $varname = $var->getVarName(); /* Save value of individual components. */ switch ($var->getTypeName()) { case 'passwordconfirm': case 'emailconfirm': $this->preserveVarByPost($vars, $varname . '[original]'); $this->preserveVarByPost($vars, $varname . '[confirm]'); break; case 'monthyear': $this->preserveVarByPost($vars, $varname . '[month]'); $this->preserveVarByPost($vars, $varname . '[year]'); break; case 'monthdayyear': $this->preserveVarByPost($vars, $varname . '[month]'); $this->preserveVarByPost($vars, $varname . '[day]'); $this->preserveVarByPost($vars, $varname . '[year]'); break; } $this->preserveVarByPost($vars, $varname); } foreach ($this->_hiddenVariables as $var) { $this->preserveVarByPost($vars, $var->getVarName()); } } function unsetVars(&$vars) { foreach ($this->getVariables() as $var) { $vars->remove($var->getVarName()); } } /** * Validates the form, checking if it really has been submitted by calling * isSubmitted() and if true does any onSubmit() calls for variable types * in the form. The _submitted variable is then rechecked. * * @param Variables $vars A Variables instance, optional since Horde * 3.2. * @param boolean $canAutofill Can the form be valid without being * submitted? * * @return boolean True if the form is valid. */ function validate($vars = null, $canAutoFill = false) { if (is_null($vars)) { $vars = $this->_vars; } /* Get submitted status. */ if ($this->isSubmitted() || $canAutoFill) { /* Form was submitted or can autofill; check for any variable * types' onSubmit(). */ $this->onSubmit($vars); /* Recheck submitted status. */ if (!$this->isSubmitted() && !$canAutoFill) { return false; } } else { /* Form has not been submitted; return false. */ return false; } $message = ''; $this->_autofilled = true; if ($this->_useFormToken) { global $conf; require_once 'Horde/Token.php'; if (isset($conf['token'])) { /* If there is a configured token system, set it up. */ $tokenSource = &Horde_Token::singleton($conf['token']['driver'], Horde::getDriverConfig('token', $conf['token']['driver'])); } else { /* Default to the file system if no config. */ $tokenSource = &Horde_Token::singleton('file'); } $passedToken = $vars->get($this->_name . '_formToken'); if (!empty($passedToken) && !$tokenSource->verify($passedToken)) { $this->_errors['_formToken'] = _("This form has already been processed."); } if (empty($_SESSION['horde_form_secrets'][$passedToken])) { $this->_errors['_formSecret'] = _("Required secret is invalid - potentially malicious request."); } } foreach ($this->getVariables() as $var) { $this->_autofilled = $var->_autofilled && $this->_autofilled; if (!$var->validate($vars, $message)) { $this->_errors[$var->getVarName()] = $message; } } if ($this->_autofilled) { unset($this->_errors['_formToken']); } foreach ($this->_hiddenVariables as $var) { if (!$var->validate($vars, $message)) { $this->_errors[$var->getVarName()] = $message; } } return $this->isValid(); } function clearValidation() { $this->_errors = array(); } function getError($var) { if (is_a($var, 'Horde_Form_Variable')) { $name = $var->getVarName(); } else { $name = $var; } return isset($this->_errors[$name]) ? $this->_errors[$name] : null; } function setError($var, $message) { if (is_a($var, 'Horde_Form_Variable')) { $name = $var->getVarName(); } else { $name = $var; } $this->_errors[$name] = $message; } function clearError($var) { if (is_a($var, 'Horde_Form_Variable')) { $name = $var->getVarName(); } else { $name = $var; } unset($this->_errors[$name]); } function isValid() { return ($this->_autofilled || count($this->_errors) == 0); } function execute() { Horde::logMessage('Warning: Horde_Form::execute() called, should be overridden', __FILE__, __LINE__, PEAR_LOG_DEBUG); } /** * Fetch the field values of the submitted form. * * @param Variables $vars A Variables instance, optional since Horde 3.2. * @param array $info Array to be filled with the submitted field * values. */ function getInfo($vars, &$info) { if (is_null($vars)) { $vars = $this->_vars; } $this->_getInfoFromVariables($this->getVariables(), $vars, $info); $this->_getInfoFromVariables($this->_hiddenVariables, $vars, $info); } /** * Fetch the field values from a given array of variables. * * @access private * * @param array $variables An array of Horde_Form_Variable objects to * fetch from. * @param object $vars The Variables object. * @param array $info The array to be filled with the submitted * field values. */ function _getInfoFromVariables($variables, &$vars, &$info) { foreach ($variables as $var) { if ($var->isArrayVal()) { $var->getInfo($vars, $values); if (is_array($values)) { $varName = str_replace('[]', '', $var->getVarName()); foreach ($values as $i => $val) { $info[$i][$varName] = $val; } } } else { require_once 'Horde/Array.php'; if (Horde_Array::getArrayParts($var->getVarName(), $base, $keys)) { if (!isset($info[$base])) { $info[$base] = array(); } $pointer = &$info[$base]; while (count($keys)) { $key = array_shift($keys); if (!isset($pointer[$key])) { $pointer[$key] = array(); } $pointer = &$pointer[$key]; } $var->getInfo($vars, $pointer); } else { $var->getInfo($vars, $info[$var->getVarName()]); } } } } function hasHelp() { return $this->_help; } /** * Determines if this form has been submitted or not. If the class * var _submitted is null then it will check for the presence of * the formname in the form variables. * * Other events can explicitly set the _submitted variable to * false to indicate a form submit but not for actual posting of * data (eg. onChange events to update the display of fields). * * @return boolean True or false indicating if the form has been * submitted. */ function isSubmitted() { if (is_null($this->_submitted)) { if ($this->_vars->get('formname') == $this->getName()) { $this->_submitted = true; } else { $this->_submitted = false; } } return $this->_submitted; } /** * Checks if there is anything to do on the submission of the form by * looping through each variable's onSubmit() function. * * @param Horde_Variables $vars */ function onSubmit(&$vars) { /* Loop through all vars and check if there's anything to do on * submit. */ $variables = $this->getVariables(); foreach ($variables as $var) { $var->type->onSubmit($var, $vars); /* If changes to var being tracked don't register the form as * submitted if old value and new value differ. */ if ($var->getOption('trackchange')) { $varname = $var->getVarName(); if (!is_null($vars->get('formname')) && $vars->get($varname) != $vars->get('__old_' . $varname)) { $this->_submitted = false; } } } } /** * Explicitly sets the state of the form submit. * * An event can override the automatic determination of the submit state * in the isSubmitted() function. * * @param boolean $state Whether to set the state of the form as being * submitted. */ function setSubmitted($state = true) { $this->_submitted = $state; } } /** * Horde_Form_Type Class * * @author Robert E. Coyle * @package Horde_Form */ class Horde_Form_Type { function Horde_Form_Type() { } function getProperty($property) { $prop = '_' . $property; return isset($this->$prop) ? $this->$prop : null; } function __get($property) { return $this->getProperty($property); } function setProperty($property, $value) { $prop = '_' . $property; $this->$prop = $value; } function __set($property, $value) { return $this->setProperty($property, $value); } function init() { } function onSubmit() { } function isValid(&$var, &$vars, $value, &$message) { $message = 'Error: Horde_Form_Type::isValid() called - should be overridden
'; return false; } function getTypeName() { return str_replace('horde_form_type_', '', String::lower(get_class($this))); } function getValues() { return null; } function getInfo(&$vars, &$var, &$info) { $info = $var->getValue($vars); } } class Horde_Form_Type_spacer extends Horde_Form_Type { function isValid(&$var, &$vars, $value, &$message) { return true; } /** * Return info about field type. */ function about() { return array('name' => _("Spacer")); } } class Horde_Form_Type_header extends Horde_Form_Type { function isValid(&$var, &$vars, $value, &$message) { return true; } /** * Return info about field type. */ function about() { return array('name' => _("Header")); } } class Horde_Form_Type_description extends Horde_Form_Type { function isValid(&$var, &$vars, $value, &$message) { return true; } /** * Return info about field type. */ function about() { return array('name' => _("Description")); } } /** * Simply renders its raw value in both active and inactive rendering. */ class Horde_Form_Type_html extends Horde_Form_Type { function isValid(&$var, &$vars, $value, &$message) { return true; } /** * Return info about field type. */ function about() { return array('name' => _("HTML")); } } class Horde_Form_Type_number extends Horde_Form_Type { var $_fraction; function init($fraction = null) { $this->_fraction = $fraction; } function isValid(&$var, &$vars, $value, &$message) { if ($var->isRequired() && empty($value) && ((string)(double)$value !== $value)) { $message = _("This field is required."); return false; } elseif (empty($value)) { return true; } /* If matched, then this is a correct numeric value. */ if (preg_match($this->_getValidationPattern(), $value)) { return true; } $message = _("This field must be a valid number."); return false; } function _getValidationPattern() { static $pattern = ''; if (!empty($pattern)) { return $pattern; } /* Get current locale information. */ $linfo = NLS::getLocaleInfo(); /* Build the pattern. */ $pattern = '(-)?'; /* Only check thousands separators if locale has any. */ if (!empty($linfo['mon_thousands_sep'])) { /* Regex to check for correct thousands separators (if any). */ $pattern .= '((\d+)|((\d{0,3}?)([' . $linfo['mon_thousands_sep'] . ']\d{3})*?))'; } else { /* No locale thousands separator, check for only digits. */ $pattern .= '(\d+)'; } /* If no decimal point specified default to dot. */ if (empty($linfo['mon_decimal_point'])) { $linfo['mon_decimal_point'] = '.'; } /* Regex to check for correct decimals (if any). */ if (empty($this->_fraction)) { $fraction = '*'; } else { $fraction = '{0,' . $this->_fraction . '}'; } $pattern .= '([' . $linfo['mon_decimal_point'] . '](\d' . $fraction . '))?'; /* Put together the whole regex pattern. */ $pattern = '/^' . $pattern . '$/'; return $pattern; } function getInfo(&$vars, &$var, &$info) { $value = $vars->get($var->getVarName()); $linfo = NLS::getLocaleInfo(); $value = str_replace($linfo['mon_thousands_sep'], '', $value); $info = str_replace($linfo['mon_decimal_point'], '.', $value); } /** * Return info about field type. */ function about() { return array('name' => _("Number")); } } class Horde_Form_Type_int extends Horde_Form_Type { function isValid(&$var, &$vars, $value, &$message) { if ($var->isRequired() && empty($value) && ((string)(int)$value !== $value)) { $message = _("This field is required."); return false; } if (empty($value) || preg_match('/^[0-9]+$/', $value)) { return true; } $message = _("This field may only contain integers."); return false; } /** * Return info about field type. */ function about() { return array('name' => _("Integer")); } } class Horde_Form_Type_octal extends Horde_Form_Type { function isValid(&$var, &$vars, $value, &$message) { if ($var->isRequired() && empty($value) && ((string)(int)$value !== $value)) { $message = _("This field is required."); return false; } if (empty($value) || preg_match('/^[0-7]+$/', $value)) { return true; } $message = _("This field may only contain octal values."); return false; } /** * Return info about field type. */ function about() { return array('name' => _("Octal")); } } class Horde_Form_Type_intlist extends Horde_Form_Type { function isValid(&$var, &$vars, $value, &$message) { if (empty($value) && $var->isRequired()) { $message = _("This field is required."); return false; } if (empty($value) || preg_match('/^[0-9 ,]+$/', $value)) { return true; } $message = _("This field must be a comma or space separated list of integers"); return false; } /** * Return info about field type. */ function about() { return array('name' => _("Integer list")); } } class Horde_Form_Type_text extends Horde_Form_Type { var $_regex; var $_size; var $_maxlength; /** * The initialisation function for the text variable type. * * @access private * * @param string $regex Any valid PHP PCRE pattern syntax that * needs to be matched for the field to be * considered valid. If left empty validity * will be checked only for required fields * whether they are empty or not. * If using this regex test it is advisable * to enter a description for this field to * warn the user what is expected, as the * generated error message is quite generic * and will not give any indication where * the regex failed. * @param integer $size The size of the input field. * @param integer $maxlength The max number of characters. */ function init($regex = '', $size = 40, $maxlength = null) { $this->_regex = $regex; $this->_size = $size; $this->_maxlength = $maxlength; } function isValid(&$var, &$vars, $value, &$message) { $valid = true; if (!empty($this->_maxlength) && String::length($value) > $this->_maxlength) { $valid = false; $message = sprintf(_("Value is over the maximum length of %d."), $this->_maxlength); } elseif ($var->isRequired() && empty($this->_regex)) { $valid = strlen(trim($value)) > 0; if (!$valid) { $message = _("This field is required."); } } elseif (!empty($this->_regex)) { $valid = preg_match($this->_regex, $value); if (!$valid) { $message = _("You must enter a valid value."); } } return $valid; } function getSize() { return $this->_size; } function getMaxLength() { return $this->_maxlength; } /** * Return info about field type. */ function about() { return array( 'name' => _("Text"), 'params' => array( 'regex' => array('label' => _("Regex"), 'type' => 'text'), 'size' => array('label' => _("Size"), 'type' => 'int'), 'maxlength' => array('label' => _("Maximum length"), 'type' => 'int'))); } } class Horde_Form_Type_stringlist extends Horde_Form_Type_text { /** * Return info about field type. */ function about() { return array( 'name' => _("String list"), 'params' => array( 'regex' => array('label' => _("Regex"), 'type' => 'text'), 'size' => array('label' => _("Size"), 'type' => 'int'), 'maxlength' => array('label' => _("Maximum length"), 'type' => 'int')), ); } } /** * @since Horde 3.3 */ class Horde_Form_Type_stringarray extends Horde_Form_Type_stringlist { function getInfo(&$vars, &$var, &$info) { $info = array_map('trim', explode(',', $vars->get($var->getVarName()))); } /** * Return info about field type. */ function about() { return array( 'name' => _("String list returning an array"), 'params' => array( 'regex' => array('label' => _("Regex"), 'type' => 'text'), 'size' => array('label' => _("Size"), 'type' => 'int'), 'maxlength' => array('label' => _("Maximum length"), 'type' => 'int')), ); } } /** * @since Horde 3.2 */ class Horde_Form_Type_phone extends Horde_Form_Type { function isValid(&$var, &$vars, $value, &$message) { if (!strlen(trim($value))) { if ($var->isRequired()) { $message = _("This field is required."); return false; } } elseif (!preg_match('/^\+?[\d()\-\/. ]*$/', $value)) { $message = _("You must enter a valid phone number, digits only with an optional '+' for the international dialing prefix."); return false; } return true; } /** * Return info about field type. */ function about() { return array('name' => _("Phone number")); } } class Horde_Form_Type_cellphone extends Horde_Form_Type_phone { /** * Return info about field type. */ function about() { return array('name' => _("Mobile phone number")); } } class Horde_Form_Type_ipaddress extends Horde_Form_Type_text { function isValid(&$var, &$vars, $value, &$message) { $valid = true; if (strlen(trim($value)) > 0) { $ip = explode('.', $value); $valid = (count($ip) == 4); if ($valid) { foreach ($ip as $part) { if (!is_numeric($part) || $part > 255 || $part < 0) { $valid = false; break; } } } if (!$valid) { $message = _("Please enter a valid IP address."); } } elseif ($var->isRequired()) { $valid = false; $message = _("This field is required."); } return $valid; } /** * Return info about field type. */ function about() { return array('name' => _("IP address")); } } class Horde_Form_Type_longtext extends Horde_Form_Type_text { var $_rows; var $_cols; var $_helper = array(); function init($rows = 8, $cols = 80, $helper = array()) { if (!is_array($helper)) { $helper = array($helper); } $this->_rows = $rows; $this->_cols = $cols; $this->_helper = $helper; } function getRows() { return $this->_rows; } function getCols() { return $this->_cols; } function hasHelper($option = '') { if (empty($option)) { /* No option specified, check if any helpers have been * activated. */ return !empty($this->_helper); } elseif (empty($this->_helper)) { /* No helpers activated at all, return false. */ return false; } else { /* Check if given helper has been activated. */ return in_array($option, $this->_helper); } } /** * Return info about field type. */ function about() { return array( 'name' => _("Long text"), 'params' => array( 'rows' => array('label' => _("Number of rows"), 'type' => 'int'), 'cols' => array('label' => _("Number of columns"), 'type' => 'int'), 'helper' => array('label' => _("Helpers"), 'type' => 'stringarray'))); } } class Horde_Form_Type_countedtext extends Horde_Form_Type_longtext { var $_chars; function init($rows = null, $cols = null, $chars = 1000) { parent::init($rows, $cols); $this->_chars = $chars; } function isValid(&$var, &$vars, $value, &$message) { $valid = true; $length = String::length(trim($value)); if ($var->isRequired() && $length <= 0) { $valid = false; $message = _("This field is required."); } elseif ($length > $this->_chars) { $valid = false; $message = sprintf(ngettext("There are too many characters in this field. You have entered %d character; ", "There are too many characters in this field. You have entered %d characters; ", $length), $length) . sprintf(_("you must enter less than %d."), $this->_chars); } return $valid; } function getChars() { return $this->_chars; } /** * Return info about field type. */ function about() { return array( 'name' => _("Counted text"), 'params' => array( 'rows' => array('label' => _("Number of rows"), 'type' => 'int'), 'cols' => array('label' => _("Number of columns"), 'type' => 'int'), 'chars' => array('label' => _("Number of characters"), 'type' => 'int'))); } } class Horde_Form_Type_address extends Horde_Form_Type_longtext { function parse($address) { $info = array(); $aus_state_regex = '(?:ACT|NSW|NT|QLD|SA|TAS|VIC|WA)'; if (preg_match('/(?s)(.*?)(?-s)\r?\n(?:(.*?)\s+)?((?:A[BL]|B[ABDHLNRST]?|C[ABFHMORTVW]|D[ADEGHLNTY]|E[CHNX]?|F[KY]|G[LUY]?|H[ADGPRSUX]|I[GMPV]|JE|K[ATWY]|L[ADELNSU]?|M[EKL]?|N[EGNPRW]?|O[LX]|P[AEHLOR]|R[GHM]|S[AEGKLMNOPRSTWY]?|T[ADFNQRSW]|UB|W[ACDFNRSV]?|YO|ZE)\d(?:\d|[A-Z])? \d[A-Z]{2})/', $address, $addressParts)) { /* UK postcode detected. */ $info = array('country' => 'uk', 'zip' => $addressParts[3]); if (!empty($addressParts[1])) { $info['street'] = $addressParts[1]; } if (!empty($addressParts[2])) { $info['city'] = $addressParts[2]; } } elseif (preg_match('/\b' . $aus_state_regex . '\b/', $address)) { /* Australian state detected. */ /* Split out the address, line-by-line. */ $addressLines = preg_split('/\r?\n/', $address); $info = array('country' => 'au'); for ($i = 0; $i < count($addressLines); $i++) { /* See if it's the street number & name. */ if (preg_match('/(\d+\s*\/\s*)?(\d+|\d+[a-zA-Z])\s+([a-zA-Z ]*)/', $addressLines[$i], $lineParts)) { $info['street'] = $addressLines[$i]; $info['streetNumber'] = $lineParts[2]; $info['streetName'] = $lineParts[3]; } /* Look for "Suburb, State". */ if (preg_match('/([a-zA-Z ]*),?\s+(' . $aus_state_regex . ')/', $addressLines[$i], $lineParts)) { $info['city'] = $lineParts[1]; $info['state'] = $lineParts[2]; } /* Look for "State <4 digit postcode>". */ if (preg_match('/(' . $aus_state_regex . ')\s+(\d{4})/', $addressLines[$i], $lineParts)) { $info['state'] = $lineParts[1]; $info['zip'] = $lineParts[2]; } } } elseif (preg_match('/(?s)(.*?)(?-s)\r?\n(.*)\s*,\s*(\w+)\.?\s+(\d+|[a-zA-Z]\d[a-zA-Z]\s?\d[a-zA-Z]\d)/', $address, $addressParts)) { /* American/Canadian address style. */ $info = array('country' => 'us'); if (!empty($addressParts[4]) && preg_match('|[a-zA-Z]\d[a-zA-Z]\s?\d[a-zA-Z]\d|', $addressParts[4])) { $info['country'] = 'ca'; } if (!empty($addressParts[1])) { $info['street'] = $addressParts[1]; } if (!empty($addressParts[2])) { $info['city'] = $addressParts[2]; } if (!empty($addressParts[3])) { $info['state'] = $addressParts[3]; } if (!empty($addressParts[4])) { $info['zip'] = $addressParts[4]; } } elseif (preg_match('/(?:(?s)(.*?)(?-s)(?:\r?\n|,\s*))?(?:([A-Z]{1,3})-)?(\d{4,5})\s+(.*)(?:\r?\n(.*))?/i', $address, $addressParts)) { /* European address style. */ $info = array(); if (!empty($addressParts[1])) { $info['street'] = $addressParts[1]; } if (!empty($addressParts[2])) { include 'Horde/NLS/carsigns.php'; $country = array_search(String::upper($addressParts[2]), $carsigns); if ($country) { $info['country'] = $country; } } if (!empty($addressParts[5])) { include 'Horde/NLS/countries.php'; $country = array_search($addressParts[5], $countries); if ($country) { $info['country'] = String::lower($country); } elseif (!isset($info['street'])) { $info['street'] = trim($addressParts[5]); } else { $info['street'] .= "\n" . $addressParts[5]; } } if (!empty($addressParts[3])) { $info['zip'] = $addressParts[3]; } if (!empty($addressParts[4])) { $info['city'] = trim($addressParts[4]); } } return $info; } /** * Return info about field type. */ function about() { return array( 'name' => _("Address"), 'params' => array( 'rows' => array('label' => _("Number of rows"), 'type' => 'int'), 'cols' => array('label' => _("Number of columns"), 'type' => 'int'))); } } class Horde_Form_Type_addresslink extends Horde_Form_Type_address { function isValid(&$var, &$vars, $value, &$message) { return true; } /** * Return info about field type. */ function about() { return array('name' => _("Address Link")); } } /** * @since Horde 3.3 */ class Horde_Form_Type_pgp extends Horde_Form_Type_longtext { /** * Path to the GnuPG binary. * * @var string */ var $_gpg; /** * A temporary directory. * * @var string */ var $_temp; function init($gpg, $temp_dir = null, $rows = null, $cols = null) { $this->_gpg = $gpg; $this->_temp = $temp_dir; parent::init($rows, $cols); } /** * Returns a parameter hash for the Horde_Crypt_pgp constructor. * * @return array A parameter hash. */ function getPGPParams() { return array('program' => $this->_gpg, 'temp' => $this->_temp); } /** * Return info about field type. */ function about() { return array( 'name' => _("PGP Key"), 'params' => array( 'gpg' => array('label' => _("Path to the GnuPG binary"), 'type' => 'string'), 'temp_dir' => array('label' => _("A temporary directory"), 'type' => 'string'), 'rows' => array('label' => _("Number of rows"), 'type' => 'int'), 'cols' => array('label' => _("Number of columns"), 'type' => 'int'))); } } /** * @since Horde 3.3 */ class Horde_Form_Type_smime extends Horde_Form_Type_longtext { /** * A temporary directory. * * @var string */ var $_temp; function init($temp_dir = null, $rows = null, $cols = null) { $this->_temp = $temp_dir; parent::init($rows, $cols); } /** * Returns a parameter hash for the Horde_Crypt_smime constructor. * * @return array A parameter hash. */ function getSMIMEParams() { return array('temp' => $this->_temp); } /** * Return info about field type. */ function about() { return array( 'name' => _("S/MIME Key"), 'params' => array( 'temp_dir' => array('label' => _("A temporary directory"), 'type' => 'string'), 'rows' => array('label' => _("Number of rows"), 'type' => 'int'), 'cols' => array('label' => _("Number of columns"), 'type' => 'int'))); } } /** * @since Horde 3.2 */ class Horde_Form_Type_country extends Horde_Form_Type_enum { function init($prompt = null) { include 'Horde/NLS/countries.php'; parent::init($countries, $prompt); } /** * Return info about field type. */ function about() { return array( 'name' => _("Country drop down list"), 'params' => array( 'prompt' => array('label' => _("Prompt text"), 'type' => 'text'))); } } class Horde_Form_Type_file extends Horde_Form_Type { function isValid(&$var, &$vars, $value, &$message) { if ($var->isRequired()) { $uploaded = Browser::wasFileUploaded($var->getVarName()); if (is_a($uploaded, 'PEAR_Error')) { $message = $uploaded->getMessage(); return false; } } return true; } function getInfo(&$vars, &$var, &$info) { $name = $var->getVarName(); $uploaded = Browser::wasFileUploaded($name); if ($uploaded === true) { $info['name'] = Util::dispelMagicQuotes($_FILES[$name]['name']); $info['type'] = $_FILES[$name]['type']; $info['tmp_name'] = $_FILES[$name]['tmp_name']; $info['file'] = $_FILES[$name]['tmp_name']; $info['error'] = $_FILES[$name]['error']; $info['size'] = $_FILES[$name]['size']; } } /** * Return info about field type. */ function about() { return array('name' => _("File upload")); } } class Horde_Form_Type_image extends Horde_Form_Type { /** * Has a file been uploaded on this form submit? * * @var boolean */ var $_uploaded = null; /** * Show the upload button? * * @var boolean */ var $_show_upload = true; /** * Show the option to upload also original non-modified image? * * @var boolean */ var $_show_keeporig = false; /** * Limit the file size? * * @var integer */ var $_max_filesize = null; /** * Hash containing the previously uploaded image info. * * @var array */ var $_img; /** * A random id that identifies the image information in the session data. * * @var string */ var $_random; function init($show_upload = true, $show_keeporig = false, $max_filesize = null) { $this->_show_upload = $show_upload; $this->_show_keeporig = $show_keeporig; $this->_max_filesize = $max_filesize; } function onSubmit(&$var, &$vars) { /* Get the upload. */ $this->getImage($vars, $var); /* If this was done through the upload button override the submitted * value of the form. */ if ($vars->get('_do_' . $var->getVarName())) { $var->form->setSubmitted(false); if (is_a($this->_uploaded, 'PEAR_Error')) { $this->_img = array('hash' => $this->getRandomId(), 'error' => $this->_uploaded->getMessage()); } } } function isValid(&$var, &$vars, $value, &$message) { /* Get the upload. */ $this->getImage($vars, $var); $field = $vars->get($var->getVarName()); /* The upload generated a PEAR Error. */ if (is_a($this->_uploaded, 'PEAR_Error')) { /* Not required and no image upload attempted. */ if (!$var->isRequired() && empty($field['hash']) && $this->_uploaded->getCode() == UPLOAD_ERR_NO_FILE) { return true; } if (($this->_uploaded->getCode() == UPLOAD_ERR_NO_FILE) && empty($field['hash'])) { /* Nothing uploaded and no older upload. */ $message = _("This field is required."); return false; } elseif (!empty($field['hash'])) { if ($this->_img && isset($this->_img['error'])) { $message = $this->_img['error']; return false; } /* Nothing uploaded but older upload present. */ return true; } else { /* Some other error message. */ $message = $this->_uploaded->getMessage(); return false; } } elseif (empty($this->_img['img']['size'])) { $message = _("The image file size could not be determined or it was 0 bytes. The upload may have been interrupted."); return false; } elseif ($this->_max_filesize && $this->_img['img']['size'] > $this->_max_filesize) { $message = sprintf(_("The image file was larger than the maximum allowed size (%d bytes)."), $this->_max_filesize); return false; } return true; } function getInfo(&$vars, &$var, &$info) { /* Get the upload. */ $this->getImage($vars, $var); /* Get image params stored in the hidden field. */ $value = $var->getValue($vars); $info = $this->_img['img']; if (empty($info['file'])) { unset($info['file']); return; } if ($this->_show_keeporig) { $info['keep_orig'] = !empty($value['keep_orig']); } /* Set the uploaded value (either true or PEAR_Error). */ $info['uploaded'] = &$this->_uploaded; /* If a modified file exists move it over the original. */ if ($this->_show_keeporig && $info['keep_orig']) { /* Requested the saving of original file also. */ $info['orig_file'] = Horde::getTempDir() . '/' . $info['file']; $info['file'] = Horde::getTempDir() . '/mod_' . $info['file']; /* Check if a modified file actually exists. */ if (!file_exists($info['file'])) { $info['file'] = $info['orig_file']; unset($info['orig_file']); } } else { /* Saving of original not required. */ $mod_file = Horde::getTempDir() . '/mod_' . $info['file']; $info['file'] = Horde::getTempDir() . '/' . $info['file']; if (file_exists($mod_file)) { /* Unlink first (has to be done on Windows machines?) */ unlink($info['file']); rename($mod_file, $info['file']); } } } /** * Gets the upload and sets up the upload data array. Either * fetches an upload done with this submit or retries stored * upload info. */ function _getUpload(&$vars, &$var) { /* Don't bother with this function if already called and set * up vars. */ if (!empty($this->_img)) { return true; } /* Check if file has been uploaded. */ $varname = $var->getVarName(); $this->_uploaded = Browser::wasFileUploaded($varname . '[new]'); if ($this->_uploaded === true) { /* A file has been uploaded on this submit. Save to temp dir for * preview work. */ $this->_img['img']['type'] = $this->getUploadedFileType($varname . '[new]'); /* Get the other parts of the upload. */ require_once 'Horde/Array.php'; Horde_Array::getArrayParts($varname . '[new]', $base, $keys); /* Get the temporary file name. */ $keys_path = array_merge(array($base, 'tmp_name'), $keys); $this->_img['img']['file'] = Horde_Array::getElement($_FILES, $keys_path); /* Get the actual file name. */ $keys_path = array_merge(array($base, 'name'), $keys); $this->_img['img']['name'] = Horde_Array::getElement($_FILES, $keys_path); /* Get the file size. */ $keys_path = array_merge(array($base, 'size'), $keys); $this->_img['img']['size'] = Horde_Array::getElement($_FILES, $keys_path); /* Get any existing values for the image upload field. */ $upload = $vars->get($var->getVarName()); if (!empty($upload['hash'])) { $upload['img'] = $_SESSION['horde_form'][$upload['hash']]; unset($_SESSION['horde_form'][$upload['hash']]); } /* Get the temp file if already one uploaded, otherwise create a * new temporary file. */ if (!empty($upload['img']['file'])) { $tmp_file = Horde::getTempDir() . '/' . $upload['img']['file']; } else { $tmp_file = Horde::getTempFile('Horde', false); } /* Move the browser created temp file to the new temp file. */ move_uploaded_file($this->_img['img']['file'], $tmp_file); $this->_img['img']['file'] = basename($tmp_file); } elseif ($this->_uploaded) { /* File has not been uploaded. */ $upload = $vars->get($var->getVarName()); if ($this->_uploaded->getCode() == 4 && !empty($upload['hash']) && isset($_SESSION['horde_form'][$upload['hash']])) { $this->_img['img'] = $_SESSION['horde_form'][$upload['hash']]; unset($_SESSION['horde_form'][$upload['hash']]); if (isset($this->_img['error'])) { $this->_uploaded = PEAR::raiseError($this->_img['error']); } } } if (isset($this->_img['img'])) { $_SESSION['horde_form'][$this->getRandomId()] = $this->_img['img']; } } function getUploadedFileType($field) { /* Get any index on the field name. */ require_once 'Horde/Array.php'; $index = Horde_Array::getArrayParts($field, $base, $keys); if ($index) { /* Index present, fetch the mime type var to check. */ $keys_path = array_merge(array($base, 'type'), $keys); $type = Horde_Array::getElement($_FILES, $keys_path); $keys_path= array_merge(array($base, 'tmp_name'), $keys); $tmp_name = Horde_Array::getElement($_FILES, $keys_path); } else { /* No index, simple set up of vars to check. */ $type = $_FILES[$field]['type']; $tmp_name = $_FILES[$field]['tmp_name']; } if (empty($type) || ($type == 'application/octet-stream')) { /* Type wasn't set on upload, try analising the upload. */ global $conf; require_once 'Horde/MIME/Magic.php'; if (!($type = MIME_Magic::analyzeFile($tmp_name, isset($conf['mime']['magic_db']) ? $conf['mime']['magic_db'] : null))) { if ($index) { /* Get the name value. */ $keys_path = array_merge(array($base, 'name'), $keys); $name = Horde_Array::getElement($_FILES, $keys_path); /* Work out the type from the file name. */ $type = MIME_Magic::filenameToMIME($name); /* Set the type. */ $keys_path = array_merge(array($base, 'type'), $keys); Horde_Array::getElement($_FILES, $keys_path, $type); } else { /* Work out the type from the file name. */ $type = MIME_Magic::filenameToMIME($_FILES[$field]['name']); /* Set the type. */ $_FILES[$field]['type'] = MIME_Magic::filenameToMIME($_FILES[$field]['name']); } } } return $type; } /** * Returns the current image information. * * @return array The current image hash. */ function getImage($vars, $var) { $this->_getUpload($vars, $var); if (!isset($this->_img)) { $image = $vars->get($var->getVarName()); if ($image) { $this->loadImageData($image); if (isset($image['img'])) { $this->_img = $image; $_SESSION['horde_form'][$this->getRandomId()] = $this->_img['img']; } } } return $this->_img; } /** * Loads any existing image data into the image field. Requires that the * array $image passed to it contains the structure: * $image['load']['file'] - the filename of the image; * $image['load']['data'] - the raw image data. * * @param array $image The image array. */ function loadImageData(&$image) { /* No existing image data to load. */ if (!isset($image['load'])) { return; } /* Save the data to the temp dir. */ $tmp_file = Horde::getTempDir() . '/' . $image['load']['file']; if ($fd = fopen($tmp_file, 'w')) { fwrite($fd, $image['load']['data']); fclose($fd); } $image['img'] = array('file' => $image['load']['file']); unset($image['load']); } function getRandomId() { if (!isset($this->_random)) { $this->_random = uniqid(mt_rand()); } return $this->_random; } /** * Return info about field type. */ function about() { return array( 'name' => _("Image upload"), 'params' => array( 'show_upload' => array('label' => _("Show upload?"), 'type' => 'boolean'), 'show_keeporig' => array('label' => _("Show option to keep original?"), 'type' => 'boolean'), 'max_filesize' => array('label' => _("Maximum file size in bytes"), 'type' => 'int'))); } } class Horde_Form_Type_boolean extends Horde_Form_Type { function isValid(&$var, &$vars, $value, &$message) { return true; } function getInfo(&$vars, &$var, &$info) { $info = String::lower($vars->get($var->getVarName())) == 'on'; } /** * Return info about field type. */ function about() { return array('name' => _("True or false")); } } class Horde_Form_Type_link extends Horde_Form_Type { /** * List of hashes containing link parameters. Possible keys: 'url', 'text', * 'target', 'onclick', 'title', 'accesskey'. * * @var array */ var $values; function init($values) { $this->values = $values; } function isValid(&$var, &$vars, $value, &$message) { return true; } /** * Return info about field type. */ function about() { return array( 'name' => _("Link"), 'params' => array( 'url' => array( 'label' => _("Link URL"), 'type' => 'text'), 'text' => array( 'label' => _("Link text"), 'type' => 'text'), 'target' => array( 'label' => _("Link target"), 'type' => 'text'), 'onclick' => array( 'label' => _("Onclick event"), 'type' => 'text'), 'title' => array( 'label' => _("Link title attribute"), 'type' => 'text'), 'accesskey' => array( 'label' => _("Link access key"), 'type' => 'text'))); } } class Horde_Form_Type_email extends Horde_Form_Type { /** * Allow multiple addresses? * * @var boolean */ var $_allow_multi = false; /** * Protect address from spammers? * * @var boolean */ var $_strip_domain = false; /** * Link the email address to the compose page when displaying? * * @var boolean */ var $_link_compose = false; /** * Whether to check the domain's SMTP server whether the address exists. * * @var boolean */ var $_check_smtp = false; /** * The name to use when linking to the compose page * * @var boolean */ var $_link_name; /** * A string containing valid delimiters (default is just comma). * * @var string */ var $_delimiters = ','; /** * @param boolean $allow_multi Allow multiple addresses? * @param boolean $strip_domain Protect address from spammers? * @param boolean $link_compose Link the email address to the compose page * when displaying? * @param string $link_name The name to use when linking to the compose page. * @param string $delimiters Character to split multiple addresses with. */ function init($allow_multi = false, $strip_domain = false, $link_compose = false, $link_name = null, $delimiters = ',') { $this->_allow_multi = $allow_multi; $this->_strip_domain = $strip_domain; $this->_link_compose = $link_compose; $this->_link_name = $link_name; $this->_delimiters = $delimiters; } /** */ function isValid(&$var, &$vars, $value, &$message) { // Split into individual addresses. $emails = $this->splitEmailAddresses($value); // Check for too many. if (!$this->_allow_multi && count($emails) > 1) { $message = _("Only one email address is allowed."); return false; } // Check for all valid and at least one non-empty. $nonEmpty = 0; foreach ($emails as $email) { if (!strlen($email)) { continue; } if (!$this->validateEmailAddress($email)) { $message = sprintf(_("\"%s\" is not a valid email address."), $email); return false; } ++$nonEmpty; } if (!$nonEmpty && $var->isRequired()) { if ($this->_allow_multi) { $message = _("You must enter at least one email address."); } else { $message = _("You must enter an email address."); } return false; } return true; } /** * 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. * * @return array The exploded string in an array. */ function splitEmailAddresses($string) { $quotes = array('"', "'"); $emails = array(); $pos = 0; $in_quote = null; $in_group = false; $prev = null; if (!strlen($string)) { return array(); } $char = $string[0]; if (in_array($char, $quotes)) { $in_quote = $char; } elseif ($char == ':') { $in_group = true; } elseif (strpos($this->_delimiters, $char) !== false) { $emails[] = ''; $pos = 1; } for ($i = 1, $iMax = strlen($string); $i < $iMax; ++$i) { $char = $string[$i]; if (in_array($char, $quotes)) { if ($prev !== '\\') { if ($in_quote === $char) { $in_quote = null; } elseif (is_null($in_quote)) { $in_quote = $char; } } } elseif ($in_group) { if ($char == ';') { $emails[] = substr($string, $pos, $i - $pos + 1); $pos = $i + 1; $in_group = false; } } elseif ($char == ':') { $in_group = true; } elseif (strpos($this->_delimiters, $char) !== false && $prev !== '\\' && is_null($in_quote)) { $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; } /** * RFC(2)822 Email Parser. * * By Cal Henderson * This code is licensed under a Creative Commons Attribution-ShareAlike 2.5 License * http://creativecommons.org/licenses/by-sa/2.5/ * * http://code.iamcal.com/php/rfc822/ * * http://iamcal.com/publish/articles/php/parsing_email * * Revision 4 * * @param string $email An individual email address to validate. * * @return boolean */ function validateEmailAddress($email) { static $comment_regexp, $email_regexp; if ($comment_regexp === null) { $this->_defineValidationRegexps($comment_regexp, $email_regexp); } // We need to strip comments first (repeat until we can't find // any more). while (true) { $new = preg_replace("!$comment_regexp!", '', $email); if (strlen($new) == strlen($email)){ break; } $email = $new; } // Now match what's left. $result = (bool)preg_match("!^$email_regexp$!", $email); if ($result && $this->_check_smtp) { $result = $this->validateEmailAddressSmtp($email); } return $result; } /** * Attempt partial delivery of mail to an address to validate it. * * @param string $email An individual email address to validate. * * @return boolean */ function validateEmailAddressSmtp($email) { list(, $maildomain) = explode('@', $email, 2); // Try to get the real mailserver from MX records. if (function_exists('getmxrr') && @getmxrr($maildomain, $mxhosts, $mxpriorities)) { // MX record found. array_multisort($mxpriorities, $mxhosts); $mailhost = $mxhosts[0]; } else { // No MX record found, try the root domain as the mail // server. $mailhost = $maildomain; } $fp = @fsockopen($mailhost, 25, $errno, $errstr, 5); if (!$fp) { return false; } // Read initial response. fgets($fp, 4096); // HELO fputs($fp, "HELO $mailhost\r\n"); fgets($fp, 4096); // MAIL FROM fputs($fp, "MAIL FROM: \r\n"); fgets($fp, 4096); // RCPT TO - gets the result we want. fputs($fp, "RCPT TO: <$email>\r\n"); $result = trim(fgets($fp, 4096)); // QUIT fputs($fp, "QUIT\r\n"); fgets($fp, 4096); fclose($fp); return substr($result, 0, 1) == '2'; } /** * Return info about field type. */ function about() { return array( 'name' => _("Email"), 'params' => array( 'allow_multi' => array( 'label' => _("Allow multiple addresses?"), 'type' => 'boolean'), 'strip_domain' => array( 'label' => _("Protect address from spammers?"), 'type' => 'boolean'), 'link_compose' => array( 'label' => _("Link the email address to the compose page when displaying?"), 'type' => 'boolean'), 'link_name' => array( 'label' => _("The name to use when linking to the compose page"), 'type' => 'text'), 'delimiters' => array( 'label' => _("Character to split multiple addresses with"), 'type' => 'text'), ), ); } /** * RFC(2)822 Email Parser. * * By Cal Henderson * This code is licensed under a Creative Commons Attribution-ShareAlike 2.5 License * http://creativecommons.org/licenses/by-sa/2.5/ * * http://code.iamcal.com/php/rfc822/ * * http://iamcal.com/publish/articles/php/parsing_email * * Revision 4 * * @param string &$comment The regexp for comments. * @param string &$addr_spec The regexp for email addresses. */ function _defineValidationRegexps(&$comment, &$addr_spec) { /** * NO-WS-CTL = %d1-8 / ; US-ASCII control characters * %d11 / ; that do not include the * %d12 / ; carriage return, line feed, * %d14-31 / ; and white space characters * %d127 * ALPHA = %x41-5A / %x61-7A ; A-Z / a-z * DIGIT = %x30-39 */ $no_ws_ctl = "[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]"; $alpha = "[\\x41-\\x5a\\x61-\\x7a]"; $digit = "[\\x30-\\x39]"; $cr = "\\x0d"; $lf = "\\x0a"; $crlf = "($cr$lf)"; /** * obs-char = %d0-9 / %d11 / ; %d0-127 except CR and * %d12 / %d14-127 ; LF * obs-text = *LF *CR *(obs-char *LF *CR) * text = %d1-9 / ; Characters excluding CR and LF * %d11 / * %d12 / * %d14-127 / * obs-text * obs-qp = "\" (%d0-127) * quoted-pair = ("\" text) / obs-qp */ $obs_char = "[\\x00-\\x09\\x0b\\x0c\\x0e-\\x7f]"; $obs_text = "($lf*$cr*($obs_char$lf*$cr*)*)"; $text = "([\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f]|$obs_text)"; $obs_qp = "(\\x5c[\\x00-\\x7f])"; $quoted_pair = "(\\x5c$text|$obs_qp)"; /** * obs-FWS = 1*WSP *(CRLF 1*WSP) * FWS = ([*WSP CRLF] 1*WSP) / ; Folding white space * obs-FWS * ctext = NO-WS-CTL / ; Non white space controls * %d33-39 / ; The rest of the US-ASCII * %d42-91 / ; characters not including "(", * %d93-126 ; ")", or "\" * ccontent = ctext / quoted-pair / comment * comment = "(" *([FWS] ccontent) [FWS] ")" * CFWS = *([FWS] comment) (([FWS] comment) / FWS) * * @note: We translate ccontent only partially to avoid an * infinite loop. Instead, we'll recursively strip comments * before processing the input. */ $wsp = "[\\x20\\x09]"; $obs_fws = "($wsp+($crlf$wsp+)*)"; $fws = "((($wsp*$crlf)?$wsp+)|$obs_fws)"; $ctext = "($no_ws_ctl|[\\x21-\\x27\\x2A-\\x5b\\x5d-\\x7e])"; $ccontent = "($ctext|$quoted_pair)"; $comment = "(\\x28($fws?$ccontent)*$fws?\\x29)"; $cfws = "(($fws?$comment)*($fws?$comment|$fws))"; $cfws = "$fws*"; /** * atext = ALPHA / DIGIT / ; Any character except controls, * "!" / "#" / ; SP, and specials. * "$" / "%" / ; Used for atoms * "&" / "'" / * "*" / "+" / * "-" / "/" / * "=" / "?" / * "^" / "_" / * "`" / "{" / * "|" / "}" / * "~" * atom = [CFWS] 1*atext [CFWS] */ $atext = "($alpha|$digit|[\\x21\\x23-\\x27\\x2a\\x2b\\x2d\\x2e\\x3d\\x3f\\x5e\\x5f\\x60\\x7b-\\x7e])"; $atom = "($cfws?$atext+$cfws?)"; /** * qtext = NO-WS-CTL / ; Non white space controls * %d33 / ; The rest of the US-ASCII * %d35-91 / ; characters not including "\" * %d93-126 ; or the quote character * qcontent = qtext / quoted-pair * quoted-string = [CFWS] * DQUOTE *([FWS] qcontent) [FWS] DQUOTE * [CFWS] * word = atom / quoted-string */ $qtext = "($no_ws_ctl|[\\x21\\x23-\\x5b\\x5d-\\x7e])"; $qcontent = "($qtext|$quoted_pair)"; $quoted_string = "($cfws?\\x22($fws?$qcontent)*$fws?\\x22$cfws?)"; $word = "($atom|$quoted_string)"; /** * obs-local-part = word *("." word) * obs-domain = atom *("." atom) */ $obs_local_part = "($word(\\x2e$word)*)"; $obs_domain = "($atom(\\x2e$atom)*)"; /** * dot-atom-text = 1*atext *("." 1*atext) * dot-atom = [CFWS] dot-atom-text [CFWS] */ $dot_atom_text = "($atext+(\\x2e$atext+)*)"; $dot_atom = "($cfws?$dot_atom_text$cfws?)"; /** * domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS] * dcontent = dtext / quoted-pair * dtext = NO-WS-CTL / ; Non white space controls * * %d33-90 / ; The rest of the US-ASCII * %d94-126 ; characters not including "[", * ; "]", or "\" */ $dtext = "($no_ws_ctl|[\\x21-\\x5a\\x5e-\\x7e])"; $dcontent = "($dtext|$quoted_pair)"; $domain_literal = "($cfws?\\x5b($fws?$dcontent)*$fws?\\x5d$cfws?)"; /** * local-part = dot-atom / quoted-string / obs-local-part * domain = dot-atom / domain-literal / obs-domain * addr-spec = local-part "@" domain */ $local_part = "($dot_atom|$quoted_string|$obs_local_part)"; $domain = "($dot_atom|$domain_literal|$obs_domain)"; $addr_spec = "($local_part\\x40$domain)"; } } class Horde_Form_Type_matrix extends Horde_Form_Type { var $_cols; var $_rows; var $_matrix; var $_new_input; /** * Initializes the variable. * * Example: * * init(array('Column A', 'Column B'), * array(1 => 'Row One', 2 => 'Row 2', 3 => 'Row 3'), * array(array(true, true, false), * array(true, false, true), * array(fasle, true, false)), * array('Row 4', 'Row 5')); * * * @param array $cols A list of column headers. * @param array $rows A hash with row IDs as the keys and row * labels as the values. * @param array $matrix A two dimensional hash with the field * values. * @param boolean|array $new_input If true, a free text field to add a new * row is displayed on the top, a select * box if this parameter is a value. */ function init($cols, $rows = array(), $matrix = array(), $new_input = false) { $this->_cols = $cols; $this->_rows = $rows; $this->_matrix = $matrix; $this->_new_input = $new_input; } function isValid(&$var, &$vars, $value, &$message) { return true; } function getCols() { return $this->_cols; } function getRows() { return $this->_rows; } function getMatrix() { return $this->_matrix; } function getNewInput() { return $this->_new_input; } function getInfo(&$vars, &$var, &$info) { $values = $vars->get($var->getVarName()); if (!empty($values['n']['r']) && isset($values['n']['v'])) { $new_row = $values['n']['r']; $values['r'][$new_row] = $values['n']['v']; unset($values['n']); } $info = (isset($values['r']) ? $values['r'] : array()); } function about() { return array( 'name' => _("Field matrix"), 'params' => array( 'cols' => array('label' => _("Column titles"), 'type' => 'stringarray'))); } } class Horde_Form_Type_emailConfirm extends Horde_Form_Type { function isValid(&$var, &$vars, $value, &$message) { if ($var->isRequired() && empty($value['original'])) { $message = _("This field is required."); return false; } if ($value['original'] != $value['confirm']) { $message = _("Email addresses must match."); return false; } else { require_once 'Horde/MIME.php'; $parsed_email = MIME::parseAddressList($value['original'], false, true); if (is_a($parsed_email, 'PEAR_Error')) { $message = $parsed_email->getMessage(); return false; } if (count($parsed_email) > 1) { $message = _("Only one email address allowed."); return false; } if (empty($parsed_email[0]->mailbox)) { $message = _("You did not enter a valid email address."); return false; } } return true; } /** * Return info about field type. */ function about() { return array('name' => _("Email with confirmation")); } } class Horde_Form_Type_password extends Horde_Form_Type { function isValid(&$var, &$vars, $value, &$message) { $valid = true; if ($var->isRequired()) { $valid = strlen(trim($value)) > 0; if (!$valid) { $message = _("This field is required."); } } return $valid; } /** * Return info about field type. */ function about() { return array('name' => _("Password")); } } class Horde_Form_Type_passwordconfirm extends Horde_Form_Type { function isValid(&$var, &$vars, $value, &$message) { if ($var->isRequired() && empty($value['original'])) { $message = _("This field is required."); return false; } if ($value['original'] != $value['confirm']) { $message = _("Passwords must match."); return false; } return true; } function getInfo(&$vars, &$var, &$info) { $value = $vars->get($var->getVarName()); $info = $value['original']; } /** * Return info about field type. */ function about() { return array('name' => _("Password with confirmation")); } } class Horde_Form_Type_enum extends Horde_Form_Type { var $_values; var $_prompt; function init($values, $prompt = null) { $this->setValues($values); if ($prompt === true) { $this->_prompt = _("-- select --"); } else { $this->_prompt = $prompt; } } function isValid(&$var, &$vars, $value, &$message) { if ($var->isRequired() && $value == '' && !isset($this->_values[$value])) { $message = _("This field is required."); return false; } if (count($this->_values) == 0 || isset($this->_values[$value]) || ($this->_prompt && empty($value))) { return true; } $message = _("Invalid data submitted."); return false; } function getValues() { return $this->_values; } /** * @since Horde 3.2 */ function setValues($values) { $this->_values = $values; } function getPrompt() { return $this->_prompt; } /** * Return info about field type. */ function about() { return array( 'name' => _("Drop down list"), 'params' => array( 'values' => array('label' => _("Values to select from"), 'type' => 'stringarray'), 'prompt' => array('label' => _("Prompt text"), 'type' => 'text'))); } } class Horde_Form_Type_mlenum extends Horde_Form_Type { var $_values; var $_prompts; function init(&$values, $prompts = null) { $this->_values = &$values; if ($prompts === true) { $this->_prompts = array(_("-- select --"), _("-- select --")); } elseif (!is_array($prompts)) { $this->_prompts = array($prompts, $prompts); } else { $this->_prompts = $prompts; } } function onSubmit(&$var, &$vars) { $varname = $var->getVarName(); $value = $vars->get($varname); if ($value['1'] != $value['old']) { $var->form->setSubmitted(false); } } function isValid(&$var, &$vars, $value, &$message) { if ($var->isRequired() && (empty($value['1']) || empty($value['2']))) { $message = _("This field is required."); return false; } if (!count($this->_values) || isset($this->_values[$value['1']]) || (!empty($this->_prompts) && empty($value['1']))) { return true; } $message = _("Invalid data submitted."); return false; } function getValues() { return $this->_values; } function getPrompts() { return $this->_prompts; } function getInfo(&$vars, &$var, &$info) { $info = $vars->get($var->getVarName()); return $info['2']; } /** * Return info about field type. */ function about() { return array( 'name' => _("Multi-level drop down lists"), 'params' => array( 'values' => array('label' => _("Values to select from"), 'type' => 'stringarray'), 'prompt' => array('label' => _("Prompt text"), 'type' => 'text'))); } } class Horde_Form_Type_multienum extends Horde_Form_Type_enum { var $size = 5; function init($values, $size = null) { if (!is_null($size)) { $this->size = (int)$size; } parent::init($values); } function isValid(&$var, &$vars, $value, &$message) { if (is_array($value)) { foreach ($value as $val) { if (!$this->isValid($var, $vars, $val, $message)) { return false; } } return true; } if (empty($value) && ((string)(int)$value !== $value)) { if ($var->isRequired()) { $message = _("This field is required."); return false; } else { return true; } } if (count($this->_values) == 0 || isset($this->_values[$value])) { return true; } $message = _("Invalid data submitted."); return false; } /** * Return info about field type. */ function about() { return array( 'name' => _("Multiple selection"), 'params' => array( 'values' => array('label' => _("Values"), 'type' => 'stringarray'), 'size' => array('label' => _("Size"), 'type' => 'int')) ); } } class Horde_Form_Type_keyval_multienum extends Horde_Form_Type_multienum { function getInfo(&$vars, &$var, &$info) { $value = $vars->get($var->getVarName()); $info = array(); foreach ($value as $key) { $info[$key] = $this->_values[$key]; } } } class Horde_Form_Type_radio extends Horde_Form_Type_enum { /* Entirely implemented by Horde_Form_Type_enum; just a different * view. */ /** * Return info about field type. */ function about() { return array( 'name' => _("Radio selection"), 'params' => array( 'values' => array('label' => _("Values"), 'type' => 'stringarray'))); } } class Horde_Form_Type_set extends Horde_Form_Type { var $_values; var $_checkAll = false; function init($values, $checkAll = false) { $this->_values = $values; $this->_checkAll = $checkAll; } function isValid(&$var, &$vars, $value, &$message) { if (count($this->_values) == 0 || count($value) == 0) { return true; } foreach ($value as $item) { if (!isset($this->_values[$item])) { $error = true; break; } } if (!isset($error)) { return true; } $message = _("Invalid data submitted."); return false; } function getValues() { return $this->_values; } /** * Return info about field type. */ function about() { return array( 'name' => _("Set"), 'params' => array( 'values' => array('label' => _("Values"), 'type' => 'stringarray'))); } } class Horde_Form_Type_date extends Horde_Form_Type { var $_format; function init($format = '%a %d %B') { $this->_format = $format; } function isValid(&$var, &$vars, $value, &$message) { $valid = true; if ($var->isRequired()) { $valid = strlen(trim($value)) > 0; if (!$valid) { $message = sprintf(_("%s is required"), $var->getHumanName()); } } return $valid; } /** * @static * * @param mixed $date The date to calculate the difference from. Can be * either a timestamp integer value, or an array * with date parts: 'day', 'month', 'year'. * * @return string */ function getAgo($date) { if ($date === null) { return ''; } elseif (!is_array($date)) { /* Date is not array, so assume timestamp. Work out the component * parts using date(). */ $date = array('day' => date('j', $date), 'month' => date('n', $date), 'year' => date('Y', $date)); } require_once 'Date/Calc.php'; $diffdays = Date_Calc::dateDiff((int)$date['day'], (int)$date['month'], (int)$date['year'], date('j'), date('n'), date('Y')); /* An error occured. */ if ($diffdays == -1) { return; } $ago = $diffdays * Date_Calc::compareDates((int)$date['day'], (int)$date['month'], (int)$date['year'], date('j'), date('n'), date('Y')); if ($ago < -1) { return sprintf(_(" (%s days ago)"), $diffdays); } elseif ($ago == -1) { return _(" (yesterday)"); } elseif ($ago == 0) { return _(" (today)"); } elseif ($ago == 1) { return _(" (tomorrow)"); } else { return sprintf(_(" (in %s days)"), $diffdays); } } function getFormattedTime($timestamp, $format = null, $showago = true) { if (empty($format)) { $format = $this->_format; } if (!empty($timestamp)) { return strftime($format, $timestamp) . ($showago ? Horde_Form_Type_date::getAgo($timestamp) : ''); } else { return ''; } } /** * Return info about field type. */ function about() { return array('name' => _("Date")); } } class Horde_Form_Type_time extends Horde_Form_Type { function isValid(&$var, &$vars, $value, &$message) { if ($var->isRequired() && empty($value) && ((string)(double)$value !== $value)) { $message = _("This field is required."); return false; } if (empty($value) || preg_match('/^[0-2]?[0-9]:[0-5][0-9]$/', $value)) { return true; } $message = _("This field may only contain numbers and the colon."); return false; } /** * Return info about field type. */ function about() { return array('name' => _("Time")); } } class Horde_Form_Type_hourminutesecond extends Horde_Form_Type { var $_show_seconds; function init($show_seconds = false) { $this->_show_seconds = $show_seconds; } function isValid(&$var, &$vars, $value, &$message) { $time = $vars->get($var->getVarName()); if (!$this->_show_seconds && count($time) && !isset($time['second'])) { $time['second'] = 0; } if (!$this->emptyTimeArray($time) && !$this->checktime($time['hour'], $time['minute'], $time['second'])) { $message = _("Please enter a valid time."); return false; } elseif ($this->emptyTimeArray($time) && $var->isRequired()) { $message = _("This field is required."); return false; } return true; } function checktime($hour, $minute, $second) { if (!isset($hour) || $hour == '' || ($hour < 0 || $hour > 23)) { return false; } if (!isset($minute) || $minute == '' || ($minute < 0 || $minute > 60)) { return false; } if (!isset($second) || $second === '' || ($second < 0 || $second > 60)) { return false; } return true; } /** * Return the time supplied as a Horde_Date object. * * @param string $time_in Date in one of the three formats supported by * Horde_Form and Horde_Date (ISO format * YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS and * UNIX epoch). * * @return Date The time object. */ function getTimeOb($time_in) { require_once 'Horde/Date.php'; if (is_array($time_in)) { if (!$this->emptyTimeArray($time_in)) { $time_in = sprintf('1970-01-01 %02d:%02d:%02d', $time_in['hour'], $time_in['minute'], $this->_show_seconds ? $time_in['second'] : 0); } } return new Horde_Date($time_in); } /** * Return the time supplied split up into an array. * * @param string $time_in Time in one of the three formats supported by * Horde_Form and Horde_Date (ISO format * YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS and * UNIX epoch). * * @return array Array with three elements - hour, minute and seconds. */ function getTimeParts($time_in) { if (is_array($time_in)) { /* This is probably a failed isValid input so just return the * parts as they are. */ return $time_in; } elseif (empty($time_in)) { /* This is just an empty field so return empty parts. */ return array('hour' => '', 'minute' => '', 'second' => ''); } $time = $this->getTimeOb($time_in); return array('hour' => $time->hour, 'minute' => $time->min, 'second' => $time->sec); } function emptyTimeArray($time) { return (is_array($time) && (!isset($time['hour']) || !strlen($time['hour'])) && (!isset($time['minute']) || !strlen($time['minute'])) && (!$this->_show_seconds || !strlen($time['second']))); } /** * Return info about field type. */ function about() { return array( 'name' => _("Time selection"), 'params' => array( 'seconds' => array('label' => _("Show seconds?"), 'type' => 'boolean'))); } } class Horde_Form_Type_monthyear extends Horde_Form_Type { var $_start_year; var $_end_year; function init($start_year = null, $end_year = null) { if (empty($start_year)) { $start_year = 1920; } if (empty($end_year)) { $end_year = date('Y'); } $this->_start_year = $start_year; $this->_end_year = $end_year; } function isValid(&$var, &$vars, $value, &$message) { if (!$var->isRequired()) { return true; } if (!$vars->get($this->getMonthVar($var)) || !$vars->get($this->getYearVar($var))) { $message = _("Please enter a month and a year."); return false; } return true; } function getMonthVar($var) { return $var->getVarName() . '[month]'; } function getYearVar($var) { return $var->getVarName() . '[year]'; } /** * Return info about field type. */ function about() { return array('name' => _("Month and year"), 'params' => array( 'start_year' => array('label' => _("Start year"), 'type' => 'int'), 'end_year' => array('label' => _("End year"), 'type' => 'int'))); } } class Horde_Form_Type_monthdayyear extends Horde_Form_Type { var $_start_year; var $_end_year; var $_picker; var $_format_in = null; var $_format_out = '%x'; /** * Return the date supplied as a Horde_Date object. * * @param integer $start_year The first available year for input. * @param integer $end_year The last available year for input. * @param boolean $picker Do we show the DHTML calendar? * @param integer $format_in The format to use when sending the date * for storage. Defaults to Unix epoch. * Similar to the strftime() function. * @param integer $format_out The format to use when displaying the * date. Similar to the strftime() function. */ function init($start_year = '', $end_year = '', $picker = true, $format_in = null, $format_out = '%x') { if (empty($start_year)) { $start_year = date('Y'); } if (empty($end_year)) { $end_year = date('Y') + 10; } $this->_start_year = $start_year; $this->_end_year = $end_year; $this->_picker = $picker; $this->_format_in = $format_in; $this->_format_out = $format_out; } function isValid(&$var, &$vars, $value, &$message) { $date = $vars->get($var->getVarName()); $empty = $this->emptyDateArray($date); if ($empty == 1 && $var->isRequired()) { $message = _("This field is required."); return false; } elseif ($empty == 0 && !checkdate($date['month'], $date['day'], $date['year'])) { $message = _("Please enter a valid date, check the number of days in the month."); return false; } elseif ($empty == -1) { $message = _("Select all date components."); return false; } return true; } /** * Determine if the provided date value is completely empty, partially empty * or non-empty. * * @param mixed $date String or date part array representation of date. * * @return integer 0 for non-empty, 1 for completely empty or -1 for * partially empty. */ function emptyDateArray($date) { if (!is_array($date)) { return (int)empty($date); } $empty = 0; /* Check each date array component. */ foreach (array('day', 'month', 'year') as $key) { if (empty($date[$key])) { $empty++; } } /* Check state of empty. */ if ($empty == 0) { /* If no empty parts return 0. */ return 0; } elseif ($empty == 3) { /* If all empty parts return 1. */ return 1; } else { /* If some empty parts return -1. */ return -1; } } /** * Return the date supplied split up into an array. * * @param string $date_in Date in one of the three formats supported by * Horde_Form and Horde_Date (ISO format * YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS * and UNIX epoch) plus the fourth YYYY-MM-DD. * * @return array Array with three elements - year, month and day. */ function getDateParts($date_in) { if (is_array($date_in)) { /* This is probably a failed isValid input so just return * the parts as they are. */ return $date_in; } elseif (empty($date_in)) { /* This is just an empty field so return empty parts. */ return array('year' => '', 'month' => '', 'day' => ''); } $date = $this->getDateOb($date_in); return array('year' => $date->year, 'month' => $date->month, 'day' => $date->mday); } /** * Return the date supplied as a Horde_Date object. * * @param string $date_in Date in one of the three formats supported by * Horde_Form and Horde_Date (ISO format * YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS * and UNIX epoch) plus the fourth YYYY-MM-DD. * * @return Date The date object. */ function getDateOb($date_in) { require_once 'Horde/Date.php'; if (is_array($date_in)) { /* If passed an array change it to the ISO format. */ if ($this->emptyDateArray($date_in) == 0) { $date_in = sprintf('%04d-%02d-%02d 00:00:00', $date_in['year'], $date_in['month'], $date_in['day']); } } elseif (preg_match('/^\d{4}-?\d{2}-?\d{2}$/', $date_in)) { /* Fix the date if it is the shortened ISO. */ $date_in = $date_in . ' 00:00:00'; } return new Horde_Date($date_in); } /** * Return the date supplied as a Horde_Date object. * * @param string $date Either an already set up Horde_Date object or a * string date in one of the three formats supported * by Horde_Form and Horde_Date (ISO format * YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS and * UNIX epoch) plus the fourth YYYY-MM-DD. * * @return string The date formatted according to the $format_out * parameter when setting up the monthdayyear field. */ function formatDate($date) { if (!is_a($date, 'Date')) { $date = $this->getDateOb($date); } return $date->strftime($this->_format_out); } /** * Insert the date input through the form into $info array, in the format * specified by the $format_in parameter when setting up monthdayyear * field. */ function getInfo(&$vars, &$var, &$info) { $info = $this->_validateAndFormat($var->getValue($vars), $var); } /** * Validate/format a date submission. */ function _validateAndFormat($value, &$var) { /* If any component is empty consider it a bad date and return the * default. */ if ($this->emptyDateArray($value) == 1) { return $var->getDefault(); } else { $date = $this->getDateOb($value); if ($this->_format_in === null) { return $date->timestamp(); } else { return $date->strftime($this->_format_in); } } } /** * Return info about field type. */ function about() { return array( 'name' => _("Date selection"), 'params' => array( 'start_year' => array('label' => _("Start year"), 'type' => 'int'), 'end_year' => array('label' => _("End year"), 'type' => 'int'), 'picker' => array('label' => _("Show picker?"), 'type' => 'boolean'), 'format_in' => array('label' => _("Storage format"), 'type' => 'text'), 'format_out' => array('label' => _("Display format"), 'type' => 'text'))); } } /** * @since Horde 3.2 */ class Horde_Form_Type_datetime extends Horde_Form_Type { var $_mdy; var $_hms; var $_show_seconds; /** * Return the date supplied as a Horde_Date object. * * @param integer $start_year The first available year for input. * @param integer $end_year The last available year for input. * @param boolean $picker Do we show the DHTML calendar? * @param integer $format_in The format to use when sending the date * for storage. Defaults to Unix epoch. * Similar to the strftime() function. * @param integer $format_out The format to use when displaying the * date. Similar to the strftime() function. * @param boolean $show_seconds Include a form input for seconds. */ function init($start_year = '', $end_year = '', $picker = true, $format_in = null, $format_out = '%x', $show_seconds = false) { $this->_mdy = new Horde_Form_Type_monthdayyear(); $this->_mdy->init($start_year, $end_year, $picker, $format_in, $format_out); $this->_hms = new Horde_Form_Type_hourminutesecond(); $this->_hms->init($show_seconds); $this->_show_seconds = $show_seconds; } function isValid(&$var, &$vars, $value, &$message) { $date = $vars->get($var->getVarName()); if (!$this->_show_seconds && !isset($date['second'])) { $date['second'] = ''; } $mdy_empty = $this->emptyDateArray($date); $hms_empty = $this->emptyTimeArray($date); $valid = true; /* Require all fields if one field is not empty */ if ($var->isRequired() || $mdy_empty != 1 || !$hms_empty) { $old_required = $var->required; $var->required = true; $mdy_valid = $this->_mdy->isValid($var, $vars, $value, $message); $hms_valid = $this->_hms->isValid($var, $vars, $value, $message); $var->required = $old_required; $valid = $mdy_valid && $hms_valid; if ($mdy_valid && !$hms_valid) { $message = _("You must choose a time."); } elseif ($hms_valid && !$mdy_valid) { $message = _("You must choose a date."); } } return $valid; } function getInfo(&$vars, &$var, &$info) { /* If any component is empty consider it a bad date and return the * default. */ $value = $var->getValue($vars); if ($this->emptyDateArray($value) == 1 || $this->emptyTimeArray($value)) { $info = $var->getDefault(); return; } $date = $this->getDateOb($value); $time = $this->getTimeOb($value); $date->hour = $time->hour; $date->min = $time->min; $date->sec = $time->sec; if ($this->getProperty('format_in') === null) { $info = $date->timestamp(); } else { $info = $date->strftime($this->getProperty('format_in')); } } function getProperty($property) { if ($property == 'show_seconds') { return $this->_hms->getProperty($property); } else { return $this->_mdy->getProperty($property); } } function setProperty($property, $value) { if ($property == 'show_seconds') { $this->_hms->setProperty($property, $value); } else { $this->_mdy->setProperty($property, $value); } } function checktime($hour, $minute, $second) { return $this->_hms->checktime($hour, $minute, $second); } function getTimeOb($time_in) { return $this->_hms->getTimeOb($time_in); } function getTimeParts($time_in) { return $this->_hms->getTimeParts($time_in); } function emptyTimeArray($time) { return $this->_hms->emptyTimeArray($time); } function emptyDateArray($date) { return $this->_mdy->emptyDateArray($date); } function getDateParts($date_in) { return $this->_mdy->getDateParts($date_in); } function getDateOb($date_in) { return $this->_mdy->getDateOb($date_in); } function formatDate($date) { if ($this->_mdy->emptyDateArray($date)) { return ''; } return $this->_mdy->formatDate($date); } function about() { return array( 'name' => _("Date and time selection"), 'params' => array( 'start_year' => array('label' => _("Start year"), 'type' => 'int'), 'end_year' => array('label' => _("End year"), 'type' => 'int'), 'picker' => array('label' => _("Show picker?"), 'type' => 'boolean'), 'format_in' => array('label' => _("Storage format"), 'type' => 'text'), 'format_out' => array('label' => _("Display format"), 'type' => 'text'), 'seconds' => array('label' => _("Show seconds?"), 'type' => 'boolean'))); } } class Horde_Form_Type_colorpicker extends Horde_Form_Type { function isValid(&$var, &$vars, $value, &$message) { if ($var->isRequired() && empty($value)) { $message = _("This field is required."); return false; } if (empty($value) || preg_match('/^#([0-9a-z]){6}$/i', $value)) { return true; } $message = _("This field must contain a color code in the RGB Hex format, for example '#1234af'."); return false; } /** * Return info about field type. */ function about() { return array('name' => _("Colour selection")); } } class Horde_Form_Type_sound extends Horde_Form_Type { var $_sounds = array(); function init() { foreach (glob($GLOBALS['registry']->get('themesfs', 'horde') . '/sounds/*.wav') as $sound) { $this->_sounds[] = basename($sound); } } function getSounds() { return $this->_sounds; } function isValid(&$var, &$vars, $value, &$message) { if ($var->isRequired() && empty($value)) { $message = _("This field is required."); return false; } if (empty($value) || in_array($value, $this->_sounds)) { return true; } $message = _("Please choose a sound."); return false; } /** * Return info about field type. */ function about() { return array('name' => _("Sound selection")); } } class Horde_Form_Type_sorter extends Horde_Form_Type { var $_instance; var $_values; var $_size; var $_header; function init($values, $size = 8, $header = '') { static $horde_sorter_instance = 0; /* Get the next progressive instance count for the horde * sorter so that multiple sorters can be used on one page. */ $horde_sorter_instance++; $this->_instance = 'horde_sorter_' . $horde_sorter_instance; $this->_values = $values; $this->_size = $size; $this->_header = $header; } function isValid(&$var, &$vars, $value, &$message) { return true; } function getValues() { return $this->_values; } function getSize() { return $this->_size; } function getHeader() { if (!empty($this->_header)) { return $this->_header; } return ''; } function getOptions($keys = null) { $html = ''; if ($this->_header) { $html .= ''; } if (empty($keys)) { $keys = array_keys($this->_values); } else { $keys = explode("\t", $keys['array']); } foreach ($keys as $sl_key) { $html .= ''; } return $html; } function getInfo(&$vars, &$var, &$info) { $value = $vars->get($var->getVarName()); $info = explode("\t", $value['array']); } /** * Return info about field type. */ function about() { return array( 'name' => _("Sort order selection"), 'params' => array( 'values' => array('label' => _("Values"), 'type' => 'stringarray'), 'size' => array('label' => _("Size"), 'type' => 'int'), 'header' => array('label' => _("Header"), 'type' => 'text'))); } } class Horde_Form_Type_selectfiles extends Horde_Form_Type { /** * The text to use in the link. * * @var string */ var $_link_text; /** * The style to use for the link. * * @var string */ var $_link_style; /** * Create the link with an icon instead of text? * * @var boolean */ var $_icon; /** * Contains gollem selectfile selectionID * * @var string */ var $_selectid; function init($selectid, $link_text = null, $link_style = '', $icon = false) { $this->_selectid = $selectid; if (is_null($link_text)) { $link_text = _("Select Files"); } $this->_link_text = $link_text; $this->_link_style = $link_style; $this->_icon = $icon; } function isValid(&$var, &$vars, $value, &$message) { return true; } function getInfo(&$var, &$vars, &$info) { $value = $vars->getValue($var); $info = $GLOBALS['registry']->call('files/selectlistResults', array($value)); } function about() { return array( 'name' => _("File selection"), 'params' => array( 'selectid' => array('label' => _("Id"), 'type' => 'text'), 'link_text' => array('label' => _("Link text"), 'type' => 'text'), 'link_style' => array('label' => _("Link style"), 'type' => 'text'), 'icon' => array('label' => _("Show icon?"), 'type' => 'boolean'))); } } class Horde_Form_Type_assign extends Horde_Form_Type { var $_leftValues; var $_rightValues; var $_leftHeader; var $_rightHeader; var $_size; var $_width; function init($leftValues, $rightValues, $leftHeader = '', $rightHeader = '', $size = 8, $width = '200px') { $this->_leftValues = $leftValues; $this->_rightValues = $rightValues; $this->_leftHeader = $leftHeader; $this->_rightHeader = $rightHeader; $this->_size = $size; $this->_width = $width; } function isValid(&$var, &$vars, $value, &$message) { return true; } function getValues($side) { return $side ? $this->_rightValues : $this->_leftValues; } function setValues($side, $values) { if ($side) { $this->_rightValues = $values; } else { $this->_leftValues = $values; } } function getHeader($side) { return $side ? $this->_rightHeader : $this->_leftHeader; } function getSize() { return $this->_size; } function getWidth() { return $this->_width; } function getOptions($side, $formname, $varname) { $html = ''; $headers = false; if ($side) { $values = $this->_rightValues; if (!empty($this->_rightHeader)) { $values = array('' => $this->_rightHeader) + $values; $headers = true; } } else { $values = $this->_leftValues; if (!empty($this->_leftHeader)) { $values = array('' => $this->_leftHeader) + $values; $headers = true; } } foreach ($values as $key => $val) { $html .= '