.
- *
- * @param array unicode code points representing a string
- * @return string utf8 string of characters
- * @return boolean FALSE if a code point cannot be found
- */
- public static function from_unicode($arr)
- {
- if ( ! isset(self::$called[__FUNCTION__]))
- {
- require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
-
- // Function has been called
- self::$called[__FUNCTION__] = TRUE;
- }
-
- return _from_unicode($arr);
- }
-
-} // End utf8
\ No newline at end of file
diff --git a/system/core/utf8/from_unicode.php b/system/core/utf8/from_unicode.php
deleted file mode 100644
index 66c6742d..00000000
--- a/system/core/utf8/from_unicode.php
+++ /dev/null
@@ -1,68 +0,0 @@
-= 0) AND ($arr[$k] <= 0x007f))
- {
- echo chr($arr[$k]);
- }
- // 2 byte sequence
- elseif ($arr[$k] <= 0x07ff)
- {
- echo chr(0xc0 | ($arr[$k] >> 6));
- echo chr(0x80 | ($arr[$k] & 0x003f));
- }
- // Byte order mark (skip)
- elseif ($arr[$k] == 0xFEFF)
- {
- // nop -- zap the BOM
- }
- // Test for illegal surrogates
- elseif ($arr[$k] >= 0xD800 AND $arr[$k] <= 0xDFFF)
- {
- // Found a surrogate
- trigger_error('utf8::from_unicode: Illegal surrogate at index: '.$k.', value: '.$arr[$k], E_USER_WARNING);
- return FALSE;
- }
- // 3 byte sequence
- elseif ($arr[$k] <= 0xffff)
- {
- echo chr(0xe0 | ($arr[$k] >> 12));
- echo chr(0x80 | (($arr[$k] >> 6) & 0x003f));
- echo chr(0x80 | ($arr[$k] & 0x003f));
- }
- // 4 byte sequence
- elseif ($arr[$k] <= 0x10ffff)
- {
- echo chr(0xf0 | ($arr[$k] >> 18));
- echo chr(0x80 | (($arr[$k] >> 12) & 0x3f));
- echo chr(0x80 | (($arr[$k] >> 6) & 0x3f));
- echo chr(0x80 | ($arr[$k] & 0x3f));
- }
- // Out of range
- else
- {
- trigger_error('utf8::from_unicode: Codepoint out of Unicode range at index: '.$k.', value: '.$arr[$k], E_USER_WARNING);
- return FALSE;
- }
- }
-
- $result = ob_get_contents();
- ob_end_clean();
- return $result;
-}
diff --git a/system/core/utf8/ltrim.php b/system/core/utf8/ltrim.php
deleted file mode 100644
index 556fe07f..00000000
--- a/system/core/utf8/ltrim.php
+++ /dev/null
@@ -1,22 +0,0 @@
-= 0 AND $ord0 <= 127)
- {
- return $ord0;
- }
-
- if ( ! isset($chr[1]))
- {
- trigger_error('Short sequence - at least 2 bytes expected, only 1 seen', E_USER_WARNING);
- return FALSE;
- }
-
- $ord1 = ord($chr[1]);
-
- if ($ord0 >= 192 AND $ord0 <= 223)
- {
- return ($ord0 - 192) * 64 + ($ord1 - 128);
- }
-
- if ( ! isset($chr[2]))
- {
- trigger_error('Short sequence - at least 3 bytes expected, only 2 seen', E_USER_WARNING);
- return FALSE;
- }
-
- $ord2 = ord($chr[2]);
-
- if ($ord0 >= 224 AND $ord0 <= 239)
- {
- return ($ord0 - 224) * 4096 + ($ord1 - 128) * 64 + ($ord2 - 128);
- }
-
- if ( ! isset($chr[3]))
- {
- trigger_error('Short sequence - at least 4 bytes expected, only 3 seen', E_USER_WARNING);
- return FALSE;
- }
-
- $ord3 = ord($chr[3]);
-
- if ($ord0 >= 240 AND $ord0 <= 247)
- {
- return ($ord0 - 240) * 262144 + ($ord1 - 128) * 4096 + ($ord2-128) * 64 + ($ord3 - 128);
- }
-
- if ( ! isset($chr[4]))
- {
- trigger_error('Short sequence - at least 5 bytes expected, only 4 seen', E_USER_WARNING);
- return FALSE;
- }
-
- $ord4 = ord($chr[4]);
-
- if ($ord0 >= 248 AND $ord0 <= 251)
- {
- return ($ord0 - 248) * 16777216 + ($ord1-128) * 262144 + ($ord2 - 128) * 4096 + ($ord3 - 128) * 64 + ($ord4 - 128);
- }
-
- if ( ! isset($chr[5]))
- {
- trigger_error('Short sequence - at least 6 bytes expected, only 5 seen', E_USER_WARNING);
- return FALSE;
- }
-
- if ($ord0 >= 252 AND $ord0 <= 253)
- {
- return ($ord0 - 252) * 1073741824 + ($ord1 - 128) * 16777216 + ($ord2 - 128) * 262144 + ($ord3 - 128) * 4096 + ($ord4 - 128) * 64 + (ord($chr[5]) - 128);
- }
-
- if ($ord0 >= 254 AND $ord0 <= 255)
- {
- trigger_error('Invalid UTF-8 with surrogate ordinal '.$ord0, E_USER_WARNING);
- return FALSE;
- }
-}
\ No newline at end of file
diff --git a/system/core/utf8/rtrim.php b/system/core/utf8/rtrim.php
deleted file mode 100644
index efa0e19d..00000000
--- a/system/core/utf8/rtrim.php
+++ /dev/null
@@ -1,22 +0,0 @@
- $val)
- {
- $str[$key] = utf8::str_ireplace($search, $replace, $val, $count);
- }
- return $str;
- }
-
- if (is_array($search))
- {
- $keys = array_keys($search);
-
- foreach ($keys as $k)
- {
- if (is_array($replace))
- {
- if (array_key_exists($k, $replace))
- {
- $str = utf8::str_ireplace($search[$k], $replace[$k], $str, $count);
- }
- else
- {
- $str = utf8::str_ireplace($search[$k], '', $str, $count);
- }
- }
- else
- {
- $str = utf8::str_ireplace($search[$k], $replace, $str, $count);
- }
- }
- return $str;
- }
-
- $search = utf8::strtolower($search);
- $str_lower = utf8::strtolower($str);
-
- $total_matched_strlen = 0;
- $i = 0;
-
- while (preg_match('/(.*?)'.preg_quote($search, '/').'/s', $str_lower, $matches))
- {
- $matched_strlen = strlen($matches[0]);
- $str_lower = substr($str_lower, $matched_strlen);
-
- $offset = $total_matched_strlen + strlen($matches[1]) + ($i * (strlen($replace) - 1));
- $str = substr_replace($str, $replace, $offset, strlen($search));
-
- $total_matched_strlen += $matched_strlen;
- $i++;
- }
-
- $count += $i;
- return $str;
-}
diff --git a/system/core/utf8/str_pad.php b/system/core/utf8/str_pad.php
deleted file mode 100644
index aab4ccc7..00000000
--- a/system/core/utf8/str_pad.php
+++ /dev/null
@@ -1,54 +0,0 @@
-0x0061, 0x03A6=>0x03C6, 0x0162=>0x0163, 0x00C5=>0x00E5, 0x0042=>0x0062,
- 0x0139=>0x013A, 0x00C1=>0x00E1, 0x0141=>0x0142, 0x038E=>0x03CD, 0x0100=>0x0101,
- 0x0490=>0x0491, 0x0394=>0x03B4, 0x015A=>0x015B, 0x0044=>0x0064, 0x0393=>0x03B3,
- 0x00D4=>0x00F4, 0x042A=>0x044A, 0x0419=>0x0439, 0x0112=>0x0113, 0x041C=>0x043C,
- 0x015E=>0x015F, 0x0143=>0x0144, 0x00CE=>0x00EE, 0x040E=>0x045E, 0x042F=>0x044F,
- 0x039A=>0x03BA, 0x0154=>0x0155, 0x0049=>0x0069, 0x0053=>0x0073, 0x1E1E=>0x1E1F,
- 0x0134=>0x0135, 0x0427=>0x0447, 0x03A0=>0x03C0, 0x0418=>0x0438, 0x00D3=>0x00F3,
- 0x0420=>0x0440, 0x0404=>0x0454, 0x0415=>0x0435, 0x0429=>0x0449, 0x014A=>0x014B,
- 0x0411=>0x0431, 0x0409=>0x0459, 0x1E02=>0x1E03, 0x00D6=>0x00F6, 0x00D9=>0x00F9,
- 0x004E=>0x006E, 0x0401=>0x0451, 0x03A4=>0x03C4, 0x0423=>0x0443, 0x015C=>0x015D,
- 0x0403=>0x0453, 0x03A8=>0x03C8, 0x0158=>0x0159, 0x0047=>0x0067, 0x00C4=>0x00E4,
- 0x0386=>0x03AC, 0x0389=>0x03AE, 0x0166=>0x0167, 0x039E=>0x03BE, 0x0164=>0x0165,
- 0x0116=>0x0117, 0x0108=>0x0109, 0x0056=>0x0076, 0x00DE=>0x00FE, 0x0156=>0x0157,
- 0x00DA=>0x00FA, 0x1E60=>0x1E61, 0x1E82=>0x1E83, 0x00C2=>0x00E2, 0x0118=>0x0119,
- 0x0145=>0x0146, 0x0050=>0x0070, 0x0150=>0x0151, 0x042E=>0x044E, 0x0128=>0x0129,
- 0x03A7=>0x03C7, 0x013D=>0x013E, 0x0422=>0x0442, 0x005A=>0x007A, 0x0428=>0x0448,
- 0x03A1=>0x03C1, 0x1E80=>0x1E81, 0x016C=>0x016D, 0x00D5=>0x00F5, 0x0055=>0x0075,
- 0x0176=>0x0177, 0x00DC=>0x00FC, 0x1E56=>0x1E57, 0x03A3=>0x03C3, 0x041A=>0x043A,
- 0x004D=>0x006D, 0x016A=>0x016B, 0x0170=>0x0171, 0x0424=>0x0444, 0x00CC=>0x00EC,
- 0x0168=>0x0169, 0x039F=>0x03BF, 0x004B=>0x006B, 0x00D2=>0x00F2, 0x00C0=>0x00E0,
- 0x0414=>0x0434, 0x03A9=>0x03C9, 0x1E6A=>0x1E6B, 0x00C3=>0x00E3, 0x042D=>0x044D,
- 0x0416=>0x0436, 0x01A0=>0x01A1, 0x010C=>0x010D, 0x011C=>0x011D, 0x00D0=>0x00F0,
- 0x013B=>0x013C, 0x040F=>0x045F, 0x040A=>0x045A, 0x00C8=>0x00E8, 0x03A5=>0x03C5,
- 0x0046=>0x0066, 0x00DD=>0x00FD, 0x0043=>0x0063, 0x021A=>0x021B, 0x00CA=>0x00EA,
- 0x0399=>0x03B9, 0x0179=>0x017A, 0x00CF=>0x00EF, 0x01AF=>0x01B0, 0x0045=>0x0065,
- 0x039B=>0x03BB, 0x0398=>0x03B8, 0x039C=>0x03BC, 0x040C=>0x045C, 0x041F=>0x043F,
- 0x042C=>0x044C, 0x00DE=>0x00FE, 0x00D0=>0x00F0, 0x1EF2=>0x1EF3, 0x0048=>0x0068,
- 0x00CB=>0x00EB, 0x0110=>0x0111, 0x0413=>0x0433, 0x012E=>0x012F, 0x00C6=>0x00E6,
- 0x0058=>0x0078, 0x0160=>0x0161, 0x016E=>0x016F, 0x0391=>0x03B1, 0x0407=>0x0457,
- 0x0172=>0x0173, 0x0178=>0x00FF, 0x004F=>0x006F, 0x041B=>0x043B, 0x0395=>0x03B5,
- 0x0425=>0x0445, 0x0120=>0x0121, 0x017D=>0x017E, 0x017B=>0x017C, 0x0396=>0x03B6,
- 0x0392=>0x03B2, 0x0388=>0x03AD, 0x1E84=>0x1E85, 0x0174=>0x0175, 0x0051=>0x0071,
- 0x0417=>0x0437, 0x1E0A=>0x1E0B, 0x0147=>0x0148, 0x0104=>0x0105, 0x0408=>0x0458,
- 0x014C=>0x014D, 0x00CD=>0x00ED, 0x0059=>0x0079, 0x010A=>0x010B, 0x038F=>0x03CE,
- 0x0052=>0x0072, 0x0410=>0x0430, 0x0405=>0x0455, 0x0402=>0x0452, 0x0126=>0x0127,
- 0x0136=>0x0137, 0x012A=>0x012B, 0x038A=>0x03AF, 0x042B=>0x044B, 0x004C=>0x006C,
- 0x0397=>0x03B7, 0x0124=>0x0125, 0x0218=>0x0219, 0x00DB=>0x00FB, 0x011E=>0x011F,
- 0x041E=>0x043E, 0x1E40=>0x1E41, 0x039D=>0x03BD, 0x0106=>0x0107, 0x03AB=>0x03CB,
- 0x0426=>0x0446, 0x00DE=>0x00FE, 0x00C7=>0x00E7, 0x03AA=>0x03CA, 0x0421=>0x0441,
- 0x0412=>0x0432, 0x010E=>0x010F, 0x00D8=>0x00F8, 0x0057=>0x0077, 0x011A=>0x011B,
- 0x0054=>0x0074, 0x004A=>0x006A, 0x040B=>0x045B, 0x0406=>0x0456, 0x0102=>0x0103,
- 0x039B=>0x03BB, 0x00D1=>0x00F1, 0x041D=>0x043D, 0x038C=>0x03CC, 0x00C9=>0x00E9,
- 0x00D0=>0x00F0, 0x0407=>0x0457, 0x0122=>0x0123,
- );
- }
-
- $uni = utf8::to_unicode($str);
-
- if ($uni === FALSE)
- return FALSE;
-
- for ($i = 0, $c = count($uni); $i < $c; $i++)
- {
- if (isset($UTF8_UPPER_TO_LOWER[$uni[$i]]))
- {
- $uni[$i] = $UTF8_UPPER_TO_LOWER[$uni[$i]];
- }
- }
-
- return utf8::from_unicode($uni);
-}
\ No newline at end of file
diff --git a/system/core/utf8/strtoupper.php b/system/core/utf8/strtoupper.php
deleted file mode 100644
index f3ded739..00000000
--- a/system/core/utf8/strtoupper.php
+++ /dev/null
@@ -1,84 +0,0 @@
-0x0041, 0x03C6=>0x03A6, 0x0163=>0x0162, 0x00E5=>0x00C5, 0x0062=>0x0042,
- 0x013A=>0x0139, 0x00E1=>0x00C1, 0x0142=>0x0141, 0x03CD=>0x038E, 0x0101=>0x0100,
- 0x0491=>0x0490, 0x03B4=>0x0394, 0x015B=>0x015A, 0x0064=>0x0044, 0x03B3=>0x0393,
- 0x00F4=>0x00D4, 0x044A=>0x042A, 0x0439=>0x0419, 0x0113=>0x0112, 0x043C=>0x041C,
- 0x015F=>0x015E, 0x0144=>0x0143, 0x00EE=>0x00CE, 0x045E=>0x040E, 0x044F=>0x042F,
- 0x03BA=>0x039A, 0x0155=>0x0154, 0x0069=>0x0049, 0x0073=>0x0053, 0x1E1F=>0x1E1E,
- 0x0135=>0x0134, 0x0447=>0x0427, 0x03C0=>0x03A0, 0x0438=>0x0418, 0x00F3=>0x00D3,
- 0x0440=>0x0420, 0x0454=>0x0404, 0x0435=>0x0415, 0x0449=>0x0429, 0x014B=>0x014A,
- 0x0431=>0x0411, 0x0459=>0x0409, 0x1E03=>0x1E02, 0x00F6=>0x00D6, 0x00F9=>0x00D9,
- 0x006E=>0x004E, 0x0451=>0x0401, 0x03C4=>0x03A4, 0x0443=>0x0423, 0x015D=>0x015C,
- 0x0453=>0x0403, 0x03C8=>0x03A8, 0x0159=>0x0158, 0x0067=>0x0047, 0x00E4=>0x00C4,
- 0x03AC=>0x0386, 0x03AE=>0x0389, 0x0167=>0x0166, 0x03BE=>0x039E, 0x0165=>0x0164,
- 0x0117=>0x0116, 0x0109=>0x0108, 0x0076=>0x0056, 0x00FE=>0x00DE, 0x0157=>0x0156,
- 0x00FA=>0x00DA, 0x1E61=>0x1E60, 0x1E83=>0x1E82, 0x00E2=>0x00C2, 0x0119=>0x0118,
- 0x0146=>0x0145, 0x0070=>0x0050, 0x0151=>0x0150, 0x044E=>0x042E, 0x0129=>0x0128,
- 0x03C7=>0x03A7, 0x013E=>0x013D, 0x0442=>0x0422, 0x007A=>0x005A, 0x0448=>0x0428,
- 0x03C1=>0x03A1, 0x1E81=>0x1E80, 0x016D=>0x016C, 0x00F5=>0x00D5, 0x0075=>0x0055,
- 0x0177=>0x0176, 0x00FC=>0x00DC, 0x1E57=>0x1E56, 0x03C3=>0x03A3, 0x043A=>0x041A,
- 0x006D=>0x004D, 0x016B=>0x016A, 0x0171=>0x0170, 0x0444=>0x0424, 0x00EC=>0x00CC,
- 0x0169=>0x0168, 0x03BF=>0x039F, 0x006B=>0x004B, 0x00F2=>0x00D2, 0x00E0=>0x00C0,
- 0x0434=>0x0414, 0x03C9=>0x03A9, 0x1E6B=>0x1E6A, 0x00E3=>0x00C3, 0x044D=>0x042D,
- 0x0436=>0x0416, 0x01A1=>0x01A0, 0x010D=>0x010C, 0x011D=>0x011C, 0x00F0=>0x00D0,
- 0x013C=>0x013B, 0x045F=>0x040F, 0x045A=>0x040A, 0x00E8=>0x00C8, 0x03C5=>0x03A5,
- 0x0066=>0x0046, 0x00FD=>0x00DD, 0x0063=>0x0043, 0x021B=>0x021A, 0x00EA=>0x00CA,
- 0x03B9=>0x0399, 0x017A=>0x0179, 0x00EF=>0x00CF, 0x01B0=>0x01AF, 0x0065=>0x0045,
- 0x03BB=>0x039B, 0x03B8=>0x0398, 0x03BC=>0x039C, 0x045C=>0x040C, 0x043F=>0x041F,
- 0x044C=>0x042C, 0x00FE=>0x00DE, 0x00F0=>0x00D0, 0x1EF3=>0x1EF2, 0x0068=>0x0048,
- 0x00EB=>0x00CB, 0x0111=>0x0110, 0x0433=>0x0413, 0x012F=>0x012E, 0x00E6=>0x00C6,
- 0x0078=>0x0058, 0x0161=>0x0160, 0x016F=>0x016E, 0x03B1=>0x0391, 0x0457=>0x0407,
- 0x0173=>0x0172, 0x00FF=>0x0178, 0x006F=>0x004F, 0x043B=>0x041B, 0x03B5=>0x0395,
- 0x0445=>0x0425, 0x0121=>0x0120, 0x017E=>0x017D, 0x017C=>0x017B, 0x03B6=>0x0396,
- 0x03B2=>0x0392, 0x03AD=>0x0388, 0x1E85=>0x1E84, 0x0175=>0x0174, 0x0071=>0x0051,
- 0x0437=>0x0417, 0x1E0B=>0x1E0A, 0x0148=>0x0147, 0x0105=>0x0104, 0x0458=>0x0408,
- 0x014D=>0x014C, 0x00ED=>0x00CD, 0x0079=>0x0059, 0x010B=>0x010A, 0x03CE=>0x038F,
- 0x0072=>0x0052, 0x0430=>0x0410, 0x0455=>0x0405, 0x0452=>0x0402, 0x0127=>0x0126,
- 0x0137=>0x0136, 0x012B=>0x012A, 0x03AF=>0x038A, 0x044B=>0x042B, 0x006C=>0x004C,
- 0x03B7=>0x0397, 0x0125=>0x0124, 0x0219=>0x0218, 0x00FB=>0x00DB, 0x011F=>0x011E,
- 0x043E=>0x041E, 0x1E41=>0x1E40, 0x03BD=>0x039D, 0x0107=>0x0106, 0x03CB=>0x03AB,
- 0x0446=>0x0426, 0x00FE=>0x00DE, 0x00E7=>0x00C7, 0x03CA=>0x03AA, 0x0441=>0x0421,
- 0x0432=>0x0412, 0x010F=>0x010E, 0x00F8=>0x00D8, 0x0077=>0x0057, 0x011B=>0x011A,
- 0x0074=>0x0054, 0x006A=>0x004A, 0x045B=>0x040B, 0x0456=>0x0406, 0x0103=>0x0102,
- 0x03BB=>0x039B, 0x00F1=>0x00D1, 0x043D=>0x041D, 0x03CC=>0x038C, 0x00E9=>0x00C9,
- 0x00F0=>0x00D0, 0x0457=>0x0407, 0x0123=>0x0122,
- );
- }
-
- $uni = utf8::to_unicode($str);
-
- if ($uni === FALSE)
- return FALSE;
-
- for ($i = 0, $c = count($uni); $i < $c; $i++)
- {
- if (isset($UTF8_LOWER_TO_UPPER[$uni[$i]]))
- {
- $uni[$i] = $UTF8_LOWER_TO_UPPER[$uni[$i]];
- }
- }
-
- return utf8::from_unicode($uni);
-}
\ No newline at end of file
diff --git a/system/core/utf8/substr.php b/system/core/utf8/substr.php
deleted file mode 100644
index daf66b81..00000000
--- a/system/core/utf8/substr.php
+++ /dev/null
@@ -1,75 +0,0 @@
-= $strlen OR ($length < 0 AND $length <= $offset - $strlen))
- return '';
-
- // Whole string
- if ($offset == 0 AND ($length === NULL OR $length >= $strlen))
- return $str;
-
- // Build regex
- $regex = '^';
-
- // Create an offset expression
- if ($offset > 0)
- {
- // PCRE repeating quantifiers must be less than 65536, so repeat when necessary
- $x = (int) ($offset / 65535);
- $y = (int) ($offset % 65535);
- $regex .= ($x == 0) ? '' : '(?:.{65535}){'.$x.'}';
- $regex .= ($y == 0) ? '' : '.{'.$y.'}';
- }
-
- // Create a length expression
- if ($length === NULL)
- {
- $regex .= '(.*)'; // No length set, grab it all
- }
- // Find length from the left (positive length)
- elseif ($length > 0)
- {
- // Reduce length so that it can't go beyond the end of the string
- $length = min($strlen - $offset, $length);
-
- $x = (int) ($length / 65535);
- $y = (int) ($length % 65535);
- $regex .= '(';
- $regex .= ($x == 0) ? '' : '(?:.{65535}){'.$x.'}';
- $regex .= '.{'.$y.'})';
- }
- // Find length from the right (negative length)
- else
- {
- $x = (int) (-$length / 65535);
- $y = (int) (-$length % 65535);
- $regex .= '(.*)';
- $regex .= ($x == 0) ? '' : '(?:.{65535}){'.$x.'}';
- $regex .= '.{'.$y.'}';
- }
-
- preg_match('/'.$regex.'/us', $str, $matches);
- return $matches[1];
-}
\ No newline at end of file
diff --git a/system/core/utf8/substr_replace.php b/system/core/utf8/substr_replace.php
deleted file mode 100644
index 45e2d2a3..00000000
--- a/system/core/utf8/substr_replace.php
+++ /dev/null
@@ -1,22 +0,0 @@
- 0x10FFFF))
- {
- trigger_error('utf8::to_unicode: Illegal sequence or codepoint in UTF-8 at byte '.$i, E_USER_WARNING);
- return FALSE;
- }
-
- if (0xFEFF != $mUcs4)
- {
- // BOM is legal but we don't want to output it
- $out[] = $mUcs4;
- }
-
- // Initialize UTF-8 cache
- $mState = 0;
- $mUcs4 = 0;
- $mBytes = 1;
- }
- }
- else
- {
- // ((0xC0 & (*in) != 0x80) AND (mState != 0))
- // Incomplete multi-octet sequence
- trigger_error('utf8::to_unicode: Incomplete multi-octet sequence in UTF-8 at byte '.$i, E_USER_WARNING);
- return FALSE;
- }
- }
- }
-
- return $out;
-}
\ No newline at end of file
diff --git a/system/core/utf8/transliterate_to_ascii.php b/system/core/utf8/transliterate_to_ascii.php
deleted file mode 100644
index 07461fbb..00000000
--- a/system/core/utf8/transliterate_to_ascii.php
+++ /dev/null
@@ -1,77 +0,0 @@
- 'a', 'ô' => 'o', 'ď' => 'd', 'ḟ' => 'f', 'ë' => 'e', 'š' => 's', 'ơ' => 'o',
- 'ß' => 'ss', 'ă' => 'a', 'ř' => 'r', 'ț' => 't', 'ň' => 'n', 'ā' => 'a', 'ķ' => 'k',
- 'ŝ' => 's', 'ỳ' => 'y', 'ņ' => 'n', 'ĺ' => 'l', 'ħ' => 'h', 'ṗ' => 'p', 'ó' => 'o',
- 'ú' => 'u', 'ě' => 'e', 'é' => 'e', 'ç' => 'c', 'ẁ' => 'w', 'ċ' => 'c', 'õ' => 'o',
- 'ṡ' => 's', 'ø' => 'o', 'ģ' => 'g', 'ŧ' => 't', 'ș' => 's', 'ė' => 'e', 'ĉ' => 'c',
- 'ś' => 's', 'î' => 'i', 'ű' => 'u', 'ć' => 'c', 'ę' => 'e', 'ŵ' => 'w', 'ṫ' => 't',
- 'ū' => 'u', 'č' => 'c', 'ö' => 'o', 'è' => 'e', 'ŷ' => 'y', 'ą' => 'a', 'ł' => 'l',
- 'ų' => 'u', 'ů' => 'u', 'ş' => 's', 'ğ' => 'g', 'ļ' => 'l', 'ƒ' => 'f', 'ž' => 'z',
- 'ẃ' => 'w', 'ḃ' => 'b', 'å' => 'a', 'ì' => 'i', 'ï' => 'i', 'ḋ' => 'd', 'ť' => 't',
- 'ŗ' => 'r', 'ä' => 'a', 'í' => 'i', 'ŕ' => 'r', 'ê' => 'e', 'ü' => 'u', 'ò' => 'o',
- 'ē' => 'e', 'ñ' => 'n', 'ń' => 'n', 'ĥ' => 'h', 'ĝ' => 'g', 'đ' => 'd', 'ĵ' => 'j',
- 'ÿ' => 'y', 'ũ' => 'u', 'ŭ' => 'u', 'ư' => 'u', 'ţ' => 't', 'ý' => 'y', 'ő' => 'o',
- 'â' => 'a', 'ľ' => 'l', 'ẅ' => 'w', 'ż' => 'z', 'ī' => 'i', 'ã' => 'a', 'ġ' => 'g',
- 'ṁ' => 'm', 'ō' => 'o', 'ĩ' => 'i', 'ù' => 'u', 'į' => 'i', 'ź' => 'z', 'á' => 'a',
- 'û' => 'u', 'þ' => 'th', 'ð' => 'dh', 'æ' => 'ae', 'µ' => 'u', 'ĕ' => 'e',
- );
- }
-
- $str = str_replace(
- array_keys($UTF8_LOWER_ACCENTS),
- array_values($UTF8_LOWER_ACCENTS),
- $str
- );
- }
-
- if ($case >= 0)
- {
- if ($UTF8_UPPER_ACCENTS === NULL)
- {
- $UTF8_UPPER_ACCENTS = array(
- 'À' => 'A', 'Ô' => 'O', 'Ď' => 'D', 'Ḟ' => 'F', 'Ë' => 'E', 'Š' => 'S', 'Ơ' => 'O',
- 'Ă' => 'A', 'Ř' => 'R', 'Ț' => 'T', 'Ň' => 'N', 'Ā' => 'A', 'Ķ' => 'K', 'Ĕ' => 'E',
- 'Ŝ' => 'S', 'Ỳ' => 'Y', 'Ņ' => 'N', 'Ĺ' => 'L', 'Ħ' => 'H', 'Ṗ' => 'P', 'Ó' => 'O',
- 'Ú' => 'U', 'Ě' => 'E', 'É' => 'E', 'Ç' => 'C', 'Ẁ' => 'W', 'Ċ' => 'C', 'Õ' => 'O',
- 'Ṡ' => 'S', 'Ø' => 'O', 'Ģ' => 'G', 'Ŧ' => 'T', 'Ș' => 'S', 'Ė' => 'E', 'Ĉ' => 'C',
- 'Ś' => 'S', 'Î' => 'I', 'Ű' => 'U', 'Ć' => 'C', 'Ę' => 'E', 'Ŵ' => 'W', 'Ṫ' => 'T',
- 'Ū' => 'U', 'Č' => 'C', 'Ö' => 'O', 'È' => 'E', 'Ŷ' => 'Y', 'Ą' => 'A', 'Ł' => 'L',
- 'Ų' => 'U', 'Ů' => 'U', 'Ş' => 'S', 'Ğ' => 'G', 'Ļ' => 'L', 'Ƒ' => 'F', 'Ž' => 'Z',
- 'Ẃ' => 'W', 'Ḃ' => 'B', 'Å' => 'A', 'Ì' => 'I', 'Ï' => 'I', 'Ḋ' => 'D', 'Ť' => 'T',
- 'Ŗ' => 'R', 'Ä' => 'A', 'Í' => 'I', 'Ŕ' => 'R', 'Ê' => 'E', 'Ü' => 'U', 'Ò' => 'O',
- 'Ē' => 'E', 'Ñ' => 'N', 'Ń' => 'N', 'Ĥ' => 'H', 'Ĝ' => 'G', 'Đ' => 'D', 'Ĵ' => 'J',
- 'Ÿ' => 'Y', 'Ũ' => 'U', 'Ŭ' => 'U', 'Ư' => 'U', 'Ţ' => 'T', 'Ý' => 'Y', 'Ő' => 'O',
- 'Â' => 'A', 'Ľ' => 'L', 'Ẅ' => 'W', 'Ż' => 'Z', 'Ī' => 'I', 'Ã' => 'A', 'Ġ' => 'G',
- 'Ṁ' => 'M', 'Ō' => 'O', 'Ĩ' => 'I', 'Ù' => 'U', 'Į' => 'I', 'Ź' => 'Z', 'Á' => 'A',
- 'Û' => 'U', 'Þ' => 'Th', 'Ð' => 'Dh', 'Æ' => 'Ae',
- );
- }
-
- $str = str_replace(
- array_keys($UTF8_UPPER_ACCENTS),
- array_values($UTF8_UPPER_ACCENTS),
- $str
- );
- }
-
- return $str;
-}
\ No newline at end of file
diff --git a/system/core/utf8/trim.php b/system/core/utf8/trim.php
deleted file mode 100644
index 7434102a..00000000
--- a/system/core/utf8/trim.php
+++ /dev/null
@@ -1,17 +0,0 @@
-> 1;
-
- if ($haystack[$mid] < $needle)
- {
- $low = $mid + 1;
- }
- elseif ($haystack[$mid] > $needle)
- {
- $high = $mid - 1;
- }
- else
- {
- return $mid;
- }
- }
-
- return FALSE;
- }
-
-
/**
* Emulates array_merge_recursive, but appends numeric keys and replaces
* associative keys, instead of appending all keys.
@@ -263,27 +231,6 @@ class arr_Core {
return $array1;
}
- /**
- * Fill an array with a range of numbers.
- *
- * @param integer stepping
- * @param integer ending number
- * @return array
- */
- public static function range($step = 10, $max = 100)
- {
- if ($step < 1)
- return array();
-
- $array = array();
- for ($i = $step; $i <= $max; $i += $step)
- {
- $array[$i] = $i;
- }
-
- return $array;
- }
-
/**
* Recursively convert an array to an object.
*
@@ -309,4 +256,20 @@ class arr_Core {
return $object;
}
+ /**
+ * Returns specific key/column from an array of objects.
+ *
+ * @param string|integer $key The key or column number to pluck from each object.
+ * @param array $array The array of objects to pluck from.
+ * @return array
+ */
+ public static function pluck($key, $array)
+ {
+ $result = array();
+ foreach ($array as $i => $object)
+ {
+ $result[$i] = isset($object[$key]) ? $object[$key] : NULL;
+ }
+ return $result;
+ }
} // End arr
diff --git a/system/helpers/cookie.php b/system/helpers/cookie.php
index 901b6d86..8a2e3659 100644
--- a/system/helpers/cookie.php
+++ b/system/helpers/cookie.php
@@ -2,12 +2,12 @@
/**
* Cookie helper class.
*
- * $Id: cookie.php 3769 2008-12-15 00:48:56Z zombor $
+ * $Id: cookie.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class cookie_Core {
@@ -42,8 +42,13 @@ class cookie_Core {
}
}
- // Expiration timestamp
- $expire = ($expire == 0) ? 0 : time() + (int) $expire;
+ if ($expire !== 0)
+ {
+ // The expiration is expected to be a UNIX timestamp
+ $expire += time();
+ }
+
+ $value = cookie::salt($name, $value).'~'.$value;
return setcookie($name, $value, $expire, $path, $domain, $secure, $httponly);
}
@@ -56,9 +61,51 @@ class cookie_Core {
* @param boolean use XSS cleaning on the value
* @return string
*/
- public static function get($name, $default = NULL, $xss_clean = FALSE)
+ public static function get($name = NULL, $default = NULL, $xss_clean = FALSE)
{
- return Input::instance()->cookie($name, $default, $xss_clean);
+ // Return an array of all the cookies if we don't have a name
+ if ($name === NULL)
+ {
+ $cookies = array();
+
+ foreach($_COOKIE AS $key => $value)
+ {
+ $cookies[$key] = cookie::get($key, $default, $xss_clean);
+ }
+ return $cookies;
+ }
+
+ if ( ! isset($_COOKIE[$name]))
+ {
+ return $default;
+ }
+
+ // Get the cookie value
+ $cookie = $_COOKIE[$name];
+
+ // Find the position of the split between salt and contents
+ $split = strlen(cookie::salt($name, NULL));
+
+ if (isset($cookie[$split]) AND $cookie[$split] === '~')
+ {
+ // Separate the salt and the value
+ list ($hash, $value) = explode('~', $cookie, 2);
+
+ if (cookie::salt($name, $value) === $hash)
+ {
+ if ($xss_clean === TRUE AND Kohana::config('core.global_xss_filtering') === FALSE)
+ {
+ return Input::instance()->xss_clean($value);
+ }
+ // Cookie signature is valid
+ return $value;
+ }
+
+ // The cookie signature is invalid, delete it
+ cookie::delete($name);
+ }
+
+ return $default;
}
/**
@@ -71,9 +118,6 @@ class cookie_Core {
*/
public static function delete($name, $path = NULL, $domain = NULL)
{
- if ( ! isset($_COOKIE[$name]))
- return FALSE;
-
// Delete the cookie from globals
unset($_COOKIE[$name]);
@@ -81,4 +125,27 @@ class cookie_Core {
return cookie::set($name, '', -86400, $path, $domain, FALSE, FALSE);
}
+ /**
+ * Generates a salt string for a cookie based on the name and value.
+ *
+ * @param string $name name of cookie
+ * @param string $value value of cookie
+ * @return string sha1 hash
+ */
+ public static function salt($name, $value)
+ {
+ // Determine the user agent
+ $agent = isset($_SERVER['HTTP_USER_AGENT']) ? strtolower($_SERVER['HTTP_USER_AGENT']) : 'unknown';
+
+ // Cookie salt.
+ $salt = Kohana::config('cookie.salt');
+
+ return sha1($agent.$name.$value.$salt);
+ }
+
+ final private function __construct()
+ {
+ // Static class.
+ }
+
} // End cookie
\ No newline at end of file
diff --git a/system/helpers/date.php b/system/helpers/date.php
index 7d5a9ab6..af7e85bd 100644
--- a/system/helpers/date.php
+++ b/system/helpers/date.php
@@ -2,12 +2,12 @@
/**
* Date helper class.
*
- * $Id: date.php 4316 2009-05-04 01:03:54Z kiall $
+ * $Id: date.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class date_Core {
@@ -57,36 +57,28 @@ class date_Core {
* Returns the offset (in seconds) between two time zones.
* @see http://php.net/timezones
*
- * @param string timezone that to find the offset of
+ * @param string timezone to find the offset of
* @param string|boolean timezone used as the baseline
+ * @param string time at which to calculate
* @return integer
*/
- public static function offset($remote, $local = TRUE)
+ public static function offset($remote, $local = TRUE, $when = 'now')
{
- static $offsets;
-
- // Default values
- $remote = (string) $remote;
- $local = ($local === TRUE) ? date_default_timezone_get() : (string) $local;
-
- // Cache key name
- $cache = $remote.$local;
-
- if (empty($offsets[$cache]))
+ if ($local === TRUE)
{
- // Create timezone objects
- $remote = new DateTimeZone($remote);
- $local = new DateTimeZone($local);
+ $local = date_default_timezone_get();
+ }
- // Create date objects from timezones
- $time_there = new DateTime('now', $remote);
- $time_here = new DateTime('now', $local);
+ // Create timezone objects
+ $remote = new DateTimeZone($remote);
+ $local = new DateTimeZone($local);
- // Find the offset
- $offsets[$cache] = $remote->getOffset($time_there) - $local->getOffset($time_here);
- }
+ // Create date objects from timezones
+ $time_there = new DateTime($when, $remote);
+ $time_here = new DateTime($when, $local);
- return $offsets[$cache];
+ // Find the offset
+ return $remote->getOffset($time_there) - $local->getOffset($time_here);
}
/**
diff --git a/system/helpers/db.php b/system/helpers/db.php
new file mode 100644
index 00000000..ce7583b7
--- /dev/null
+++ b/system/helpers/db.php
@@ -0,0 +1,49 @@
+select($columns);
+ }
+
+ public static function insert($table = NULL, $set = NULL)
+ {
+ return db::build()->insert($table, $set);
+ }
+
+ public static function update($table = NULL, $set = NULL, $where = NULL)
+ {
+ return db::build()->update($table, $set, $where);
+ }
+
+ public static function delete($table = NULL, $where = NULL)
+ {
+ return db::build()->delete($table, $where);
+ }
+
+ public static function expr($expression)
+ {
+ return new Database_Expression($expression);
+ }
+
+} // End db
diff --git a/system/helpers/download.php b/system/helpers/download.php
index 49fed42c..58a7ab94 100644
--- a/system/helpers/download.php
+++ b/system/helpers/download.php
@@ -2,104 +2,136 @@
/**
* Download helper class.
*
- * $Id: download.php 3769 2008-12-15 00:48:56Z zombor $
+ * $Id: download.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class download_Core {
/**
- * Force a download of a file to the user's browser. This function is
- * binary-safe and will work with any MIME type that Kohana is aware of.
+ * Send headers necessary to invoke a "Save As" dialog
+ *
+ * @link http://support.microsoft.com/kb/260519
+ * @link http://greenbytes.de/tech/tc2231/
+ *
+ * @param string file name
+ * @return string file name as it was sent
+ */
+ public static function dialog($filename)
+ {
+ $filename = basename($filename);
+
+ header('Content-Disposition: attachment; filename="'.$filename.'"');
+
+ return $filename;
+ }
+
+ /**
+ * Send the contents of a file or a data string with the proper MIME type and exit.
+ *
+ * @uses exit()
+ * @uses Kohana::close_buffers()
*
* @param string a file path or file name
- * @param mixed data to be sent if the filename does not exist
- * @param string suggested filename to display in the download
+ * @param string optional data to send
* @return void
*/
- public static function force($filename = NULL, $data = NULL, $nicename = NULL)
+ public static function send($filename, $data = NULL)
{
- if (empty($filename))
- return FALSE;
-
- if (is_file($filename))
+ if ($data === NULL)
{
- // Get the real path
- $filepath = str_replace('\\', '/', realpath($filename));
+ $filepath = realpath($filename);
- // Set filesize
+ $filename = basename($filepath);
$filesize = filesize($filepath);
-
- // Get filename
- $filename = substr(strrchr('/'.$filepath, '/'), 1);
-
- // Get extension
- $extension = strtolower(substr(strrchr($filepath, '.'), 1));
}
else
{
- // Get filesize
+ $filename = basename($filename);
$filesize = strlen($data);
+ }
- // Make sure the filename does not have directory info
- $filename = substr(strrchr('/'.$filename, '/'), 1);
+ // Retrieve MIME type by extension
+ $mime = Kohana::config('mimes.'.strtolower(substr(strrchr($filename, '.'), 1)));
+ $mime = empty($mime) ? 'application/octet-stream' : $mime[0];
- // Get extension
- $extension = strtolower(substr(strrchr($filename, '.'), 1));
- }
+ // Close output buffers
+ Kohana::close_buffers(FALSE);
+
+ // Clear any output
+ Event::add('system.display', create_function('', 'Kohana::$output = "";'));
- // Get the mime type of the file
- $mime = Kohana::config('mimes.'.$extension);
+ // Send headers
+ header("Content-Type: $mime");
+ header('Content-Length: '.sprintf('%d', $filesize));
+ header('Content-Transfer-Encoding: binary');
- if (empty($mime))
+ // Send data
+ if ($data === NULL)
{
- // Set a default mime if none was found
- $mime = array('application/octet-stream');
+ $handle = fopen($filepath, 'rb');
+
+ fpassthru($handle);
+ fclose($handle);
+ }
+ else
+ {
+ echo $data;
}
- // Generate the server headers
- header('Content-Type: '.$mime[0]);
- header('Content-Disposition: attachment; filename="'.(empty($nicename) ? $filename : $nicename).'"');
- header('Content-Transfer-Encoding: binary');
- header('Content-Length: '.sprintf('%d', $filesize));
+ exit;
+ }
+
+ /**
+ * Force the download of a file by the user's browser by preventing any
+ * caching. Contains a workaround for Internet Explorer.
+ *
+ * @link http://support.microsoft.com/kb/316431
+ * @link http://support.microsoft.com/kb/812935
+ *
+ * @uses download::dialog()
+ * @uses download::send()
+ *
+ * @param string a file path or file name
+ * @param mixed data to be sent if the filename does not exist
+ * @param string suggested filename to display in the download
+ * @return void
+ */
+ public static function force($filename = NULL, $data = NULL, $nicename = NULL)
+ {
+ download::dialog(empty($nicename) ? $filename : $nicename);
- // More caching prevention
- header('Expires: 0');
+ // Prevent caching
+ header('Expires: Thu, 01 Jan 1970 00:00:00 GMT');
- if (Kohana::user_agent('browser') === 'Internet Explorer')
+ if (request::user_agent('browser') === 'Internet Explorer' AND request::user_agent('version') <= '6.0')
{
- // Send IE headers
+ // HTTP 1.0
+ header('Pragma:');
+
+ // HTTP 1.1 with IE extensions
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
- header('Pragma: public');
}
else
{
- // Send normal headers
+ // HTTP 1.0
header('Pragma: no-cache');
- }
- // Clear the output buffer
- Kohana::close_buffers(FALSE);
+ // HTTP 1.1
+ header('Cache-Control: no-cache, max-age=0');
+ }
- if (isset($filepath))
+ if (is_file($filename))
{
- // Open the file
- $handle = fopen($filepath, 'rb');
-
- // Send the file data
- fpassthru($handle);
-
- // Close the file
- fclose($handle);
+ download::send($filename);
}
else
{
- // Send the file data
- echo $data;
+ download::send($filename, $data);
}
}
-} // End download
\ No newline at end of file
+} // End download
diff --git a/system/helpers/email.php b/system/helpers/email.php
deleted file mode 100644
index fb222d0c..00000000
--- a/system/helpers/email.php
+++ /dev/null
@@ -1,181 +0,0 @@
-setUsername($config['options']['username']);
- empty($config['options']['password']) or $connection->setPassword($config['options']['password']);
-
- if ( ! empty($config['options']['auth']))
- {
- // Get the class name and params
- list ($class, $params) = arr::callback_string($config['options']['auth']);
-
- if ($class === 'PopB4Smtp')
- {
- // Load the PopB4Smtp class manually, due to its odd filename
- require Kohana::find_file('vendor', 'swift/Swift/Authenticator/$PopB4Smtp$');
- }
-
- // Prepare the class name for auto-loading
- $class = 'Swift_Authenticator_'.$class;
-
- // Attach the authenticator
- $connection->attachAuthenticator(($params === NULL) ? new $class : new $class($params[0]));
- }
-
- // Set the timeout to 5 seconds
- $connection->setTimeout(empty($config['options']['timeout']) ? 5 : (int) $config['options']['timeout']);
- break;
- case 'sendmail':
- // Create a sendmail connection
- $connection = new Swift_Connection_Sendmail
- (
- empty($config['options']) ? Swift_Connection_Sendmail::AUTO_DETECT : $config['options']
- );
-
- // Set the timeout to 5 seconds
- $connection->setTimeout(5);
- break;
- default:
- // Use the native connection
- $connection = new Swift_Connection_NativeMail($config['options']);
- break;
- }
-
- // Create the SwiftMailer instance
- return email::$mail = new Swift($connection);
- }
-
- /**
- * Send an email message.
- *
- * @param string|array recipient email (and name), or an array of To, Cc, Bcc names
- * @param string|array sender email (and name)
- * @param string message subject
- * @param string message body
- * @param boolean send email as HTML
- * @return integer number of emails sent
- */
- public static function send($to, $from, $subject, $message, $html = FALSE)
- {
- // Connect to SwiftMailer
- (email::$mail === NULL) and email::connect();
-
- // Determine the message type
- $html = ($html === TRUE) ? 'text/html' : 'text/plain';
-
- // Create the message
- $message = new Swift_Message($subject, $message, $html, '8bit', 'utf-8');
-
- if (is_string($to))
- {
- // Single recipient
- $recipients = new Swift_Address($to);
- }
- elseif (is_array($to))
- {
- if (isset($to[0]) AND isset($to[1]))
- {
- // Create To: address set
- $to = array('to' => $to);
- }
-
- // Create a list of recipients
- $recipients = new Swift_RecipientList;
-
- foreach ($to as $method => $set)
- {
- if ( ! in_array($method, array('to', 'cc', 'bcc')))
- {
- // Use To: by default
- $method = 'to';
- }
-
- // Create method name
- $method = 'add'.ucfirst($method);
-
- if (is_array($set))
- {
- // Add a recipient with name
- $recipients->$method($set[0], $set[1]);
- }
- else
- {
- // Add a recipient without name
- $recipients->$method($set);
- }
- }
- }
-
- if (is_string($from))
- {
- // From without a name
- $from = new Swift_Address($from);
- }
- elseif (is_array($from))
- {
- // From with a name
- $from = new Swift_Address($from[0], $from[1]);
- }
-
- return email::$mail->send($message, $recipients, $from);
- }
-
-} // End email
\ No newline at end of file
diff --git a/system/helpers/expires.php b/system/helpers/expires.php
index c43cc0cc..ce0482c8 100644
--- a/system/helpers/expires.php
+++ b/system/helpers/expires.php
@@ -2,54 +2,49 @@
/**
* Controls headers that effect client caching of pages
*
- * $Id: expires.php 4272 2009-04-25 21:47:26Z zombor $
+ * $Id: expires.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class expires_Core {
/**
- * Sets the amount of time before a page expires
+ * Sets the amount of time before content expires
*
- * @param integer Seconds before the page expires
- * @return boolean
+ * @param integer Seconds before the content expires
+ * @return integer Timestamp when the content expires
*/
public static function set($seconds = 60)
{
- if (expires::check_headers())
- {
- $now = $expires = time();
+ $now = time();
+ $expires = $now + $seconds;
- // Set the expiration timestamp
- $expires += $seconds;
+ header('Last-Modified: '.gmdate('D, d M Y H:i:s T', $now));
- // Send headers
- header('Last-Modified: '.gmdate('D, d M Y H:i:s T', $now));
- header('Expires: '.gmdate('D, d M Y H:i:s T', $expires));
- header('Cache-Control: max-age='.$seconds);
+ // HTTP 1.0
+ header('Expires: '.gmdate('D, d M Y H:i:s T', $expires));
- return $expires;
- }
+ // HTTP 1.1
+ header('Cache-Control: max-age='.$seconds);
- return FALSE;
+ return $expires;
}
/**
- * Checks to see if a page should be updated or send Not Modified status
+ * Parses the If-Modified-Since header
*
- * @param integer Seconds added to the modified time received to calculate what should be sent
- * @return bool FALSE when the request needs to be updated
+ * @return integer|boolean Timestamp or FALSE when header is lacking or malformed
*/
- public static function check($seconds = 60)
+ public static function get()
{
- if ( ! empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) AND expires::check_headers())
+ if ( ! empty($_SERVER['HTTP_IF_MODIFIED_SINCE']))
{
+ // Some versions of IE6 append "; length=####"
if (($strpos = strpos($_SERVER['HTTP_IF_MODIFIED_SINCE'], ';')) !== FALSE)
{
- // IE6 and perhaps other IE versions send length too, compensate here
$mod_time = substr($_SERVER['HTTP_IF_MODIFIED_SINCE'], 0, $strpos);
}
else
@@ -57,55 +52,69 @@ class expires_Core {
$mod_time = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
}
- $mod_time = strtotime($mod_time);
- $mod_time_diff = $mod_time + $seconds - time();
+ return strtotime($mod_time);
+ }
+
+ return FALSE;
+ }
- if ($mod_time_diff > 0)
+ /**
+ * Checks to see if content should be updated otherwise sends Not Modified status
+ * and exits.
+ *
+ * @uses exit()
+ * @uses expires::get()
+ *
+ * @param integer Maximum age of the content in seconds
+ * @return integer|boolean Timestamp of the If-Modified-Since header or FALSE when header is lacking or malformed
+ */
+ public static function check($seconds = 60)
+ {
+ if ($last_modified = expires::get())
+ {
+ $expires = $last_modified + $seconds;
+ $max_age = $expires - time();
+
+ if ($max_age > 0)
{
- // Re-send headers
- header('Last-Modified: '.gmdate('D, d M Y H:i:s T', $mod_time));
- header('Expires: '.gmdate('D, d M Y H:i:s T', time() + $mod_time_diff));
- header('Cache-Control: max-age='.$mod_time_diff);
- header('Status: 304 Not Modified', TRUE, 304);
+ // Content has not expired
+ header($_SERVER['SERVER_PROTOCOL'].' 304 Not Modified');
+ header('Last-Modified: '.gmdate('D, d M Y H:i:s T', $last_modified));
+
+ // HTTP 1.0
+ header('Expires: '.gmdate('D, d M Y H:i:s T', $expires));
- // Prevent any output
- Event::add('system.display', array('expires', 'prevent_output'));
+ // HTTP 1.1
+ header('Cache-Control: max-age='.$max_age);
+
+ // Clear any output
+ Event::add('system.display', create_function('', 'Kohana::$output = "";'));
- // Exit to prevent other output
exit;
}
}
- return FALSE;
+ return $last_modified;
}
/**
- * Check headers already created to not step on download or Img_lib's feet
+ * Check if expiration headers are already set
*
* @return boolean
*/
- public static function check_headers()
+ public static function headers_set()
{
foreach (headers_list() as $header)
{
- if ((session_cache_limiter() == '' AND stripos($header, 'Last-Modified:') === 0)
- OR stripos($header, 'Expires:') === 0)
+ if (strncasecmp($header, 'Expires:', 8) === 0
+ OR strncasecmp($header, 'Cache-Control:', 14) === 0
+ OR strncasecmp($header, 'Last-Modified:', 14) === 0)
{
- return FALSE;
+ return TRUE;
}
}
- return TRUE;
- }
-
- /**
- * Prevent any output from being displayed. Executed during system.display.
- *
- * @return void
- */
- public static function prevent_output()
- {
- Kohana::$output = '';
+ return FALSE;
}
-} // End expires
\ No newline at end of file
+} // End expires
diff --git a/system/helpers/feed.php b/system/helpers/feed.php
index 74bb2f6b..4aab1dcd 100644
--- a/system/helpers/feed.php
+++ b/system/helpers/feed.php
@@ -2,12 +2,12 @@
/**
* Feed helper class.
*
- * $Id: feed.php 4152 2009-04-03 23:26:23Z ixmatus $
+ * $Id: feed.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class feed_Core {
diff --git a/system/helpers/file.php b/system/helpers/file.php
index b1b71740..0d4a7980 100644
--- a/system/helpers/file.php
+++ b/system/helpers/file.php
@@ -2,12 +2,12 @@
/**
* File helper class.
*
- * $Id: file.php 3769 2008-12-15 00:48:56Z zombor $
+ * $Id: file.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class file_Core {
diff --git a/system/helpers/form.php b/system/helpers/form.php
index 815eef84..901edc91 100644
--- a/system/helpers/form.php
+++ b/system/helpers/form.php
@@ -2,12 +2,12 @@
/**
* Form helper class.
*
- * $Id: form.php 4291 2009-04-29 22:51:58Z kiall $
+ * $Id: form.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class form_Core {
@@ -17,9 +17,10 @@ class form_Core {
* @param string form action attribute
* @param array extra attributes
* @param array hidden fields to be created immediately after the form tag
+ * @param string non-default protocol, eg: https
* @return string
*/
- public static function open($action = NULL, $attr = array(), $hidden = NULL)
+ public static function open($action = NULL, $attr = array(), $hidden = NULL, $protocol = NULL)
{
// Make sure that the method is always set
empty($attr['method']) and $attr['method'] = 'post';
@@ -33,12 +34,12 @@ class form_Core {
if ($action === NULL)
{
// Use the current URL as the default action
- $action = url::site(Router::$complete_uri);
+ $action = url::site(Router::$complete_uri, $protocol);
}
elseif (strpos($action, '://') === FALSE)
{
// Make the action URI into a URL
- $action = url::site($action);
+ $action = url::site($action, $protocol);
}
// Set action
@@ -70,72 +71,23 @@ class form_Core {
}
/**
- * Generates a fieldset opening tag.
+ * Creates a HTML form hidden input tag.
*
- * @param array html attributes
- * @param string a string to be attached to the end of the attributes
- * @return string
- */
- public static function open_fieldset($data = NULL, $extra = '')
- {
- return ''."\n";
- }
-
- /**
- * Generates a fieldset closing tag.
- *
- * @return string
- */
- public static function close_fieldset()
- {
- return ' '."\n";
- }
-
- /**
- * Generates a legend tag for use with a fieldset.
- *
- * @param string legend text
- * @param array HTML attributes
- * @param string a string to be attached to the end of the attributes
- * @return string
- */
- public static function legend($text = '', $data = NULL, $extra = '')
- {
- return ''.$text.' '."\n";
- }
-
- /**
- * Generates hidden form fields.
- * You can pass a simple key/value string or an associative array with multiple values.
- *
- * @param string|array input name (string) or key/value pairs (array)
- * @param string input value, if using an input name
+ * @param string|array input name or an array of HTML attributes
+ * @param string input value, when using a name
+ * @param string a string to be attached to the end of the attributes
* @return string
*/
- public static function hidden($data, $value = '')
+ public static function hidden($data, $value = '', $extra = '')
{
if ( ! is_array($data))
{
- $data = array
- (
- $data => $value
- );
+ $data = array('name' => $data);
}
- $input = '';
- foreach ($data as $name => $value)
- {
- $attr = array
- (
- 'type' => 'hidden',
- 'name' => $name,
- 'value' => $value
- );
-
- $input .= form::input($attr)."\n";
- }
+ $data['type'] = 'hidden';
- return $input;
+ return form::input($data, $value, $extra);
}
/**
@@ -219,13 +171,23 @@ class form_Core {
$data = array('name' => $data);
}
+ if ( ! isset($data['rows']))
+ {
+ $data['rows'] = '';
+ }
+
+ if ( ! isset($data['cols']))
+ {
+ $data['cols'] = '';
+ }
+
// Use the value from $data if possible, or use $value
$value = isset($data['value']) ? $data['value'] : $value;
// Value is not part of the attributes
unset($data['value']);
- return '';
+ return '';
}
/**
@@ -283,21 +245,15 @@ class form_Core {
// Inner key should always be a string
$inner_key = (string) $inner_key;
- $attr = array('value' => $inner_key);
- if (in_array($inner_key, $selected)) {
- $attr['selected'] = 'selected';
- }
- $input .= ''.html::purify($inner_val).' '."\n";
+ $sel = in_array($inner_key, $selected) ? ' selected="selected"' : '';
+ $input .= ''.htmlspecialchars($inner_val, ENT_QUOTES, Kohana::CHARSET).' '."\n";
}
$input .= ''."\n";
}
else
{
- $attr = array('value' => $key);
- if (in_array($key, $selected)) {
- $attr['selected'] = 'selected';
- }
- $input .= ''.html::purify($val).' '."\n";
+ $sel = in_array($key, $selected) ? ' selected="selected"' : '';
+ $input .= ''.htmlspecialchars($val, ENT_QUOTES, Kohana::CHARSET).' '."\n";
}
}
$input .= '';
@@ -416,20 +372,8 @@ class form_Core {
{
$value = arr::remove('value', $data);
}
- // $value must be ::purify
-
- return ''.html::purify($value).' ';
- }
- /**
- * Closes an open form tag.
- *
- * @param string string to be attached after the closing tag
- * @return string
- */
- public static function close($extra = '')
- {
- return ''."\n".$extra;
+ return ''.$value.' ';
}
/**
@@ -462,7 +406,7 @@ class form_Core {
$text = ucwords(inflector::humanize($data['for']));
}
- return ''.html::purify($text).' ';
+ return ''.$text.' ';
}
/**
@@ -496,6 +440,7 @@ class form_Core {
case 'image':
case 'button':
case 'submit':
+ case 'hidden':
// Only specific types of inputs use name to id matching
$attr['id'] = $attr['name'];
break;
@@ -546,4 +491,4 @@ class form_Core {
return html::attributes(array_merge($sorted, $attr));
}
-} // End form
\ No newline at end of file
+} // End form
diff --git a/system/helpers/format.php b/system/helpers/format.php
index fb8a0294..9351afda 100644
--- a/system/helpers/format.php
+++ b/system/helpers/format.php
@@ -2,15 +2,32 @@
/**
* Format helper class.
*
- * $Id: format.php 4070 2009-03-11 20:37:38Z Geert $
+ * $Id: format.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class format_Core {
+ /**
+ * Formats a number according to the current locale.
+ *
+ * @param float
+ * @param int|boolean number of fractional digits or TRUE to use the locale default
+ * @return string
+ */
+ public static function number($number, $decimals = 0)
+ {
+ $locale = localeconv();
+
+ if ($decimals === TRUE)
+ return number_format($number, $locale['frac_digits'], $locale['decimal_point'], $locale['thousands_sep']);
+
+ return number_format($number, $decimals, $locale['decimal_point'], $locale['thousands_sep']);
+ }
+
/**
* Formats a phone number according to the specified format.
*
@@ -63,4 +80,35 @@ class format_Core {
return $str;
}
+ /**
+ * Normalizes a hexadecimal HTML color value. All values will be converted
+ * to lowercase, have a "#" prepended and contain six characters.
+ *
+ * @param string hexadecimal HTML color value
+ * @return string
+ */
+ public static function color($str = '')
+ {
+ // Reject invalid values
+ if ( ! valid::color($str))
+ return '';
+
+ // Convert to lowercase
+ $str = strtolower($str);
+
+ // Prepend "#"
+ if ($str[0] !== '#')
+ {
+ $str = '#'.$str;
+ }
+
+ // Expand short notation
+ if (strlen($str) === 4)
+ {
+ $str = '#'.$str[1].$str[1].$str[2].$str[2].$str[3].$str[3];
+ }
+
+ return $str;
+ }
+
} // End format
\ No newline at end of file
diff --git a/system/helpers/html.php b/system/helpers/html.php
index 2c609567..2d759ac0 100644
--- a/system/helpers/html.php
+++ b/system/helpers/html.php
@@ -2,12 +2,12 @@
/**
* HTML helper class.
*
- * $Id: html.php 4376 2009-06-01 11:40:39Z samsoir $
+ * $Id: html.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class html_Core {
@@ -21,46 +21,12 @@ class html_Core {
* @param boolean encode existing entities
* @return string
*/
- public static function specialchars($str, $double_encode = TRUE)
+ public static function chars($str, $double_encode = TRUE)
{
- // Force the string to be a string
- $str = (string) $str;
-
- // Do encode existing HTML entities (default)
- if ($double_encode === TRUE)
- {
- $str = htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
- }
- else
- {
- // Do not encode existing HTML entities
- // From PHP 5.2.3 this functionality is built-in, otherwise use a regex
- if (version_compare(PHP_VERSION, '5.2.3', '>='))
- {
- $str = htmlspecialchars($str, ENT_QUOTES, 'UTF-8', FALSE);
- }
- else
- {
- $str = preg_replace('/&(?!(?:#\d++|[a-z]++);)/ui', '&', $str);
- $str = str_replace(array('<', '>', '\'', '"'), array('<', '>', ''', '"'), $str);
- }
- }
-
- return $str;
+ // Return HTML entities using the Kohana charset
+ return htmlspecialchars($str, ENT_QUOTES, Kohana::CHARSET, $double_encode);
}
- /**
- * Perform a html::specialchars() with additional URL specific encoding.
- *
- * @param string string to convert
- * @param boolean encode existing entities
- * @return string
- */
- public static function specialurlencode($str, $double_encode = TRUE)
- {
- return str_replace(' ', '%20', html::specialchars($str, $double_encode));
- }
-
/**
* Create HTML link anchors.
*
@@ -98,11 +64,11 @@ class html_Core {
return
// Parsed URL
- ''
// Title empty? Use the parsed URL
- .($escape_title ? html::specialchars((($title === NULL) ? $site_url : $title), FALSE) : (($title === NULL) ? $site_url : $title)).' ';
+ .($escape_title ? htmlspecialchars((($title === NULL) ? $site_url : $title), ENT_QUOTES, Kohana::CHARSET, FALSE) : (($title === NULL) ? $site_url : $title)).'';
}
/**
@@ -118,44 +84,13 @@ class html_Core {
{
return
// Base URL + URI = full URL
- ''
// Title empty? Use the filename part of the URI
.(($title === NULL) ? end(explode('/', $file)) : $title) .' ';
}
- /**
- * Similar to anchor, but with the protocol parameter first.
- *
- * @param string link protocol
- * @param string URI or URL to link to
- * @param string link text
- * @param array HTML anchor attributes
- * @return string
- */
- public static function panchor($protocol, $uri, $title = NULL, $attributes = FALSE)
- {
- return html::anchor($uri, $title, $attributes, $protocol);
- }
-
- /**
- * Create an array of anchors from an array of link/title pairs.
- *
- * @param array link/title pairs
- * @return array
- */
- public static function anchor_array(array $array)
- {
- $anchors = array();
- foreach ($array as $link => $title)
- {
- // Create list of anchors
- $anchors[] = html::anchor($link, $title);
- }
- return $anchors;
- }
-
/**
* Generates an obfuscated version of an email address.
*
@@ -286,7 +221,7 @@ class html_Core {
*/
public static function stylesheet($style, $media = FALSE, $index = FALSE)
{
- return html::link($style, 'stylesheet', 'text/css', '.css', $media, $index);
+ return html::link($style, 'stylesheet', 'text/css', $media, $index);
}
/**
@@ -295,12 +230,11 @@ class html_Core {
* @param string|array filename
* @param string|array relationship
* @param string|array mimetype
- * @param string specifies suffix of the file
* @param string|array specifies on what device the document will be displayed
* @param boolean include the index_page in the link
* @return string
*/
- public static function link($href, $rel, $type, $suffix = FALSE, $media = FALSE, $index = FALSE)
+ public static function link($href, $rel, $type, $media = FALSE, $index = FALSE)
{
$compiled = '';
@@ -312,7 +246,7 @@ class html_Core {
$_type = is_array($type) ? array_shift($type) : $type;
$_media = is_array($media) ? array_shift($media) : $media;
- $compiled .= html::link($_href, $_rel, $_type, $suffix, $_media, $index);
+ $compiled .= html::link($_href, $_rel, $_type, $_media, $index);
}
}
else
@@ -323,14 +257,6 @@ class html_Core {
$href = url::base($index).$href;
}
- $length = strlen($suffix);
-
- if ( $length > 0 AND substr_compare($href, $suffix, -$length, $length, FALSE) !== 0)
- {
- // Add the defined suffix
- $href .= $suffix;
- }
-
$attr = array
(
'rel' => $rel,
@@ -376,12 +302,6 @@ class html_Core {
$script = url::base((bool) $index).$script;
}
- if (substr_compare($script, '.js', -3, 3, FALSE) !== 0)
- {
- // Add the javascript suffix
- $script .= '.js';
- }
-
$compiled = '';
}
@@ -437,7 +357,7 @@ class html_Core {
$compiled = '';
foreach ($attrs as $key => $val)
{
- $compiled .= ' '.$key.'="'.html::specialchars($val).'"';
+ $compiled .= ' '.$key.'="'.htmlspecialchars($val, ENT_QUOTES, Kohana::CHARSET).'"';
}
return $compiled;
diff --git a/system/helpers/inflector.php b/system/helpers/inflector.php
index 1e4fee23..9bd281db 100644
--- a/system/helpers/inflector.php
+++ b/system/helpers/inflector.php
@@ -2,12 +2,12 @@
/**
* Inflector helper class.
*
- * $Id: inflector.php 4072 2009-03-13 17:20:38Z jheathco $
+ * $Id: inflector.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class inflector_Core {
@@ -45,7 +45,27 @@ class inflector_Core {
* @param integer number of things
* @return string
*/
- public static function singular($str, $count = NULL)
+ public static function singular($str, $count = NULL) {
+ $parts = explode('_', $str);
+
+ $last = inflector::_singular(array_pop($parts), $count);
+
+ $pre = implode('_', $parts);
+ if (strlen($pre))
+ $pre .= '_';
+
+ return $pre.$last;
+ }
+
+
+ /**
+ * Makes a plural word singular.
+ *
+ * @param string word to singularize
+ * @param integer number of things
+ * @return string
+ */
+ public static function _singular($str, $count = NULL)
{
// Remove garbage
$str = strtolower(trim($str));
@@ -103,6 +123,29 @@ class inflector_Core {
* @return string
*/
public static function plural($str, $count = NULL)
+ {
+ if ( ! $str)
+ return $str;
+
+ $parts = explode('_', $str);
+
+ $last = inflector::_plural(array_pop($parts), $count);
+
+ $pre = implode('_', $parts);
+ if (strlen($pre))
+ $pre .= '_';
+
+ return $pre.$last;
+ }
+
+
+ /**
+ * Makes a singular word plural.
+ *
+ * @param string word to pluralize
+ * @return string
+ */
+ public static function _plural($str, $count = NULL)
{
// Remove garbage
$str = strtolower(trim($str));
@@ -154,6 +197,24 @@ class inflector_Core {
return inflector::$cache[$key] = $str;
}
+ /**
+ * Makes a word possessive.
+ *
+ * @param string word to to make possessive
+ * @return string
+ */
+ public static function possessive($string)
+ {
+ $length = strlen($string);
+
+ if (substr($string, $length - 1, $length) == 's')
+ {
+ return $string.'\'';
+ }
+
+ return $string.'\'s';
+ }
+
/**
* Makes a phrase camel case.
*
@@ -176,7 +237,7 @@ class inflector_Core {
*/
public static function underscore($str)
{
- return preg_replace('/\s+/', '_', trim($str));
+ return trim(preg_replace('/[\s_]+/', '_', $str), '_');
}
/**
@@ -187,7 +248,7 @@ class inflector_Core {
*/
public static function humanize($str)
{
- return preg_replace('/[_-]+/', ' ', trim($str));
+ return trim(preg_replace('/[_-\s]+/', ' ', $str));
}
} // End inflector
\ No newline at end of file
diff --git a/system/helpers/num.php b/system/helpers/num.php
index 3eb5d5ac..42f13bec 100644
--- a/system/helpers/num.php
+++ b/system/helpers/num.php
@@ -2,12 +2,12 @@
/**
* Number helper class.
*
- * $Id: num.php 3769 2008-12-15 00:48:56Z zombor $
+ * $Id: num.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class num_Core {
diff --git a/system/helpers/remote.php b/system/helpers/remote.php
index f9e0267f..37995cdb 100644
--- a/system/helpers/remote.php
+++ b/system/helpers/remote.php
@@ -2,12 +2,12 @@
/**
* Remote url/file helper.
*
- * $Id: remote.php 3769 2008-12-15 00:48:56Z zombor $
+ * $Id: remote.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class remote_Core {
@@ -35,7 +35,7 @@ class remote_Core {
$CRLF = "\r\n";
// Send request
- fwrite($remote, 'HEAD '.$url['path'].' HTTP/1.0'.$CRLF);
+ fwrite($remote, 'HEAD '.$url['path'].(isset($url['query']) ? '?'.$url['query'] : '').' HTTP/1.0'.$CRLF);
fwrite($remote, 'Host: '.$url['host'].$CRLF);
fwrite($remote, 'Connection: close'.$CRLF);
fwrite($remote, 'User-Agent: Kohana Framework (+http://kohanaphp.com/)'.$CRLF);
@@ -63,4 +63,4 @@ class remote_Core {
return isset($response) ? $response : FALSE;
}
-} // End remote
\ No newline at end of file
+} // End remote
diff --git a/system/helpers/request.php b/system/helpers/request.php
index 4203d0e5..4770d64b 100644
--- a/system/helpers/request.php
+++ b/system/helpers/request.php
@@ -2,35 +2,48 @@
/**
* Request helper class.
*
- * $Id: request.php 4355 2009-05-15 17:18:28Z kiall $
+ * $Id: request.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class request_Core {
// Possible HTTP methods
protected static $http_methods = array('get', 'head', 'options', 'post', 'put', 'delete');
- // Content types from client's HTTP Accept request header (array)
+ // Character sets from client's HTTP Accept-Charset request header
+ protected static $accept_charsets;
+
+ // Content codings from client's HTTP Accept-Encoding request header
+ protected static $accept_encodings;
+
+ // Language tags from client's HTTP Accept-Language request header
+ protected static $accept_languages;
+
+ // Content types from client's HTTP Accept request header
protected static $accept_types;
+ // The current user agent and its parsed attributes
+ protected static $user_agent;
+
/**
* Returns the HTTP referrer, or the default if the referrer is not set.
*
* @param mixed default to return
+ * @param bool Remove base URL
* @return string
*/
- public static function referrer($default = FALSE)
+ public static function referrer($default = FALSE, $remove_base = FALSE)
{
if ( ! empty($_SERVER['HTTP_REFERER']))
{
// Set referrer
$ref = $_SERVER['HTTP_REFERER'];
- if (strpos($ref, url::base(FALSE)) === 0)
+ if ($remove_base === TRUE AND (strpos($ref, url::base(FALSE)) === 0))
{
// Remove the base URL from the referrer
$ref = substr($ref, strlen(url::base(FALSE)));
@@ -84,10 +97,59 @@ class request_Core {
$method = strtolower($_SERVER['REQUEST_METHOD']);
if ( ! in_array($method, request::$http_methods))
- throw new Kohana_Exception('request.unknown_method', $method);
+ throw new Kohana_Exception('Invalid request method :method:', array(':method:' => $method));
return $method;
- }
+ }
+
+ /**
+ * Retrieves current user agent information
+ * keys: browser, version, platform, mobile, robot
+ *
+ * @param string key
+ * @return mixed NULL or the parsed value
+ */
+ public static function user_agent($key = 'agent')
+ {
+ // Retrieve raw user agent without parsing
+ if ($key === 'agent')
+ {
+ if (request::$user_agent === NULL)
+ return request::$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? trim($_SERVER['HTTP_USER_AGENT']) : '';
+
+ if (is_array(request::$user_agent))
+ return request::$user_agent['agent'];
+
+ return request::$user_agent;
+ }
+
+ if ( ! is_array(request::$user_agent))
+ {
+ request::$user_agent['agent'] = isset($_SERVER['HTTP_USER_AGENT']) ? trim($_SERVER['HTTP_USER_AGENT']) : '';
+
+ // Parse the user agent and extract basic information
+ foreach (Kohana::config('user_agents') as $type => $data)
+ {
+ foreach ($data as $fragment => $name)
+ {
+ if (stripos(request::$user_agent['agent'], $fragment) !== FALSE)
+ {
+ if ($type === 'browser' AND preg_match('|'.preg_quote($fragment).'[^0-9.]*+([0-9.][0-9.a-z]*)|i', request::$user_agent['agent'], $match))
+ {
+ // Set the browser version
+ request::$user_agent['version'] = $match[1];
+ }
+
+ // Set the agent name
+ request::$user_agent[$type] = $name;
+ break;
+ }
+ }
+ }
+ }
+
+ return isset(request::$user_agent[$key]) ? request::$user_agent[$key] : NULL;
+ }
/**
* Returns boolean of whether client accepts content type.
@@ -98,7 +160,7 @@ class request_Core {
*/
public static function accepts($type = NULL, $explicit_check = FALSE)
{
- request::parse_accept_header();
+ request::parse_accept_content_header();
if ($type === NULL)
return request::$accept_types;
@@ -106,6 +168,56 @@ class request_Core {
return (request::accepts_at_quality($type, $explicit_check) > 0);
}
+ /**
+ * Returns boolean indicating if the client accepts a charset
+ *
+ * @param string
+ * @return boolean
+ */
+ public static function accepts_charset($charset = NULL)
+ {
+ request::parse_accept_charset_header();
+
+ if ($charset === NULL)
+ return request::$accept_charsets;
+
+ return (request::accepts_charset_at_quality($charset) > 0);
+ }
+
+ /**
+ * Returns boolean indicating if the client accepts an encoding
+ *
+ * @param string
+ * @param boolean set to TRUE to disable wildcard checking
+ * @return boolean
+ */
+ public static function accepts_encoding($encoding = NULL, $explicit_check = FALSE)
+ {
+ request::parse_accept_encoding_header();
+
+ if ($encoding === NULL)
+ return request::$accept_encodings;
+
+ return (request::accepts_encoding_at_quality($encoding, $explicit_check) > 0);
+ }
+
+ /**
+ * Returns boolean indicating if the client accepts a language tag
+ *
+ * @param string language tag
+ * @param boolean set to TRUE to disable prefix and wildcard checking
+ * @return boolean
+ */
+ public static function accepts_language($tag = NULL, $explicit_check = FALSE)
+ {
+ request::parse_accept_language_header();
+
+ if ($tag === NULL)
+ return request::$accept_languages;
+
+ return (request::accepts_language_at_quality($tag, $explicit_check) > 0);
+ }
+
/**
* Compare the q values for given array of content types and return the one with the highest value.
* If items are found to have the same q value, the first one encountered in the given array wins.
@@ -117,24 +229,103 @@ class request_Core {
*/
public static function preferred_accept($types, $explicit_check = FALSE)
{
- // Initialize
- $mime_types = array();
$max_q = 0;
$preferred = FALSE;
- // Load q values for all given content types
- foreach (array_unique($types) as $type)
+ foreach ($types as $type)
+ {
+ $q = request::accepts_at_quality($type, $explicit_check);
+
+ if ($q > $max_q)
+ {
+ $max_q = $q;
+ $preferred = $type;
+ }
+ }
+
+ return $preferred;
+ }
+
+ /**
+ * Compare the q values for a given array of character sets and return the
+ * one with the highest value. If items are found to have the same q value,
+ * the first one encountered takes precedence. If all items in the given
+ * array have a q value of 0, FALSE is returned.
+ *
+ * @param array character sets
+ * @return mixed
+ */
+ public static function preferred_charset($charsets)
+ {
+ $max_q = 0;
+ $preferred = FALSE;
+
+ foreach ($charsets as $charset)
+ {
+ $q = request::accepts_charset_at_quality($charset);
+
+ if ($q > $max_q)
+ {
+ $max_q = $q;
+ $preferred = $charset;
+ }
+ }
+
+ return $preferred;
+ }
+
+ /**
+ * Compare the q values for a given array of encodings and return the one with
+ * the highest value. If items are found to have the same q value, the first
+ * one encountered takes precedence. If all items in the given array have
+ * a q value of 0, FALSE is returned.
+ *
+ * @param array encodings
+ * @param boolean set to TRUE to disable wildcard checking
+ * @return mixed
+ */
+ public static function preferred_encoding($encodings, $explicit_check = FALSE)
+ {
+ $max_q = 0;
+ $preferred = FALSE;
+
+ foreach ($encodings as $encoding)
{
- $mime_types[$type] = request::accepts_at_quality($type, $explicit_check);
+ $q = request::accepts_encoding_at_quality($encoding, $explicit_check);
+
+ if ($q > $max_q)
+ {
+ $max_q = $q;
+ $preferred = $encoding;
+ }
}
- // Look for the highest q value
- foreach ($mime_types as $type => $q)
+ return $preferred;
+ }
+
+ /**
+ * Compare the q values for a given array of language tags and return the
+ * one with the highest value. If items are found to have the same q value,
+ * the first one encountered takes precedence. If all items in the given
+ * array have a q value of 0, FALSE is returned.
+ *
+ * @param array language tags
+ * @param boolean set to TRUE to disable prefix and wildcard checking
+ * @return mixed
+ */
+ public static function preferred_language($tags, $explicit_check = FALSE)
+ {
+ $max_q = 0;
+ $preferred = FALSE;
+
+ foreach ($tags as $tag)
{
+ $q = request::accepts_language_at_quality($tag, $explicit_check);
+
if ($q > $max_q)
{
$max_q = $q;
- $preferred = $type;
+ $preferred = $tag;
}
}
@@ -142,18 +333,18 @@ class request_Core {
}
/**
- * Returns quality factor at which the client accepts content type.
+ * Returns quality factor at which the client accepts content type
*
* @param string content type (e.g. "image/jpg", "jpg")
* @param boolean set to TRUE to disable wildcard checking
* @return integer|float
*/
- public static function accepts_at_quality($type = NULL, $explicit_check = FALSE)
+ public static function accepts_at_quality($type, $explicit_check = FALSE)
{
- request::parse_accept_header();
+ request::parse_accept_content_header();
// Normalize type
- $type = strtolower((string) $type);
+ $type = strtolower($type);
// General content type (e.g. "jpg")
if (strpos($type, '/') === FALSE)
@@ -178,62 +369,251 @@ class request_Core {
if (isset(request::$accept_types[$type[0]][$type[1]]))
return request::$accept_types[$type[0]][$type[1]];
- // Wildcard match (if not checking explicitly)
- if ($explicit_check === FALSE AND isset(request::$accept_types[$type[0]]['*']))
- return request::$accept_types[$type[0]]['*'];
+ if ($explicit_check === FALSE)
+ {
+ // Wildcard match
+ if (isset(request::$accept_types[$type[0]]['*']))
+ return request::$accept_types[$type[0]]['*'];
- // Catch-all wildcard match (if not checking explicitly)
- if ($explicit_check === FALSE AND isset(request::$accept_types['*']['*']))
- return request::$accept_types['*']['*'];
+ // Catch-all wildcard match
+ if (isset(request::$accept_types['*']['*']))
+ return request::$accept_types['*']['*'];
+ }
// Content type not accepted
return 0;
}
/**
- * Parses client's HTTP Accept request header, and builds array structure representing it.
+ * Returns quality factor at which the client accepts a charset
+ *
+ * @param string charset (e.g., "ISO-8859-1", "utf-8")
+ * @return integer|float
+ */
+ public static function accepts_charset_at_quality($charset)
+ {
+ request::parse_accept_charset_header();
+
+ // Normalize charset
+ $charset = strtolower($charset);
+
+ // Exact match
+ if (isset(request::$accept_charsets[$charset]))
+ return request::$accept_charsets[$charset];
+
+ if (isset(request::$accept_charsets['*']))
+ return request::$accept_charsets['*'];
+
+ if ($charset === 'iso-8859-1')
+ return 1;
+
+ return 0;
+ }
+
+ /**
+ * Returns quality factor at which the client accepts an encoding
+ *
+ * @param string encoding (e.g., "gzip", "deflate")
+ * @param boolean set to TRUE to disable wildcard checking
+ * @return integer|float
+ */
+ public static function accepts_encoding_at_quality($encoding, $explicit_check = FALSE)
+ {
+ request::parse_accept_encoding_header();
+
+ // Normalize encoding
+ $encoding = strtolower($encoding);
+
+ // Exact match
+ if (isset(request::$accept_encodings[$encoding]))
+ return request::$accept_encodings[$encoding];
+
+ if ($explicit_check === FALSE)
+ {
+ if (isset(request::$accept_encodings['*']))
+ return request::$accept_encodings['*'];
+
+ if ($encoding === 'identity')
+ return 1;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns quality factor at which the client accepts a language
+ *
+ * @param string tag (e.g., "en", "en-us", "fr-ca")
+ * @param boolean set to TRUE to disable prefix and wildcard checking
+ * @return integer|float
+ */
+ public static function accepts_language_at_quality($tag, $explicit_check = FALSE)
+ {
+ request::parse_accept_language_header();
+
+ $tag = explode('-', strtolower($tag), 2);
+
+ if (isset(request::$accept_languages[$tag[0]]))
+ {
+ if (isset($tag[1]))
+ {
+ // Exact match
+ if (isset(request::$accept_languages[$tag[0]][$tag[1]]))
+ return request::$accept_languages[$tag[0]][$tag[1]];
+
+ // A prefix matches
+ if ($explicit_check === FALSE AND isset(request::$accept_languages[$tag[0]]['*']))
+ return request::$accept_languages[$tag[0]]['*'];
+ }
+ else
+ {
+ // No subtags
+ if (isset(request::$accept_languages[$tag[0]]['*']))
+ return request::$accept_languages[$tag[0]]['*'];
+ }
+ }
+
+ if ($explicit_check === FALSE AND isset(request::$accept_languages['*']))
+ return request::$accept_languages['*'];
+
+ return 0;
+ }
+
+ /**
+ * Parses a HTTP Accept or Accept-* header for q values
*
- * @return void
+ * @param string header data
+ * @return array
+ */
+ protected static function parse_accept_header($header)
+ {
+ $result = array();
+
+ // Remove linebreaks and parse the HTTP Accept header
+ foreach (explode(',', str_replace(array("\r", "\n"), '', strtolower($header))) as $entry)
+ {
+ // Explode each entry in content type and possible quality factor
+ $entry = explode(';', trim($entry), 2);
+
+ $q = (isset($entry[1]) AND preg_match('~\bq\s*+=\s*+([.0-9]+)~', $entry[1], $match)) ? (float) $match[1] : 1;
+
+ // Overwrite entries with a smaller q value
+ if ( ! isset($result[$entry[0]]) OR $q > $result[$entry[0]])
+ {
+ $result[$entry[0]] = $q;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Parses a client's HTTP Accept-Charset header
*/
- protected static function parse_accept_header()
+ protected static function parse_accept_charset_header()
{
// Run this function just once
- if (request::$accept_types !== NULL)
+ if (request::$accept_charsets !== NULL)
return;
- // Initialize accept_types array
- request::$accept_types = array();
+ // No HTTP Accept-Charset header found
+ if (empty($_SERVER['HTTP_ACCEPT_CHARSET']))
+ {
+ // Accept everything
+ request::$accept_charsets['*'] = 1;
+ }
+ else
+ {
+ request::$accept_charsets = request::parse_accept_header($_SERVER['HTTP_ACCEPT_CHARSET']);
+ }
+ }
+
+ /**
+ * Parses a client's HTTP Accept header
+ */
+ protected static function parse_accept_content_header()
+ {
+ // Run this function just once
+ if (request::$accept_types !== NULL)
+ return;
// No HTTP Accept header found
if (empty($_SERVER['HTTP_ACCEPT']))
{
// Accept everything
request::$accept_types['*']['*'] = 1;
- return;
}
-
- // Remove linebreaks and parse the HTTP Accept header
- foreach (explode(',', str_replace(array("\r", "\n"), '', $_SERVER['HTTP_ACCEPT'])) as $accept_entry)
+ else
{
- // Explode each entry in content type and possible quality factor
- $accept_entry = explode(';', trim($accept_entry), 2);
+ request::$accept_types = array();
+
+ foreach (request::parse_accept_header($_SERVER['HTTP_ACCEPT']) as $type => $q)
+ {
+ // Explode each content type (e.g. "text/html")
+ $type = explode('/', $type, 2);
+
+ // Skip invalid content types
+ if ( ! isset($type[1]))
+ continue;
+
+ request::$accept_types[$type[0]][$type[1]] = $q;
+ }
+ }
+ }
+
+ /**
+ * Parses a client's HTTP Accept-Encoding header
+ */
+ protected static function parse_accept_encoding_header()
+ {
+ // Run this function just once
+ if (request::$accept_encodings !== NULL)
+ return;
- // Explode each content type (e.g. "text/html")
- $type = explode('/', $accept_entry[0], 2);
+ // No HTTP Accept-Encoding header found
+ if ( ! isset($_SERVER['HTTP_ACCEPT_ENCODING']))
+ {
+ // Accept everything
+ request::$accept_encodings['*'] = 1;
+ }
+ elseif ($_SERVER['HTTP_ACCEPT_ENCODING'] === '')
+ {
+ // Accept only identity
+ request::$accept_encodings['identity'] = 1;
+ }
+ else
+ {
+ request::$accept_encodings = request::parse_accept_header($_SERVER['HTTP_ACCEPT_ENCODING']);
+ }
+ }
- // Skip invalid content types
- if ( ! isset($type[1]))
- continue;
+ /**
+ * Parses a client's HTTP Accept-Language header
+ */
+ protected static function parse_accept_language_header()
+ {
+ // Run this function just once
+ if (request::$accept_languages !== NULL)
+ return;
- // Assume a default quality factor of 1 if no custom q value found
- $q = (isset($accept_entry[1]) AND preg_match('~\bq\s*+=\s*+([.0-9]+)~', $accept_entry[1], $match)) ? (float) $match[1] : 1;
+ // No HTTP Accept-Language header found
+ if (empty($_SERVER['HTTP_ACCEPT_LANGUAGE']))
+ {
+ // Accept everything
+ request::$accept_languages['*'] = 1;
+ }
+ else
+ {
+ request::$accept_languages = array();
- // Populate accept_types array
- if ( ! isset(request::$accept_types[$type[0]][$type[1]]) OR $q > request::$accept_types[$type[0]][$type[1]])
+ foreach (request::parse_accept_header($_SERVER['HTTP_ACCEPT_LANGUAGE']) as $tag => $q)
{
- request::$accept_types[$type[0]][$type[1]] = $q;
+ // Explode each language (e.g. "en-us")
+ $tag = explode('-', $tag, 2);
+
+ request::$accept_languages[$tag[0]][isset($tag[1]) ? $tag[1] : '*'] = $q;
}
}
}
-} // End request
\ No newline at end of file
+} // End request
diff --git a/system/helpers/security.php b/system/helpers/security.php
index cd48d2e0..9eb82a58 100644
--- a/system/helpers/security.php
+++ b/system/helpers/security.php
@@ -2,12 +2,12 @@
/**
* Security helper class.
*
- * $Id: security.php 3769 2008-12-15 00:48:56Z zombor $
+ * $Id: security.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class security_Core {
@@ -15,11 +15,12 @@ class security_Core {
* Sanitize a string with the xss_clean method.
*
* @param string string to sanitize
+ * @param string xss_clean method to use ('htmlpurifier' or defaults to built-in method)
* @return string
*/
- public static function xss_clean($str)
+ public static function xss_clean($str, $tool = NULL)
{
- return Input::instance()->xss_clean($str);
+ return Input::instance()->xss_clean($str, $tool);
}
/**
diff --git a/system/helpers/text.php b/system/helpers/text.php
index d0e573ec..66bcd243 100644
--- a/system/helpers/text.php
+++ b/system/helpers/text.php
@@ -2,12 +2,12 @@
/**
* Text helper class.
*
- * $Id: text.php 3769 2008-12-15 00:48:56Z zombor $
+ * $Id: text.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class text_Core {
@@ -52,7 +52,7 @@ class text_Core {
$limit = (int) $limit;
- if (trim($str) === '' OR utf8::strlen($str) <= $limit)
+ if (trim($str) === '' OR mb_strlen($str) <= $limit)
return $str;
if ($limit <= 0)
@@ -60,7 +60,7 @@ class text_Core {
if ($preserve_words == FALSE)
{
- return rtrim(utf8::substr($str, 0, $limit)).$end_char;
+ return rtrim(mb_substr($str, 0, $limit)).$end_char;
}
preg_match('/^.{'.($limit - 1).'}\S*/us', $str, $matches);
@@ -128,7 +128,7 @@ class text_Core {
break;
default:
$pool = (string) $type;
- $utf8 = ! utf8::is_ascii($pool);
+ $utf8 = ! text::is_ascii($pool);
break;
}
@@ -183,7 +183,7 @@ class text_Core {
* @param boolean replace words across word boundries (space, period, etc)
* @return string
*/
- public static function censor($str, $badwords, $replacement = '#', $replace_partial_words = FALSE)
+ public static function censor($str, $badwords, $replacement = '#', $replace_partial_words = TRUE)
{
foreach ((array) $badwords as $key => $badword)
{
@@ -192,7 +192,7 @@ class text_Core {
$regex = '('.implode('|', $badwords).')';
- if ($replace_partial_words == TRUE)
+ if ($replace_partial_words === FALSE)
{
// Just using \b isn't sufficient when we need to replace a badword that already contains word boundaries itself
$regex = '(?<=\b|\s|^)'.$regex.'(?=\b|\s|$)';
@@ -200,10 +200,10 @@ class text_Core {
$regex = '!'.$regex.'!ui';
- if (utf8::strlen($replacement) == 1)
+ if (mb_strlen($replacement) == 1)
{
$regex .= 'e';
- return preg_replace($regex, 'str_repeat($replacement, utf8::strlen(\'$1\'))', $str);
+ return preg_replace($regex, 'str_repeat($replacement, mb_strlen(\'$1\'))', $str);
}
return preg_replace($regex, $replacement, $str);
@@ -235,15 +235,59 @@ class text_Core {
}
/**
- * Converts text email addresses and anchors into links.
+ * An alternative to the php levenshtein() function that work out the
+ * distance between 2 words using the Damerau–Levenshtein algorithm.
+ * Credit: http://forums.devnetwork.net/viewtopic.php?f=50&t=89094
*
- * @param string text to auto link
- * @return string
+ * @see http://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance
+ * @param string first word
+ * @param string second word
+ * @return int distance between words
*/
- public static function auto_link($text)
+ public static function distance($string1, $string2)
{
- // Auto link emails first to prevent problems with "www.domain.com@example.com"
- return text::auto_link_urls(text::auto_link_emails($text));
+ $string1_length = strlen($string1);
+ $string2_length = strlen($string2);
+
+ // Here we start building the table of values
+ $matrix = array();
+
+ // String1 length + 1 = rows.
+ for ($i = 0; $i <= $string1_length; ++$i)
+ {
+ $matrix[$i][0] = $i;
+ }
+
+ // String2 length + 1 columns.
+ for ($j = 0; $j <= $string2_length; ++$j)
+ {
+ $matrix[0][$j] = $j;
+ }
+
+ for ($i = 1; $i <= $string1_length; ++$i)
+ {
+ for ($j = 1; $j <= $string2_length; ++$j)
+ {
+ $cost = substr($string1, $i - 1, 1) == substr($string2, $j - 1, 1) ? 0 : 1;
+
+ $matrix[$i][$j] = min(
+ $matrix[$i - 1][$j] + 1, // deletion
+ $matrix[$i][$j - 1] + 1, // insertion
+ $matrix[$i - 1][$j - 1] + $cost // substitution
+ );
+
+ if ($i > 1 && $j > 1 && (substr($string1, $i - 1, 1) == substr($string2, $j - 2, 1))
+ && (substr($string1, $i - 2, 1) == substr($string2, $j - 1, 1)))
+ {
+ $matrix[$i][$j] = min(
+ $matrix[$i][$j],
+ $matrix[$i - 2][$j - 2] + $cost // transposition
+ );
+ }
+ }
+ }
+
+ return $matrix[$string1_length][$string2_length];
}
/**
@@ -304,9 +348,10 @@ class text_Core {
* Automatically applies and markup to text. Basically nl2br() on steroids.
*
* @param string subject
+ * @param boolean convert single linebreaks to
* @return string
*/
- public static function auto_p($str)
+ public static function auto_p($str, $br = TRUE)
{
// Trim whitespace
if (($str = trim($str)) === '')
@@ -343,7 +388,10 @@ class text_Core {
}
// Convert single linebreaks to
- $str = preg_replace('~(?\n", $str);
+ if ($br === TRUE)
+ {
+ $str = preg_replace('~(?\n", $str);
+ }
return $str;
}
@@ -368,13 +416,13 @@ class text_Core {
// IEC prefixes (binary)
if ($si == FALSE OR strpos($force_unit, 'i') !== FALSE)
{
- $units = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB');
+ $units = array(__('B'), __('KiB'), __('MiB'), __('GiB'), __('TiB'), __('PiB'));
$mod = 1024;
}
// SI prefixes (decimal)
else
{
- $units = array('B', 'kB', 'MB', 'GB', 'TB', 'PB');
+ $units = array(__('B'), __('kB'), __('MB'), __('GB'), __('TB'), __('PB'));
$mod = 1000;
}
@@ -407,4 +455,134 @@ class text_Core {
return $str;
}
+ /**
+ * Tests whether a string contains only 7bit ASCII bytes. This is used to
+ * determine when to use native functions or UTF-8 functions.
+ *
+ * @see http://sourceforge.net/projects/phputf8/
+ * @copyright (c) 2007-2009 Kohana Team
+ * @copyright (c) 2005 Harry Fuecks
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ *
+ * @param string string to check
+ * @return bool
+ */
+ public static function is_ascii($str)
+ {
+ return is_string($str) AND ! preg_match('/[^\x00-\x7F]/S', $str);
+ }
+
+ /**
+ * Strips out device control codes in the ASCII range.
+ *
+ * @see http://sourceforge.net/projects/phputf8/
+ * @copyright (c) 2007-2009 Kohana Team
+ * @copyright (c) 2005 Harry Fuecks
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ *
+ * @param string string to clean
+ * @return string
+ */
+ public static function strip_ascii_ctrl($str)
+ {
+ return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $str);
+ }
+
+ /**
+ * Strips out all non-7bit ASCII bytes.
+ *
+ * @see http://sourceforge.net/projects/phputf8/
+ * @copyright (c) 2007-2009 Kohana Team
+ * @copyright (c) 2005 Harry Fuecks
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ *
+ * @param string string to clean
+ * @return string
+ */
+ public static function strip_non_ascii($str)
+ {
+ return preg_replace('/[^\x00-\x7F]+/S', '', $str);
+ }
+
+ /**
+ * Replaces special/accented UTF-8 characters by ASCII-7 'equivalents'.
+ *
+ * @author Andreas Gohr
+ * @see http://sourceforge.net/projects/phputf8/
+ * @copyright (c) 2007-2009 Kohana Team
+ * @copyright (c) 2005 Harry Fuecks
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ *
+ * @param string string to transliterate
+ * @param integer -1 lowercase only, +1 uppercase only, 0 both cases
+ * @return string
+ */
+ public static function transliterate_to_ascii($str, $case = 0)
+ {
+ static $UTF8_LOWER_ACCENTS = NULL;
+ static $UTF8_UPPER_ACCENTS = NULL;
+
+ if ($case <= 0)
+ {
+ if ($UTF8_LOWER_ACCENTS === NULL)
+ {
+ $UTF8_LOWER_ACCENTS = array(
+ 'à' => 'a', 'ô' => 'o', 'ď' => 'd', 'ḟ' => 'f', 'ë' => 'e', 'š' => 's', 'ơ' => 'o',
+ 'ß' => 'ss', 'ă' => 'a', 'ř' => 'r', 'ț' => 't', 'ň' => 'n', 'ā' => 'a', 'ķ' => 'k',
+ 'ŝ' => 's', 'ỳ' => 'y', 'ņ' => 'n', 'ĺ' => 'l', 'ħ' => 'h', 'ṗ' => 'p', 'ó' => 'o',
+ 'ú' => 'u', 'ě' => 'e', 'é' => 'e', 'ç' => 'c', 'ẁ' => 'w', 'ċ' => 'c', 'õ' => 'o',
+ 'ṡ' => 's', 'ø' => 'o', 'ģ' => 'g', 'ŧ' => 't', 'ș' => 's', 'ė' => 'e', 'ĉ' => 'c',
+ 'ś' => 's', 'î' => 'i', 'ű' => 'u', 'ć' => 'c', 'ę' => 'e', 'ŵ' => 'w', 'ṫ' => 't',
+ 'ū' => 'u', 'č' => 'c', 'ö' => 'o', 'è' => 'e', 'ŷ' => 'y', 'ą' => 'a', 'ł' => 'l',
+ 'ų' => 'u', 'ů' => 'u', 'ş' => 's', 'ğ' => 'g', 'ļ' => 'l', 'ƒ' => 'f', 'ž' => 'z',
+ 'ẃ' => 'w', 'ḃ' => 'b', 'å' => 'a', 'ì' => 'i', 'ï' => 'i', 'ḋ' => 'd', 'ť' => 't',
+ 'ŗ' => 'r', 'ä' => 'a', 'í' => 'i', 'ŕ' => 'r', 'ê' => 'e', 'ü' => 'u', 'ò' => 'o',
+ 'ē' => 'e', 'ñ' => 'n', 'ń' => 'n', 'ĥ' => 'h', 'ĝ' => 'g', 'đ' => 'd', 'ĵ' => 'j',
+ 'ÿ' => 'y', 'ũ' => 'u', 'ŭ' => 'u', 'ư' => 'u', 'ţ' => 't', 'ý' => 'y', 'ő' => 'o',
+ 'â' => 'a', 'ľ' => 'l', 'ẅ' => 'w', 'ż' => 'z', 'ī' => 'i', 'ã' => 'a', 'ġ' => 'g',
+ 'ṁ' => 'm', 'ō' => 'o', 'ĩ' => 'i', 'ù' => 'u', 'į' => 'i', 'ź' => 'z', 'á' => 'a',
+ 'û' => 'u', 'þ' => 'th', 'ð' => 'dh', 'æ' => 'ae', 'µ' => 'u', 'ĕ' => 'e', 'ı' => 'i',
+ );
+ }
+
+ $str = str_replace(
+ array_keys($UTF8_LOWER_ACCENTS),
+ array_values($UTF8_LOWER_ACCENTS),
+ $str
+ );
+ }
+
+ if ($case >= 0)
+ {
+ if ($UTF8_UPPER_ACCENTS === NULL)
+ {
+ $UTF8_UPPER_ACCENTS = array(
+ 'À' => 'A', 'Ô' => 'O', 'Ď' => 'D', 'Ḟ' => 'F', 'Ë' => 'E', 'Š' => 'S', 'Ơ' => 'O',
+ 'Ă' => 'A', 'Ř' => 'R', 'Ț' => 'T', 'Ň' => 'N', 'Ā' => 'A', 'Ķ' => 'K', 'Ĕ' => 'E',
+ 'Ŝ' => 'S', 'Ỳ' => 'Y', 'Ņ' => 'N', 'Ĺ' => 'L', 'Ħ' => 'H', 'Ṗ' => 'P', 'Ó' => 'O',
+ 'Ú' => 'U', 'Ě' => 'E', 'É' => 'E', 'Ç' => 'C', 'Ẁ' => 'W', 'Ċ' => 'C', 'Õ' => 'O',
+ 'Ṡ' => 'S', 'Ø' => 'O', 'Ģ' => 'G', 'Ŧ' => 'T', 'Ș' => 'S', 'Ė' => 'E', 'Ĉ' => 'C',
+ 'Ś' => 'S', 'Î' => 'I', 'Ű' => 'U', 'Ć' => 'C', 'Ę' => 'E', 'Ŵ' => 'W', 'Ṫ' => 'T',
+ 'Ū' => 'U', 'Č' => 'C', 'Ö' => 'O', 'È' => 'E', 'Ŷ' => 'Y', 'Ą' => 'A', 'Ł' => 'L',
+ 'Ų' => 'U', 'Ů' => 'U', 'Ş' => 'S', 'Ğ' => 'G', 'Ļ' => 'L', 'Ƒ' => 'F', 'Ž' => 'Z',
+ 'Ẃ' => 'W', 'Ḃ' => 'B', 'Å' => 'A', 'Ì' => 'I', 'Ï' => 'I', 'Ḋ' => 'D', 'Ť' => 'T',
+ 'Ŗ' => 'R', 'Ä' => 'A', 'Í' => 'I', 'Ŕ' => 'R', 'Ê' => 'E', 'Ü' => 'U', 'Ò' => 'O',
+ 'Ē' => 'E', 'Ñ' => 'N', 'Ń' => 'N', 'Ĥ' => 'H', 'Ĝ' => 'G', 'Đ' => 'D', 'Ĵ' => 'J',
+ 'Ÿ' => 'Y', 'Ũ' => 'U', 'Ŭ' => 'U', 'Ư' => 'U', 'Ţ' => 'T', 'Ý' => 'Y', 'Ő' => 'O',
+ 'Â' => 'A', 'Ľ' => 'L', 'Ẅ' => 'W', 'Ż' => 'Z', 'Ī' => 'I', 'Ã' => 'A', 'Ġ' => 'G',
+ 'Ṁ' => 'M', 'Ō' => 'O', 'Ĩ' => 'I', 'Ù' => 'U', 'Į' => 'I', 'Ź' => 'Z', 'Á' => 'A',
+ 'Û' => 'U', 'Þ' => 'Th', 'Ð' => 'Dh', 'Æ' => 'Ae', 'İ' => 'I',
+ );
+ }
+
+ $str = str_replace(
+ array_keys($UTF8_UPPER_ACCENTS),
+ array_values($UTF8_UPPER_ACCENTS),
+ $str
+ );
+ }
+
+ return $str;
+ }
+
} // End text
\ No newline at end of file
diff --git a/system/helpers/upload.php b/system/helpers/upload.php
index 422e9e8d..3aec2ac4 100644
--- a/system/helpers/upload.php
+++ b/system/helpers/upload.php
@@ -3,12 +3,12 @@
* Upload helper class for working with the global $_FILES
* array and Validation library.
*
- * $Id: upload.php 3769 2008-12-15 00:48:56Z zombor $
+ * $Id: upload.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class upload_Core {
@@ -54,7 +54,7 @@ class upload_Core {
}
if ( ! is_writable($directory))
- throw new Kohana_Exception('upload.not_writable', $directory);
+ throw new Kohana_Exception('The upload destination folder, :dir:, does not appear to be writable.', array(':dir:' => $directory));
if (is_uploaded_file($file['tmp_name']) AND move_uploaded_file($file['tmp_name'], $filename = $directory.$filename))
{
@@ -118,11 +118,8 @@ class upload_Core {
// Get the default extension of the file
$extension = strtolower(substr(strrchr($file['name'], '.'), 1));
- // Get the mime types for the extension
- $mime_types = Kohana::config('mimes.'.$extension);
-
- // Make sure there is an extension, that the extension is allowed, and that mime types exist
- return ( ! empty($extension) AND in_array($extension, $allowed_types) AND is_array($mime_types));
+ // Make sure there is an extension and that the extension is allowed
+ return ( ! empty($extension) AND in_array($extension, $allowed_types));
}
/**
diff --git a/system/helpers/url.php b/system/helpers/url.php
index 56f6db4b..6c2a6c66 100644
--- a/system/helpers/url.php
+++ b/system/helpers/url.php
@@ -2,12 +2,12 @@
/**
* URL helper class.
*
- * $Id: url.php 4479 2009-07-23 04:51:22Z ixmatus $
+ * $Id: url.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class url_Core {
@@ -15,7 +15,7 @@ class url_Core {
* Fetches the current URI.
*
* @param boolean include the query string
- * @param boolean include the suffix
+ * @param boolean include the suffix
* @return string
*/
public static function current($qs = FALSE, $suffix = FALSE)
@@ -167,7 +167,7 @@ class url_Core {
$separator = ($separator === '-') ? '-' : '_';
// Replace accented characters by their unaccented equivalents
- $title = utf8::transliterate_to_ascii($title);
+ $title = text::transliterate_to_ascii($title);
// Remove all characters that are not the separator, a-z, 0-9, or whitespace
$title = preg_replace('/[^'.$separator.'a-z0-9\s]+/', '', strtolower($title));
diff --git a/system/helpers/utf8.php b/system/helpers/utf8.php
new file mode 100644
index 00000000..20c6878c
--- /dev/null
+++ b/system/helpers/utf8.php
@@ -0,0 +1,746 @@
+
+ *
+ * @param string input string
+ * @param string replacement string
+ * @param integer offset
+ * @return string
+ */
+ public static function substr_replace($str, $replacement, $offset, $length = NULL)
+ {
+ if (text::is_ascii($str))
+ return ($length === NULL) ? substr_replace($str, $replacement, $offset) : substr_replace($str, $replacement, $offset, $length);
+
+ $length = ($length === NULL) ? mb_strlen($str) : (int) $length;
+ preg_match_all('/./us', $str, $str_array);
+ preg_match_all('/./us', $replacement, $replacement_array);
+
+ array_splice($str_array[0], $offset, $length, $replacement_array[0]);
+ return implode('', $str_array[0]);
+ }
+
+ /**
+ * Makes a UTF-8 string's first character uppercase.
+ * @see http://php.net/ucfirst
+ *
+ * @author Harry Fuecks
+ *
+ * @param string mixed case string
+ * @return string
+ */
+ public static function ucfirst($str)
+ {
+ if (text::is_ascii($str))
+ return ucfirst($str);
+
+ preg_match('/^(.?)(.*)$/us', $str, $matches);
+ return mb_strtoupper($matches[1]).$matches[2];
+ }
+
+ /**
+ * Case-insensitive UTF-8 string comparison.
+ * @see http://php.net/strcasecmp
+ *
+ * @author Harry Fuecks
+ *
+ * @param string string to compare
+ * @param string string to compare
+ * @return integer less than 0 if str1 is less than str2
+ * @return integer greater than 0 if str1 is greater than str2
+ * @return integer 0 if they are equal
+ */
+ public static function strcasecmp($str1, $str2)
+ {
+ if (text::is_ascii($str1) AND text::is_ascii($str2))
+ return strcasecmp($str1, $str2);
+
+ $str1 = mb_strtolower($str1);
+ $str2 = mb_strtolower($str2);
+ return strcmp($str1, $str2);
+ }
+
+ /**
+ * Returns a string or an array with all occurrences of search in subject (ignoring case).
+ * replaced with the given replace value.
+ * @see http://php.net/str_ireplace
+ *
+ * @note It's not fast and gets slower if $search and/or $replace are arrays.
+ * @author Harry Fuecks $val)
+ {
+ $str[$key] = utf8::str_ireplace($search, $replace, $val, $count);
+ }
+ return $str;
+ }
+
+ if (is_array($search))
+ {
+ $keys = array_keys($search);
+
+ foreach ($keys as $k)
+ {
+ if (is_array($replace))
+ {
+ if (array_key_exists($k, $replace))
+ {
+ $str = utf8::str_ireplace($search[$k], $replace[$k], $str, $count);
+ }
+ else
+ {
+ $str = utf8::str_ireplace($search[$k], '', $str, $count);
+ }
+ }
+ else
+ {
+ $str = utf8::str_ireplace($search[$k], $replace, $str, $count);
+ }
+ }
+ return $str;
+ }
+
+ $search = mb_strtolower($search);
+ $str_lower = mb_strtolower($str);
+
+ $total_matched_strlen = 0;
+ $i = 0;
+
+ while (preg_match('/(.*?)'.preg_quote($search, '/').'/s', $str_lower, $matches))
+ {
+ $matched_strlen = strlen($matches[0]);
+ $str_lower = substr($str_lower, $matched_strlen);
+
+ $offset = $total_matched_strlen + strlen($matches[1]) + ($i * (strlen($replace) - 1));
+ $str = substr_replace($str, $replace, $offset, strlen($search));
+
+ $total_matched_strlen += $matched_strlen;
+ $i++;
+ }
+
+ $count += $i;
+ return $str;
+ }
+
+ /**
+ * Case-insenstive UTF-8 version of strstr. Returns all of input string
+ * from the first occurrence of needle to the end.
+ * @see http://php.net/stristr
+ *
+ * @author Harry Fuecks
+ *
+ * @param string input string
+ * @param string needle
+ * @return string matched substring if found
+ * @return boolean FALSE if the substring was not found
+ */
+ public static function stristr($str, $search)
+ {
+ if (text::is_ascii($str) AND text::is_ascii($search))
+ return stristr($str, $search);
+
+ if ($search == '')
+ return $str;
+
+ $str_lower = mb_strtolower($str);
+ $search_lower = mb_strtolower($search);
+
+ preg_match('/^(.*?)'.preg_quote($search, '/').'/s', $str_lower, $matches);
+
+ if (isset($matches[1]))
+ return substr($str, strlen($matches[1]));
+
+ return FALSE;
+ }
+
+ /**
+ * Finds the length of the initial segment matching mask.
+ * @see http://php.net/strspn
+ *
+ * @author Harry Fuecks
+ *
+ * @param string input string
+ * @param string mask for search
+ * @param integer start position of the string to examine
+ * @param integer length of the string to examine
+ * @return integer length of the initial segment that contains characters in the mask
+ */
+ public static function strspn($str, $mask, $offset = NULL, $length = NULL)
+ {
+ if ($str == '' OR $mask == '')
+ return 0;
+
+ if (text::is_ascii($str) AND text::is_ascii($mask))
+ return ($offset === NULL) ? strspn($str, $mask) : (($length === NULL) ? strspn($str, $mask, $offset) : strspn($str, $mask, $offset, $length));
+
+ if ($offset !== NULL OR $length !== NULL)
+ {
+ $str = mb_substr($str, $offset, $length);
+ }
+
+ // Escape these characters: - [ ] . : \ ^ /
+ // The . and : are escaped to prevent possible warnings about POSIX regex elements
+ $mask = preg_replace('#[-[\].:\\\\^/]#', '\\\\$0', $mask);
+ preg_match('/^[^'.$mask.']+/u', $str, $matches);
+
+ return isset($matches[0]) ? mb_strlen($matches[0]) : 0;
+ }
+
+ /**
+ * Finds the length of the initial segment not matching mask.
+ * @see http://php.net/strcspn
+ *
+ * @author Harry Fuecks
+ *
+ * @param string input string
+ * @param string mask for search
+ * @param integer start position of the string to examine
+ * @param integer length of the string to examine
+ * @return integer length of the initial segment that contains characters not in the mask
+ */
+ public static function strcspn($str, $mask, $offset = NULL, $length = NULL)
+ {
+ if ($str == '' OR $mask == '')
+ return 0;
+
+ if (text::is_ascii($str) AND text::is_ascii($mask))
+ return ($offset === NULL) ? strcspn($str, $mask) : (($length === NULL) ? strcspn($str, $mask, $offset) : strcspn($str, $mask, $offset, $length));
+
+ if ($str !== NULL OR $length !== NULL)
+ {
+ $str = mb_substr($str, $offset, $length);
+ }
+
+ // Escape these characters: - [ ] . : \ ^ /
+ // The . and : are escaped to prevent possible warnings about POSIX regex elements
+ $mask = preg_replace('#[-[\].:\\\\^/]#', '\\\\$0', $mask);
+ preg_match('/^[^'.$mask.']+/u', $str, $matches);
+
+ return isset($matches[0]) ? mb_strlen($matches[0]) : 0;
+ }
+
+ /**
+ * Pads a UTF-8 string to a certain length with another string.
+ * @see http://php.net/str_pad
+ *
+ * @author Harry Fuecks
+ *
+ * @param string input string
+ * @param integer desired string length after padding
+ * @param string string to use as padding
+ * @param string padding type: STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH
+ * @return string
+ */
+ public static function str_pad($str, $final_str_length, $pad_str = ' ', $pad_type = STR_PAD_RIGHT)
+ {
+ if (text::is_ascii($str) AND text::is_ascii($pad_str))
+ {
+ return str_pad($str, $final_str_length, $pad_str, $pad_type);
+ }
+
+ $str_length = mb_strlen($str);
+
+ if ($final_str_length <= 0 OR $final_str_length <= $str_length)
+ {
+ return $str;
+ }
+
+ $pad_str_length = mb_strlen($pad_str);
+ $pad_length = $final_str_length - $str_length;
+
+ if ($pad_type == STR_PAD_RIGHT)
+ {
+ $repeat = ceil($pad_length / $pad_str_length);
+ return mb_substr($str.str_repeat($pad_str, $repeat), 0, $final_str_length);
+ }
+
+ if ($pad_type == STR_PAD_LEFT)
+ {
+ $repeat = ceil($pad_length / $pad_str_length);
+ return mb_substr(str_repeat($pad_str, $repeat), 0, floor($pad_length)).$str;
+ }
+
+ if ($pad_type == STR_PAD_BOTH)
+ {
+ $pad_length /= 2;
+ $pad_length_left = floor($pad_length);
+ $pad_length_right = ceil($pad_length);
+ $repeat_left = ceil($pad_length_left / $pad_str_length);
+ $repeat_right = ceil($pad_length_right / $pad_str_length);
+
+ $pad_left = mb_substr(str_repeat($pad_str, $repeat_left), 0, $pad_length_left);
+ $pad_right = mb_substr(str_repeat($pad_str, $repeat_right), 0, $pad_length_left);
+ return $pad_left.$str.$pad_right;
+ }
+
+ trigger_error('utf8::str_pad: Unknown padding type (' . $pad_type . ')', E_USER_ERROR);
+ }
+
+ /**
+ * Converts a UTF-8 string to an array.
+ * @see http://php.net/str_split
+ *
+ * @author Harry Fuecks
+ *
+ * @param string input string
+ * @param integer maximum length of each chunk
+ * @return array
+ */
+ public static function str_split($str, $split_length = 1)
+ {
+ $split_length = (int) $split_length;
+
+ if (text::is_ascii($str))
+ {
+ return str_split($str, $split_length);
+ }
+
+ if ($split_length < 1)
+ {
+ return FALSE;
+ }
+
+ if (mb_strlen($str) <= $split_length)
+ {
+ return array($str);
+ }
+
+ preg_match_all('/.{'.$split_length.'}|[^\x00]{1,'.$split_length.'}$/us', $str, $matches);
+
+ return $matches[0];
+ }
+
+ /**
+ * Reverses a UTF-8 string.
+ * @see http://php.net/strrev
+ *
+ * @author Harry Fuecks
+ *
+ * @param string string to be reversed
+ * @return string
+ */
+ public static function strrev($str)
+ {
+ if (text::is_ascii($str))
+ return strrev($str);
+
+ preg_match_all('/./us', $str, $matches);
+ return implode('', array_reverse($matches[0]));
+ }
+
+ /**
+ * Strips whitespace (or other UTF-8 characters) from the beginning and
+ * end of a string.
+ * @see http://php.net/trim
+ *
+ * @author Andreas Gohr
+ *
+ * @param string input string
+ * @param string string of characters to remove
+ * @return string
+ */
+ public static function trim($str, $charlist = NULL)
+ {
+ if ($charlist === NULL)
+ return trim($str);
+
+ return utf8::ltrim(utf8::rtrim($str, $charlist), $charlist);
+ }
+
+ /**
+ * Strips whitespace (or other UTF-8 characters) from the beginning of a string.
+ * @see http://php.net/ltrim
+ *
+ * @author Andreas Gohr
+ *
+ * @param string input string
+ * @param string string of characters to remove
+ * @return string
+ */
+ public static function ltrim($str, $charlist = NULL)
+ {
+ if ($charlist === NULL)
+ return ltrim($str);
+
+ if (text::is_ascii($charlist))
+ return ltrim($str, $charlist);
+
+ $charlist = preg_replace('#[-\[\]:\\\\^/]#', '\\\\$0', $charlist);
+
+ return preg_replace('/^['.$charlist.']+/u', '', $str);
+ }
+
+ /**
+ * Strips whitespace (or other UTF-8 characters) from the end of a string.
+ * @see http://php.net/rtrim
+ *
+ * @author Andreas Gohr
+ *
+ * @param string input string
+ * @param string string of characters to remove
+ * @return string
+ */
+ public static function rtrim($str, $charlist = NULL)
+ {
+ if ($charlist === NULL)
+ return rtrim($str);
+
+ if (text::is_ascii($charlist))
+ return rtrim($str, $charlist);
+
+ $charlist = preg_replace('#[-\[\]:\\\\^/]#', '\\\\$0', $charlist);
+
+ return preg_replace('/['.$charlist.']++$/uD', '', $str);
+ }
+
+ /**
+ * Returns the unicode ordinal for a character.
+ * @see http://php.net/ord
+ *
+ * @author Harry Fuecks
+ *
+ * @param string UTF-8 encoded character
+ * @return integer
+ */
+ public static function ord($chr)
+ {
+ $ord0 = ord($chr);
+
+ if ($ord0 >= 0 AND $ord0 <= 127)
+ {
+ return $ord0;
+ }
+
+ if ( ! isset($chr[1]))
+ {
+ trigger_error('Short sequence - at least 2 bytes expected, only 1 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ $ord1 = ord($chr[1]);
+
+ if ($ord0 >= 192 AND $ord0 <= 223)
+ {
+ return ($ord0 - 192) * 64 + ($ord1 - 128);
+ }
+
+ if ( ! isset($chr[2]))
+ {
+ trigger_error('Short sequence - at least 3 bytes expected, only 2 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ $ord2 = ord($chr[2]);
+
+ if ($ord0 >= 224 AND $ord0 <= 239)
+ {
+ return ($ord0 - 224) * 4096 + ($ord1 - 128) * 64 + ($ord2 - 128);
+ }
+
+ if ( ! isset($chr[3]))
+ {
+ trigger_error('Short sequence - at least 4 bytes expected, only 3 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ $ord3 = ord($chr[3]);
+
+ if ($ord0 >= 240 AND $ord0 <= 247)
+ {
+ return ($ord0 - 240) * 262144 + ($ord1 - 128) * 4096 + ($ord2-128) * 64 + ($ord3 - 128);
+ }
+
+ if ( ! isset($chr[4]))
+ {
+ trigger_error('Short sequence - at least 5 bytes expected, only 4 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ $ord4 = ord($chr[4]);
+
+ if ($ord0 >= 248 AND $ord0 <= 251)
+ {
+ return ($ord0 - 248) * 16777216 + ($ord1-128) * 262144 + ($ord2 - 128) * 4096 + ($ord3 - 128) * 64 + ($ord4 - 128);
+ }
+
+ if ( ! isset($chr[5]))
+ {
+ trigger_error('Short sequence - at least 6 bytes expected, only 5 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ if ($ord0 >= 252 AND $ord0 <= 253)
+ {
+ return ($ord0 - 252) * 1073741824 + ($ord1 - 128) * 16777216 + ($ord2 - 128) * 262144 + ($ord3 - 128) * 4096 + ($ord4 - 128) * 64 + (ord($chr[5]) - 128);
+ }
+
+ if ($ord0 >= 254 AND $ord0 <= 255)
+ {
+ trigger_error('Invalid UTF-8 with surrogate ordinal '.$ord0, E_USER_WARNING);
+ return FALSE;
+ }
+ }
+
+ /**
+ * Takes an UTF-8 string and returns an array of ints representing the Unicode characters.
+ * Astral planes are supported i.e. the ints in the output can be > 0xFFFF.
+ * Occurrances of the BOM are ignored. Surrogates are not allowed.
+ *
+ * The Original Code is Mozilla Communicator client code.
+ * The Initial Developer of the Original Code is Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer.
+ * Ported to PHP by Henri Sivonen , see http://hsivonen.iki.fi/php-utf8/.
+ * Slight modifications to fit with phputf8 library by Harry Fuecks .
+ *
+ * @param string UTF-8 encoded string
+ * @return array unicode code points
+ * @return boolean FALSE if the string is invalid
+ */
+ public static function to_unicode($str)
+ {
+ $mState = 0; // cached expected number of octets after the current octet until the beginning of the next UTF8 character sequence
+ $mUcs4 = 0; // cached Unicode character
+ $mBytes = 1; // cached expected number of octets in the current sequence
+
+ $out = array();
+
+ $len = strlen($str);
+
+ for ($i = 0; $i < $len; $i++)
+ {
+ $in = ord($str[$i]);
+
+ if ($mState == 0)
+ {
+ // When mState is zero we expect either a US-ASCII character or a
+ // multi-octet sequence.
+ if (0 == (0x80 & $in))
+ {
+ // US-ASCII, pass straight through.
+ $out[] = $in;
+ $mBytes = 1;
+ }
+ elseif (0xC0 == (0xE0 & $in))
+ {
+ // First octet of 2 octet sequence
+ $mUcs4 = $in;
+ $mUcs4 = ($mUcs4 & 0x1F) << 6;
+ $mState = 1;
+ $mBytes = 2;
+ }
+ elseif (0xE0 == (0xF0 & $in))
+ {
+ // First octet of 3 octet sequence
+ $mUcs4 = $in;
+ $mUcs4 = ($mUcs4 & 0x0F) << 12;
+ $mState = 2;
+ $mBytes = 3;
+ }
+ elseif (0xF0 == (0xF8 & $in))
+ {
+ // First octet of 4 octet sequence
+ $mUcs4 = $in;
+ $mUcs4 = ($mUcs4 & 0x07) << 18;
+ $mState = 3;
+ $mBytes = 4;
+ }
+ elseif (0xF8 == (0xFC & $in))
+ {
+ // First octet of 5 octet sequence.
+ //
+ // This is illegal because the encoded codepoint must be either
+ // (a) not the shortest form or
+ // (b) outside the Unicode range of 0-0x10FFFF.
+ // Rather than trying to resynchronize, we will carry on until the end
+ // of the sequence and let the later error handling code catch it.
+ $mUcs4 = $in;
+ $mUcs4 = ($mUcs4 & 0x03) << 24;
+ $mState = 4;
+ $mBytes = 5;
+ }
+ elseif (0xFC == (0xFE & $in))
+ {
+ // First octet of 6 octet sequence, see comments for 5 octet sequence.
+ $mUcs4 = $in;
+ $mUcs4 = ($mUcs4 & 1) << 30;
+ $mState = 5;
+ $mBytes = 6;
+ }
+ else
+ {
+ // Current octet is neither in the US-ASCII range nor a legal first octet of a multi-octet sequence.
+ trigger_error('utf8::to_unicode: Illegal sequence identifier in UTF-8 at byte '.$i, E_USER_WARNING);
+ return FALSE;
+ }
+ }
+ else
+ {
+ // When mState is non-zero, we expect a continuation of the multi-octet sequence
+ if (0x80 == (0xC0 & $in))
+ {
+ // Legal continuation
+ $shift = ($mState - 1) * 6;
+ $tmp = $in;
+ $tmp = ($tmp & 0x0000003F) << $shift;
+ $mUcs4 |= $tmp;
+
+ // End of the multi-octet sequence. mUcs4 now contains the final Unicode codepoint to be output
+ if (0 == --$mState)
+ {
+ // Check for illegal sequences and codepoints
+
+ // From Unicode 3.1, non-shortest form is illegal
+ if (((2 == $mBytes) AND ($mUcs4 < 0x0080)) OR
+ ((3 == $mBytes) AND ($mUcs4 < 0x0800)) OR
+ ((4 == $mBytes) AND ($mUcs4 < 0x10000)) OR
+ (4 < $mBytes) OR
+ // From Unicode 3.2, surrogate characters are illegal
+ (($mUcs4 & 0xFFFFF800) == 0xD800) OR
+ // Codepoints outside the Unicode range are illegal
+ ($mUcs4 > 0x10FFFF))
+ {
+ trigger_error('utf8::to_unicode: Illegal sequence or codepoint in UTF-8 at byte '.$i, E_USER_WARNING);
+ return FALSE;
+ }
+
+ if (0xFEFF != $mUcs4)
+ {
+ // BOM is legal but we don't want to output it
+ $out[] = $mUcs4;
+ }
+
+ // Initialize UTF-8 cache
+ $mState = 0;
+ $mUcs4 = 0;
+ $mBytes = 1;
+ }
+ }
+ else
+ {
+ // ((0xC0 & (*in) != 0x80) AND (mState != 0))
+ // Incomplete multi-octet sequence
+ trigger_error('utf8::to_unicode: Incomplete multi-octet sequence in UTF-8 at byte '.$i, E_USER_WARNING);
+ return FALSE;
+ }
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * Takes an array of ints representing the Unicode characters and returns a UTF-8 string.
+ * Astral planes are supported i.e. the ints in the input can be > 0xFFFF.
+ * Occurrances of the BOM are ignored. Surrogates are not allowed.
+ *
+ * The Original Code is Mozilla Communicator client code.
+ * The Initial Developer of the Original Code is Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer.
+ * Ported to PHP by Henri Sivonen , see http://hsivonen.iki.fi/php-utf8/.
+ * Slight modifications to fit with phputf8 library by Harry Fuecks .
+ *
+ * @param array unicode code points representing a string
+ * @return string utf8 string of characters
+ * @return boolean FALSE if a code point cannot be found
+ */
+ public static function from_unicode($arr)
+ {
+ ob_start();
+
+ $keys = array_keys($arr);
+
+ foreach ($keys as $k)
+ {
+ // ASCII range (including control chars)
+ if (($arr[$k] >= 0) AND ($arr[$k] <= 0x007f))
+ {
+ echo chr($arr[$k]);
+ }
+ // 2 byte sequence
+ elseif ($arr[$k] <= 0x07ff)
+ {
+ echo chr(0xc0 | ($arr[$k] >> 6));
+ echo chr(0x80 | ($arr[$k] & 0x003f));
+ }
+ // Byte order mark (skip)
+ elseif ($arr[$k] == 0xFEFF)
+ {
+ // nop -- zap the BOM
+ }
+ // Test for illegal surrogates
+ elseif ($arr[$k] >= 0xD800 AND $arr[$k] <= 0xDFFF)
+ {
+ // Found a surrogate
+ trigger_error('utf8::from_unicode: Illegal surrogate at index: '.$k.', value: '.$arr[$k], E_USER_WARNING);
+ return FALSE;
+ }
+ // 3 byte sequence
+ elseif ($arr[$k] <= 0xffff)
+ {
+ echo chr(0xe0 | ($arr[$k] >> 12));
+ echo chr(0x80 | (($arr[$k] >> 6) & 0x003f));
+ echo chr(0x80 | ($arr[$k] & 0x003f));
+ }
+ // 4 byte sequence
+ elseif ($arr[$k] <= 0x10ffff)
+ {
+ echo chr(0xf0 | ($arr[$k] >> 18));
+ echo chr(0x80 | (($arr[$k] >> 12) & 0x3f));
+ echo chr(0x80 | (($arr[$k] >> 6) & 0x3f));
+ echo chr(0x80 | ($arr[$k] & 0x3f));
+ }
+ // Out of range
+ else
+ {
+ trigger_error('utf8::from_unicode: Codepoint out of Unicode range at index: '.$k.', value: '.$arr[$k], E_USER_WARNING);
+ return FALSE;
+ }
+ }
+
+ $result = ob_get_contents();
+ ob_end_clean();
+ return $result;
+ }
+
+} // End utf8
\ No newline at end of file
diff --git a/system/helpers/valid.php b/system/helpers/valid.php
index 8a3583b2..cffcd7c0 100644
--- a/system/helpers/valid.php
+++ b/system/helpers/valid.php
@@ -2,12 +2,12 @@
/**
* Validation helper class.
*
- * $Id: valid.php 4367 2009-05-27 21:23:57Z samsoir $
+ * $Id: valid.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class valid_Core {
@@ -161,13 +161,13 @@ class valid_Core {
for ($i = $length - 1; $i >= 0; $i -= 2)
{
// Add up every 2nd digit, starting from the right
- $checksum += $number[$i];
+ $checksum += substr($number, $i, 1);
}
for ($i = $length - 2; $i >= 0; $i -= 2)
{
// Add up every 2nd digit doubled, starting from the right
- $double = $number[$i] * 2;
+ $double = substr($number, $i, 1) * 2;
// Subtract 9 from the double where value is greater than 10
$checksum += ($double >= 10) ? $double - 9 : $double;
@@ -199,7 +199,7 @@ class valid_Core {
/**
* Tests if a string is a valid date string.
- *
+ *
* @param string date to check
* @return boolean
*/
@@ -269,7 +269,7 @@ class valid_Core {
*
* @see Uses locale conversion to allow decimal point to be locale specific.
* @see http://www.php.net/manual/en/function.localeconv.php
- *
+ *
* @param string input string
* @return boolean
*/
@@ -281,21 +281,35 @@ class valid_Core {
}
/**
- * Checks whether a string is a valid text. Letters, numbers, whitespace,
- * dashes, periods, and underscores are allowed.
+ * Tests if an integer is within a range.
*
- * @param string text to check
+ * @param integer number to check
+ * @param array valid range of input
* @return boolean
*/
- public static function standard_text($str)
+ public static function range($number, array $range)
{
- // pL matches letters
- // pN matches numbers
- // pZ matches whitespace
- // pPc matches underscores
- // pPd matches dashes
- // pPo matches normal puncuation
- return (bool) preg_match('/^[\pL\pN\pZ\p{Pc}\p{Pd}\p{Po}]++$/uD', (string) $str);
+ // Invalid by default
+ $status = FALSE;
+
+ if (is_int($number) OR ctype_digit($number))
+ {
+ if (count($range) > 1)
+ {
+ if ($number >= $range[0] AND $number <= $range[1])
+ {
+ // Number is within the required range
+ $status = TRUE;
+ }
+ }
+ elseif ($number >= $range[0])
+ {
+ // Number is greater than the minimum
+ $status = TRUE;
+ }
+ }
+
+ return $status;
}
/**
@@ -335,4 +349,18 @@ class valid_Core {
return (bool) preg_match($pattern, (string) $str);
}
+ /**
+ * Checks if a string is a proper hexadecimal HTML color value. The validation
+ * is quite flexible as it does not require an initial "#" and also allows for
+ * the short notation using only three instead of six hexadecimal characters.
+ * You may want to normalize these values with format::color().
+ *
+ * @param string input string
+ * @return boolean
+ */
+ public static function color($str)
+ {
+ return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $str);
+ }
+
} // End valid
\ No newline at end of file
diff --git a/system/i18n/en_US/cache.php b/system/i18n/en_US/cache.php
deleted file mode 100644
index bef02793..00000000
--- a/system/i18n/en_US/cache.php
+++ /dev/null
@@ -1,10 +0,0 @@
- 'The %s group is not defined in your configuration.',
- 'extension_not_loaded' => 'The %s PHP extension must be loaded to use this driver.',
- 'unwritable' => 'The configured storage location, %s, is not writable.',
- 'resources' => 'Caching of resources is impossible, because resources cannot be serialized.',
- 'driver_error' => '%s',
-);
\ No newline at end of file
diff --git a/system/i18n/en_US/calendar.php b/system/i18n/en_US/calendar.php
deleted file mode 100644
index 21dad220..00000000
--- a/system/i18n/en_US/calendar.php
+++ /dev/null
@@ -1,59 +0,0 @@
- 'Su',
- 'mo' => 'Mo',
- 'tu' => 'Tu',
- 'we' => 'We',
- 'th' => 'Th',
- 'fr' => 'Fr',
- 'sa' => 'Sa',
-
- // Short day names
- 'sun' => 'Sun',
- 'mon' => 'Mon',
- 'tue' => 'Tue',
- 'wed' => 'Wed',
- 'thu' => 'Thu',
- 'fri' => 'Fri',
- 'sat' => 'Sat',
-
- // Long day names
- 'sunday' => 'Sunday',
- 'monday' => 'Monday',
- 'tuesday' => 'Tuesday',
- 'wednesday' => 'Wednesday',
- 'thursday' => 'Thursday',
- 'friday' => 'Friday',
- 'saturday' => 'Saturday',
-
- // Short month names
- 'jan' => 'Jan',
- 'feb' => 'Feb',
- 'mar' => 'Mar',
- 'apr' => 'Apr',
- 'may' => 'May',
- 'jun' => 'Jun',
- 'jul' => 'Jul',
- 'aug' => 'Aug',
- 'sep' => 'Sep',
- 'oct' => 'Oct',
- 'nov' => 'Nov',
- 'dec' => 'Dec',
-
- // Long month names
- 'january' => 'January',
- 'february' => 'February',
- 'march' => 'March',
- 'april' => 'April',
- 'mayl' => 'May',
- 'june' => 'June',
- 'july' => 'July',
- 'august' => 'August',
- 'september' => 'September',
- 'october' => 'October',
- 'november' => 'November',
- 'december' => 'December'
-);
\ No newline at end of file
diff --git a/system/i18n/en_US/core.php b/system/i18n/en_US/core.php
deleted file mode 100644
index 9711b7cd..00000000
--- a/system/i18n/en_US/core.php
+++ /dev/null
@@ -1,34 +0,0 @@
- 'There can be only one instance of Kohana per page request',
- 'uncaught_exception' => 'Uncaught %s: %s in file %s on line %s',
- 'invalid_method' => 'Invalid method %s called in %s',
- 'invalid_property' => 'The %s property does not exist in the %s class.',
- 'log_dir_unwritable' => 'The log directory is not writable: %s',
- 'resource_not_found' => 'The requested %s, %s, could not be found',
- 'invalid_filetype' => 'The requested filetype, .%s, is not allowed in your view configuration file',
- 'view_set_filename' => 'You must set the the view filename before calling render',
- 'no_default_route' => 'Please set a default route in config/routes.php',
- 'no_controller' => 'Kohana was not able to determine a controller to process this request: %s',
- 'page_not_found' => 'The page you requested, %s, could not be found.',
- 'stats_footer' => 'Loaded in {execution_time} seconds, using {memory_usage} of memory. Generated by Kohana v{kohana_version}.',
- 'error_file_line' => '%s [%s]: ',
- 'stack_trace' => 'Stack Trace',
- 'generic_error' => 'Unable to Complete Request',
- 'errors_disabled' => 'You can go to the home page or try again .',
-
- // Drivers
- 'driver_implements' => 'The %s driver for the %s library must implement the %s interface',
- 'driver_not_found' => 'The %s driver for the %s library could not be found',
-
- // Resource names
- 'config' => 'config file',
- 'controller' => 'controller',
- 'helper' => 'helper',
- 'library' => 'library',
- 'driver' => 'driver',
- 'model' => 'model',
- 'view' => 'view',
-);
diff --git a/system/i18n/en_US/database.php b/system/i18n/en_US/database.php
deleted file mode 100644
index 172e5c90..00000000
--- a/system/i18n/en_US/database.php
+++ /dev/null
@@ -1,15 +0,0 @@
- 'The %s group is not defined in your configuration.',
- 'error' => 'There was an SQL error: %s',
- 'connection' => 'There was an error connecting to the database: %s',
- 'invalid_dsn' => 'The DSN you supplied is not valid: %s',
- 'must_use_set' => 'You must set a SET clause for your query.',
- 'must_use_where' => 'You must set a WHERE clause for your query.',
- 'must_use_table' => 'You must set a database table for your query.',
- 'table_not_found' => 'Table %s does not exist in your database.',
- 'not_implemented' => 'The method you called, %s, is not supported by this driver.',
- 'result_read_only' => 'Query results are read only.'
-);
\ No newline at end of file
diff --git a/system/i18n/en_US/encrypt.php b/system/i18n/en_US/encrypt.php
deleted file mode 100644
index 9afd6208..00000000
--- a/system/i18n/en_US/encrypt.php
+++ /dev/null
@@ -1,8 +0,0 @@
- 'The %s group is not defined in your configuration.',
- 'requires_mcrypt' => 'To use the Encrypt library, mcrypt must be enabled in your PHP installation',
- 'no_encryption_key' => 'To use the Encrypt library, you must set an encryption key in your config file'
-);
diff --git a/system/i18n/en_US/errors.php b/system/i18n/en_US/errors.php
deleted file mode 100644
index 83341a2c..00000000
--- a/system/i18n/en_US/errors.php
+++ /dev/null
@@ -1,16 +0,0 @@
- array( 1, 'Framework Error', 'Please check the Kohana documentation for information about the following error.'),
- E_PAGE_NOT_FOUND => array( 1, 'Page Not Found', 'The requested page was not found. It may have moved, been deleted, or archived.'),
- E_DATABASE_ERROR => array( 1, 'Database Error', 'A database error occurred while performing the requested procedure. Please review the database error below for more information.'),
- E_RECOVERABLE_ERROR => array( 1, 'Recoverable Error', 'An error was detected which prevented the loading of this page. If this problem persists, please contact the website administrator.'),
- E_ERROR => array( 1, 'Fatal Error', ''),
- E_USER_ERROR => array( 1, 'Fatal Error', ''),
- E_PARSE => array( 1, 'Syntax Error', ''),
- E_WARNING => array( 1, 'Warning Message', ''),
- E_USER_WARNING => array( 1, 'Warning Message', ''),
- E_STRICT => array( 2, 'Strict Mode Error', ''),
- E_NOTICE => array( 2, 'Runtime Message', ''),
-);
\ No newline at end of file
diff --git a/system/i18n/en_US/event.php b/system/i18n/en_US/event.php
deleted file mode 100644
index 282a0a25..00000000
--- a/system/i18n/en_US/event.php
+++ /dev/null
@@ -1,7 +0,0 @@
- 'Attempt to attach invalid subject %s to %s failed: Subjects must extend the Event_Subject class',
- 'invalid_observer' => 'Attempt to attach invalid observer %s to %s failed: Observers must extend the Event_Observer class',
-);
diff --git a/system/i18n/en_US/image.php b/system/i18n/en_US/image.php
deleted file mode 100644
index 9f184930..00000000
--- a/system/i18n/en_US/image.php
+++ /dev/null
@@ -1,33 +0,0 @@
- 'The Image library requires the getimagesize() PHP function, which is not available in your installation.',
- 'unsupported_method' => 'Your configured driver does not support the %s image transformation.',
- 'file_not_found' => 'The specified image, %s, was not found. Please verify that images exist by using file_exists() before manipulating them.',
- 'type_not_allowed' => 'The specified image, %s, is not an allowed image type.',
- 'invalid_width' => 'The width you specified, %s, is not valid.',
- 'invalid_height' => 'The height you specified, %s, is not valid.',
- 'invalid_dimensions' => 'The dimensions specified for %s are not valid.',
- 'invalid_master' => 'The master dimension specified is not valid.',
- 'invalid_flip' => 'The flip direction specified is not valid.',
- 'directory_unwritable' => 'The specified directory, %s, is not writable.',
-
- // ImageMagick specific messages
- 'imagemagick' => array
- (
- 'not_found' => 'The ImageMagick directory specified does not contain a required program, %s.',
- ),
-
- // GraphicsMagick specific messages
- 'graphicsmagick' => array
- (
- 'not_found' => 'The GraphicsMagick directory specified does not contain a required program, %s.',
- ),
-
- // GD specific messages
- 'gd' => array
- (
- 'requires_v2' => 'The Image library requires GD2. Please see http://php.net/gd_info for more information.',
- ),
-);
diff --git a/system/i18n/en_US/orm.php b/system/i18n/en_US/orm.php
deleted file mode 100644
index 3c5720bc..00000000
--- a/system/i18n/en_US/orm.php
+++ /dev/null
@@ -1,3 +0,0 @@
- 'Benchmarks',
- 'post_data' => 'Post Data',
- 'no_post' => 'No post data',
- 'session_data' => 'Session Data',
- 'no_session' => 'No session data',
- 'queries' => 'Database Queries',
- 'no_queries' => 'No queries',
- 'no_database' => 'Database not loaded',
- 'cookie_data' => 'Cookie Data',
- 'no_cookie' => 'No cookie data',
-);
diff --git a/system/i18n/en_US/session.php b/system/i18n/en_US/session.php
deleted file mode 100644
index ee781c65..00000000
--- a/system/i18n/en_US/session.php
+++ /dev/null
@@ -1,6 +0,0 @@
- 'The session_name, %s, is invalid. It must contain only alphanumeric characters and underscores. Also at least one letter must be present.',
-);
\ No newline at end of file
diff --git a/system/i18n/en_US/upload.php b/system/i18n/en_US/upload.php
deleted file mode 100644
index 7f6e216d..00000000
--- a/system/i18n/en_US/upload.php
+++ /dev/null
@@ -1,6 +0,0 @@
- 'The upload destination folder, %s, does not appear to be writable.',
-);
\ No newline at end of file
diff --git a/system/i18n/en_US/validation.php b/system/i18n/en_US/validation.php
deleted file mode 100644
index d98e548f..00000000
--- a/system/i18n/en_US/validation.php
+++ /dev/null
@@ -1,41 +0,0 @@
- 'Invalid validation rule used: %s',
- 'i18n_array' => 'The %s i18n key must be an array to be used with the in_lang rule',
- 'not_callable' => 'Callback %s used for Validation is not callable',
-
- // General errors
- 'unknown_error' => 'Unknown validation error while validating the %s field.',
- 'required' => 'The %s field is required.',
- 'min_length' => 'The %s field must be at least %d characters long.',
- 'max_length' => 'The %s field must be %d characters or fewer.',
- 'exact_length' => 'The %s field must be exactly %d characters.',
- 'in_array' => 'The %s field must be selected from the options listed.',
- 'matches' => 'The %s field must match the %s field.',
- 'valid_url' => 'The %s field must contain a valid URL.',
- 'valid_email' => 'The %s field must contain a valid email address.',
- 'valid_ip' => 'The %s field must contain a valid IP address.',
- 'valid_type' => 'The %s field must only contain %s characters.',
- 'range' => 'The %s field must be between specified ranges.',
- 'regex' => 'The %s field does not match accepted input.',
- 'depends_on' => 'The %s field depends on the %s field.',
-
- // Upload errors
- 'user_aborted' => 'The %s file was aborted during upload.',
- 'invalid_type' => 'The %s file is not an allowed file type.',
- 'max_size' => 'The %s file you uploaded was too large. The maximum size allowed is %s.',
- 'max_width' => 'The %s file you uploaded was too big. The maximum allowed width is %spx.',
- 'max_height' => 'The %s file you uploaded was too big. The maximum allowed height is %spx.',
- 'min_width' => 'The %s file you uploaded was too small. The minimum allowed width is %spx.',
- 'min_height' => 'The %s file you uploaded was too small. The minimum allowed height is %spx.',
-
- // Field types
- 'alpha' => 'alphabetical',
- 'alpha_numeric' => 'alphabetical and numeric',
- 'alpha_dash' => 'alphabetical, dash, and underscore',
- 'digit' => 'digit',
- 'numeric' => 'numeric',
-);
diff --git a/system/libraries/Cache.php b/system/libraries/Cache.php
index 8a02a905..2ed344fa 100644
--- a/system/libraries/Cache.php
+++ b/system/libraries/Cache.php
@@ -4,20 +4,17 @@
* resources. Caches are identified by a unique string. Tagging of caches is
* also supported, and caches can be found and deleted by id or tag.
*
- * $Id: Cache.php 4321 2009-05-04 01:39:44Z kiall $
+ * $Id: Cache.php 4605 2009-09-14 17:22:21Z kiall $
*
* @package Cache
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class Cache_Core {
protected static $instances = array();
- // For garbage collection
- protected static $loaded;
-
// Configuration
protected $config;
@@ -55,7 +52,7 @@ class Cache_Core {
// Test the config group name
if (($config = Kohana::config('cache.'.$config)) === NULL)
- throw new Kohana_Exception('cache.undefined_group', $name);
+ throw new Kohana_Exception('The :group: group is not defined in your configuration.', array(':group:' => $name));
}
if (is_array($config))
@@ -77,132 +74,98 @@ class Cache_Core {
// Load the driver
if ( ! Kohana::auto_load($driver))
- throw new Kohana_Exception('core.driver_not_found', $this->config['driver'], get_class($this));
+ throw new Kohana_Exception('The :driver: driver for the :class: library could not be found',
+ array(':driver:' => $this->config['driver'], ':class:' => get_class($this)));
// Initialize the driver
$this->driver = new $driver($this->config['params']);
// Validate the driver
if ( ! ($this->driver instanceof Cache_Driver))
- throw new Kohana_Exception('core.driver_implements', $this->config['driver'], get_class($this), 'Cache_Driver');
-
- Kohana::log('debug', 'Cache Library initialized');
-
- if (Cache::$loaded !== TRUE)
- {
- $this->config['requests'] = (int) $this->config['requests'];
-
- if ($this->config['requests'] > 0 AND mt_rand(1, $this->config['requests']) === 1)
- {
- // Do garbage collection
- $this->driver->delete_expired();
+ throw new Kohana_Exception('The :driver: driver for the :library: library must implement the :interface: interface',
+ array(':driver:' => $this->config['driver'], ':library:' => get_class($this), ':interface:' => 'Cache_Driver'));
- Kohana::log('debug', 'Cache: Expired caches deleted.');
- }
-
- // Cache has been loaded once
- Cache::$loaded = TRUE;
- }
+ Kohana_Log::add('debug', 'Cache Library initialized');
}
/**
- * Fetches a cache by id. NULL is returned when a cache item is not found.
- *
- * @param string cache id
- * @return mixed cached data or NULL
+ * Set cache items
*/
- public function get($id)
+ public function set($key, $value = NULL, $tags = NULL, $lifetime = NULL)
{
- // Sanitize the ID
- $id = $this->sanitize_id($id);
+ if ($lifetime === NULL)
+ {
+ $lifetime = $this->config['lifetime'];
+ }
- return $this->driver->get($id);
- }
+ if ( ! is_array($key))
+ {
+ $key = array($key => $value);
+ }
- /**
- * Fetches all of the caches for a given tag. An empty array will be
- * returned when no matching caches are found.
- *
- * @param string cache tag
- * @return array all cache items matching the tag
- */
- public function find($tag)
- {
- return $this->driver->find($tag);
+ return $this->driver->set($key, $tags, $lifetime);
}
/**
- * Set a cache item by id. Tags may also be added and a custom lifetime
- * can be set. Non-string data is automatically serialized.
- *
- * @param string unique cache id
- * @param mixed data to cache
- * @param array|string tags for this item
- * @param integer number of seconds until the cache expires
- * @return boolean
+ * Get a cache items by key
*/
- function set($id, $data, $tags = NULL, $lifetime = NULL)
+ public function get($keys)
{
- if (is_resource($data))
- throw new Kohana_Exception('cache.resources');
-
- // Sanitize the ID
- $id = $this->sanitize_id($id);
+ $single = FALSE;
- if ($lifetime === NULL)
+ if ( ! is_array($keys))
{
- // Get the default lifetime
- $lifetime = $this->config['lifetime'];
+ $keys = array($keys);
+ $single = TRUE;
}
- return $this->driver->set($id, $data, (array) $tags, $lifetime);
+ return $this->driver->get($keys, $single);
}
/**
- * Delete a cache item by id.
- *
- * @param string cache id
- * @return boolean
+ * Get cache items by tags
*/
- public function delete($id)
+ public function get_tag($tags)
{
- // Sanitize the ID
- $id = $this->sanitize_id($id);
+ if ( ! is_array($tags))
+ {
+ $tags = array($tags);
+ }
- return $this->driver->delete($id);
+ return $this->driver->get_tag($tags);
}
/**
- * Delete all cache items with a given tag.
- *
- * @param string cache tag name
- * @return boolean
+ * Delete cache item by key
*/
- public function delete_tag($tag)
+ public function delete($keys)
{
- return $this->driver->delete($tag, TRUE);
+ if ( ! is_array($keys))
+ {
+ $keys = array($keys);
+ }
+
+ return $this->driver->delete($keys);
}
/**
- * Delete ALL cache items items.
- *
- * @return boolean
+ * Delete cache items by tag
*/
- public function delete_all()
+ public function delete_tag($tags)
{
- return $this->driver->delete(TRUE);
+ if ( ! is_array($tags))
+ {
+ $tags = array($tags);
+ }
+
+ return $this->driver->delete_tag($tags);
}
/**
- * Replaces troublesome characters with underscores.
- *
- * @param string cache id
- * @return string
+ * Empty the cache
*/
- protected function sanitize_id($id)
+ public function delete_all()
{
- // Change slashes and spaces to underscores
- return str_replace(array('/', '\\', ' '), '_', $id);
+ return $this->driver->delete_all();
}
-
-} // End Cache
+} // End Cache Library
\ No newline at end of file
diff --git a/system/libraries/Cache_Exception.php b/system/libraries/Cache_Exception.php
new file mode 100644
index 00000000..040d086d
--- /dev/null
+++ b/system/libraries/Cache_Exception.php
@@ -0,0 +1,12 @@
+ TRUE,
- 'persistent' => FALSE,
- 'connection' => '',
- 'character_set' => 'utf8',
- 'table_prefix' => '',
- 'object' => TRUE,
- 'cache' => FALSE,
- 'escape' => TRUE,
- );
-
- // Database driver object
- protected $driver;
- protected $link;
-
- // Un-compiled parts of the SQL query
- protected $select = array();
- protected $set = array();
- protected $from = array();
- protected $join = array();
- protected $where = array();
- protected $orderby = array();
- protected $order = array();
- protected $groupby = array();
- protected $having = array();
- protected $distinct = FALSE;
- protected $limit = FALSE;
- protected $offset = FALSE;
- protected $last_query = '';
-
- // Stack of queries for push/pop
- protected $query_history = array();
+ // Last execute query
+ protected $last_query;
+
+ // Configuration array
+ protected $config;
+
+ // Required configuration keys
+ protected $config_required = array();
+
+ // Raw server connection
+ protected $connection;
+
+ // Cache (Cache object for cross-request, array for per-request)
+ protected $cache;
+
+ // Quote character to use for identifiers (tables/columns/aliases)
+ protected $quote = '"';
/**
* Returns a singleton instance of Database.
*
- * @param mixed configuration array or DSN
+ * @param string Database name
* @return Database_Core
*/
- public static function & instance($name = 'default', $config = NULL)
+ public static function instance($name = 'default')
{
if ( ! isset(Database::$instances[$name]))
{
- // Create a new instance
- Database::$instances[$name] = new Database($config === NULL ? $name : $config);
+ // Load the configuration for this database group
+ $config = Kohana::config('database.'.$name);
+
+ if (is_string($config['connection']))
+ {
+ // Parse the DSN into connection array
+ $config['connection'] = Database::parse_dsn($config['connection']);
+ }
+
+ // Set the driver class name
+ $driver = 'Database_'.ucfirst($config['connection']['type']);
+
+ // Create the database connection instance
+ Database::$instances[$name] = new $driver($config);
}
return Database::$instances[$name];
}
/**
- * Returns the name of a given database instance.
+ * Constructs a new Database object
*
- * @param Database instance of Database
- * @return string
+ * @param array Database config array
+ * @return Database_Core
*/
- public static function instance_name(Database $db)
+ protected function __construct(array $config)
+ {
+ // Store the config locally
+ $this->config = $config;
+
+ if ($this->config['cache'] !== FALSE)
+ {
+ if (is_string($this->config['cache']))
+ {
+ // Use Cache library
+ $this->cache = new Cache($this->config['cache']);
+ }
+ elseif ($this->config['cache'] === TRUE)
+ {
+ // Use array
+ $this->cache = array();
+ }
+ }
+ }
+
+ public function __destruct()
{
- return array_search($db, Database::$instances, TRUE);
+ $this->disconnect();
}
/**
- * Sets up the database configuration, loads the Database_Driver.
+ * Connects to the database
+ *
+ * @return void
+ */
+ abstract public function connect();
+
+ /**
+ * Disconnects from the database
+ *
+ * @return void
+ */
+ abstract public function disconnect();
+
+ /**
+ * Sets the character set
+ *
+ * @return void
+ */
+ abstract public function set_charset($charset);
+
+ /**
+ * Executes the query
+ *
+ * @param string SQL
+ * @return Database_Result
+ */
+ abstract public function query_execute($sql);
+
+ /**
+ * Escapes the given value
+ *
+ * @param mixed Value
+ * @return mixed Escaped value
+ */
+ abstract public function escape($value);
+
+ /**
+ * List constraints for the given table
+ *
+ * @param string Table name
+ * @return array
+ */
+ abstract public function list_constraints($table);
+
+ /**
+ * List fields for the given table
+ *
+ * @param string Table name
+ * @return array
+ */
+ abstract public function list_fields($table);
+
+ /**
+ * List tables for the given connection (checks for prefix)
+ *
+ * @return array
+ */
+ abstract public function list_tables();
+
+ /**
+ * Converts the given DSN string to an array of database connection components
*
- * @throws Kohana_Database_Exception
+ * @param string DSN string
+ * @return array
*/
- public function __construct($config = array())
+ public static function parse_dsn($dsn)
{
- if (empty($config))
+ $db = array
+ (
+ 'type' => FALSE,
+ 'user' => FALSE,
+ 'pass' => FALSE,
+ 'host' => FALSE,
+ 'port' => FALSE,
+ 'socket' => FALSE,
+ 'database' => FALSE
+ );
+
+ // Get the protocol and arguments
+ list ($db['type'], $connection) = explode('://', $dsn, 2);
+
+ if ($connection[0] === '/')
{
- // Load the default group
- $config = Kohana::config('database.default');
+ // Strip leading slash
+ $db['database'] = substr($connection, 1);
}
- elseif (is_array($config) AND count($config) > 0)
+ else
{
- if ( ! array_key_exists('connection', $config))
+ $connection = parse_url('http://'.$connection);
+
+ if (isset($connection['user']))
{
- $config = array('connection' => $config);
+ $db['user'] = $connection['user'];
}
- }
- elseif (is_string($config))
- {
- // The config is a DSN string
- if (strpos($config, '://') !== FALSE)
+
+ if (isset($connection['pass']))
{
- $config = array('connection' => $config);
+ $db['pass'] = $connection['pass'];
}
- // The config is a group name
- else
- {
- $name = $config;
- // Test the config group name
- if (($config = Kohana::config('database.'.$config)) === NULL)
- throw new Kohana_Database_Exception('database.undefined_group', $name);
+ if (isset($connection['port']))
+ {
+ $db['port'] = $connection['port'];
}
- }
- // Merge the default config with the passed config
- $this->config = array_merge($this->config, $config);
-
- if (is_string($this->config['connection']))
- {
- // Make sure the connection is valid
- if (strpos($this->config['connection'], '://') === FALSE)
- throw new Kohana_Database_Exception('database.invalid_dsn', $this->config['connection']);
-
- // Parse the DSN, creating an array to hold the connection parameters
- $db = array
- (
- 'type' => FALSE,
- 'user' => FALSE,
- 'pass' => FALSE,
- 'host' => FALSE,
- 'port' => FALSE,
- 'socket' => FALSE,
- 'database' => FALSE
- );
-
- // Get the protocol and arguments
- list ($db['type'], $connection) = explode('://', $this->config['connection'], 2);
-
- if (strpos($connection, '@') !== FALSE)
+ if (isset($connection['host']))
{
- // Get the username and password
- list ($db['pass'], $connection) = explode('@', $connection, 2);
- // Check if a password is supplied
- $logindata = explode(':', $db['pass'], 2);
- $db['pass'] = (count($logindata) > 1) ? $logindata[1] : '';
- $db['user'] = $logindata[0];
-
- // Prepare for finding the database
- $connection = explode('/', $connection);
-
- // Find the database name
- $db['database'] = array_pop($connection);
-
- // Reset connection string
- $connection = implode('/', $connection);
-
- // Find the socket
- if (preg_match('/^unix\([^)]++\)/', $connection))
+ if ($connection['host'] === 'unix(')
{
- // This one is a little hairy: we explode based on the end of
- // the socket, removing the 'unix(' from the connection string
- list ($db['socket'], $connection) = explode(')', substr($connection, 5), 2);
- }
- elseif (strpos($connection, ':') !== FALSE)
- {
- // Fetch the host and port name
- list ($db['host'], $db['port']) = explode(':', $connection, 2);
+ list($db['socket'], $connection['path']) = explode(')', $connection['path'], 2);
}
else
{
- $db['host'] = $connection;
+ $db['host'] = $connection['host'];
}
}
- else
- {
- // File connection
- $connection = explode('/', $connection);
- // Find database file name
- $db['database'] = array_pop($connection);
-
- // Find database directory name
- $db['socket'] = implode('/', $connection).'/';
+ if (isset($connection['path']) AND $connection['path'])
+ {
+ // Strip leading slash
+ $db['database'] = substr($connection['path'], 1);
}
-
- // Reset the connection array to the database config
- $this->config['connection'] = $db;
}
- // Set driver name
- $driver = 'Database_'.ucfirst($this->config['connection']['type']).'_Driver';
-
- // Load the driver
- if ( ! Kohana::auto_load($driver))
- throw new Kohana_Database_Exception('core.driver_not_found', $this->config['connection']['type'], get_class($this));
- // Initialize the driver
- $this->driver = new $driver($this->config);
-
- // Validate the driver
- if ( ! ($this->driver instanceof Database_Driver))
- throw new Kohana_Database_Exception('core.driver_implements', $this->config['connection']['type'], get_class($this), 'Database_Driver');
-
- Kohana::log('debug', 'Database Library initialized');
+ return $db;
}
/**
- * Simple connect method to get the database queries up and running.
+ * Returns the last executed query for this database
*
- * @return void
+ * @return string
*/
- public function connect()
+ public function last_query()
{
- // A link can be a resource or an object
- if ( ! is_resource($this->link) AND ! is_object($this->link))
- {
- $this->link = $this->driver->connect();
- if ( ! is_resource($this->link) AND ! is_object($this->link))
- throw new Kohana_Database_Exception('database.connection', $this->driver->show_error());
-
- // Clear password after successful connect
- $this->config['connection']['pass'] = NULL;
- }
+ return $this->last_query;
}
/**
- * Runs a query into the driver and returns the result.
+ * Executes the given query, returning the cached version if enabled
*
- * @param string SQL query to execute
- * @return Database_Result
+ * @param string SQL query
+ * @return Database_Result
*/
- public function query($sql = '')
+ public function query($sql)
{
- if ($sql == '') return FALSE;
-
- // No link? Connect!
- $this->link or $this->connect();
-
// Start the benchmark
$start = microtime(TRUE);
- if (func_num_args() > 1) //if we have more than one argument ($sql)
+ if (is_array($this->cache))
{
- $argv = func_get_args();
- $binds = (is_array(next($argv))) ? current($argv) : array_slice($argv, 1);
- }
+ $hash = $this->query_hash($sql);
+
+ if (isset($this->cache[$hash]))
+ {
+ // Use cached result
+ $result = $this->cache[$hash];
- // Compile binds if needed
- if (isset($binds))
+ // It's from cache
+ $sql .= ' [CACHE]';
+ }
+ else
+ {
+ // No cache, execute query and store in cache
+ $result = $this->cache[$hash] = $this->query_execute($sql);
+ }
+ }
+ else
{
- $sql = $this->compile_binds($sql, $binds);
+ // Execute the query, cache is off
+ $result = $this->query_execute($sql);
}
- // Fetch the result
- $result = $this->driver->query($this->last_query = $sql);
-
// Stop the benchmark
$stop = microtime(TRUE);
- if ($this->config['benchmark'] == TRUE)
+ if ($this->config['benchmark'] === TRUE)
{
// Benchmark the query
Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result));
@@ -271,1187 +285,358 @@ class Database_Core {
}
/**
- * Selects the column names for a database query.
+ * Performs the query on the cache (and caches it if it's not found)
*
- * @param string string or array of column names to select
- * @return Database_Core This Database object.
+ * @param string query
+ * @param int time-to-live (NULL for Cache default)
+ * @return Database_Cache_Result
*/
- public function select($sql = '*')
+ public function query_cache($sql, $ttl)
{
- if (func_num_args() > 1)
+ if ( ! $this->cache instanceof Cache)
{
- $sql = func_get_args();
+ throw new Database_Exception('Database :name has not been configured to use the Cache library.');
}
- elseif (is_string($sql))
+
+ // Start the benchmark
+ $start = microtime(TRUE);
+
+ $hash = $this->query_hash($sql);
+
+ if (($data = $this->cache->get($hash)) !== NULL)
{
- $sql = explode(',', $sql);
+ // Found in cache, create result
+ $result = new Database_Cache_Result($data, $sql, $this->config['object']);
+
+ // It's from the cache
+ $sql .= ' [CACHE]';
}
else
{
- $sql = (array) $sql;
- }
-
- foreach ($sql as $val)
- {
- if (($val = trim($val)) === '') continue;
+ // Run the query and return the full array of rows
+ $data = $this->query_execute($sql)->as_array(TRUE);
- if (strpos($val, '(') === FALSE AND $val !== '*')
- {
- if (preg_match('/^DISTINCT\s++(.+)$/i', $val, $matches))
- {
- // Only prepend with table prefix if table name is specified
- $val = (strpos($matches[1], '.') !== FALSE) ? $this->config['table_prefix'].$matches[1] : $matches[1];
+ // Set the Cache
+ $this->cache->set($hash, $data, NULL, $ttl);
- $this->distinct = TRUE;
- }
- else
- {
- $val = (strpos($val, '.') !== FALSE) ? $this->config['table_prefix'].$val : $val;
- }
+ // Create result
+ $result = new Database_Cache_Result($data, $sql, $this->config['object']);
+ }
- $val = $this->driver->escape_column($val);
- }
+ // Stop the benchmark
+ $stop = microtime(TRUE);
- $this->select[] = $val;
+ if ($this->config['benchmark'] === TRUE)
+ {
+ // Benchmark the query
+ Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result));
}
- return $this;
+ return $result;
}
/**
- * Selects the from table(s) for a database query.
+ * Generates a hash for the given query
*
- * @param string string or array of tables to select
- * @return Database_Core This Database object.
+ * @param string SQL query string
+ * @return string
*/
- public function from($sql)
+ protected function query_hash($sql)
{
- if (func_num_args() > 1)
- {
- $sql = func_get_args();
- }
- elseif (is_string($sql))
- {
- $sql = explode(',', $sql);
- }
- else
- {
- $sql = array($sql);
- }
-
- foreach ($sql as $val)
- {
- if (is_string($val))
- {
- if (($val = trim($val)) === '') continue;
-
- // TODO: Temporary solution, this should be moved to database driver (AS is checked for twice)
- if (stripos($val, ' AS ') !== FALSE)
- {
- $val = str_ireplace(' AS ', ' AS ', $val);
-
- list($table, $alias) = explode(' AS ', $val);
-
- // Attach prefix to both sides of the AS
- $val = $this->config['table_prefix'].$table.' AS '.$this->config['table_prefix'].$alias;
- }
- else
- {
- $val = $this->config['table_prefix'].$val;
- }
- }
-
- $this->from[] = $val;
- }
-
- return $this;
+ return sha1(str_replace("\n", ' ', trim($sql)));
}
/**
- * Generates the JOIN portion of the query.
+ * Clears the internal query cache.
*
- * @param string table name
- * @param string|array where key or array of key => value pairs
- * @param string where value
- * @param string type of join
- * @return Database_Core This Database object.
+ * @param mixed clear cache by SQL statement, NULL for all, or TRUE for last query
+ * @return Database
*/
- public function join($table, $key, $value = NULL, $type = '')
+ public function clear_cache($sql = NULL)
{
- $join = array();
-
- if ( ! empty($type))
+ if ($this->cache instanceof Cache)
{
- $type = strtoupper(trim($type));
-
- if ( ! in_array($type, array('LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'), TRUE))
+ // Using cross-request Cache library
+ if ($sql === TRUE)
+ {
+ $this->cache->delete($this->query_hash($this->last_query));
+ }
+ elseif (is_string($sql))
{
- $type = '';
+ $this->cache->delete($this->query_hash($sql));
}
else
{
- $type .= ' ';
+ $this->cache->delete_all();
}
}
-
- $cond = array();
- $keys = is_array($key) ? $key : array($key => $value);
- foreach ($keys as $key => $value)
+ elseif (is_array($this->cache))
{
- $key = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
-
- if (is_string($value))
+ // Using per-request memory cache
+ if ($sql === TRUE)
{
- // Only escape if it's a string
- $value = $this->driver->escape_column($this->config['table_prefix'].$value);
+ unset($this->cache[$this->query_hash($this->last_query)]);
}
-
- $cond[] = $this->driver->where($key, $value, 'AND ', count($cond), FALSE);
- }
-
- if ( ! is_array($this->join))
- {
- $this->join = array();
- }
-
- if ( ! is_array($table))
- {
- $table = array($table);
- }
-
- foreach ($table as $t)
- {
- if (is_string($t))
+ elseif (is_string($sql))
{
- // TODO: Temporary solution, this should be moved to database driver (AS is checked for twice)
- if (stripos($t, ' AS ') !== FALSE)
- {
- $t = str_ireplace(' AS ', ' AS ', $t);
-
- list($table, $alias) = explode(' AS ', $t);
-
- // Attach prefix to both sides of the AS
- $t = $this->config['table_prefix'].$table.' AS '.$this->config['table_prefix'].$alias;
- }
- else
- {
- $t = $this->config['table_prefix'].$t;
- }
+ unset($this->cache[$this->query_hash($sql)]);
+ }
+ else
+ {
+ $this->cache = array();
}
-
- $join['tables'][] = $this->driver->escape_column($t);
}
-
- $join['conditions'] = '('.trim(implode(' ', $cond)).')';
- $join['type'] = $type;
-
- $this->join[] = $join;
-
- return $this;
}
-
/**
- * Selects the where(s) for a database query.
+ * Quotes the given value
*
- * @param string|array key name or array of key => value pairs
- * @param string value to match with key
- * @param boolean disable quoting of WHERE clause
- * @return Database_Core This Database object.
+ * @param mixed value
+ * @return mixed
*/
- public function where($key, $value = NULL, $quote = TRUE)
+ public function quote($value)
{
- $quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote;
- if (is_object($key))
+ if ( ! $this->config['escape'])
+ return $value;
+
+ if ($value === NULL)
{
- $keys = array((string) $key => '');
+ return 'NULL';
}
- elseif ( ! is_array($key))
+ elseif ($value === TRUE)
{
- $keys = array($key => $value);
+ return 'TRUE';
}
- else
+ elseif ($value === FALSE)
{
- $keys = $key;
+ return 'FALSE';
}
-
- foreach ($keys as $key => $value)
+ elseif (is_int($value))
+ {
+ return (int) $value;
+ }
+ elseif ($value instanceof Database_Expression)
{
- $key = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
- $this->where[] = $this->driver->where($key, $value, 'AND ', count($this->where), $quote);
+ return (string) $value;
}
- return $this;
+ return '\''.$this->escape($value).'\'';
}
/**
- * Selects the or where(s) for a database query.
+ * Quotes a table, adding the table prefix
+ * Reserved characters not allowed in table names for the builder are [ .*] (space, dot, asterisk)
*
- * @param string|array key name or array of key => value pairs
- * @param string value to match with key
- * @param boolean disable quoting of WHERE clause
- * @return Database_Core This Database object.
+ * @param string|array table name or array - 'users u' or array('u' => 'users') both valid
+ * @param string table alias
+ * @return string
*/
- public function orwhere($key, $value = NULL, $quote = TRUE)
+ public function quote_table($table, $alias = NULL)
{
- $quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote;
- if (is_object($key))
+ if (is_array($table))
{
- $keys = array((string) $key => '');
+ // Using array('u' => 'user')
+ list($alias, $table) = each($table);
}
- elseif ( ! is_array($key))
+ elseif (strpos(' ', $table) !== FALSE)
{
- $keys = array($key => $value);
+ // Using format 'user u'
+ list($table, $alias) = explode(' ', $table);
}
- else
+
+ if ($table instanceof Database_Expression)
{
- $keys = $key;
+ if ($alias)
+ {
+ if ($this->config['escape'])
+ {
+ $alias = $this->quote.$alias.$this->quote;
+ }
+
+ return $table.' AS '.$alias;
+ }
+
+ return (string) $table;
}
- foreach ($keys as $key => $value)
+ if ($this->config['table_prefix'])
{
- $key = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
- $this->where[] = $this->driver->where($key, $value, 'OR ', count($this->where), $quote);
+ $table = $this->config['table_prefix'].$table;
}
- return $this;
- }
+ if ($alias)
+ {
+ if ($this->config['escape'])
+ {
+ $table = $this->quote.$table.$this->quote;
+ $alias = $this->quote.$alias.$this->quote;
+ }
- /**
- * Selects the like(s) for a database query.
- *
- * @param string|array field name or array of field => match pairs
- * @param string like value to match with field
- * @param boolean automatically add starting and ending wildcards
- * @return Database_Core This Database object.
- */
- public function like($field, $match = '', $auto = TRUE)
- {
- $fields = is_array($field) ? $field : array($field => $match);
+ return $table.' AS '.$alias;
+ }
- foreach ($fields as $field => $match)
+ if ($this->config['escape'])
{
- $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
- $this->where[] = $this->driver->like($field, $match, $auto, 'AND ', count($this->where));
+ $table = $this->quote.$table.$this->quote;
}
- return $this;
+ return $table;
}
/**
- * Selects the or like(s) for a database query.
+ * Quotes column or table.column, adding the table prefix if necessary
+ * Reserved characters not allowed in table names for the builder are [ .*] (space, dot, asterisk)
+ * Complex column names must have table/columns in double quotes, e.g. array('mycount' => 'COUNT("users.id")')
*
- * @param string|array field name or array of field => match pairs
- * @param string like value to match with field
- * @param boolean automatically add starting and ending wildcards
- * @return Database_Core This Database object.
+ * @param string|array column name or array('u' => 'COUNT("*")')
+ * @param string column alias
+ * @return string
*/
- public function orlike($field, $match = '', $auto = TRUE)
+ public function quote_column($column, $alias = NULL)
{
- $fields = is_array($field) ? $field : array($field => $match);
+ if ($column === '*')
+ return $column;
- foreach ($fields as $field => $match)
+ if (is_array($column))
{
- $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
- $this->where[] = $this->driver->like($field, $match, $auto, 'OR ', count($this->where));
+ list($alias, $column) = each($column);
}
- return $this;
- }
+ if ($column instanceof Database_Expression)
+ {
+ if ($alias)
+ {
+ if ($this->config['escape'])
+ {
+ $alias = $this->quote.$alias.$this->quote;
+ }
- /**
- * Selects the not like(s) for a database query.
- *
- * @param string|array field name or array of field => match pairs
- * @param string like value to match with field
- * @param boolean automatically add starting and ending wildcards
- * @return Database_Core This Database object.
- */
- public function notlike($field, $match = '', $auto = TRUE)
- {
- $fields = is_array($field) ? $field : array($field => $match);
+ return $column.' AS '.$alias;
+ }
+
+ return (string) $column;
+ }
- foreach ($fields as $field => $match)
+ if ($this->config['table_prefix'] AND strpos($column, '.') !== FALSE)
{
- $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
- $this->where[] = $this->driver->notlike($field, $match, $auto, 'AND ', count($this->where));
+ if (strpos($column, '"') !== FALSE)
+ {
+ // Find "table.column" and replace them with "[prefix]table.column"
+ $column = preg_replace('/"([^.]++)\.([^"]++)"/', '"'.$this->config['table_prefix'].'$1.$2"', $column);
+ }
+ else
+ {
+ // Attach table prefix if table.column format
+ $column = $this->config['table_prefix'].$column;
+ }
}
- return $this;
- }
+ if ($this->config['escape'])
+ {
+ if (strpos($column, '"') === FALSE)
+ {
+ // Quote the column
+ $column = $this->quote.$column.$this->quote;
+ }
+ elseif ($this->quote !== '"')
+ {
+ // Replace double quotes
+ $column = str_replace('"', $this->quote, $column);
+ }
- /**
- * Selects the or not like(s) for a database query.
- *
- * @param string|array field name or array of field => match pairs
- * @param string like value to match with field
- * @return Database_Core This Database object.
- */
- public function ornotlike($field, $match = '', $auto = TRUE)
- {
- $fields = is_array($field) ? $field : array($field => $match);
+ // Replace . with "."
+ $column = str_replace('.', $this->quote.'.'.$this->quote, $column);
- foreach ($fields as $field => $match)
- {
- $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
- $this->where[] = $this->driver->notlike($field, $match, $auto, 'OR ', count($this->where));
+ // Unescape any asterisks
+ $column = str_replace($this->quote.'*'.$this->quote, '*', $column);
+
+ if ($alias)
+ {
+ // Quote the alias
+ return $column.' AS '.$this->quote.$alias.$this->quote;
+ }
+
+ return $column;
}
- return $this;
+ // Strip double quotes
+ $column = str_replace('"', '', $column);
+
+ if ($alias)
+ return $column.' AS '.$alias;
+
+ return $column;
}
/**
- * Selects the like(s) for a database query.
+ * Get the table prefix
*
- * @param string|array field name or array of field => match pairs
- * @param string like value to match with field
- * @return Database_Core This Database object.
+ * @param string Optional new table prefix to set
+ * @return string
*/
- public function regex($field, $match = '')
+ public function table_prefix($new_prefix = NULL)
{
- $fields = is_array($field) ? $field : array($field => $match);
+ $prefix = $this->config['table_prefix'];
- foreach ($fields as $field => $match)
+ if ($new_prefix !== NULL)
{
- $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
- $this->where[] = $this->driver->regex($field, $match, 'AND ', count($this->where));
+ // Set a new prefix
+ $this->config['table_prefix'] = $new_prefix;
}
- return $this;
+ return $prefix;
}
/**
- * Selects the or like(s) for a database query.
+ * Fetches SQL type information about a field, in a generic format.
*
- * @param string|array field name or array of field => match pairs
- * @param string like value to match with field
- * @return Database_Core This Database object.
+ * @param string field datatype
+ * @return array
*/
- public function orregex($field, $match = '')
+ protected function sql_type($str)
{
- $fields = is_array($field) ? $field : array($field => $match);
+ static $sql_types;
- foreach ($fields as $field => $match)
+ if ($sql_types === NULL)
{
- $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
- $this->where[] = $this->driver->regex($field, $match, 'OR ', count($this->where));
+ // Load SQL data types
+ $sql_types = Kohana::config('sql_types');
}
- return $this;
- }
+ $str = trim($str);
- /**
- * Selects the not regex(s) for a database query.
- *
- * @param string|array field name or array of field => match pairs
- * @param string regex value to match with field
- * @return Database_Core This Database object.
- */
- public function notregex($field, $match = '')
- {
- $fields = is_array($field) ? $field : array($field => $match);
+ if (($open = strpos($str, '(')) !== FALSE)
+ {
+ // Closing bracket
+ $close = strpos($str, ')', $open);
+
+ // Length without brackets
+ $length = substr($str, $open + 1, $close - 1 - $open);
- foreach ($fields as $field => $match)
+ // Type without the length
+ $type = substr($str, 0, $open).substr($str, $close + 1);
+ }
+ else
{
- $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
- $this->where[] = $this->driver->notregex($field, $match, 'AND ', count($this->where));
+ // No length
+ $type = $str;
}
- return $this;
- }
+ if (empty($sql_types[$type]))
+ throw new Database_Exception('Undefined field type :type', array(':type' => $str));
- /**
- * Selects the or not regex(s) for a database query.
- *
- * @param string|array field name or array of field => match pairs
- * @param string regex value to match with field
- * @return Database_Core This Database object.
- */
- public function ornotregex($field, $match = '')
- {
- $fields = is_array($field) ? $field : array($field => $match);
+ // Fetch the field definition
+ $field = $sql_types[$type];
+
+ $field['sql_type'] = $type;
- foreach ($fields as $field => $match)
+ if (isset($length))
{
- $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
- $this->where[] = $this->driver->notregex($field, $match, 'OR ', count($this->where));
+ // Add the length to the field info
+ $field['length'] = $length;
}
- return $this;
+ return $field;
}
- /**
- * Chooses the column to group by in a select query.
- *
- * @param string column name to group by
- * @return Database_Core This Database object.
- */
- public function groupby($by)
- {
- if ( ! is_array($by))
- {
- $by = explode(',', (string) $by);
- }
-
- foreach ($by as $val)
- {
- $val = trim($val);
-
- if ($val != '')
- {
- // Add the table prefix if we are using table.column names
- if(strpos($val, '.'))
- {
- $val = $this->config['table_prefix'].$val;
- }
-
- $this->groupby[] = $this->driver->escape_column($val);
- }
- }
-
- return $this;
- }
-
- /**
- * Selects the having(s) for a database query.
- *
- * @param string|array key name or array of key => value pairs
- * @param string value to match with key
- * @param boolean disable quoting of WHERE clause
- * @return Database_Core This Database object.
- */
- public function having($key, $value = '', $quote = TRUE)
- {
- $this->having[] = $this->driver->where($key, $value, 'AND', count($this->having), TRUE);
- return $this;
- }
-
- /**
- * Selects the or having(s) for a database query.
- *
- * @param string|array key name or array of key => value pairs
- * @param string value to match with key
- * @param boolean disable quoting of WHERE clause
- * @return Database_Core This Database object.
- */
- public function orhaving($key, $value = '', $quote = TRUE)
- {
- $this->having[] = $this->driver->where($key, $value, 'OR', count($this->having), TRUE);
- return $this;
- }
-
- /**
- * Chooses which column(s) to order the select query by.
- *
- * @param string|array column(s) to order on, can be an array, single column, or comma seperated list of columns
- * @param string direction of the order
- * @return Database_Core This Database object.
- */
- public function orderby($orderby, $direction = NULL)
- {
- if ( ! is_array($orderby))
- {
- $orderby = array($orderby => $direction);
- }
-
- foreach ($orderby as $column => $direction)
- {
- $direction = strtoupper(trim($direction));
-
- // Add a direction if the provided one isn't valid
- if ( ! in_array($direction, array('ASC', 'DESC', 'RAND()', 'RANDOM()', 'NULL')))
- {
- $direction = 'ASC';
- }
-
- // Add the table prefix if a table.column was passed
- if (strpos($column, '.'))
- {
- $column = $this->config['table_prefix'].$column;
- }
-
- $this->orderby[] = $this->driver->escape_column($column).' '.$direction;
- }
-
- return $this;
- }
-
- /**
- * Selects the limit section of a query.
- *
- * @param integer number of rows to limit result to
- * @param integer offset in result to start returning rows from
- * @return Database_Core This Database object.
- */
- public function limit($limit, $offset = NULL)
- {
- $this->limit = (int) $limit;
-
- if ($offset !== NULL OR ! is_int($this->offset))
- {
- $this->offset($offset);
- }
-
- return $this;
- }
-
- /**
- * Sets the offset portion of a query.
- *
- * @param integer offset value
- * @return Database_Core This Database object.
- */
- public function offset($value)
- {
- $this->offset = (int) $value;
-
- return $this;
- }
-
- /**
- * Allows key/value pairs to be set for inserting or updating.
- *
- * @param string|array key name or array of key => value pairs
- * @param string value to match with key
- * @return Database_Core This Database object.
- */
- public function set($key, $value = '')
- {
- if ( ! is_array($key))
- {
- $key = array($key => $value);
- }
-
- foreach ($key as $k => $v)
- {
- // Add a table prefix if the column includes the table.
- if (strpos($k, '.'))
- $k = $this->config['table_prefix'].$k;
-
- $this->set[$k] = $this->driver->escape($v);
- }
-
- return $this;
- }
-
- /**
- * Compiles the select statement based on the other functions called and runs the query.
- *
- * @param string table name
- * @param string limit clause
- * @param string offset clause
- * @return Database_Result
- */
- public function get($table = '', $limit = NULL, $offset = NULL)
- {
- if ($table != '')
- {
- $this->from($table);
- }
-
- if ( ! is_null($limit))
- {
- $this->limit($limit, $offset);
- }
-
- $sql = $this->driver->compile_select(get_object_vars($this));
-
- $this->reset_select();
-
- $result = $this->query($sql);
-
- $this->last_query = $sql;
-
- return $result;
- }
-
- /**
- * Compiles the select statement based on the other functions called and runs the query.
- *
- * @param string table name
- * @param array where clause
- * @param string limit clause
- * @param string offset clause
- * @return Database_Core This Database object.
- */
- public function getwhere($table = '', $where = NULL, $limit = NULL, $offset = NULL)
- {
- if ($table != '')
- {
- $this->from($table);
- }
-
- if ( ! is_null($where))
- {
- $this->where($where);
- }
-
- if ( ! is_null($limit))
- {
- $this->limit($limit, $offset);
- }
-
- $sql = $this->driver->compile_select(get_object_vars($this));
-
- $this->reset_select();
-
- $result = $this->query($sql);
-
- return $result;
- }
-
- /**
- * Compiles the select statement based on the other functions called and returns the query string.
- *
- * @param string table name
- * @param string limit clause
- * @param string offset clause
- * @return string sql string
- */
- public function compile($table = '', $limit = NULL, $offset = NULL)
- {
- if ($table != '')
- {
- $this->from($table);
- }
-
- if ( ! is_null($limit))
- {
- $this->limit($limit, $offset);
- }
-
- $sql = $this->driver->compile_select(get_object_vars($this));
-
- $this->reset_select();
-
- return $sql;
- }
-
- /**
- * Compiles an insert string and runs the query.
- *
- * @param string table name
- * @param array array of key/value pairs to insert
- * @return Database_Result Query result
- */
- public function insert($table = '', $set = NULL)
- {
- if ( ! is_null($set))
- {
- $this->set($set);
- }
-
- if ($this->set == NULL)
- throw new Kohana_Database_Exception('database.must_use_set');
-
- if ($table == '')
- {
- if ( ! isset($this->from[0]))
- throw new Kohana_Database_Exception('database.must_use_table');
-
- $table = $this->from[0];
- }
-
- // If caching is enabled, clear the cache before inserting
- ($this->config['cache'] === TRUE) and $this->clear_cache();
-
- $sql = $this->driver->insert($this->config['table_prefix'].$table, array_keys($this->set), array_values($this->set));
-
- $this->reset_write();
-
- return $this->query($sql);
- }
-
- /**
- * Adds an "IN" condition to the where clause
- *
- * @param string Name of the column being examined
- * @param mixed An array or string to match against
- * @param bool Generate a NOT IN clause instead
- * @return Database_Core This Database object.
- */
- public function in($field, $values, $not = FALSE)
- {
- if (is_array($values))
- {
- $escaped_values = array();
- foreach ($values as $v)
- {
- if (is_numeric($v))
- {
- $escaped_values[] = $v;
- }
- else
- {
- $escaped_values[] = "'".$this->driver->escape_str($v)."'";
- }
- }
- $values = implode(",", $escaped_values);
- }
-
- $where = $this->driver->escape_column(((strpos($field,'.') !== FALSE) ? $this->config['table_prefix'] : ''). $field).' '.($not === TRUE ? 'NOT ' : '').'IN ('.$values.')';
- $this->where[] = $this->driver->where($where, '', 'AND ', count($this->where), -1);
-
- return $this;
- }
-
- /**
- * Adds a "NOT IN" condition to the where clause
- *
- * @param string Name of the column being examined
- * @param mixed An array or string to match against
- * @return Database_Core This Database object.
- */
- public function notin($field, $values)
- {
- return $this->in($field, $values, TRUE);
- }
-
- /**
- * Compiles a merge string and runs the query.
- *
- * @param string table name
- * @param array array of key/value pairs to merge
- * @return Database_Result Query result
- */
- public function merge($table = '', $set = NULL)
- {
- if ( ! is_null($set))
- {
- $this->set($set);
- }
-
- if ($this->set == NULL)
- throw new Kohana_Database_Exception('database.must_use_set');
-
- if ($table == '')
- {
- if ( ! isset($this->from[0]))
- throw new Kohana_Database_Exception('database.must_use_table');
-
- $table = $this->from[0];
- }
- else
- {
- $table = $this->config['table_prefix'].$table;
- }
-
- $sql = $this->driver->merge($table, array_keys($this->set), array_values($this->set));
-
- $this->reset_write();
- return $this->query($sql);
- }
-
- /**
- * Compiles an update string and runs the query.
- *
- * @param string table name
- * @param array associative array of update values
- * @param array where clause
- * @return Database_Result Query result
- */
- public function update($table = '', $set = NULL, $where = NULL)
- {
- if ( is_array($set))
- {
- $this->set($set);
- }
-
- if ( ! is_null($where))
- {
- $this->where($where);
- }
-
- if ($this->set == FALSE)
- throw new Kohana_Database_Exception('database.must_use_set');
-
- if ($table == '')
- {
- if ( ! isset($this->from[0]))
- throw new Kohana_Database_Exception('database.must_use_table');
-
- $table = $this->from[0];
- }
- else
- {
- $table = $this->config['table_prefix'].$table;
- }
-
- $sql = $this->driver->update($table, $this->set, $this->where);
-
- $this->reset_write();
- return $this->query($sql);
- }
-
- /**
- * Compiles a delete string and runs the query.
- *
- * @param string table name
- * @param array where clause
- * @return Database_Result Query result
- */
- public function delete($table = '', $where = NULL)
- {
- if ($table == '')
- {
- if ( ! isset($this->from[0]))
- throw new Kohana_Database_Exception('database.must_use_table');
-
- $table = $this->from[0];
- }
- else
- {
- $table = $this->config['table_prefix'].$table;
- }
-
- if (! is_null($where))
- {
- $this->where($where);
- }
-
- if (count($this->where) < 1)
- throw new Kohana_Database_Exception('database.must_use_where');
-
- $sql = $this->driver->delete($table, $this->where);
-
- $this->reset_write();
- return $this->query($sql);
- }
-
- /**
- * Returns the last query run.
- *
- * @return string SQL
- */
- public function last_query()
- {
- return $this->last_query;
- }
-
- /**
- * Count query records.
- *
- * @param string table name
- * @param array where clause
- * @return integer
- */
- public function count_records($table = FALSE, $where = NULL)
- {
- if (count($this->from) < 1)
- {
- if ($table == FALSE)
- throw new Kohana_Database_Exception('database.must_use_table');
-
- $this->from($table);
- }
-
- if ($where !== NULL)
- {
- $this->where($where);
- }
-
- $query = $this->select('COUNT(*) AS '.$this->escape_column('records_found'))->get()->result(TRUE);
-
- $query = $query->current();
-
- if ( ! $query)
- return 0;
- else
- return (int) $query->records_found;
- }
-
- /**
- * Resets all private select variables.
- *
- * @return void
- */
- protected function reset_select()
- {
- $this->select = array();
- $this->from = array();
- $this->join = array();
- $this->where = array();
- $this->orderby = array();
- $this->groupby = array();
- $this->having = array();
- $this->distinct = FALSE;
- $this->limit = FALSE;
- $this->offset = FALSE;
- }
-
- /**
- * Resets all private insert and update variables.
- *
- * @return void
- */
- protected function reset_write()
- {
- $this->set = array();
- $this->from = array();
- $this->where = array();
- }
-
- /**
- * Lists all the tables in the current database.
- *
- * @return array
- */
- public function list_tables()
- {
- $this->link or $this->connect();
-
- return $this->driver->list_tables();
- }
-
- /**
- * See if a table exists in the database.
- *
- * @param string table name
- * @param boolean True to attach table prefix
- * @return boolean
- */
- public function table_exists($table_name, $prefix = TRUE)
- {
- if ($prefix)
- return in_array($this->config['table_prefix'].$table_name, $this->list_tables());
- else
- return in_array($table_name, $this->list_tables());
- }
-
- /**
- * Combine a SQL statement with the bind values. Used for safe queries.
- *
- * @param string query to bind to the values
- * @param array array of values to bind to the query
- * @return string
- */
- public function compile_binds($sql, $binds)
- {
- foreach ((array) $binds as $val)
- {
- // If the SQL contains no more bind marks ("?"), we're done.
- if (($next_bind_pos = strpos($sql, '?')) === FALSE)
- break;
-
- // Properly escape the bind value.
- $val = $this->driver->escape($val);
-
- // Temporarily replace possible bind marks ("?"), in the bind value itself, with a placeholder.
- $val = str_replace('?', '{%B%}', $val);
-
- // Replace the first bind mark ("?") with its corresponding value.
- $sql = substr($sql, 0, $next_bind_pos).$val.substr($sql, $next_bind_pos + 1);
- }
-
- // Restore placeholders.
- return str_replace('{%B%}', '?', $sql);
- }
-
- /**
- * Get the field data for a database table, along with the field's attributes.
- *
- * @param string table name
- * @return array
- */
- public function field_data($table = '')
- {
- $this->link or $this->connect();
-
- return $this->driver->field_data($this->config['table_prefix'].$table);
- }
-
- /**
- * Get the field data for a database table, along with the field's attributes.
- *
- * @param string table name
- * @return array
- */
- public function list_fields($table = '')
- {
- $this->link or $this->connect();
-
- return $this->driver->list_fields($this->config['table_prefix'].$table);
- }
-
- /**
- * Escapes a value for a query.
- *
- * @param mixed value to escape
- * @return string
- */
- public function escape($value)
- {
- return $this->driver->escape($value);
- }
-
- /**
- * Escapes a string for a query.
- *
- * @param string string to escape
- * @return string
- */
- public function escape_str($str)
- {
- return $this->driver->escape_str($str);
- }
-
- /**
- * Escapes a table name for a query.
- *
- * @param string string to escape
- * @return string
- */
- public function escape_table($table)
- {
- return $this->driver->escape_table($table);
- }
-
- /**
- * Escapes a column name for a query.
- *
- * @param string string to escape
- * @return string
- */
- public function escape_column($table)
- {
- return $this->driver->escape_column($table);
- }
-
- /**
- * Returns table prefix of current configuration.
- *
- * @return string
- */
- public function table_prefix()
- {
- return $this->config['table_prefix'];
- }
-
- /**
- * Clears the query cache.
- *
- * @param string|TRUE clear cache by SQL statement or TRUE for last query
- * @return Database_Core This Database object.
- */
- public function clear_cache($sql = NULL)
- {
- if ($sql === TRUE)
- {
- $this->driver->clear_cache($this->last_query);
- }
- elseif (is_string($sql))
- {
- $this->driver->clear_cache($sql);
- }
- else
- {
- $this->driver->clear_cache();
- }
-
- return $this;
- }
-
- /**
- * Pushes existing query space onto the query stack. Use push
- * and pop to prevent queries from clashing before they are
- * executed
- *
- * @return Database_Core This Databaes object
- */
- public function push()
- {
- array_push($this->query_history, array(
- $this->select,
- $this->from,
- $this->join,
- $this->where,
- $this->orderby,
- $this->order,
- $this->groupby,
- $this->having,
- $this->distinct,
- $this->limit,
- $this->offset
- ));
-
- $this->reset_select();
-
- return $this;
- }
-
- /**
- * Pops from query stack into the current query space.
- *
- * @return Database_Core This Databaes object
- */
- public function pop()
- {
- if (count($this->query_history) == 0)
- {
- // No history
- return $this;
- }
-
- list(
- $this->select,
- $this->from,
- $this->join,
- $this->where,
- $this->orderby,
- $this->order,
- $this->groupby,
- $this->having,
- $this->distinct,
- $this->limit,
- $this->offset
- ) = array_pop($this->query_history);
-
- return $this;
- }
-
- /**
- * Count the number of records in the last query, without LIMIT or OFFSET applied.
- *
- * @return integer
- */
- public function count_last_query()
- {
- if ($sql = $this->last_query())
- {
- if (stripos($sql, 'LIMIT') !== FALSE)
- {
- // Remove LIMIT from the SQL
- $sql = preg_replace('/\sLIMIT\s+[^a-z]+/i', ' ', $sql);
- }
-
- if (stripos($sql, 'OFFSET') !== FALSE)
- {
- // Remove OFFSET from the SQL
- $sql = preg_replace('/\sOFFSET\s+\d+/i', '', $sql);
- }
-
- // Get the total rows from the last query executed
- $result = $this->query
- (
- 'SELECT COUNT(*) AS '.$this->escape_column('total_rows').' '.
- 'FROM ('.trim($sql).') AS '.$this->escape_table('counted_results')
- );
-
- // Return the total number of rows from the query
- return (int) $result->current()->total_rows;
- }
-
- return FALSE;
- }
-
-} // End Database Class
-
-
-/**
- * Sets the code for a Database exception.
- */
-class Kohana_Database_Exception extends Kohana_Exception {
-
- protected $code = E_DATABASE_ERROR;
-
-} // End Kohana Database Exception
+} // End Database
diff --git a/system/libraries/Database_Builder.php b/system/libraries/Database_Builder.php
new file mode 100644
index 00000000..5a5a058c
--- /dev/null
+++ b/system/libraries/Database_Builder.php
@@ -0,0 +1,938 @@
+db = $db;
+ }
+
+ /**
+ * Resets all query components
+ */
+ public function reset()
+ {
+ $this->select = array();
+ $this->from = array();
+ $this->join = array();
+ $this->where = array();
+ $this->group_by = array();
+ $this->having = array();
+ $this->order_by = array();
+ $this->limit = NULL;
+ $this->offset = NULL;
+ $this->set = array();
+ $this->values = array();
+ }
+
+ public function __toString()
+ {
+ return $this->compile();
+ }
+
+ /**
+ * Compiles the builder object into a SQL query
+ *
+ * @return string Compiled query
+ */
+ protected function compile()
+ {
+ if ( ! is_object($this->db))
+ {
+ // Use default database for compiling to string if none is given
+ $this->db = Database::instance($this->db);
+ }
+
+ if ($this->type === Database::SELECT)
+ {
+ // SELECT columns FROM table
+ $sql = 'SELECT '.$this->compile_select();
+
+ if ( ! empty($this->from))
+ {
+ $sql .= "\nFROM ".$this->compile_from();
+ }
+ }
+ elseif ($this->type === Database::UPDATE)
+ {
+ $sql = 'UPDATE '.$this->compile_from()."\n".'SET '.$this->compile_set();
+ }
+ elseif ($this->type === Database::INSERT)
+ {
+ $sql = 'INSERT INTO '.$this->compile_from()."\n".$this->compile_columns()."\nVALUES ".$this->compile_values();
+ }
+ elseif ($this->type === Database::DELETE)
+ {
+ $sql = 'DELETE FROM '.$this->compile_from();
+ }
+
+ if ( ! empty($this->join))
+ {
+ $sql .= $this->compile_join();
+ }
+
+ if ( ! empty($this->where))
+ {
+ $sql .= "\n".'WHERE '.$this->compile_conditions($this->where);
+ }
+
+ if ( ! empty($this->having))
+ {
+ $sql .= "\n".'HAVING '.$this->compile_conditions($this->having);
+ }
+
+ if ( ! empty($this->group_by))
+ {
+ $sql .= "\n".'GROUP BY '.$this->compile_group_by();
+ }
+
+ if ( ! empty($this->order_by))
+ {
+ $sql .= "\nORDER BY ".$this->compile_order_by();
+ }
+
+ if (is_int($this->limit))
+ {
+ $sql .= "\nLIMIT ".$this->limit;
+ }
+
+ if (is_int($this->offset))
+ {
+ $sql .= "\nOFFSET ".$this->offset;
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Compiles the SELECT portion of the query
+ *
+ * @return string
+ */
+ protected function compile_select()
+ {
+ $vals = array();
+
+ foreach ($this->select as $alias => $name)
+ {
+ if (is_string($alias))
+ {
+ $vals[] = $this->db->quote_column($name, $alias);
+ }
+ else
+ {
+ $vals[] = $this->db->quote_column($name);
+ }
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Compiles the FROM portion of the query
+ *
+ * @return string
+ */
+ protected function compile_from()
+ {
+ $vals = array();
+
+ foreach ($this->from as $alias => $name)
+ {
+ if (is_string($alias))
+ {
+ // Using AS format so escape both
+ $vals[] = $this->db->quote_table($name, $alias);
+ }
+ else
+ {
+ // Just using the table name itself
+ $vals[] = $this->db->quote_table($name);
+ }
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Compiles the JOIN portion of the query
+ *
+ * @return string
+ */
+ protected function compile_join()
+ {
+ $sql = '';
+ foreach ($this->join as $join)
+ {
+ list($table, $keys, $type) = $join;
+
+ if ($type !== NULL)
+ {
+ // Join type
+ $sql .= ' '.$type;
+ }
+
+ $sql .= ' JOIN '.$this->db->quote_table($table);
+
+ $condition = '';
+ if ($keys instanceof Database_Expression)
+ {
+ $condition = (string) $keys;
+ }
+ elseif (is_array($keys))
+ {
+ // ON condition is an array of matches
+ foreach ($keys as $key => $value)
+ {
+ if ( ! empty($condition))
+ {
+ $condition .= ' AND ';
+ }
+
+ $condition .= $this->db->quote_column($key).' = '.$this->db->quote_column($value);
+ }
+ }
+
+ if ( ! empty($condition))
+ {
+ // Add ON condition
+ $sql .= ' ON ('.$condition.')';
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Compiles the GROUP BY portion of the query
+ *
+ * @return string
+ */
+ protected function compile_group_by()
+ {
+ $vals = array();
+
+ foreach ($this->group_by as $column)
+ {
+ // Escape the column
+ $vals[] = $this->db->quote_column($column);
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Compiles the ORDER BY portion of the query
+ *
+ * @return string
+ */
+ public function compile_order_by()
+ {
+ $ordering = array();
+
+ foreach ($this->order_by as $column => $order_by)
+ {
+ list($column, $direction) = each($order_by);
+
+ $column = $this->db->quote_column($column);
+
+ if ($direction !== NULL)
+ {
+ $direction = ' '.$direction;
+ }
+
+ $ordering[] = $column.$direction;
+ }
+
+ return implode(', ', $ordering);
+ }
+
+ /**
+ * Compiles the SET portion of the query for UPDATE
+ *
+ * @return string
+ */
+ public function compile_set()
+ {
+ $vals = array();
+
+ foreach ($this->set as $key => $value)
+ {
+ // Using an UPDATE so Key = Val
+ $vals[] = $this->db->quote_column($key).' = '.$this->db->quote($value);
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Join tables to the builder
+ *
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @param string Join type (LEFT, RIGHT, INNER, etc.)
+ * @return Database_Builder
+ */
+ public function join($table, $keys, $value = NULL, $type = NULL)
+ {
+ if (is_string($keys))
+ {
+ $keys = array($keys => $value);
+ }
+
+ if ($type !== NULL)
+ {
+ $type = strtoupper($type);
+ }
+
+ $this->join[] = array($table, $keys, $type);
+
+ return $this;
+ }
+
+ /**
+ * Add tables to the FROM portion of the builder
+ *
+ * @param string|array table name or array(alias => table)
+ * @return Database_Builder
+ */
+ public function from($tables)
+ {
+ if ( ! is_array($tables))
+ {
+ $tables = func_get_args();
+ }
+
+ $this->from = array_merge($this->from, $tables);
+
+ return $this;
+ }
+
+ /**
+ * Add fields to the GROUP BY portion
+ *
+ * @param mixed Field names or an array of fields
+ * @return Database_Builder
+ */
+ public function group_by($columns)
+ {
+ if ( ! is_array($columns))
+ {
+ $columns = func_get_args();
+ }
+
+ $this->group_by = array_merge($this->group_by, $columns);
+
+ return $this;
+ }
+
+ /**
+ * Add conditions to the HAVING clause (AND)
+ *
+ * @param mixed Column name or array of columns => vals
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function having($columns, $op = '=', $value = NULL)
+ {
+ return $this->and_having($columns, $op, $value);
+ }
+
+ /**
+ * Add conditions to the HAVING clause (AND)
+ *
+ * @param mixed Column name or array of columns => vals
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function and_having($columns, $op = '=', $value = NULL)
+ {
+ $this->having[] = array('AND' => array($columns, $op, $value));
+ return $this;
+ }
+
+ /**
+ * Add conditions to the HAVING clause (OR)
+ *
+ * @param mixed Column name or array of columns => vals
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function or_having($columns, $op = '=', $value = NULL)
+ {
+ $this->having[] = array('OR' => array($columns, $op, $value));
+ return $this;
+ }
+
+ /**
+ * Add fields to the ORDER BY portion
+ *
+ * @param mixed Field names or an array of fields (field => direction)
+ * @param string Direction or NULL for ascending
+ * @return Database_Builder
+ */
+ public function order_by($columns, $direction = NULL)
+ {
+ if (is_string($columns))
+ {
+ $columns = array($columns => $direction);
+ }
+
+ $this->order_by[] = $columns;
+
+ return $this;
+ }
+
+ /**
+ * Limit rows returned
+ *
+ * @param int Number of rows
+ * @return Database_Builder
+ */
+ public function limit($number)
+ {
+ $this->limit = (int) $number;
+
+ return $this;
+ }
+
+ /**
+ * Offset into result set
+ *
+ * @param int Offset
+ * @return Database_Builder
+ */
+ public function offset($number)
+ {
+ $this->offset = (int) $number;
+
+ return $this;
+ }
+
+ public function left_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'LEFT');
+ }
+
+ public function right_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'RIGHT');
+ }
+
+ public function inner_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'INNER');
+ }
+
+ public function outer_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'OUTER');
+ }
+
+ public function full_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'FULL');
+ }
+
+ public function left_inner_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'LEFT INNER');
+ }
+
+ public function right_inner_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'RIGHT INNER');
+ }
+
+ public function open($clause = 'WHERE')
+ {
+ return $this->and_open($clause);
+ }
+
+ public function and_open($clause = 'WHERE')
+ {
+ if ($clause === 'WHERE')
+ {
+ $this->where[] = array('AND' => '(');
+ }
+ else
+ {
+ $this->having[] = array('AND' => '(');
+ }
+
+ return $this;
+ }
+
+ public function or_open($clause = 'WHERE')
+ {
+ if ($clause === 'WHERE')
+ {
+ $this->where[] = array('OR' => '(');
+ }
+ else
+ {
+ $this->having[] = array('OR' => '(');
+ }
+
+ return $this;
+ }
+
+ public function close($clause = 'WHERE')
+ {
+ if ($clause === 'WHERE')
+ {
+ $this->where[] = array(')');
+ }
+ else
+ {
+ $this->having[] = array(')');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add conditions to the WHERE clause (AND)
+ *
+ * @param mixed Column name or array of columns => vals
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function where($columns, $op = '=', $value = NULL)
+ {
+ return $this->and_where($columns, $op, $value);
+ }
+
+ /**
+ * Add conditions to the WHERE clause (AND)
+ *
+ * @param mixed Column name or array of columns => vals
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function and_where($columns, $op = '=', $value = NULL)
+ {
+ $this->where[] = array('AND' => array($columns, $op, $value));
+ return $this;
+ }
+
+ /**
+ * Add conditions to the WHERE clause (OR)
+ *
+ * @param mixed Column name or array of columns => vals
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function or_where($columns, $op = '=', $value = NULL)
+ {
+ $this->where[] = array('OR' => array($columns, $op, $value));
+ return $this;
+ }
+
+ /**
+ * Compiles the given clause's conditions
+ *
+ * @param array Clause conditions
+ * @return string
+ */
+ protected function compile_conditions($groups)
+ {
+ $last_condition = NULL;
+
+ $sql = '';
+ foreach ($groups as $group)
+ {
+ // Process groups of conditions
+ foreach ($group as $logic => $condition)
+ {
+ if ($condition === '(')
+ {
+ if ( ! empty($sql) AND $last_condition !== '(')
+ {
+ // Include logic operator
+ $sql .= ' '.$logic.' ';
+ }
+
+ $sql .= '(';
+ }
+ elseif ($condition === ')')
+ {
+ $sql .= ')';
+ }
+ else
+ {
+ list($columns, $op, $value) = $condition;
+
+ // Stores each individual condition
+ $vals = array();
+
+ if ($columns instanceof Database_Expression)
+ {
+ // Add directly to condition list
+ $vals[] = (string) $columns;
+ }
+ else
+ {
+ $op = strtoupper($op);
+
+ if ( ! is_array($columns))
+ {
+ $columns = array($columns => $value);
+ }
+
+ foreach ($columns as $column => $value)
+ {
+ if ($value instanceof Database_Builder)
+ {
+ // Using a subquery
+ $value->db = $this->db;
+ $value = '('.(string) $value.')';
+ }
+ elseif (is_array($value))
+ {
+ if ($op === 'BETWEEN' OR $op === 'NOT BETWEEN')
+ {
+ // Falls between two values
+ $value = $this->db->quote($value[0]).' AND '.$this->db->quote($value[1]);
+ }
+ else
+ {
+ // Return as list
+ $value = array_map(array($this->db, 'quote'), $value);
+ $value = '('.implode(', ', $value).')';
+ }
+ }
+ else
+ {
+ $value = $this->db->quote($value);
+ }
+
+ if ( ! empty($column))
+ {
+ // Ignore blank columns
+ $column = $this->db->quote_column($column);
+ }
+
+ // Add to condition list
+ $vals[] = $column.' '.$op.' '.$value;
+ }
+ }
+
+ if ( ! empty($sql) AND $last_condition !== '(')
+ {
+ // Add the logic operator
+ $sql .= ' '.$logic.' ';
+ }
+
+ // Join the condition list items together by the given logic operator
+ $sql .= implode(' '.$logic.' ', $vals);
+ }
+
+ $last_condition = $condition;
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Set values for UPDATE
+ *
+ * @param mixed Column name or array of columns => vals
+ * @param mixed Value (can be a Database_Expression)
+ * @return Database_Builder
+ */
+ public function set($keys, $value = NULL)
+ {
+ if (is_string($keys))
+ {
+ $keys = array($keys => $value);
+ }
+
+ $this->set = array_merge($keys, $this->set);
+
+ return $this;
+ }
+
+ /**
+ * Columns used for INSERT queries
+ *
+ * @param array Columns
+ * @return Database_Builder
+ */
+ public function columns($columns)
+ {
+ if ( ! is_array($columns))
+ {
+ $columns = func_get_args();
+ }
+
+ $this->columns = $columns;
+
+ return $this;
+ }
+
+ /**
+ * Compiles the columns portion of the query for INSERT
+ *
+ * @return string
+ */
+ protected function compile_columns()
+ {
+ return '('.implode(', ', array_map(array($this->db, 'quote_column'), $this->columns)).')';
+ }
+
+ /**
+ * Values used for INSERT queries
+ *
+ * @param array Values
+ * @return Database_Builder
+ */
+ public function values($values)
+ {
+ if ( ! is_array($values))
+ {
+ $values = func_get_args();
+ }
+
+ $this->values[] = $values;
+
+ return $this;
+ }
+
+ /**
+ * Compiles the VALUES portion of the query for INSERT
+ *
+ * @return string
+ */
+ protected function compile_values()
+ {
+ $values = array();
+ foreach ($this->values as $group)
+ {
+ // Each set of values to be inserted
+ $values[] = '('.implode(', ', array_map(array($this->db, 'quote'), $group)).')';
+ }
+
+ return implode(', ', $values);
+ }
+
+ /**
+ * Create a SELECT query and specify selected columns
+ *
+ * @param string|array column name or array(alias => column)
+ * @return Database_Builder
+ */
+ public function select($columns = NULL)
+ {
+ $this->type = Database::SELECT;
+
+ if ($columns === NULL)
+ {
+ $columns = array('*');
+ }
+ elseif ( ! is_array($columns))
+ {
+ $columns = func_get_args();
+ }
+
+ $this->select = array_merge($this->select, $columns);
+
+ return $this;
+ }
+
+ /**
+ * Create an UPDATE query
+ *
+ * @param string Table name
+ * @param array Array of Keys => Values
+ * @param array WHERE conditions
+ * @return Database_Builder
+ */
+ public function update($table = NULL, $set = NULL, $where = NULL)
+ {
+ $this->type = Database::UPDATE;
+
+ if (is_array($set))
+ {
+ $this->set($set);
+ }
+
+ if ($where !== NULL)
+ {
+ $this->where($where);
+ }
+
+ if ($table !== NULL)
+ {
+ $this->from($table);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Create an INSERT query. Use 'columns' and 'values' methods for multi-row inserts
+ *
+ * @param string Table name
+ * @param array Array of Keys => Values
+ * @return Database_Builder
+ */
+ public function insert($table = NULL, $set = NULL)
+ {
+ $this->type = Database::INSERT;
+
+ if (is_array($set))
+ {
+ $this->columns(array_keys($set));
+ $this->values(array_values($set));
+ }
+
+ if ($table !== NULL)
+ {
+ $this->from($table);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Create a DELETE query
+ *
+ * @param string Table name
+ * @param array WHERE conditions
+ * @return Database_Builder
+ */
+ public function delete($table, $where = NULL)
+ {
+ $this->type = Database::DELETE;
+
+ if ($where !== NULL)
+ {
+ $this->where($where);
+ }
+
+ if ($table !== NULL)
+ {
+ $this->from($table);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Count records for a given table
+ *
+ * @param string Table name
+ * @param array WHERE conditions
+ * @return int
+ */
+ public function count_records($table = FALSE, $where = NULL)
+ {
+ if (count($this->from) < 1)
+ {
+ if ($table === FALSE)
+ throw new Database_Exception('Database count_records requires a table');
+
+ $this->from($table);
+ }
+
+ if ($where !== NULL)
+ {
+ $this->where($where);
+ }
+
+ // Grab the count AS records_found
+ $result = $this->select(array('records_found' => 'COUNT("*")'))->execute();
+
+ return $result->get('records_found');
+ }
+
+ /**
+ * Executes the built query
+ *
+ * @param mixed Database name or object
+ * @return Database_Result
+ */
+ public function execute($db = NULL)
+ {
+ if ($db !== NULL)
+ {
+ $this->db = $db;
+ }
+
+ if ( ! is_object($this->db))
+ {
+ // Get the database instance
+ $this->db = Database::instance($this->db);
+ }
+
+ $query = $this->compile();
+
+ // Reset the query after executing
+ $this->reset();
+
+ if ($this->ttl !== FALSE AND $this->type === Database::SELECT)
+ {
+ // Return result from cache (only allowed with SELECT)
+ return $this->db->query_cache($query, $this->ttl);
+ }
+ else
+ {
+ // Load the result (no caching)
+ return $this->db->query($query);
+ }
+ }
+
+ /**
+ * Set caching for the query
+ *
+ * @param mixed Time-to-live (FALSE to disable, NULL for Cache default, seconds otherwise)
+ * @return Database_Builder
+ */
+ public function cache($ttl = NULL)
+ {
+ $this->ttl = $ttl;
+
+ return $this;
+ }
+
+} // End Database_Builder
diff --git a/system/libraries/Database_Cache_Result.php b/system/libraries/Database_Cache_Result.php
new file mode 100644
index 00000000..12ad48ea
--- /dev/null
+++ b/system/libraries/Database_Cache_Result.php
@@ -0,0 +1,83 @@
+data = $data;
+ $this->sql = $sql;
+ $this->total_rows = count($data);
+ $this->return_objects = $return_objects;
+ }
+
+ public function __destruct()
+ {
+ // Not used
+ }
+
+ public function as_array($return = FALSE)
+ {
+ // Return arrays rather than objects
+ $this->return_objects = FALSE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return the entire array of rows
+ return $this->data;
+ }
+
+ public function as_object($class = NULL, $return = FALSE)
+ {
+ if ($class !== NULL)
+ throw new Database_Exception('Database cache results do not support object casting');
+
+ // Return objects of type $class (or stdClass if none given)
+ $this->return_objects = TRUE;
+
+ return $this;
+ }
+
+ public function seek($offset)
+ {
+ if ( ! $this->offsetExists($offset))
+ return FALSE;
+
+ $this->current_row = $offset;
+
+ return TRUE;
+ }
+
+ public function current()
+ {
+ if ($this->return_objects)
+ {
+ // Return a new object with the current row of data
+ return (object) $this->data[$this->current_row];
+ }
+ else
+ {
+ // Return an array of the row
+ return $this->data[$this->current_row];
+ }
+ }
+
+} // End Database_Cache_Result
\ No newline at end of file
diff --git a/system/libraries/Database_Exception.php b/system/libraries/Database_Exception.php
new file mode 100644
index 00000000..4a9cd83e
--- /dev/null
+++ b/system/libraries/Database_Exception.php
@@ -0,0 +1,17 @@
+expression;
+ return $this->expression;
}
-
-} // End Database Expr Class
\ No newline at end of file
+}
diff --git a/system/libraries/Database_Mysql.php b/system/libraries/Database_Mysql.php
new file mode 100644
index 00000000..cb531194
--- /dev/null
+++ b/system/libraries/Database_Mysql.php
@@ -0,0 +1,229 @@
+connection)
+ return;
+
+ if (Database_Mysql::$set_names === NULL)
+ {
+ // Determine if we can use mysql_set_charset(), which is only
+ // available on PHP 5.2.3+ when compiled against MySQL 5.0+
+ Database_Mysql::$set_names = ! function_exists('mysql_set_charset');
+ }
+
+ extract($this->config['connection']);
+
+ // Set the connection type
+ $connect = ($this->config['persistent'] === TRUE) ? 'mysql_pconnect' : 'mysql_connect';
+
+ $host = isset($host) ? $host : $socket;
+ $port = isset($port) ? ':'.$port : '';
+
+ try
+ {
+ // Connect to the database
+ $this->connection = $connect($host.$port, $user, $pass, TRUE);
+ }
+ catch (Kohana_PHP_Exception $e)
+ {
+ // No connection exists
+ $this->connection = NULL;
+
+ // Unable to connect to the database
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => mysql_error(),
+ ':errno' => mysql_errno()));
+ }
+
+ if ( ! mysql_select_db($database, $this->connection))
+ {
+ // Unable to select database
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => mysql_error($this->connection),
+ ':errno' => mysql_errno($this->connection)));
+ }
+
+ if (isset($this->config['character_set']))
+ {
+ // Set the character set
+ $this->set_charset($this->config['character_set']);
+ }
+ }
+
+ public function disconnect()
+ {
+ try
+ {
+ // Database is assumed disconnected
+ $status = TRUE;
+
+ if (is_resource($this->connection))
+ {
+ $status = mysql_close($this->connection);
+ }
+ }
+ catch (Exception $e)
+ {
+ // Database is probably not disconnected
+ $status = is_resource($this->connection);
+ }
+
+ return $status;
+ }
+
+ public function set_charset($charset)
+ {
+ // Make sure the database is connected
+ $this->connection or $this->connect();
+
+ if (Database_Mysql::$set_names === TRUE)
+ {
+ // PHP is compiled against MySQL 4.x
+ $status = (bool) mysql_query('SET NAMES '.$this->quote($charset), $this->connection);
+ }
+ else
+ {
+ // PHP is compiled against MySQL 5.x
+ $status = mysql_set_charset($charset, $this->connection);
+ }
+
+ if ($status === FALSE)
+ {
+ // Unable to set charset
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => mysql_error($this->connection),
+ ':errno' => mysql_errno($this->connection)));
+ }
+ }
+
+ public function query_execute($sql)
+ {
+ // Make sure the database is connected
+ $this->connection or $this->connect();
+
+ $result = mysql_query($sql, $this->connection);
+
+ // Set the last query
+ $this->last_query = $sql;
+
+ return new Database_Mysql_Result($result, $sql, $this->connection, $this->config['object']);
+ }
+
+ public function escape($value)
+ {
+ // Make sure the database is connected
+ $this->connection or $this->connect();
+
+ if (($value = mysql_real_escape_string($value, $this->connection)) === FALSE)
+ {
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => mysql_error($this->connection),
+ ':errno' => mysql_errno($this->connection)));
+ }
+
+ return $value;
+ }
+
+ public function list_constraints($table)
+ {
+ $prefix = strlen($this->table_prefix());
+ $result = array();
+
+ $constraints = $this->query('
+ SELECT c.constraint_name, c.constraint_type, k.column_name, k.referenced_table_name, k.referenced_column_name
+ FROM information_schema.table_constraints c
+ JOIN information_schema.key_column_usage k ON (k.table_schema = c.table_schema AND k.table_name = c.table_name AND k.constraint_name = c.constraint_name)
+ WHERE c.table_schema = '.$this->quote($this->config['connection']['database']).'
+ AND c.table_name = '.$this->quote($this->table_prefix().$table).'
+ AND (k.referenced_table_schema IS NULL OR k.referenced_table_schema ='.$this->quote($this->config['connection']['database']).')
+ ORDER BY k.ordinal_position
+ ');
+
+ foreach ($constraints->as_array() as $row)
+ {
+ switch ($row['constraint_type'])
+ {
+ case 'FOREIGN KEY':
+ if (isset($result[$row['constraint_name']]))
+ {
+ $result[$row['constraint_name']][1][] = $row['column_name'];
+ $result[$row['constraint_name']][3][] = $row['referenced_column_name'];
+ }
+ else
+ {
+ $result[$row['constraint_name']] = array($row['constraint_type'], array($row['column_name']), substr($row['referenced_table_name'], $prefix), array($row['referenced_column_name']));
+ }
+ break;
+ case 'PRIMARY KEY':
+ case 'UNIQUE':
+ if (isset($result[$row['constraint_name']]))
+ {
+ $result[$row['constraint_name']][1][] = $row['column_name'];
+ }
+ else
+ {
+ $result[$row['constraint_name']] = array($row['constraint_type'], array($row['column_name']));
+ }
+ break;
+ }
+ }
+
+ return $result;
+ }
+
+ public function list_fields($table)
+ {
+ $result = array();
+
+ foreach ($this->query('SHOW COLUMNS FROM '.$this->quote_table($table))->as_array() as $row)
+ {
+ $column = $this->sql_type($row['Type']);
+
+ $column['default'] = $row['Default'];
+ $column['nullable'] = $row['Null'] === 'YES';
+ $column['sequenced'] = $row['Extra'] === 'auto_increment';
+
+ if (isset($column['length']) AND $column['type'] === 'float')
+ {
+ list($column['precision'], $column['scale']) = explode(',', $column['length']);
+ }
+
+ $result[$row['Field']] = $column;
+ }
+
+ return $result;
+ }
+
+ public function list_tables()
+ {
+ $prefix = strlen($this->table_prefix());
+ $tables = array();
+
+ foreach ($this->query('SHOW TABLES FROM '.$this->escape($this->config['connection']['database']).' LIKE '.$this->quote($this->table_prefix().'%'))->as_array() as $row)
+ {
+ // The value is the table name
+ $tables[] = substr(current($row), $prefix);
+ }
+
+ return $tables;
+ }
+
+} // End Database_MySQL
diff --git a/system/libraries/Database_Mysql_Result.php b/system/libraries/Database_Mysql_Result.php
new file mode 100644
index 00000000..020360d1
--- /dev/null
+++ b/system/libraries/Database_Mysql_Result.php
@@ -0,0 +1,176 @@
+return_objects = $return_objects;
+
+ $this->total_rows = mysql_num_rows($result);
+ }
+ elseif (is_bool($result))
+ {
+ if ($result == FALSE)
+ {
+ throw new Database_Exception('#:errno: :error [ :query ]',
+ array(':error' => mysql_error($link),
+ ':query' => $sql,
+ ':errno' => mysql_errno($link)));
+
+ }
+ else
+ {
+ // It's a DELETE, INSERT, REPLACE, or UPDATE query
+ $this->insert_id = mysql_insert_id($link);
+ $this->total_rows = mysql_affected_rows($link);
+ }
+ }
+
+ // Store the result locally
+ $this->result = $result;
+
+ $this->sql = $sql;
+ }
+
+ public function __destruct()
+ {
+ if (is_resource($this->result))
+ {
+ mysql_free_result($this->result);
+ }
+ }
+
+ public function as_array($return = FALSE)
+ {
+ // Return arrays rather than objects
+ $this->return_objects = FALSE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return a nested array of all results
+ $array = array();
+
+ if ($this->total_rows > 0)
+ {
+ // Seek to the beginning of the result
+ mysql_data_seek($this->result, 0);
+
+ while ($row = mysql_fetch_assoc($this->result))
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+
+ $this->internal_row = $this->total_rows;
+ }
+
+ return $array;
+ }
+
+ public function as_object($class = NULL, $return = FALSE)
+ {
+ // Return objects of type $class (or stdClass if none given)
+ $this->return_objects = ($class !== NULL) ? $class : TRUE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return a nested array of all results
+ $array = array();
+
+ if ($this->total_rows > 0)
+ {
+ // Seek to the beginning of the result
+ mysql_data_seek($this->result, 0);
+
+ if (is_string($this->return_objects))
+ {
+ while ($row = mysql_fetch_object($this->result, $this->return_objects))
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ }
+ else
+ {
+ while ($row = mysql_fetch_object($this->result))
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ }
+
+ $this->internal_row = $this->total_rows;
+ }
+
+ return $array;
+ }
+
+ /**
+ * SeekableIterator: seek
+ */
+ public function seek($offset)
+ {
+ if ($this->offsetExists($offset) AND mysql_data_seek($this->result, $offset))
+ {
+ // Set the current row to the offset
+ $this->current_row = $this->internal_row = $offset;
+
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+ /**
+ * Iterator: current
+ */
+ public function current()
+ {
+ if ($this->current_row !== $this->internal_row AND ! $this->seek($this->current_row))
+ return NULL;
+
+ ++$this->internal_row;
+
+ if ($this->return_objects)
+ {
+ if (is_string($this->return_objects))
+ {
+ return mysql_fetch_object($this->result, $this->return_objects);
+ }
+ else
+ {
+ return mysql_fetch_object($this->result);
+ }
+ }
+ else
+ {
+ // Return an array of the row
+ return mysql_fetch_assoc($this->result);
+ }
+ }
+
+} // End Database_MySQL_Result
\ No newline at end of file
diff --git a/system/libraries/Database_Mysqli.php b/system/libraries/Database_Mysqli.php
new file mode 100644
index 00000000..523fcb19
--- /dev/null
+++ b/system/libraries/Database_Mysqli.php
@@ -0,0 +1,92 @@
+connection))
+ return;
+
+ extract($this->config['connection']);
+
+ // Persistent connections are supported as of PHP 5.3
+ if (RUNS_MYSQLND AND $this->config['persistent'] === TRUE)
+ {
+ $host = 'p:'.$host;
+ }
+
+ $host = isset($host) ? $host : $socket;
+
+ if($this->connection = new mysqli($host, $user, $pass, $database, $port)) {
+
+ if (isset($this->config['character_set']))
+ {
+ // Set the character set
+ $this->set_charset($this->config['character_set']);
+ }
+
+ // Clear password after successful connect
+ $this->db_config['connection']['pass'] = NULL;
+
+ return $this->connection;
+ }
+
+ // Unable to connect to the database
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => $this->connection->connect_error,
+ ':errno' => $this->connection->connect_errno));
+ }
+
+ public function disconnect()
+ {
+ return is_object($this->connection) and $this->connection->close();
+ }
+
+ public function set_charset($charset)
+ {
+ // Make sure the database is connected
+ is_object($this->connection) or $this->connect();
+
+ if ( ! $this->connection->set_charset($charset))
+ {
+ // Unable to set charset
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => $this->connection->connect_error,
+ ':errno' => $this->connection->connect_errno));
+ }
+ }
+
+ public function query_execute($sql)
+ {
+ // Make sure the database is connected
+ is_object($this->connection) or $this->connect();
+
+ $result = $this->connection->query($sql);
+
+ // Set the last query
+ $this->last_query = $sql;
+
+ return new Database_Mysqli_Result($result, $sql, $this->connection, $this->config['object']);
+ }
+
+ public function escape($value)
+ {
+ // Make sure the database is connected
+ is_object($this->connection) or $this->connect();
+
+ return $this->connection->real_escape_string($value);
+ }
+
+} // End Database_MySQLi
diff --git a/system/libraries/Database_Mysqli_Result.php b/system/libraries/Database_Mysqli_Result.php
new file mode 100644
index 00000000..3601aeac
--- /dev/null
+++ b/system/libraries/Database_Mysqli_Result.php
@@ -0,0 +1,177 @@
+return_objects = $return_objects;
+
+ $this->total_rows = $result->num_rows;
+ }
+ elseif (is_bool($result))
+ {
+ if ($result == FALSE)
+ {
+ throw new Database_Exception('#:errno: :error [ :query ]',
+ array(':error' => $link->error,
+ ':query' => $sql,
+ ':errno' => $link->errno));
+ }
+ else
+ {
+ // It's a DELETE, INSERT, REPLACE, or UPDATE query
+ $this->insert_id = $link->insert_id;
+ $this->total_rows = $link->affected_rows;
+ }
+ }
+
+ // Store the result locally
+ $this->result = $result;
+
+ $this->sql = $sql;
+ }
+
+ public function __destruct()
+ {
+ if (is_object($this->result))
+ {
+ $this->result->free();
+ }
+ }
+
+ public function as_array($return = FALSE)
+ {
+ // Return arrays rather than objects
+ $this->return_objects = FALSE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return a nested array of all results
+ if (RUNS_MYSQLND)
+ return $this->result->fetch_all(MYSQLI_ASSOC);
+
+ $array = array();
+
+ if ($this->total_rows > 0)
+ {
+ // Seek to the beginning of the result
+ $this->result->data_seek(0);
+
+ while ($row = $this->result->fetch_assoc())
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ $this->internal_row = $this->total_rows;
+ }
+
+ return $array;
+ }
+
+ public function as_object($class = NULL, $return = FALSE)
+ {
+ // Return objects of type $class (or stdClass if none given)
+ $this->return_objects = ($class !== NULL) ? $class : TRUE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return a nested array of all results
+ $array = array();
+
+ if ($this->total_rows > 0)
+ {
+ // Seek to the beginning of the result
+ $this->result->data_seek(0);
+
+ if (is_string($this->return_objects))
+ {
+ while ($row = $this->result->fetch_object($this->return_objects))
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ }
+ else
+ {
+ while ($row = $this->result->fetch_object())
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ }
+
+ $this->internal_row = $this->total_rows;
+ }
+
+ return $array;
+ }
+
+ /**
+ * SeekableIterator: seek
+ */
+ public function seek($offset)
+ {
+ if ($this->offsetExists($offset) AND $this->result->data_seek($offset))
+ {
+ // Set the current row to the offset
+ $this->current_row = $offset;
+
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+ /**
+ * Iterator: current
+ */
+ public function current()
+ {
+ if ($this->current_row !== $this->internal_row AND ! $this->seek($this->current_row))
+ return NULL;
+
+ ++$this->internal_row;
+
+ if ($this->return_objects)
+ {
+ if (is_string($this->return_objects))
+ {
+ return $this->result->fetch_object($this->return_objects);
+ }
+ else
+ {
+ return $this->result->fetch_object();
+ }
+ }
+ else
+ {
+ // Return an array of the row
+ return $this->result->fetch_assoc();
+ }
+ }
+
+} // End Database_MySQLi_Result
\ No newline at end of file
diff --git a/system/libraries/Database_Query.php b/system/libraries/Database_Query.php
new file mode 100644
index 00000000..d9399d66
--- /dev/null
+++ b/system/libraries/Database_Query.php
@@ -0,0 +1,95 @@
+sql = $sql;
+ }
+
+ public function __toString()
+ {
+ // Return the SQL of this query
+ return $this->sql;
+ }
+
+ public function sql($sql)
+ {
+ $this->sql = $sql;
+
+ return $this;
+ }
+
+ public function value($key, $value)
+ {
+ $this->params[$key] = $value;
+
+ return $this;
+ }
+
+ public function bind($key, & $value)
+ {
+ $this->params[$key] =& $value;
+
+ return $this;
+ }
+
+ public function execute($db = 'default')
+ {
+ if ( ! is_object($db))
+ {
+ // Get the database instance
+ $db = Database::instance($db);
+ }
+
+ // Import the SQL locally
+ $sql = $this->sql;
+
+ if ( ! empty($this->params))
+ {
+ // Quote all of the values
+ $params = array_map(array($db, 'quote'), $this->params);
+
+ // Replace the values in the SQL
+ $sql = strtr($sql, $params);
+ }
+
+ if ($this->ttl !== FALSE)
+ {
+ // Load the result from the cache
+ return $db->query_cache($sql, $this->ttl);
+ }
+ else
+ {
+ // Load the result (no caching)
+ return $db->query($sql);
+ }
+ }
+
+ /**
+ * Set caching for the query
+ *
+ * @param mixed Time-to-live (FALSE to disable, NULL for Cache default, seconds otherwise)
+ * @return Database_Query
+ */
+ public function cache($ttl = NULL)
+ {
+ $this->ttl = $ttl;
+
+ return $this;
+ }
+
+} // End Database_Query
\ No newline at end of file
diff --git a/system/libraries/Database_Result.php b/system/libraries/Database_Result.php
new file mode 100644
index 00000000..cf2056f3
--- /dev/null
+++ b/system/libraries/Database_Result.php
@@ -0,0 +1,170 @@
+insert_id;
+ }
+
+ /**
+ * Return the named column from the current row.
+ *
+ * @param string Column name
+ * @return mixed
+ */
+ public function get($name)
+ {
+ // Get the current row
+ $row = $this->current();
+
+ if ( ! $this->return_objects)
+ return $row[$name];
+
+ return $row->$name;
+ }
+
+ /**
+ * Countable: count
+ */
+ public function count()
+ {
+ return $this->total_rows;
+ }
+
+ /**
+ * ArrayAccess: offsetExists
+ */
+ public function offsetExists($offset)
+ {
+ return ($offset >= 0 AND $offset < $this->total_rows);
+ }
+
+ /**
+ * ArrayAccess: offsetGet
+ */
+ public function offsetGet($offset)
+ {
+ if ( ! $this->seek($offset))
+ return NULL;
+
+ return $this->current();
+ }
+
+ /**
+ * ArrayAccess: offsetSet
+ *
+ * @throws Kohana_Database_Exception
+ */
+ final public function offsetSet($offset, $value)
+ {
+ throw new Kohana_Exception('Database results are read-only');
+ }
+
+ /**
+ * ArrayAccess: offsetUnset
+ *
+ * @throws Kohana_Database_Exception
+ */
+ final public function offsetUnset($offset)
+ {
+ throw new Kohana_Exception('Database results are read-only');
+ }
+
+ /**
+ * Iterator: key
+ */
+ public function key()
+ {
+ return $this->current_row;
+ }
+
+ /**
+ * Iterator: next
+ */
+ public function next()
+ {
+ ++$this->current_row;
+ return $this;
+ }
+
+ /**
+ * Iterator: prev
+ */
+ public function prev()
+ {
+ --$this->current_row;
+ return $this;
+ }
+
+ /**
+ * Iterator: rewind
+ */
+ public function rewind()
+ {
+ $this->current_row = 0;
+ return $this;
+ }
+
+ /**
+ * Iterator: valid
+ */
+ public function valid()
+ {
+ return $this->offsetExists($this->current_row);
+ }
+
+} // End Database_Result
\ No newline at end of file
diff --git a/system/libraries/Encrypt.php b/system/libraries/Encrypt.php
index 3d564f99..0fbcfc1b 100644
--- a/system/libraries/Encrypt.php
+++ b/system/libraries/Encrypt.php
@@ -4,12 +4,12 @@
* using the MCrypt extension.
* @see http://php.net/mcrypt
*
- * $Id: Encrypt.php 4072 2009-03-13 17:20:38Z jheathco $
+ * $Id: Encrypt.php 4683 2009-11-14 17:10:53Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class Encrypt_Core {
@@ -44,7 +44,7 @@ class Encrypt_Core {
public function __construct($config = FALSE)
{
if ( ! defined('MCRYPT_ENCRYPT'))
- throw new Kohana_Exception('encrypt.requires_mcrypt');
+ throw new Kohana_Exception('To use the Encrypt library, mcrypt must be enabled in your PHP installation');
if (is_string($config))
{
@@ -52,7 +52,7 @@ class Encrypt_Core {
// Test the config group name
if (($config = Kohana::config('encryption.'.$config)) === NULL)
- throw new Kohana_Exception('encrypt.undefined_group', $name);
+ throw new Kohana_Exception('The :name: group is not defined in your configuration.', array(':name:' => $name));
}
if (is_array($config))
@@ -67,7 +67,7 @@ class Encrypt_Core {
}
if (empty($config['key']))
- throw new Kohana_Exception('encrypt.no_encryption_key');
+ throw new Kohana_Exception('To use the Encrypt library, you must set an encryption key in your config file');
// Find the max length of the key, based on cipher and mode
$size = mcrypt_get_key_size($config['cipher'], $config['mode']);
@@ -84,7 +84,7 @@ class Encrypt_Core {
// Cache the config in the object
$this->config = $config;
- Kohana::log('debug', 'Encrypt Library initialized');
+ Kohana_Log::add('debug', 'Encrypt Library initialized');
}
/**
@@ -144,16 +144,28 @@ class Encrypt_Core {
* Decrypts an encoded string back to its original value.
*
* @param string encoded string to be decrypted
- * @return string decrypted data
+ * @return string decrypted data or FALSE if decryption fails
*/
public function decode($data)
{
// Convert the data back to binary
- $data = base64_decode($data);
+ $data = base64_decode($data, TRUE);
+
+ if ( ! $data)
+ {
+ // Invalid base64 data
+ return FALSE;
+ }
// Extract the initialization vector from the data
$iv = substr($data, 0, $this->config['iv_size']);
+ if ($this->config['iv_size'] !== strlen($iv))
+ {
+ // The iv is not the correct size
+ return FALSE;
+ }
+
// Remove the iv from the data
$data = substr($data, $this->config['iv_size']);
diff --git a/system/libraries/Event_Observer.php b/system/libraries/Event_Observer.php
deleted file mode 100644
index 086c8a23..00000000
--- a/system/libraries/Event_Observer.php
+++ /dev/null
@@ -1,70 +0,0 @@
-update($caller);
- }
-
- /**
- * Updates the observer subject with a new caller.
- *
- * @chainable
- * @param object Event_Subject
- * @return object
- */
- public function update(SplSubject $caller)
- {
- if ( ! ($caller instanceof Event_Subject))
- throw new Kohana_Exception('event.invalid_subject', get_class($caller), get_class($this));
-
- // Update the caller
- $this->caller = $caller;
-
- return $this;
- }
-
- /**
- * Detaches this observer from the subject.
- *
- * @chainable
- * @return object
- */
- public function remove()
- {
- // Detach this observer from the caller
- $this->caller->detach($this);
-
- return $this;
- }
-
- /**
- * Notify the observer of a new message. This function must be defined in
- * all observers and must take exactly one parameter of any type.
- *
- * @param mixed message string, object, or array
- * @return void
- */
- abstract public function notify($message);
-
-} // End Event Observer
\ No newline at end of file
diff --git a/system/libraries/Event_Subject.php b/system/libraries/Event_Subject.php
deleted file mode 100644
index d1ccc544..00000000
--- a/system/libraries/Event_Subject.php
+++ /dev/null
@@ -1,67 +0,0 @@
-listeners[spl_object_hash($obj)] = $obj;
-
- return $this;
- }
-
- /**
- * Detach an observer from the object.
- *
- * @chainable
- * @param object Event_Observer
- * @return object
- */
- public function detach(SplObserver $obj)
- {
- // Remove the listener
- unset($this->listeners[spl_object_hash($obj)]);
-
- return $this;
- }
-
- /**
- * Notify all attached observers of a new message.
- *
- * @chainable
- * @param mixed message string, object, or array
- * @return object
- */
- public function notify($message)
- {
- foreach ($this->listeners as $obj)
- {
- $obj->notify($message);
- }
-
- return $this;
- }
-
-} // End Event Subject
\ No newline at end of file
diff --git a/system/libraries/I18n.php b/system/libraries/I18n.php
new file mode 100644
index 00000000..96752e51
--- /dev/null
+++ b/system/libraries/I18n.php
@@ -0,0 +1,103 @@
+ $image));
// Disable error reporting, to prevent PHP warnings
$ER = error_reporting(0);
@@ -85,11 +91,11 @@ class Image_Core {
// Make sure that the image is readable and valid
if ( ! is_array($image_info) OR count($image_info) < 3)
- throw new Kohana_Exception('image.file_unreadable', $image);
+ throw new Kohana_Exception('The file specified, :file:, is not readable or is not an image', array(':file:' => $image));
// Check to make sure the image type is allowed
if ( ! isset(Image::$allowed_types[$image_info[2]]))
- throw new Kohana_Exception('image.type_not_allowed', $image);
+ throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $image));
// Image has been validated, load it
$this->image = array
@@ -102,6 +108,8 @@ class Image_Core {
'mime' => $image_info['mime']
);
+ $this->determine_orientation();
+
// Load configuration
$this->config = (array) $config + Kohana::config('image');
@@ -110,14 +118,40 @@ class Image_Core {
// Load the driver
if ( ! Kohana::auto_load($driver))
- throw new Kohana_Exception('core.driver_not_found', $this->config['driver'], get_class($this));
+ throw new Kohana_Exception('The :driver: driver for the :library: library could not be found',
+ array(':driver:' => $this->config['driver'], ':library:' => get_class($this)));
// Initialize the driver
$this->driver = new $driver($this->config['params']);
// Validate the driver
if ( ! ($this->driver instanceof Image_Driver))
- throw new Kohana_Exception('core.driver_implements', $this->config['driver'], get_class($this), 'Image_Driver');
+ throw new Kohana_Exception('The :driver: driver for the :library: library must implement the :interface: interface',
+ array(':driver:' => $this->config['driver'], ':library:' => get_class($this), ':interface:' => 'Image_Driver'));
+ }
+
+ /**
+ * Works out the correct orientation for the image
+ *
+ * @return void
+ */
+ protected function determine_orientation()
+ {
+ switch (TRUE)
+ {
+ case $this->image['height'] > $this->image['width']:
+ $orientation = Image::PORTRAIT;
+ break;
+
+ case $this->image['height'] < $this->image['width']:
+ $orientation = Image::LANDSCAPE;
+ break;
+
+ default:
+ $orientation = Image::SQUARE;
+ }
+
+ $this->image['orientation'] = $orientation;
}
/**
@@ -134,7 +168,8 @@ class Image_Core {
}
else
{
- throw new Kohana_Exception('core.invalid_property', $property, get_class($this));
+ throw new Kohana_Exception('The :property: property does not exist in the :class: class.',
+ array(':property:' => $property, ':class:' => get_class($this)));
}
}
@@ -153,13 +188,13 @@ class Image_Core {
public function resize($width, $height, $master = NULL)
{
if ( ! $this->valid_size('width', $width))
- throw new Kohana_Exception('image.invalid_width', $width);
+ throw new Kohana_Exception('The width you specified, :width:, is not valid.', array(':width:' => $width));
if ( ! $this->valid_size('height', $height))
- throw new Kohana_Exception('image.invalid_height', $height);
+ throw new Kohana_Exception('The height you specified, :height:, is not valid.', array(':height:' => $height));
if (empty($width) AND empty($height))
- throw new Kohana_Exception('image.invalid_dimensions', __FUNCTION__);
+ throw new Kohana_Exception('The dimensions specified for :function: are not valid.', array(':function:' => __FUNCTION__));
if ($master === NULL)
{
@@ -167,7 +202,7 @@ class Image_Core {
$master = Image::AUTO;
}
elseif ( ! $this->valid_size('master', $master))
- throw new Kohana_Exception('image.invalid_master');
+ throw new Kohana_Exception('The master dimension specified is not valid.');
$this->actions['resize'] = array
(
@@ -176,6 +211,8 @@ class Image_Core {
'master' => $master,
);
+ $this->determine_orientation();
+
return $this;
}
@@ -194,19 +231,19 @@ class Image_Core {
public function crop($width, $height, $top = 'center', $left = 'center')
{
if ( ! $this->valid_size('width', $width))
- throw new Kohana_Exception('image.invalid_width', $width);
+ throw new Kohana_Exception('The width you specified, :width:, is not valid.', array(':width:' => $width));
if ( ! $this->valid_size('height', $height))
- throw new Kohana_Exception('image.invalid_height', $height);
+ throw new Kohana_Exception('The height you specified, :height:, is not valid.', array(':height:' => $height));
if ( ! $this->valid_size('top', $top))
- throw new Kohana_Exception('image.invalid_top', $top);
+ throw new Kohana_Exception('The top offset you specified, :top:, is not valid.', array(':top:' => $top));
if ( ! $this->valid_size('left', $left))
- throw new Kohana_Exception('image.invalid_left', $left);
+ throw new Kohana_Exception('The left offset you specified, :left:, is not valid.', array(':left:' => $left));
if (empty($width) AND empty($height))
- throw new Kohana_Exception('image.invalid_dimensions', __FUNCTION__);
+ throw new Kohana_Exception('The dimensions specified for :function: are not valid.', array(':function:' => __FUNCTION__));
$this->actions['crop'] = array
(
@@ -216,6 +253,8 @@ class Image_Core {
'left' => $left,
);
+ $this->determine_orientation();
+
return $this;
}
@@ -269,7 +308,7 @@ class Image_Core {
// Check to make sure the image type is allowed
if ( ! isset(Image::$allowed_types[$image_info[2]]))
- throw new Kohana_Exception('image.type_not_allowed', $overlay_file);
+ throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $overlay_file));
$this->actions['composite'] = array
(
@@ -293,7 +332,7 @@ class Image_Core {
public function flip($direction)
{
if ($direction !== Image::HORIZONTAL AND $direction !== Image::VERTICAL)
- throw new Kohana_Exception('image.invalid_flip');
+ throw new Kohana_Exception('The flip direction specified is not valid.');
$this->actions['flip'] = $direction;
@@ -335,7 +374,7 @@ class Image_Core {
* @param boolean keep or discard image process actions
* @return object
*/
- public function save($new_image = FALSE, $chmod = 0644, $keep_actions = FALSE)
+ public function save($new_image = FALSE, $chmod = 0644, $keep_actions = FALSE, $background = NULL)
{
// If no new image is defined, use the current image
empty($new_image) and $new_image = $this->image['file'];
@@ -348,9 +387,9 @@ class Image_Core {
$dir = str_replace('\\', '/', realpath($dir)).'/';
if ( ! is_writable($dir))
- throw new Kohana_Exception('image.directory_unwritable', $dir);
+ throw new Kohana_Exception('The specified directory, :dir:, is not writable.', array(':dir:' => $dir));
- if ($status = $this->driver->process($this->image, $this->actions, $dir, $file))
+ if ($status = $this->driver->process($this->image, $this->actions, $dir, $file, FALSE, $background))
{
if ($chmod !== FALSE)
{
@@ -359,9 +398,11 @@ class Image_Core {
}
}
- // Reset actions. Subsequent save() or render() will not apply previous actions.
- if ($keep_actions === FALSE)
+ if ($keep_actions !== TRUE)
+ {
+ // Reset actions. Subsequent save() or render() will not apply previous actions.
$this->actions = array();
+ }
return $status;
}
@@ -372,7 +413,7 @@ class Image_Core {
* @param boolean keep or discard image process actions
* @return object
*/
- public function render($keep_actions = FALSE)
+ public function render($keep_actions = FALSE, $background = NULL)
{
$new_image = $this->image['file'];
@@ -384,11 +425,13 @@ class Image_Core {
$dir = str_replace('\\', '/', realpath($dir)).'/';
// Process the image with the driver
- $status = $this->driver->process($this->image, $this->actions, $dir, $file, $render = TRUE);
+ $status = $this->driver->process($this->image, $this->actions, $dir, $file, TRUE, $background);
- // Reset actions. Subsequent save() or render() will not apply previous actions.
- if ($keep_actions === FALSE)
+ if ($keep_actions !== TRUE)
+ {
+ // Reset actions. Subsequent save() or render() will not apply previous actions.
$this->actions = array();
+ }
return $status;
}
diff --git a/system/libraries/Input.php b/system/libraries/Input.php
index 0e23c800..83f0ed17 100644
--- a/system/libraries/Input.php
+++ b/system/libraries/Input.php
@@ -2,12 +2,12 @@
/**
* Input library.
*
- * $Id: Input.php 4346 2009-05-11 17:08:15Z zombor $
+ * $Id: Input.php 4680 2009-11-10 01:57:00Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class Input_Core {
@@ -48,6 +48,18 @@ class Input_Core {
*/
public function __construct()
{
+ // Convert all global variables to Kohana charset
+ $_GET = Input::clean($_GET);
+ $_POST = Input::clean($_POST);
+ $_COOKIE = Input::clean($_COOKIE);
+ $_SERVER = Input::clean($_SERVER);
+
+ if (PHP_SAPI == 'cli')
+ {
+ // Convert command line arguments
+ $_SERVER['argv'] = Input::clean($_SERVER['argv']);
+ }
+
// Use XSS clean?
$this->use_xss_clean = (bool) Kohana::config('core.global_xss_filtering');
@@ -56,15 +68,15 @@ class Input_Core {
// magic_quotes_runtime is enabled
if (get_magic_quotes_runtime())
{
- set_magic_quotes_runtime(0);
- Kohana::log('debug', 'Disable magic_quotes_runtime! It is evil and deprecated: http://php.net/magic_quotes');
+ @set_magic_quotes_runtime(0);
+ Kohana_Log::add('debug', 'Disable magic_quotes_runtime! It is evil and deprecated: http://php.net/magic_quotes');
}
// magic_quotes_gpc is enabled
if (get_magic_quotes_gpc())
{
$this->magic_quotes_gpc = TRUE;
- Kohana::log('debug', 'Disable magic_quotes_gpc! It is evil and deprecated: http://php.net/magic_quotes');
+ Kohana_Log::add('debug', 'Disable magic_quotes_gpc! It is evil and deprecated: http://php.net/magic_quotes');
}
// register_globals is enabled
@@ -93,7 +105,7 @@ class Input_Core {
}
// Warn the developer about register globals
- Kohana::log('debug', 'Disable register_globals! It is evil and deprecated: http://php.net/register_globals');
+ Kohana_Log::add('debug', 'Disable register_globals! It is evil and deprecated: http://php.net/register_globals');
}
if (is_array($_GET))
@@ -142,7 +154,7 @@ class Input_Core {
// Create a singleton
Input::$instance = $this;
- Kohana::log('debug', 'Global GET, POST and COOKIE data sanitized');
+ Kohana_Log::add('debug', 'Global GET, POST and COOKIE data sanitized');
}
}
@@ -173,7 +185,8 @@ class Input_Core {
}
/**
- * Fetch an item from the $_COOKIE array.
+ * Fetch an item from the cookie::get() ($_COOKIE won't work with signed
+ * cookies.)
*
* @param string key to find
* @param mixed default value
@@ -182,7 +195,7 @@ class Input_Core {
*/
public function cookie($key = array(), $default = NULL, $xss_clean = FALSE)
{
- return $this->search_array($_COOKIE, $key, $default, $xss_clean);
+ return $this->search_array(cookie::get(), $key, $default, $xss_clean);
}
/**
@@ -300,93 +313,123 @@ class Input_Core {
if ($tool === TRUE)
{
- // NOTE: This is necessary because switch is NOT type-sensative!
$tool = 'default';
}
+ elseif ( ! method_exists($this, 'xss_filter_'.$tool))
+ {
+ Kohana_Log::add('error', 'Unable to use Input::xss_filter_'.$tool.'(), no such method exists');
+ $tool = 'default';
+ }
+
+ $method = 'xss_filter_'.$tool;
- switch ($tool)
+ return $this->$method($data);
+ }
+
+ /**
+ * Default built-in cross site scripting filter.
+ *
+ * @param string data to clean
+ * @return string
+ */
+ protected function xss_filter_default($data)
+ {
+ // http://svn.bitflux.ch/repos/public/popoon/trunk/classes/externalinput.php
+ // +----------------------------------------------------------------------+
+ // | Copyright (c) 2001-2006 Bitflux GmbH |
+ // +----------------------------------------------------------------------+
+ // | Licensed under the Apache License, Version 2.0 (the "License"); |
+ // | you may not use this file except in compliance with the License. |
+ // | You may obtain a copy of the License at |
+ // | http://www.apache.org/licenses/LICENSE-2.0 |
+ // | Unless required by applicable law or agreed to in writing, software |
+ // | distributed under the License is distributed on an "AS IS" BASIS, |
+ // | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
+ // | implied. See the License for the specific language governing |
+ // | permissions and limitations under the License. |
+ // +----------------------------------------------------------------------+
+ // | Author: Christian Stocker |
+ // +----------------------------------------------------------------------+
+ //
+ // Kohana Modifications:
+ // * Changed double quotes to single quotes, changed indenting and spacing
+ // * Removed magic_quotes stuff
+ // * Increased regex readability:
+ // * Used delimeters that aren't found in the pattern
+ // * Removed all unneeded escapes
+ // * Deleted U modifiers and swapped greediness where needed
+ // * Increased regex speed:
+ // * Made capturing parentheses non-capturing where possible
+ // * Removed parentheses where possible
+ // * Split up alternation alternatives
+ // * Made some quantifiers possessive
+
+ // Fix &entity\n;
+ $data = str_replace(array('&','<','>'), array('&','<','>'), $data);
+ $data = preg_replace('/(*\w+)[\x00-\x20]+;/u', '$1;', $data);
+ $data = preg_replace('/(*[0-9A-F]+);*/iu', '$1;', $data);
+ $data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');
+
+ // Remove any attribute starting with "on" or xmlns
+ $data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data);
+
+ // Remove javascript: and vbscript: protocols
+ $data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data);
+ $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data);
+ $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data);
+
+ // Only works in IE:
+ $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
+ $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
+ $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data);
+
+ // Remove namespaced elements (we do not need them)
+ $data = preg_replace('#*\w+:\w[^>]*+>#i', '', $data);
+
+ do
{
- case 'htmlpurifier':
- /**
- * @todo License should go here, http://htmlpurifier.org/
- */
- if ( ! class_exists('HTMLPurifier_Config', FALSE))
- {
- // Load HTMLPurifier
- require Kohana::find_file('vendor', 'htmlpurifier/HTMLPurifier.auto', TRUE);
- require 'HTMLPurifier.func.php';
- }
+ // Remove really unwanted tags
+ $old_data = $data;
+ $data = preg_replace('#*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $data);
+ }
+ while ($old_data !== $data);
- // Set configuration
- $config = HTMLPurifier_Config::createDefault();
- $config->set('HTML', 'TidyLevel', 'none'); // Only XSS cleaning now
-
- // Run HTMLPurifier
- $data = HTMLPurifier($data, $config);
- break;
- default:
- // http://svn.bitflux.ch/repos/public/popoon/trunk/classes/externalinput.php
- // +----------------------------------------------------------------------+
- // | Copyright (c) 2001-2006 Bitflux GmbH |
- // +----------------------------------------------------------------------+
- // | Licensed under the Apache License, Version 2.0 (the "License"); |
- // | you may not use this file except in compliance with the License. |
- // | You may obtain a copy of the License at |
- // | http://www.apache.org/licenses/LICENSE-2.0 |
- // | Unless required by applicable law or agreed to in writing, software |
- // | distributed under the License is distributed on an "AS IS" BASIS, |
- // | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
- // | implied. See the License for the specific language governing |
- // | permissions and limitations under the License. |
- // +----------------------------------------------------------------------+
- // | Author: Christian Stocker |
- // +----------------------------------------------------------------------+
- //
- // Kohana Modifications:
- // * Changed double quotes to single quotes, changed indenting and spacing
- // * Removed magic_quotes stuff
- // * Increased regex readability:
- // * Used delimeters that aren't found in the pattern
- // * Removed all unneeded escapes
- // * Deleted U modifiers and swapped greediness where needed
- // * Increased regex speed:
- // * Made capturing parentheses non-capturing where possible
- // * Removed parentheses where possible
- // * Split up alternation alternatives
- // * Made some quantifiers possessive
-
- // Fix &entity\n;
- $data = str_replace(array('&','<','>'), array('&','<','>'), $data);
- $data = preg_replace('/(*\w+)[\x00-\x20]+;/u', '$1;', $data);
- $data = preg_replace('/(*[0-9A-F]+);*/iu', '$1;', $data);
- $data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');
-
- // Remove any attribute starting with "on" or xmlns
- $data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data);
-
- // Remove javascript: and vbscript: protocols
- $data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data);
- $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data);
- $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data);
-
- // Only works in IE:
- $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
- $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
- $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data);
-
- // Remove namespaced elements (we do not need them)
- $data = preg_replace('#*\w+:\w[^>]*+>#i', '', $data);
-
- do
- {
- // Remove really unwanted tags
- $old_data = $data;
- $data = preg_replace('#*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $data);
- }
- while ($old_data !== $data);
- break;
+ return $data;
+ }
+
+ /**
+ * HTMLPurifier cross site scripting filter. This version assumes the
+ * existence of the "Standalone Distribution" htmlpurifier library, and is set to not tidy
+ * input.
+ *
+ * @param string data to clean
+ * @return string
+ */
+ protected function xss_filter_htmlpurifier($data)
+ {
+ /**
+ * @todo License should go here, http://htmlpurifier.org/
+ */
+ if ( ! class_exists('HTMLPurifier_Config', FALSE))
+ {
+ // Load HTMLPurifier
+ require Kohana::find_file('vendor', 'htmlpurifier/HTMLPurifier.standalone', TRUE);
}
+ // Set configuration
+ $config = HTMLPurifier_Config::createDefault();
+ $config->set('HTML.TidyLevel', 'none'); // Only XSS cleaning now
+
+ $cache = Kohana::config('html_purifier.cache');
+
+ if ($cache AND is_string($cache))
+ {
+ $config->set('Cache.SerializerPath', $cache);
+ }
+
+ // Run HTMLPurifier
+ $data = HTMLPurifier::instance($config)->purify($data);
+
return $data;
}
@@ -399,9 +442,7 @@ class Input_Core {
*/
public function clean_input_keys($str)
{
- $chars = PCRE_UNICODE_PROPERTIES ? '\pL' : 'a-zA-Z';
-
- if ( ! preg_match('#^['.$chars.'0-9:_.-]++$#uD', $str))
+ if ( ! preg_match('#^[\pL0-9:_.-]++$#uD', $str))
{
exit('Disallowed key characters in global data.');
}
@@ -449,4 +490,43 @@ class Input_Core {
return $str;
}
+ /**
+ * Recursively cleans arrays, objects, and strings. Removes ASCII control
+ * codes and converts to UTF-8 while silently discarding incompatible
+ * UTF-8 characters.
+ *
+ * @param string string to clean
+ * @return string
+ */
+ public static function clean($str)
+ {
+ if (is_array($str) OR is_object($str))
+ {
+ foreach ($str as $key => $val)
+ {
+ // Recursion!
+ $str[Input::clean($key)] = Input::clean($val);
+ }
+ }
+ elseif (is_string($str) AND $str !== '')
+ {
+ // Remove control characters
+ $str = text::strip_ascii_ctrl($str);
+
+ if ( ! text::is_ascii($str))
+ {
+ // Disable notices
+ $ER = error_reporting(~E_NOTICE);
+
+ // iconv is expensive, so it is only used when needed
+ $str = iconv(Kohana::CHARSET, Kohana::CHARSET.'//IGNORE', $str);
+
+ // Turn notices back on
+ error_reporting($ER);
+ }
+ }
+
+ return $str;
+ }
+
} // End Input Class
diff --git a/system/libraries/Kohana_404_Exception.php b/system/libraries/Kohana_404_Exception.php
new file mode 100644
index 00000000..8c7cc787
--- /dev/null
+++ b/system/libraries/Kohana_404_Exception.php
@@ -0,0 +1,56 @@
+ $page));
+ }
+
+ /**
+ * Throws a new 404 exception.
+ *
+ * @throws Kohana_404_Exception
+ * @return void
+ */
+ public static function trigger($page = NULL)
+ {
+ throw new Kohana_404_Exception($page);
+ }
+
+ /**
+ * Sends 404 headers, to emulate server behavior.
+ *
+ * @return void
+ */
+ public function sendHeaders()
+ {
+ // Send the 404 header
+ header('HTTP/1.1 404 File Not Found');
+ }
+
+} // End Kohana 404 Exception
\ No newline at end of file
diff --git a/system/libraries/Kohana_Log.php b/system/libraries/Kohana_Log.php
new file mode 100644
index 00000000..44ef8af8
--- /dev/null
+++ b/system/libraries/Kohana_Log.php
@@ -0,0 +1,90 @@
+ $driver));
+
+ // Initialize the driver
+ $driver = new $driver(array_merge(Kohana::config('log'), Kohana::config('log_'.$driver_name)));
+
+ // Validate the driver
+ if ( ! ($driver instanceof Log_Driver))
+ throw new Kohana_Exception('%driver% does not implement the Log_Driver interface', array('%driver%' => $driver));
+
+ Kohana_Log::$drivers[] = $driver;
+ }
+
+ // Always save logs on shutdown
+ Event::add('system.shutdown', array('Kohana_Log', 'save'));
+ }
+
+ Kohana_Log::$messages[] = array('date' => time(), 'type' => $type, 'message' => $message);
+ }
+
+ /**
+ * Save all currently logged messages.
+ *
+ * @return void
+ */
+ public static function save()
+ {
+ if (empty(Kohana_Log::$messages))
+ return;
+
+ foreach (Kohana_Log::$drivers as $driver)
+ {
+ // We can't throw exceptions here or else we will get a
+ // Exception thrown without a stack frame error
+ try
+ {
+ $driver->save(Kohana_Log::$messages);
+ }
+ catch(Exception $e){}
+ }
+
+ // Reset the messages
+ Kohana_Log::$messages = array();
+ }
+}
\ No newline at end of file
diff --git a/system/libraries/Kohana_PHP_Exception.php b/system/libraries/Kohana_PHP_Exception.php
new file mode 100644
index 00000000..fca5b30b
--- /dev/null
+++ b/system/libraries/Kohana_PHP_Exception.php
@@ -0,0 +1,99 @@
+code = $code;
+ $this->file = $file;
+ $this->line = $line;
+ }
+
+ /**
+ * PHP error handler.
+ *
+ * @throws Kohana_PHP_Exception
+ * @return void
+ */
+ public static function error_handler($code, $error, $file, $line, $context = NULL)
+ {
+ // Respect error_reporting settings
+ if (error_reporting() & $code)
+ {
+ // Throw an exception
+ throw new Kohana_PHP_Exception($code, $error, $file, $line, $context);
+ }
+ }
+
+ /**
+ * Catches errors that are not caught by the error handler, such as E_PARSE.
+ *
+ * @uses Kohana_Exception::handle()
+ * @return void
+ */
+ public static function shutdown_handler()
+ {
+ if (Kohana_PHP_Exception::$enabled AND $error = error_get_last())
+ {
+ // Fake an exception for nice debugging
+ Kohana_Exception::handle(new Kohana_PHP_Exception($error['type'], $error['message'], $error['file'], $error['line']));
+ }
+ }
+
+} // End Kohana PHP Exception
diff --git a/system/libraries/Kohana_User_Exception.php b/system/libraries/Kohana_User_Exception.php
new file mode 100644
index 00000000..95b7bc68
--- /dev/null
+++ b/system/libraries/Kohana_User_Exception.php
@@ -0,0 +1,30 @@
+code = $title;
+ }
+
+} // End Kohana User Exception
diff --git a/system/libraries/Model.php b/system/libraries/Model.php
index 0c9fd8d6..01d16fdd 100644
--- a/system/libraries/Model.php
+++ b/system/libraries/Model.php
@@ -2,15 +2,46 @@
/**
* Model base class.
*
- * $Id: Model.php 4007 2009-02-20 01:54:00Z jheathco $
+ * $Id: Model.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
* @copyright (c) 2007-2009 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @license http://kohanaphp.com/license
*/
class Model_Core {
+ /**
+ * Creates and returns a new model.
+ *
+ * @param string model name
+ * @param mixed constructor arguments
+ * @param boolean construct the model with multiple arguments
+ * @return Model
+ */
+ public static function factory($name, $args = NULL, $multiple = FALSE)
+ {
+ // Model class name
+ $class = ucfirst($name).'_Model';
+
+ if ($args === NULL)
+ {
+ // Create a new model with no arguments
+ return new $class;
+ }
+
+ if ($multiple !== TRUE)
+ {
+ // Create a model with a single argument
+ return new $class($args);
+ }
+
+ $class = new ReflectionClass($class);
+
+ // Create a model with multiple arguments
+ return $class->newInstanceArgs($args);
+ }
+
// Database object
protected $db = 'default';
diff --git a/system/libraries/ORM.php b/system/libraries/ORM.php
index 5196ba27..1d1f48f5 100644
--- a/system/libraries/ORM.php
+++ b/system/libraries/ORM.php
@@ -8,12 +8,12 @@
* [ref-orm]: http://wikipedia.org/wiki/Object-relational_mapping
* [ref-act]: http://wikipedia.org/wiki/Active_record
*
- * $Id: ORM.php 4354 2009-05-15 16:51:37Z kiall $
+ * $Id: ORM.php 4682 2009-11-11 20:53:23Z isaiah $
*
* @package ORM
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class ORM_Core {
@@ -22,6 +22,7 @@ class ORM_Core {
protected $belongs_to = array();
protected $has_many = array();
protected $has_and_belongs_to_many = array();
+ protected $has_many_through = array();
// Relationships that should always be joined
protected $load_with = array();
@@ -30,9 +31,11 @@ class ORM_Core {
protected $object = array();
protected $changed = array();
protected $related = array();
- protected $loaded = FALSE;
- protected $saved = FALSE;
+ protected $_valid = FALSE;
+ protected $_loaded = FALSE;
+ protected $_saved = FALSE;
protected $sorting;
+ protected $rules = array();
// Related objects
protected $object_relations = array();
@@ -45,6 +48,10 @@ class ORM_Core {
protected $table_columns;
protected $ignored_columns;
+ // Auto-update columns for creation and updates
+ protected $updated_column = NULL;
+ protected $created_column = NULL;
+
// Table primary key and value
protected $primary_key = 'id';
protected $primary_val = 'name';
@@ -59,6 +66,7 @@ class ORM_Core {
// Database configuration
protected $db = 'default';
protected $db_applied = array();
+ protected $db_builder;
// With calls already applied
protected $with_applied = array();
@@ -109,12 +117,15 @@ class ORM_Core {
if (is_object($id))
{
// Load an object
- $this->load_values((array) $id);
+ $this->_load_values((array) $id);
}
elseif (!empty($id))
{
- // Find an object
- $this->find($id);
+ // Set the object's primary key, but don't load it until needed
+ $this->object[$this->primary_key] = $id;
+
+ // Object is considered saved until something is set
+ $this->_saved = TRUE;
}
}
@@ -152,6 +163,9 @@ class ORM_Core {
// Load column information
$this->reload_columns();
+
+ // Initialize the builder
+ $this->db_builder = db::build();
}
/**
@@ -163,7 +177,7 @@ class ORM_Core {
public function __sleep()
{
// Store only information about the object
- return array('object_name', 'object', 'changed', 'loaded', 'saved', 'sorting');
+ return array('object_name', 'object', 'changed', '_loaded', '_saved', 'sorting');
}
/**
@@ -194,10 +208,10 @@ class ORM_Core {
*/
public function __call($method, array $args)
{
- if (method_exists($this->db, $method))
+ if (method_exists($this->db_builder, $method))
{
- if (in_array($method, array('query', 'get', 'insert', 'update', 'delete')))
- throw new Kohana_Exception('orm.query_methods_not_allowed');
+ if (in_array($method, array('execute', 'insert', 'update', 'delete')))
+ throw new Kohana_Exception('Query methods cannot be used through ORM');
// Method has been applied to the database
$this->db_applied[$method] = $method;
@@ -208,7 +222,7 @@ class ORM_Core {
if ($method === 'select' AND $num_args > 3)
{
// Call select() manually to avoid call_user_func_array
- $this->db->select($args);
+ $this->db_builder->select($args);
}
else
{
@@ -220,28 +234,28 @@ class ORM_Core {
switch ($num_args)
{
case 0:
- if (in_array($method, array('open_paren', 'close_paren', 'enable_cache', 'disable_cache')))
+ if (in_array($method, array('open', 'close', 'cache')))
{
// Should return ORM, not Database
- $this->db->$method();
+ $this->db_builder->$method();
}
else
{
// Support for things like reset_select, reset_write, list_tables
- return $this->db->$method();
+ return $this->db_builder->$method();
}
break;
case 1:
- $this->db->$method($args[0]);
+ $this->db_builder->$method($args[0]);
break;
case 2:
- $this->db->$method($args[0], $args[1]);
+ $this->db_builder->$method($args[0], $args[1]);
break;
case 3:
- $this->db->$method($args[0], $args[1], $args[2]);
+ $this->db_builder->$method($args[0], $args[1], $args[2]);
break;
case 4:
- $this->db->$method($args[0], $args[1], $args[2], $args[3]);
+ $this->db_builder->$method($args[0], $args[1], $args[2], $args[3]);
break;
default:
// Here comes the snail...
@@ -254,7 +268,8 @@ class ORM_Core {
}
else
{
- throw new Kohana_Exception('core.invalid_method', $method, get_class($this));
+ throw new Kohana_Exception('Invalid method :method called in :class',
+ array(':method' => $method, ':class' => get_class($this)));
}
}
@@ -268,6 +283,13 @@ class ORM_Core {
{
if (array_key_exists($column, $this->object))
{
+ if( ! $this->loaded() AND ! $this->empty_primary_key())
+ {
+ // Column asked for but the object hasn't been loaded yet, so do it now
+ // Ignore loading of any columns that have been changed
+ $this->find($this->object[$this->primary_key], TRUE);
+ }
+
return $this->object[$column];
}
elseif (isset($this->related[$column]))
@@ -276,16 +298,29 @@ class ORM_Core {
}
elseif ($column === 'primary_key_value')
{
+ if( ! $this->loaded() AND ! $this->empty_primary_key() AND $this->unique_key($this->object[$this->primary_key]) !== $this->primary_key)
+ {
+ // Load if object hasn't been loaded and the key given isn't the primary_key
+ // that we need (i.e. passing an email address to ORM::factory rather than the id)
+ $this->find($this->object[$this->primary_key], TRUE);
+ }
+
return $this->object[$this->primary_key];
}
elseif ($model = $this->related_object($column))
{
// This handles the has_one and belongs_to relationships
- if (in_array($model->object_name, $this->belongs_to) OR ! array_key_exists($this->foreign_key($column), $model->object))
+ if (in_array($model->object_name, $this->belongs_to))
{
- // Foreign key lies in this table (this model belongs_to target model) OR an invalid has_one relationship
- $where = array($model->table_name.'.'.$model->primary_key => $this->object[$this->foreign_key($column)]);
+ if ( ! $this->loaded() AND ! $this->empty_primary_key())
+ {
+ // Load this object first so we know what id to look for in the foreign table
+ $this->find($this->object[$this->primary_key], TRUE);
+ }
+
+ // Foreign key lies in this table (this model belongs_to target model)
+ $where = array($model->foreign_key(TRUE) => $this->object[$this->foreign_key($column)]);
}
else
{
@@ -296,10 +331,10 @@ class ORM_Core {
// one<>alias:one relationship
return $this->related[$column] = $model->find($where);
}
- elseif (isset($this->has_many[$column]))
+ elseif (isset($this->has_many_through[$column]))
{
// Load the "middle" model
- $through = ORM::factory(inflector::singular($this->has_many[$column]));
+ $through = ORM::factory(inflector::singular($this->has_many_through[$column]));
// Load the "end" model
$model = ORM::factory(inflector::singular($column));
@@ -308,21 +343,27 @@ class ORM_Core {
// User-defined foreign keys must be defined in the 'through' model
$join_table = $through->table_name;
$join_col1 = $through->foreign_key($model->object_name, $join_table);
- $join_col2 = $model->table_name.'.'.$model->primary_key;
+ $join_col2 = $model->foreign_key(TRUE);
// one<>alias:many relationship
- return $this->related[$column] = $model
+ return $model
->join($join_table, $join_col1, $join_col2)
- ->where($through->foreign_key($this->object_name, $join_table), $this->object[$this->primary_key])
- ->find_all();
+ ->where($through->foreign_key($this->object_name, $join_table), '=', $this->primary_key_value);
+ }
+ elseif (isset($this->has_many[$column]))
+ {
+ // one<>many aliased relationship
+ $model_name = $this->has_many[$column];
+
+ $model = ORM::factory(inflector::singular($model_name));
+
+ return $model->where($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value);
}
elseif (in_array($column, $this->has_many))
{
// one<>many relationship
$model = ORM::factory(inflector::singular($column));
- return $this->related[$column] = $model
- ->where($this->foreign_key($column, $model->table_name), $this->object[$this->primary_key])
- ->find_all();
+ return $model->where($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value);
}
elseif (in_array($column, $this->has_and_belongs_to_many))
{
@@ -332,16 +373,12 @@ class ORM_Core {
if ($this->has($model, TRUE))
{
// many<>many relationship
- return $this->related[$column] = $model
- ->in($model->table_name.'.'.$model->primary_key, $this->changed_relations[$column])
- ->find_all();
+ return $model->where($model->foreign_key(TRUE), 'IN', $this->changed_relations[$column]);
}
else
{
// empty many<>many relationship
- return $this->related[$column] = $model
- ->where($model->table_name.'.'.$model->primary_key, NULL)
- ->find_all();
+ return $model->where($model->foreign_key(TRUE), 'IS', NULL);
}
}
elseif (isset($this->ignored_columns[$column]))
@@ -350,10 +387,9 @@ class ORM_Core {
}
elseif (in_array($column, array
(
- 'object_name', 'object_plural', // Object
+ 'object_name', 'object_plural','_valid', // Object
'primary_key', 'primary_val', 'table_name', 'table_columns', // Table
- 'loaded', 'saved', // Status
- 'has_one', 'belongs_to', 'has_many', 'has_and_belongs_to_many', 'load_with' // Relationships
+ 'has_one', 'belongs_to', 'has_many', 'has_many_through', 'has_and_belongs_to_many', 'load_with' // Relationships
)))
{
// Model meta information
@@ -361,8 +397,32 @@ class ORM_Core {
}
else
{
- throw new Kohana_Exception('core.invalid_property', $column, get_class($this));
+ throw new Kohana_Exception('The :property property does not exist in the :class class',
+ array(':property' => $column, ':class' => get_class($this)));
+ }
+ }
+
+ /**
+ * Tells you if the Model has been loaded or not
+ *
+ * @return bool
+ */
+ public function loaded() {
+ if ( ! $this->_loaded AND ! $this->empty_primary_key())
+ {
+ // If returning the loaded member and no load has been attempted, do it now
+ $this->find($this->object[$this->primary_key], TRUE);
}
+ return $this->_loaded;
+ }
+
+ /**
+ * Tells you if the model was saved successfully or not
+ *
+ * @return bool
+ */
+ public function saved() {
+ return $this->_saved;
}
/**
@@ -386,7 +446,7 @@ class ORM_Core {
$this->changed[$column] = $column;
// Object is no longer saved
- $this->saved = FALSE;
+ $this->_saved = FALSE;
}
$this->object[$column] = $this->load_type($column, $value);
@@ -413,8 +473,33 @@ class ORM_Core {
}
else
{
- throw new Kohana_Exception('core.invalid_property', $column, get_class($this));
+ throw new Kohana_Exception('The :property: property does not exist in the :class: class',
+ array(':property:' => $column, ':class:' => get_class($this)));
+ }
+ }
+
+ /**
+ * Chainable set method
+ *
+ * @param string name of field or array of key => val
+ * @param mixed value
+ * @return ORM
+ */
+ public function set($name, $value = NULL)
+ {
+ if (is_array($name))
+ {
+ foreach ($name as $key => $value)
+ {
+ $this->__set($key, $value);
+ }
+ }
+ else
+ {
+ $this->__set($name, $value);
}
+
+ return $this;
}
/**
@@ -446,7 +531,7 @@ class ORM_Core {
*/
public function __toString()
{
- return (string) $this->object[$this->primary_key];
+ return (string) $this->primary_key_value;
}
/**
@@ -464,6 +549,15 @@ class ORM_Core {
$object[$key] = $this->$key;
}
+ foreach ($this->with_applied as $model => $enabled)
+ {
+ // Generate arrays for relationships
+ if ($enabled)
+ {
+ $object[$model] = $this->$model->as_array();
+ }
+ }
+
return $object;
}
@@ -471,8 +565,8 @@ class ORM_Core {
* Binds another one-to-one object to this model. One-to-one objects
* can be nested using 'object1:object2' syntax
*
- * @param string $target_path
- * @return void
+ * @param string target model to bind to
+ * @return void
*/
public function with($target_path)
{
@@ -521,19 +615,19 @@ class ORM_Core {
// Add to with_applied to prevent duplicate joins
$this->with_applied[$target_path] = TRUE;
+ $select = array();
+
// Use the keys of the empty object to determine the columns
- $select = array_keys($target->object);
- foreach ($select as $i => $column)
+ foreach (array_keys($target->object) as $column)
{
// Add the prefix so that load_result can determine the relationship
- $select[$i] = $target_path.'.'.$column.' AS '.$target_path.':'.$column;
+ $select[$target_path.':'.$column] = $target_path.'.'.$column;
}
-
// Select all of the prefixed keys in the object
- $this->db->select($select);
+ $this->db_builder->select($select);
- if (in_array($target->object_name, $parent->belongs_to) OR ! isset($target->object[$parent->foreign_key($target_name)]))
+ if (in_array($target->object_name, $parent->belongs_to))
{
// Parent belongs_to target, use target's primary key as join column
$join_col1 = $target->foreign_key(TRUE, $target_path);
@@ -546,11 +640,12 @@ class ORM_Core {
$join_col1 = $parent->foreign_key($target_name, $target_path);
}
- // This allows for models to use different table prefixes (sharing the same database)
- $join_table = new Database_Expression($target->db->table_prefix().$target->table_name.' AS '.$this->db->table_prefix().$target_path);
+ // This trick allows for models to use different table prefixes (sharing the same database)
+ $join_table = array($this->db->quote_table($target_path) => $target->db->quote_table($target->table_name));
// Join the related object into the result
- $this->db->join($join_table, $join_col1, $join_col2, 'LEFT');
+ // Use Database_Expression to disable prefixing
+ $this->db_builder->join(new Database_Expression($join_table), $join_col1, $join_col2, 'LEFT');
return $this;
}
@@ -560,25 +655,26 @@ class ORM_Core {
*
* @chainable
* @param mixed primary key or an array of clauses
+ * @param bool ignore loading of columns that have been modified
* @return ORM
*/
- public function find($id = NULL)
+ public function find($id = NULL, $ignore_changed = FALSE)
{
if ($id !== NULL)
{
if (is_array($id))
{
// Search for all clauses
- $this->db->where($id);
+ $this->db_builder->where($id);
}
else
{
// Search for a specific column
- $this->db->where($this->table_name.'.'.$this->unique_key($id), $id);
+ $this->db_builder->where($this->table_name.'.'.$this->unique_key($id), '=', $id);
}
}
- return $this->load_result();
+ return $this->load_result(FALSE, $ignore_changed);
}
/**
@@ -627,68 +723,52 @@ class ORM_Core {
}
// Return a select list from the results
- return $this->select($key, $val)->find_all()->select_list($key, $val);
+ return $this->select($this->table_name.'.'.$key, $this->table_name.'.'.$val)->find_all()->select_list($key, $val);
}
/**
- * Validates the current object. This method should generally be called
- * via the model, after the $_POST Validation object has been created.
+ * Validates the current object. This method requires that rules are set to be useful, if called with out
+ * any rules set, or if a Validation object isn't passed, nothing happens.
*
* @param object Validation array
- * @return boolean
+ * @param boolean Save on validate
+ * @return ORM
+ * @chainable
*/
- public function validate(Validation $array, $save = FALSE)
+ public function validate(Validation $array = NULL)
{
- $safe_array = $array->safe_array();
+ if ($array === NULL)
+ $array = new Validation($this->object);
- if ( ! $array->submitted())
+ if (count($this->rules) > 0)
{
- foreach ($safe_array as $key => $value)
+ foreach ($this->rules as $field => $parameters)
{
- // Get the value from this object
- $value = $this->$key;
-
- if (is_object($value) AND $value instanceof ORM_Iterator)
- {
- // Convert the value to an array of primary keys
- $value = $value->primary_key_array();
+ foreach ($parameters as $type => $value) {
+ switch ($type) {
+ case 'pre_filter':
+ $array->pre_filter($value,$field);
+ break;
+ case 'rules':
+ $rules = array_merge(array($field),$value);
+ call_user_func_array(array($array,'add_rules'), $rules);
+ break;
+ case 'callbacks':
+ $callbacks = array_merge(array($field),$value);
+ call_user_func_array(array($array,'add_callbacks'), $callbacks);
+ break;
+ }
}
-
- // Pre-fill data
- $array[$key] = $value;
}
}
-
// Validate the array
- if ($status = $array->validate())
+ if (($this->_valid = $array->validate()) === FALSE)
{
- // Grab only set fields (excludes missing data, unlike safe_array)
- $fields = $array->as_array();
-
- foreach ($fields as $key => $value)
- {
- if (isset($safe_array[$key]))
- {
- // Set new data, ignoring any missing fields or fields without rules
- $this->$key = $value;
- }
- }
-
- if ($save === TRUE OR is_string($save))
- {
- // Save this object
- $this->save();
-
- if (is_string($save))
- {
- // Redirect to the saved page
- url::redirect($save);
- }
- }
+ ORM_Validation_Exception::handle_validation($this->table_name, $array);
}
// Return validation status
- return $status;
+ return $this;
}
/**
@@ -701,6 +781,10 @@ class ORM_Core {
{
if ( ! empty($this->changed))
{
+ // Require model validation before saving
+ if (!$this->_valid)
+ $this->validate();
+
$data = array();
foreach ($this->changed as $column)
{
@@ -708,41 +792,64 @@ class ORM_Core {
$data[$column] = $this->object[$column];
}
- if ($this->loaded === TRUE)
+ if ( ! $this->empty_primary_key() AND ! isset($this->changed[$this->primary_key]))
{
- $query = $this->db
- ->where($this->primary_key, $this->object[$this->primary_key])
- ->update($this->table_name, $data);
+ // Primary key isn't empty and hasn't been changed so do an update
+
+ if (is_array($this->updated_column))
+ {
+ // Fill the updated column
+ $column = $this->updated_column['column'];
+ $format = $this->updated_column['format'];
+
+ $data[$column] = $this->object[$column] = ($format === TRUE) ? time() : date($format);
+ }
+
+ $query = db::update($this->table_name)
+ ->set($data)
+ ->where($this->primary_key, '=', $this->primary_key_value)
+ ->execute($this->db);
// Object has been saved
- $this->saved = TRUE;
+ $this->_saved = TRUE;
}
else
{
- $query = $this->db
- ->insert($this->table_name, $data);
+ if (is_array($this->created_column))
+ {
+ // Fill the created column
+ $column = $this->created_column['column'];
+ $format = $this->created_column['format'];
+
+ $data[$column] = $this->object[$column] = ($format === TRUE) ? time() : date($format);
+ }
+
+ $result = db::insert($this->table_name)
+ ->columns(array_keys($data))
+ ->values(array_values($data))
+ ->execute($this->db);
- if ($query->count() > 0)
+ if ($result->count() > 0)
{
if (empty($this->object[$this->primary_key]))
{
// Load the insert id as the primary key
- $this->object[$this->primary_key] = $query->insert_id();
+ $this->object[$this->primary_key] = $result->insert_id();
}
// Object is now loaded and saved
- $this->loaded = $this->saved = TRUE;
+ $this->_loaded = $this->_saved = TRUE;
}
}
- if ($this->saved === TRUE)
+ if ($this->saved() === TRUE)
{
// All changes have been saved
$this->changed = array();
}
}
- if ($this->saved === TRUE AND ! empty($this->changed_relations))
+ if ($this->saved() === TRUE AND ! empty($this->changed_relations))
{
foreach ($this->changed_relations as $column => $values)
{
@@ -782,20 +889,19 @@ class ORM_Core {
foreach ($added as $id)
{
// Insert the new relationship
- $this->db->insert($join_table, array
- (
- $object_fk => $this->object[$this->primary_key],
- $related_fk => $id,
- ));
+ db::insert($join_table)
+ ->columns($object_fk, $related_fk)
+ ->values($this->primary_key_value, $id)
+ ->execute($this->db);
}
}
if ( ! empty($removed))
{
- $this->db
- ->where($object_fk, $this->object[$this->primary_key])
- ->in($related_fk, $removed)
- ->delete($join_table);
+ db::delete($join_table)
+ ->where($object_fk, '=', $this->primary_key_value)
+ ->where($related_fk, 'IN', $removed)
+ ->execute($this->db);
}
// Clear all relations for this column
@@ -811,18 +917,21 @@ class ORM_Core {
* relationships that have been created with other objects.
*
* @chainable
+ * @param mixed id to delete
* @return ORM
*/
public function delete($id = NULL)
{
- if ($id === NULL AND $this->loaded)
+ if ($id === NULL)
{
// Use the the primary key value
- $id = $this->object[$this->primary_key];
+ $id = $this->primary_key_value;
}
// Delete this object
- $this->db->where($this->primary_key, $id)->delete($this->table_name);
+ db::delete($this->table_name)
+ ->where($this->primary_key, '=', $id)
+ ->execute($this->db);
return $this->clear();
}
@@ -840,12 +949,15 @@ class ORM_Core {
if (is_array($ids))
{
// Delete only given ids
- $this->db->in($this->primary_key, $ids);
+ db::delete($this->table_name)
+ ->where($this->primary_key, 'IN', $ids)
+ ->execute($this->db);
}
- elseif (is_null($ids))
+ elseif ($ids === NULL)
{
// Delete all records
- $this->db->where('1=1');
+ db::delete($this->table_name)
+ ->execute($this->db);
}
else
{
@@ -853,9 +965,6 @@ class ORM_Core {
return $this;
}
- // Delete all objects
- $this->db->delete($this->table_name);
-
return $this->clear();
}
@@ -872,7 +981,7 @@ class ORM_Core {
$values = array_combine($columns, array_fill(0, count($columns), NULL));
// Replace the current object with an empty one
- $this->load_values($values);
+ $this->_load_values($values);
return $this;
}
@@ -941,6 +1050,12 @@ class ORM_Core {
$this->changed_relations[$related] = $this->object_relations[$related] = $this->load_relations($join_table, $model);
}
+ if( ! $model->loaded() AND ! $model->empty_primary_key())
+ {
+ // Load the related model if it hasn't already been
+ $model->find($model->object[$model->primary_key]);
+ }
+
if ( ! $model->empty_primary_key())
{
// Check if a specific object exists
@@ -1020,40 +1135,22 @@ class ORM_Core {
public function count_all()
{
// Return the total number of records in a table
- return $this->db->count_records($this->table_name);
+ return $this->db_builder->count_records($this->table_name);
}
/**
* Proxy method to Database list_fields.
*
- * @param string table name or NULL to use this table
- * @return array
- */
- public function list_fields($table = NULL)
- {
- if ($table === NULL)
- {
- $table = $this->table_name;
- }
-
- // Proxy to database
- return $this->db->list_fields($table);
- }
-
- /**
- * Proxy method to Database field_data.
- *
- * @param string table name
* @return array
*/
- public function field_data($table)
+ public function list_fields()
{
// Proxy to database
- return $this->db->field_data($table);
+ return $this->db->list_fields($this->table_name);
}
/**
- * Proxy method to Database field_data.
+ * Proxy method to Database clear_cache.
*
* @chainable
* @param string SQL query to clear
@@ -1203,14 +1300,63 @@ class ORM_Core {
* @return ORM
*/
public function load_values(array $values)
+ {
+ // Related objects
+ $related = array();
+
+ foreach ($values as $column => $value)
+ {
+ if (strpos($column, ':') === FALSE)
+ {
+ if ( ! isset($this->changed[$column]))
+ {
+ if (isset($this->table_columns[$column]))
+ {
+ //Update the column, respects __get()
+ $this->$column = $value;
+ }
+ }
+ }
+ else
+ {
+ list ($prefix, $column) = explode(':', $column, 2);
+
+ $related[$prefix][$column] = $value;
+ }
+ }
+
+ if ( ! empty($related))
+ {
+ foreach ($related as $object => $values)
+ {
+ // Load the related objects with the values in the result
+ $this->related[$object] = $this->related_object($object)->load_values($values);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Loads an array of values into into the current object. Only used internally
+ *
+ * @chainable
+ * @param array values to load
+ * @param bool ignore loading of columns that have been modified
+ * @return ORM
+ */
+ public function _load_values(array $values, $ignore_changed = FALSE)
{
if (array_key_exists($this->primary_key, $values))
{
- // Replace the object and reset the object status
- $this->object = $this->changed = $this->related = array();
+ if ( ! $ignore_changed)
+ {
+ // Replace the object and reset the object status
+ $this->object = $this->changed = $this->related = array();
+ }
// Set the loaded and saved object status based on the primary key
- $this->loaded = $this->saved = ($values[$this->primary_key] !== NULL);
+ $this->_loaded = $this->_saved = ($values[$this->primary_key] !== NULL);
}
// Related objects
@@ -1220,13 +1366,16 @@ class ORM_Core {
{
if (strpos($column, ':') === FALSE)
{
- if (isset($this->table_columns[$column]))
+ if ( ! $ignore_changed OR ! isset($this->changed[$column]))
{
- // The type of the value can be determined, convert the value
- $value = $this->load_type($column, $value);
- }
+ if (isset($this->table_columns[$column]))
+ {
+ // The type of the value can be determined, convert the value
+ $value = $this->load_type($column, $value);
+ }
- $this->object[$column] = $value;
+ $this->object[$column] = $value;
+ }
}
else
{
@@ -1241,13 +1390,14 @@ class ORM_Core {
foreach ($related as $object => $values)
{
// Load the related objects with the values in the result
- $this->related[$object] = $this->related_object($object)->load_values($values);
+ $this->related[$object] = $this->related_object($object)->_load_values($values);
}
}
return $this;
}
+
/**
* Loads a value according to the types defined by the column metadata.
*
@@ -1264,7 +1414,7 @@ class ORM_Core {
// Load column data
$column = $this->table_columns[$column];
- if ($value === NULL AND ! empty($column['null']))
+ if ($value === NULL AND ! empty($column['nullable']))
return $value;
if ( ! empty($column['binary']) AND ! empty($column['exact']) AND (int) $column['length'] === 1)
@@ -1276,7 +1426,7 @@ class ORM_Core {
switch ($column['type'])
{
case 'int':
- if ($value === '' AND ! empty($column['null']))
+ if ($value === '' AND ! empty($column['nullable']))
{
// Forms will only submit strings, so empty integer values must be null
$value = NULL;
@@ -1295,9 +1445,7 @@ class ORM_Core {
$value = (float) $value;
break;
case 'boolean':
- if ($value === "t") $value = true; // For PgSQL
- else if ($value === "f") $value = false; // For PgSQL
- else $value = (bool) $value;
+ $value = (bool) $value;
break;
case 'string':
$value = (string) $value;
@@ -1313,21 +1461,24 @@ class ORM_Core {
*
* @chainable
* @param boolean return an iterator or load a single row
+ * @param boolean ignore loading of columns that have been modified
* @return ORM for single rows
* @return ORM_Iterator for multiple rows
*/
- protected function load_result($array = FALSE)
+ protected function load_result($array = FALSE, $ignore_changed = FALSE)
{
+ $this->db_builder->from($this->table_name);
+
if ($array === FALSE)
{
// Only fetch 1 record
- $this->db->limit(1);
+ $this->db_builder->limit(1);
}
if ( ! isset($this->db_applied['select']))
{
// Select all columns by default
- $this->db->select($this->table_name.'.*');
+ $this->db_builder->select($this->table_name.'.*');
}
if ( ! empty($this->load_with))
@@ -1348,7 +1499,7 @@ class ORM_Core {
}
}
- if ( ! isset($this->db_applied['orderby']) AND ! empty($this->sorting))
+ if ( ! isset($this->db_applied['order_by']) AND ! empty($this->sorting))
{
$sorting = array();
foreach ($this->sorting as $column => $direction)
@@ -1364,11 +1515,11 @@ class ORM_Core {
}
// Apply the user-defined sorting
- $this->db->orderby($sorting);
+ $this->db_builder->order_by($sorting);
}
// Load the result
- $result = $this->db->get($this->table_name);
+ $result = $this->db_builder->execute($this->db);
if ($array === TRUE)
{
@@ -1379,7 +1530,7 @@ class ORM_Core {
if ($result->count() === 1)
{
// Load object values
- $this->load_values($result->result(FALSE)->current());
+ $this->_load_values($result->as_array()->current(), $ignore_changed);
}
else
{
@@ -1399,20 +1550,14 @@ class ORM_Core {
*/
protected function load_relations($table, ORM $model)
{
- // Save the current query chain (otherwise the next call will clash)
- $this->db->push();
-
- $query = $this->db
- ->select($model->foreign_key(NULL).' AS id')
+ $result = db::select(array('id' => $model->foreign_key(NULL)))
->from($table)
- ->where($this->foreign_key(NULL, $table), $this->object[$this->primary_key])
- ->get()
- ->result(TRUE);
-
- $this->db->pop();
+ ->where($this->foreign_key(NULL, $table), '=', $this->primary_key_value)
+ ->execute($this->db)
+ ->as_object();
$relations = array();
- foreach ($query as $row)
+ foreach ($result as $row)
{
$relations[] = $row->id;
}
diff --git a/system/libraries/ORM_Iterator.php b/system/libraries/ORM_Iterator.php
index 41aa8065..403d1e93 100644
--- a/system/libraries/ORM_Iterator.php
+++ b/system/libraries/ORM_Iterator.php
@@ -2,12 +2,12 @@
/**
* Object Relational Mapping (ORM) result iterator.
*
-* $Id: ORM_Iterator.php 3769 2008-12-15 00:48:56Z zombor $
+* $Id: ORM_Iterator.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package ORM
* @author Kohana Team
-* @copyright (c) 2007-2008 Kohana Team
-* @license http://kohanaphp.com/license.html
+* @copyright (c) 2007-2009 Kohana Team
+* @license http://kohanaphp.com/license
*/
class ORM_Iterator_Core implements Iterator, ArrayAccess, Countable {
@@ -26,27 +26,67 @@ class ORM_Iterator_Core implements Iterator, ArrayAccess, Countable {
$this->primary_key = $model->primary_key;
$this->primary_val = $model->primary_val;
- // Database result
- $this->result = $result->result(TRUE);
+ // Database result (make sure rows are returned as arrays)
+ $this->result = $result;
}
/**
- * Returns an array of the results as ORM objects.
+ * Returns an array of the results as ORM objects or a nested array
*
+ * @param bool TRUE to return an array of ORM objects, FALSE for an array of arrays
+ * @param string key column to index on, NULL to ignore
* @return array
*/
- public function as_array()
+ public function as_array($objects = TRUE, $key = NULL)
{
$array = array();
- if ($results = $this->result->result_array())
- {
- // Import class name
- $class = $this->class_name;
+ // Import class name
+ $class = $this->class_name;
- foreach ($results as $obj)
+ if ($objects)
+ {
+ // Generate an array of objects
+ foreach ($this->result as $data)
{
- $array[] = new $class($obj);
+ if ($key === NULL)
+ {
+ // No indexing
+ $array[] = new $class($data);
+ }
+ else
+ {
+ // Index on the given key
+ $array[$data->$key] = new $class($data);
+ }
+ }
+ }
+ else
+ {
+ // Generate an array of arrays (and the subarrays may be nested in the case of relationships)
+ // This could be done by creating a new ORM object and calling as_array on it, but this is much faster
+ foreach ($this->result as $data)
+ {
+ // Have to do a bit of magic here to handle any relationships and generate a nested array for them
+ $temp = array();
+
+ foreach ($data as $key => $val)
+ {
+ $ptr = & $temp;
+
+ foreach (explode(':', $key) as $subkey)
+ {
+ // Walk thru the relationships (separated in the key name by a ':')
+ // 'user:email:address' will be array['user']['email']['address']
+ $ptr = & $ptr[$subkey];
+ }
+
+ // Set the value
+ $ptr = $val;
+ }
+
+ // Append the result
+ $array[] = $temp;
}
}
@@ -90,7 +130,7 @@ class ORM_Iterator_Core implements Iterator, ArrayAccess, Countable {
}
$array = array();
- foreach ($this->result->result_array() as $row)
+ foreach ($this->result as $row)
{
$array[$row->$key] = $row->$val;
}
diff --git a/system/libraries/ORM_Tree.php b/system/libraries/ORM_Tree.php
deleted file mode 100644
index cdb09fd0..00000000
--- a/system/libraries/ORM_Tree.php
+++ /dev/null
@@ -1,76 +0,0 @@
-related[$column]))
- {
- // Load child model
- $model = ORM::factory(inflector::singular($this->ORM_Tree_children));
-
- if (array_key_exists($this->ORM_Tree_parent_key, $this->object))
- {
- // Find children of this parent
- $model->where($model->primary_key, $this->object[$this->ORM_Tree_parent_key])->find();
- }
-
- $this->related[$column] = $model;
- }
-
- return $this->related[$column];
- }
- elseif ($column === 'children')
- {
- if (empty($this->related[$column]))
- {
- $model = ORM::factory(inflector::singular($this->ORM_Tree_children));
-
- if ($this->ORM_Tree_children === $this->table_name)
- {
- // Load children within this table
- $this->related[$column] = $model
- ->where($this->ORM_Tree_parent_key, $this->object[$this->primary_key])
- ->find_all();
- }
- else
- {
- // Find first selection of children
- $this->related[$column] = $model
- ->where($this->foreign_key(), $this->object[$this->primary_key])
- ->where($this->ORM_Tree_parent_key, NULL)
- ->find_all();
- }
- }
-
- return $this->related[$column];
- }
-
- return parent::__get($column);
- }
-
-} // End ORM Tree
\ No newline at end of file
diff --git a/system/libraries/ORM_Validation_Exception.php b/system/libraries/ORM_Validation_Exception.php
new file mode 100644
index 00000000..95f96c3b
--- /dev/null
+++ b/system/libraries/ORM_Validation_Exception.php
@@ -0,0 +1,26 @@
+$table));
+ $exception->validation = $array;
+ throw $exception;
+ }
+} // End ORM_Validation_Exception
\ No newline at end of file
diff --git a/system/libraries/ORM_Versioned.php b/system/libraries/ORM_Versioned.php
deleted file mode 100644
index 078fe16a..00000000
--- a/system/libraries/ORM_Versioned.php
+++ /dev/null
@@ -1,143 +0,0 @@
-last_version = 1 + ($this->last_version === NULL ? $this->object['version'] : $this->last_version);
- $this->__set('version', $this->last_version);
-
- parent::save();
-
- if ($this->saved)
- {
- $data = array();
- foreach ($this->object as $key => $value)
- {
- if ($key === 'id')
- continue;
-
- $data[$key] = $value;
- }
- $data[$this->foreign_key()] = $this->id;
-
- $this->db->insert($this->table_name.'_versions', $data);
- }
-
- return $this;
- }
-
- /**
- * Loads previous version from current object
- *
- * @chainable
- * @return ORM
- */
- public function previous()
- {
- if ( ! $this->loaded)
- return $this;
-
- $this->last_version = ($this->last_version === NULL) ? $this->object['version'] : $this->last_version;
- $version = $this->last_version - 1;
-
- $query = $this->db
- ->where($this->foreign_key(), $this->object[$this->primary_key])
- ->where('version', $version)
- ->limit(1)
- ->get($this->table_name.'_versions');
-
- if ($query->count())
- {
- $this->load_values($query->result(FALSE)->current());
- }
-
- return $this;
- }
-
- /**
- * Restores the object with data from stored version
- *
- * @param integer version number you want to restore
- * @return ORM
- */
- public function restore($version)
- {
- if ( ! $this->loaded)
- return $this;
-
- $query = $this->db
- ->where($this->foreign_key(), $this->object[$this->primary_key])
- ->where('version', $version)
- ->limit(1)
- ->get($this->table_name.'_versions');
-
- if ($query->count())
- {
- $row = $query->result(FALSE)->current();
-
- foreach ($row as $key => $value)
- {
- if ($key === $this->primary_key OR $key === $this->foreign_key())
- {
- // Do not overwrite the primary key
- continue;
- }
-
- if ($key === 'version')
- {
- // Always use the current version
- $value = $this->version;
- }
-
- $this->__set($key, $value);
- }
-
- $this->save();
- }
-
- return $this;
- }
-
- /**
- * Overloads ORM::delete() to delete all versioned entries of current object
- * and the object itself
- *
- * @param integer id of the object you want to delete
- * @return ORM
- */
- public function delete($id = NULL)
- {
- if ($id === NULL)
- {
- // Use the current object id
- $id = $this->object[$this->primary_key];
- }
-
- if ($status = parent::delete($id))
- {
- $this->db->where($this->foreign_key(), $id)->delete($this->table_name.'_versions');
- }
-
- return $status;
- }
-
-}
\ No newline at end of file
diff --git a/system/libraries/Pagination.php b/system/libraries/Pagination.php
deleted file mode 100644
index a8f7bb19..00000000
--- a/system/libraries/Pagination.php
+++ /dev/null
@@ -1,236 +0,0 @@
-initialize($config);
-
- Kohana::log('debug', 'Pagination Library initialized');
- }
-
- /**
- * Sets config values.
- *
- * @throws Kohana_Exception
- * @param array configuration settings
- * @return void
- */
- public function initialize($config = array())
- {
- // Load config group
- if (isset($config['group']))
- {
- // Load and validate config group
- if ( ! is_array($group_config = Kohana::config('pagination.'.$config['group'])))
- throw new Kohana_Exception('pagination.undefined_group', $config['group']);
-
- // All pagination config groups inherit default config group
- if ($config['group'] !== 'default')
- {
- // Load and validate default config group
- if ( ! is_array($default_config = Kohana::config('pagination.default')))
- throw new Kohana_Exception('pagination.undefined_group', 'default');
-
- // Merge config group with default config group
- $group_config += $default_config;
- }
-
- // Merge custom config items with config group
- $config += $group_config;
- }
-
- // Assign config values to the object
- foreach ($config as $key => $value)
- {
- if (property_exists($this, $key))
- {
- $this->$key = $value;
- }
- }
-
- // Clean view directory
- $this->directory = trim($this->directory, '/').'/';
-
- // Build generic URL with page in query string
- if ($this->query_string !== '')
- {
- // Extract current page
- $this->current_page = isset($_GET[$this->query_string]) ? (int) $_GET[$this->query_string] : 1;
-
- // Insert {page} placeholder
- $_GET[$this->query_string] = '{page}';
-
- // Create full URL
- $base_url = ($this->base_url === '') ? Router::$current_uri : $this->base_url;
- $this->url = url::site($base_url).'?'.str_replace('%7Bpage%7D', '{page}', http_build_query($_GET));
-
- // Reset page number
- $_GET[$this->query_string] = $this->current_page;
- }
-
- // Build generic URL with page as URI segment
- else
- {
- // Use current URI if no base_url set
- $this->url = ($this->base_url === '') ? Router::$segments : explode('/', trim($this->base_url, '/'));
-
- // Convert uri 'label' to corresponding integer if needed
- if (is_string($this->uri_segment))
- {
- if (($key = array_search($this->uri_segment, $this->url)) === FALSE)
- {
- // If uri 'label' is not found, auto add it to base_url
- $this->url[] = $this->uri_segment;
- $this->uri_segment = count($this->url) + 1;
- }
- else
- {
- $this->uri_segment = $key + 2;
- }
- }
-
- // Insert {page} placeholder
- $this->url[$this->uri_segment - 1] = '{page}';
-
- // Create full URL
- $this->url = url::site(implode('/', $this->url)).Router::$query_string;
-
- // Extract current page
- $this->current_page = URI::instance()->segment($this->uri_segment);
- }
-
- // Core pagination values
- $this->total_items = (int) max(0, $this->total_items);
- $this->items_per_page = (int) max(1, $this->items_per_page);
- $this->total_pages = (int) ceil($this->total_items / $this->items_per_page);
- $this->current_page = (int) min(max(1, $this->current_page), max(1, $this->total_pages));
- $this->current_first_item = (int) min((($this->current_page - 1) * $this->items_per_page) + 1, $this->total_items);
- $this->current_last_item = (int) min($this->current_first_item + $this->items_per_page - 1, $this->total_items);
-
- // If there is no first/last/previous/next page, relative to the
- // current page, value is set to FALSE. Valid page number otherwise.
- $this->first_page = ($this->current_page === 1) ? FALSE : 1;
- $this->last_page = ($this->current_page >= $this->total_pages) ? FALSE : $this->total_pages;
- $this->previous_page = ($this->current_page > 1) ? $this->current_page - 1 : FALSE;
- $this->next_page = ($this->current_page < $this->total_pages) ? $this->current_page + 1 : FALSE;
-
- // SQL values
- $this->sql_offset = (int) ($this->current_page - 1) * $this->items_per_page;
- $this->sql_limit = sprintf(' LIMIT %d OFFSET %d ', $this->items_per_page, $this->sql_offset);
- }
-
- /**
- * Generates the HTML for the chosen pagination style.
- *
- * @param string pagination style
- * @return string pagination html
- */
- public function render($style = NULL)
- {
- // Hide single page pagination
- if ($this->auto_hide === TRUE AND $this->total_pages <= 1)
- return '';
-
- if ($style === NULL)
- {
- // Use default style
- $style = $this->style;
- }
-
- // Return rendered pagination view
- return View::factory($this->directory.$style, get_object_vars($this))->render();
- }
-
- /**
- * Magically converts Pagination object to string.
- *
- * @return string pagination html
- */
- public function __toString()
- {
- return $this->render();
- }
-
- /**
- * Magically gets a pagination variable.
- *
- * @param string variable key
- * @return mixed variable value if the key is found
- * @return void if the key is not found
- */
- public function __get($key)
- {
- if (isset($this->$key))
- return $this->$key;
- }
-
- /**
- * Adds a secondary interface for accessing properties, e.g. $pagination->total_pages().
- * Note that $pagination->total_pages is the recommended way to access properties.
- *
- * @param string function name
- * @return string
- */
- public function __call($func, $args = NULL)
- {
- return $this->__get($func);
- }
-
-} // End Pagination Class
\ No newline at end of file
diff --git a/system/libraries/Profiler.php b/system/libraries/Profiler.php
index 9da053fb..b7a5ecae 100644
--- a/system/libraries/Profiler.php
+++ b/system/libraries/Profiler.php
@@ -8,100 +8,115 @@
* POST Data - The name and values of any POST data submitted to the current page.
* Cookie Data - All cookies sent for the current request.
*
- * $Id: Profiler.php 4383 2009-06-03 00:17:24Z ixmatus $
+ * $Id: Profiler.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Profiler
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class Profiler_Core {
- protected $profiles = array();
- protected $show;
+ protected static $profiles = array();
+ protected static $show;
- public function __construct()
+ /**
+ * Enable the profiler.
+ *
+ * @return void
+ */
+ public static function enable()
{
// Add all built in profiles to event
- Event::add('profiler.run', array($this, 'benchmarks'));
- Event::add('profiler.run', array($this, 'database'));
- Event::add('profiler.run', array($this, 'session'));
- Event::add('profiler.run', array($this, 'post'));
- Event::add('profiler.run', array($this, 'cookies'));
+ Event::add('profiler.run', array('Profiler', 'benchmarks'));
+ Event::add('profiler.run', array('Profiler', 'database'));
+ Event::add('profiler.run', array('Profiler', 'session'));
+ Event::add('profiler.run', array('Profiler', 'post'));
+ Event::add('profiler.run', array('Profiler', 'cookies'));
// Add profiler to page output automatically
- Event::add('system.display', array($this, 'render'));
+ Event::add('system.display', array('Profiler', 'render'));
+
+ Kohana_Log::add('debug', 'Profiler library enabled');
- Kohana::log('debug', 'Profiler Library initialized');
}
/**
- * Magic __call method. Creates a new profiler section object.
+ * Disables the profiler for this page only.
+ * Best used when profiler is autoloaded.
*
- * @param string input type
- * @param string input name
- * @return object
+ * @return void
*/
- public function __call($method, $args)
+ public static function disable()
{
- if ( ! $this->show OR (is_array($this->show) AND ! in_array($args[0], $this->show)))
- return FALSE;
-
- // Class name
- $class = 'Profiler_'.ucfirst($method);
-
- $class = new $class();
-
- $this->profiles[$args[0]] = $class;
+ // Removes itself from the event queue
+ Event::clear('system.display', array('Profiler', 'render'));
+ }
- return $class;
+ /**
+ * Return whether a profile should be shown.
+ * Determined by the config setting or GET parameter.
+ *
+ * @param string profile name
+ * @return boolean
+ */
+ public static function show($name)
+ {
+ return (Profiler::$show === TRUE OR (is_array(Profiler::$show) AND in_array($name, Profiler::$show))) ? TRUE : FALSE;
}
/**
- * Disables the profiler for this page only.
- * Best used when profiler is autoloaded.
+ * Add a new profile.
*
- * @return void
+ * @param object profile object
+ * @return boolean
+ * @throws Kohana_Exception
*/
- public function disable()
+ public static function add($profile)
{
- // Removes itself from the event queue
- Event::clear('system.display', array($this, 'render'));
+ if (is_object($profile))
+ {
+ Profiler::$profiles[] = $profile;
+ return TRUE;
+ }
+
+ throw new Kohana_Exception('The profile must be an object');
}
/**
- * Render the profiler. Output is added to the bottom of the page by default.
+ * Render the profiler.
*
- * @param boolean return the output if TRUE
+ * @param boolean return the output instead of adding it to bottom of page
* @return void|string
*/
- public function render($return = FALSE)
+ public static function render($return = FALSE)
{
$start = microtime(TRUE);
+ // Determine the profiles that should be shown
$get = isset($_GET['profiler']) ? explode(',', $_GET['profiler']) : array();
- $this->show = empty($get) ? Kohana::config('profiler.show') : $get;
+ Profiler::$show = empty($get) ? Kohana::config('profiler.show') : $get;
- Event::run('profiler.run', $this);
+ Event::run('profiler.run');
+
+ // Don't display if there's no profiles
+ if (empty(Profiler::$profiles))
+ return $output;
$styles = '';
- foreach ($this->profiles as $profile)
+ foreach (Profiler::$profiles as $profile)
{
$styles .= $profile->styles();
}
- // Don't display if there's no profiles
- if (empty($this->profiles))
- return;
-
// Load the profiler view
$data = array
(
- 'profiles' => $this->profiles,
- 'styles' => $styles,
+ 'profiles' => Profiler::$profiles,
+ 'styles' => $styles,
'execution_time' => microtime(TRUE) - $start
);
- $view = new View('kohana_profiler', $data);
+ $view = new View('profiler/profiler', $data);
// Return rendered view if $return is TRUE
if ($return === TRUE)
@@ -125,16 +140,17 @@ class Profiler_Core {
*
* @return void
*/
- public function benchmarks()
+ public static function benchmarks()
{
- if ( ! $table = $this->table('benchmarks'))
+ if ( ! Profiler::show('benchmarks'))
return;
+ $table = new Profiler_Table();
$table->add_column();
$table->add_column('kp-column kp-data');
$table->add_column('kp-column kp-data');
$table->add_column('kp-column kp-data');
- $table->add_row(array('Benchmarks', 'Time', 'Count', 'Memory'), 'kp-title', 'background-color: #FFE0E0');
+ $table->add_row(array(__('Benchmarks'), __('Count'), __('Time'), __('Memory')), 'kp-title', 'background-color: #FFE0E0');
$benchmarks = Benchmark::get(TRUE);
@@ -147,14 +163,20 @@ class Profiler_Core {
// Clean unique id from system benchmark names
$name = ucwords(str_replace(array('_', '-'), ' ', str_replace(SYSTEM_BENCHMARK.'_', '', $name)));
- $data = array($name, number_format($benchmark['time'], 3), $benchmark['count'], number_format($benchmark['memory'] / 1024 / 1024, 2).'MB');
+ $data = array(__($name), $benchmark['count'], number_format($benchmark['time'], Kohana::config('profiler.time_decimals')), number_format($benchmark['memory'] / 1024 / 1024, Kohana::config('profiler.memory_decimals')).'MB');
$class = text::alternate('', 'kp-altrow');
if ($name == 'Total Execution')
+ {
+ // Clear the count column
+ $data[1] = '';
$class = 'kp-totalrow';
+ }
$table->add_row($data, $class);
}
+
+ Profiler::add($table);
}
/**
@@ -162,31 +184,37 @@ class Profiler_Core {
*
* @return void
*/
- public function database()
+ public static function database()
{
- if ( ! $table = $this->table('database'))
+ if ( ! Profiler::show('database'))
return;
+ $queries = Database::$benchmarks;
+
+ // Don't show if there are no queries
+ if (empty($queries)) return;
+
+ $table = new Profiler_Table();
$table->add_column();
$table->add_column('kp-column kp-data');
$table->add_column('kp-column kp-data');
- $table->add_row(array('Queries', 'Time', 'Rows'), 'kp-title', 'background-color: #E0FFE0');
-
- $queries = Database::$benchmarks;
+ $table->add_row(array(__('Queries'), __('Time'), __('Rows')), 'kp-title', 'background-color: #E0FFE0');
text::alternate();
$total_time = $total_rows = 0;
foreach ($queries as $query)
{
- $data = array($query['query'], number_format($query['time'], 3), $query['rows']);
+ $data = array($query['query'], number_format($query['time'], Kohana::config('profiler.time_decimals')), $query['rows']);
$class = text::alternate('', 'kp-altrow');
$table->add_row($data, $class);
$total_time += $query['time'];
$total_rows += $query['rows'];
}
- $data = array('Total: ' . count($queries), number_format($total_time, 3), $total_rows);
+ $data = array(__('Total: ') . count($queries), number_format($total_time, Kohana::config('profiler.time_decimals')), $total_rows);
$table->add_row($data, 'kp-totalrow');
+
+ Profiler::add($table);
}
/**
@@ -194,16 +222,17 @@ class Profiler_Core {
*
* @return void
*/
- public function session()
+ public static function session()
{
if (empty($_SESSION)) return;
- if ( ! $table = $this->table('session'))
+ if ( ! Profiler::show('session'))
return;
+ $table = new Profiler_Table();
$table->add_column('kp-name');
$table->add_column();
- $table->add_row(array('Session', 'Value'), 'kp-title', 'background-color: #CCE8FB');
+ $table->add_row(array(__('Session'), __('Value')), 'kp-title', 'background-color: #CCE8FB');
text::alternate();
foreach($_SESSION as $name => $value)
@@ -217,6 +246,8 @@ class Profiler_Core {
$class = text::alternate('', 'kp-altrow');
$table->add_row($data, $class);
}
+
+ Profiler::add($table);
}
/**
@@ -224,16 +255,17 @@ class Profiler_Core {
*
* @return void
*/
- public function post()
+ public static function post()
{
if (empty($_POST)) return;
- if ( ! $table = $this->table('post'))
+ if ( ! Profiler::show('post'))
return;
+ $table = new Profiler_Table();
$table->add_column('kp-name');
$table->add_column();
- $table->add_row(array('POST', 'Value'), 'kp-title', 'background-color: #E0E0FF');
+ $table->add_row(array(__('POST'), __('Value')), 'kp-title', 'background-color: #E0E0FF');
text::alternate();
foreach($_POST as $name => $value)
@@ -242,6 +274,8 @@ class Profiler_Core {
$class = text::alternate('', 'kp-altrow');
$table->add_row($data, $class);
}
+
+ Profiler::add($table);
}
/**
@@ -249,16 +283,17 @@ class Profiler_Core {
*
* @return void
*/
- public function cookies()
+ public static function cookies()
{
if (empty($_COOKIE)) return;
- if ( ! $table = $this->table('cookies'))
+ if ( ! Profiler::show('cookies'))
return;
+ $table = new Profiler_Table();
$table->add_column('kp-name');
$table->add_column();
- $table->add_row(array('Cookies', 'Value'), 'kp-title', 'background-color: #FFF4D7');
+ $table->add_row(array(__('Cookies'), __('Value')), 'kp-title', 'background-color: #FFF4D7');
text::alternate();
foreach($_COOKIE as $name => $value)
@@ -267,5 +302,7 @@ class Profiler_Core {
$class = text::alternate('', 'kp-altrow');
$table->add_row($data, $class);
}
+
+ Profiler::add($table);
}
-}
\ No newline at end of file
+}
diff --git a/system/libraries/Profiler_Table.php b/system/libraries/Profiler_Table.php
index a0058a58..fdb62aef 100644
--- a/system/libraries/Profiler_Table.php
+++ b/system/libraries/Profiler_Table.php
@@ -2,12 +2,12 @@
/**
* Provides a table layout for sections in the Profiler library.
*
- * $Id$
+ * $Id: Profiler_Table.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Profiler
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class Profiler_Table_Core {
@@ -26,7 +26,7 @@ class Profiler_Table_Core {
if ( ! $styles_output)
{
$styles_output = TRUE;
- return file_get_contents(Kohana::find_file('views', 'kohana_profiler_table', FALSE, 'css'));
+ return file_get_contents(Kohana::find_file('views', 'profiler/table', FALSE, 'css'));
}
return '';
@@ -64,6 +64,6 @@ class Profiler_Table_Core {
{
$data['rows'] = $this->rows;
$data['columns'] = $this->columns;
- return View::factory('kohana_profiler_table', $data)->render();
+ return View::factory('profiler/table', $data)->render();
}
}
\ No newline at end of file
diff --git a/system/libraries/Router.php b/system/libraries/Router.php
index ef0e1e47..82dbb9b5 100644
--- a/system/libraries/Router.php
+++ b/system/libraries/Router.php
@@ -2,12 +2,12 @@
/**
* Router
*
- * $Id: Router.php 4391 2009-06-04 03:10:12Z zombor $
+ * $Id: Router.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class Router_Core {
@@ -38,7 +38,7 @@ class Router_Core {
if ( ! empty($_SERVER['QUERY_STRING']))
{
// Set the query string to the current query string
- Router::$query_string = '?'.trim($_SERVER['QUERY_STRING'], '&/');
+ Router::$query_string = '?'.trim(urldecode($_SERVER['QUERY_STRING']), '&/');
}
if (Router::$routes === NULL)
@@ -53,8 +53,8 @@ class Router_Core {
if (Router::$current_uri === '')
{
// Make sure the default route is set
- if ( ! isset(Router::$routes['_default']))
- throw new Kohana_Exception('core.no_default_route');
+ if (empty(Router::$routes['_default']))
+ throw new Kohana_Exception('Please set a default route in config/routes.php.');
// Use the default route when no segments exist
Router::$current_uri = Router::$routes['_default'];
@@ -63,32 +63,34 @@ class Router_Core {
$default_route = TRUE;
}
- // Make sure the URL is not tainted with HTML characters
- Router::$current_uri = html::specialchars(Router::$current_uri, FALSE);
-
// Remove all dot-paths from the URI, they are not valid
Router::$current_uri = preg_replace('#\.[\s./]*/#', '', Router::$current_uri);
- // At this point segments, rsegments, and current URI are all the same
- Router::$segments = Router::$rsegments = Router::$current_uri = trim(Router::$current_uri, '/');
-
- // Set the complete URI
- Router::$complete_uri = Router::$current_uri.Router::$query_string;
+ // At this point routed URI and current URI are the same
+ Router::$routed_uri = Router::$current_uri = trim(Router::$current_uri, '/');
- // Explode the segments by slashes
- Router::$segments = ($default_route === TRUE OR Router::$segments === '') ? array() : explode('/', Router::$segments);
-
- if ($default_route === FALSE AND count(Router::$routes) > 1)
+ if ($default_route === TRUE)
{
- // Custom routing
- Router::$rsegments = Router::routed_uri(Router::$current_uri);
+ Router::$complete_uri = Router::$query_string;
+ Router::$current_uri = '';
+ Router::$segments = array();
}
+ else
+ {
+ Router::$complete_uri = Router::$current_uri.Router::$query_string;
- // The routed URI is now complete
- Router::$routed_uri = Router::$rsegments;
+ // Explode the segments by slashes
+ Router::$segments = explode('/', Router::$current_uri);
- // Routed segments will never be empty
- Router::$rsegments = explode('/', Router::$rsegments);
+ if (count(Router::$routes) > 1)
+ {
+ // Custom routing
+ Router::$routed_uri = Router::routed_uri(Router::$current_uri);
+ }
+ }
+
+ // Explode the routed segments by slashes
+ Router::$rsegments = explode('/', Router::$routed_uri);
// Prepare to find the controller
$controller_path = '';
@@ -187,7 +189,7 @@ class Router_Core {
parse_str($query, $_GET);
// Convert $_GET to UTF-8
- $_GET = utf8::clean($_GET);
+ $_GET = Input::clean($_GET);
}
}
}
@@ -202,28 +204,37 @@ class Router_Core {
// Remove the URI from $_SERVER['QUERY_STRING']
$_SERVER['QUERY_STRING'] = preg_replace('~\bkohana_uri\b[^&]*+&?~', '', $_SERVER['QUERY_STRING']);
}
- elseif (isset($_SERVER['PATH_INFO']) AND $_SERVER['PATH_INFO'])
- {
- Router::$current_uri = $_SERVER['PATH_INFO'];
- }
- elseif (isset($_SERVER['ORIG_PATH_INFO']) AND $_SERVER['ORIG_PATH_INFO'])
- {
- Router::$current_uri = $_SERVER['ORIG_PATH_INFO'];
- }
- elseif (isset($_SERVER['PHP_SELF']) AND $_SERVER['PHP_SELF'])
- {
- Router::$current_uri = $_SERVER['PHP_SELF'];
- }
-
- if (($strpos_fc = strpos(Router::$current_uri, KOHANA)) !== FALSE)
+ else
{
- // Remove the front controller from the current uri
- Router::$current_uri = (string) substr(Router::$current_uri, $strpos_fc + strlen(KOHANA));
+ if (isset($_SERVER['PATH_INFO']) AND $_SERVER['PATH_INFO'])
+ {
+ Router::$current_uri = $_SERVER['PATH_INFO'];
+ }
+ elseif (isset($_SERVER['ORIG_PATH_INFO']) AND $_SERVER['ORIG_PATH_INFO'])
+ {
+ Router::$current_uri = $_SERVER['ORIG_PATH_INFO'];
+ }
+ elseif (isset($_SERVER['PHP_SELF']) AND $_SERVER['PHP_SELF'])
+ {
+ // PATH_INFO is empty during requests to the front controller
+ Router::$current_uri = $_SERVER['PHP_SELF'];
+ }
+
+ if (isset($_SERVER['SCRIPT_NAME']) AND $_SERVER['SCRIPT_NAME'])
+ {
+ // Clean up PATH_INFO fallbacks
+ // PATH_INFO may be formatted for ISAPI instead of CGI on IIS
+ if (strncmp(Router::$current_uri, $_SERVER['SCRIPT_NAME'], strlen($_SERVER['SCRIPT_NAME'])) === 0)
+ {
+ // Remove the front controller from the current uri
+ Router::$current_uri = (string) substr(Router::$current_uri, strlen($_SERVER['SCRIPT_NAME']));
+ }
+ }
}
-
+
// Remove slashes from the start and end of the URI
Router::$current_uri = trim(Router::$current_uri, '/');
-
+
if (Router::$current_uri !== '')
{
if ($suffix = Kohana::config('core.url_suffix') AND strpos(Router::$current_uri, $suffix) !== FALSE)
diff --git a/system/libraries/Session.php b/system/libraries/Session.php
index 51acce00..9764a35c 100644
--- a/system/libraries/Session.php
+++ b/system/libraries/Session.php
@@ -2,12 +2,12 @@
/**
* Session library.
*
- * $Id: Session.php 4493 2009-07-27 20:05:41Z ixmatus $
+ * $Id: Session.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class Session_Core {
@@ -32,18 +32,24 @@ class Session_Core {
/**
* Singleton instance of Session.
+ *
+ * @param string Force a specific session_id
*/
- public static function instance()
+ public static function instance($session_id = NULL)
{
if (Session::$instance == NULL)
{
// Create a new instance
- new Session;
+ new Session($session_id);
+ }
+ elseif( ! is_null($session_id) AND $session_id != session_id() )
+ {
+ throw new Kohana_Exception('A session (SID: :session:) is already open, cannot open the specified session (SID: :new_session:).', array(':session:' => session_id(), ':new_session:' => $session_id));
}
return Session::$instance;
}
-
+
/**
* Be sure to block the use of __clone.
*/
@@ -51,8 +57,10 @@ class Session_Core {
/**
* On first session instance creation, sets up the driver and creates session.
+ *
+ * @param string Force a specific session_id
*/
- protected function __construct()
+ protected function __construct($session_id = NULL)
{
$this->input = Input::instance();
@@ -71,7 +79,7 @@ class Session_Core {
ini_set('session.gc_maxlifetime', (Session::$config['expiration'] == 0) ? 86400 : Session::$config['expiration']);
// Create a new session
- $this->create();
+ $this->create(NULL, $session_id);
if (Session::$config['regenerate'] > 0 AND ($_SESSION['total_hits'] % Session::$config['regenerate']) === 0)
{
@@ -84,18 +92,15 @@ class Session_Core {
cookie::set(Session::$config['name'], $_SESSION['session_id'], Session::$config['expiration']);
}
- // Close the session just before sending the headers, so that
+ // Close the session on system shutdown (run before sending the headers), so that
// the session cookie(s) can be written.
- Event::add('system.send_headers', array($this, 'write_close'));
-
- // Make sure that sessions are closed before exiting
- register_shutdown_function(array($this, 'write_close'));
+ Event::add('system.shutdown', array($this, 'write_close'));
// Singleton instance
Session::$instance = $this;
}
- Kohana::log('debug', 'Session Library initialized');
+ Kohana_Log::add('debug', 'Session Library initialized');
}
/**
@@ -112,9 +117,10 @@ class Session_Core {
* Create a new session.
*
* @param array variables to set after creation
+ * @param string Force a specific session_id
* @return void
*/
- public function create($vars = NULL)
+ public function create($vars = NULL, $session_id = NULL)
{
// Destroy any current sessions
$this->destroy();
@@ -126,14 +132,16 @@ class Session_Core {
// Load the driver
if ( ! Kohana::auto_load($driver))
- throw new Kohana_Exception('core.driver_not_found', Session::$config['driver'], get_class($this));
+ throw new Kohana_Exception('The :driver: driver for the :library: library could not be found',
+ array(':driver:' => Session::$config['driver'], ':library:' => get_class($this)));
// Initialize the driver
Session::$driver = new $driver();
// Validate the driver
if ( ! (Session::$driver instanceof Session_Driver))
- throw new Kohana_Exception('core.driver_implements', Session::$config['driver'], get_class($this), 'Session_Driver');
+ throw new Kohana_Exception('The :driver: driver for the :library: library must implement the :interface: interface',
+ array(':driver:' => Session::$config['driver'], ':library:' => get_class($this), ':interface:' => 'Session_Driver'));
// Register non-native driver as the session handler
session_set_save_handler
@@ -149,7 +157,7 @@ class Session_Core {
// Validate the session name
if ( ! preg_match('~^(?=.*[a-z])[a-z0-9_]++$~iD', Session::$config['name']))
- throw new Kohana_Exception('session.invalid_session_name', Session::$config['name']);
+ throw new Kohana_Exception('The session_name, :session:, is invalid. It must contain only alphanumeric characters and underscores. Also at least one letter must be present.', array(':session:' => Session::$config['name']));
// Name the session, this will also be the name of the cookie
session_name(Session::$config['name']);
@@ -164,6 +172,20 @@ class Session_Core {
Kohana::config('cookie.httponly')
);
+ $cookie = cookie::get(Session::$config['name']);
+
+ if ($session_id === NULL)
+ {
+ // Reopen session from signed cookie value.
+ $session_id = $cookie;
+ }
+
+ // Reopen an existing session if supplied
+ if ( ! is_null($session_id))
+ {
+ session_id($session_id);
+ }
+
// Start the session!
session_start();
@@ -176,7 +198,7 @@ class Session_Core {
$_SESSION['total_hits'] = 0;
$_SESSION['_kf_flash_'] = array();
- $_SESSION['user_agent'] = Kohana::$user_agent;
+ $_SESSION['user_agent'] = request::user_agent();
$_SESSION['ip_address'] = $this->input->ip_address();
}
@@ -196,7 +218,7 @@ class Session_Core {
{
// Check user agent for consistency
case 'user_agent':
- if ($_SESSION[$valid] !== Kohana::$user_agent)
+ if ($_SESSION[$valid] !== request::user_agent())
return $this->create();
break;
@@ -253,7 +275,7 @@ class Session_Core {
if (isset($_COOKIE[$name]))
{
// Change the cookie value to match the new session id to prevent "lag"
- $_COOKIE[$name] = $_SESSION['session_id'];
+ cookie::set($name, $_SESSION['session_id']);
}
}
@@ -467,7 +489,7 @@ class Session_Core {
* Do not save this session.
* This is a performance feature only, if using the native
* session "driver" the save will NOT be aborted.
- *
+ *
* @return void
*/
public function abort_save()
diff --git a/system/libraries/URI.php b/system/libraries/URI.php
index d9ccdcf7..4df54fc0 100644
--- a/system/libraries/URI.php
+++ b/system/libraries/URI.php
@@ -2,12 +2,12 @@
/**
* URI library.
*
- * $Id: URI.php 4072 2009-03-13 17:20:38Z jheathco $
+ * $Id: URI.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class URI_Core extends Router {
diff --git a/system/libraries/Validation.php b/system/libraries/Validation.php
index 5a48bfc5..f340e63c 100644
--- a/system/libraries/Validation.php
+++ b/system/libraries/Validation.php
@@ -2,12 +2,12 @@
/**
* Validation library.
*
- * $Id: Validation.php 4120 2009-03-25 19:22:31Z jheathco $
+ * $Id: Validation.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Validation
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class Validation_Core extends ArrayObject {
@@ -247,7 +247,7 @@ class Validation_Core extends ArrayObject {
$name = $callback;
}
- throw new Kohana_Exception('validation.not_callable', $name);
+ throw new Kohana_Exception('Callback %name% used for Validation is not callable', array('%name%' => $name));
}
return $callback;
@@ -349,8 +349,23 @@ class Validation_Core extends ArrayObject {
// Arguments for rule
$args = NULL;
+ // False rule
+ $false_rule = FALSE;
+
+ $rule_tmp = trim(is_string($rule) ? $rule : $rule[1]);
+
+ // Should the rule return false?
+ if ($rule_tmp !== ($rule_name = ltrim($rule_tmp, '! ')))
+ {
+ $false_rule = TRUE;
+ }
+
if (is_string($rule))
{
+ // Use the updated rule name
+ $rule = $rule_name;
+
+ // Have arguments?
if (preg_match('/^([^\[]++)\[(.+)\]$/', $rule, $matches))
{
// Split the rule into the function and args
@@ -361,6 +376,10 @@ class Validation_Core extends ArrayObject {
$args = str_replace('\,', ',', $args);
}
}
+ else
+ {
+ $rule[1] = $rule_name;
+ }
if ($rule === 'is_array')
{
@@ -372,7 +391,7 @@ class Validation_Core extends ArrayObject {
$rule = $this->callback($rule);
// Add the rule, with args, to the field
- $this->rules[$field][] = array($rule, $args);
+ $this->rules[$field][] = array($rule, $args, $false_rule);
}
return $this;
@@ -429,36 +448,7 @@ class Validation_Core extends ArrayObject {
$object = $this;
}
- // Get all field names
- $fields = $this->field_names();
-
- // Copy the array from the object, to optimize multiple sets
- $array = $this->getArrayCopy();
-
- foreach ($fields as $field)
- {
- if ($field === '*')
- {
- // Ignore wildcard
- continue;
- }
-
- if ( ! isset($array[$field]))
- {
- if (isset($this->array_fields[$field]))
- {
- // This field must be an array
- $array[$field] = array();
- }
- else
- {
- $array[$field] = NULL;
- }
- }
- }
-
- // Swap the array back into the object
- $this->exchangeArray($array);
+ $array = $this->safe_array();
// Get all defined field names
$fields = array_keys($array);
@@ -471,12 +461,12 @@ class Validation_Core extends ArrayObject {
{
foreach ($fields as $f)
{
- $this[$f] = is_array($this[$f]) ? array_map($callback, $this[$f]) : call_user_func($callback, $this[$f]);
+ $array[$f] = is_array($array[$f]) ? array_map($callback, $array[$f]) : call_user_func($callback, $array[$f]);
}
}
else
{
- $this[$field] = is_array($this[$field]) ? array_map($callback, $this[$field]) : call_user_func($callback, $this[$field]);
+ $array[$field] = is_array($array[$field]) ? array_map($callback, $array[$field]) : call_user_func($callback, $array[$field]);
}
}
}
@@ -488,8 +478,8 @@ class Validation_Core extends ArrayObject {
{
foreach ($callbacks as $callback)
{
- // Separate the callback and arguments
- list ($callback, $args) = $callback;
+ // Separate the callback, arguments and is false bool
+ list ($callback, $args, $is_false) = $callback;
// Function or method name of the rule
$rule = is_array($callback) ? $callback[1] : $callback;
@@ -508,31 +498,20 @@ class Validation_Core extends ArrayObject {
continue;
}
- if (empty($this[$f]) AND ! in_array($rule, $this->empty_rules))
+ if (empty($array[$f]) AND ! in_array($rule, $this->empty_rules))
{
// This rule does not need to be processed on empty fields
continue;
}
- if ($args === NULL)
- {
- if ( ! call_user_func($callback, $this[$f]))
- {
- $this->errors[$f] = $rule;
+ $result = ($args === NULL) ? call_user_func($callback, $array[$f]) : call_user_func($callback, $array[$f], $args);
- // Stop validating this field when an error is found
- continue;
- }
- }
- else
+ if (($result == $is_false))
{
- if ( ! call_user_func($callback, $this[$f], $args))
- {
- $this->errors[$f] = $rule;
+ $this->add_error($f, $rule, $args);
- // Stop validating this field when an error is found
- continue;
- }
+ // Stop validating this field when an error is found
+ continue;
}
}
}
@@ -544,31 +523,21 @@ class Validation_Core extends ArrayObject {
break;
}
- if ( ! in_array($rule, $this->empty_rules) AND ! $this->required($this[$field]))
+ if ( ! in_array($rule, $this->empty_rules) AND ! $this->required($array[$field]))
{
// This rule does not need to be processed on empty fields
continue;
}
- if ($args === NULL)
- {
- if ( ! call_user_func($callback, $this[$field]))
- {
- $this->errors[$field] = $rule;
+ // Results of our test
+ $result = ($args === NULL) ? call_user_func($callback, $array[$field]) : call_user_func($callback, $array[$field], $args);
- // Stop validating this field when an error is found
- break;
- }
- }
- else
+ if (($result == $is_false))
{
- if ( ! call_user_func($callback, $this[$field], $args))
- {
- $this->errors[$field] = $rule;
+ $this->add_error($field, $rule, $args);
- // Stop validating this field when an error is found
- break;
- }
+ // Stop validating this field when an error is found
+ break;
}
}
}
@@ -616,16 +585,19 @@ class Validation_Core extends ArrayObject {
{
foreach ($fields as $f)
{
- $this[$f] = is_array($this[$f]) ? array_map($callback, $this[$f]) : call_user_func($callback, $this[$f]);
+ $array[$f] = is_array($array[$f]) ? array_map($callback, $array[$f]) : call_user_func($callback, $array[$f]);
}
}
else
{
- $this[$field] = is_array($this[$field]) ? array_map($callback, $this[$field]) : call_user_func($callback, $this[$field]);
+ $array[$field] = is_array($array[$field]) ? array_map($callback, $array[$field]) : call_user_func($callback, $array[$field]);
}
}
}
+ // Swap the array back into the object
+ $this->exchangeArray($array);
+
// Return TRUE if there are no errors
return $this->errors === array();
}
@@ -636,11 +608,12 @@ class Validation_Core extends ArrayObject {
* @chainable
* @param string input name
* @param string unique error name
+ * @param string arguments to pass to lang file
* @return object
*/
- public function add_error($field, $name)
+ public function add_error($field, $name, $args = NULL)
{
- $this->errors[$field] = $name;
+ $this->errors[$field] = array($name, $args);
return $this;
}
@@ -688,14 +661,19 @@ class Validation_Core extends ArrayObject {
/**
* Return the errors array.
*
- * @param boolean load errors from a lang file
+ * @param boolean load errors from a message file
* @return array
*/
public function errors($file = NULL)
{
if ($file === NULL)
{
- return $this->errors;
+ $errors = array();
+ foreach($this->errors as $field => $error)
+ {
+ $errors[$field] = $error[0];
+ }
+ return $errors;
}
else
{
@@ -704,12 +682,12 @@ class Validation_Core extends ArrayObject {
foreach ($this->errors as $input => $error)
{
// Key for this input error
- $key = "$file.$input.$error";
+ $key = "$file.$input.$error[0]";
- if (($errors[$input] = Kohana::lang($key)) === $key)
+ if (($errors[$input] = Kohana::message('validation/'.$key, $error[1])) === $key)
{
// Get the default error message
- $errors[$input] = Kohana::lang("$file.$input.default");
+ $errors[$input] = Kohana::message("$file.$input.default");
}
}
@@ -772,7 +750,7 @@ class Validation_Core extends ArrayObject {
if ( ! is_string($str))
return FALSE;
- $size = utf8::strlen($str);
+ $size = mb_strlen($str);
$status = FALSE;
if (count($length) > 1)
@@ -823,4 +801,4 @@ class Validation_Core extends ArrayObject {
return ! preg_match('![^'.implode('', $chars).']!u', $value);
}
-} // End Validation
+} // End Validation
\ No newline at end of file
diff --git a/system/libraries/View.php b/system/libraries/View.php
index 2b8471c6..d7e5e774 100644
--- a/system/libraries/View.php
+++ b/system/libraries/View.php
@@ -3,12 +3,12 @@
* Loads and displays Kohana view files. Can also handle output of some binary
* files, such as image, Javascript, and CSS files.
*
- * $Id: View.php 4072 2009-03-13 17:20:38Z jheathco $
+ * $Id: View.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class View_Core {
@@ -18,7 +18,6 @@ class View_Core {
// View variable storage
protected $kohana_local_data = array();
- protected static $kohana_global_data = array();
/**
* Creates a new View using the given parameters.
@@ -88,7 +87,7 @@ class View_Core {
{
// Check if the filetype is allowed by the configuration
if ( ! in_array($type, Kohana::config('view.allowed_filetypes')))
- throw new Kohana_Exception('core.invalid_filetype', $type);
+ throw new Kohana_Exception('The requested filetype, .:type:, is not allowed in your view configuration file', array(':type:' => $type));
// Load the filename and set the content type
$this->kohana_filename = Kohana::find_file('views', $name, TRUE, $type);
@@ -152,13 +151,13 @@ class View_Core {
foreach ($key as $property)
{
// Set the result to an associative array
- $result[$property] = (array_key_exists($property, $this->kohana_local_data) OR array_key_exists($property, View::$kohana_global_data)) ? TRUE : FALSE;
+ $result[$property] = (array_key_exists($property, $this->kohana_local_data)) ? TRUE : FALSE;
}
}
else
{
// Otherwise just check one property
- $result = (array_key_exists($key, $this->kohana_local_data) OR array_key_exists($key, View::$kohana_global_data)) ? TRUE : FALSE;
+ $result = (array_key_exists($key, $this->kohana_local_data)) ? TRUE : FALSE;
}
// Return the result
@@ -179,28 +178,6 @@ class View_Core {
return $this;
}
- /**
- * Sets a view global variable.
- *
- * @param string|array name of variable or an array of variables
- * @param mixed value when using a named variable
- * @return void
- */
- public static function set_global($name, $value = NULL)
- {
- if (is_array($name))
- {
- foreach ($name as $key => $value)
- {
- View::$kohana_global_data[$key] = $value;
- }
- }
- else
- {
- View::$kohana_global_data[$name] = $value;
- }
- }
-
/**
* Magically sets a view variable.
*
@@ -223,13 +200,18 @@ class View_Core {
public function &__get($key)
{
if (isset($this->kohana_local_data[$key]))
+ {
return $this->kohana_local_data[$key];
-
- if (isset(View::$kohana_global_data[$key]))
- return View::$kohana_global_data[$key];
-
- if (isset($this->$key))
+ }
+ elseif (isset($this->$key))
+ {
return $this->$key;
+ }
+ else
+ {
+ throw new Kohana_Exception('Undefined view variable: :var',
+ array(':var' => $key));
+ }
}
/**
@@ -245,8 +227,8 @@ class View_Core {
}
catch (Exception $e)
{
- // Display the exception using its internal __toString method
- return (string) $e;
+ Kohana_Exception::handle($e);
+ return (string) '';
}
}
@@ -255,21 +237,27 @@ class View_Core {
*
* @param boolean set to TRUE to echo the output instead of returning it
* @param callback special renderer to pass the output through
+ * @param callback modifier to pass the data through before rendering
* @return string if print is FALSE
* @return void if print is TRUE
*/
- public function render($print = FALSE, $renderer = FALSE)
+ public function render($print = FALSE, $renderer = FALSE, $modifier = FALSE)
{
if (empty($this->kohana_filename))
- throw new Kohana_Exception('core.view_set_filename');
+ throw new Kohana_Exception('You must set the the view filename before calling render');
if (is_string($this->kohana_filetype))
{
// Merge global and local data, local overrides global with the same name
- $data = array_merge(View::$kohana_global_data, $this->kohana_local_data);
+ $data = $this->kohana_local_data;
+
+ if ($modifier !== FALSE AND is_callable($modifier, TRUE))
+ {
+ // Pass the data through the user defined modifier
+ $data = call_user_func($modifier, $data);
+ }
- // Load the view in the controller for access to $this
- $output = Kohana::$instance->_kohana_load_view($this->kohana_filename, $data);
+ $output = $this->load_view($this->kohana_filename, $data);
if ($renderer !== FALSE AND is_callable($renderer, TRUE))
{
@@ -306,4 +294,36 @@ class View_Core {
return $output;
}
-} // End View
\ No newline at end of file
+
+ /**
+ * Includes a View within the controller scope.
+ *
+ * @param string view filename
+ * @param array array of view variables
+ * @return string
+ */
+ public function load_view($kohana_view_filename, $kohana_input_data)
+ {
+ if ($kohana_view_filename == '')
+ return;
+
+ // Buffering on
+ ob_start();
+
+ // Import the view variables to local namespace
+ extract($kohana_input_data, EXTR_SKIP);
+
+ try
+ {
+ include $kohana_view_filename;
+ }
+ catch (Exception $e)
+ {
+ ob_end_clean();
+ throw $e;
+ }
+
+ // Fetch the output and close the buffer
+ return ob_get_clean();
+ }
+} // End View
diff --git a/system/libraries/drivers/Cache.php b/system/libraries/drivers/Cache.php
index 7c5e3c31..97415096 100644
--- a/system/libraries/drivers/Cache.php
+++ b/system/libraries/drivers/Cache.php
@@ -1,40 +1,42 @@
config = $config;
+ $this->config['directory'] = str_replace('\\', '/', realpath($this->config['directory'])).'/';
- // Make sure the cache directory is writable
- if ( ! is_dir($directory) OR ! is_writable($directory))
- throw new Kohana_Exception('cache.unwritable', $directory);
-
- // Directory is valid
- $this->directory = $directory;
+ if ( ! is_dir($this->config['directory']) OR ! is_writable($this->config['directory']))
+ throw new Cache_Exception('The configured cache directory, :directory:, is not writable.', array(':directory:' => $this->config['directory']));
}
/**
* Finds an array of files matching the given id or tag.
*
- * @param string cache id or tag
+ * @param string cache key or tag
* @param bool search for tags
* @return array of filenames matching the id or tag
*/
- public function exists($id, $tag = FALSE)
+ public function exists($keys, $tag = FALSE)
{
- if ($id === TRUE)
+ if ($keys === TRUE)
{
// Find all the files
- return glob($this->directory.'*~*~*');
+ return glob($this->config['directory'].'*~*~*');
}
elseif ($tag === TRUE)
{
// Find all the files that have the tag name
- $paths = glob($this->directory.'*~*'.$id.'*~*');
+ $paths = array();
+
+ foreach ( (array) $keys as $tag)
+ {
+ $paths = array_merge($paths, glob($this->config['directory'].'*~*'.$tag.'*~*'));
+ }
// Find all tags matching the given tag
$files = array();
+
foreach ($paths as $path)
{
// Split the files
@@ -60,12 +59,16 @@ class Cache_File_Driver implements Cache_Driver {
continue;
// Split the tags by plus signs, used to separate tags
- $tags = explode('+', $tags[1]);
+ $item_tags = explode('+', $tags[1]);
- if (in_array($tag, $tags))
+ // Check each supplied tag, and match aginst the tags on each item.
+ foreach ($keys as $tag)
{
- // Add the file to the array, it has the requested tag
- $files[] = $path;
+ if (in_array($tag, $item_tags))
+ {
+ // Add the file to the array, it has the requested tag
+ $files[] = $path;
+ }
}
}
@@ -73,98 +76,68 @@ class Cache_File_Driver implements Cache_Driver {
}
else
{
- // Find the file matching the given id
- return glob($this->directory.$id.'~*');
+ $paths = array();
+
+ foreach ( (array) $keys as $key)
+ {
+ // Find the file matching the given key
+ $paths = array_merge($paths, glob($this->config['directory'].str_replace(array('/', '\\', ' '), '_', $key).'~*'));
+ }
+
+ return $paths;
}
}
- /**
- * Sets a cache item to the given data, tags, and lifetime.
- *
- * @param string cache id to set
- * @param string data in the cache
- * @param array cache tags
- * @param integer lifetime
- * @return bool
- */
- public function set($id, $data, array $tags = NULL, $lifetime)
+ public function set($items, $tags = NULL, $lifetime = NULL)
{
- // Remove old cache files
- $this->delete($id);
-
- // Cache File driver expects unix timestamp
if ($lifetime !== 0)
{
+ // File driver expects unix timestamp
$lifetime += time();
}
- if ( ! empty($tags))
+
+ if ( ! is_null($tags) AND ! empty($tags))
{
// Convert the tags into a string list
- $tags = implode('+', $tags);
+ $tags = implode('+', (array) $tags);
}
- // Write out a serialized cache
- return (bool) file_put_contents($this->directory.$id.'~'.$tags.'~'.$lifetime, serialize($data));
- }
+ $success = TRUE;
- /**
- * Finds an array of ids for a given tag.
- *
- * @param string tag name
- * @return array of ids that match the tag
- */
- public function find($tag)
- {
- // An array will always be returned
- $result = array();
-
- if ($paths = $this->exists($tag, TRUE))
+ foreach ($items as $key => $value)
{
- // Length of directory name
- $offset = strlen($this->directory);
+ if (is_resource($value))
+ throw new Cache_Exception('Caching of resources is impossible, because resources cannot be serialised.');
- // Find all the files with the given tag
- foreach ($paths as $path)
- {
- // Get the id from the filename
- list($id, $junk) = explode('~', basename($path), 2);
+ // Remove old cache file
+ $this->delete($key);
- if (($data = $this->get($id)) !== FALSE)
- {
- // Add the result to the array
- $result[$id] = $data;
- }
+ if ( ! (bool) file_put_contents($this->config['directory'].str_replace(array('/', '\\', ' '), '_', $key).'~'.$tags.'~'.$lifetime, serialize($value)))
+ {
+ $success = FALSE;
}
}
- return $result;
+ return $success;
}
- /**
- * Fetches a cache item. This will delete the item if it is expired or if
- * the hash does not match the stored hash.
- *
- * @param string cache id
- * @return mixed|NULL
- */
- public function get($id)
+ public function get($keys, $single = FALSE)
{
- if ($file = $this->exists($id))
+ $items = array();
+
+ if ($files = $this->exists($keys))
{
- // Use the first file
- $file = current($file);
+ // Turn off errors while reading the files
+ $ER = error_reporting(0);
- // Validate that the cache has not expired
- if ($this->expired($file))
- {
- // Remove this cache, it has expired
- $this->delete($id);
- }
- else
+ foreach ($files as $file)
{
- // Turn off errors while reading the file
- $ER = error_reporting(0);
+ // Validate that the item has not expired
+ if ($this->expired($file))
+ continue;
+
+ list($key, $junk) = explode('~', basename($file), 2);
if (($data = file_get_contents($file)) !== FALSE)
{
@@ -173,80 +146,102 @@ class Cache_File_Driver implements Cache_Driver {
}
else
{
- // Delete the data
- unset($data);
+ $data = NULL;
}
- // Turn errors back on
- error_reporting($ER);
+ $items[$key] = $data;
}
+
+ // Turn errors back on
+ error_reporting($ER);
}
- // Return NULL if there is no data
- return isset($data) ? $data : NULL;
+ // Reutrn a single item if only one key was requested
+ if ($single)
+ {
+ return (count($items) > 0) ? current($items) : NULL;
+ }
+ else
+ {
+ return $items;
+ }
}
/**
- * Deletes a cache item by id or tag
- *
- * @param string cache id or tag, or TRUE for "all items"
- * @param boolean use tags
- * @return boolean
+ * Get cache items by tag
+ */
+ public function get_tag($tags)
+ {
+ // An array will always be returned
+ $result = array();
+
+ if ($paths = $this->exists($tags, TRUE))
+ {
+ // Find all the files with the given tag
+ foreach ($paths as $path)
+ {
+ // Get the id from the filename
+ list($key, $junk) = explode('~', basename($path), 2);
+
+ if (($data = $this->get($key)) !== FALSE)
+ {
+ // Add the result to the array
+ $result[$key] = $data;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Delete cache items by keys or tags
*/
- public function delete($id, $tag = FALSE)
+ public function delete($keys, $tag = FALSE)
{
- $files = $this->exists($id, $tag);
+ $success = TRUE;
- if (empty($files))
- return FALSE;
+ $paths = $this->exists($keys, $tag);
// Disable all error reporting while deleting
$ER = error_reporting(0);
- foreach ($files as $file)
+ foreach ($paths as $path)
{
// Remove the cache file
- if ( ! unlink($file))
- Kohana::log('error', 'Cache: Unable to delete cache file: '.$file);
+ if ( ! unlink($path))
+ {
+ Kohana::log('error', 'Cache: Unable to delete cache file: '.$path);
+ $success = FALSE;
+ }
}
// Turn on error reporting again
error_reporting($ER);
- return TRUE;
+ return $success;
}
/**
- * Deletes all cache files that are older than the current time.
- *
- * @return void
+ * Delete cache items by tag
*/
- public function delete_expired()
+ public function delete_tag($tags)
{
- if ($files = $this->exists(TRUE))
- {
- // Disable all error reporting while deleting
- $ER = error_reporting(0);
-
- foreach ($files as $file)
- {
- if ($this->expired($file))
- {
- // The cache file has already expired, delete it
- if ( ! unlink($file))
- Kohana::log('error', 'Cache: Unable to delete cache file: '.$file);
- }
- }
+ return $this->delete($tags, TRUE);
+ }
- // Turn on error reporting again
- error_reporting($ER);
- }
+ /**
+ * Empty the cache
+ */
+ public function delete_all()
+ {
+ return $this->delete(TRUE);
}
/**
* Check if a cache file has expired by filename.
*
- * @param string filename
+ * @param string|array array of filenames
* @return bool
*/
protected function expired($file)
@@ -257,5 +252,4 @@ class Cache_File_Driver implements Cache_Driver {
// Expirations of 0 are "never expire"
return ($expires !== 0 AND $expires <= time());
}
-
-} // End Cache File Driver
\ No newline at end of file
+} // End Cache Memcache Driver
diff --git a/system/libraries/drivers/Cache/Memcache.php b/system/libraries/drivers/Cache/Memcache.php
index d801de9c..636191d4 100644
--- a/system/libraries/drivers/Cache/Memcache.php
+++ b/system/libraries/drivers/Cache/Memcache.php
@@ -2,190 +2,128 @@
/**
* Memcache-based Cache driver.
*
- * $Id: Memcache.php 4102 2009-03-19 12:55:54Z Shadowhand $
+ * $Id$
*
* @package Cache
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
-class Cache_Memcache_Driver implements Cache_Driver {
-
- const TAGS_KEY = 'memcache_tags_array';
-
- // Cache backend object and flags
+class Cache_Memcache_Driver extends Cache_Driver {
+ protected $config;
protected $backend;
protected $flags;
- // Tags array
- protected static $tags;
-
- // Have the tags been changed?
- protected static $tags_changed = FALSE;
-
- public function __construct()
+ public function __construct($config)
{
if ( ! extension_loaded('memcache'))
- throw new Kohana_Exception('cache.extension_not_loaded', 'memcache');
+ throw new Kohana_Exception('The memcache PHP extension must be loaded to use this driver.');
+ ini_set('memcache.allow_failover', (isset($config['allow_failover']) AND $config['allow_failover']) ? TRUE : FALSE);
+
+ $this->config = $config;
$this->backend = new Memcache;
- $this->flags = Kohana::config('cache_memcache.compression') ? MEMCACHE_COMPRESSED : FALSE;
- $servers = Kohana::config('cache_memcache.servers');
+ $this->flags = (isset($config['compression']) AND $config['compression']) ? MEMCACHE_COMPRESSED : FALSE;
- foreach ($servers as $server)
+ foreach ($config['servers'] as $server)
{
// Make sure all required keys are set
- $server += array('host' => '127.0.0.1', 'port' => 11211, 'persistent' => FALSE);
+ $server += array('host' => '127.0.0.1',
+ 'port' => 11211,
+ 'persistent' => FALSE,
+ 'weight' => 1,
+ 'timeout' => 1,
+ 'retry_interval' => 15
+ );
// Add the server to the pool
- $this->backend->addServer($server['host'], $server['port'], (bool) $server['persistent'])
- or Kohana::log('error', 'Cache: Connection failed: '.$server['host']);
- }
-
- // Load tags
- self::$tags = $this->backend->get(self::TAGS_KEY);
-
- if ( ! is_array(self::$tags))
- {
- // Create a new tags array
- self::$tags = array();
-
- // Tags have been created
- self::$tags_changed = TRUE;
+ $this->backend->addServer($server['host'], $server['port'], (bool) $server['persistent'], (int) $server['weight'], (int) $server['timeout'], (int) $server['retry_interval'], TRUE, array($this,'_memcache_failure_callback'));
}
}
- public function __destruct()
+ public function _memcache_failure_callback($host, $port)
{
- if (self::$tags_changed === TRUE)
- {
- // Save the tags
- $this->backend->set(self::TAGS_KEY, self::$tags, $this->flags, 0);
-
- // Tags are now unchanged
- self::$tags_changed = FALSE;
- }
+ $this->backend->setServerParams($host, $port, 1, -1, FALSE);
+ Kohana_Log::add('error', __('Cache: Memcache server down: :host:::port:',array(':host:' => $host,':port:' => $port)));
}
- public function find($tag)
+ public function set($items, $tags = NULL, $lifetime = NULL)
{
- if (isset(self::$tags[$tag]) AND $results = $this->backend->get(self::$tags[$tag]))
- {
- // Return all the found caches
- return $results;
- }
- else
+ if ($lifetime !== 0)
{
- // No matching tags
- return array();
+ // Memcache driver expects unix timestamp
+ $lifetime += time();
}
- }
- public function get($id)
- {
- return (($return = $this->backend->get($id)) === FALSE) ? NULL : $return;
- }
+ if ($tags !== NULL)
+ throw new Cache_Exception('Memcache driver does not support tags');
- public function set($id, $data, array $tags = NULL, $lifetime)
- {
- if ( ! empty($tags))
+ foreach ($items as $key => $value)
{
- // Tags will be changed
- self::$tags_changed = TRUE;
+ if (is_resource($value))
+ throw new Cache_Exception('Caching of resources is impossible, because resources cannot be serialised.');
- foreach ($tags as $tag)
+ if ( ! $this->backend->set($key, $value, $this->flags, $lifetime))
{
- // Add the id to each tag
- self::$tags[$tag][$id] = $id;
+ return FALSE;
}
}
- if ($lifetime !== 0)
- {
- // Memcache driver expects unix timestamp
- $lifetime += time();
- }
-
- // Set a new value
- return $this->backend->set($id, $data, $this->flags, $lifetime);
+ return TRUE;
}
- public function delete($id, $tag = FALSE)
+ public function get($keys, $single = FALSE)
{
- // Tags will be changed
- self::$tags_changed = TRUE;
+ $items = $this->backend->get($keys);
- if ($id === TRUE)
+ if ($single)
{
- if ($status = $this->backend->flush())
- {
- // Remove all tags, all items have been deleted
- self::$tags = array();
-
- // We must sleep after flushing, or overwriting will not work!
- // @see http://php.net/manual/en/function.memcache-flush.php#81420
- sleep(1);
- }
-
- return $status;
- }
- elseif ($tag === TRUE)
- {
- if (isset(self::$tags[$id]))
- {
- foreach (self::$tags[$id] as $_id)
- {
- // Delete each id in the tag
- $this->backend->delete($_id);
- }
-
- // Delete the tag
- unset(self::$tags[$id]);
- }
-
- return TRUE;
+ return ($items === FALSE OR count($items) > 0) ? current($items) : NULL;
}
else
{
- foreach (self::$tags as $tag => $_ids)
- {
- if (isset(self::$tags[$tag][$id]))
- {
- // Remove the id from the tags
- unset(self::$tags[$tag][$id]);
- }
- }
-
- return $this->backend->delete($id);
+ return ($items === FALSE) ? array() : $items;
}
}
- public function delete_expired()
+ /**
+ * Get cache items by tag
+ */
+ public function get_tag($tags)
{
- // Tags will be changed
- self::$tags_changed = TRUE;
+ throw new Cache_Exception('Memcache driver does not support tags');
+ }
- foreach (self::$tags as $tag => $_ids)
+ /**
+ * Delete cache item by key
+ */
+ public function delete($keys)
+ {
+ foreach ($keys as $key)
{
- foreach ($_ids as $id)
+ if ( ! $this->backend->delete($key))
{
- if ( ! $this->backend->get($id))
- {
- // This id has disappeared, delete it from the tags
- unset(self::$tags[$tag][$id]);
- }
- }
-
- if (empty(self::$tags[$tag]))
- {
- // The tag no longer has any valid ids
- unset(self::$tags[$tag]);
+ return FALSE;
}
}
- // Memcache handles garbage collection internally
return TRUE;
}
+ /**
+ * Delete cache items by tag
+ */
+ public function delete_tag($tags)
+ {
+ throw new Cache_Exception('Memcache driver does not support tags');
+ }
+
+ /**
+ * Empty the cache
+ */
+ public function delete_all()
+ {
+ return $this->backend->flush();
+ }
} // End Cache Memcache Driver
diff --git a/system/libraries/drivers/Cache/Sqlite.php b/system/libraries/drivers/Cache/Sqlite.php
deleted file mode 100644
index 9458d851..00000000
--- a/system/libraries/drivers/Cache/Sqlite.php
+++ /dev/null
@@ -1,257 +0,0 @@
-db = new SQLiteDatabase($filename, '0666', $error);
-
- // Throw an exception if there's an error
- if ( ! empty($error))
- throw new Kohana_Exception('cache.driver_error', sqlite_error_string($error));
-
- $query = "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'caches'";
- $tables = $this->db->query($query, SQLITE_BOTH, $error);
-
- // Throw an exception if there's an error
- if ( ! empty($error))
- throw new Kohana_Exception('cache.driver_error', sqlite_error_string($error));
-
- if ($tables->numRows() == 0)
- {
- Kohana::log('error', 'Cache: Initializing new SQLite cache database');
-
- // Issue a CREATE TABLE command
- $this->db->unbufferedQuery(Kohana::config('cache_sqlite.schema'));
- }
- }
-
- /**
- * Checks if a cache id is already set.
- *
- * @param string cache id
- * @return boolean
- */
- public function exists($id)
- {
- // Find the id that matches
- $query = "SELECT id FROM caches WHERE id = '$id'";
-
- return ($this->db->query($query)->numRows() > 0);
- }
-
- /**
- * Sets a cache item to the given data, tags, and lifetime.
- *
- * @param string cache id to set
- * @param string data in the cache
- * @param array cache tags
- * @param integer lifetime
- * @return bool
- */
- public function set($id, $data, array $tags = NULL, $lifetime)
- {
- // Serialize and escape the data
- $data = sqlite_escape_string(serialize($data));
-
- if ( ! empty($tags))
- {
- // Escape the tags, adding brackets so the tag can be explicitly matched
- $tags = sqlite_escape_string('<'.implode('>,<', $tags).'>');
- }
-
- // Cache Sqlite driver expects unix timestamp
- if ($lifetime !== 0)
- {
- $lifetime += time();
- }
-
- $query = $this->exists($id)
- ? "UPDATE caches SET tags = '$tags', expiration = '$lifetime', cache = '$data' WHERE id = '$id'"
- : "INSERT INTO caches VALUES('$id', '$tags', '$lifetime', '$data')";
-
- // Run the query
- $this->db->unbufferedQuery($query, SQLITE_BOTH, $error);
-
- if ( ! empty($error))
- {
- self::log_error($error);
- return FALSE;
- }
- else
- {
- return TRUE;
- }
- }
-
- /**
- * Finds an array of ids for a given tag.
- *
- * @param string tag name
- * @return array of ids that match the tag
- */
- public function find($tag)
- {
- $query = "SELECT id,cache FROM caches WHERE tags LIKE '%<{$tag}>%'";
- $query = $this->db->query($query, SQLITE_BOTH, $error);
-
- // An array will always be returned
- $result = array();
-
- if ( ! empty($error))
- {
- self::log_error($error);
- }
- elseif ($query->numRows() > 0)
- {
- // Disable notices for unserializing
- $ER = error_reporting(~E_NOTICE);
-
- while ($row = $query->fetchObject())
- {
- // Add each cache to the array
- $result[$row->id] = unserialize($row->cache);
- }
-
- // Turn notices back on
- error_reporting($ER);
- }
-
- return $result;
- }
-
- /**
- * Fetches a cache item. This will delete the item if it is expired or if
- * the hash does not match the stored hash.
- *
- * @param string cache id
- * @return mixed|NULL
- */
- public function get($id)
- {
- $query = "SELECT id, expiration, cache FROM caches WHERE id = '$id' LIMIT 0, 1";
- $query = $this->db->query($query, SQLITE_BOTH, $error);
-
- if ( ! empty($error))
- {
- self::log_error($error);
- }
- elseif ($cache = $query->fetchObject())
- {
- // Make sure the expiration is valid and that the hash matches
- if ($cache->expiration != 0 AND $cache->expiration <= time())
- {
- // Cache is not valid, delete it now
- $this->delete($cache->id);
- }
- else
- {
- // Disable notices for unserializing
- $ER = error_reporting(~E_NOTICE);
-
- // Return the valid cache data
- $data = $cache->cache;
-
- // Turn notices back on
- error_reporting($ER);
- }
- }
-
- // No valid cache found
- return NULL;
- }
-
- /**
- * Deletes a cache item by id or tag
- *
- * @param string cache id or tag, or TRUE for "all items"
- * @param bool delete a tag
- * @return bool
- */
- public function delete($id, $tag = FALSE)
- {
- if ($id === TRUE)
- {
- // Delete all caches
- $where = '1';
- }
- elseif ($tag === TRUE)
- {
- // Delete by tag
- $where = "tags LIKE '%<{$id}>%'";
- }
- else
- {
- // Delete by id
- $where = "id = '$id'";
- }
-
- $this->db->unbufferedQuery('DELETE FROM caches WHERE '.$where, SQLITE_BOTH, $error);
-
- if ( ! empty($error))
- {
- self::log_error($error);
- return FALSE;
- }
- else
- {
- return TRUE;
- }
- }
-
- /**
- * Deletes all cache files that are older than the current time.
- */
- public function delete_expired()
- {
- // Delete all expired caches
- $query = 'DELETE FROM caches WHERE expiration != 0 AND expiration <= '.time();
-
- $this->db->unbufferedQuery($query);
-
- return TRUE;
- }
-
-} // End Cache SQLite Driver
\ No newline at end of file
diff --git a/system/libraries/drivers/Cache/Xcache.php b/system/libraries/drivers/Cache/Xcache.php
index 6254bbb6..ad11e6d7 100644
--- a/system/libraries/drivers/Cache/Xcache.php
+++ b/system/libraries/drivers/Cache/Xcache.php
@@ -1,85 +1,128 @@
config = $config;
}
- public function set($id, $data, array $tags = NULL, $lifetime)
+ public function set($items, $tags = NULL, $lifetime = NULL)
{
- if ( ! empty($tags))
+ if ($tags !== NULL)
{
- Kohana::log('error', 'Cache: tags are unsupported by the Xcache driver');
+ Kohana_Log::add('debug', __('Cache: XCache driver does not support tags'));
}
- return xcache_set($id, $data, $lifetime);
- }
+ foreach ($items as $key => $value)
+ {
+ if (is_resource($value))
+ throw new Cache_Exception('Caching of resources is impossible, because resources cannot be serialised.');
- public function find($tag)
- {
- Kohana::log('error', 'Cache: tags are unsupported by the Xcache driver');
- return FALSE;
+ if ( ! xcache_set($key, $value, $lifetime))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
}
- public function delete($id, $tag = FALSE)
+ public function get($keys, $single = FALSE)
{
- if ($tag !== FALSE)
- {
- Kohana::log('error', 'Cache: tags are unsupported by the Xcache driver');
- return TRUE;
- }
- elseif ($id !== TRUE)
+ $items = array();
+
+ foreach ($keys as $key)
{
if (xcache_isset($id))
- return xcache_unset($id);
+ {
+ $items[$key] = xcache_get($id);
+ }
+ else
+ {
+ $items[$key] = NULL;
+ }
+ }
- return FALSE;
+ if ($single)
+ {
+ return ($items === FALSE OR count($items) > 0) ? current($items) : NULL;
}
else
{
- // Do the login
- $this->auth();
- $result = TRUE;
- for ($i = 0, $max = xcache_count(XC_TYPE_VAR); $i < $max; $i++)
+ return ($items === FALSE) ? array() : $items;
+ }
+ }
+
+ /**
+ * Get cache items by tag
+ */
+ public function get_tag($tags)
+ {
+ Kohana_Log::add('debug', __('Cache: XCache driver does not support tags'));
+ return NULL;
+ }
+
+ /**
+ * Delete cache item by key
+ */
+ public function delete($keys)
+ {
+ foreach ($keys as $key)
+ {
+ if ( ! xcache_unset($key))
{
- if (xcache_clear_cache(XC_TYPE_VAR, $i) !== NULL)
- {
- $result = FALSE;
- break;
- }
+ return FALSE;
}
-
- // Undo the login
- $this->auth(TRUE);
- return $result;
}
return TRUE;
}
- public function delete_expired()
+ /**
+ * Delete cache items by tag
+ */
+ public function delete_tag($tags)
{
- return TRUE;
+ Kohana_Log::add('debug', __('Cache: XCache driver does not support tags'));
+ return NULL;
+ }
+
+ /**
+ * Empty the cache
+ */
+ public function delete_all()
+ {
+ $this->auth();
+ $result = TRUE;
+
+ for ($i = 0, $max = xcache_count(XC_TYPE_VAR); $i < $max; $i++)
+ {
+ if (xcache_clear_cache(XC_TYPE_VAR, $i) !== NULL)
+ {
+ $result = FALSE;
+ break;
+ }
+ }
+
+ // Undo the login
+ $this->auth(TRUE);
+
+ return $result;
}
private function auth($reverse = FALSE)
@@ -111,9 +154,8 @@ class Cache_Xcache_Driver implements Cache_Driver {
$backup[$key] = $value;
}
- $_SERVER[$key] = Kohana::config('cache_xcache.'.$key);
+ $_SERVER[$key] = $this->config->{$key};
}
}
}
-
-} // End Cache Xcache Driver
+} // End Cache XCache Driver
diff --git a/system/libraries/drivers/Captcha.php b/system/libraries/drivers/Captcha.php
deleted file mode 100644
index a4343e19..00000000
--- a/system/libraries/drivers/Captcha.php
+++ /dev/null
@@ -1,227 +0,0 @@
-response = $this->generate_challenge();
-
- // Store the correct Captcha response in a session
- Event::add('system.post_controller', array($this, 'update_response_session'));
- }
-
- /**
- * Generate a new Captcha challenge.
- *
- * @return string the challenge answer
- */
- abstract public function generate_challenge();
-
- /**
- * Output the Captcha challenge.
- *
- * @param boolean html output
- * @return mixed the rendered Captcha (e.g. an image, riddle, etc.)
- */
- abstract public function render($html);
-
- /**
- * Stores the response for the current Captcha challenge in a session so it is available
- * on the next page load for Captcha::valid(). This method is called after controller
- * execution (in the system.post_controller event) in order not to overwrite itself too soon.
- *
- * @return void
- */
- public function update_response_session()
- {
- Session::instance()->set('captcha_response', sha1(strtoupper($this->response)));
- }
-
- /**
- * Validates a Captcha response from a user.
- *
- * @param string captcha response
- * @return boolean
- */
- public function valid($response)
- {
- return (sha1(strtoupper($response)) === Session::instance()->get('captcha_response'));
- }
-
- /**
- * Returns the image type.
- *
- * @param string filename
- * @return string|FALSE image type ("png", "gif" or "jpeg")
- */
- public function image_type($filename)
- {
- switch (strtolower(substr(strrchr($filename, '.'), 1)))
- {
- case 'png':
- return 'png';
-
- case 'gif':
- return 'gif';
-
- case 'jpg':
- case 'jpeg':
- // Return "jpeg" and not "jpg" because of the GD2 function names
- return 'jpeg';
-
- default:
- return FALSE;
- }
- }
-
- /**
- * Creates an image resource with the dimensions specified in config.
- * If a background image is supplied, the image dimensions are used.
- *
- * @throws Kohana_Exception if no GD2 support
- * @param string path to the background image file
- * @return void
- */
- public function image_create($background = NULL)
- {
- // Check for GD2 support
- if ( ! function_exists('imagegd2'))
- throw new Kohana_Exception('captcha.requires_GD2');
-
- // Create a new image (black)
- $this->image = imagecreatetruecolor(Captcha::$config['width'], Captcha::$config['height']);
-
- // Use a background image
- if ( ! empty($background))
- {
- // Create the image using the right function for the filetype
- $function = 'imagecreatefrom'.$this->image_type($background);
- $this->background_image = $function($background);
-
- // Resize the image if needed
- if (imagesx($this->background_image) !== Captcha::$config['width']
- OR imagesy($this->background_image) !== Captcha::$config['height'])
- {
- imagecopyresampled
- (
- $this->image, $this->background_image, 0, 0, 0, 0,
- Captcha::$config['width'], Captcha::$config['height'],
- imagesx($this->background_image), imagesy($this->background_image)
- );
- }
-
- // Free up resources
- imagedestroy($this->background_image);
- }
- }
-
- /**
- * Fills the background with a gradient.
- *
- * @param resource gd image color identifier for start color
- * @param resource gd image color identifier for end color
- * @param string direction: 'horizontal' or 'vertical', 'random' by default
- * @return void
- */
- public function image_gradient($color1, $color2, $direction = NULL)
- {
- $directions = array('horizontal', 'vertical');
-
- // Pick a random direction if needed
- if ( ! in_array($direction, $directions))
- {
- $direction = $directions[array_rand($directions)];
-
- // Switch colors
- if (mt_rand(0, 1) === 1)
- {
- $temp = $color1;
- $color1 = $color2;
- $color2 = $temp;
- }
- }
-
- // Extract RGB values
- $color1 = imagecolorsforindex($this->image, $color1);
- $color2 = imagecolorsforindex($this->image, $color2);
-
- // Preparations for the gradient loop
- $steps = ($direction === 'horizontal') ? Captcha::$config['width'] : Captcha::$config['height'];
-
- $r1 = ($color1['red'] - $color2['red']) / $steps;
- $g1 = ($color1['green'] - $color2['green']) / $steps;
- $b1 = ($color1['blue'] - $color2['blue']) / $steps;
-
- if ($direction === 'horizontal')
- {
- $x1 =& $i;
- $y1 = 0;
- $x2 =& $i;
- $y2 = Captcha::$config['height'];
- }
- else
- {
- $x1 = 0;
- $y1 =& $i;
- $x2 = Captcha::$config['width'];
- $y2 =& $i;
- }
-
- // Execute the gradient loop
- for ($i = 0; $i <= $steps; $i++)
- {
- $r2 = $color1['red'] - floor($i * $r1);
- $g2 = $color1['green'] - floor($i * $g1);
- $b2 = $color1['blue'] - floor($i * $b1);
- $color = imagecolorallocate($this->image, $r2, $g2, $b2);
-
- imageline($this->image, $x1, $y1, $x2, $y2, $color);
- }
- }
-
- /**
- * Returns the img html element or outputs the image to the browser.
- *
- * @param boolean html output
- * @return mixed html string or void
- */
- public function image_render($html)
- {
- // Output html element
- if ($html)
- return ' ';
-
- // Send the correct HTTP header
- header('Content-Type: image/'.$this->image_type);
-
- // Pick the correct output function
- $function = 'image'.$this->image_type;
- $function($this->image);
-
- // Free up resources
- imagedestroy($this->image);
- }
-
-} // End Captcha Driver
\ No newline at end of file
diff --git a/system/libraries/drivers/Captcha/Alpha.php b/system/libraries/drivers/Captcha/Alpha.php
deleted file mode 100644
index 27795804..00000000
--- a/system/libraries/drivers/Captcha/Alpha.php
+++ /dev/null
@@ -1,92 +0,0 @@
-image
- $this->image_create(Captcha::$config['background']);
-
- // Add a random gradient
- if (empty(Captcha::$config['background']))
- {
- $color1 = imagecolorallocate($this->image, mt_rand(0, 100), mt_rand(0, 100), mt_rand(0, 100));
- $color2 = imagecolorallocate($this->image, mt_rand(0, 100), mt_rand(0, 100), mt_rand(0, 100));
- $this->image_gradient($color1, $color2);
- }
-
- // Add a few random circles
- for ($i = 0, $count = mt_rand(10, Captcha::$config['complexity'] * 3); $i < $count; $i++)
- {
- $color = imagecolorallocatealpha($this->image, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255), mt_rand(80, 120));
- $size = mt_rand(5, Captcha::$config['height'] / 3);
- imagefilledellipse($this->image, mt_rand(0, Captcha::$config['width']), mt_rand(0, Captcha::$config['height']), $size, $size, $color);
- }
-
- // Calculate character font-size and spacing
- $default_size = min(Captcha::$config['width'], Captcha::$config['height'] * 2) / strlen($this->response);
- $spacing = (int) (Captcha::$config['width'] * 0.9 / strlen($this->response));
-
- // Background alphabetic character attributes
- $color_limit = mt_rand(96, 160);
- $chars = 'ABEFGJKLPQRTVY';
-
- // Draw each Captcha character with varying attributes
- for ($i = 0, $strlen = strlen($this->response); $i < $strlen; $i++)
- {
- // Use different fonts if available
- $font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])];
-
- $angle = mt_rand(-40, 20);
- // Scale the character size on image height
- $size = $default_size / 10 * mt_rand(8, 12);
- $box = imageftbbox($size, $angle, $font, $this->response[$i]);
-
- // Calculate character starting coordinates
- $x = $spacing / 4 + $i * $spacing;
- $y = Captcha::$config['height'] / 2 + ($box[2] - $box[5]) / 4;
-
- // Draw captcha text character
- // Allocate random color, size and rotation attributes to text
- $color = imagecolorallocate($this->image, mt_rand(150, 255), mt_rand(200, 255), mt_rand(0, 255));
-
- // Write text character to image
- imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response[$i]);
-
- // Draw "ghost" alphabetic character
- $text_color = imagecolorallocatealpha($this->image, mt_rand($color_limit + 8, 255), mt_rand($color_limit + 8, 255), mt_rand($color_limit + 8, 255), mt_rand(70, 120));
- $char = $chars[mt_rand(0, 14)];
- imagettftext($this->image, $size * 2, mt_rand(-45, 45), ($x - (mt_rand(5, 10))), ($y + (mt_rand(5, 10))), $text_color, $font, $char);
- }
-
- // Output
- return $this->image_render($html);
- }
-
-} // End Captcha Alpha Driver Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Captcha/Basic.php b/system/libraries/drivers/Captcha/Basic.php
deleted file mode 100644
index d212e72c..00000000
--- a/system/libraries/drivers/Captcha/Basic.php
+++ /dev/null
@@ -1,81 +0,0 @@
-image
- $this->image_create(Captcha::$config['background']);
-
- // Add a random gradient
- if (empty(Captcha::$config['background']))
- {
- $color1 = imagecolorallocate($this->image, mt_rand(200, 255), mt_rand(200, 255), mt_rand(150, 255));
- $color2 = imagecolorallocate($this->image, mt_rand(200, 255), mt_rand(200, 255), mt_rand(150, 255));
- $this->image_gradient($color1, $color2);
- }
-
- // Add a few random lines
- for ($i = 0, $count = mt_rand(5, Captcha::$config['complexity'] * 4); $i < $count; $i++)
- {
- $color = imagecolorallocatealpha($this->image, mt_rand(0, 255), mt_rand(0, 255), mt_rand(100, 255), mt_rand(50, 120));
- imageline($this->image, mt_rand(0, Captcha::$config['width']), 0, mt_rand(0, Captcha::$config['width']), Captcha::$config['height'], $color);
- }
-
- // Calculate character font-size and spacing
- $default_size = min(Captcha::$config['width'], Captcha::$config['height'] * 2) / (strlen($this->response) + 1);
- $spacing = (int) (Captcha::$config['width'] * 0.9 / strlen($this->response));
-
- // Draw each Captcha character with varying attributes
- for ($i = 0, $strlen = strlen($this->response); $i < $strlen; $i++)
- {
- // Use different fonts if available
- $font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])];
-
- // Allocate random color, size and rotation attributes to text
- $color = imagecolorallocate($this->image, mt_rand(0, 150), mt_rand(0, 150), mt_rand(0, 150));
- $angle = mt_rand(-40, 20);
-
- // Scale the character size on image height
- $size = $default_size / 10 * mt_rand(8, 12);
- $box = imageftbbox($size, $angle, $font, $this->response[$i]);
-
- // Calculate character starting coordinates
- $x = $spacing / 4 + $i * $spacing;
- $y = Captcha::$config['height'] / 2 + ($box[2] - $box[5]) / 4;
-
- // Write text character to image
- imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response[$i]);
- }
-
- // Output
- return $this->image_render($html);
- }
-
-} // End Captcha Basic Driver Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Captcha/Black.php b/system/libraries/drivers/Captcha/Black.php
deleted file mode 100644
index 6a2e2226..00000000
--- a/system/libraries/drivers/Captcha/Black.php
+++ /dev/null
@@ -1,72 +0,0 @@
-image_create(Captcha::$config['background']);
-
- // Add random white/gray arcs, amount depends on complexity setting
- $count = (Captcha::$config['width'] + Captcha::$config['height']) / 2;
- $count = $count / 5 * min(10, Captcha::$config['complexity']);
- for ($i = 0; $i < $count; $i++)
- {
- imagesetthickness($this->image, mt_rand(1, 2));
- $color = imagecolorallocatealpha($this->image, 255, 255, 255, mt_rand(0, 120));
- imagearc($this->image, mt_rand(-Captcha::$config['width'], Captcha::$config['width']), mt_rand(-Captcha::$config['height'], Captcha::$config['height']), mt_rand(-Captcha::$config['width'], Captcha::$config['width']), mt_rand(-Captcha::$config['height'], Captcha::$config['height']), mt_rand(0, 360), mt_rand(0, 360), $color);
- }
-
- // Use different fonts if available
- $font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])];
-
- // Draw the character's white shadows
- $size = (int) min(Captcha::$config['height'] / 2, Captcha::$config['width'] * 0.8 / strlen($this->response));
- $angle = mt_rand(-15 + strlen($this->response), 15 - strlen($this->response));
- $x = mt_rand(1, Captcha::$config['width'] * 0.9 - $size * strlen($this->response));
- $y = ((Captcha::$config['height'] - $size) / 2) + $size;
- $color = imagecolorallocate($this->image, 255, 255, 255);
- imagefttext($this->image, $size, $angle, $x + 1, $y + 1, $color, $font, $this->response);
-
- // Add more shadows for lower complexities
- (Captcha::$config['complexity'] < 10) and imagefttext($this->image, $size, $angle, $x - 1, $y - 1, $color, $font , $this->response);
- (Captcha::$config['complexity'] < 8) and imagefttext($this->image, $size, $angle, $x - 2, $y + 2, $color, $font , $this->response);
- (Captcha::$config['complexity'] < 6) and imagefttext($this->image, $size, $angle, $x + 2, $y - 2, $color, $font , $this->response);
- (Captcha::$config['complexity'] < 4) and imagefttext($this->image, $size, $angle, $x + 3, $y + 3, $color, $font , $this->response);
- (Captcha::$config['complexity'] < 2) and imagefttext($this->image, $size, $angle, $x - 3, $y - 3, $color, $font , $this->response);
-
- // Finally draw the foreground characters
- $color = imagecolorallocate($this->image, 0, 0, 0);
- imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response);
-
- // Output
- return $this->image_render($html);
- }
-
-} // End Captcha Black Driver Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Captcha/Math.php b/system/libraries/drivers/Captcha/Math.php
deleted file mode 100644
index 4ac20248..00000000
--- a/system/libraries/drivers/Captcha/Math.php
+++ /dev/null
@@ -1,61 +0,0 @@
-math_exercice = implode(' + ', $numbers).' = ';
-
- // Return the answer
- return array_sum($numbers);
- }
-
- /**
- * Outputs the Captcha riddle.
- *
- * @param boolean html output
- * @return mixed
- */
- public function render($html)
- {
- return $this->math_exercice;
- }
-
-} // End Captcha Math Driver Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Captcha/Riddle.php b/system/libraries/drivers/Captcha/Riddle.php
deleted file mode 100644
index 765eeaad..00000000
--- a/system/libraries/drivers/Captcha/Riddle.php
+++ /dev/null
@@ -1,47 +0,0 @@
-riddle = $riddle[0];
-
- // Return the answer
- return $riddle[1];
- }
-
- /**
- * Outputs the Captcha riddle.
- *
- * @param boolean html output
- * @return mixed
- */
- public function render($html)
- {
- return $this->riddle;
- }
-
-} // End Captcha Riddle Driver Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Captcha/Word.php b/system/libraries/drivers/Captcha/Word.php
deleted file mode 100644
index 856bd9b4..00000000
--- a/system/libraries/drivers/Captcha/Word.php
+++ /dev/null
@@ -1,37 +0,0 @@
-cache_lifetime = $cache_setting;
+ // Restore the cached configuration
+ $this->config = $this->load_cache();
+
+ if (count($this->config) > 0)
+ $this->loaded = TRUE;
+
+ // Add the save cache method to system.shutshut event
+ Event::add('system.shutdown', array($this, 'save_cache'));
+ }
+
+ }
+
+ /**
+ * Gets a value from config. If required is TRUE
+ * then get will throw an exception if value cannot
+ * be loaded.
+ *
+ * @param string key the setting to get
+ * @param bool slash remove trailing slashes
+ * @param bool required is setting required?
+ * @return mixed
+ * @access public
+ */
+ public function get($key, $slash = FALSE, $required = FALSE)
+ {
+ // Get the group name from the key
+ $group = explode('.', $key, 2);
+ $group = $group[0];
+
+ // Check for existing value and load it dynamically if required
+ if ( ! isset($this->config[$group]))
+ $this->config[$group] = $this->load($group, $required);
+
+ // Get the value of the key string
+ $value = Kohana::key_string($this->config, $key);
+
+ if ($slash === TRUE AND is_string($value) AND $value !== '')
+ {
+ // Force the value to end with "/"
+ $value = rtrim($value, '/').'/';
+ }
+
+ if (($required === TRUE) AND ($value === null))
+ throw new Kohana_Config_Exception('Value not found in config driver');
+
+ $this->loaded = TRUE;
+ return $value;
+ }
+
+ /**
+ * Sets a new value to the configuration
+ *
+ * @param string key
+ * @param mixed value
+ * @return bool
+ * @access public
+ */
+ public function set($key, $value)
+ {
+ // Do this to make sure that the config array is already loaded
+ $this->get($key);
+
+ if (substr($key, 0, 7) === 'routes.')
+ {
+ // Routes cannot contain sub keys due to possible dots in regex
+ $keys = explode('.', $key, 2);
+ }
+ else
+ {
+ // Convert dot-noted key string to an array
+ $keys = explode('.', $key);
+ }
+
+ // Used for recursion
+ $conf =& $this->config;
+ $last = count($keys) - 1;
+
+ foreach ($keys as $i => $k)
+ {
+ if ($i === $last)
+ {
+ $conf[$k] = $value;
+ }
+ else
+ {
+ $conf =& $conf[$k];
+ }
+ }
+
+ if (substr($key,0,12) === 'core.modules')
+ {
+ // Reprocess the include paths
+ Kohana::include_paths(TRUE);
+ }
+
+ // Set config to changed
+ return $this->changed = TRUE;
+ }
+
+ /**
+ * Clear the configuration
+ *
+ * @param string group
+ * @return bool
+ * @access public
+ */
+ public function clear($group)
+ {
+ // Remove the group from config
+ unset($this->config[$group]);
+
+ // Set config to changed
+ return $this->changed = TRUE;
+ }
+
+ /**
+ * Checks whether the setting exists in
+ * config
+ *
+ * @param string $key
+ * @return bool
+ * @access public
+ */
+ public function setting_exists($key)
+ {
+ return $this->get($key) === NULL;
+ }
+
+ /**
+ * Loads a configuration group based on the setting
+ *
+ * @param string group
+ * @param bool required
+ * @return array
+ * @access public
+ * @abstract
+ */
+ abstract public function load($group, $required = FALSE);
+
+ /**
+ * Loads the cached version of this configuration driver
+ *
+ * @return array
+ * @access public
+ */
+ public function load_cache()
+ {
+ // Load the cache for this configuration
+ $cached_config = Kohana::cache($this->cache_name, $this->cache_lifetime);
+
+ // If the configuration wasn't loaded from the cache
+ if ($cached_config === NULL)
+ $cached_config = array();
+
+ // Return the cached config
+ return $cached_config;
+ }
+
+ /**
+ * Saves a cached version of this configuration driver
+ *
+ * @return bool
+ * @access public
+ */
+ public function save_cache()
+ {
+ // If this configuration has changed
+ if ($this->get('core.internal_cache') !== FALSE AND $this->changed)
+ {
+ $data = $this->config;
+
+ // Save the cache
+ return Kohana::cache_save($this->cache_name, $data, $this->cache_lifetime);
+ }
+
+ return TRUE;
+ }
+} // End Kohana_Config_Driver
\ No newline at end of file
diff --git a/system/libraries/drivers/Config/Array.php b/system/libraries/drivers/Config/Array.php
new file mode 100644
index 00000000..b2ca19ba
--- /dev/null
+++ b/system/libraries/drivers/Config/Array.php
@@ -0,0 +1,83 @@
+escape_table($table).' WHERE '.implode(' ', $where);
- }
-
- /**
- * Builds an UPDATE query.
- *
- * @param string table name
- * @param array key => value pairs
- * @param array where clause
- * @return string
- */
- public function update($table, $values, $where)
- {
- foreach ($values as $key => $val)
- {
- $valstr[] = $this->escape_column($key).' = '.$val;
- }
- return 'UPDATE '.$this->escape_table($table).' SET '.implode(', ', $valstr).' WHERE '.implode(' ',$where);
- }
-
- /**
- * Set the charset using 'SET NAMES '.
- *
- * @param string character set to use
- */
- public function set_charset($charset)
- {
- throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
- }
-
- /**
- * Wrap the tablename in backticks, has support for: table.field syntax.
- *
- * @param string table name
- * @return string
- */
- abstract public function escape_table($table);
-
- /**
- * Escape a column/field name, has support for special commands.
- *
- * @param string column name
- * @return string
- */
- abstract public function escape_column($column);
-
- /**
- * Builds a WHERE portion of a query.
- *
- * @param mixed key
- * @param string value
- * @param string type
- * @param int number of where clauses
- * @param boolean escape the value
- * @return string
- */
- public function where($key, $value, $type, $num_wheres, $quote)
- {
- $prefix = ($num_wheres == 0) ? '' : $type;
-
- if ($quote === -1)
- {
- $value = '';
- }
- else
- {
- if ($value === NULL)
- {
- if ( ! $this->has_operator($key))
- {
- $key .= ' IS';
- }
-
- $value = ' NULL';
- }
- elseif (is_bool($value))
- {
- if ( ! $this->has_operator($key))
- {
- $key .= ' =';
- }
-
- $value = ($value == TRUE) ? ' TRUE' : ' FALSE';
- }
- else
- {
- if ( ! $this->has_operator($key) AND ! empty($key))
- {
- $key = $this->escape_column($key).' =';
- }
- else
- {
- preg_match('/^(.+?)([<>!=]+|\bIS(?:\s+NULL))\s*$/i', $key, $matches);
- if (isset($matches[1]) AND isset($matches[2]))
- {
- $key = $this->escape_column(trim($matches[1])).' '.trim($matches[2]);
- }
- }
-
- $value = ' '.(($quote == TRUE) ? $this->escape($value) : $value);
- }
- }
-
- return $prefix.$key.$value;
- }
-
- /**
- * Builds a LIKE portion of a query.
- *
- * @param mixed field name
- * @param string value to match with field
- * @param boolean add wildcards before and after the match
- * @param string clause type (AND or OR)
- * @param int number of likes
- * @return string
- */
- public function like($field, $match, $auto, $type, $num_likes)
- {
- $prefix = ($num_likes == 0) ? '' : $type;
-
- $match = $this->escape_str($match);
-
- if ($auto === TRUE)
- {
- // Add the start and end quotes
- $match = '%'.str_replace('%', '\\%', $match).'%';
- }
-
- return $prefix.' '.$this->escape_column($field).' LIKE \''.$match . '\'';
- }
-
- /**
- * Builds a NOT LIKE portion of a query.
- *
- * @param mixed field name
- * @param string value to match with field
- * @param string clause type (AND or OR)
- * @param int number of likes
- * @return string
- */
- public function notlike($field, $match, $auto, $type, $num_likes)
- {
- $prefix = ($num_likes == 0) ? '' : $type;
-
- $match = $this->escape_str($match);
-
- if ($auto === TRUE)
- {
- // Add the start and end quotes
- $match = '%'.$match.'%';
- }
-
- return $prefix.' '.$this->escape_column($field).' NOT LIKE \''.$match.'\'';
- }
-
- /**
- * Builds a REGEX portion of a query.
- *
- * @param string field name
- * @param string value to match with field
- * @param string clause type (AND or OR)
- * @param integer number of regexes
- * @return string
- */
- public function regex($field, $match, $type, $num_regexs)
- {
- throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
- }
-
- /**
- * Builds a NOT REGEX portion of a query.
- *
- * @param string field name
- * @param string value to match with field
- * @param string clause type (AND or OR)
- * @param integer number of regexes
- * @return string
- */
- public function notregex($field, $match, $type, $num_regexs)
- {
- throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
- }
-
- /**
- * Builds an INSERT query.
- *
- * @param string table name
- * @param array keys
- * @param array values
- * @return string
- */
- public function insert($table, $keys, $values)
- {
- // Escape the column names
- foreach ($keys as $key => $value)
- {
- $keys[$key] = $this->escape_column($value);
- }
- return 'INSERT INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
- }
-
- /**
- * Builds a MERGE portion of a query.
- *
- * @param string table name
- * @param array keys
- * @param array values
- * @return string
- */
- public function merge($table, $keys, $values)
- {
- throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
- }
-
- /**
- * Builds a LIMIT portion of a query.
- *
- * @param integer limit
- * @param integer offset
- * @return string
- */
- abstract public function limit($limit, $offset = 0);
-
- /**
- * Creates a prepared statement.
- *
- * @param string SQL query
- * @return Database_Stmt
- */
- public function stmt_prepare($sql = '')
- {
- throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
- }
-
- /**
- * Compiles the SELECT statement.
- * Generates a query string based on which functions were used.
- * Should not be called directly, the get() function calls it.
- *
- * @param array select query values
- * @return string
- */
- abstract public function compile_select($database);
-
- /**
- * Determines if the string has an arithmetic operator in it.
- *
- * @param string string to check
- * @return boolean
- */
- public function has_operator($str)
- {
- return (bool) preg_match('/[<>!=]|\sIS(?:\s+NOT\s+)?\b|BETWEEN/i', trim($str));
- }
-
- /**
- * Escapes any input value.
- *
- * @param mixed value to escape
- * @return string
- */
- public function escape($value)
- {
- if ( ! $this->db_config['escape'])
- return $value;
-
- switch (gettype($value))
- {
- case 'string':
- $value = '\''.$this->escape_str($value).'\'';
- break;
- case 'boolean':
- $value = ($value == TRUE) ? 'TRUE' : 'FALSE';
- break;
- case 'double':
- // Convert to non-locale aware float to prevent possible commas
- $value = sprintf('%F', $value);
- break;
- default:
- $value = ($value === NULL) ? 'NULL' : $value;
- break;
- }
-
- return (string) $value;
- }
-
- /**
- * Escapes a string for a query.
- *
- * @param mixed value to escape
- * @return string
- */
- abstract public function escape_str($str);
-
- /**
- * Lists all tables in the database.
- *
- * @return array
- */
- abstract public function list_tables();
-
- /**
- * Lists all fields in a table.
- *
- * @param string table name
- * @return array
- */
- abstract function list_fields($table);
-
- /**
- * Returns the last database error.
- *
- * @return string
- */
- abstract public function show_error();
-
- /**
- * Returns field data about a table.
- *
- * @param string table name
- * @return array
- */
- abstract public function field_data($table);
-
- /**
- * Fetches SQL type information about a field, in a generic format.
- *
- * @param string field datatype
- * @return array
- */
- protected function sql_type($str)
- {
- static $sql_types;
-
- if ($sql_types === NULL)
- {
- // Load SQL data types
- $sql_types = Kohana::config('sql_types');
- }
-
- $str = strtolower(trim($str));
-
- if (($open = strpos($str, '(')) !== FALSE)
- {
- // Find closing bracket
- $close = strpos($str, ')', $open) - 1;
-
- // Find the type without the size
- $type = substr($str, 0, $open);
- }
- else
- {
- // No length
- $type = $str;
- }
-
- empty($sql_types[$type]) and exit
- (
- 'Unknown field type: '.$type.'. '.
- 'Please report this: http://trac.kohanaphp.com/newticket'
- );
-
- // Fetch the field definition
- $field = $sql_types[$type];
-
- switch ($field['type'])
- {
- case 'string':
- case 'float':
- if (isset($close))
- {
- // Add the length to the field info
- $field['length'] = substr($str, $open + 1, $close - $open);
- }
- break;
- case 'int':
- // Add unsigned value
- $field['unsigned'] = (strpos($str, 'unsigned') !== FALSE);
- break;
- }
-
- return $field;
- }
-
- /**
- * Clears the internal query cache.
- *
- * @param string SQL query
- */
- public function clear_cache($sql = NULL)
- {
- if (empty($sql))
- {
- $this->query_cache = array();
- }
- else
- {
- unset($this->query_cache[$this->query_hash($sql)]);
- }
-
- Kohana::log('debug', 'Database cache cleared: '.get_class($this));
- }
-
- /**
- * Creates a hash for an SQL query string. Replaces newlines with spaces,
- * trims, and hashes.
- *
- * @param string SQL query
- * @return string
- */
- protected function query_hash($sql)
- {
- return sha1(str_replace("\n", ' ', trim($sql)));
- }
-
-} // End Database Driver Interface
-
-/**
- * Database_Result
- *
- */
-abstract class Database_Result implements ArrayAccess, Iterator, Countable {
-
- // Result resource, insert id, and SQL
- protected $result;
- protected $insert_id;
- protected $sql;
-
- // Current and total rows
- protected $current_row = 0;
- protected $total_rows = 0;
-
- // Fetch function and return type
- protected $fetch_type;
- protected $return_type;
-
- /**
- * Returns the SQL used to fetch the result.
- *
- * @return string
- */
- public function sql()
- {
- return $this->sql;
- }
-
- /**
- * Returns the insert id from the result.
- *
- * @return mixed
- */
- public function insert_id()
- {
- return $this->insert_id;
- }
-
- /**
- * Prepares the query result.
- *
- * @param boolean return rows as objects
- * @param mixed type
- * @return Database_Result
- */
- abstract function result($object = TRUE, $type = FALSE);
-
- /**
- * Builds an array of query results.
- *
- * @param boolean return rows as objects
- * @param mixed type
- * @return array
- */
- abstract function result_array($object = NULL, $type = FALSE);
-
- /**
- * Gets the fields of an already run query.
- *
- * @return array
- */
- abstract public function list_fields();
-
- /**
- * Seek to an offset in the results.
- *
- * @return boolean
- */
- abstract public function seek($offset);
-
- /**
- * Countable: count
- */
- public function count()
- {
- return $this->total_rows;
- }
-
- /**
- * ArrayAccess: offsetExists
- */
- public function offsetExists($offset)
- {
- if ($this->total_rows > 0)
- {
- $min = 0;
- $max = $this->total_rows - 1;
-
- return ! ($offset < $min OR $offset > $max);
- }
-
- return FALSE;
- }
-
- /**
- * ArrayAccess: offsetGet
- */
- public function offsetGet($offset)
- {
- if ( ! $this->seek($offset))
- return FALSE;
-
- // Return the row by calling the defined fetching callback
- return call_user_func($this->fetch_type, $this->result, $this->return_type);
- }
-
- /**
- * ArrayAccess: offsetSet
- *
- * @throws Kohana_Database_Exception
- */
- final public function offsetSet($offset, $value)
- {
- throw new Kohana_Database_Exception('database.result_read_only');
- }
-
- /**
- * ArrayAccess: offsetUnset
- *
- * @throws Kohana_Database_Exception
- */
- final public function offsetUnset($offset)
- {
- throw new Kohana_Database_Exception('database.result_read_only');
- }
-
- /**
- * Iterator: current
- */
- public function current()
- {
- return $this->offsetGet($this->current_row);
- }
-
- /**
- * Iterator: key
- */
- public function key()
- {
- return $this->current_row;
- }
-
- /**
- * Iterator: next
- */
- public function next()
- {
- ++$this->current_row;
- return $this;
- }
-
- /**
- * Iterator: prev
- */
- public function prev()
- {
- --$this->current_row;
- return $this;
- }
-
- /**
- * Iterator: rewind
- */
- public function rewind()
- {
- $this->current_row = 0;
- return $this;
- }
-
- /**
- * Iterator: valid
- */
- public function valid()
- {
- return $this->offsetExists($this->current_row);
- }
-
-} // End Database Result Interface
diff --git a/system/libraries/drivers/Database/Mssql.php b/system/libraries/drivers/Database/Mssql.php
deleted file mode 100644
index 8b5ed50b..00000000
--- a/system/libraries/drivers/Database/Mssql.php
+++ /dev/null
@@ -1,462 +0,0 @@
-db_config = $config;
-
- Kohana::log('debug', 'MSSQL Database Driver Initialized');
- }
-
- /**
- * Closes the database connection.
- */
- public function __destruct()
- {
- is_resource($this->link) and mssql_close($this->link);
- }
-
- /**
- * Make the connection
- *
- * @return return connection
- */
- public function connect()
- {
- // Check if link already exists
- if (is_resource($this->link))
- return $this->link;
-
- // Import the connect variables
- extract($this->db_config['connection']);
-
- // Persistent connections enabled?
- $connect = ($this->db_config['persistent'] == TRUE) ? 'mssql_pconnect' : 'mssql_connect';
-
- // Build the connection info
- $host = isset($host) ? $host : $socket;
-
- // Windows uses a comma instead of a colon
- $port = (isset($port) AND is_string($port)) ? (KOHANA_IS_WIN ? ',' : ':').$port : '';
-
- // Make the connection and select the database
- if (($this->link = $connect($host.$port, $user, $pass, TRUE)) AND mssql_select_db($database, $this->link))
- {
- /* This is being removed so I can use it, will need to come up with a more elegant workaround in the future...
- *
- if ($charset = $this->db_config['character_set'])
- {
- $this->set_charset($charset);
- }
- */
-
- // Clear password after successful connect
- $this->db_config['connection']['pass'] = NULL;
-
- return $this->link;
- }
-
- return FALSE;
- }
-
- public function query($sql)
- {
- // Only cache if it's turned on, and only cache if it's not a write statement
- if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET)\b#i', $sql))
- {
- $hash = $this->query_hash($sql);
-
- if ( ! isset($this->query_cache[$hash]))
- {
- // Set the cached object
- $this->query_cache[$hash] = new Mssql_Result(mssql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
- }
- else
- {
- // Rewind cached result
- $this->query_cache[$hash]->rewind();
- }
-
- // Return the cached query
- return $this->query_cache[$hash];
- }
-
- return new Mssql_Result(mssql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
- }
-
- public function escape_table($table)
- {
- if (stripos($table, ' AS ') !== FALSE)
- {
- // Force 'AS' to uppercase
- $table = str_ireplace(' AS ', ' AS ', $table);
-
- // Runs escape_table on both sides of an AS statement
- $table = array_map(array($this, __FUNCTION__), explode(' AS ', $table));
-
- // Re-create the AS statement
- return implode(' AS ', $table);
- }
- return '['.str_replace('.', '[.]', $table).']';
- }
-
- public function escape_column($column)
- {
- if (!$this->db_config['escape'])
- return $column;
-
- if ($column == '*')
- return $column;
-
- // This matches any functions we support to SELECT.
- if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches))
- {
- if ( count($matches) == 3)
- {
- return $matches[1].'('.$this->escape_column($matches[2]).')';
- }
- else if ( count($matches) == 5)
- {
- return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]);
- }
- }
-
- // This matches any modifiers we support to SELECT.
- if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column))
- {
- if (stripos($column, ' AS ') !== FALSE)
- {
- // Force 'AS' to uppercase
- $column = str_ireplace(' AS ', ' AS ', $column);
-
- // Runs escape_column on both sides of an AS statement
- $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
-
- // Re-create the AS statement
- return implode(' AS ', $column);
- }
-
- return preg_replace('/[^.*]+/', '[$0]', $column);
- }
-
- $parts = explode(' ', $column);
- $column = '';
-
- for ($i = 0, $c = count($parts); $i < $c; $i++)
- {
- // The column is always last
- if ($i == ($c - 1))
- {
- $column .= preg_replace('/[^.*]+/', '[$0]', $parts[$i]);
- }
- else // otherwise, it's a modifier
- {
- $column .= $parts[$i].' ';
- }
- }
- return $column;
- }
-
- /**
- * Limit in SQL Server 2000 only uses the keyword
- * 'TOP'; 2007 may have an offset keyword, but
- * I am unsure - for pagination style limit,offset
- * functionality, a fancy query needs to be built.
- *
- * @param unknown_type $limit
- * @return unknown
- */
- public function limit($limit, $offset=null)
- {
- return 'TOP '.$limit;
- }
-
- public function compile_select($database)
- {
- $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
- $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
-
- if (count($database['from']) > 0)
- {
- // Escape the tables
- $froms = array();
- foreach ($database['from'] as $from)
- $froms[] = $this->escape_column($from);
- $sql .= "\nFROM ";
- $sql .= implode(', ', $froms);
- }
-
- if (count($database['join']) > 0)
- {
- foreach($database['join'] AS $join)
- {
- $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
- }
- }
-
- if (count($database['where']) > 0)
- {
- $sql .= "\nWHERE ";
- }
-
- $sql .= implode("\n", $database['where']);
-
- if (count($database['groupby']) > 0)
- {
- $sql .= "\nGROUP BY ";
- $sql .= implode(', ', $database['groupby']);
- }
-
- if (count($database['having']) > 0)
- {
- $sql .= "\nHAVING ";
- $sql .= implode("\n", $database['having']);
- }
-
- if (count($database['orderby']) > 0)
- {
- $sql .= "\nORDER BY ";
- $sql .= implode(', ', $database['orderby']);
- }
-
- if (is_numeric($database['limit']))
- {
- $sql .= "\n";
- $sql .= $this->limit($database['limit']);
- }
-
- return $sql;
- }
-
- public function escape_str($str)
- {
- if (!$this->db_config['escape'])
- return $str;
-
- is_resource($this->link) or $this->connect();
- //mssql_real_escape_string($str, $this->link); <-- this function doesn't exist
-
- $characters = array('/\x00/', '/\x1a/', '/\n/', '/\r/', '/\\\/', '/\'/');
- $replace = array('\\\x00', '\\x1a', '\\n', '\\r', '\\\\', "''");
- return preg_replace($characters, $replace, $str);
- }
-
- public function list_tables()
- {
- $sql = 'SHOW TABLES FROM ['.$this->db_config['connection']['database'].']';
- $result = $this->query($sql)->result(FALSE, MSSQL_ASSOC);
-
- $retval = array();
- foreach ($result as $row)
- {
- $retval[] = current($row);
- }
-
- return $retval;
- }
-
- public function show_error()
- {
- return mssql_get_last_message($this->link);
- }
-
- public function list_fields($table)
- {
- $result = array();
-
- foreach ($this->field_data($table) as $row)
- {
- // Make an associative array
- $result[$row->Field] = $this->sql_type($row->Type);
- }
-
- return $result;
- }
-
- public function field_data($table)
- {
- $query = $this->query("SELECT COLUMN_NAME AS Field, DATA_TYPE as Type FROM INFORMATION_SCHEMA.Columns WHERE TABLE_NAME = '".$this->escape_table($table)."'", $this->link);
-
- return $query->result_array(TRUE);
- }
-}
-
-/**
- * MSSQL Result
- */
-class Mssql_Result extends Database_Result {
-
- // Fetch function and return type
- protected $fetch_type = 'mssql_fetch_object';
- protected $return_type = MSSQL_ASSOC;
-
- /**
- * Sets up the result variables.
- *
- * @param resource query result
- * @param resource database link
- * @param boolean return objects or arrays
- * @param string SQL query that was run
- */
- public function __construct($result, $link, $object = TRUE, $sql)
- {
- $this->result = $result;
-
- // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
- if (is_resource($result))
- {
- $this->current_row = 0;
- $this->total_rows = mssql_num_rows($this->result);
- $this->fetch_type = ($object === TRUE) ? 'mssql_fetch_object' : 'mssql_fetch_array';
- }
- elseif (is_bool($result))
- {
- if ($result == FALSE)
- {
- // SQL error
- throw new Kohana_Database_Exception('database.error', mssql_get_last_message($link).' - '.$sql);
- }
- else
- {
- // Its an DELETE, INSERT, REPLACE, or UPDATE querys
- $last_id = mssql_query('SELECT @@IDENTITY AS last_id', $link);
- $result = mssql_fetch_assoc($last_id);
- $this->insert_id = $result['last_id'];
- $this->total_rows = mssql_rows_affected($link);
- }
- }
-
- // Set result type
- $this->result($object);
-
- // Store the SQL
- $this->sql = $sql;
- }
-
- /**
- * Destruct, the cleanup crew!
- */
- public function __destruct()
- {
- if (is_resource($this->result))
- {
- mssql_free_result($this->result);
- }
- }
-
- public function result($object = TRUE, $type = MSSQL_ASSOC)
- {
- $this->fetch_type = ((bool) $object) ? 'mssql_fetch_object' : 'mssql_fetch_array';
-
- // This check has to be outside the previous statement, because we do not
- // know the state of fetch_type when $object = NULL
- // NOTE - The class set by $type must be defined before fetching the result,
- // autoloading is disabled to save a lot of stupid overhead.
- if ($this->fetch_type == 'mssql_fetch_object')
- {
- $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
- }
- else
- {
- $this->return_type = $type;
- }
-
- return $this;
- }
-
- public function as_array($object = NULL, $type = MSSQL_ASSOC)
- {
- return $this->result_array($object, $type);
- }
-
- public function result_array($object = NULL, $type = MSSQL_ASSOC)
- {
- $rows = array();
-
- if (is_string($object))
- {
- $fetch = $object;
- }
- elseif (is_bool($object))
- {
- if ($object === TRUE)
- {
- $fetch = 'mssql_fetch_object';
-
- // NOTE - The class set by $type must be defined before fetching the result,
- // autoloading is disabled to save a lot of stupid overhead.
- $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
- }
- else
- {
- $fetch = 'mssql_fetch_array';
- }
- }
- else
- {
- // Use the default config values
- $fetch = $this->fetch_type;
-
- if ($fetch == 'mssql_fetch_object')
- {
- $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
- }
- }
-
- if (mssql_num_rows($this->result))
- {
- // Reset the pointer location to make sure things work properly
- mssql_data_seek($this->result, 0);
-
- while ($row = $fetch($this->result, $type))
- {
- $rows[] = $row;
- }
- }
-
- return isset($rows) ? $rows : array();
- }
-
- public function list_fields()
- {
- $field_names = array();
- while ($field = mssql_fetch_field($this->result))
- {
- $field_names[] = $field->name;
- }
-
- return $field_names;
- }
-
- public function seek($offset)
- {
- if ( ! $this->offsetExists($offset))
- return FALSE;
-
- return mssql_data_seek($this->result, $offset);
- }
-
-} // End mssql_Result Class
diff --git a/system/libraries/drivers/Database/Mysql.php b/system/libraries/drivers/Database/Mysql.php
deleted file mode 100644
index d5222f50..00000000
--- a/system/libraries/drivers/Database/Mysql.php
+++ /dev/null
@@ -1,496 +0,0 @@
-db_config = $config;
-
- Kohana::log('debug', 'MySQL Database Driver Initialized');
- }
-
- /**
- * Closes the database connection.
- */
- public function __destruct()
- {
- is_resource($this->link) and mysql_close($this->link);
- }
-
- public function connect()
- {
- // Check if link already exists
- if (is_resource($this->link))
- return $this->link;
-
- // Import the connect variables
- extract($this->db_config['connection']);
-
- // Persistent connections enabled?
- $connect = ($this->db_config['persistent'] == TRUE) ? 'mysql_pconnect' : 'mysql_connect';
-
- // Build the connection info
- $host = isset($host) ? $host : $socket;
- $port = isset($port) ? ':'.$port : '';
-
- // Make the connection and select the database
- if (($this->link = $connect($host.$port, $user, $pass, TRUE)) AND mysql_select_db($database, $this->link))
- {
- if ($charset = $this->db_config['character_set'])
- {
- $this->set_charset($charset);
- }
-
- // Clear password after successful connect
- $this->db_config['connection']['pass'] = NULL;
-
- return $this->link;
- }
-
- return FALSE;
- }
-
- public function query($sql)
- {
- // Only cache if it's turned on, and only cache if it's not a write statement
- if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET|DELETE|TRUNCATE)\b#i', $sql))
- {
- $hash = $this->query_hash($sql);
-
- if ( ! isset($this->query_cache[$hash]))
- {
- // Set the cached object
- $this->query_cache[$hash] = new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
- }
- else
- {
- // Rewind cached result
- $this->query_cache[$hash]->rewind();
- }
-
- // Return the cached query
- return $this->query_cache[$hash];
- }
-
- return new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
- }
-
- public function set_charset($charset)
- {
- $this->query('SET NAMES '.$this->escape_str($charset));
- }
-
- public function escape_table($table)
- {
- if (!$this->db_config['escape'])
- return $table;
-
- if (stripos($table, ' AS ') !== FALSE)
- {
- // Force 'AS' to uppercase
- $table = str_ireplace(' AS ', ' AS ', $table);
-
- // Runs escape_table on both sides of an AS statement
- $table = array_map(array($this, __FUNCTION__), explode(' AS ', $table));
-
- // Re-create the AS statement
- return implode(' AS ', $table);
- }
- return '`'.str_replace('.', '`.`', $table).'`';
- }
-
- public function escape_column($column)
- {
- if (!$this->db_config['escape'])
- return $column;
-
- if ($column == '*')
- return $column;
-
- // This matches any functions we support to SELECT.
- if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches))
- {
- if ( count($matches) == 3)
- {
- return $matches[1].'('.$this->escape_column($matches[2]).')';
- }
- else if ( count($matches) == 5)
- {
- return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]);
- }
- }
-
- // This matches any modifiers we support to SELECT.
- if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column))
- {
- if (stripos($column, ' AS ') !== FALSE)
- {
- // Force 'AS' to uppercase
- $column = str_ireplace(' AS ', ' AS ', $column);
-
- // Runs escape_column on both sides of an AS statement
- $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
-
- // Re-create the AS statement
- return implode(' AS ', $column);
- }
-
- return preg_replace('/[^.*]+/', '`$0`', $column);
- }
-
- $parts = explode(' ', $column);
- $column = '';
-
- for ($i = 0, $c = count($parts); $i < $c; $i++)
- {
- // The column is always last
- if ($i == ($c - 1))
- {
- $column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]);
- }
- else // otherwise, it's a modifier
- {
- $column .= $parts[$i].' ';
- }
- }
- return $column;
- }
-
- public function regex($field, $match, $type, $num_regexs)
- {
- $prefix = ($num_regexs == 0) ? '' : $type;
-
- return $prefix.' '.$this->escape_column($field).' REGEXP \''.$this->escape_str($match).'\'';
- }
-
- public function notregex($field, $match, $type, $num_regexs)
- {
- $prefix = $num_regexs == 0 ? '' : $type;
-
- return $prefix.' '.$this->escape_column($field).' NOT REGEXP \''.$this->escape_str($match) . '\'';
- }
-
- public function merge($table, $keys, $values)
- {
- // Escape the column names
- foreach ($keys as $key => $value)
- {
- $keys[$key] = $this->escape_column($value);
- }
- return 'REPLACE INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
- }
-
- public function limit($limit, $offset = 0)
- {
- return 'LIMIT '.$offset.', '.$limit;
- }
-
- public function compile_select($database)
- {
- $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
- $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
-
- if (count($database['from']) > 0)
- {
- // Escape the tables
- $froms = array();
- foreach ($database['from'] as $from)
- {
- $froms[] = $this->escape_column($from);
- }
- $sql .= "\nFROM (";
- $sql .= implode(', ', $froms).")";
- }
-
- if (count($database['join']) > 0)
- {
- foreach($database['join'] AS $join)
- {
- $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
- }
- }
-
- if (count($database['where']) > 0)
- {
- $sql .= "\nWHERE ";
- }
-
- $sql .= implode("\n", $database['where']);
-
- if (count($database['groupby']) > 0)
- {
- $sql .= "\nGROUP BY ";
- $sql .= implode(', ', $database['groupby']);
- }
-
- if (count($database['having']) > 0)
- {
- $sql .= "\nHAVING ";
- $sql .= implode("\n", $database['having']);
- }
-
- if (count($database['orderby']) > 0)
- {
- $sql .= "\nORDER BY ";
- $sql .= implode(', ', $database['orderby']);
- }
-
- if (is_numeric($database['limit']))
- {
- $sql .= "\n";
- $sql .= $this->limit($database['limit'], $database['offset']);
- }
-
- return $sql;
- }
-
- public function escape_str($str)
- {
- if (!$this->db_config['escape'])
- return $str;
-
- is_resource($this->link) or $this->connect();
-
- return mysql_real_escape_string($str, $this->link);
- }
-
- public function list_tables()
- {
- $tables = array();
-
- if ($query = $this->query('SHOW TABLES FROM '.$this->escape_table($this->db_config['connection']['database'])))
- {
- foreach ($query->result(FALSE) as $row)
- {
- $tables[] = current($row);
- }
- }
-
- return $tables;
- }
-
- public function show_error()
- {
- return mysql_error($this->link);
- }
-
- public function list_fields($table)
- {
- $result = NULL;
-
- foreach ($this->field_data($table) as $row)
- {
- // Make an associative array
- $result[$row->Field] = $this->sql_type($row->Type);
-
- if ($row->Key === 'PRI' AND $row->Extra === 'auto_increment')
- {
- // For sequenced (AUTO_INCREMENT) tables
- $result[$row->Field]['sequenced'] = TRUE;
- }
-
- if ($row->Null === 'YES')
- {
- // Set NULL status
- $result[$row->Field]['null'] = TRUE;
- }
- }
-
- if (!isset($result))
- throw new Kohana_Database_Exception('database.table_not_found', $table);
-
- return $result;
- }
-
- public function field_data($table)
- {
- $result = $this->query('SHOW COLUMNS FROM '.$this->escape_table($table));
-
- return $result->result_array(TRUE);
- }
-
-} // End Database_Mysql_Driver Class
-
-/**
- * MySQL Result
- */
-class Mysql_Result extends Database_Result {
-
- // Fetch function and return type
- protected $fetch_type = 'mysql_fetch_object';
- protected $return_type = MYSQL_ASSOC;
-
- /**
- * Sets up the result variables.
- *
- * @param resource query result
- * @param resource database link
- * @param boolean return objects or arrays
- * @param string SQL query that was run
- */
- public function __construct($result, $link, $object = TRUE, $sql)
- {
- $this->result = $result;
-
- // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
- if (is_resource($result))
- {
- $this->current_row = 0;
- $this->total_rows = mysql_num_rows($this->result);
- $this->fetch_type = ($object === TRUE) ? 'mysql_fetch_object' : 'mysql_fetch_array';
- }
- elseif (is_bool($result))
- {
- if ($result == FALSE)
- {
- // SQL error
- throw new Kohana_Database_Exception('database.error', mysql_error($link).' - '.$sql);
- }
- else
- {
- // Its an DELETE, INSERT, REPLACE, or UPDATE query
- $this->insert_id = mysql_insert_id($link);
- $this->total_rows = mysql_affected_rows($link);
- }
- }
-
- // Set result type
- $this->result($object);
-
- // Store the SQL
- $this->sql = $sql;
- }
-
- /**
- * Destruct, the cleanup crew!
- */
- public function __destruct()
- {
- if (is_resource($this->result))
- {
- mysql_free_result($this->result);
- }
- }
-
- public function result($object = TRUE, $type = MYSQL_ASSOC)
- {
- $this->fetch_type = ((bool) $object) ? 'mysql_fetch_object' : 'mysql_fetch_array';
-
- // This check has to be outside the previous statement, because we do not
- // know the state of fetch_type when $object = NULL
- // NOTE - The class set by $type must be defined before fetching the result,
- // autoloading is disabled to save a lot of stupid overhead.
- if ($this->fetch_type == 'mysql_fetch_object' AND $object === TRUE)
- {
- $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
- }
- else
- {
- $this->return_type = $type;
- }
-
- return $this;
- }
-
- public function as_array($object = NULL, $type = MYSQL_ASSOC)
- {
- return $this->result_array($object, $type);
- }
-
- public function result_array($object = NULL, $type = MYSQL_ASSOC)
- {
- $rows = array();
-
- if (is_string($object))
- {
- $fetch = $object;
- }
- elseif (is_bool($object))
- {
- if ($object === TRUE)
- {
- $fetch = 'mysql_fetch_object';
-
- $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
- }
- else
- {
- $fetch = 'mysql_fetch_array';
- }
- }
- else
- {
- // Use the default config values
- $fetch = $this->fetch_type;
-
- if ($fetch == 'mysql_fetch_object')
- {
- $type = (is_string($this->return_type) AND Kohana::auto_load($this->return_type)) ? $this->return_type : 'stdClass';
- }
- }
-
- if (mysql_num_rows($this->result))
- {
- // Reset the pointer location to make sure things work properly
- mysql_data_seek($this->result, 0);
-
- while ($row = $fetch($this->result, $type))
- {
- $rows[] = $row;
- }
- }
-
- return isset($rows) ? $rows : array();
- }
-
- public function list_fields()
- {
- $field_names = array();
- while ($field = mysql_fetch_field($this->result))
- {
- $field_names[] = $field->name;
- }
-
- return $field_names;
- }
-
- public function seek($offset)
- {
- if ($this->offsetExists($offset) AND mysql_data_seek($this->result, $offset))
- {
- // Set the current row to the offset
- $this->current_row = $offset;
-
- return TRUE;
- }
- else
- {
- return FALSE;
- }
- }
-
-} // End Mysql_Result Class
diff --git a/system/libraries/drivers/Database/Mysqli.php b/system/libraries/drivers/Database/Mysqli.php
deleted file mode 100644
index 0dd9f05c..00000000
--- a/system/libraries/drivers/Database/Mysqli.php
+++ /dev/null
@@ -1,358 +0,0 @@
-db_config = $config;
-
- Kohana::log('debug', 'MySQLi Database Driver Initialized');
- }
-
- /**
- * Closes the database connection.
- */
- public function __destruct()
- {
- is_object($this->link) and $this->link->close();
- }
-
- public function connect()
- {
- // Check if link already exists
- if (is_object($this->link))
- return $this->link;
-
- // Import the connect variables
- extract($this->db_config['connection']);
-
- // Build the connection info
- $host = isset($host) ? $host : $socket;
-
- // Make the connection and select the database
- if ($this->link = new mysqli($host, $user, $pass, $database, $port))
- {
- if ($charset = $this->db_config['character_set'])
- {
- $this->set_charset($charset);
- }
-
- // Clear password after successful connect
- $this->db_config['connection']['pass'] = NULL;
-
- return $this->link;
- }
-
- return FALSE;
- }
-
- public function query($sql)
- {
- // Only cache if it's turned on, and only cache if it's not a write statement
- if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET|DELETE|TRUNCATE)\b#i', $sql))
- {
- $hash = $this->query_hash($sql);
-
- if ( ! isset($this->query_cache[$hash]))
- {
- // Set the cached object
- $this->query_cache[$hash] = new Kohana_Mysqli_Result($this->link, $this->db_config['object'], $sql);
- }
- else
- {
- // Rewind cached result
- $this->query_cache[$hash]->rewind();
- }
-
- // Return the cached query
- return $this->query_cache[$hash];
- }
-
- return new Kohana_Mysqli_Result($this->link, $this->db_config['object'], $sql);
- }
-
- public function set_charset($charset)
- {
- if ($this->link->set_charset($charset) === FALSE)
- throw new Kohana_Database_Exception('database.error', $this->show_error());
- }
-
- public function escape_str($str)
- {
- if (!$this->db_config['escape'])
- return $str;
-
- is_object($this->link) or $this->connect();
-
- return $this->link->real_escape_string($str);
- }
-
- public function show_error()
- {
- return $this->link->error;
- }
-
-} // End Database_Mysqli_Driver Class
-
-/**
- * MySQLi Result
- */
-class Kohana_Mysqli_Result extends Database_Result {
-
- // Database connection
- protected $link;
-
- // Data fetching types
- protected $fetch_type = 'mysqli_fetch_object';
- protected $return_type = MYSQLI_ASSOC;
-
- /**
- * Sets up the result variables.
- *
- * @param object database link
- * @param boolean return objects or arrays
- * @param string SQL query that was run
- */
- public function __construct($link, $object = TRUE, $sql)
- {
- $this->link = $link;
-
- if ( ! $this->link->multi_query($sql))
- {
- // SQL error
- throw new Kohana_Database_Exception('database.error', $this->link->error.' - '.$sql);
- }
- else
- {
- $this->result = $this->link->store_result();
-
- // If the query is an object, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
- if (is_object($this->result))
- {
- $this->current_row = 0;
- $this->total_rows = $this->result->num_rows;
- $this->fetch_type = ($object === TRUE) ? 'fetch_object' : 'fetch_array';
- }
- elseif ($this->link->error)
- {
- // SQL error
- throw new Kohana_Database_Exception('database.error', $this->link->error.' - '.$sql);
- }
- else
- {
- // Its an DELETE, INSERT, REPLACE, or UPDATE query
- $this->insert_id = $this->link->insert_id;
- $this->total_rows = $this->link->affected_rows;
- }
- }
-
- // Set result type
- $this->result($object);
-
- // Store the SQL
- $this->sql = $sql;
- }
-
- /**
- * Magic __destruct function, frees the result.
- */
- public function __destruct()
- {
- if (is_object($this->result))
- {
- $this->result->free_result();
-
- // this is kinda useless, but needs to be done to avoid the "Commands out of sync; you
- // can't run this command now" error. Basically, we get all results after the first one
- // (the one we actually need) and free them.
- if (is_resource($this->link) AND $this->link->more_results())
- {
- do
- {
- if ($result = $this->link->store_result())
- {
- $result->free_result();
- }
- } while ($this->link->next_result());
- }
- }
- }
-
- public function result($object = TRUE, $type = MYSQLI_ASSOC)
- {
- $this->fetch_type = ((bool) $object) ? 'fetch_object' : 'fetch_array';
-
- // This check has to be outside the previous statement, because we do not
- // know the state of fetch_type when $object = NULL
- // NOTE - The class set by $type must be defined before fetching the result,
- // autoloading is disabled to save a lot of stupid overhead.
- if ($this->fetch_type == 'fetch_object')
- {
- $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
- }
- else
- {
- $this->return_type = $type;
- }
-
- return $this;
- }
-
- public function as_array($object = NULL, $type = MYSQLI_ASSOC)
- {
- return $this->result_array($object, $type);
- }
-
- public function result_array($object = NULL, $type = MYSQLI_ASSOC)
- {
- $rows = array();
-
- if (is_string($object))
- {
- $fetch = $object;
- }
- elseif (is_bool($object))
- {
- if ($object === TRUE)
- {
- $fetch = 'fetch_object';
-
- // NOTE - The class set by $type must be defined before fetching the result,
- // autoloading is disabled to save a lot of stupid overhead.
- $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
- }
- else
- {
- $fetch = 'fetch_array';
- }
- }
- else
- {
- // Use the default config values
- $fetch = $this->fetch_type;
-
- if ($fetch == 'fetch_object')
- {
- $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
- }
- }
-
- if ($this->result->num_rows)
- {
- // Reset the pointer location to make sure things work properly
- $this->result->data_seek(0);
-
- while ($row = $this->result->$fetch($type))
- {
- $rows[] = $row;
- }
- }
-
- return isset($rows) ? $rows : array();
- }
-
- public function list_fields()
- {
- $field_names = array();
- while ($field = $this->result->fetch_field())
- {
- $field_names[] = $field->name;
- }
-
- return $field_names;
- }
-
- public function seek($offset)
- {
- if ($this->offsetExists($offset) AND $this->result->data_seek($offset))
- {
- // Set the current row to the offset
- $this->current_row = $offset;
-
- return TRUE;
- }
-
- return FALSE;
- }
-
- public function offsetGet($offset)
- {
- if ( ! $this->seek($offset))
- return FALSE;
-
- // Return the row
- $fetch = $this->fetch_type;
- return $this->result->$fetch($this->return_type);
- }
-
-} // End Mysqli_Result Class
-
-/**
- * MySQLi Prepared Statement (experimental)
- */
-class Kohana_Mysqli_Statement {
-
- protected $link = NULL;
- protected $stmt;
- protected $var_names = array();
- protected $var_values = array();
-
- public function __construct($sql, $link)
- {
- $this->link = $link;
-
- $this->stmt = $this->link->prepare($sql);
-
- return $this;
- }
-
- public function __destruct()
- {
- $this->stmt->close();
- }
-
- // Sets the bind parameters
- public function bind_params($param_types, $params)
- {
- $this->var_names = array_keys($params);
- $this->var_values = array_values($params);
- call_user_func_array(array($this->stmt, 'bind_param'), array_merge($param_types, $var_names));
-
- return $this;
- }
-
- public function bind_result($params)
- {
- call_user_func_array(array($this->stmt, 'bind_result'), $params);
- }
-
- // Runs the statement
- public function execute()
- {
- foreach ($this->var_names as $key => $name)
- {
- $$name = $this->var_values[$key];
- }
- $this->stmt->execute();
- return $this->stmt;
- }
-}
diff --git a/system/libraries/drivers/Database/Pdosqlite.php b/system/libraries/drivers/Database/Pdosqlite.php
deleted file mode 100644
index c2d1bb21..00000000
--- a/system/libraries/drivers/Database/Pdosqlite.php
+++ /dev/null
@@ -1,486 +0,0 @@
-
- */
-
-class Database_Pdosqlite_Driver extends Database_Driver {
-
- // Database connection link
- protected $link;
- protected $db_config;
-
- /*
- * Constructor: __construct
- * Sets up the config for the class.
- *
- * Parameters:
- * config - database configuration
- *
- */
- public function __construct($config)
- {
- $this->db_config = $config;
-
- Kohana::log('debug', 'PDO:Sqlite Database Driver Initialized');
- }
-
- public function connect()
- {
- // Import the connect variables
- extract($this->db_config['connection']);
-
- try
- {
- $this->link = new PDO('sqlite:'.$socket.$database, $user, $pass,
- array(PDO::ATTR_PERSISTENT => $this->db_config['persistent']));
-
- $this->link->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL);
- //$this->link->query('PRAGMA count_changes=1;');
-
- if ($charset = $this->db_config['character_set'])
- {
- $this->set_charset($charset);
- }
- }
- catch (PDOException $e)
- {
- throw new Kohana_Database_Exception('database.error', $e->getMessage());
- }
-
- // Clear password after successful connect
- $this->db_config['connection']['pass'] = NULL;
-
- return $this->link;
- }
-
- public function query($sql)
- {
- try
- {
- $sth = $this->link->prepare($sql);
- }
- catch (PDOException $e)
- {
- throw new Kohana_Database_Exception('database.error', $e->getMessage());
- }
- return new Pdosqlite_Result($sth, $this->link, $this->db_config['object'], $sql);
- }
-
- public function set_charset($charset)
- {
- $this->link->query('PRAGMA encoding = '.$this->escape_str($charset));
- }
-
- public function escape_table($table)
- {
- if ( ! $this->db_config['escape'])
- return $table;
-
- return '`'.str_replace('.', '`.`', $table).'`';
- }
-
- public function escape_column($column)
- {
- if ( ! $this->db_config['escape'])
- return $column;
-
- if ($column == '*')
- return $column;
-
- // This matches any functions we support to SELECT.
- if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches))
- {
- if ( count($matches) == 3)
- {
- return $matches[1].'('.$this->escape_column($matches[2]).')';
- }
- else if ( count($matches) == 5)
- {
- return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]);
- }
- }
-
- // This matches any modifiers we support to SELECT.
- if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column))
- {
- if (stripos($column, ' AS ') !== FALSE)
- {
- // Force 'AS' to uppercase
- $column = str_ireplace(' AS ', ' AS ', $column);
-
- // Runs escape_column on both sides of an AS statement
- $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
-
- // Re-create the AS statement
- return implode(' AS ', $column);
- }
-
- return preg_replace('/[^.*]+/', '`$0`', $column);
- }
-
- $parts = explode(' ', $column);
- $column = '';
-
- for ($i = 0, $c = count($parts); $i < $c; $i++)
- {
- // The column is always last
- if ($i == ($c - 1))
- {
- $column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]);
- }
- else // otherwise, it's a modifier
- {
- $column .= $parts[$i].' ';
- }
- }
- return $column;
- }
-
- public function limit($limit, $offset = 0)
- {
- return 'LIMIT '.$offset.', '.$limit;
- }
-
- public function compile_select($database)
- {
- $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
- $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
-
- if (count($database['from']) > 0)
- {
- $sql .= "\nFROM ";
- $sql .= implode(', ', $database['from']);
- }
-
- if (count($database['join']) > 0)
- {
- foreach($database['join'] AS $join)
- {
- $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
- }
- }
-
- if (count($database['where']) > 0)
- {
- $sql .= "\nWHERE ";
- }
-
- $sql .= implode("\n", $database['where']);
-
- if (count($database['groupby']) > 0)
- {
- $sql .= "\nGROUP BY ";
- $sql .= implode(', ', $database['groupby']);
- }
-
- if (count($database['having']) > 0)
- {
- $sql .= "\nHAVING ";
- $sql .= implode("\n", $database['having']);
- }
-
- if (count($database['orderby']) > 0)
- {
- $sql .= "\nORDER BY ";
- $sql .= implode(', ', $database['orderby']);
- }
-
- if (is_numeric($database['limit']))
- {
- $sql .= "\n";
- $sql .= $this->limit($database['limit'], $database['offset']);
- }
-
- return $sql;
- }
-
- public function escape_str($str)
- {
- if ( ! $this->db_config['escape'])
- return $str;
-
- if (function_exists('sqlite_escape_string'))
- {
- $res = sqlite_escape_string($str);
- }
- else
- {
- $res = str_replace("'", "''", $str);
- }
- return $res;
- }
-
- public function list_tables()
- {
- $sql = "SELECT `name` FROM `sqlite_master` WHERE `type`='table' ORDER BY `name`;";
- try
- {
- $result = $this->query($sql)->result(FALSE, PDO::FETCH_ASSOC);
- $tables = array();
- foreach ($result as $row)
- {
- $tables[] = current($row);
- }
- }
- catch (PDOException $e)
- {
- throw new Kohana_Database_Exception('database.error', $e->getMessage());
- }
- return $tables;
- }
-
- public function show_error()
- {
- $err = $this->link->errorInfo();
- return isset($err[2]) ? $err[2] : 'Unknown error!';
- }
-
- public function list_fields($table, $query = FALSE)
- {
- static $tables;
- if (is_object($query))
- {
- if (empty($tables[$table]))
- {
- $tables[$table] = array();
-
- foreach ($query->result() as $row)
- {
- $tables[$table][] = $row->name;
- }
- }
-
- return $tables[$table];
- }
- else
- {
- $result = $this->link->query( 'PRAGMA table_info('.$this->escape_table($table).')' );
-
- foreach ($result as $row)
- {
- $tables[$table][$row['name']] = $this->sql_type($row['type']);
- }
-
- return $tables[$table];
- }
- }
-
- public function field_data($table)
- {
- Kohana::log('error', 'This method is under developing');
- }
- /**
- * Version number query string
- *
- * @access public
- * @return string
- */
- function version()
- {
- return $this->link->getAttribute(constant("PDO::ATTR_SERVER_VERSION"));
- }
-
-} // End Database_PdoSqlite_Driver Class
-
-/*
- * PDO-sqlite Result
- */
-class Pdosqlite_Result extends Database_Result {
-
- // Data fetching types
- protected $fetch_type = PDO::FETCH_OBJ;
- protected $return_type = PDO::FETCH_ASSOC;
-
- /**
- * Sets up the result variables.
- *
- * @param resource query result
- * @param resource database link
- * @param boolean return objects or arrays
- * @param string SQL query that was run
- */
- public function __construct($result, $link, $object = TRUE, $sql)
- {
- if (is_object($result) OR $result = $link->prepare($sql))
- {
- // run the query. Return true if success, false otherwise
- if( ! $result->execute())
- {
- // Throw Kohana Exception with error message. See PDOStatement errorInfo() method
- $arr_infos = $result->errorInfo();
- throw new Kohana_Database_Exception('database.error', $arr_infos[2]);
- }
-
- if (preg_match('/^SELECT|PRAGMA|EXPLAIN/i', $sql))
- {
- $this->result = $result;
- $this->current_row = 0;
-
- $this->total_rows = $this->sqlite_row_count();
-
- $this->fetch_type = ($object === TRUE) ? PDO::FETCH_OBJ : PDO::FETCH_ASSOC;
- }
- elseif (preg_match('/^DELETE|INSERT|UPDATE/i', $sql))
- {
- $this->insert_id = $link->lastInsertId();
-
- $this->total_rows = $result->rowCount();
- }
- }
- else
- {
- // SQL error
- throw new Kohana_Database_Exception('database.error', $link->errorInfo().' - '.$sql);
- }
-
- // Set result type
- $this->result($object);
-
- // Store the SQL
- $this->sql = $sql;
- }
-
- private function sqlite_row_count()
- {
- $count = 0;
- while ($this->result->fetch())
- {
- $count++;
- }
-
- // The query must be re-fetched now.
- $this->result->execute();
-
- return $count;
- }
-
- /*
- * Destructor: __destruct
- * Magic __destruct function, frees the result.
- */
- public function __destruct()
- {
- if (is_object($this->result))
- {
- $this->result->closeCursor();
- $this->result = NULL;
- }
- }
-
- public function result($object = TRUE, $type = PDO::FETCH_BOTH)
- {
- $this->fetch_type = (bool) $object ? PDO::FETCH_OBJ : PDO::FETCH_BOTH;
-
- if ($this->fetch_type == PDO::FETCH_OBJ)
- {
- $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
- }
- else
- {
- $this->return_type = $type;
- }
-
- return $this;
- }
-
- public function as_array($object = NULL, $type = PDO::FETCH_ASSOC)
- {
- return $this->result_array($object, $type);
- }
-
- public function result_array($object = NULL, $type = PDO::FETCH_ASSOC)
- {
- $rows = array();
-
- if (is_string($object))
- {
- $fetch = $object;
- }
- elseif (is_bool($object))
- {
- if ($object === TRUE)
- {
- $fetch = PDO::FETCH_OBJ;
-
- // NOTE - The class set by $type must be defined before fetching the result,
- // autoloading is disabled to save a lot of stupid overhead.
- $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
- }
- else
- {
- $fetch = PDO::FETCH_OBJ;
- }
- }
- else
- {
- // Use the default config values
- $fetch = $this->fetch_type;
-
- if ($fetch == PDO::FETCH_OBJ)
- {
- $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
- }
- }
- try
- {
- while ($row = $this->result->fetch($fetch))
- {
- $rows[] = $row;
- }
- }
- catch(PDOException $e)
- {
- throw new Kohana_Database_Exception('database.error', $e->getMessage());
- return FALSE;
- }
- return $rows;
- }
-
- public function list_fields()
- {
- $field_names = array();
- for ($i = 0, $max = $this->result->columnCount(); $i < $max; $i++)
- {
- $info = $this->result->getColumnMeta($i);
- $field_names[] = $info['name'];
- }
- return $field_names;
- }
-
- public function seek($offset)
- {
- // To request a scrollable cursor for your PDOStatement object, you must
- // set the PDO::ATTR_CURSOR attribute to PDO::CURSOR_SCROLL when you
- // prepare the statement.
- Kohana::log('error', get_class($this).' does not support scrollable cursors, '.__FUNCTION__.' call ignored');
-
- return FALSE;
- }
-
- public function offsetGet($offset)
- {
- try
- {
- return $this->result->fetch($this->fetch_type, PDO::FETCH_ORI_ABS, $offset);
- }
- catch(PDOException $e)
- {
- throw new Kohana_Database_Exception('database.error', $e->getMessage());
- }
- }
-
- public function rewind()
- {
- // Same problem that seek() has, see above.
- return $this->seek(0);
- }
-
-} // End PdoSqlite_Result Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Database/Pgsql.php b/system/libraries/drivers/Database/Pgsql.php
deleted file mode 100644
index c53c8439..00000000
--- a/system/libraries/drivers/Database/Pgsql.php
+++ /dev/null
@@ -1,538 +0,0 @@
-db_config = $config;
-
- Kohana::log('debug', 'PgSQL Database Driver Initialized');
- }
-
- public function connect()
- {
- // Check if link already exists
- if (is_resource($this->link))
- return $this->link;
-
- // Import the connect variables
- extract($this->db_config['connection']);
-
- // Persistent connections enabled?
- $connect = ($this->db_config['persistent'] == TRUE) ? 'pg_pconnect' : 'pg_connect';
-
- // Build the connection info
- $port = isset($port) ? 'port=\''.$port.'\'' : '';
- $host = isset($host) ? 'host=\''.$host.'\' '.$port : ''; // if no host, connect with the socket
-
- $connection_string = $host.' dbname=\''.$database.'\' user=\''.$user.'\' password=\''.$pass.'\'';
- // Make the connection and select the database
- if ($this->link = $connect($connection_string))
- {
- if ($charset = $this->db_config['character_set'])
- {
- echo $this->set_charset($charset);
- }
-
- // Clear password after successful connect
- $this->db_config['connection']['pass'] = NULL;
-
- return $this->link;
- }
-
- return FALSE;
- }
-
- public function query($sql)
- {
- // Only cache if it's turned on, and only cache if it's not a write statement
- if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|SET)\b#i', $sql))
- {
- $hash = $this->query_hash($sql);
-
- if ( ! isset($this->query_cache[$hash]))
- {
- // Set the cached object
- $this->query_cache[$hash] = new Pgsql_Result(pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql);
- }
- else
- {
- // Rewind cached result
- $this->query_cache[$hash]->rewind();
- }
-
- return $this->query_cache[$hash];
- }
-
- // Suppress warning triggered when a database error occurs (e.g., a constraint violation)
- return new Pgsql_Result(@pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql);
- }
-
- public function set_charset($charset)
- {
- $this->query('SET client_encoding TO '.pg_escape_string($this->link, $charset));
- }
-
- public function escape_table($table)
- {
- if (!$this->db_config['escape'])
- return $table;
-
- return '"'.str_replace('.', '"."', $table).'"';
- }
-
- public function escape_column($column)
- {
- if (!$this->db_config['escape'])
- return $column;
-
- if ($column == '*')
- return $column;
-
- // This matches any functions we support to SELECT.
- if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches))
- {
- if ( count($matches) == 3)
- {
- return $matches[1].'('.$this->escape_column($matches[2]).')';
- }
- else if ( count($matches) == 5)
- {
- return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]);
- }
- }
-
- // This matches any modifiers we support to SELECT.
- if ( ! preg_match('/\b(?:all|distinct)\s/i', $column))
- {
- if (stripos($column, ' AS ') !== FALSE)
- {
- // Force 'AS' to uppercase
- $column = str_ireplace(' AS ', ' AS ', $column);
-
- // Runs escape_column on both sides of an AS statement
- $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
-
- // Re-create the AS statement
- return implode(' AS ', $column);
- }
-
- return preg_replace('/[^.*]+/', '"$0"', $column);
- }
-
- $parts = explode(' ', $column);
- $column = '';
-
- for ($i = 0, $c = count($parts); $i < $c; $i++)
- {
- // The column is always last
- if ($i == ($c - 1))
- {
- $column .= preg_replace('/[^.*]+/', '"$0"', $parts[$i]);
- }
- else // otherwise, it's a modifier
- {
- $column .= $parts[$i].' ';
- }
- }
- return $column;
- }
-
- public function regex($field, $match, $type, $num_regexs)
- {
- $prefix = ($num_regexs == 0) ? '' : $type;
-
- return $prefix.' '.$this->escape_column($field).' ~* \''.$this->escape_str($match).'\'';
- }
-
- public function notregex($field, $match, $type, $num_regexs)
- {
- $prefix = $num_regexs == 0 ? '' : $type;
-
- return $prefix.' '.$this->escape_column($field).' !~* \''.$this->escape_str($match) . '\'';
- }
-
- public function limit($limit, $offset = 0)
- {
- return 'LIMIT '.$limit.' OFFSET '.$offset;
- }
-
- public function compile_select($database)
- {
- $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
- $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
-
- if (count($database['from']) > 0)
- {
- $sql .= "\nFROM ";
- $sql .= implode(', ', $database['from']);
- }
-
- if (count($database['join']) > 0)
- {
- foreach($database['join'] AS $join)
- {
- $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
- }
- }
-
- if (count($database['where']) > 0)
- {
- $sql .= "\nWHERE ";
- }
-
- $sql .= implode("\n", $database['where']);
-
- if (count($database['groupby']) > 0)
- {
- $sql .= "\nGROUP BY ";
- $sql .= implode(', ', $database['groupby']);
- }
-
- if (count($database['having']) > 0)
- {
- $sql .= "\nHAVING ";
- $sql .= implode("\n", $database['having']);
- }
-
- if (count($database['orderby']) > 0)
- {
- $sql .= "\nORDER BY ";
- $sql .= implode(', ', $database['orderby']);
- }
-
- if (is_numeric($database['limit']))
- {
- $sql .= "\n";
- $sql .= $this->limit($database['limit'], $database['offset']);
- }
-
- return $sql;
- }
-
- public function escape_str($str)
- {
- if (!$this->db_config['escape'])
- return $str;
-
- is_resource($this->link) or $this->connect();
-
- return pg_escape_string($this->link, $str);
- }
-
- public function list_tables()
- {
- $sql = 'SELECT table_schema || \'.\' || table_name FROM information_schema.tables WHERE table_schema NOT IN (\'pg_catalog\', \'information_schema\')';
- $result = $this->query($sql)->result(FALSE, PGSQL_ASSOC);
-
- $retval = array();
- foreach ($result as $row)
- {
- $retval[] = current($row);
- }
-
- return $retval;
- }
-
- public function show_error()
- {
- return pg_last_error($this->link);
- }
-
- public function list_fields($table)
- {
- $result = NULL;
-
- foreach ($this->field_data($table) as $row)
- {
- // Make an associative array
- $result[$row->column_name] = $this->sql_type($row->data_type);
-
- if (!strncmp($row->column_default, 'nextval(', 8))
- {
- $result[$row->column_name]['sequenced'] = TRUE;
- }
-
- if ($row->is_nullable === 'YES')
- {
- $result[$row->column_name]['null'] = TRUE;
- }
- }
-
- if (!isset($result))
- throw new Kohana_Database_Exception('database.table_not_found', $table);
-
- return $result;
- }
-
- public function field_data($table)
- {
- // http://www.postgresql.org/docs/8.3/static/infoschema-columns.html
- $result = $this->query('
- SELECT column_name, column_default, is_nullable, data_type, udt_name,
- character_maximum_length, numeric_precision, numeric_precision_radix, numeric_scale
- FROM information_schema.columns
- WHERE table_name = \''. $this->escape_str($table) .'\'
- ORDER BY ordinal_position
- ');
-
- return $result->result_array(TRUE);
- }
-
-} // End Database_Pgsql_Driver Class
-
-/**
- * PostgreSQL Result
- */
-class Pgsql_Result extends Database_Result {
-
- // Data fetching types
- protected $fetch_type = 'pgsql_fetch_object';
- protected $return_type = PGSQL_ASSOC;
-
- /**
- * Sets up the result variables.
- *
- * @param resource query result
- * @param resource database link
- * @param boolean return objects or arrays
- * @param string SQL query that was run
- */
- public function __construct($result, $link, $object = TRUE, $sql)
- {
- $this->link = $link;
- $this->result = $result;
-
- // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
- if (is_resource($result))
- {
- // Its an DELETE, INSERT, REPLACE, or UPDATE query
- if (preg_match('/^(?:delete|insert|replace|update)\b/iD', trim($sql), $matches))
- {
- $this->insert_id = (strtolower($matches[0]) == 'insert') ? $this->insert_id() : FALSE;
- $this->total_rows = pg_affected_rows($this->result);
- }
- else
- {
- $this->current_row = 0;
- $this->total_rows = pg_num_rows($this->result);
- $this->fetch_type = ($object === TRUE) ? 'pg_fetch_object' : 'pg_fetch_array';
- }
- }
- else
- {
- throw new Kohana_Database_Exception('database.error', pg_last_error().' - '.$sql);
- }
-
- // Set result type
- $this->result($object);
-
- // Store the SQL
- $this->sql = $sql;
- }
-
- /**
- * Magic __destruct function, frees the result.
- */
- public function __destruct()
- {
- if (is_resource($this->result))
- {
- pg_free_result($this->result);
- }
- }
-
- public function result($object = TRUE, $type = PGSQL_ASSOC)
- {
- $this->fetch_type = ((bool) $object) ? 'pg_fetch_object' : 'pg_fetch_array';
-
- // This check has to be outside the previous statement, because we do not
- // know the state of fetch_type when $object = NULL
- // NOTE - The class set by $type must be defined before fetching the result,
- // autoloading is disabled to save a lot of stupid overhead.
- if ($this->fetch_type == 'pg_fetch_object')
- {
- $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
- }
- else
- {
- $this->return_type = $type;
- }
-
- return $this;
- }
-
- public function as_array($object = NULL, $type = PGSQL_ASSOC)
- {
- return $this->result_array($object, $type);
- }
-
- public function result_array($object = NULL, $type = PGSQL_ASSOC)
- {
- $rows = array();
-
- if (is_string($object))
- {
- $fetch = $object;
- }
- elseif (is_bool($object))
- {
- if ($object === TRUE)
- {
- $fetch = 'pg_fetch_object';
-
- // NOTE - The class set by $type must be defined before fetching the result,
- // autoloading is disabled to save a lot of stupid overhead.
- $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
- }
- else
- {
- $fetch = 'pg_fetch_array';
- }
- }
- else
- {
- // Use the default config values
- $fetch = $this->fetch_type;
-
- if ($fetch == 'pg_fetch_object')
- {
- $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
- }
- }
-
- if ($this->total_rows)
- {
- pg_result_seek($this->result, 0);
-
- while ($row = $fetch($this->result, NULL, $type))
- {
- $rows[] = $row;
- }
- }
-
- return $rows;
- }
-
- public function insert_id()
- {
- if ($this->insert_id === NULL)
- {
- $query = 'SELECT LASTVAL() AS insert_id';
-
- // Disable error reporting for this, just to silence errors on
- // tables that have no serial column.
- $ER = error_reporting(0);
-
- $result = pg_query($this->link, $query);
- $insert_id = pg_fetch_array($result, NULL, PGSQL_ASSOC);
-
- $this->insert_id = $insert_id['insert_id'];
-
- // Reset error reporting
- error_reporting($ER);
- }
-
- return $this->insert_id;
- }
-
- public function seek($offset)
- {
- if ($this->offsetExists($offset) AND pg_result_seek($this->result, $offset))
- {
- // Set the current row to the offset
- $this->current_row = $offset;
-
- return TRUE;
- }
-
- return FALSE;
- }
-
- public function list_fields()
- {
- $field_names = array();
-
- $fields = pg_num_fields($this->result);
- for ($i = 0; $i < $fields; ++$i)
- {
- $field_names[] = pg_field_name($this->result, $i);
- }
-
- return $field_names;
- }
-
- /**
- * ArrayAccess: offsetGet
- */
- public function offsetGet($offset)
- {
- if ( ! $this->seek($offset))
- return FALSE;
-
- // Return the row by calling the defined fetching callback
- $fetch = $this->fetch_type;
- return $fetch($this->result, NULL, $this->return_type);
- }
-
-} // End Pgsql_Result Class
-
-/**
- * PostgreSQL Prepared Statement (experimental)
- */
-class Kohana_Pgsql_Statement {
-
- protected $link = NULL;
- protected $stmt;
-
- public function __construct($sql, $link)
- {
- $this->link = $link;
-
- $this->stmt = $this->link->prepare($sql);
-
- return $this;
- }
-
- public function __destruct()
- {
- $this->stmt->close();
- }
-
- // Sets the bind parameters
- public function bind_params()
- {
- $argv = func_get_args();
- return $this;
- }
-
- // sets the statement values to the bound parameters
- public function set_vals()
- {
- return $this;
- }
-
- // Runs the statement
- public function execute()
- {
- return $this;
- }
-}
diff --git a/system/libraries/drivers/Image.php b/system/libraries/drivers/Image.php
index f89ba953..39936c30 100644
--- a/system/libraries/drivers/Image.php
+++ b/system/libraries/drivers/Image.php
@@ -2,12 +2,12 @@
/**
* Image API driver.
*
- * $Id: Image.php 3769 2008-12-15 00:48:56Z zombor $
+ * $Id: Image.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Image
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
abstract class Image_Driver {
@@ -102,9 +102,11 @@ abstract class Image_Driver {
* @param array actions to execute
* @param string destination directory path
* @param string destination filename
+ * @param boolean render the image
+ * @param string background color
* @return boolean
*/
- abstract public function process($image, $actions, $dir, $file);
+ abstract public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL);
/**
* Flip an image. Valid directions are horizontal and vertical.
diff --git a/system/libraries/drivers/Image/GD.php b/system/libraries/drivers/Image/GD.php
index be2af4e2..6ffffe8a 100644
--- a/system/libraries/drivers/Image/GD.php
+++ b/system/libraries/drivers/Image/GD.php
@@ -2,12 +2,12 @@
/**
* GD Image Driver.
*
- * $Id: GD.php 3769 2008-12-15 00:48:56Z zombor $
+ * $Id: GD.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Image
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class Image_GD_Driver extends Image_Driver {
@@ -20,17 +20,17 @@ class Image_GD_Driver extends Image_Driver {
{
// Make sure that GD2 is available
if ( ! function_exists('gd_info'))
- throw new Kohana_Exception('image.gd.requires_v2');
+ throw new Kohana_Exception('The Image library requires GD2. Please see http://php.net/gd_info for more information.');
// Get the GD information
$info = gd_info();
// Make sure that the GD2 is installed
if (strpos($info['GD Version'], '2.') === FALSE)
- throw new Kohana_Exception('image.gd.requires_v2');
+ throw new Kohana_Exception('The Image library requires GD2. Please see http://php.net/gd_info for more information.');
}
- public function process($image, $actions, $dir, $file, $render = FALSE)
+ public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL)
{
// Set the "create" function
switch ($image['type'])
@@ -63,11 +63,11 @@ class Image_GD_Driver extends Image_Driver {
// Make sure the image type is supported for import
if (empty($create) OR ! function_exists($create))
- throw new Kohana_Exception('image.type_not_allowed', $image['file']);
+ throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $image['file']));
// Make sure the image type is supported for saving
if (empty($save) OR ! function_exists($save))
- throw new Kohana_Exception('image.type_not_allowed', $dir.$file);
+ throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $dir.$file));
// Load the image
$this->image = $image;
@@ -211,11 +211,11 @@ class Image_GD_Driver extends Image_Driver {
// Recalculate the percentage to a pixel size
$properties['height'] = round($height * (substr($properties['height'], 0, -1) / 100));
}
-
+
// Recalculate the width and height, if they are missing
empty($properties['width']) and $properties['width'] = round($width * $properties['height'] / $height);
empty($properties['height']) and $properties['height'] = round($height * $properties['width'] / $width);
-
+
if ($properties['master'] === Image::AUTO)
{
// Change an automatic master dim to the correct type
@@ -314,7 +314,7 @@ class Image_GD_Driver extends Image_Driver {
{
// Make sure that the sharpening function is available
if ( ! function_exists('imageconvolution'))
- throw new Kohana_Exception('image.unsupported_method', __FUNCTION__);
+ throw new Kohana_Exception('Your configured driver does not support the :method: image transformation.', array(':method:' => __FUNCTION__));
// Amount should be in the range of 18-10
$amount = round(abs(-18 + ($amount * 0.08)), 2);
@@ -336,23 +336,52 @@ class Image_GD_Driver extends Image_Driver {
switch($properties['mime'])
{
case "image/jpeg":
- $overlay_img = imagecreatefromjpeg($properties['overlay_file']);
+ $overlay_img = imagecreatefromjpeg($properties['overlay_file']);
break;
case "image/gif":
- $overlay_img = imagecreatefromgif($properties['overlay_file']);
+ $overlay_img = imagecreatefromgif($properties['overlay_file']);
break;
case "image/png":
- $overlay_img = imagecreatefrompng($properties['overlay_file']);
+ $overlay_img = imagecreatefrompng($properties['overlay_file']);
break;
}
- imagecopymerge($this->tmp_image, $overlay_img, $properties['x'], $properties['y'], 0, 0, imagesx($overlay_img), imagesy($overlay_img), $properties['transparency']);
+ $this->imagecopymerge_alpha($this->tmp_image, $overlay_img, $properties['x'], $properties['y'], 0, 0, imagesx($overlay_img), imagesy($overlay_img), $properties['transparency']);
+
imagedestroy($overlay_img);
+
return TRUE;
}
+ /**
+ * A replacement for php's imagecopymerge() function that supports the alpha channel
+ * See php bug #23815: http://bugs.php.net/bug.php?id=23815
+ *
+ * @param resource $dst_im Destination image link resource
+ * @param resource $src_im Source image link resource
+ * @param integer $dst_x x-coordinate of destination point
+ * @param integer $dst_y y-coordinate of destination point
+ * @param integer $src_x x-coordinate of source point
+ * @param integer $src_y y-coordinate of source point
+ * @param integer $src_w Source width
+ * @param integer $src_h Source height
+ * @param integer $pct Transparency percent (0 to 100)
+ */
+ protected function imagecopymerge_alpha($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct)
+ {
+ // Create a new blank image the site of our source image
+ $cut = imagecreatetruecolor($src_w, $src_h);
+
+ // Copy the blank image into the destination image where the source goes
+ imagecopy($cut, $dst_im, 0, 0, $dst_x, $dst_y, $src_w, $src_h);
+
+ // Place the source image in the destination image
+ imagecopy($cut, $src_im, 0, 0, $src_x, $src_y, $src_w, $src_h);
+ imagecopymerge($dst_im, $cut, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct);
+ }
+
protected function properties()
{
return array(imagesx($this->tmp_image), imagesy($this->tmp_image));
@@ -368,6 +397,16 @@ class Image_GD_Driver extends Image_Driver {
*/
protected function imagecreatetransparent($width, $height)
{
+ if ($width < 1)
+ {
+ $width = 1;
+ }
+
+ if ($height < 1)
+ {
+ $height = 1;
+ }
+
if (self::$blank_png === NULL)
{
// Decode the blank PNG if it has not been done already
diff --git a/system/libraries/drivers/Image/GraphicsMagick.php b/system/libraries/drivers/Image/GraphicsMagick.php
index a8bc4d9b..89b40b41 100644
--- a/system/libraries/drivers/Image/GraphicsMagick.php
+++ b/system/libraries/drivers/Image/GraphicsMagick.php
@@ -4,8 +4,8 @@
*
* @package Image
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class Image_GraphicsMagick_Driver extends Image_Driver {
@@ -31,7 +31,7 @@ class Image_GraphicsMagick_Driver extends Image_Driver {
{
// Attempt to locate GM by using "which" (only works for *nix!)
if ( ! is_file($path = exec('which gm')))
- throw new Kohana_Exception('image.graphicsmagick.not_found');
+ throw new Kohana_Exception('The GraphicsMagick directory specified does not contain a required program.');
$config['directory'] = dirname($path);
}
@@ -41,7 +41,7 @@ class Image_GraphicsMagick_Driver extends Image_Driver {
// Check to make sure the provided path is correct
if ( ! is_file(realpath($config['directory']).'/gm'.$this->ext))
- throw new Kohana_Exception('image.graphicsmagick.not_found', 'gm'.$this->ext);
+ throw new Kohana_Exception('The GraphicsMagick directory specified does not contain a required program, :gm:.', array(':gm:' => 'gm'.$this->ext));
// Set the installation directory
@@ -52,8 +52,12 @@ class Image_GraphicsMagick_Driver extends Image_Driver {
* Creates a temporary image and executes the given actions. By creating a
* temporary copy of the image before manipulating it, this process is atomic.
*/
- public function process($image, $actions, $dir, $file, $render = FALSE)
+ public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL)
{
+ // Need to implement $background support
+ if ($background !== NULL)
+ throw new Kohana_Exception('The GraphicsMagick driver does not support setting a background color');
+
// We only need the filename
$image = $image['file'];
diff --git a/system/libraries/drivers/Image/ImageMagick.php b/system/libraries/drivers/Image/ImageMagick.php
index 4b381fd6..31862f75 100644
--- a/system/libraries/drivers/Image/ImageMagick.php
+++ b/system/libraries/drivers/Image/ImageMagick.php
@@ -2,12 +2,12 @@
/**
* ImageMagick Image Driver.
*
- * $Id: ImageMagick.php 3769 2008-12-15 00:48:56Z zombor $
+ * $Id: ImageMagick.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Image
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class Image_ImageMagick_Driver extends Image_Driver {
@@ -33,7 +33,7 @@ class Image_ImageMagick_Driver extends Image_Driver {
{
// Attempt to locate IM by using "which" (only works for *nix!)
if ( ! is_file($path = exec('which convert')))
- throw new Kohana_Exception('image.imagemagick.not_found');
+ throw new Kohana_Exception('The ImageMagick directory specified does not contain a required program.');
$config['directory'] = dirname($path);
}
@@ -43,7 +43,7 @@ class Image_ImageMagick_Driver extends Image_Driver {
// Check to make sure the provided path is correct
if ( ! is_file(realpath($config['directory']).'/convert'.$this->ext))
- throw new Kohana_Exception('image.imagemagick.not_found', 'convert'.$this->ext);
+ throw new Kohana_Exception('The ImageMagick directory specified does not contain a required program, :im:', array(':im:' => 'convert'.$this->ext));
// Set the installation directory
$this->dir = str_replace('\\', '/', realpath($config['directory'])).'/';
@@ -53,7 +53,7 @@ class Image_ImageMagick_Driver extends Image_Driver {
* Creates a temporary image and executes the given actions. By creating a
* temporary copy of the image before manipulating it, this process is atomic.
*/
- public function process($image, $actions, $dir, $file, $render = FALSE)
+ public function process($image, $actions, $dir, $file, $render = FALSE, $background = NULL)
{
// We only need the filename
$image = $image['file'];
@@ -70,23 +70,34 @@ class Image_ImageMagick_Driver extends Image_Driver {
// Use 95 for the default quality
empty($quality) and $quality = 95;
+ if (is_string($background))
+ {
+ // Set the background color
+ $this->background = escapeshellarg($background);
+ }
+ else
+ {
+ // Use a transparent background
+ $this->background = 'transparent';
+ }
+
// All calls to these will need to be escaped, so do it now
$this->cmd_image = escapeshellarg($this->tmp_image);
- $this->new_image = ($render)? $this->cmd_image : escapeshellarg($dir.$file);
+ $this->new_image = $render ? $this->cmd_image : escapeshellarg($dir.$file);
if ($status = $this->execute($actions))
{
// Use convert to change the image into its final version. This is
// done to allow the file type to change correctly, and to handle
// the quality conversion in the most effective way possible.
- if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -quality '.$quality.'% '.$this->cmd_image.' '.$this->new_image))
+ if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -quality '.$quality.'% '.$this->cmd_image.' '.$this->new_image))
{
$this->errors[] = $error;
}
else
{
// Output the image directly to the browser
- if ($render !== FALSE)
+ if ($render === TRUE)
{
$contents = file_get_contents($this->tmp_image);
switch (substr($file, strrpos($file, '.') + 1))
@@ -122,7 +133,7 @@ class Image_ImageMagick_Driver extends Image_Driver {
// Set the IM geometry based on the properties
$geometry = escapeshellarg($prop['width'].'x'.$prop['height'].'+'.$prop['left'].'+'.$prop['top']);
- if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -crop '.$geometry.' '.$this->cmd_image.' '.$this->cmd_image))
+ if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -crop '.$geometry.' '.$this->cmd_image.' '.$this->cmd_image))
{
$this->errors[] = $error;
return FALSE;
@@ -136,7 +147,7 @@ class Image_ImageMagick_Driver extends Image_Driver {
// Convert the direction into a IM command
$dir = ($dir === Image::HORIZONTAL) ? '-flop' : '-flip';
- if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' '.$dir.' '.$this->cmd_image.' '.$this->cmd_image))
+ if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten '.$dir.' '.$this->cmd_image.' '.$this->cmd_image))
{
$this->errors[] = $error;
return FALSE;
@@ -164,7 +175,7 @@ class Image_ImageMagick_Driver extends Image_Driver {
}
// Use "convert" to change the width and height
- if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -resize '.$dim.' '.$this->cmd_image.' '.$this->cmd_image))
+ if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -resize '.$dim.' '.$this->cmd_image.' '.$this->cmd_image))
{
$this->errors[] = $error;
return FALSE;
@@ -175,7 +186,7 @@ class Image_ImageMagick_Driver extends Image_Driver {
public function rotate($amt)
{
- if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -rotate '.escapeshellarg($amt).' -background transparent '.$this->cmd_image.' '.$this->cmd_image))
+ if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -rotate '.escapeshellarg($amt).' -background transparent '.$this->cmd_image.' '.$this->cmd_image))
{
$this->errors[] = $error;
return FALSE;
@@ -195,7 +206,7 @@ class Image_ImageMagick_Driver extends Image_Driver {
// Convert the amount to an IM command
$sharpen = escapeshellarg($radius.'x'.$sigma.'+'.$amount.'+0');
- if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -unsharp '.$sharpen.' '.$this->cmd_image.' '.$this->cmd_image))
+ if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -background '.$this->background.' -flatten -unsharp '.$sharpen.' '.$this->cmd_image.' '.$this->cmd_image))
{
$this->errors[] = $error;
return FALSE;
diff --git a/system/libraries/drivers/Log.php b/system/libraries/drivers/Log.php
new file mode 100644
index 00000000..cd6dba7d
--- /dev/null
+++ b/system/libraries/drivers/Log.php
@@ -0,0 +1,22 @@
+config = $config;
+ }
+
+ abstract public function save(array $messages);
+}
\ No newline at end of file
diff --git a/system/libraries/drivers/Log/Database.php b/system/libraries/drivers/Log/Database.php
new file mode 100644
index 00000000..19db9747
--- /dev/null
+++ b/system/libraries/drivers/Log/Database.php
@@ -0,0 +1,40 @@
+config['group'])
+ ->insert($this->config['table'])
+ ->columns(array('date', 'level', 'message'));
+
+ $run_insert = FALSE;
+
+ foreach ($messages AS $message)
+ {
+ if ($this->config['log_levels'][$message['type']] <= $this->config['log_threshold'])
+ {
+ // Add new message to database
+ $insert->values($message);
+
+ // There is data to insert
+ $run_insert = TRUE;
+ }
+ }
+
+ // Update the database
+ if ($run_insert)
+ {
+ $insert->execute();
+ }
+ }
+}
\ No newline at end of file
diff --git a/system/libraries/drivers/Log/File.php b/system/libraries/drivers/Log/File.php
new file mode 100644
index 00000000..6ad565b4
--- /dev/null
+++ b/system/libraries/drivers/Log/File.php
@@ -0,0 +1,44 @@
+config['log_directory'].'/'.date('Y-m-d').'.log'.EXT;
+
+ if ( ! is_file($filename))
+ {
+ // Write the SYSPATH checking header
+ file_put_contents($filename,
+ ''.PHP_EOL.PHP_EOL);
+
+ // Prevent external writes
+ chmod($filename, $this->config['posix_permissions']);
+ }
+
+ foreach ($messages AS $message)
+ {
+ if ($this->config['log_levels'][$message['type']] <= $this->config['log_threshold'])
+ {
+ // Add a new message line
+ $messages_to_write[] = date($this->config['date_format'], $message['date']).' --- '.$message['type'].': '.$message['message'];
+ }
+ }
+
+ if ( ! empty($messages_to_write))
+ {
+ // Write messages to log file
+ file_put_contents($filename, implode(PHP_EOL, $messages_to_write).PHP_EOL, FILE_APPEND);
+ }
+ }
+}
\ No newline at end of file
diff --git a/system/libraries/drivers/Log/Syslog.php b/system/libraries/drivers/Log/Syslog.php
new file mode 100644
index 00000000..5da5d255
--- /dev/null
+++ b/system/libraries/drivers/Log/Syslog.php
@@ -0,0 +1,34 @@
+ LOG_ERR,
+ 'alert' => LOG_WARNING,
+ 'info' => LOG_INFO,
+ 'debug' => LOG_DEBUG);
+
+ public function save(array $messages)
+ {
+ // Open the connection to syslog
+ openlog($this->config['ident'], LOG_CONS, LOG_USER);
+
+ do
+ {
+ // Load the next message
+ list ($date, $type, $text) = array_shift($messages);
+
+ syslog($this->syslog_levels[$type], $text);
+ }
+ while ( ! empty($messages));
+
+ // Close connection to syslog
+ closelog();
+ }
+}
\ No newline at end of file
diff --git a/system/libraries/drivers/Session.php b/system/libraries/drivers/Session.php
index fb58c8d3..759ccd84 100644
--- a/system/libraries/drivers/Session.php
+++ b/system/libraries/drivers/Session.php
@@ -2,12 +2,12 @@
/**
* Session driver interface
*
- * $Id: Session.php 3769 2008-12-15 00:48:56Z zombor $
+ * $Id: Session.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
interface Session_Driver {
diff --git a/system/libraries/drivers/Session/Cache.php b/system/libraries/drivers/Session/Cache.php
index 45e49495..c0d4d0a4 100644
--- a/system/libraries/drivers/Session/Cache.php
+++ b/system/libraries/drivers/Session/Cache.php
@@ -10,12 +10,12 @@
* Lifetime does not need to be set as it is
* overridden by the session expiration setting.
*
- * $Id: Cache.php 4431 2009-07-01 03:41:41Z kiall $
+ * $Id: Cache.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class Session_Cache_Driver implements Session_Driver {
@@ -30,7 +30,7 @@ class Session_Cache_Driver implements Session_Driver {
$this->encrypt = new Encrypt;
}
- Kohana::log('debug', 'Session Cache Driver Initialized');
+ Kohana_Log::add('debug', 'Session Cache Driver Initialized');
}
public function open($path, $name)
@@ -48,7 +48,7 @@ class Session_Cache_Driver implements Session_Driver {
// Test the config group name
if (($config = Kohana::config('cache.'.$config)) === NULL)
- throw new Kohana_Exception('cache.undefined_group', $name);
+ throw new Kohana_Exception('The :group: group is not defined in your configuration.', array(':group:' => $name));
}
$config['lifetime'] = (Kohana::config('session.expiration') == 0) ? 86400 : Kohana::config('session.expiration');
diff --git a/system/libraries/drivers/Session/Cookie.php b/system/libraries/drivers/Session/Cookie.php
index 4cf18fc2..ef575cab 100644
--- a/system/libraries/drivers/Session/Cookie.php
+++ b/system/libraries/drivers/Session/Cookie.php
@@ -2,12 +2,12 @@
/**
* Session cookie driver.
*
- * $Id: Cookie.php 4431 2009-07-01 03:41:41Z kiall $
+ * $Id: Cookie.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class Session_Cookie_Driver implements Session_Driver {
@@ -23,7 +23,7 @@ class Session_Cookie_Driver implements Session_Driver {
$this->encrypt = Encrypt::instance();
}
- Kohana::log('debug', 'Session Cookie Driver Initialized');
+ Kohana_Log::add('debug', 'Session Cookie Driver Initialized');
}
public function open($path, $name)
@@ -55,7 +55,7 @@ class Session_Cookie_Driver implements Session_Driver {
if (strlen($data) > 4048)
{
- Kohana::log('error', 'Session ('.$id.') data exceeds the 4KB limit, ignoring write.');
+ Kohana_Log::add('error', 'Session ('.$id.') data exceeds the 4KB limit, ignoring write.');
return FALSE;
}
diff --git a/system/libraries/drivers/Session/Database.php b/system/libraries/drivers/Session/Database.php
index 490875a1..cbe76001 100644
--- a/system/libraries/drivers/Session/Database.php
+++ b/system/libraries/drivers/Session/Database.php
@@ -2,12 +2,12 @@
/**
* Session database driver.
*
- * $Id: Database.php 4431 2009-07-01 03:41:41Z kiall $
+ * $Id: Database.php 4679 2009-11-10 01:45:52Z isaiah $
*
* @package Core
* @author Kohana Team
- * @copyright (c) 2007-2008 Kohana Team
- * @license http://kohanaphp.com/license.html
+ * @copyright (c) 2007-2009 Kohana Team
+ * @license http://kohanaphp.com/license
*/
class Session_Database_Driver implements Session_Driver {
@@ -58,10 +58,7 @@ class Session_Database_Driver implements Session_Driver {
}
}
- // Load database
- $this->db = Database::instance($this->db);
-
- Kohana::log('debug', 'Session Database Driver Initialized');
+ Kohana_Log::add('debug', 'Session Database Driver Initialized');
}
public function open($path, $name)
@@ -77,7 +74,11 @@ class Session_Database_Driver implements Session_Driver {
public function read($id)
{
// Load the session
- $query = $this->db->from($this->table)->where('session_id', $id)->limit(1)->get()->result(TRUE);
+ $query = db::select('data')
+ ->from($this->table)
+ ->where('session_id', '=', $id)
+ ->limit(1)
+ ->execute($this->db);
if ($query->count() === 0)
{
@@ -111,7 +112,8 @@ class Session_Database_Driver implements Session_Driver {
if ($this->session_id === NULL)
{
// Insert a new session
- $query = $this->db->insert($this->table, $data);
+ $query = db::insert($this->table, $data)
+ ->execute($this->db);
}
elseif ($id === $this->session_id)
{
@@ -119,12 +121,18 @@ class Session_Database_Driver implements Session_Driver {
unset($data['session_id']);
// Update the existing session
- $query = $this->db->update($this->table, $data, array('session_id' => $id));
+ $query = db::update($this->table)
+ ->set($data)
+ ->where('session_id', '=', $id)
+ ->execute($this->db);
}
else
{
// Update the session and id
- $query = $this->db->update($this->table, $data, array('session_id' => $this->session_id));
+ $query = db::update($this->table)
+ ->set($data)
+ ->where('session_id', '=', $this->session_id)
+ ->execute($this->db);
// Set the new session id
$this->session_id = $id;
@@ -136,7 +144,9 @@ class Session_Database_Driver implements Session_Driver {
public function destroy($id)
{
// Delete the requested session
- $this->db->delete($this->table, array('session_id' => $id));
+ db::delete($this->table)
+ ->where('session_id', '=', $id)
+ ->execute($this->db);
// Session id is no longer valid
$this->session_id = NULL;
@@ -156,9 +166,11 @@ class Session_Database_Driver implements Session_Driver {
public function gc($maxlifetime)
{
// Delete all expired sessions
- $query = $this->db->delete($this->table, array('last_activity <' => time() - $maxlifetime));
+ $query = db::delete($this->table)
+ ->where('last_activity', '<', time() - $maxlifetime)
+ ->execute($this->db);
- Kohana::log('debug', 'Session garbage collected: '.$query->count().' row(s) deleted.');
+ Kohana_Log::add('debug', 'Session garbage collected: '.$query->count().' row(s) deleted.');
return TRUE;
}
diff --git a/system/messages/core.php b/system/messages/core.php
new file mode 100644
index 00000000..4bf6ee8c
--- /dev/null
+++ b/system/messages/core.php
@@ -0,0 +1,32 @@
+ array
+ (
+ E_KOHANA => __('Framework Error'), // __('Please check the Kohana documentation for information about the following error.'),
+ E_PAGE_NOT_FOUND => __('Page Not Found'), // __('The requested page was not found. It may have moved, been deleted, or archived.'),
+ E_DATABASE_ERROR => __('Database Error'), // __('A database error occurred while performing the requested procedure. Please review the database error below for more information.'),
+ E_RECOVERABLE_ERROR => __('Recoverable Error'), // __('An error was detected which prevented the loading of this page. If this problem persists, please contact the website administrator.'),
+
+ E_ERROR => __('Fatal Error'),
+ E_COMPILE_ERROR => __('Fatal Error'),
+ E_CORE_ERROR => __('Fatal Error'),
+ E_USER_ERROR => __('Fatal Error'),
+ E_PARSE => __('Syntax Error'),
+ E_WARNING => __('Warning Message'),
+ E_COMPILE_WARNING => __('Warning Message'),
+ E_CORE_WARNING => __('Warning Message'),
+ E_USER_WARNING => __('Warning Message'),
+ E_STRICT => __('Strict Mode Error'),
+ E_NOTICE => __('Runtime Message'),
+ E_USER_NOTICE => __('Runtime Message'),
+ ),
+ 'config' => 'config file',
+ 'controller' => 'controller',
+ 'helper' => 'helper',
+ 'library' => 'library',
+ 'driver' => 'driver',
+ 'model' => 'model',
+ 'view' => 'view',
+);
\ No newline at end of file
diff --git a/system/vendor/Markdown.php b/system/vendor/Markdown.php
new file mode 100644
index 00000000..b649f6c1
--- /dev/null
+++ b/system/vendor/Markdown.php
@@ -0,0 +1,2909 @@
+
+#
+# Original Markdown
+# Copyright (c) 2004-2006 John Gruber
+#
+#
+
+
+define( 'MARKDOWN_VERSION', "1.0.1m" ); # Sat 21 Jun 2008
+define( 'MARKDOWNEXTRA_VERSION', "1.2.3" ); # Wed 31 Dec 2008
+
+
+#
+# Global default settings:
+#
+
+# Change to ">" for HTML output
+@define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX', " />");
+
+# Define the width of a tab for code blocks.
+@define( 'MARKDOWN_TAB_WIDTH', 4 );
+
+# Optional title attribute for footnote links and backlinks.
+@define( 'MARKDOWN_FN_LINK_TITLE', "" );
+@define( 'MARKDOWN_FN_BACKLINK_TITLE', "" );
+
+# Optional class attribute for footnote links and backlinks.
+@define( 'MARKDOWN_FN_LINK_CLASS', "" );
+@define( 'MARKDOWN_FN_BACKLINK_CLASS', "" );
+
+
+#
+# WordPress settings:
+#
+
+# Change to false to remove Markdown from posts and/or comments.
+@define( 'MARKDOWN_WP_POSTS', true );
+@define( 'MARKDOWN_WP_COMMENTS', true );
+
+
+
+### Standard Function Interface ###
+
+@define( 'MARKDOWN_PARSER_CLASS', 'MarkdownExtra_Parser' );
+
+function Markdown($text) {
+#
+# Initialize the parser and return the result of its transform method.
+#
+ # Setup static parser variable.
+ static $parser;
+ if (!isset($parser)) {
+ $parser_class = MARKDOWN_PARSER_CLASS;
+ $parser = new $parser_class;
+ }
+
+ # Transform text using parser.
+ return $parser->transform($text);
+}
+
+
+### WordPress Plugin Interface ###
+
+/*
+Plugin Name: Markdown Extra
+Plugin URI: http://www.michelf.com/projects/php-markdown/
+Description: Markdown syntax allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by John Gruber . More...
+Version: 1.2.2
+Author: Michel Fortin
+Author URI: http://www.michelf.com/
+*/
+
+if (isset($wp_version)) {
+ # More details about how it works here:
+ #
+
+ # Post content and excerpts
+ # - Remove WordPress paragraph generator.
+ # - Run Markdown on excerpt, then remove all tags.
+ # - Add paragraph tag around the excerpt, but remove it for the excerpt rss.
+ if (MARKDOWN_WP_POSTS) {
+ remove_filter('the_content', 'wpautop');
+ remove_filter('the_content_rss', 'wpautop');
+ remove_filter('the_excerpt', 'wpautop');
+ add_filter('the_content', 'mdwp_MarkdownPost', 6);
+ add_filter('the_content_rss', 'mdwp_MarkdownPost', 6);
+ add_filter('get_the_excerpt', 'mdwp_MarkdownPost', 6);
+ add_filter('get_the_excerpt', 'trim', 7);
+ add_filter('the_excerpt', 'mdwp_add_p');
+ add_filter('the_excerpt_rss', 'mdwp_strip_p');
+
+ remove_filter('content_save_pre', 'balanceTags', 50);
+ remove_filter('excerpt_save_pre', 'balanceTags', 50);
+ add_filter('the_content', 'balanceTags', 50);
+ add_filter('get_the_excerpt', 'balanceTags', 9);
+ }
+
+ # Add a footnote id prefix to posts when inside a loop.
+ function mdwp_MarkdownPost($text) {
+ static $parser;
+ if (!$parser) {
+ $parser_class = MARKDOWN_PARSER_CLASS;
+ $parser = new $parser_class;
+ }
+ if (is_single() || is_page() || is_feed()) {
+ $parser->fn_id_prefix = "";
+ } else {
+ $parser->fn_id_prefix = get_the_ID() . ".";
+ }
+ return $parser->transform($text);
+ }
+
+ # Comments
+ # - Remove WordPress paragraph generator.
+ # - Remove WordPress auto-link generator.
+ # - Scramble important tags before passing them to the kses filter.
+ # - Run Markdown on excerpt then remove paragraph tags.
+ if (MARKDOWN_WP_COMMENTS) {
+ remove_filter('comment_text', 'wpautop', 30);
+ remove_filter('comment_text', 'make_clickable');
+ add_filter('pre_comment_content', 'Markdown', 6);
+ add_filter('pre_comment_content', 'mdwp_hide_tags', 8);
+ add_filter('pre_comment_content', 'mdwp_show_tags', 12);
+ add_filter('get_comment_text', 'Markdown', 6);
+ add_filter('get_comment_excerpt', 'Markdown', 6);
+ add_filter('get_comment_excerpt', 'mdwp_strip_p', 7);
+
+ global $mdwp_hidden_tags, $mdwp_placeholders;
+ $mdwp_hidden_tags = explode(' ',
+ '
');
+ $mdwp_placeholders = explode(' ', str_rot13(
+ 'pEj07ZbbBZ U1kqgh4w4p pre2zmeN6K QTi31t9pre ol0MP1jzJR '.
+ 'ML5IjmbRol ulANi1NsGY J7zRLJqPul liA8ctl16T K9nhooUHli'));
+ }
+
+ function mdwp_add_p($text) {
+ if (!preg_match('{^$|^<(p|ul|ol|dl|pre|blockquote)>}i', $text)) {
+ $text = ''.$text.'
';
+ $text = preg_replace('{\n{2,}}', "
\n\n", $text);
+ }
+ return $text;
+ }
+
+ function mdwp_strip_p($t) { return preg_replace('{?p>}i', '', $t); }
+
+ function mdwp_hide_tags($text) {
+ global $mdwp_hidden_tags, $mdwp_placeholders;
+ return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text);
+ }
+ function mdwp_show_tags($text) {
+ global $mdwp_hidden_tags, $mdwp_placeholders;
+ return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text);
+ }
+}
+
+
+### bBlog Plugin Info ###
+
+function identify_modifier_markdown() {
+ return array(
+ 'name' => 'markdown',
+ 'type' => 'modifier',
+ 'nicename' => 'PHP Markdown Extra',
+ 'description' => 'A text-to-HTML conversion tool for web writers',
+ 'authors' => 'Michel Fortin and John Gruber',
+ 'licence' => 'GPL',
+ 'version' => MARKDOWNEXTRA_VERSION,
+ 'help' => 'Markdown syntax allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by John Gruber . More... ',
+ );
+}
+
+
+### Smarty Modifier Interface ###
+
+function smarty_modifier_markdown($text) {
+ return Markdown($text);
+}
+
+
+### Textile Compatibility Mode ###
+
+# Rename this file to "classTextile.php" and it can replace Textile everywhere.
+
+if (strcasecmp(substr(__FILE__, -16), "classTextile.php") == 0) {
+ # Try to include PHP SmartyPants. Should be in the same directory.
+ @include_once 'smartypants.php';
+ # Fake Textile class. It calls Markdown instead.
+ class Textile {
+ function TextileThis($text, $lite='', $encode='') {
+ if ($lite == '' && $encode == '') $text = Markdown($text);
+ if (function_exists('SmartyPants')) $text = SmartyPants($text);
+ return $text;
+ }
+ # Fake restricted version: restrictions are not supported for now.
+ function TextileRestricted($text, $lite='', $noimage='') {
+ return $this->TextileThis($text, $lite);
+ }
+ # Workaround to ensure compatibility with TextPattern 4.0.3.
+ function blockLite($text) { return $text; }
+ }
+}
+
+
+
+#
+# Markdown Parser Class
+#
+
+class Markdown_Parser {
+
+ # Regex to match balanced [brackets].
+ # Needed to insert a maximum bracked depth while converting to PHP.
+ var $nested_brackets_depth = 6;
+ var $nested_brackets_re;
+
+ var $nested_url_parenthesis_depth = 4;
+ var $nested_url_parenthesis_re;
+
+ # Table of hash values for escaped characters:
+ var $escape_chars = '\`*_{}[]()>#+-.!';
+ var $escape_chars_re;
+
+ # Change to ">" for HTML output.
+ var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX;
+ var $tab_width = MARKDOWN_TAB_WIDTH;
+
+ # Change to `true` to disallow markup or entities.
+ var $no_markup = false;
+ var $no_entities = false;
+
+ # Predefined urls and titles for reference links and images.
+ var $predef_urls = array();
+ var $predef_titles = array();
+
+
+ function Markdown_Parser() {
+ #
+ # Constructor function. Initialize appropriate member variables.
+ #
+ $this->_initDetab();
+ $this->prepareItalicsAndBold();
+
+ $this->nested_brackets_re =
+ str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth).
+ str_repeat('\])*', $this->nested_brackets_depth);
+
+ $this->nested_url_parenthesis_re =
+ str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth).
+ str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth);
+
+ $this->escape_chars_re = '['.preg_quote($this->escape_chars).']';
+
+ # Sort document, block, and span gamut in ascendent priority order.
+ asort($this->document_gamut);
+ asort($this->block_gamut);
+ asort($this->span_gamut);
+ }
+
+
+ # Internal hashes used during transformation.
+ var $urls = array();
+ var $titles = array();
+ var $html_hashes = array();
+
+ # Status flag to avoid invalid nesting.
+ var $in_anchor = false;
+
+
+ function setup() {
+ #
+ # Called before the transformation process starts to setup parser
+ # states.
+ #
+ # Clear global hashes.
+ $this->urls = $this->predef_urls;
+ $this->titles = $this->predef_titles;
+ $this->html_hashes = array();
+
+ $in_anchor = false;
+ }
+
+ function teardown() {
+ #
+ # Called after the transformation process to clear any variable
+ # which may be taking up memory unnecessarly.
+ #
+ $this->urls = array();
+ $this->titles = array();
+ $this->html_hashes = array();
+ }
+
+
+ function transform($text) {
+ #
+ # Main function. Performs some preprocessing on the input text
+ # and pass it through the document gamut.
+ #
+ $this->setup();
+
+ # Remove UTF-8 BOM and marker character in input, if present.
+ $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text);
+
+ # Standardize line endings:
+ # DOS to Unix and Mac to Unix
+ $text = preg_replace('{\r\n?}', "\n", $text);
+
+ # Make sure $text ends with a couple of newlines:
+ $text .= "\n\n";
+
+ # Convert all tabs to spaces.
+ $text = $this->detab($text);
+
+ # Turn block-level HTML blocks into hash entries
+ $text = $this->hashHTMLBlocks($text);
+
+ # Strip any lines consisting only of spaces and tabs.
+ # This makes subsequent regexen easier to write, because we can
+ # match consecutive blank lines with /\n+/ instead of something
+ # contorted like /[ ]*\n+/ .
+ $text = preg_replace('/^[ ]+$/m', '', $text);
+
+ # Run document gamut methods.
+ foreach ($this->document_gamut as $method => $priority) {
+ $text = $this->$method($text);
+ }
+
+ $this->teardown();
+
+ return $text . "\n";
+ }
+
+ var $document_gamut = array(
+ # Strip link definitions, store in hashes.
+ "stripLinkDefinitions" => 20,
+
+ "runBasicBlockGamut" => 30,
+ );
+
+
+ function stripLinkDefinitions($text) {
+ #
+ # Strips link definitions from text, stores the URLs and titles in
+ # hash references.
+ #
+ $less_than_tab = $this->tab_width - 1;
+
+ # Link defs are in the form: ^[id]: url "optional title"
+ $text = preg_replace_callback('{
+ ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1
+ [ ]*
+ \n? # maybe *one* newline
+ [ ]*
+ (\S+?)>? # url = $2
+ [ ]*
+ \n? # maybe one newline
+ [ ]*
+ (?:
+ (?<=\s) # lookbehind for whitespace
+ ["(]
+ (.*?) # title = $3
+ [")]
+ [ ]*
+ )? # title is optional
+ (?:\n+|\Z)
+ }xm',
+ array(&$this, '_stripLinkDefinitions_callback'),
+ $text);
+ return $text;
+ }
+ function _stripLinkDefinitions_callback($matches) {
+ $link_id = strtolower($matches[1]);
+ $this->urls[$link_id] = $matches[2];
+ $this->titles[$link_id] =& $matches[3];
+ return ''; # String that will replace the block
+ }
+
+
+ function hashHTMLBlocks($text) {
+ if ($this->no_markup) return $text;
+
+ $less_than_tab = $this->tab_width - 1;
+
+ # Hashify HTML blocks:
+ # We only want to do this for block-level HTML tags, such as headers,
+ # lists, and tables. That's because we still want to wrap
s around
+ # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
+ # phrase emphasis, and spans. The list of tags we're looking for is
+ # hard-coded:
+ #
+ # * List "a" is made of tags which can be both inline or block-level.
+ # These will be treated block-level when the start tag is alone on
+ # its line, otherwise they're not matched here and will be taken as
+ # inline later.
+ # * List "b" is made of tags which are always block-level;
+ #
+ $block_tags_a_re = 'ins|del';
+ $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'.
+ 'script|noscript|form|fieldset|iframe|math';
+
+ # Regular expression for the content of a block tag.
+ $nested_tags_level = 4;
+ $attr = '
+ (?> # optional tag attributes
+ \s # starts with whitespace
+ (?>
+ [^>"/]+ # text outside quotes
+ |
+ /+(?!>) # slash not followed by ">"
+ |
+ "[^"]*" # text inside double quotes (tolerate ">")
+ |
+ \'[^\']*\' # text inside single quotes (tolerate ">")
+ )*
+ )?
+ ';
+ $content =
+ str_repeat('
+ (?>
+ [^<]+ # content without tag
+ |
+ <\2 # nested opening tag
+ '.$attr.' # attributes
+ (?>
+ />
+ |
+ >', $nested_tags_level). # end of opening tag
+ '.*?'. # last level nested tag content
+ str_repeat('
+ \2\s*> # closing nested tag
+ )
+ |
+ <(?!/\2\s*> # other tags with a different name
+ )
+ )*',
+ $nested_tags_level);
+ $content2 = str_replace('\2', '\3', $content);
+
+ # First, look for nested blocks, e.g.:
+ #
+ #
+ # tags for inner block must be indented.
+ #
+ #
+ #
+ # The outermost tags must start at the left margin for this to match, and
+ # the inner nested divs must be indented.
+ # We need to do this before the next, more liberal match, because the next
+ # match will start at the first `` and stop at the first `
`.
+ $text = preg_replace_callback('{(?>
+ (?>
+ (?<=\n\n) # Starting after a blank line
+ | # or
+ \A\n? # the beginning of the doc
+ )
+ ( # save in $1
+
+ # Match from `\n` to ` \n`, handling nested tags
+ # in between.
+
+ [ ]{0,'.$less_than_tab.'}
+ <('.$block_tags_b_re.')# start tag = $2
+ '.$attr.'> # attributes followed by > and \n
+ '.$content.' # content, support nesting
+ \2> # the matching end tag
+ [ ]* # trailing spaces/tabs
+ (?=\n+|\Z) # followed by a newline or end of document
+
+ | # Special version for tags of group a.
+
+ [ ]{0,'.$less_than_tab.'}
+ <('.$block_tags_a_re.')# start tag = $3
+ '.$attr.'>[ ]*\n # attributes followed by >
+ '.$content2.' # content, support nesting
+ \3> # the matching end tag
+ [ ]* # trailing spaces/tabs
+ (?=\n+|\Z) # followed by a newline or end of document
+
+ | # Special case just for . It was easier to make a special
+ # case than to make the other regex more complicated.
+
+ [ ]{0,'.$less_than_tab.'}
+ <(hr) # start tag = $2
+ '.$attr.' # attributes
+ /?> # the matching end tag
+ [ ]*
+ (?=\n{2,}|\Z) # followed by a blank line or end of document
+
+ | # Special case for standalone HTML comments:
+
+ [ ]{0,'.$less_than_tab.'}
+ (?s:
+
+ )
+ [ ]*
+ (?=\n{2,}|\Z) # followed by a blank line or end of document
+
+ | # PHP and ASP-style processor instructions ( and <%)
+
+ [ ]{0,'.$less_than_tab.'}
+ (?s:
+ <([?%]) # $2
+ .*?
+ \2>
+ )
+ [ ]*
+ (?=\n{2,}|\Z) # followed by a blank line or end of document
+
+ )
+ )}Sxmi',
+ array(&$this, '_hashHTMLBlocks_callback'),
+ $text);
+
+ return $text;
+ }
+ function _hashHTMLBlocks_callback($matches) {
+ $text = $matches[1];
+ $key = $this->hashBlock($text);
+ return "\n\n$key\n\n";
+ }
+
+
+ function hashPart($text, $boundary = 'X') {
+ #
+ # Called whenever a tag must be hashed when a function insert an atomic
+ # element in the text stream. Passing $text to through this function gives
+ # a unique text-token which will be reverted back when calling unhash.
+ #
+ # The $boundary argument specify what character should be used to surround
+ # the token. By convension, "B" is used for block elements that needs not
+ # to be wrapped into paragraph tags at the end, ":" is used for elements
+ # that are word separators and "X" is used in the general case.
+ #
+ # Swap back any tag hash found in $text so we do not have to `unhash`
+ # multiple times at the end.
+ $text = $this->unhash($text);
+
+ # Then hash the block.
+ static $i = 0;
+ $key = "$boundary\x1A" . ++$i . $boundary;
+ $this->html_hashes[$key] = $text;
+ return $key; # String that will replace the tag.
+ }
+
+
+ function hashBlock($text) {
+ #
+ # Shortcut function for hashPart with block-level boundaries.
+ #
+ return $this->hashPart($text, 'B');
+ }
+
+
+ var $block_gamut = array(
+ #
+ # These are all the transformations that form block-level
+ # tags like paragraphs, headers, and list items.
+ #
+ "doHeaders" => 10,
+ "doHorizontalRules" => 20,
+
+ "doLists" => 40,
+ "doCodeBlocks" => 50,
+ "doBlockQuotes" => 60,
+ );
+
+ function runBlockGamut($text) {
+ #
+ # Run block gamut tranformations.
+ #
+ # We need to escape raw HTML in Markdown source before doing anything
+ # else. This need to be done for each block, and not only at the
+ # begining in the Markdown function since hashed blocks can be part of
+ # list items and could have been indented. Indented blocks would have
+ # been seen as a code block in a previous pass of hashHTMLBlocks.
+ $text = $this->hashHTMLBlocks($text);
+
+ return $this->runBasicBlockGamut($text);
+ }
+
+ function runBasicBlockGamut($text) {
+ #
+ # Run block gamut tranformations, without hashing HTML blocks. This is
+ # useful when HTML blocks are known to be already hashed, like in the first
+ # whole-document pass.
+ #
+ foreach ($this->block_gamut as $method => $priority) {
+ $text = $this->$method($text);
+ }
+
+ # Finally form paragraph and restore hashed blocks.
+ $text = $this->formParagraphs($text);
+
+ return $text;
+ }
+
+
+ function doHorizontalRules($text) {
+ # Do Horizontal Rules:
+ return preg_replace(
+ '{
+ ^[ ]{0,3} # Leading space
+ ([-*_]) # $1: First marker
+ (?> # Repeated marker group
+ [ ]{0,2} # Zero, one, or two spaces.
+ \1 # Marker character
+ ){2,} # Group repeated at least twice
+ [ ]* # Tailing spaces
+ $ # End of line.
+ }mx',
+ "\n".$this->hashBlock(" empty_element_suffix")."\n",
+ $text);
+ }
+
+
+ var $span_gamut = array(
+ #
+ # These are all the transformations that occur *within* block-level
+ # tags like paragraphs, headers, and list items.
+ #
+ # Process character escapes, code spans, and inline HTML
+ # in one shot.
+ "parseSpan" => -30,
+
+ # Process anchor and image tags. Images must come first,
+ # because ![foo][f] looks like an anchor.
+ "doImages" => 10,
+ "doAnchors" => 20,
+
+ # Make links out of things like ` `
+ # Must come after doAnchors, because you can use < and >
+ # delimiters in inline links like [this]().
+ "doAutoLinks" => 30,
+ "encodeAmpsAndAngles" => 40,
+
+ "doItalicsAndBold" => 50,
+ "doHardBreaks" => 60,
+ );
+
+ function runSpanGamut($text) {
+ #
+ # Run span gamut tranformations.
+ #
+ foreach ($this->span_gamut as $method => $priority) {
+ $text = $this->$method($text);
+ }
+
+ return $text;
+ }
+
+
+ function doHardBreaks($text) {
+ # Do hard breaks:
+ return preg_replace_callback('/ {2,}\n/',
+ array(&$this, '_doHardBreaks_callback'), $text);
+ }
+ function _doHardBreaks_callback($matches) {
+ return $this->hashPart(" empty_element_suffix\n");
+ }
+
+
+ function doAnchors($text) {
+ #
+ # Turn Markdown link shortcuts into XHTML tags.
+ #
+ if ($this->in_anchor) return $text;
+ $this->in_anchor = true;
+
+ #
+ # First, handle reference-style links: [link text] [id]
+ #
+ $text = preg_replace_callback('{
+ ( # wrap whole match in $1
+ \[
+ ('.$this->nested_brackets_re.') # link text = $2
+ \]
+
+ [ ]? # one optional space
+ (?:\n[ ]*)? # one optional newline followed by spaces
+
+ \[
+ (.*?) # id = $3
+ \]
+ )
+ }xs',
+ array(&$this, '_doAnchors_reference_callback'), $text);
+
+ #
+ # Next, inline-style links: [link text](url "optional title")
+ #
+ $text = preg_replace_callback('{
+ ( # wrap whole match in $1
+ \[
+ ('.$this->nested_brackets_re.') # link text = $2
+ \]
+ \( # literal paren
+ [ ]*
+ (?:
+ <(\S*)> # href = $3
+ |
+ ('.$this->nested_url_parenthesis_re.') # href = $4
+ )
+ [ ]*
+ ( # $5
+ ([\'"]) # quote char = $6
+ (.*?) # Title = $7
+ \6 # matching quote
+ [ ]* # ignore any spaces/tabs between closing quote and )
+ )? # title is optional
+ \)
+ )
+ }xs',
+ array(&$this, '_DoAnchors_inline_callback'), $text);
+
+ #
+ # Last, handle reference-style shortcuts: [link text]
+ # These must come last in case you've also got [link test][1]
+ # or [link test](/foo)
+ #
+// $text = preg_replace_callback('{
+// ( # wrap whole match in $1
+// \[
+// ([^\[\]]+) # link text = $2; can\'t contain [ or ]
+// \]
+// )
+// }xs',
+// array(&$this, '_doAnchors_reference_callback'), $text);
+
+ $this->in_anchor = false;
+ return $text;
+ }
+ function _doAnchors_reference_callback($matches) {
+ $whole_match = $matches[1];
+ $link_text = $matches[2];
+ $link_id =& $matches[3];
+
+ if ($link_id == "") {
+ # for shortcut links like [this][] or [this].
+ $link_id = $link_text;
+ }
+
+ # lower-case and turn embedded newlines into spaces
+ $link_id = strtolower($link_id);
+ $link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
+
+ if (isset($this->urls[$link_id])) {
+ $url = $this->urls[$link_id];
+ $url = $this->encodeAttribute($url);
+
+ $result = " titles[$link_id] ) ) {
+ $title = $this->titles[$link_id];
+ $title = $this->encodeAttribute($title);
+ $result .= " title=\"$title\"";
+ }
+
+ $link_text = $this->runSpanGamut($link_text);
+ $result .= ">$link_text ";
+ $result = $this->hashPart($result);
+ }
+ else {
+ $result = $whole_match;
+ }
+ return $result;
+ }
+ function _doAnchors_inline_callback($matches) {
+ $whole_match = $matches[1];
+ $link_text = $this->runSpanGamut($matches[2]);
+ $url = $matches[3] == '' ? $matches[4] : $matches[3];
+ $title =& $matches[7];
+
+ $url = $this->encodeAttribute($url);
+
+ $result = "encodeAttribute($title);
+ $result .= " title=\"$title\"";
+ }
+
+ $link_text = $this->runSpanGamut($link_text);
+ $result .= ">$link_text ";
+
+ return $this->hashPart($result);
+ }
+
+
+ function doImages($text) {
+ #
+ # Turn Markdown image shortcuts into tags.
+ #
+ #
+ # First, handle reference-style labeled images: ![alt text][id]
+ #
+ $text = preg_replace_callback('{
+ ( # wrap whole match in $1
+ !\[
+ ('.$this->nested_brackets_re.') # alt text = $2
+ \]
+
+ [ ]? # one optional space
+ (?:\n[ ]*)? # one optional newline followed by spaces
+
+ \[
+ (.*?) # id = $3
+ \]
+
+ )
+ }xs',
+ array(&$this, '_doImages_reference_callback'), $text);
+
+ #
+ # Next, handle inline images: 
+ # Don't forget: encode * and _
+ #
+ $text = preg_replace_callback('{
+ ( # wrap whole match in $1
+ !\[
+ ('.$this->nested_brackets_re.') # alt text = $2
+ \]
+ \s? # One optional whitespace character
+ \( # literal paren
+ [ ]*
+ (?:
+ <(\S*)> # src url = $3
+ |
+ ('.$this->nested_url_parenthesis_re.') # src url = $4
+ )
+ [ ]*
+ ( # $5
+ ([\'"]) # quote char = $6
+ (.*?) # title = $7
+ \6 # matching quote
+ [ ]*
+ )? # title is optional
+ \)
+ )
+ }xs',
+ array(&$this, '_doImages_inline_callback'), $text);
+
+ return $text;
+ }
+ function _doImages_reference_callback($matches) {
+ $whole_match = $matches[1];
+ $alt_text = $matches[2];
+ $link_id = strtolower($matches[3]);
+
+ if ($link_id == "") {
+ $link_id = strtolower($alt_text); # for shortcut links like ![this][].
+ }
+
+ $alt_text = $this->encodeAttribute($alt_text);
+ if (isset($this->urls[$link_id])) {
+ $url = $this->encodeAttribute($this->urls[$link_id]);
+ $result = " titles[$link_id])) {
+ $title = $this->titles[$link_id];
+ $title = $this->encodeAttribute($title);
+ $result .= " title=\"$title\"";
+ }
+ $result .= $this->empty_element_suffix;
+ $result = $this->hashPart($result);
+ }
+ else {
+ # If there's no such link ID, leave intact:
+ $result = $whole_match;
+ }
+
+ return $result;
+ }
+ function _doImages_inline_callback($matches) {
+ $whole_match = $matches[1];
+ $alt_text = $matches[2];
+ $url = $matches[3] == '' ? $matches[4] : $matches[3];
+ $title =& $matches[7];
+
+ $alt_text = $this->encodeAttribute($alt_text);
+ $url = $this->encodeAttribute($url);
+ $result = " encodeAttribute($title);
+ $result .= " title=\"$title\""; # $title already quoted
+ }
+ $result .= $this->empty_element_suffix;
+
+ return $this->hashPart($result);
+ }
+
+
+ function doHeaders($text) {
+ # Setext-style headers:
+ # Header 1
+ # ========
+ #
+ # Header 2
+ # --------
+ #
+ $text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',
+ array(&$this, '_doHeaders_callback_setext'), $text);
+
+ # atx-style headers:
+ # # Header 1
+ # ## Header 2
+ # ## Header 2 with closing hashes ##
+ # ...
+ # ###### Header 6
+ #
+ $text = preg_replace_callback('{
+ ^(\#{1,6}) # $1 = string of #\'s
+ [ ]*
+ (.+?) # $2 = Header text
+ [ ]*
+ \#* # optional closing #\'s (not counted)
+ \n+
+ }xm',
+ array(&$this, '_doHeaders_callback_atx'), $text);
+
+ return $text;
+ }
+ function _doHeaders_callback_setext($matches) {
+ # Terrible hack to check we haven't found an empty list item.
+ if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1]))
+ return $matches[0];
+
+ $level = $matches[2]{0} == '=' ? 1 : 2;
+ $block = "".$this->runSpanGamut($matches[1])." ";
+ return "\n" . $this->hashBlock($block) . "\n\n";
+ }
+ function _doHeaders_callback_atx($matches) {
+ $level = strlen($matches[1]);
+ $block = "".$this->runSpanGamut($matches[2])." ";
+ return "\n" . $this->hashBlock($block) . "\n\n";
+ }
+
+
+ function doLists($text) {
+ #
+ # Form HTML ordered (numbered) and unordered (bulleted) lists.
+ #
+ $less_than_tab = $this->tab_width - 1;
+
+ # Re-usable patterns to match list item bullets and number markers:
+ $marker_ul_re = '[*+-]';
+ $marker_ol_re = '\d+[.]';
+ $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
+
+ $markers_relist = array($marker_ul_re, $marker_ol_re);
+
+ foreach ($markers_relist as $marker_re) {
+ # Re-usable pattern to match any entirel ul or ol list:
+ $whole_list_re = '
+ ( # $1 = whole list
+ ( # $2
+ [ ]{0,'.$less_than_tab.'}
+ ('.$marker_re.') # $3 = first list item marker
+ [ ]+
+ )
+ (?s:.+?)
+ ( # $4
+ \z
+ |
+ \n{2,}
+ (?=\S)
+ (?! # Negative lookahead for another list item marker
+ [ ]*
+ '.$marker_re.'[ ]+
+ )
+ )
+ )
+ '; // mx
+
+ # We use a different prefix before nested lists than top-level lists.
+ # See extended comment in _ProcessListItems().
+
+ if ($this->list_level) {
+ $text = preg_replace_callback('{
+ ^
+ '.$whole_list_re.'
+ }mx',
+ array(&$this, '_doLists_callback'), $text);
+ }
+ else {
+ $text = preg_replace_callback('{
+ (?:(?<=\n)\n|\A\n?) # Must eat the newline
+ '.$whole_list_re.'
+ }mx',
+ array(&$this, '_doLists_callback'), $text);
+ }
+ }
+
+ return $text;
+ }
+ function _doLists_callback($matches) {
+ # Re-usable patterns to match list item bullets and number markers:
+ $marker_ul_re = '[*+-]';
+ $marker_ol_re = '\d+[.]';
+ $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
+
+ $list = $matches[1];
+ $list_type = preg_match("/$marker_ul_re/", $matches[3]) ? "ul" : "ol";
+
+ $marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re );
+
+ $list .= "\n";
+ $result = $this->processListItems($list, $marker_any_re);
+
+ $result = $this->hashBlock("<$list_type>\n" . $result . "$list_type>");
+ return "\n". $result ."\n\n";
+ }
+
+ var $list_level = 0;
+
+ function processListItems($list_str, $marker_any_re) {
+ #
+ # Process the contents of a single ordered or unordered list, splitting it
+ # into individual list items.
+ #
+ # The $this->list_level global keeps track of when we're inside a list.
+ # Each time we enter a list, we increment it; when we leave a list,
+ # we decrement. If it's zero, we're not in a list anymore.
+ #
+ # We do this because when we're not inside a list, we want to treat
+ # something like this:
+ #
+ # I recommend upgrading to version
+ # 8. Oops, now this line is treated
+ # as a sub-list.
+ #
+ # As a single paragraph, despite the fact that the second line starts
+ # with a digit-period-space sequence.
+ #
+ # Whereas when we're inside a list (or sub-list), that line will be
+ # treated as the start of a sub-list. What a kludge, huh? This is
+ # an aspect of Markdown's syntax that's hard to parse perfectly
+ # without resorting to mind-reading. Perhaps the solution is to
+ # change the syntax rules such that sub-lists must start with a
+ # starting cardinal number; e.g. "1." or "a.".
+
+ $this->list_level++;
+
+ # trim trailing blank lines:
+ $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
+
+ $list_str = preg_replace_callback('{
+ (\n)? # leading line = $1
+ (^[ ]*) # leading whitespace = $2
+ ('.$marker_any_re.' # list marker and space = $3
+ (?:[ ]+|(?=\n)) # space only required if item is not empty
+ )
+ ((?s:.*?)) # list item text = $4
+ (?:(\n+(?=\n))|\n) # tailing blank line = $5
+ (?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n))))
+ }xm',
+ array(&$this, '_processListItems_callback'), $list_str);
+
+ $this->list_level--;
+ return $list_str;
+ }
+ function _processListItems_callback($matches) {
+ $item = $matches[4];
+ $leading_line =& $matches[1];
+ $leading_space =& $matches[2];
+ $marker_space = $matches[3];
+ $tailing_blank_line =& $matches[5];
+
+ if ($leading_line || $tailing_blank_line ||
+ preg_match('/\n{2,}/', $item))
+ {
+ # Replace marker with the appropriate whitespace indentation
+ $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item;
+ $item = $this->runBlockGamut($this->outdent($item)."\n");
+ }
+ else {
+ # Recursion for sub-lists:
+ $item = $this->doLists($this->outdent($item));
+ $item = preg_replace('/\n+$/', '', $item);
+ $item = $this->runSpanGamut($item);
+ }
+
+ return "" . $item . " \n";
+ }
+
+
+ function doCodeBlocks($text) {
+ #
+ # Process Markdown `` blocks.
+ #
+ $text = preg_replace_callback('{
+ (?:\n\n|\A\n?)
+ ( # $1 = the code block -- one or more lines, starting with a space/tab
+ (?>
+ [ ]{'.$this->tab_width.'} # Lines must start with a tab or a tab-width of spaces
+ .*\n+
+ )+
+ )
+ ((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
+ }xm',
+ array(&$this, '_doCodeBlocks_callback'), $text);
+
+ return $text;
+ }
+ function _doCodeBlocks_callback($matches) {
+ $codeblock = $matches[1];
+
+ $codeblock = $this->outdent($codeblock);
+ $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
+
+ # trim leading newlines and trailing newlines
+ $codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
+
+ $codeblock = "$codeblock\n ";
+ return "\n\n".$this->hashBlock($codeblock)."\n\n";
+ }
+
+
+ function makeCodeSpan($code) {
+ #
+ # Create a code span markup for $code. Called from handleSpanToken.
+ #
+ $code = htmlspecialchars(trim($code), ENT_NOQUOTES);
+ return $this->hashPart("$code");
+ }
+
+
+ var $em_relist = array(
+ '' => '(?:(? '(?<=\S)(? '(?<=\S)(? '(?:(? '(?<=\S)(? '(?<=\S)(? '(?:(? '(?<=\S)(? '(?<=\S)(?em_relist as $em => $em_re) {
+ foreach ($this->strong_relist as $strong => $strong_re) {
+ # Construct list of allowed token expressions.
+ $token_relist = array();
+ if (isset($this->em_strong_relist["$em$strong"])) {
+ $token_relist[] = $this->em_strong_relist["$em$strong"];
+ }
+ $token_relist[] = $em_re;
+ $token_relist[] = $strong_re;
+
+ # Construct master expression from list.
+ $token_re = '{('. implode('|', $token_relist) .')}';
+ $this->em_strong_prepared_relist["$em$strong"] = $token_re;
+ }
+ }
+ }
+
+ function doItalicsAndBold($text) {
+ $token_stack = array('');
+ $text_stack = array('');
+ $em = '';
+ $strong = '';
+ $tree_char_em = false;
+
+ while (1) {
+ #
+ # Get prepared regular expression for seraching emphasis tokens
+ # in current context.
+ #
+ $token_re = $this->em_strong_prepared_relist["$em$strong"];
+
+ #
+ # Each loop iteration seach for the next emphasis token.
+ # Each token is then passed to handleSpanToken.
+ #
+ $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
+ $text_stack[0] .= $parts[0];
+ $token =& $parts[1];
+ $text =& $parts[2];
+
+ if (empty($token)) {
+ # Reached end of text span: empty stack without emitting.
+ # any more emphasis.
+ while ($token_stack[0]) {
+ $text_stack[1] .= array_shift($token_stack);
+ $text_stack[0] .= array_shift($text_stack);
+ }
+ break;
+ }
+
+ $token_len = strlen($token);
+ if ($tree_char_em) {
+ # Reached closing marker while inside a three-char emphasis.
+ if ($token_len == 3) {
+ # Three-char closing marker, close em and strong.
+ array_shift($token_stack);
+ $span = array_shift($text_stack);
+ $span = $this->runSpanGamut($span);
+ $span = "$span ";
+ $text_stack[0] .= $this->hashPart($span);
+ $em = '';
+ $strong = '';
+ } else {
+ # Other closing marker: close one em or strong and
+ # change current token state to match the other
+ $token_stack[0] = str_repeat($token{0}, 3-$token_len);
+ $tag = $token_len == 2 ? "strong" : "em";
+ $span = $text_stack[0];
+ $span = $this->runSpanGamut($span);
+ $span = "<$tag>$span$tag>";
+ $text_stack[0] = $this->hashPart($span);
+ $$tag = ''; # $$tag stands for $em or $strong
+ }
+ $tree_char_em = false;
+ } else if ($token_len == 3) {
+ if ($em) {
+ # Reached closing marker for both em and strong.
+ # Closing strong marker:
+ for ($i = 0; $i < 2; ++$i) {
+ $shifted_token = array_shift($token_stack);
+ $tag = strlen($shifted_token) == 2 ? "strong" : "em";
+ $span = array_shift($text_stack);
+ $span = $this->runSpanGamut($span);
+ $span = "<$tag>$span$tag>";
+ $text_stack[0] .= $this->hashPart($span);
+ $$tag = ''; # $$tag stands for $em or $strong
+ }
+ } else {
+ # Reached opening three-char emphasis marker. Push on token
+ # stack; will be handled by the special condition above.
+ $em = $token{0};
+ $strong = "$em$em";
+ array_unshift($token_stack, $token);
+ array_unshift($text_stack, '');
+ $tree_char_em = true;
+ }
+ } else if ($token_len == 2) {
+ if ($strong) {
+ # Unwind any dangling emphasis marker:
+ if (strlen($token_stack[0]) == 1) {
+ $text_stack[1] .= array_shift($token_stack);
+ $text_stack[0] .= array_shift($text_stack);
+ }
+ # Closing strong marker:
+ array_shift($token_stack);
+ $span = array_shift($text_stack);
+ $span = $this->runSpanGamut($span);
+ $span = "$span ";
+ $text_stack[0] .= $this->hashPart($span);
+ $strong = '';
+ } else {
+ array_unshift($token_stack, $token);
+ array_unshift($text_stack, '');
+ $strong = $token;
+ }
+ } else {
+ # Here $token_len == 1
+ if ($em) {
+ if (strlen($token_stack[0]) == 1) {
+ # Closing emphasis marker:
+ array_shift($token_stack);
+ $span = array_shift($text_stack);
+ $span = $this->runSpanGamut($span);
+ $span = "$span ";
+ $text_stack[0] .= $this->hashPart($span);
+ $em = '';
+ } else {
+ $text_stack[0] .= $token;
+ }
+ } else {
+ array_unshift($token_stack, $token);
+ array_unshift($text_stack, '');
+ $em = $token;
+ }
+ }
+ }
+ return $text_stack[0];
+ }
+
+
+ function doBlockQuotes($text) {
+ $text = preg_replace_callback('/
+ ( # Wrap whole match in $1
+ (?>
+ ^[ ]*>[ ]? # ">" at the start of a line
+ .+\n # rest of the first line
+ (.+\n)* # subsequent consecutive lines
+ \n* # blanks
+ )+
+ )
+ /xm',
+ array(&$this, '_doBlockQuotes_callback'), $text);
+
+ return $text;
+ }
+ function _doBlockQuotes_callback($matches) {
+ $bq = $matches[1];
+ # trim one level of quoting - trim whitespace-only lines
+ $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
+ $bq = $this->runBlockGamut($bq); # recurse
+
+ $bq = preg_replace('/^/m', " ", $bq);
+ # These leading spaces cause problem with content,
+ # so we need to fix that:
+ $bq = preg_replace_callback('{(\s*.+? )}sx',
+ array(&$this, '_DoBlockQuotes_callback2'), $bq);
+
+ return "\n". $this->hashBlock("\n$bq\n ")."\n\n";
+ }
+ function _doBlockQuotes_callback2($matches) {
+ $pre = $matches[1];
+ $pre = preg_replace('/^ /m', '', $pre);
+ return $pre;
+ }
+
+
+ function formParagraphs($text) {
+ #
+ # Params:
+ # $text - string to process with html tags
+ #
+ # Strip leading and trailing lines:
+ $text = preg_replace('/\A\n+|\n+\z/', '', $text);
+
+ $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
+
+ #
+ # Wrap
tags and unhashify HTML blocks
+ #
+ foreach ($grafs as $key => $value) {
+ if (!preg_match('/^B\x1A[0-9]+B$/', $value)) {
+ # Is a paragraph.
+ $value = $this->runSpanGamut($value);
+ $value = preg_replace('/^([ ]*)/', "
", $value);
+ $value .= "
";
+ $grafs[$key] = $this->unhash($value);
+ }
+ else {
+ # Is a block.
+ # Modify elements of @grafs in-place...
+ $graf = $value;
+ $block = $this->html_hashes[$graf];
+ $graf = $block;
+// if (preg_match('{
+// \A
+// ( # $1 = tag
+//
]*
+// \b
+// markdown\s*=\s* ([\'"]) # $2 = attr quote char
+// 1
+// \2
+// [^>]*
+// >
+// )
+// ( # $3 = contents
+// .*
+// )
+// (
) # $4 = closing tag
+// \z
+// }xs', $block, $matches))
+// {
+// list(, $div_open, , $div_content, $div_close) = $matches;
+//
+// # We can't call Markdown(), because that resets the hash;
+// # that initialization code should be pulled into its own sub, though.
+// $div_content = $this->hashHTMLBlocks($div_content);
+//
+// # Run document gamut methods on the content.
+// foreach ($this->document_gamut as $method => $priority) {
+// $div_content = $this->$method($div_content);
+// }
+//
+// $div_open = preg_replace(
+// '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
+//
+// $graf = $div_open . "\n" . $div_content . "\n" . $div_close;
+// }
+ $grafs[$key] = $graf;
+ }
+ }
+
+ return implode("\n\n", $grafs);
+ }
+
+
+ function encodeAttribute($text) {
+ #
+ # Encode text for a double-quoted HTML attribute. This function
+ # is *not* suitable for attributes enclosed in single quotes.
+ #
+ $text = $this->encodeAmpsAndAngles($text);
+ $text = str_replace('"', '"', $text);
+ return $text;
+ }
+
+
+ function encodeAmpsAndAngles($text) {
+ #
+ # Smart processing for ampersands and angle brackets that need to
+ # be encoded. Valid character entities are left alone unless the
+ # no-entities mode is set.
+ #
+ if ($this->no_entities) {
+ $text = str_replace('&', '&', $text);
+ } else {
+ # Ampersand-encoding based entirely on Nat Irons's Amputator
+ # MT plugin:
+ $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/',
+ '&', $text);;
+ }
+ # Encode remaining <'s
+ $text = str_replace('<', '<', $text);
+
+ return $text;
+ }
+
+
+ function doAutoLinks($text) {
+ $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i',
+ array(&$this, '_doAutoLinks_url_callback'), $text);
+
+ # Email addresses:
+ $text = preg_replace_callback('{
+ <
+ (?:mailto:)?
+ (
+ [-.\w\x80-\xFF]+
+ \@
+ [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
+ )
+ >
+ }xi',
+ array(&$this, '_doAutoLinks_email_callback'), $text);
+
+ return $text;
+ }
+ function _doAutoLinks_url_callback($matches) {
+ $url = $this->encodeAttribute($matches[1]);
+ $link = "$url ";
+ return $this->hashPart($link);
+ }
+ function _doAutoLinks_email_callback($matches) {
+ $address = $matches[1];
+ $link = $this->encodeEmailAddress($address);
+ return $this->hashPart($link);
+ }
+
+
+ function encodeEmailAddress($addr) {
+ #
+ # Input: an email address, e.g. "foo@example.com"
+ #
+ # Output: the email address as a mailto link, with each character
+ # of the address encoded as either a decimal or hex entity, in
+ # the hopes of foiling most address harvesting spam bots. E.g.:
+ #
+ # foo@exampl
+ # e.com
+ #
+ # Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
+ # With some optimizations by Milian Wolff.
+ #
+ $addr = "mailto:" . $addr;
+ $chars = preg_split('/(? $char) {
+ $ord = ord($char);
+ # Ignore non-ascii chars.
+ if ($ord < 128) {
+ $r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
+ # roughly 10% raw, 45% hex, 45% dec
+ # '@' *must* be encoded. I insist.
+ if ($r > 90 && $char != '@') /* do nothing */;
+ else if ($r < 45) $chars[$key] = ''.dechex($ord).';';
+ else $chars[$key] = ''.$ord.';';
+ }
+ }
+
+ $addr = implode('', $chars);
+ $text = implode('', array_slice($chars, 7)); # text without `mailto:`
+ $addr = "$text ";
+
+ return $addr;
+ }
+
+
+ function parseSpan($str) {
+ #
+ # Take the string $str and parse it into tokens, hashing embeded HTML,
+ # escaped characters and handling code spans.
+ #
+ $output = '';
+
+ $span_re = '{
+ (
+ \\\\'.$this->escape_chars_re.'
+ |
+ (?no_markup ? '' : '
+ |
+ # comment
+ |
+ <\?.*?\?> | <%.*?%> # processing instruction
+ |
+ <[/!$]?[-a-zA-Z0-9:]+ # regular tags
+ (?>
+ \s
+ (?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
+ )?
+ >
+ ').'
+ )
+ }xs';
+
+ while (1) {
+ #
+ # Each loop iteration seach for either the next tag, the next
+ # openning code span marker, or the next escaped character.
+ # Each token is then passed to handleSpanToken.
+ #
+ $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);
+
+ # Create token from text preceding tag.
+ if ($parts[0] != "") {
+ $output .= $parts[0];
+ }
+
+ # Check if we reach the end.
+ if (isset($parts[1])) {
+ $output .= $this->handleSpanToken($parts[1], $parts[2]);
+ $str = $parts[2];
+ }
+ else {
+ break;
+ }
+ }
+
+ return $output;
+ }
+
+
+ function handleSpanToken($token, &$str) {
+ #
+ # Handle $token provided by parseSpan by determining its nature and
+ # returning the corresponding value that should replace it.
+ #
+ switch ($token{0}) {
+ case "\\":
+ return $this->hashPart("". ord($token{1}). ";");
+ case "`":
+ # Search for end marker in remaining text.
+ if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm',
+ $str, $matches))
+ {
+ $str = $matches[2];
+ $codespan = $this->makeCodeSpan($matches[1]);
+ return $this->hashPart($codespan);
+ }
+ return $token; // return as text since no ending marker found.
+ default:
+ return $this->hashPart($token);
+ }
+ }
+
+
+ function outdent($text) {
+ #
+ # Remove one level of line-leading tabs or spaces
+ #
+ return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text);
+ }
+
+
+ # String length function for detab. `_initDetab` will create a function to
+ # hanlde UTF-8 if the default function does not exist.
+ var $utf8_strlen = 'mb_strlen';
+
+ function detab($text) {
+ #
+ # Replace tabs with the appropriate amount of space.
+ #
+ # For each line we separate the line in blocks delemited by
+ # tab characters. Then we reconstruct every line by adding the
+ # appropriate number of space between each blocks.
+
+ $text = preg_replace_callback('/^.*\t.*$/m',
+ array(&$this, '_detab_callback'), $text);
+
+ return $text;
+ }
+ function _detab_callback($matches) {
+ $line = $matches[0];
+ $strlen = $this->utf8_strlen; # strlen function for UTF-8.
+
+ # Split in blocks.
+ $blocks = explode("\t", $line);
+ # Add each blocks to the line.
+ $line = $blocks[0];
+ unset($blocks[0]); # Do not add first block twice.
+ foreach ($blocks as $block) {
+ # Calculate amount of space, insert spaces, insert block.
+ $amount = $this->tab_width -
+ $strlen($line, 'UTF-8') % $this->tab_width;
+ $line .= str_repeat(" ", $amount) . $block;
+ }
+ return $line;
+ }
+ function _initDetab() {
+ #
+ # Check for the availability of the function in the `utf8_strlen` property
+ # (initially `mb_strlen`). If the function is not available, create a
+ # function that will loosely count the number of UTF-8 characters with a
+ # regular expression.
+ #
+ if (function_exists($this->utf8_strlen)) return;
+ $this->utf8_strlen = create_function('$text', 'return preg_match_all(
+ "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/",
+ $text, $m);');
+ }
+
+
+ function unhash($text) {
+ #
+ # Swap back in all the tags hashed by _HashHTMLBlocks.
+ #
+ return preg_replace_callback('/(.)\x1A[0-9]+\1/',
+ array(&$this, '_unhash_callback'), $text);
+ }
+ function _unhash_callback($matches) {
+ return $this->html_hashes[$matches[0]];
+ }
+
+}
+
+
+#
+# Markdown Extra Parser Class
+#
+
+class MarkdownExtra_Parser extends Markdown_Parser {
+
+ # Prefix for footnote ids.
+ var $fn_id_prefix = "";
+
+ # Optional title attribute for footnote links and backlinks.
+ var $fn_link_title = MARKDOWN_FN_LINK_TITLE;
+ var $fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE;
+
+ # Optional class attribute for footnote links and backlinks.
+ var $fn_link_class = MARKDOWN_FN_LINK_CLASS;
+ var $fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS;
+
+ # Predefined abbreviations.
+ var $predef_abbr = array();
+
+
+ function MarkdownExtra_Parser() {
+ #
+ # Constructor function. Initialize the parser object.
+ #
+ # Add extra escapable characters before parent constructor
+ # initialize the table.
+ $this->escape_chars .= ':|';
+
+ # Insert extra document, block, and span transformations.
+ # Parent constructor will do the sorting.
+ $this->document_gamut += array(
+ "doFencedCodeBlocks" => 5,
+ "stripFootnotes" => 15,
+ "stripAbbreviations" => 25,
+ "appendFootnotes" => 50,
+ );
+ $this->block_gamut += array(
+ "doFencedCodeBlocks" => 5,
+ "doTables" => 15,
+ "doDefLists" => 45,
+ );
+ $this->span_gamut += array(
+ "doFootnotes" => 5,
+ "doAbbreviations" => 70,
+ );
+
+ parent::Markdown_Parser();
+ }
+
+
+ # Extra variables used during extra transformations.
+ var $footnotes = array();
+ var $footnotes_ordered = array();
+ var $abbr_desciptions = array();
+ var $abbr_word_re = '';
+
+ # Give the current footnote number.
+ var $footnote_counter = 1;
+
+
+ function setup() {
+ #
+ # Setting up Extra-specific variables.
+ #
+ parent::setup();
+
+ $this->footnotes = array();
+ $this->footnotes_ordered = array();
+ $this->abbr_desciptions = array();
+ $this->abbr_word_re = '';
+ $this->footnote_counter = 1;
+
+ foreach ($this->predef_abbr as $abbr_word => $abbr_desc) {
+ if ($this->abbr_word_re)
+ $this->abbr_word_re .= '|';
+ $this->abbr_word_re .= preg_quote($abbr_word);
+ $this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
+ }
+ }
+
+ function teardown() {
+ #
+ # Clearing Extra-specific variables.
+ #
+ $this->footnotes = array();
+ $this->footnotes_ordered = array();
+ $this->abbr_desciptions = array();
+ $this->abbr_word_re = '';
+
+ parent::teardown();
+ }
+
+
+ ### HTML Block Parser ###
+
+ # Tags that are always treated as block tags:
+ var $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend';
+
+ # Tags treated as block tags only if the opening tag is alone on it's line:
+ var $context_block_tags_re = 'script|noscript|math|ins|del';
+
+ # Tags where markdown="1" default to span mode:
+ var $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address';
+
+ # Tags which must not have their contents modified, no matter where
+ # they appear:
+ var $clean_tags_re = 'script|math';
+
+ # Tags that do not need to be closed.
+ var $auto_close_tags_re = 'hr|img';
+
+
+ function hashHTMLBlocks($text) {
+ #
+ # Hashify HTML Blocks and "clean tags".
+ #
+ # We only want to do this for block-level HTML tags, such as headers,
+ # lists, and tables. That's because we still want to wrap s around
+ # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
+ # phrase emphasis, and spans. The list of tags we're looking for is
+ # hard-coded.
+ #
+ # This works by calling _HashHTMLBlocks_InMarkdown, which then calls
+ # _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1"
+ # attribute is found whitin a tag, _HashHTMLBlocks_InHTML calls back
+ # _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag.
+ # These two functions are calling each other. It's recursive!
+ #
+ #
+ # Call the HTML-in-Markdown hasher.
+ #
+ list($text, ) = $this->_hashHTMLBlocks_inMarkdown($text);
+
+ return $text;
+ }
+ function _hashHTMLBlocks_inMarkdown($text, $indent = 0,
+ $enclosing_tag_re = '', $span = false)
+ {
+ #
+ # Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags.
+ #
+ # * $indent is the number of space to be ignored when checking for code
+ # blocks. This is important because if we don't take the indent into
+ # account, something like this (which looks right) won't work as expected:
+ #
+ #
+ #
+ # Hello World. <-- Is this a Markdown code block or text?
+ #
<-- Is this a Markdown code block or a real tag?
+ #
+ #
+ # If you don't like this, just don't indent the tag on which
+ # you apply the markdown="1" attribute.
+ #
+ # * If $enclosing_tag_re is not empty, stops at the first unmatched closing
+ # tag with that name. Nested tags supported.
+ #
+ # * If $span is true, text inside must treated as span. So any double
+ # newline will be replaced by a single newline so that it does not create
+ # paragraphs.
+ #
+ # Returns an array of that form: ( processed text , remaining text )
+ #
+ if ($text === '') return array('', '');
+
+ # Regex to check for the presense of newlines around a block tag.
+ $newline_before_re = '/(?:^\n?|\n\n)*$/';
+ $newline_after_re =
+ '{
+ ^ # Start of text following the tag.
+ (?>[ ]*)? # Optional comment.
+ [ ]*\n # Must be followed by newline.
+ }xs';
+
+ # Regex to match any tag.
+ $block_tag_re =
+ '{
+ ( # $2: Capture hole tag.
+ ? # Any opening or closing tag.
+ (?> # Tag name.
+ '.$this->block_tags_re.' |
+ '.$this->context_block_tags_re.' |
+ '.$this->clean_tags_re.' |
+ (?!\s)'.$enclosing_tag_re.'
+ )
+ (?:
+ (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name.
+ (?>
+ ".*?" | # Double quotes (can contain `>`)
+ \'.*?\' | # Single quotes (can contain `>`)
+ .+? # Anything but quotes and `>`.
+ )*?
+ )?
+ > # End of tag.
+ |
+ # HTML Comment
+ |
+ <\?.*?\?> | <%.*?%> # Processing instruction
+ |
+ # CData Block
+ |
+ # Code span marker
+ `+
+ '. ( !$span ? ' # If not in span.
+ |
+ # Indented code block
+ (?> ^[ ]*\n? | \n[ ]*\n )
+ [ ]{'.($indent+4).'}[^\n]* \n
+ (?>
+ (?: [ ]{'.($indent+4).'}[^\n]* | [ ]* ) \n
+ )*
+ |
+ # Fenced code block marker
+ (?> ^ | \n )
+ [ ]{'.($indent).'}~~~+[ ]*\n
+ ' : '' ). ' # End (if not is span).
+ )
+ }xs';
+
+
+ $depth = 0; # Current depth inside the tag tree.
+ $parsed = ""; # Parsed text that will be returned.
+
+ #
+ # Loop through every tag until we find the closing tag of the parent
+ # or loop until reaching the end of text if no parent tag specified.
+ #
+ do {
+ #
+ # Split the text using the first $tag_match pattern found.
+ # Text before pattern will be first in the array, text after
+ # pattern will be at the end, and between will be any catches made
+ # by the pattern.
+ #
+ $parts = preg_split($block_tag_re, $text, 2,
+ PREG_SPLIT_DELIM_CAPTURE);
+
+ # If in Markdown span mode, add a empty-string span-level hash
+ # after each newline to prevent triggering any block element.
+ if ($span) {
+ $void = $this->hashPart("", ':');
+ $newline = "$void\n";
+ $parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void;
+ }
+
+ $parsed .= $parts[0]; # Text before current tag.
+
+ # If end of $text has been reached. Stop loop.
+ if (count($parts) < 3) {
+ $text = "";
+ break;
+ }
+
+ $tag = $parts[1]; # Tag to handle.
+ $text = $parts[2]; # Remaining text after current tag.
+ $tag_re = preg_quote($tag); # For use in a regular expression.
+
+ #
+ # Check for: Code span marker
+ #
+ if ($tag{0} == "`") {
+ # Find corresponding end marker.
+ $tag_re = preg_quote($tag);
+ if (preg_match('{^(?>.+?|\n(?!\n))*?(?.*\n)+?'.$tag_re.' *\n}', $text,
+ $matches))
+ {
+ # End marker found: pass text unchanged until marker.
+ $parsed .= $tag . $matches[0];
+ $text = substr($text, strlen($matches[0]));
+ }
+ else {
+ # No end marker: just skip it.
+ $parsed .= $tag;
+ }
+ }
+ }
+ #
+ # Check for: Opening Block level tag or
+ # Opening Context Block tag (like ins and del)
+ # used as a block tag (tag is alone on it's line).
+ #
+ else if (preg_match('{^<(?:'.$this->block_tags_re.')\b}', $tag) ||
+ ( preg_match('{^<(?:'.$this->context_block_tags_re.')\b}', $tag) &&
+ preg_match($newline_before_re, $parsed) &&
+ preg_match($newline_after_re, $text) )
+ )
+ {
+ # Need to parse tag and following text using the HTML parser.
+ list($block_text, $text) =
+ $this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true);
+
+ # Make sure it stays outside of any paragraph by adding newlines.
+ $parsed .= "\n\n$block_text\n\n";
+ }
+ #
+ # Check for: Clean tag (like script, math)
+ # HTML Comments, processing instructions.
+ #
+ else if (preg_match('{^<(?:'.$this->clean_tags_re.')\b}', $tag) ||
+ $tag{1} == '!' || $tag{1} == '?')
+ {
+ # Need to parse tag and following text using the HTML parser.
+ # (don't check for markdown attribute)
+ list($block_text, $text) =
+ $this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false);
+
+ $parsed .= $block_text;
+ }
+ #
+ # Check for: Tag with same name as enclosing tag.
+ #
+ else if ($enclosing_tag_re !== '' &&
+ # Same name as enclosing tag.
+ preg_match('{^?(?:'.$enclosing_tag_re.')\b}', $tag))
+ {
+ #
+ # Increase/decrease nested tag count.
+ #
+ if ($tag{1} == '/') $depth--;
+ else if ($tag{strlen($tag)-2} != '/') $depth++;
+
+ if ($depth < 0) {
+ #
+ # Going out of parent element. Clean up and break so we
+ # return to the calling function.
+ #
+ $text = $tag . $text;
+ break;
+ }
+
+ $parsed .= $tag;
+ }
+ else {
+ $parsed .= $tag;
+ }
+ } while ($depth >= 0);
+
+ return array($parsed, $text);
+ }
+ function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) {
+ #
+ # Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags.
+ #
+ # * Calls $hash_method to convert any blocks.
+ # * Stops when the first opening tag closes.
+ # * $md_attr indicate if the use of the `markdown="1"` attribute is allowed.
+ # (it is not inside clean tags)
+ #
+ # Returns an array of that form: ( processed text , remaining text )
+ #
+ if ($text === '') return array('', '');
+
+ # Regex to match `markdown` attribute inside of a tag.
+ $markdown_attr_re = '
+ {
+ \s* # Eat whitespace before the `markdown` attribute
+ markdown
+ \s*=\s*
+ (?>
+ (["\']) # $1: quote delimiter
+ (.*?) # $2: attribute value
+ \1 # matching delimiter
+ |
+ ([^\s>]*) # $3: unquoted attribute value
+ )
+ () # $4: make $3 always defined (avoid warnings)
+ }xs';
+
+ # Regex to match any tag.
+ $tag_re = '{
+ ( # $2: Capture hole tag.
+ ? # Any opening or closing tag.
+ [\w:$]+ # Tag name.
+ (?:
+ (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name.
+ (?>
+ ".*?" | # Double quotes (can contain `>`)
+ \'.*?\' | # Single quotes (can contain `>`)
+ .+? # Anything but quotes and `>`.
+ )*?
+ )?
+ > # End of tag.
+ |
+ # HTML Comment
+ |
+ <\?.*?\?> | <%.*?%> # Processing instruction
+ |
+ # CData Block
+ )
+ }xs';
+
+ $original_text = $text; # Save original text in case of faliure.
+
+ $depth = 0; # Current depth inside the tag tree.
+ $block_text = ""; # Temporary text holder for current text.
+ $parsed = ""; # Parsed text that will be returned.
+
+ #
+ # Get the name of the starting tag.
+ # (This pattern makes $base_tag_name_re safe without quoting.)
+ #
+ if (preg_match('/^<([\w:$]*)\b/', $text, $matches))
+ $base_tag_name_re = $matches[1];
+
+ #
+ # Loop through every tag until we find the corresponding closing tag.
+ #
+ do {
+ #
+ # Split the text using the first $tag_match pattern found.
+ # Text before pattern will be first in the array, text after
+ # pattern will be at the end, and between will be any catches made
+ # by the pattern.
+ #
+ $parts = preg_split($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
+
+ if (count($parts) < 3) {
+ #
+ # End of $text reached with unbalenced tag(s).
+ # In that case, we return original text unchanged and pass the
+ # first character as filtered to prevent an infinite loop in the
+ # parent function.
+ #
+ return array($original_text{0}, substr($original_text, 1));
+ }
+
+ $block_text .= $parts[0]; # Text before current tag.
+ $tag = $parts[1]; # Tag to handle.
+ $text = $parts[2]; # Remaining text after current tag.
+
+ #
+ # Check for: Auto-close tag (like
)
+ # Comments and Processing Instructions.
+ #
+ if (preg_match('{^?(?:'.$this->auto_close_tags_re.')\b}', $tag) ||
+ $tag{1} == '!' || $tag{1} == '?')
+ {
+ # Just add the tag to the block as if it was text.
+ $block_text .= $tag;
+ }
+ else {
+ #
+ # Increase/decrease nested tag count. Only do so if
+ # the tag's name match base tag's.
+ #
+ if (preg_match('{^?'.$base_tag_name_re.'\b}', $tag)) {
+ if ($tag{1} == '/') $depth--;
+ else if ($tag{strlen($tag)-2} != '/') $depth++;
+ }
+
+ #
+ # Check for `markdown="1"` attribute and handle it.
+ #
+ if ($md_attr &&
+ preg_match($markdown_attr_re, $tag, $attr_m) &&
+ preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3]))
+ {
+ # Remove `markdown` attribute from opening tag.
+ $tag = preg_replace($markdown_attr_re, '', $tag);
+
+ # Check if text inside this tag must be parsed in span mode.
+ $this->mode = $attr_m[2] . $attr_m[3];
+ $span_mode = $this->mode == 'span' || $this->mode != 'block' &&
+ preg_match('{^<(?:'.$this->contain_span_tags_re.')\b}', $tag);
+
+ # Calculate indent before tag.
+ if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) {
+ $strlen = $this->utf8_strlen;
+ $indent = $strlen($matches[1], 'UTF-8');
+ } else {
+ $indent = 0;
+ }
+
+ # End preceding block with this tag.
+ $block_text .= $tag;
+ $parsed .= $this->$hash_method($block_text);
+
+ # Get enclosing tag name for the ParseMarkdown function.
+ # (This pattern makes $tag_name_re safe without quoting.)
+ preg_match('/^<([\w:$]*)\b/', $tag, $matches);
+ $tag_name_re = $matches[1];
+
+ # Parse the content using the HTML-in-Markdown parser.
+ list ($block_text, $text)
+ = $this->_hashHTMLBlocks_inMarkdown($text, $indent,
+ $tag_name_re, $span_mode);
+
+ # Outdent markdown text.
+ if ($indent > 0) {
+ $block_text = preg_replace("/^[ ]{1,$indent}/m", "",
+ $block_text);
+ }
+
+ # Append tag content to parsed text.
+ if (!$span_mode) $parsed .= "\n\n$block_text\n\n";
+ else $parsed .= "$block_text";
+
+ # Start over a new block.
+ $block_text = "";
+ }
+ else $block_text .= $tag;
+ }
+
+ } while ($depth > 0);
+
+ #
+ # Hash last block text that wasn't processed inside the loop.
+ #
+ $parsed .= $this->$hash_method($block_text);
+
+ return array($parsed, $text);
+ }
+
+
+ function hashClean($text) {
+ #
+ # Called whenever a tag must be hashed when a function insert a "clean" tag
+ # in $text, it pass through this function and is automaticaly escaped,
+ # blocking invalid nested overlap.
+ #
+ return $this->hashPart($text, 'C');
+ }
+
+
+ function doHeaders($text) {
+ #
+ # Redefined to add id attribute support.
+ #
+ # Setext-style headers:
+ # Header 1 {#header1}
+ # ========
+ #
+ # Header 2 {#header2}
+ # --------
+ #
+ $text = preg_replace_callback(
+ '{
+ (^.+?) # $1: Header text
+ (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # $2: Id attribute
+ [ ]*\n(=+|-+)[ ]*\n+ # $3: Header footer
+ }mx',
+ array(&$this, '_doHeaders_callback_setext'), $text);
+
+ # atx-style headers:
+ # # Header 1 {#header1}
+ # ## Header 2 {#header2}
+ # ## Header 2 with closing hashes ## {#header3}
+ # ...
+ # ###### Header 6 {#header2}
+ #
+ $text = preg_replace_callback('{
+ ^(\#{1,6}) # $1 = string of #\'s
+ [ ]*
+ (.+?) # $2 = Header text
+ [ ]*
+ \#* # optional closing #\'s (not counted)
+ (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # id attribute
+ [ ]*
+ \n+
+ }xm',
+ array(&$this, '_doHeaders_callback_atx'), $text);
+
+ return $text;
+ }
+ function _doHeaders_attr($attr) {
+ if (empty($attr)) return "";
+ return " id=\"$attr\"";
+ }
+ function _doHeaders_callback_setext($matches) {
+ if ($matches[3] == '-' && preg_match('{^- }', $matches[1]))
+ return $matches[0];
+ $level = $matches[3]{0} == '=' ? 1 : 2;
+ $attr = $this->_doHeaders_attr($id =& $matches[2]);
+ $block = "
".$this->runSpanGamut($matches[1])." ";
+ return "\n" . $this->hashBlock($block) . "\n\n";
+ }
+ function _doHeaders_callback_atx($matches) {
+ $level = strlen($matches[1]);
+ $attr = $this->_doHeaders_attr($id =& $matches[3]);
+ $block = "
".$this->runSpanGamut($matches[2])." ";
+ return "\n" . $this->hashBlock($block) . "\n\n";
+ }
+
+
+ function doTables($text) {
+ #
+ # Form HTML tables.
+ #
+ $less_than_tab = $this->tab_width - 1;
+ #
+ # Find tables with leading pipe.
+ #
+ # | Header 1 | Header 2
+ # | -------- | --------
+ # | Cell 1 | Cell 2
+ # | Cell 3 | Cell 4
+ #
+ $text = preg_replace_callback('
+ {
+ ^ # Start of a line
+ [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
+ [|] # Optional leading pipe (present)
+ (.+) \n # $1: Header row (at least one pipe)
+
+ [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
+ [|] ([ ]*[-:]+[-| :]*) \n # $2: Header underline
+
+ ( # $3: Cells
+ (?>
+ [ ]* # Allowed whitespace.
+ [|] .* \n # Row content.
+ )*
+ )
+ (?=\n|\Z) # Stop at final double newline.
+ }xm',
+ array(&$this, '_doTable_leadingPipe_callback'), $text);
+
+ #
+ # Find tables without leading pipe.
+ #
+ # Header 1 | Header 2
+ # -------- | --------
+ # Cell 1 | Cell 2
+ # Cell 3 | Cell 4
+ #
+ $text = preg_replace_callback('
+ {
+ ^ # Start of a line
+ [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
+ (\S.*[|].*) \n # $1: Header row (at least one pipe)
+
+ [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
+ ([-:]+[ ]*[|][-| :]*) \n # $2: Header underline
+
+ ( # $3: Cells
+ (?>
+ .* [|] .* \n # Row content
+ )*
+ )
+ (?=\n|\Z) # Stop at final double newline.
+ }xm',
+ array(&$this, '_DoTable_callback'), $text);
+
+ return $text;
+ }
+ function _doTable_leadingPipe_callback($matches) {
+ $head = $matches[1];
+ $underline = $matches[2];
+ $content = $matches[3];
+
+ # Remove leading pipe for each row.
+ $content = preg_replace('/^ *[|]/m', '', $content);
+
+ return $this->_doTable_callback(array($matches[0], $head, $underline, $content));
+ }
+ function _doTable_callback($matches) {
+ $head = $matches[1];
+ $underline = $matches[2];
+ $content = $matches[3];
+
+ # Remove any tailing pipes for each line.
+ $head = preg_replace('/[|] *$/m', '', $head);
+ $underline = preg_replace('/[|] *$/m', '', $underline);
+ $content = preg_replace('/[|] *$/m', '', $content);
+
+ # Reading alignement from header underline.
+ $separators = preg_split('/ *[|] */', $underline);
+ foreach ($separators as $n => $s) {
+ if (preg_match('/^ *-+: *$/', $s)) $attr[$n] = ' align="right"';
+ else if (preg_match('/^ *:-+: *$/', $s))$attr[$n] = ' align="center"';
+ else if (preg_match('/^ *:-+ *$/', $s)) $attr[$n] = ' align="left"';
+ else $attr[$n] = '';
+ }
+
+ # Parsing span elements, including code spans, character escapes,
+ # and inline HTML tags, so that pipes inside those gets ignored.
+ $head = $this->parseSpan($head);
+ $headers = preg_split('/ *[|] */', $head);
+ $col_count = count($headers);
+
+ # Write column headers.
+ $text = "
\n";
+ $text .= "\n";
+ $text .= "\n";
+ foreach ($headers as $n => $header)
+ $text .= " ".$this->runSpanGamut(trim($header))." \n";
+ $text .= " \n";
+ $text .= " \n";
+
+ # Split content by row.
+ $rows = explode("\n", trim($content, "\n"));
+
+ $text .= "\n";
+ foreach ($rows as $row) {
+ # Parsing span elements, including code spans, character escapes,
+ # and inline HTML tags, so that pipes inside those gets ignored.
+ $row = $this->parseSpan($row);
+
+ # Split row by cell.
+ $row_cells = preg_split('/ *[|] */', $row, $col_count);
+ $row_cells = array_pad($row_cells, $col_count, '');
+
+ $text .= "\n";
+ foreach ($row_cells as $n => $cell)
+ $text .= " ".$this->runSpanGamut(trim($cell))." \n";
+ $text .= " \n";
+ }
+ $text .= " \n";
+ $text .= "
";
+
+ return $this->hashBlock($text) . "\n";
+ }
+
+
+ function doDefLists($text) {
+ #
+ # Form HTML definition lists.
+ #
+ $less_than_tab = $this->tab_width - 1;
+
+ # Re-usable pattern to match any entire dl list:
+ $whole_list_re = '(?>
+ ( # $1 = whole list
+ ( # $2
+ [ ]{0,'.$less_than_tab.'}
+ ((?>.*\S.*\n)+) # $3 = defined term
+ \n?
+ [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
+ )
+ (?s:.+?)
+ ( # $4
+ \z
+ |
+ \n{2,}
+ (?=\S)
+ (?! # Negative lookahead for another term
+ [ ]{0,'.$less_than_tab.'}
+ (?: \S.*\n )+? # defined term
+ \n?
+ [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
+ )
+ (?! # Negative lookahead for another definition
+ [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
+ )
+ )
+ )
+ )'; // mx
+
+ $text = preg_replace_callback('{
+ (?>\A\n?|(?<=\n\n))
+ '.$whole_list_re.'
+ }mx',
+ array(&$this, '_doDefLists_callback'), $text);
+
+ return $text;
+ }
+ function _doDefLists_callback($matches) {
+ # Re-usable patterns to match list item bullets and number markers:
+ $list = $matches[1];
+
+ # Turn double returns into triple returns, so that we can make a
+ # paragraph for the last item in a list, if necessary:
+ $result = trim($this->processDefListItems($list));
+ $result = "
\n" . $result . "\n ";
+ return $this->hashBlock($result) . "\n\n";
+ }
+
+
+ function processDefListItems($list_str) {
+ #
+ # Process the contents of a single definition list, splitting it
+ # into individual term and definition list items.
+ #
+ $less_than_tab = $this->tab_width - 1;
+
+ # trim trailing blank lines:
+ $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
+
+ # Process definition terms.
+ $list_str = preg_replace_callback('{
+ (?>\A\n?|\n\n+) # leading line
+ ( # definition terms = $1
+ [ ]{0,'.$less_than_tab.'} # leading whitespace
+ (?![:][ ]|[ ]) # negative lookahead for a definition
+ # mark (colon) or more whitespace.
+ (?> \S.* \n)+? # actual term (not whitespace).
+ )
+ (?=\n?[ ]{0,3}:[ ]) # lookahead for following line feed
+ # with a definition mark.
+ }xm',
+ array(&$this, '_processDefListItems_callback_dt'), $list_str);
+
+ # Process actual definitions.
+ $list_str = preg_replace_callback('{
+ \n(\n+)? # leading line = $1
+ ( # marker space = $2
+ [ ]{0,'.$less_than_tab.'} # whitespace before colon
+ [:][ ]+ # definition mark (colon)
+ )
+ ((?s:.+?)) # definition text = $3
+ (?= \n+ # stop at next definition mark,
+ (?: # next term or end of text
+ [ ]{0,'.$less_than_tab.'} [:][ ] |
+
| \z
+ )
+ )
+ }xm',
+ array(&$this, '_processDefListItems_callback_dd'), $list_str);
+
+ return $list_str;
+ }
+ function _processDefListItems_callback_dt($matches) {
+ $terms = explode("\n", trim($matches[1]));
+ $text = '';
+ foreach ($terms as $term) {
+ $term = $this->runSpanGamut(trim($term));
+ $text .= "\n" . $term . " ";
+ }
+ return $text . "\n";
+ }
+ function _processDefListItems_callback_dd($matches) {
+ $leading_line = $matches[1];
+ $marker_space = $matches[2];
+ $def = $matches[3];
+
+ if ($leading_line || preg_match('/\n{2,}/', $def)) {
+ # Replace marker with the appropriate whitespace indentation
+ $def = str_repeat(' ', strlen($marker_space)) . $def;
+ $def = $this->runBlockGamut($this->outdent($def . "\n\n"));
+ $def = "\n". $def ."\n";
+ }
+ else {
+ $def = rtrim($def);
+ $def = $this->runSpanGamut($this->outdent($def));
+ }
+
+ return "\n " . $def . " \n";
+ }
+
+
+ function doFencedCodeBlocks($text) {
+ #
+ # Adding the fenced code block syntax to regular Markdown:
+ #
+ # ~~~
+ # Code block
+ # ~~~
+ #
+ $less_than_tab = $this->tab_width;
+
+ $text = preg_replace_callback('{
+ (?:\n|\A)
+ # 1: Opening marker
+ (
+ ~{3,} # Marker: three tilde or more.
+ )
+ [ ]* \n # Whitespace and newline following marker.
+
+ # 2: Content
+ (
+ (?>
+ (?!\1 [ ]* \n) # Not a closing marker.
+ .*\n+
+ )+
+ )
+
+ # Closing marker.
+ \1 [ ]* \n
+ }xm',
+ array(&$this, '_doFencedCodeBlocks_callback'), $text);
+
+ return $text;
+ }
+ function _doFencedCodeBlocks_callback($matches) {
+ $codeblock = $matches[2];
+ $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
+ $codeblock = preg_replace_callback('/^\n+/',
+ array(&$this, '_doFencedCodeBlocks_newlines'), $codeblock);
+ $codeblock = "
$codeblock";
+ return "\n\n".$this->hashBlock($codeblock)."\n\n";
+ }
+ function _doFencedCodeBlocks_newlines($matches) {
+ return str_repeat("
empty_element_suffix",
+ strlen($matches[0]));
+ }
+
+
+ #
+ # Redefining emphasis markers so that emphasis by underscore does not
+ # work in the middle of a word.
+ #
+ var $em_relist = array(
+ '' => '(?:(? '(?<=\S)(? '(?<=\S)(? '(?:(? '(?<=\S)(? '(?<=\S)(? '(?:(? '(?<=\S)(? '(?<=\S)(? tags
+ #
+ # Strip leading and trailing lines:
+ $text = preg_replace('/\A\n+|\n+\z/', '', $text);
+
+ $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
+
+ #
+ # Wrap
tags and unhashify HTML blocks
+ #
+ foreach ($grafs as $key => $value) {
+ $value = trim($this->runSpanGamut($value));
+
+ # Check if this should be enclosed in a paragraph.
+ # Clean tag hashes & block tag hashes are left alone.
+ $is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value);
+
+ if ($is_p) {
+ $value = "
$value
";
+ }
+ $grafs[$key] = $value;
+ }
+
+ # Join grafs in one text, then unhash HTML tags.
+ $text = implode("\n\n", $grafs);
+
+ # Finish by removing any tag hashes still present in $text.
+ $text = $this->unhash($text);
+
+ return $text;
+ }
+
+
+ ### Footnotes
+
+ function stripFootnotes($text) {
+ #
+ # Strips link definitions from text, stores the URLs and titles in
+ # hash references.
+ #
+ $less_than_tab = $this->tab_width - 1;
+
+ # Link defs are in the form: [^id]: url "optional title"
+ $text = preg_replace_callback('{
+ ^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?: # note_id = $1
+ [ ]*
+ \n? # maybe *one* newline
+ ( # text = $2 (no blank lines allowed)
+ (?:
+ .+ # actual text
+ |
+ \n # newlines but
+ (?!\[\^.+?\]:\s)# negative lookahead for footnote marker.
+ (?!\n+[ ]{0,3}\S)# ensure line is not blank and followed
+ # by non-indented content
+ )*
+ )
+ }xm',
+ array(&$this, '_stripFootnotes_callback'),
+ $text);
+ return $text;
+ }
+ function _stripFootnotes_callback($matches) {
+ $note_id = $this->fn_id_prefix . $matches[1];
+ $this->footnotes[$note_id] = $this->outdent($matches[2]);
+ return ''; # String that will replace the block
+ }
+
+
+ function doFootnotes($text) {
+ #
+ # Replace footnote references in $text [^id] with a special text-token
+ # which will be replaced by the actual footnote marker in appendFootnotes.
+ #
+ if (!$this->in_anchor) {
+ $text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text);
+ }
+ return $text;
+ }
+
+
+ function appendFootnotes($text) {
+ #
+ # Append footnote list to text.
+ #
+ $text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}',
+ array(&$this, '_appendFootnotes_callback'), $text);
+
+ if (!empty($this->footnotes_ordered)) {
+ $text .= "\n\n";
+ $text .= "";
+ }
+ return $text;
+ }
+ function _appendFootnotes_callback($matches) {
+ $node_id = $this->fn_id_prefix . $matches[1];
+
+ # Create footnote marker only if it has a corresponding footnote *and*
+ # the footnote hasn't been used by another marker.
+ if (isset($this->footnotes[$node_id])) {
+ # Transfert footnote content to the ordered list.
+ $this->footnotes_ordered[$node_id] = $this->footnotes[$node_id];
+ unset($this->footnotes[$node_id]);
+
+ $num = $this->footnote_counter++;
+ $attr = " rel=\"footnote\"";
+ if ($this->fn_link_class != "") {
+ $class = $this->fn_link_class;
+ $class = $this->encodeAttribute($class);
+ $attr .= " class=\"$class\"";
+ }
+ if ($this->fn_link_title != "") {
+ $title = $this->fn_link_title;
+ $title = $this->encodeAttribute($title);
+ $attr .= " title=\"$title\"";
+ }
+
+ $attr = str_replace("%%", $num, $attr);
+ $node_id = $this->encodeAttribute($node_id);
+
+ return
+ "
".
+ "$num ".
+ " ";
+ }
+
+ return "[^".$matches[1]."]";
+ }
+
+
+ ### Abbreviations ###
+
+ function stripAbbreviations($text) {
+ #
+ # Strips abbreviations from text, stores titles in hash references.
+ #
+ $less_than_tab = $this->tab_width - 1;
+
+ # Link defs are in the form: [id]*: url "optional title"
+ $text = preg_replace_callback('{
+ ^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?: # abbr_id = $1
+ (.*) # text = $2 (no blank lines allowed)
+ }xm',
+ array(&$this, '_stripAbbreviations_callback'),
+ $text);
+ return $text;
+ }
+ function _stripAbbreviations_callback($matches) {
+ $abbr_word = $matches[1];
+ $abbr_desc = $matches[2];
+ if ($this->abbr_word_re)
+ $this->abbr_word_re .= '|';
+ $this->abbr_word_re .= preg_quote($abbr_word);
+ $this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
+ return ''; # String that will replace the block
+ }
+
+
+ function doAbbreviations($text) {
+ #
+ # Find defined abbreviations in text and wrap them in
elements.
+ #
+ if ($this->abbr_word_re) {
+ // cannot use the /x modifier because abbr_word_re may
+ // contain significant spaces:
+ $text = preg_replace_callback('{'.
+ '(?abbr_word_re.')'.
+ '(?![\w\x1A])'.
+ '}',
+ array(&$this, '_doAbbreviations_callback'), $text);
+ }
+ return $text;
+ }
+ function _doAbbreviations_callback($matches) {
+ $abbr = $matches[0];
+ if (isset($this->abbr_desciptions[$abbr])) {
+ $desc = $this->abbr_desciptions[$abbr];
+ if (empty($desc)) {
+ return $this->hashPart("$abbr ");
+ } else {
+ $desc = $this->encodeAttribute($desc);
+ return $this->hashPart("$abbr ");
+ }
+ } else {
+ return $matches[0];
+ }
+ }
+
+}
+
+
+/*
+
+PHP Markdown Extra
+==================
+
+Description
+-----------
+
+This is a PHP port of the original Markdown formatter written in Perl
+by John Gruber. This special "Extra" version of PHP Markdown features
+further enhancements to the syntax for making additional constructs
+such as tables and definition list.
+
+Markdown is a text-to-HTML filter; it translates an easy-to-read /
+easy-to-write structured text format into HTML. Markdown's text format
+is most similar to that of plain text email, and supports features such
+as headers, *emphasis*, code blocks, blockquotes, and links.
+
+Markdown's syntax is designed not as a generic markup language, but
+specifically to serve as a front-end to (X)HTML. You can use span-level
+HTML tags anywhere in a Markdown document, and you can use block level
+HTML tags (like and
as well).
+
+For more information about Markdown's syntax, see:
+
+
+
+
+Bugs
+----
+
+To file bug reports please send email to:
+
+
+
+Please include with your report: (1) the example input; (2) the output you
+expected; (3) the output Markdown actually produced.
+
+
+Version History
+---------------
+
+See the readme file for detailed release notes for this version.
+
+
+Copyright and License
+---------------------
+
+PHP Markdown & Extra
+Copyright (c) 2004-2008 Michel Fortin
+
+All rights reserved.
+
+Based on Markdown
+Copyright (c) 2003-2006 John Gruber
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+* Neither the name "Markdown" nor the names of its contributors may
+ be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+This software is provided by the copyright holders and contributors "as
+is" and any express or implied warranties, including, but not limited
+to, the implied warranties of merchantability and fitness for a
+particular purpose are disclaimed. In no event shall the copyright owner
+or contributors be liable for any direct, indirect, incidental, special,
+exemplary, or consequential damages (including, but not limited to,
+procurement of substitute goods or services; loss of use, data, or
+profits; or business interruption) however caused and on any theory of
+liability, whether in contract, strict liability, or tort (including
+negligence or otherwise) arising in any way out of the use of this
+software, even if advised of the possibility of such damage.
+
+*/
+?>
\ No newline at end of file
diff --git a/system/views/kohana/error.php b/system/views/kohana/error.php
new file mode 100644
index 00000000..b40c0f8a
--- /dev/null
+++ b/system/views/kohana/error.php
@@ -0,0 +1,252 @@
+
+
+
+
+
+
+ [ ]:
+
+
+
+
+
+
+
+
+[ ]
+
+
+
+
+
$row) : ?>
+
+
+
+
+ $step): ?>
+
+
+
+
+
+ [ ]
+
+ [ ]
+
+
+ {}
+
+
+ »
+ (
+)
+
+
+
+
+ $arg): ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $row) : ?>
+
+
+
+
+
+
+
+
+
+
+
+
()
+
+
+
()
+
+
+
+
+
+
+ $value): ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/system/views/kohana/error_disabled.php b/system/views/kohana/error_disabled.php
new file mode 100644
index 00000000..1024eb1e
--- /dev/null
+++ b/system/views/kohana/error_disabled.php
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+home page or try again .',
+ array('%site%' => htmlspecialchars(url::site(), ENT_QUOTES, Kohana::CHARSET), '%uri%' => htmlspecialchars(url::site(Router::$current_uri), ENT_QUOTES, Kohana::CHARSET)));
+?>
+
+
+
+
diff --git a/system/views/kohana/template.php b/system/views/kohana/template.php
index b090fd88..84ddbff5 100644
--- a/system/views/kohana/template.php
+++ b/system/views/kohana/template.php
@@ -5,7 +5,7 @@
-
+
-
-
-
-
-
-
-