diff options
| author | 2014-10-09 15:53:10 +0200 | |
|---|---|---|
| committer | 2014-10-09 15:53:10 +0200 | |
| commit | f97d4b3b6cca4a55636bbd50158f3c57666b0f08 (patch) | |
| tree | 3ca9dd42155228292f0842d65b9b6d90e9140639 /lib | |
| parent | e51ceb6812e3736aa9b9ce1f2d5181f5b4b6aaa3 (diff) | |
| parent | 444b1552364b39761c3278c7da5152fd3998f216 (diff) | |
Merge branch 'master' into hotfixes
Diffstat (limited to 'lib')
80 files changed, 3777 insertions, 2215 deletions
diff --git a/lib/.htaccess b/lib/.htaccess new file mode 100644 index 000000000..9e768397d --- /dev/null +++ b/lib/.htaccess @@ -0,0 +1,3 @@ +Order Allow,Deny +Deny from all +Satisfy all diff --git a/lib/JSON.php b/lib/JSON.php new file mode 100644 index 000000000..8dc8a6f01 --- /dev/null +++ b/lib/JSON.php @@ -0,0 +1,933 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ +/** + * Converts to and from JSON format. + * + * JSON (JavaScript Object Notation) is a lightweight data-interchange + * format. It is easy for humans to read and write. It is easy for machines + * to parse and generate. It is based on a subset of the JavaScript + * Programming Language, Standard ECMA-262 3rd Edition - December 1999. + * This feature can also be found in Python. JSON is a text format that is + * completely language independent but uses conventions that are familiar + * to programmers of the C-family of languages, including C, C++, C#, Java, + * JavaScript, Perl, TCL, and many others. These properties make JSON an + * ideal data-interchange language. + * + * This package provides a simple encoder and decoder for JSON notation. It + * is intended for use with client-side Javascript applications that make + * use of HTTPRequest to perform server communication functions - data can + * be encoded into JSON notation for use in a client-side javascript, or + * decoded from incoming Javascript requests. JSON format is native to + * Javascript, and can be directly eval()'ed with no further parsing + * overhead + * + * All strings should be in ASCII or UTF-8 format! + * + * LICENSE: 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. + * + * THIS SOFTWARE IS PROVIDED ``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 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. + * + * @category + * @package Services_JSON + * @author Michal Migurski <mike-json@teczno.com> + * @author Matt Knapp <mdknapp[at]gmail[dot]com> + * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com> + * @copyright 2005 Michal Migurski + * @version CVS: $Id: JSON.php 305040 2010-11-02 23:19:03Z alan_k $ + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 + */ + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_SLICE', 1); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_STR', 2); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_ARR', 3); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_OBJ', 4); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_CMT', 5); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_LOOSE_TYPE', 16); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_SUPPRESS_ERRORS', 32); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_USE_TO_JSON', 64); + +/** + * Converts to and from JSON format. + * + * Brief example of use: + * + * <code> + * // create a new instance of Services_JSON + * $json = new Services_JSON(); + * + * // convert a complexe value to JSON notation, and send it to the browser + * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); + * $output = $json->encode($value); + * + * print($output); + * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] + * + * // accept incoming POST data, assumed to be in JSON notation + * $input = file_get_contents('php://input', 1000000); + * $value = $json->decode($input); + * </code> + */ +class Services_JSON +{ + /** + * constructs a new JSON instance + * + * @param int $use object behavior flags; combine with boolean-OR + * + * possible values: + * - SERVICES_JSON_LOOSE_TYPE: loose typing. + * "{...}" syntax creates associative arrays + * instead of objects in decode(). + * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. + * Values which can't be encoded (e.g. resources) + * appear as NULL instead of throwing errors. + * By default, a deeply-nested resource will + * bubble up with an error, so all return values + * from encode() should be checked with isError() + * - SERVICES_JSON_USE_TO_JSON: call toJSON when serializing objects + * It serializes the return value from the toJSON call rather + * than the object it'self, toJSON can return associative arrays, + * strings or numbers, if you return an object, make sure it does + * not have a toJSON method, otherwise an error will occur. + */ + function Services_JSON($use = 0) + { + $this->use = $use; + $this->_mb_strlen = function_exists('mb_strlen'); + $this->_mb_convert_encoding = function_exists('mb_convert_encoding'); + $this->_mb_substr = function_exists('mb_substr'); + } + // private - cache the mbstring lookup results.. + var $_mb_strlen = false; + var $_mb_substr = false; + var $_mb_convert_encoding = false; + + /** + * convert a string from one UTF-16 char to one UTF-8 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf16 UTF-16 character + * @return string UTF-8 character + * @access private + */ + function utf162utf8($utf16) + { + // oh please oh please oh please oh please oh please + if($this->_mb_convert_encoding) { + return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); + } + + $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); + + switch(true) { + case ((0x7F & $bytes) == $bytes): + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x7F & $bytes); + + case (0x07FF & $bytes) == $bytes: + // return a 2-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xC0 | (($bytes >> 6) & 0x1F)) + . chr(0x80 | ($bytes & 0x3F)); + + case (0xFFFF & $bytes) == $bytes: + // return a 3-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xE0 | (($bytes >> 12) & 0x0F)) + . chr(0x80 | (($bytes >> 6) & 0x3F)) + . chr(0x80 | ($bytes & 0x3F)); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * convert a string from one UTF-8 char to one UTF-16 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf8 UTF-8 character + * @return string UTF-16 character + * @access private + */ + function utf82utf16($utf8) + { + // oh please oh please oh please oh please oh please + if($this->_mb_convert_encoding) { + return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); + } + + switch($this->strlen8($utf8)) { + case 1: + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return $utf8; + + case 2: + // return a UTF-16 character from a 2-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x07 & (ord($utf8{0}) >> 2)) + . chr((0xC0 & (ord($utf8{0}) << 6)) + | (0x3F & ord($utf8{1}))); + + case 3: + // return a UTF-16 character from a 3-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr((0xF0 & (ord($utf8{0}) << 4)) + | (0x0F & (ord($utf8{1}) >> 2))) + . chr((0xC0 & (ord($utf8{1}) << 6)) + | (0x7F & ord($utf8{2}))); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * encodes an arbitrary variable into JSON format (and sends JSON Header) + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function encode($var) + { + header('Content-type: application/json'); + return $this->encodeUnsafe($var); + } + /** + * encodes an arbitrary variable into JSON format without JSON Header - warning - may allow XSS!!!!) + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function encodeUnsafe($var) + { + // see bug #16908 - regarding numeric locale printing + $lc = setlocale(LC_NUMERIC, 0); + setlocale(LC_NUMERIC, 'C'); + $ret = $this->_encode($var); + setlocale(LC_NUMERIC, $lc); + return $ret; + + } + /** + * PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function _encode($var) + { + + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; + + case 'NULL': + return 'null'; + + case 'integer': + return (int) $var; + + case 'double': + case 'float': + return (float) $var; + + case 'string': + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = $this->strlen8($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { + + $ord_var_c = ord($var{$c}); + + switch (true) { + case $ord_var_c == 0x08: + $ascii .= '\b'; + break; + case $ord_var_c == 0x09: + $ascii .= '\t'; + break; + case $ord_var_c == 0x0A: + $ascii .= '\n'; + break; + case $ord_var_c == 0x0C: + $ascii .= '\f'; + break; + case $ord_var_c == 0x0D: + $ascii .= '\r'; + break; + + case $ord_var_c == 0x22: + case $ord_var_c == 0x2F: + case $ord_var_c == 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): + // characters U-00000000 - U-0000007F (same as ASCII) + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + if ($c+1 >= $strlen_var) { + $c += 1; + $ascii .= '?'; + break; + } + + $char = pack('C*', $ord_var_c, ord($var{$c + 1})); + $c += 1; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + if ($c+2 >= $strlen_var) { + $c += 2; + $ascii .= '?'; + break; + } + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + @ord($var{$c + 1}), + @ord($var{$c + 2})); + $c += 2; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + if ($c+3 >= $strlen_var) { + $c += 3; + $ascii .= '?'; + break; + } + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3})); + $c += 3; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFC) == 0xF8): + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + if ($c+4 >= $strlen_var) { + $c += 4; + $ascii .= '?'; + break; + } + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4})); + $c += 4; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFE) == 0xFC): + if ($c+5 >= $strlen_var) { + $c += 5; + $ascii .= '?'; + break; + } + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4}), + ord($var{$c + 5})); + $c += 5; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + } + } + return '"'.$ascii.'"'; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { + $properties = array_map(array($this, 'name_value'), + array_keys($var), + array_values($var)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + } + + // treat it like a regular array + $elements = array_map(array($this, '_encode'), $var); + + foreach($elements as $element) { + if(Services_JSON::isError($element)) { + return $element; + } + } + + return '[' . join(',', $elements) . ']'; + + case 'object': + + // support toJSON methods. + if (($this->use & SERVICES_JSON_USE_TO_JSON) && method_exists($var, 'toJSON')) { + // this may end up allowing unlimited recursion + // so we check the return value to make sure it's not got the same method. + $recode = $var->toJSON(); + + if (method_exists($recode, 'toJSON')) { + + return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) + ? 'null' + : new Services_JSON_Error(class_name($var). + " toJSON returned an object with a toJSON method."); + + } + + return $this->_encode( $recode ); + } + + $vars = get_object_vars($var); + + $properties = array_map(array($this, 'name_value'), + array_keys($vars), + array_values($vars)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + + default: + return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) + ? 'null' + : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); + } + } + + /** + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + function name_value($name, $value) + { + $encoded_value = $this->_encode($value); + + if(Services_JSON::isError($encoded_value)) { + return $encoded_value; + } + + return $this->_encode(strval($name)) . ':' . $encoded_value; + } + + /** + * reduce a string by removing leading and trailing comments and whitespace + * + * @param $str string string value to strip of comments and whitespace + * + * @return string string value stripped of comments and whitespace + * @access private + */ + function reduce_string($str) + { + $str = preg_replace(array( + + // eliminate single line comments in '// ...' form + '#^\s*//(.+)$#m', + + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.+)\*/#Us', + + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.+)\*/\s*$#Us' + + ), '', $str); + + // eliminate extraneous space + return trim($str); + } + + /** + * decodes a JSON string into appropriate variable + * + * @param string $str JSON-formatted string + * + * @return mixed number, boolean, string, array, or object + * corresponding to given JSON input string. + * See argument 1 to Services_JSON() above for object-output behavior. + * Note that decode() always returns strings + * in ASCII or UTF-8 format! + * @access public + */ + function decode($str) + { + $str = $this->reduce_string($str); + + switch (strtolower($str)) { + case 'true': + return true; + + case 'false': + return false; + + case 'null': + return null; + + default: + $m = array(); + + if (is_numeric($str)) { + // Lookie-loo, it's a number + + // This would work on its own, but I'm trying to be + // good about returning integers where appropriate: + // return (float)$str; + + // Return float or int, as appropriate + return ((float)$str == (integer)$str) + ? (integer)$str + : (float)$str; + + } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { + // STRINGS RETURNED IN UTF-8 FORMAT + $delim = $this->substr8($str, 0, 1); + $chrs = $this->substr8($str, 1, -1); + $utf8 = ''; + $strlen_chrs = $this->strlen8($chrs); + + for ($c = 0; $c < $strlen_chrs; ++$c) { + + $substr_chrs_c_2 = $this->substr8($chrs, $c, 2); + $ord_chrs_c = ord($chrs{$c}); + + switch (true) { + case $substr_chrs_c_2 == '\b': + $utf8 .= chr(0x08); + ++$c; + break; + case $substr_chrs_c_2 == '\t': + $utf8 .= chr(0x09); + ++$c; + break; + case $substr_chrs_c_2 == '\n': + $utf8 .= chr(0x0A); + ++$c; + break; + case $substr_chrs_c_2 == '\f': + $utf8 .= chr(0x0C); + ++$c; + break; + case $substr_chrs_c_2 == '\r': + $utf8 .= chr(0x0D); + ++$c; + break; + + case $substr_chrs_c_2 == '\\"': + case $substr_chrs_c_2 == '\\\'': + case $substr_chrs_c_2 == '\\\\': + case $substr_chrs_c_2 == '\\/': + if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || + ($delim == "'" && $substr_chrs_c_2 != '\\"')) { + $utf8 .= $chrs{++$c}; + } + break; + + case preg_match('/\\\u[0-9A-F]{4}/i', $this->substr8($chrs, $c, 6)): + // single, escaped unicode character + $utf16 = chr(hexdec($this->substr8($chrs, ($c + 2), 2))) + . chr(hexdec($this->substr8($chrs, ($c + 4), 2))); + $utf8 .= $this->utf162utf8($utf16); + $c += 5; + break; + + case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): + $utf8 .= $chrs{$c}; + break; + + case ($ord_chrs_c & 0xE0) == 0xC0: + // characters U-00000080 - U-000007FF, mask 110XXXXX + //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= $this->substr8($chrs, $c, 2); + ++$c; + break; + + case ($ord_chrs_c & 0xF0) == 0xE0: + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= $this->substr8($chrs, $c, 3); + $c += 2; + break; + + case ($ord_chrs_c & 0xF8) == 0xF0: + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= $this->substr8($chrs, $c, 4); + $c += 3; + break; + + case ($ord_chrs_c & 0xFC) == 0xF8: + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= $this->substr8($chrs, $c, 5); + $c += 4; + break; + + case ($ord_chrs_c & 0xFE) == 0xFC: + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= $this->substr8($chrs, $c, 6); + $c += 5; + break; + + } + + } + + return $utf8; + + } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { + // array, or object notation + + if ($str{0} == '[') { + $stk = array(SERVICES_JSON_IN_ARR); + $arr = array(); + } else { + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = array(); + } else { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = new stdClass(); + } + } + + array_push($stk, array('what' => SERVICES_JSON_SLICE, + 'where' => 0, + 'delim' => false)); + + $chrs = $this->substr8($str, 1, -1); + $chrs = $this->reduce_string($chrs); + + if ($chrs == '') { + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } else { + return $obj; + + } + } + + //print("\nparsing {$chrs}\n"); + + $strlen_chrs = $this->strlen8($chrs); + + for ($c = 0; $c <= $strlen_chrs; ++$c) { + + $top = end($stk); + $substr_chrs_c_2 = $this->substr8($chrs, $c, 2); + + if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { + // found a comma that is not inside a string, array, etc., + // OR we've reached the end of the character list + $slice = $this->substr8($chrs, $top['where'], ($c - $top['where'])); + array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); + //print("Found split at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + // we are in an array, so just push an element onto the stack + array_push($arr, $this->decode($slice)); + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + // we are in an object, so figure + // out the property name and set an + // element in an associative array, + // for now + $parts = array(); + + if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:/Uis', $slice, $parts)) { + // "name":value pair + $key = $this->decode($parts[1]); + $val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B")); + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } elseif (preg_match('/^\s*(\w+)\s*:/Uis', $slice, $parts)) { + // name:value pair, where name is unquoted + $key = $parts[1]; + $val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B")); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } + + } + + } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { + // found a quote, and we are not inside a string + array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); + //print("Found start of string at {$c}\n"); + + } elseif (($chrs{$c} == $top['delim']) && + ($top['what'] == SERVICES_JSON_IN_STR) && + (($this->strlen8($this->substr8($chrs, 0, $c)) - $this->strlen8(rtrim($this->substr8($chrs, 0, $c), '\\'))) % 2 != 1)) { + // found a quote, we're in a string, and it's not escaped + // we know that it's not escaped becase there is _not_ an + // odd number of backslashes at the end of the string so far + array_pop($stk); + //print("Found end of string at {$c}: ".$this->substr8($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '[') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-bracket, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); + //print("Found start of array at {$c}\n"); + + } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { + // found a right-bracket, and we're in an array + array_pop($stk); + //print("Found end of array at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '{') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-brace, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); + //print("Found start of object at {$c}\n"); + + } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { + // found a right-brace, and we're in an object + array_pop($stk); + //print("Found end of object at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($substr_chrs_c_2 == '/*') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a comment start, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); + $c++; + //print("Found start of comment at {$c}\n"); + + } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { + // found a comment end, and we're in one now + array_pop($stk); + $c++; + + for ($i = $top['where']; $i <= $c; ++$i) + $chrs = substr_replace($chrs, ' ', $i, 1); + + //print("Found end of comment at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } + + } + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + return $obj; + + } + + } + } + } + + /** + * @todo Ultimately, this should just call PEAR::isError() + */ + function isError($data, $code = null) + { + if (class_exists('pear')) { + return PEAR::isError($data, $code); + } elseif (is_object($data) && (get_class($data) == 'services_json_error' || + is_subclass_of($data, 'services_json_error'))) { + return true; + } + + return false; + } + + /** + * Calculates length of string in bytes + * @param string + * @return integer length + */ + function strlen8( $str ) + { + if ( $this->_mb_strlen ) { + return mb_strlen( $str, "8bit" ); + } + return strlen( $str ); + } + + /** + * Returns part of a string, interpreting $start and $length as number of bytes. + * @param string + * @param integer start + * @param integer length + * @return integer length + */ + function substr8( $string, $start, $length=false ) + { + if ( $length === false ) { + $length = $this->strlen8( $string ) - $start; + } + if ( $this->_mb_substr ) { + return mb_substr( $string, $start, $length, "8bit" ); + } + return substr( $string, $start, $length ); + } + +} + +if (class_exists('PEAR_Error')) { + + class Services_JSON_Error extends PEAR_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + parent::PEAR_Error($message, $code, $mode, $options, $userinfo); + } + } + +} else { + + /** + * @todo Ultimately, this class shall be descended from PEAR_Error + */ + class Services_JSON_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + + } + } + +} diff --git a/lib/minz/ActionController.php b/lib/Minz/ActionController.php index ab9389dbd..b47c54554 100755..100644 --- a/lib/minz/ActionController.php +++ b/lib/Minz/ActionController.php @@ -7,18 +7,14 @@ /** * La classe ActionController représente le contrôleur de l'application */ -class ActionController { - protected $router; +class Minz_ActionController { protected $view; /** * Constructeur - * @param $controller nom du controller - * @param $action nom de l'action à lancer */ - public function __construct ($router) { - $this->router = $router; - $this->view = new View (); + public function __construct () { + $this->view = new Minz_View (); $this->view->attributeParams (); } diff --git a/lib/Minz/ActionException.php b/lib/Minz/ActionException.php new file mode 100644 index 000000000..c566a076f --- /dev/null +++ b/lib/Minz/ActionException.php @@ -0,0 +1,9 @@ +<?php +class Minz_ActionException extends Minz_Exception { + public function __construct ($controller_name, $action_name, $code = self::ERROR) { + $message = '`' . $action_name . '` cannot be invoked on `' + . $controller_name . '`'; + + parent::__construct ($message, $code); + } +} diff --git a/lib/Minz/BadConfigurationException.php b/lib/Minz/BadConfigurationException.php new file mode 100644 index 000000000..a7b77d687 --- /dev/null +++ b/lib/Minz/BadConfigurationException.php @@ -0,0 +1,9 @@ +<?php +class Minz_BadConfigurationException extends Minz_Exception { + public function __construct ($part_missing, $code = self::ERROR) { + $message = '`' . $part_missing + . '` in the configuration file is missing or is misconfigured'; + + parent::__construct ($message, $code); + } +} diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php new file mode 100644 index 000000000..4e9da58b4 --- /dev/null +++ b/lib/Minz/Configuration.php @@ -0,0 +1,375 @@ +<?php +/** + * MINZ - Copyright 2011 Marien Fressinaud + * Sous licence AGPL3 <http://www.gnu.org/licenses/> +*/ + +/** + * La classe Configuration permet de gérer la configuration de l'application + */ +class Minz_Configuration { + const CONF_PATH_NAME = '/config.php'; + + /** + * VERSION est la version actuelle de MINZ + */ + const VERSION = '1.3.1.freshrss'; // version spéciale FreshRSS + + /** + * valeurs possibles pour l'"environment" + * SILENT rend l'application muette (pas de log) + * PRODUCTION est recommandée pour une appli en production + * (log les erreurs critiques) + * DEVELOPMENT log toutes les erreurs + */ + const SILENT = 0; + const PRODUCTION = 1; + const DEVELOPMENT = 2; + + /** + * définition des variables de configuration + * $salt une chaîne de caractères aléatoires (obligatoire) + * $environment gère le niveau d'affichage pour log et erreurs + * $base_url le chemin de base pour accéder à l'application + * $title le nom de l'application + * $language la langue par défaut de l'application + * $db paramètres pour la base de données (tableau) + * - host le serveur de la base + * - user nom d'utilisateur + * - password mot de passe de l'utilisateur + * - base le nom de la base de données + */ + private static $salt = ''; + private static $environment = Minz_Configuration::PRODUCTION; + private static $base_url = ''; + private static $title = ''; + private static $language = 'en'; + private static $default_user = ''; + private static $allow_anonymous = false; + private static $allow_anonymous_refresh = false; + private static $auth_type = 'none'; + private static $api_enabled = false; + private static $unsafe_autologin_enabled = false; + + private static $db = array ( + 'type' => 'mysql', + 'host' => '', + 'user' => '', + 'password' => '', + 'base' => '', + 'prefix' => '', + ); + + /* + * Getteurs + */ + public static function salt () { + return self::$salt; + } + public static function environment ($str = false) { + $env = self::$environment; + + if ($str) { + switch (self::$environment) { + case self::SILENT: + $env = 'silent'; + break; + case self::DEVELOPMENT: + $env = 'development'; + break; + case self::PRODUCTION: + default: + $env = 'production'; + } + } + + return $env; + } + public static function baseUrl () { + return self::$base_url; + } + public static function title () { + return self::$title; + } + public static function language () { + return self::$language; + } + public static function dataBase () { + return self::$db; + } + public static function defaultUser () { + return self::$default_user; + } + public static function isAdmin($currentUser) { + return $currentUser === self::$default_user; + } + public static function allowAnonymous() { + return self::$allow_anonymous; + } + public static function allowAnonymousRefresh() { + return self::$allow_anonymous_refresh; + } + public static function authType() { + return self::$auth_type; + } + public static function needsLogin() { + return self::$auth_type !== 'none'; + } + public static function canLogIn() { + return self::$auth_type === 'form' || self::$auth_type === 'persona'; + } + public static function apiEnabled() { + return self::$api_enabled; + } + public static function unsafeAutologinEnabled() { + return self::$unsafe_autologin_enabled; + } + + public static function _allowAnonymous($allow = false) { + self::$allow_anonymous = ((bool)$allow) && self::canLogIn(); + } + public static function _allowAnonymousRefresh($allow = false) { + self::$allow_anonymous_refresh = ((bool)$allow) && self::allowAnonymous(); + } + public static function _authType($value) { + $value = strtolower($value); + switch ($value) { + case 'form': + case 'http_auth': + case 'persona': + case 'none': + self::$auth_type = $value; + break; + } + self::_allowAnonymous(self::$allow_anonymous); + } + + public static function _enableApi($value = false) { + self::$api_enabled = (bool)$value; + } + public static function _enableAutologin($value = false) { + self::$unsafe_autologin_enabled = (bool)$value; + } + + /** + * Initialise les variables de configuration + * @exception Minz_FileNotExistException si le CONF_PATH_NAME n'existe pas + * @exception Minz_BadConfigurationException si CONF_PATH_NAME mal formaté + */ + public static function init () { + try { + self::parseFile (); + self::setReporting (); + } catch (Minz_FileNotExistException $e) { + throw $e; + } catch (Minz_BadConfigurationException $e) { + throw $e; + } + } + + public static function writeFile() { + $ini_array = array( + 'general' => array( + 'environment' => self::environment(true), + 'salt' => self::$salt, + 'base_url' => self::$base_url, + 'title' => self::$title, + 'default_user' => self::$default_user, + 'allow_anonymous' => self::$allow_anonymous, + 'allow_anonymous_refresh' => self::$allow_anonymous_refresh, + 'auth_type' => self::$auth_type, + 'api_enabled' => self::$api_enabled, + 'unsafe_autologin_enabled' => self::$unsafe_autologin_enabled, + ), + 'db' => self::$db, + ); + @rename(DATA_PATH . self::CONF_PATH_NAME, DATA_PATH . self::CONF_PATH_NAME . '.bak.php'); + $result = file_put_contents(DATA_PATH . self::CONF_PATH_NAME, "<?php\n return " . var_export($ini_array, true) . ';'); + if (function_exists('opcache_invalidate')) { + opcache_invalidate(DATA_PATH . self::CONF_PATH_NAME); //Clear PHP 5.5+ cache for include + } + return (bool)$result; + } + + /** + * Parse un fichier de configuration + * @exception Minz_PermissionDeniedException si le CONF_PATH_NAME n'est pas accessible + * @exception Minz_BadConfigurationException si CONF_PATH_NAME mal formaté + */ + private static function parseFile () { + $ini_array = include(DATA_PATH . self::CONF_PATH_NAME); + + if (!is_array($ini_array)) { + throw new Minz_PermissionDeniedException ( + DATA_PATH . self::CONF_PATH_NAME, + Minz_Exception::ERROR + ); + } + + // [general] est obligatoire + if (!isset ($ini_array['general'])) { + throw new Minz_BadConfigurationException ( + '[general]', + Minz_Exception::ERROR + ); + } + $general = $ini_array['general']; + + // salt est obligatoire + if (!isset ($general['salt'])) { + if (isset($general['sel_application'])) { //v0.6 + $general['salt'] = $general['sel_application']; + } else { + throw new Minz_BadConfigurationException ( + 'salt', + Minz_Exception::ERROR + ); + } + } + self::$salt = $general['salt']; + + if (isset ($general['environment'])) { + switch ($general['environment']) { + case 'silent': + self::$environment = Minz_Configuration::SILENT; + break; + case 'development': + self::$environment = Minz_Configuration::DEVELOPMENT; + break; + case 'production': + self::$environment = Minz_Configuration::PRODUCTION; + break; + default: + if ($general['environment'] >= 0 && + $general['environment'] <= 2) { + // fallback 0.7-beta + self::$environment = $general['environment']; + } else { + throw new Minz_BadConfigurationException ( + 'environment', + Minz_Exception::ERROR + ); + } + } + + } + if (isset ($general['base_url'])) { + self::$base_url = $general['base_url']; + } + + if (isset ($general['title'])) { + self::$title = $general['title']; + } + if (isset ($general['language'])) { + self::$language = $general['language']; + } + if (isset ($general['default_user'])) { + self::$default_user = $general['default_user']; + } + if (isset ($general['auth_type'])) { + self::_authType($general['auth_type']); + } + if (isset ($general['allow_anonymous'])) { + self::$allow_anonymous = ( + ((bool)($general['allow_anonymous'])) && + ($general['allow_anonymous'] !== 'no') + ); + } + if (isset ($general['allow_anonymous_refresh'])) { + self::$allow_anonymous_refresh = ( + ((bool)($general['allow_anonymous_refresh'])) && + ($general['allow_anonymous_refresh'] !== 'no') + ); + } + if (isset ($general['api_enabled'])) { + self::$api_enabled = ( + ((bool)($general['api_enabled'])) && + ($general['api_enabled'] !== 'no') + ); + } + if (isset ($general['unsafe_autologin_enabled'])) { + self::$unsafe_autologin_enabled = ( + ((bool)($general['unsafe_autologin_enabled'])) && + ($general['unsafe_autologin_enabled'] !== 'no') + ); + } + + // Base de données + if (isset ($ini_array['db'])) { + $db = $ini_array['db']; + if (empty($db['type'])) { + throw new Minz_BadConfigurationException ( + 'type', + Minz_Exception::ERROR + ); + } + switch ($db['type']) { + case 'mysql': + if (empty($db['host'])) { + throw new Minz_BadConfigurationException ( + 'host', + Minz_Exception::ERROR + ); + } + if (empty($db['user'])) { + throw new Minz_BadConfigurationException ( + 'user', + Minz_Exception::ERROR + ); + } + if (!isset($db['password'])) { + throw new Minz_BadConfigurationException ( + 'password', + Minz_Exception::ERROR + ); + } + if (empty($db['base'])) { + throw new Minz_BadConfigurationException ( + 'base', + Minz_Exception::ERROR + ); + } + self::$db['host'] = $db['host']; + self::$db['user'] = $db['user']; + self::$db['password'] = $db['password']; + self::$db['base'] = $db['base']; + if (isset($db['prefix'])) { + self::$db['prefix'] = $db['prefix']; + } + break; + case 'sqlite': + self::$db['host'] = ''; + self::$db['user'] = ''; + self::$db['password'] = ''; + self::$db['base'] = ''; + self::$db['prefix'] = ''; + break; + default: + throw new Minz_BadConfigurationException ( + 'type', + Minz_Exception::ERROR + ); + break; + } + self::$db['type'] = $db['type']; + } + } + + private static function setReporting() { + switch (self::$environment) { + case self::PRODUCTION: + error_reporting(E_ALL); + ini_set('display_errors','Off'); + ini_set('log_errors', 'On'); + break; + case self::DEVELOPMENT: + error_reporting(E_ALL); + ini_set('display_errors','On'); + ini_set('log_errors', 'On'); + break; + case self::SILENT: + error_reporting(0); + break; + } + } +} diff --git a/lib/Minz/ControllerNotActionControllerException.php b/lib/Minz/ControllerNotActionControllerException.php new file mode 100644 index 000000000..535a1377e --- /dev/null +++ b/lib/Minz/ControllerNotActionControllerException.php @@ -0,0 +1,9 @@ +<?php +class Minz_ControllerNotActionControllerException extends Minz_Exception { + public function __construct ($controller_name, $code = self::ERROR) { + $message = 'Controller `' . $controller_name + . '` isn\'t instance of ActionController'; + + parent::__construct ($message, $code); + } +} diff --git a/lib/Minz/ControllerNotExistException.php b/lib/Minz/ControllerNotExistException.php new file mode 100644 index 000000000..523867d11 --- /dev/null +++ b/lib/Minz/ControllerNotExistException.php @@ -0,0 +1,9 @@ +<?php +class Minz_ControllerNotExistException extends Minz_Exception { + public function __construct ($controller_name, $code = self::ERROR) { + $message = 'Controller `' . $controller_name + . '` doesn\'t exist'; + + parent::__construct ($message, $code); + } +} diff --git a/lib/Minz/CurrentPagePaginationException.php b/lib/Minz/CurrentPagePaginationException.php new file mode 100644 index 000000000..74214d879 --- /dev/null +++ b/lib/Minz/CurrentPagePaginationException.php @@ -0,0 +1,8 @@ +<?php +class Minz_CurrentPagePaginationException extends Minz_Exception { + public function __construct ($page) { + $message = 'Page number `' . $page . '` doesn\'t exist'; + + parent::__construct ($message, self::ERROR); + } +} diff --git a/lib/Minz/Dispatcher.php b/lib/Minz/Dispatcher.php new file mode 100644 index 000000000..f62a92911 --- /dev/null +++ b/lib/Minz/Dispatcher.php @@ -0,0 +1,117 @@ +<?php +/** + * MINZ - Copyright 2011 Marien Fressinaud + * Sous licence AGPL3 <http://www.gnu.org/licenses/> +*/ + +/** + * Le Dispatcher s'occupe d'initialiser le Controller et d'executer l'action + * déterminée dans la Request + * C'est un singleton + */ +class Minz_Dispatcher { + const CONTROLLERS_PATH_NAME = '/Controllers'; + + /* singleton */ + private static $instance = null; + private static $needsReset; + + private $controller; + + /** + * Récupère l'instance du Dispatcher + */ + public static function getInstance () { + if (self::$instance === null) { + self::$instance = new Minz_Dispatcher (); + } + return self::$instance; + } + + /** + * Lance le controller indiqué dans Request + * Remplit le body de Response à partir de la Vue + * @exception Minz_Exception + */ + public function run () { + do { + self::$needsReset = false; + + try { + $this->createController ('FreshRSS_' . Minz_Request::controllerName () . '_Controller'); + $this->controller->init (); + $this->controller->firstAction (); + if (!self::$needsReset) { + $this->launchAction ( + Minz_Request::actionName () + . 'Action' + ); + } + $this->controller->lastAction (); + + if (!self::$needsReset) { + $this->controller->view ()->build (); + } + } catch (Minz_Exception $e) { + throw $e; + } + } while (self::$needsReset); + } + + /** + * Informe le contrôleur qu'il doit recommancer car la requête a été modifiée + */ + public static function reset() { + self::$needsReset = true; + } + + /** + * Instancie le Controller + * @param $controller_name le nom du controller à instancier + * @exception ControllerNotExistException le controller n'existe pas + * @exception ControllerNotActionControllerException controller n'est + * > pas une instance de ActionController + */ + private function createController ($controller_name) { + $filename = APP_PATH . self::CONTROLLERS_PATH_NAME . '/' + . $controller_name . '.php'; + + if (!class_exists ($controller_name)) { + throw new Minz_ControllerNotExistException ( + $controller_name, + Minz_Exception::ERROR + ); + } + $this->controller = new $controller_name (); + + if (! ($this->controller instanceof Minz_ActionController)) { + throw new Minz_ControllerNotActionControllerException ( + $controller_name, + Minz_Exception::ERROR + ); + } + } + + /** + * Lance l'action sur le controller du dispatcher + * @param $action_name le nom de l'action + * @exception ActionException si on ne peut pas exécuter l'action sur + * le controller + */ + private function launchAction ($action_name) { + if (!is_callable (array ( + $this->controller, + $action_name + ))) { + throw new Minz_ActionException ( + get_class ($this->controller), + $action_name, + Minz_Exception::ERROR + ); + } + call_user_func (array ( + $this->controller, + $action_name + )); + } +} diff --git a/lib/minz/Error.php b/lib/Minz/Error.php index 0e8c2f60b..c8222a430 100755..100644 --- a/lib/minz/Error.php +++ b/lib/Minz/Error.php @@ -1,5 +1,5 @@ <?php -/** +/** * MINZ - Copyright 2011 Marien Fressinaud * Sous licence AGPL3 <http://www.gnu.org/licenses/> */ @@ -7,7 +7,7 @@ /** * La classe Error permet de lancer des erreurs HTTP */ -class Error { +class Minz_Error { public function __construct () { } /** @@ -21,43 +21,59 @@ class Error { */ public static function error ($code = 404, $logs = array (), $redirect = false) { $logs = self::processLogs ($logs); - $error_filename = APP_PATH . '/controllers/errorController.php'; - + $error_filename = APP_PATH . '/Controllers/errorController.php'; + + switch ($code) { + case 200 : + header('HTTP/1.1 200 OK'); + break; + case 403 : + header('HTTP/1.1 403 Forbidden'); + break; + case 404 : + header('HTTP/1.1 404 Not Found'); + break; + case 500 : + header('HTTP/1.1 500 Internal Server Error'); + break; + case 503 : + header('HTTP/1.1 503 Service Unavailable'); + break; + default : + header('HTTP/1.1 500 Internal Server Error'); + } + if (file_exists ($error_filename)) { $params = array ( 'code' => $code, 'logs' => $logs ); - - Response::setHeader ($code); + if ($redirect) { - Request::forward (array ( + Minz_Request::forward (array ( 'c' => 'error' ), true); } else { - Request::forward (array ( + Minz_Request::forward (array ( 'c' => 'error', 'params' => $params ), false); } } else { - $text = '<h1>An error occured</h1>'."\n"; - + echo '<h1>An error occured</h1>' . "\n"; + if (!empty ($logs)) { - $text .= '<ul>'."\n"; + echo '<ul>' . "\n"; foreach ($logs as $log) { - $text .= '<li>' . $log . '</li>'."\n"; + echo '<li>' . $log . '</li>' . "\n"; } - $text .= '</ul>'."\n"; + echo '</ul>' . "\n"; } - - Response::setHeader ($code); - Response::setBody ($text); - Response::send (); + exit (); } } - + /** * Permet de retourner les logs de façon à n'avoir que * ceux que l'on veut réellement @@ -66,12 +82,12 @@ class Error { * > en fonction de l'environment */ private static function processLogs ($logs) { - $env = Configuration::environment (); + $env = Minz_Configuration::environment (); $logs_ok = array (); $error = array (); $warning = array (); $notice = array (); - + if (isset ($logs['error'])) { $error = $logs['error']; } @@ -81,14 +97,14 @@ class Error { if (isset ($logs['notice'])) { $notice = $logs['notice']; } - - if ($env == Configuration::PRODUCTION) { + + if ($env == Minz_Configuration::PRODUCTION) { $logs_ok = $error; } - if ($env == Configuration::DEVELOPMENT) { + if ($env == Minz_Configuration::DEVELOPMENT) { $logs_ok = array_merge ($error, $warning, $notice); } - + return $logs_ok; } } diff --git a/lib/Minz/Exception.php b/lib/Minz/Exception.php new file mode 100644 index 000000000..b5e71e0d8 --- /dev/null +++ b/lib/Minz/Exception.php @@ -0,0 +1,16 @@ +<?php +class Minz_Exception extends Exception { + const ERROR = 0; + const WARNING = 10; + const NOTICE = 20; + + public function __construct ($message, $code = self::ERROR) { + if ($code != Minz_Exception::ERROR + && $code != Minz_Exception::WARNING + && $code != Minz_Exception::NOTICE) { + $code = Minz_Exception::ERROR; + } + + parent::__construct ($message, $code); + } +} diff --git a/lib/Minz/FileNotExistException.php b/lib/Minz/FileNotExistException.php new file mode 100644 index 000000000..f8dfbdf66 --- /dev/null +++ b/lib/Minz/FileNotExistException.php @@ -0,0 +1,8 @@ +<?php +class Minz_FileNotExistException extends Minz_Exception { + public function __construct ($file_name, $code = self::ERROR) { + $message = 'File not found: `' . $file_name.'`'; + + parent::__construct ($message, $code); + } +} diff --git a/lib/Minz/FrontController.php b/lib/Minz/FrontController.php new file mode 100644 index 000000000..f13882801 --- /dev/null +++ b/lib/Minz/FrontController.php @@ -0,0 +1,117 @@ +<?php +# ***** BEGIN LICENSE BLOCK ***** +# MINZ - a free PHP Framework like Zend Framework +# Copyright (C) 2011 Marien Fressinaud +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# ***** END LICENSE BLOCK ***** + +/** + * La classe FrontController est le Dispatcher du framework, elle lance l'application + * Elle est appelée en général dans le fichier index.php à la racine du serveur + */ +class Minz_FrontController { + protected $dispatcher; + + /** + * Constructeur + * Initialise le dispatcher, met à jour la Request + */ + public function __construct () { + if (LOG_PATH === false) { + $this->killApp ('Path not found: LOG_PATH'); + } + + try { + Minz_Configuration::init (); + + Minz_Request::init (); + + $url = $this->buildUrl(); + $url['params'] = array_merge ( + $url['params'], + Minz_Request::fetchPOST () + ); + Minz_Request::forward ($url); + } catch (Minz_Exception $e) { + Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); + $this->killApp ($e->getMessage ()); + } + + $this->dispatcher = Minz_Dispatcher::getInstance(); + } + + /** + * Retourne un tableau représentant l'url passée par la barre d'adresses + * @return tableau représentant l'url + */ + private function buildUrl() { + $url = array (); + + $url['c'] = Minz_Request::fetchGET ( + 'c', + Minz_Request::defaultControllerName () + ); + $url['a'] = Minz_Request::fetchGET ( + 'a', + Minz_Request::defaultActionName () + ); + $url['params'] = Minz_Request::fetchGET (); + + // post-traitement + unset ($url['params']['c']); + unset ($url['params']['a']); + + return $url; + } + + /** + * Démarre l'application (lance le dispatcher et renvoie la réponse) + */ + public function run () { + try { + $this->dispatcher->run(); + } catch (Minz_Exception $e) { + try { + Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); + } catch (Minz_PermissionDeniedException $e) { + $this->killApp ($e->getMessage ()); + } + + if ($e instanceof Minz_FileNotExistException || + $e instanceof Minz_ControllerNotExistException || + $e instanceof Minz_ControllerNotActionControllerException || + $e instanceof Minz_ActionException) { + Minz_Error::error ( + 404, + array ('error' => array ($e->getMessage ())), + true + ); + } else { + $this->killApp (); + } + } + } + + /** + * Permet d'arrêter le programme en urgence + */ + private function killApp ($txt = '') { + if ($txt == '') { + $txt = 'See logs files'; + } + exit ('### Application problem ###<br />'."\n".$txt); + } +} diff --git a/lib/Minz/Helper.php b/lib/Minz/Helper.php new file mode 100644 index 000000000..f4a547c4e --- /dev/null +++ b/lib/Minz/Helper.php @@ -0,0 +1,33 @@ +<?php +/** + * MINZ - Copyright 2011 Marien Fressinaud + * Sous licence AGPL3 <http://www.gnu.org/licenses/> +*/ + +/** + * La classe Helper représente une aide pour des tâches récurrentes + */ +class Minz_Helper { + /** + * Annule les effets des magic_quotes pour une variable donnée + * @param $var variable à traiter (tableau ou simple variable) + */ + public static function stripslashes_r($var) { + if (is_array($var)){ + return array_map(array('Minz_Helper', 'stripslashes_r'), $var); + } else { + return stripslashes($var); + } + } + + /** + * Wrapper for htmlspecialchars. + * Force UTf-8 value and can be used on array too. + */ + public static function htmlspecialchars_utf8($var) { + if (is_array($var)) { + return array_map(array('Minz_Helper', 'htmlspecialchars_utf8'), $var); + } + return htmlspecialchars($var, ENT_COMPAT, 'UTF-8'); + } +} diff --git a/lib/minz/Log.php b/lib/Minz/Log.php index c6f23d900..d3eaec2ae 100755..100644 --- a/lib/minz/Log.php +++ b/lib/Minz/Log.php @@ -1,5 +1,5 @@ <?php -/** +/** * MINZ - Copyright 2011 Marien Fressinaud * Sous licence AGPL3 <http://www.gnu.org/licenses/> */ @@ -12,12 +12,14 @@ class Minz_Log { * Les différents niveau de log * ERROR erreurs bloquantes de l'application * WARNING erreurs pouvant géner le bon fonctionnement, mais non bloquantes - * NOTICE messages d'informations, affichés pour le déboggage + * NOTICE erreurs mineures ou messages d'informations + * DEBUG Informations affichées pour le déboggage */ - const ERROR = 0; - const WARNING = 10; - const NOTICE = 20; - + const ERROR = 2; + const WARNING = 4; + const NOTICE = 8; + const DEBUG = 16; + /** * Enregistre un message dans un fichier de log spécifique * Message non loggué si @@ -29,15 +31,15 @@ class Minz_Log { * @param $file_name fichier de log, par défaut LOG_PATH/application.log */ public static function record ($information, $level, $file_name = null) { - $env = Configuration::environment (); - - if (! ($env == Configuration::SILENT - || ($env == Configuration::PRODUCTION - && ($level == Minz_Log::WARNING || $level == Minz_Log::NOTICE)))) { - if (is_null ($file_name)) { - $file_name = LOG_PATH . '/application.log'; + $env = Minz_Configuration::environment (); + + if (! ($env === Minz_Configuration::SILENT + || ($env === Minz_Configuration::PRODUCTION + && ($level >= Minz_Log::NOTICE)))) { + if ($file_name === null) { + $file_name = LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log'; } - + switch ($level) { case Minz_Log::ERROR : $level_label = 'error'; @@ -48,30 +50,19 @@ class Minz_Log { case Minz_Log::NOTICE : $level_label = 'notice'; break; + case Minz_Log::DEBUG : + $level_label = 'debug'; + break; default : $level_label = 'unknown'; } - - if ($env == Configuration::PRODUCTION) { - $file = @fopen ($file_name, 'a'); - } else { - $file = fopen ($file_name, 'a'); - } - - if ($file !== false) { - $log = '[' . date('r') . ']'; - $log .= ' [' . $level_label . ']'; - $log .= ' --- ' . $information . "\n"; - fwrite ($file, $log); - fclose ($file); - } else { - Error::error ( - 500, - array ('error' => array ( - 'Permission is denied for `' - . $file_name . '`') - ) - ); + + $log = '[' . date('r') . ']' + . ' [' . $level_label . ']' + . ' --- ' . $information . "\n"; + + if (file_put_contents($file_name, $log, FILE_APPEND | LOCK_EX) === false) { + throw new Minz_PermissionDeniedException($file_name, Minz_Exception::ERROR); } } } @@ -86,7 +77,24 @@ class Minz_Log { $msg_get = str_replace("\n", '', '$_GET content : ' . print_r($_GET, true)); $msg_post = str_replace("\n", '', '$_POST content : ' . print_r($_POST, true)); - self::record($msg_get, Minz_Log::NOTICE, $file_name); - self::record($msg_post, Minz_Log::NOTICE, $file_name); + self::record($msg_get, Minz_Log::DEBUG, $file_name); + self::record($msg_post, Minz_Log::DEBUG, $file_name); + } + + /** + * Some helpers to Minz_Log::record() method + * Parameters are the same of those of the record() method. + */ + public static function debug($msg, $file_name = null) { + self::record($msg, Minz_Log::DEBUG, $file_name); + } + public static function notice($msg, $file_name = null) { + self::record($msg, Minz_Log::NOTICE, $file_name); + } + public static function warning($msg, $file_name = null) { + self::record($msg, Minz_Log::WARNING, $file_name); + } + public static function error($msg, $file_name = null) { + self::record($msg, Minz_Log::ERROR, $file_name); } } diff --git a/lib/minz/Model.php b/lib/Minz/Model.php index 37fc19ed1..adbaba942 100755..100644 --- a/lib/minz/Model.php +++ b/lib/Minz/Model.php @@ -7,6 +7,6 @@ /** * La classe Model représente un modèle de l'application (représentation MVC) */ -class Model { +class Minz_Model { } diff --git a/lib/Minz/ModelArray.php b/lib/Minz/ModelArray.php new file mode 100644 index 000000000..ff23dbc83 --- /dev/null +++ b/lib/Minz/ModelArray.php @@ -0,0 +1,81 @@ +<?php +/** + * MINZ - Copyright 2011 Marien Fressinaud + * Sous licence AGPL3 <http://www.gnu.org/licenses/> +*/ + +/** + * La classe Model_array représente le modèle interragissant avec les fichiers de type texte gérant des tableaux php + */ +class Minz_ModelArray { + /** + * $filename est le nom du fichier + */ + protected $filename; + + /** + * Ouvre le fichier indiqué, charge le tableau dans $array et le $filename + * @param $filename le nom du fichier à ouvrir contenant un tableau + * Remarque : $array sera obligatoirement un tableau + */ + public function __construct ($filename) { + $this->filename = $filename; + } + + protected function loadArray() { + if (!file_exists($this->filename)) { + throw new Minz_FileNotExistException($this->filename, Minz_Exception::WARNING); + } + elseif (($handle = $this->getLock()) === false) { + throw new Minz_PermissionDeniedException($this->filename); + } else { + $data = include($this->filename); + $this->releaseLock($handle); + + if ($data === false) { + throw new Minz_PermissionDeniedException($this->filename); + } elseif (!is_array($data)) { + $data = array(); + } + return $data; + } + } + + /** + * Sauve le tableau $array dans le fichier $filename + **/ + protected function writeArray($array) { + if (file_put_contents($this->filename, "<?php\n return " . var_export($array, true) . ';', LOCK_EX) === false) { + throw new Minz_PermissionDeniedException($this->filename); + } + if (function_exists('opcache_invalidate')) { + opcache_invalidate($this->filename); //Clear PHP 5.5+ cache for include + } + return true; + } + + private function getLock() { + $handle = fopen($this->filename, 'r'); + if ($handle === false) { + return false; + } + + $count = 50; + while (!flock($handle, LOCK_SH) && $count > 0) { + $count--; + usleep(1000); + } + + if ($count > 0) { + return $handle; + } else { + fclose($handle); + return false; + } + } + + private function releaseLock($handle) { + flock($handle, LOCK_UN); + fclose($handle); + } +} diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php new file mode 100644 index 000000000..b4bfca746 --- /dev/null +++ b/lib/Minz/ModelPdo.php @@ -0,0 +1,124 @@ +<?php +/** + * MINZ - Copyright 2011 Marien Fressinaud + * Sous licence AGPL3 <http://www.gnu.org/licenses/> +*/ + +/** + * La classe Model_sql représente le modèle interragissant avec les bases de données + * Seul la connexion MySQL est prise en charge pour le moment + */ +class Minz_ModelPdo { + + /** + * Partage la connexion à la base de données entre toutes les instances. + */ + public static $useSharedBd = true; + private static $sharedBd = null; + private static $sharedPrefix; + protected static $sharedDbType; + + /** + * $bd variable représentant la base de données + */ + protected $bd; + + protected $prefix; + + public function dbType() { + return self::$sharedDbType; + } + + /** + * Créé la connexion à la base de données à l'aide des variables + * HOST, BASE, USER et PASS définies dans le fichier de configuration + */ + public function __construct($currentUser = null) { + if (self::$useSharedBd && self::$sharedBd != null && $currentUser === null) { + $this->bd = self::$sharedBd; + $this->prefix = self::$sharedPrefix; + return; + } + + $db = Minz_Configuration::dataBase(); + + if ($currentUser === null) { + $currentUser = Minz_Session::param('currentUser', '_'); + } + + try { + $type = $db['type']; + if ($type === 'mysql') { + $string = 'mysql:host=' . $db['host'] + . ';dbname=' . $db['base'] + . ';charset=utf8'; + $driver_options = array( + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', + ); + $this->prefix = $db['prefix'] . $currentUser . '_'; + } elseif ($type === 'sqlite') { + $string = 'sqlite:' . DATA_PATH . '/' . $currentUser . '.sqlite'; + $driver_options = array( + //PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + ); + $this->prefix = ''; + } else { + throw new Minz_PDOConnectionException( + 'Invalid database type!', + $db['user'], Minz_Exception::ERROR + ); + } + self::$sharedDbType = $type; + self::$sharedPrefix = $this->prefix; + + $this->bd = new MinzPDO( + $string, + $db['user'], + $db['password'], + $driver_options + ); + if ($type === 'sqlite') { + $this->bd->exec('PRAGMA foreign_keys = ON;'); + } + self::$sharedBd = $this->bd; + } catch (Exception $e) { + throw new Minz_PDOConnectionException( + $string, + $db['user'], Minz_Exception::ERROR + ); + } + } + + public function beginTransaction() { + $this->bd->beginTransaction(); + } + public function commit() { + $this->bd->commit(); + } + public function rollBack() { + $this->bd->rollBack(); + } + + public static function clean() { + self::$sharedBd = null; + self::$sharedPrefix = ''; + } +} + +class MinzPDO extends PDO { + private static function check($statement) { + if (preg_match('/^(?:UPDATE|INSERT|DELETE)/i', $statement)) { + invalidateHttpCache(); + } + } + + public function prepare($statement, $driver_options = array()) { + MinzPDO::check($statement); + return parent::prepare($statement, $driver_options); + } + + public function exec($statement) { + MinzPDO::check($statement); + return parent::exec($statement); + } +} diff --git a/lib/Minz/PDOConnectionException.php b/lib/Minz/PDOConnectionException.php new file mode 100644 index 000000000..faf2e0fe9 --- /dev/null +++ b/lib/Minz/PDOConnectionException.php @@ -0,0 +1,9 @@ +<?php +class Minz_PDOConnectionException extends Minz_Exception { + public function __construct ($string_connection, $user, $code = self::ERROR) { + $message = 'Access to database is denied for `' . $user . '`' + . ' (`' . $string_connection . '`)'; + + parent::__construct ($message, $code); + } +} diff --git a/lib/minz/Paginator.php b/lib/Minz/Paginator.php index 1a8376e75..5858e76a5 100755..100644 --- a/lib/minz/Paginator.php +++ b/lib/Minz/Paginator.php @@ -7,7 +7,7 @@ /** * La classe Paginator permet de gérer la pagination de l'application facilement */ -class Paginator { +class Minz_Paginator { /** * $items tableau des éléments à afficher/gérer */ diff --git a/lib/Minz/PermissionDeniedException.php b/lib/Minz/PermissionDeniedException.php new file mode 100644 index 000000000..61be530d3 --- /dev/null +++ b/lib/Minz/PermissionDeniedException.php @@ -0,0 +1,8 @@ +<?php +class Minz_PermissionDeniedException extends Minz_Exception { + public function __construct ($file_name, $code = self::ERROR) { + $message = 'Permission is denied for `' . $file_name.'`'; + + parent::__construct ($message, $code); + } +} diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php new file mode 100644 index 000000000..f7a24c026 --- /dev/null +++ b/lib/Minz/Request.php @@ -0,0 +1,227 @@ +<?php +/** + * MINZ - Copyright 2011 Marien Fressinaud + * Sous licence AGPL3 <http://www.gnu.org/licenses/> +*/ + +/** + * Request représente la requête http + */ +class Minz_Request { + private static $controller_name = ''; + private static $action_name = ''; + private static $params = array(); + + private static $default_controller_name = 'index'; + private static $default_action_name = 'index'; + + /** + * Getteurs + */ + public static function controllerName() { + return self::$controller_name; + } + public static function actionName() { + return self::$action_name; + } + public static function params() { + return self::$params; + } + public static function param($key, $default = false, $specialchars = false) { + if (isset(self::$params[$key])) { + $p = self::$params[$key]; + if (is_object($p) || $specialchars) { + return $p; + } else { + return Minz_Helper::htmlspecialchars_utf8($p); + } + } else { + return $default; + } + } + public static function defaultControllerName() { + return self::$default_controller_name; + } + public static function defaultActionName() { + return self::$default_action_name; + } + + /** + * Setteurs + */ + public static function _controllerName($controller_name) { + self::$controller_name = $controller_name; + } + public static function _actionName($action_name) { + self::$action_name = $action_name; + } + public static function _params($params) { + if (!is_array($params)) { + $params = array($params); + } + + self::$params = $params; + } + public static function _param($key, $value = false) { + if ($value === false) { + unset(self::$params[$key]); + } else { + self::$params[$key] = $value; + } + } + + /** + * Initialise la Request + */ + public static function init() { + self::magicQuotesOff(); + } + + /** + * Retourn le nom de domaine du site + */ + public static function getDomainName() { + return $_SERVER['HTTP_HOST']; + } + + /** + * Détermine la base de l'url + * @return la base de l'url + */ + public static function getBaseUrl() { + $defaultBaseUrl = Minz_Configuration::baseUrl(); + if (!empty($defaultBaseUrl)) { + return $defaultBaseUrl; + } elseif (isset($_SERVER['REQUEST_URI'])) { + return dirname($_SERVER['REQUEST_URI']) . '/'; + } else { + return '/'; + } + } + + /** + * Récupère l'URI de la requête + * @return l'URI + */ + public static function getURI() { + if (isset($_SERVER['REQUEST_URI'])) { + $base_url = self::getBaseUrl(); + $uri = $_SERVER['REQUEST_URI']; + + $len_base_url = strlen($base_url); + $real_uri = substr($uri, $len_base_url); + } else { + $real_uri = ''; + } + + return $real_uri; + } + + /** + * Relance une requête + * @param $url l'url vers laquelle est relancée la requête + * @param $redirect si vrai, force la redirection http + * > sinon, le dispatcher recharge en interne + */ + public static function forward($url = array(), $redirect = false) { + if (!is_array($url)) { + header('Location: ' . $url); + exit(); + } + + $url = Minz_Url::checkUrl($url); + + if ($redirect) { + header('Location: ' . Minz_Url::display($url, 'php')); + exit(); + } else { + self::_controllerName($url['c']); + self::_actionName($url['a']); + self::_params(array_merge( + self::$params, + $url['params'] + )); + Minz_Dispatcher::reset(); + } + } + + + /** + * Wrappers good notifications + redirection + * @param $msg notification content + * @param $url url array to where we should be forwarded + */ + public static function good($msg, $url = array()) { + Minz_Session::_param('notification', array( + 'type' => 'good', + 'content' => $msg + )); + + Minz_Request::forward($url, true); + } + + public static function bad($msg, $url = array()) { + Minz_Session::_param('notification', array( + 'type' => 'bad', + 'content' => $msg + )); + + Minz_Request::forward($url, true); + } + + + /** + * Permet de récupérer une variable de type $_GET + * @param $param nom de la variable + * @param $default valeur par défaut à attribuer à la variable + * @return $_GET[$param] + * $_GET si $param = false + * $default si $_GET[$param] n'existe pas + */ + public static function fetchGET($param = false, $default = false) { + if ($param === false) { + return $_GET; + } elseif (isset($_GET[$param])) { + return $_GET[$param]; + } else { + return $default; + } + } + + /** + * Permet de récupérer une variable de type $_POST + * @param $param nom de la variable + * @param $default valeur par défaut à attribuer à la variable + * @return $_POST[$param] + * $_POST si $param = false + * $default si $_POST[$param] n'existe pas + */ + public static function fetchPOST($param = false, $default = false) { + if ($param === false) { + return $_POST; + } elseif (isset($_POST[$param])) { + return $_POST[$param]; + } else { + return $default; + } + } + + /** + * Méthode désactivant les magic_quotes pour les variables + * $_GET + * $_POST + * $_COOKIE + */ + private static function magicQuotesOff() { + if (get_magic_quotes_gpc()) { + $_GET = Minz_Helper::stripslashes_r($_GET); + $_POST = Minz_Helper::stripslashes_r($_POST); + $_COOKIE = Minz_Helper::stripslashes_r($_COOKIE); + } + } + + public static function isPost() { + return isset($_SERVER['REQUEST_METHOD']) && + $_SERVER['REQUEST_METHOD'] === 'POST'; + } +} diff --git a/lib/Minz/Session.php b/lib/Minz/Session.php new file mode 100644 index 000000000..af4de75bb --- /dev/null +++ b/lib/Minz/Session.php @@ -0,0 +1,93 @@ +<?php + +/** + * La classe Session gère la session utilisateur + */ +class Minz_Session { + /** + * Initialise la session, avec un nom + * Le nom de session est utilisé comme nom pour les cookies et les URLs(i.e. PHPSESSID). + * Il ne doit contenir que des caractères alphanumériques ; il doit être court et descriptif + */ + public static function init($name) { + $cookie = session_get_cookie_params(); + self::keepCookie($cookie['lifetime']); + + // démarre la session + session_name($name); + session_start(); + } + + + /** + * Permet de récupérer une variable de session + * @param $p le paramètre à récupérer + * @return la valeur de la variable de session, false si n'existe pas + */ + public static function param($p, $default = false) { + return isset($_SESSION[$p]) ? $_SESSION[$p] : $default; + } + + + /** + * Permet de créer ou mettre à jour une variable de session + * @param $p le paramètre à créer ou modifier + * @param $v la valeur à attribuer, false pour supprimer + */ + public static function _param($p, $v = false) { + if ($v === false) { + unset($_SESSION[$p]); + } else { + $_SESSION[$p] = $v; + } + } + + + /** + * Permet d'effacer une session + * @param $force si à false, n'efface pas le paramètre de langue + */ + public static function unset_session($force = false) { + $language = self::param('language'); + + session_destroy(); + $_SESSION = array(); + + if (!$force) { + self::_param('language', $language); + Minz_Translate::reset(); + } + } + + + /** + * Spécifie la durée de vie des cookies + * @param $l la durée de vie + */ + public static function keepCookie($l) { + $cookie_dir = empty($_SERVER['REQUEST_URI']) ? '' : $_SERVER['REQUEST_URI']; + session_set_cookie_params($l, $cookie_dir, '', false, true); + } + + + /** + * Régénère un id de session. + * Utile pour appeler session_set_cookie_params après session_start() + */ + public static function regenerateID() { + session_regenerate_id(true); + } + + public static function deleteLongTermCookie($name) { + setcookie($name, '', 1, '', '', false, true); + } + + public static function setLongTermCookie($name, $value, $expire) { + setcookie($name, $value, $expire, '', '', false, true); + } + + public static function getLongTermCookie($name) { + return isset($_COOKIE[$name]) ? $_COOKIE[$name] : null; + } + +} diff --git a/lib/minz/Translate.php b/lib/Minz/Translate.php index e8cbe4852..8c2f90041 100644 --- a/lib/minz/Translate.php +++ b/lib/Minz/Translate.php @@ -8,7 +8,7 @@ * La classe Translate se charge de la traduction * Utilise les fichiers du répertoire /app/i18n/ */ -class Translate { +class Minz_Translate { /** * $language est la langue à afficher */ @@ -18,28 +18,28 @@ class Translate { * $translates est le tableau de correspondance * $key => $traduction */ - private static $translates = array (); + private static $translates = array(); /** * Inclus le fichier de langue qui va bien * l'enregistre dans $translates */ - public static function init () { - $l = Configuration::language (); - self::$language = Session::param ('language', $l); + public static function init() { + $l = Minz_Configuration::language(); + self::$language = Minz_Session::param('language', $l); $l_path = APP_PATH . '/i18n/' . self::$language . '.php'; - if (file_exists ($l_path)) { - self::$translates = include ($l_path); + if (file_exists($l_path)) { + self::$translates = include($l_path); } } /** * Alias de init */ - public static function reset () { - self::init (); + public static function reset() { + self::init(); } /** @@ -48,24 +48,32 @@ class Translate { * @return la valeur correspondante à la clé * > si non présente dans le tableau, on retourne la clé elle-même */ - public static function t ($key) { + public static function t($key) { $translate = $key; - if (isset (self::$translates[$key])) { + if (isset(self::$translates[$key])) { $translate = self::$translates[$key]; } - $args = func_get_args (); + $args = func_get_args(); unset($args[0]); - return vsprintf ($translate, $args); + return vsprintf($translate, $args); } /** * Retourne la langue utilisée actuellement * @return la langue */ - public static function language () { + public static function language() { return self::$language; } } + +function _t($key) { + $args = func_get_args(); + unset($args[0]); + array_unshift($args, $key); + + return call_user_func_array('Minz_Translate::t', $args); +} diff --git a/lib/minz/Url.php b/lib/Minz/Url.php index c1c3e9a0f..e9f9a69ba 100755..100644 --- a/lib/minz/Url.php +++ b/lib/Minz/Url.php @@ -3,10 +3,9 @@ /** * La classe Url permet de gérer les URL à travers MINZ */ -class Url { +class Minz_Url { /** - * Affiche une Url formatée selon que l'on utilise l'url_rewriting ou non - * si oui, on cherche dans la table de routage la correspondance pour formater + * Affiche une Url formatée * @param $url l'url à formater définie comme un tableau : * $url['c'] = controller * $url['a'] = action @@ -16,50 +15,46 @@ class Url { * @param $encodage pour indiquer comment encoder les & (& ou & pour html) * @return l'url formatée */ - public static function display ($url = array (), $encodage = 'html') { - $url = self::checkUrl ($url); - + public static function display ($url = array (), $encodage = 'html', $absolute = false) { + $isArray = is_array($url); + + if ($isArray) { + $url = self::checkUrl ($url); + } + $url_string = ''; - - if (is_array ($url) && isset ($url['protocol'])) { - $protocol = $url['protocol']; - } else { - if(isset($_SERVER['HTTPS']) && $_SERVER["HTTPS"] == 'on') { - $protocol = 'https'; + + if ($absolute) { + if ($isArray && isset ($url['protocol'])) { + $protocol = $url['protocol']; + } elseif (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') { + $protocol = 'https:'; } else { - $protocol = 'http'; + $protocol = 'http:'; } + $url_string = $protocol . '//' . Minz_Request::getDomainName () . Minz_Request::getBaseUrl (); + } else { + $url_string = $isArray ? '.' : PUBLIC_RELATIVE; } - $url_string .= $protocol . '://'; - - $url_string .= Request::getDomainName (); - - $url_string .= Request::getBaseUrl (); - - if (is_array ($url)) { - $router = new Router (); - - if (Configuration::useUrlRewriting ()) { - $url_string .= $router->printUriRewrited ($url); - } else { - $url_string .= self::printUri ($url, $encodage); - } + + if ($isArray) { + $url_string .= self::printUri ($url, $encodage); } else { $url_string .= $url; } - + return $url_string; } /** - * Construit l'URI d'une URL sans url rewriting + * Construit l'URI d'une URL * @param l'url sous forme de tableau * @param $encodage pour indiquer comment encoder les & (& ou & pour html) * @return l'uri sous la forme ?key=value&key2=value2 */ private static function printUri ($url, $encodage) { $uri = ''; - $separator = '/?'; + $separator = '?'; if($encodage == 'html') { $and = '&'; @@ -68,13 +63,13 @@ class Url { } if (isset ($url['c']) - && $url['c'] != Request::defaultControllerName ()) { + && $url['c'] != Minz_Request::defaultControllerName ()) { $uri .= $separator . 'c=' . $url['c']; $separator = $and; } if (isset ($url['a']) - && $url['a'] != Request::defaultActionName ()) { + && $url['a'] != Minz_Request::defaultActionName ()) { $uri .= $separator . 'a=' . $url['a']; $separator = $and; } @@ -99,10 +94,10 @@ class Url { if (is_array ($url)) { if (!isset ($url['c'])) { - $url_checked['c'] = Request::defaultControllerName (); + $url_checked['c'] = Minz_Request::defaultControllerName (); } if (!isset ($url['a'])) { - $url_checked['a'] = Request::defaultActionName (); + $url_checked['a'] = Minz_Request::defaultActionName (); } if (!isset ($url['params'])) { $url_checked['params'] = array (); @@ -126,5 +121,5 @@ function _url ($controller, $action) { $params[$args[$i]] = $args[$i + 1]; } - return Url::display (array ('c' => $controller, 'a' => $action, 'params' => $params)); + return Minz_Url::display (array ('c' => $controller, 'a' => $action, 'params' => $params)); } diff --git a/lib/minz/View.php b/lib/Minz/View.php index 1bfa0a61f..a0dec1824 100755..100644 --- a/lib/minz/View.php +++ b/lib/Minz/View.php @@ -7,13 +7,13 @@ /** * La classe View représente la vue de l'application */ -class View { +class Minz_View { const VIEWS_PATH_NAME = '/views'; const LAYOUT_PATH_NAME = '/layout'; const LAYOUT_FILENAME = '/layout.phtml'; private $view_filename = ''; - private $use_layout = false; + private $use_layout = null; private static $title = ''; private static $styles = array (); @@ -26,24 +26,28 @@ class View { * Détermine si on utilise un layout ou non */ public function __construct () { + $this->change_view(Minz_Request::controllerName(), + Minz_Request::actionName()); + self::$title = Minz_Configuration::title (); + } + + /** + * Change le fichier de vue en fonction d'un controller / action + */ + public function change_view($controller_name, $action_name) { $this->view_filename = APP_PATH . self::VIEWS_PATH_NAME . '/' - . Request::controllerName () . '/' - . Request::actionName () . '.phtml'; - - if (file_exists (APP_PATH - . self::LAYOUT_PATH_NAME - . self::LAYOUT_FILENAME)) { - $this->use_layout = true; - } - - self::$title = Configuration::title (); + . $controller_name . '/' + . $action_name . '.phtml'; } /** * Construit la vue */ public function build () { + if ($this->use_layout === null) { //TODO: avoid file_exists and require views to be explicit + $this->use_layout = file_exists (APP_PATH . self::LAYOUT_PATH_NAME . self::LAYOUT_FILENAME); + } if ($this->use_layout) { $this->buildLayout (); } else { @@ -66,10 +70,8 @@ class View { * Affiche la Vue en elle-même */ public function render () { - if (file_exists ($this->view_filename)) { - include ($this->view_filename); - } else { - Minz_Log::record ('File doesn\'t exist : `' + if ((include($this->view_filename)) === false) { + Minz_Log::record ('File not found: `' . $this->view_filename . '`', Minz_Log::NOTICE); } @@ -84,10 +86,8 @@ class View { . self::LAYOUT_PATH_NAME . '/' . $part . '.phtml'; - if (file_exists ($fic_partial)) { - include ($fic_partial); - } else { - Minz_Log::record ('File doesn\'t exist : `' + if ((include($fic_partial)) === false) { + Minz_Log::record ('File not found: `' . $fic_partial . '`', Minz_Log::WARNING); } @@ -102,16 +102,24 @@ class View { . '/views/helpers/' . $helper . '.phtml'; - if (file_exists ($fic_helper)) { - include ($fic_helper); - } else { - Minz_Log::record ('File doesn\'t exist : `' + if ((include($fic_helper)) === false) {; + Minz_Log::record ('File not found: `' . $fic_helper . '`', Minz_Log::WARNING); } } /** + * Retourne renderHelper() dans une chaîne + * @param $helper l'élément à traîter + */ + public function helperToString($helper) { + ob_start(); + $this->renderHelper($helper); + return ob_get_clean(); + } + + /** * Permet de choisir si on souhaite utiliser le layout * @param $use true si on souhaite utiliser le layout, false sinon */ @@ -150,9 +158,9 @@ class View { $styles .= '<!--[if ' . $cond . ']>'; } - $styles .= '<link rel="stylesheet" type="text/css"'; - $styles .= ' media="' . $style['media'] . '"'; - $styles .= ' href="' . $style['url'] . '" />'; + $styles .= '<link rel="stylesheet" ' . + ($style['media'] === 'all' ? '' : 'media="' . $style['media'] . '" ') . + 'href="' . $style['url'] . '" />'; if ($cond) { $styles .= '<![endif]-->'; @@ -190,9 +198,14 @@ class View { $scripts .= '<!--[if ' . $cond . ']>'; } - $scripts .= '<script type="text/javascript"'; - $scripts .= ' src="' . $script['url'] . '">'; - $scripts .= '</script>'; + $scripts .= '<script src="' . $script['url'] . '"'; + if ($script['defer']) { + $scripts .= ' defer="defer"'; + } + if ($script['async']) { + $scripts .= ' async="async"'; + } + $scripts .= '></script>'; if ($cond) { $scripts .= '<![endif]-->'; @@ -203,16 +216,20 @@ class View { return $scripts; } - public static function prependScript ($url, $cond = false) { + public static function prependScript ($url, $cond = false, $defer = true, $async = true) { array_unshift(self::$scripts, array ( 'url' => $url, - 'cond' => $cond + 'cond' => $cond, + 'defer' => $defer, + 'async' => $async, )); } - public static function appendScript ($url, $cond = false) { + public static function appendScript ($url, $cond = false, $defer = true, $async = true) { self::$scripts[] = array ( 'url' => $url, - 'cond' => $cond + 'cond' => $cond, + 'defer' => $defer, + 'async' => $async, ); } @@ -223,7 +240,7 @@ class View { self::$params[$key] = $value; } public function attributeParams () { - foreach (View::$params as $key => $value) { + foreach (Minz_View::$params as $key => $value) { $this->$key = $value; } } diff --git a/lib/SimplePie/SimplePie.php b/lib/SimplePie/SimplePie.php index b33c635f1..06c100f59 100644 --- a/lib/SimplePie/SimplePie.php +++ b/lib/SimplePie/SimplePie.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev-FreshRSS * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -50,7 +50,7 @@ define('SIMPLEPIE_NAME', 'SimplePie'); /** * SimplePie Version */ -define('SIMPLEPIE_VERSION', '1.3.1'); +define('SIMPLEPIE_VERSION', '1.4-dev-FreshRSS'); /** * SimplePie Build @@ -446,6 +446,13 @@ class SimplePie public $feed_url; /** + * @var string Original feed URL, or new feed URL iff HTTP 301 Moved Permanently + * @see SimplePie::subscribe_url() + * @access private + */ + public $permanent_url = null; //FreshRSS + + /** * @var object Instance of SimplePie_File to use as a feed * @see SimplePie::set_file() * @access private @@ -602,6 +609,13 @@ class SimplePie public $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'); /** + * @var array Stores the default attributes to add to different tags by add_attributes(). + * @see SimplePie::add_attributes() + * @access private + */ + public $add_attributes = array('audio' => array('preload' => 'none'), 'iframe' => array('sandbox' => 'allow-scripts allow-same-origin'), 'video' => array('preload' => 'none')); //FreshRSS + + /** * @var array Stores the default tags to be stripped by strip_htmltags(). * @see SimplePie::strip_htmltags() * @access private @@ -637,7 +651,7 @@ class SimplePie if (func_num_args() > 0) { $level = defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_WARNING; - trigger_error('Passing parameters to the constructor is no longer supported. Please use set_feed_url(), set_cache_location(), and set_cache_location() directly.', $level); + trigger_error('Passing parameters to the constructor is no longer supported. Please use set_feed_url(), set_cache_location(), and set_cache_duration() directly.', $level); $args = func_get_args(); switch (count($args)) { @@ -728,6 +742,7 @@ class SimplePie else { $this->feed_url = $this->registry->call('Misc', 'fix_protocol', array($url, 1)); + $this->permanent_url = $this->feed_url; //FreshRSS } } @@ -742,6 +757,7 @@ class SimplePie if ($file instanceof SimplePie_File) { $this->feed_url = $file->url; + $this->permanent_url = $this->feed_url; //FreshRSS $this->file =& $file; return true; } @@ -1073,6 +1089,7 @@ class SimplePie $this->strip_comments(false); $this->strip_htmltags(false); $this->strip_attributes(false); + $this->add_attributes(false); $this->set_image_handler(false); } } @@ -1119,6 +1136,15 @@ class SimplePie $this->sanitize->strip_attributes($attribs); } + public function add_attributes($attribs = '') + { + if ($attribs === '') + { + $attribs = $this->add_attributes; + } + $this->sanitize->add_attributes($attribs); + } + /** * Set the output encoding * @@ -1196,13 +1222,27 @@ class SimplePie } /** + * Enable throwing exceptions + * + * @param boolean $enable Should we throw exceptions, or use the old-style error property? + */ + public function enable_exceptions($enable = true) + { + $this->enable_exceptions = $enable; + } + + function cleanMd5($rss) { //FreshRSS + return md5(preg_replace(array('#<(lastBuildDate|pubDate|updated|feedDate|dc:date|slash:comments)>[^<]+</\\1>#', '#<!--.+?-->#s'), '', $rss)); + } + + /** * Initialize the feed object * * This is what makes everything happen. Period. This is where all of the * configuration options get processed, feeds are fetched, cached, and * parsed, and all of that other good stuff. * - * @return boolean True if successful, false otherwise + * @return positive integer with modification time if using cache, boolean true if otherwise successful, false otherwise */ public function init() { @@ -1281,13 +1321,17 @@ class SimplePie // Fetch the data via SimplePie_File into $this->raw_data if (($fetched = $this->fetch_data($cache)) === true) { - return true; + return $this->data['mtime']; //FreshRSS } elseif ($fetched === false) { return false; } list($headers, $sniffed) = $fetched; + + if (isset($this->data['md5'])) { //FreshRSS + $md5 = $this->data['md5']; + } } // Set up array of possible encodings @@ -1296,7 +1340,7 @@ class SimplePie // First check to see if input has been overridden. if ($this->input_encoding !== false) { - $encodings[] = $this->input_encoding; + $encodings[] = strtoupper($this->input_encoding); } $application_types = array('application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity'); @@ -1311,14 +1355,20 @@ class SimplePie { $encodings[] = strtoupper($charset[1]); } - $encodings = array_merge($encodings, $this->registry->call('Misc', 'xml_encoding', array($this->raw_data, &$this->registry))); - $encodings[] = 'UTF-8'; + else + { + $encodings[] = ''; //FreshRSS: Let the DOM parser decide first + } } elseif (in_array($sniffed, $text_types) || substr($sniffed, 0, 5) === 'text/' && substr($sniffed, -4) === '+xml') { if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset)) { - $encodings[] = $charset[1]; + $encodings[] = strtoupper($charset[1]); + } + else + { + $encodings[] = ''; //FreshRSS: Let the DOM parser decide first } $encodings[] = 'US-ASCII'; } @@ -1341,13 +1391,14 @@ class SimplePie foreach ($encodings as $encoding) { // Change the encoding to UTF-8 (as we always use UTF-8 internally) - if ($utf8_data = $this->registry->call('Misc', 'change_encoding', array($this->raw_data, $encoding, 'UTF-8'))) + if ($utf8_data = (empty($encoding) || $encoding === 'UTF-8') ? $this->raw_data : //FreshRSS + $this->registry->call('Misc', 'change_encoding', array($this->raw_data, $encoding, 'UTF-8'))) { // Create new parser $parser = $this->registry->create('Parser'); // If it's parsed fine - if ($parser->parse($utf8_data, 'UTF-8')) + if ($parser->parse($utf8_data, empty($encoding) ? '' : 'UTF-8')) //FreshRSS { $this->data = $parser->get_data(); if (!($this->get_type() & ~SIMPLEPIE_TYPE_NONE)) @@ -1362,6 +1413,8 @@ class SimplePie $this->data['headers'] = $headers; } $this->data['build'] = SIMPLEPIE_BUILD; + $this->data['mtime'] = time(); //FreshRSS + $this->data['md5'] = empty($md5) ? $this->cleanMd5($this->raw_data) : $md5; //FreshRSS // Cache the file if caching is enabled if ($cache && !$cache->save($this)) @@ -1437,7 +1490,7 @@ class SimplePie elseif ($cache->mtime() + $this->cache_duration < time()) { // If we have last-modified and/or etag set - if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag'])) + //if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag'])) //FreshRSS removed { $headers = array( 'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1', @@ -1451,7 +1504,7 @@ class SimplePie $headers['if-none-match'] = $this->data['headers']['etag']; } - $file = $this->registry->create('File', array($this->feed_url, $this->timeout/10, 5, $headers, $this->useragent, $this->force_fsockopen)); + $file = $this->registry->create('File', array($this->feed_url, $this->timeout, 5, $headers, $this->useragent, $this->force_fsockopen)); //FreshRSS if ($file->success) { @@ -1463,7 +1516,20 @@ class SimplePie } else { - unset($file); + $this->error = $file->error; //FreshRSS + return !empty($this->data); //FreshRSS + //unset($file); //FreshRSS removed + } + } + { //FreshRSS + $md5 = $this->cleanMd5($file->body); + if ($this->data['md5'] === $md5) { + syslog(LOG_DEBUG, 'SimplePie MD5 cache match for ' . $this->feed_url); + $cache->touch(); + return true; //Content unchanged even though server did not send a 304 + } else { + syslog(LOG_DEBUG, 'SimplePie MD5 cache no match for ' . $this->feed_url); + $this->data['md5'] = $md5; } } } @@ -1531,6 +1597,8 @@ class SimplePie if ($cache) { $this->data = array('url' => $this->feed_url, 'feed_url' => $file->url, 'build' => SIMPLEPIE_BUILD); + $this->data['mtime'] = time(); //FreshRSS + $this->data['md5'] = empty($md5) ? $this->cleanMd5($file->body) : $md5; //FreshRSS if (!$cache->save($this)) { trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); @@ -1543,7 +1611,7 @@ class SimplePie } $this->raw_data = $file->body; - + $this->permanent_url = $file->permanent_url; //FreshRSS $headers = $file->headers; $sniffer = $this->registry->create('Content_Type_Sniffer', array(&$file)); $sniffed = $sniffer->get_type(); @@ -1729,26 +1797,39 @@ class SimplePie /** * Get the URL for the feed + * + * When the 'permanent' mode is enabled, returns the original feed URL, + * except in the case of an `HTTP 301 Moved Permanently` status response, + * in which case the location of the first redirection is returned. * - * May or may not be different from the URL passed to {@see set_feed_url()}, + * When the 'permanent' mode is disabled (default), + * may or may not be different from the URL passed to {@see set_feed_url()}, * depending on whether auto-discovery was used. * * @since Preview Release (previously called `get_feed_url()` since SimplePie 0.8.) - * @todo If we have a perm redirect we should return the new URL - * @todo When we make the above change, let's support <itunes:new-feed-url> as well + * @todo Support <itunes:new-feed-url> * @todo Also, |atom:link|@rel=self + * @param bool $permanent Permanent mode to return only the original URL or the first redirection + * iff it is a 301 redirection * @return string|null */ - public function subscribe_url() + public function subscribe_url($permanent = false) { - if ($this->feed_url !== null) + if ($permanent) //FreshRSS { - return $this->sanitize($this->feed_url, SIMPLEPIE_CONSTRUCT_IRI); + if ($this->permanent_url !== null) + { + return $this->sanitize($this->permanent_url, SIMPLEPIE_CONSTRUCT_IRI); + } } else { - return null; + if ($this->feed_url !== null) + { + return $this->sanitize($this->feed_url, SIMPLEPIE_CONSTRUCT_IRI); + } } + return null; } /** @@ -1963,7 +2044,21 @@ class SimplePie */ public function sanitize($data, $type, $base = '') { - return $this->sanitize->sanitize($data, $type, $base); + try + { + return $this->sanitize->sanitize($data, $type, $base); + } + catch (SimplePie_Exception $e) + { + if (!$this->enable_exceptions) + { + $this->error = $e->getMessage(); + $this->registry->call('Misc', 'error', array($this->error, E_USER_WARNING, $e->getFile(), $e->getLine())); + return ''; + } + + throw $e; + } } /** diff --git a/lib/SimplePie/SimplePie/Author.php b/lib/SimplePie/SimplePie/Author.php index bbf3812ff..19563c5cc 100644 --- a/lib/SimplePie/SimplePie/Author.php +++ b/lib/SimplePie/SimplePie/Author.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Cache.php b/lib/SimplePie/SimplePie/Cache.php index 75586d749..86b618693 100644 --- a/lib/SimplePie/SimplePie/Cache.php +++ b/lib/SimplePie/SimplePie/Cache.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Cache/Base.php b/lib/SimplePie/SimplePie/Cache/Base.php index 937e34631..d3f353961 100644 --- a/lib/SimplePie/SimplePie/Cache/Base.php +++ b/lib/SimplePie/SimplePie/Cache/Base.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Cache/DB.php b/lib/SimplePie/SimplePie/Cache/DB.php index ac509ae08..d728a9a6d 100644 --- a/lib/SimplePie/SimplePie/Cache/DB.php +++ b/lib/SimplePie/SimplePie/Cache/DB.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Cache/File.php b/lib/SimplePie/SimplePie/Cache/File.php index 5797b3aed..3b163545b 100644 --- a/lib/SimplePie/SimplePie/Cache/File.php +++ b/lib/SimplePie/SimplePie/Cache/File.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Cache/Memcache.php b/lib/SimplePie/SimplePie/Cache/Memcache.php index fd4478060..23b1c9367 100644 --- a/lib/SimplePie/SimplePie/Cache/Memcache.php +++ b/lib/SimplePie/SimplePie/Cache/Memcache.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -95,10 +95,8 @@ class SimplePie_Cache_Memcache implements SimplePie_Cache_Base 'prefix' => 'simplepie_', ), ); - $parsed = SimplePie_Cache::parse_URL($location); - $this->options['host'] = empty($parsed['host']) ? $this->options['host'] : $parsed['host']; - $this->options['port'] = empty($parsed['port']) ? $this->options['port'] : $parsed['port']; - $this->options['extras'] = array_merge($this->options['extras'], $parsed['extras']); + $this->options = SimplePie_Misc::array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location)); + $this->name = $this->options['extras']['prefix'] . md5("$name:$type"); $this->cache = new Memcache(); @@ -147,7 +145,7 @@ class SimplePie_Cache_Memcache implements SimplePie_Cache_Base if ($data !== false) { - // essentially ignore the mtime because Memcache expires on it's own + // essentially ignore the mtime because Memcache expires on its own return time(); } @@ -165,7 +163,7 @@ class SimplePie_Cache_Memcache implements SimplePie_Cache_Base if ($data !== false) { - return $this->cache->set($this->name, $data, MEMCACHE_COMPRESSED, (int) $this->duration); + return $this->cache->set($this->name, $data, MEMCACHE_COMPRESSED, (int) $this->options['extras']['timeout']); } return false; diff --git a/lib/SimplePie/SimplePie/Cache/MySQL.php b/lib/SimplePie/SimplePie/Cache/MySQL.php index d53ebc117..511ef6d3a 100644 --- a/lib/SimplePie/SimplePie/Cache/MySQL.php +++ b/lib/SimplePie/SimplePie/Cache/MySQL.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -96,7 +96,8 @@ class SimplePie_Cache_MySQL extends SimplePie_Cache_DB 'prefix' => '', ), ); - $this->options = array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location)); + + $this->options = SimplePie_Misc::array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location)); // Path is prefixed with a "/" $this->options['dbname'] = substr($this->options['path'], 1); @@ -136,7 +137,7 @@ class SimplePie_Cache_MySQL extends SimplePie_Cache_DB if (!in_array($this->options['extras']['prefix'] . 'items', $db)) { - $query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` TEXT CHARACTER SET utf8 NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))'); + $query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` MEDIUMBLOB CHARACTER SET utf8 NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))'); if ($query === false) { $this->mysql = null; diff --git a/lib/SimplePie/SimplePie/Caption.php b/lib/SimplePie/SimplePie/Caption.php index 52922c5d9..a77b02ef1 100644 --- a/lib/SimplePie/SimplePie/Caption.php +++ b/lib/SimplePie/SimplePie/Caption.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Category.php b/lib/SimplePie/SimplePie/Category.php index ad0407b4e..c6a273989 100644 --- a/lib/SimplePie/SimplePie/Category.php +++ b/lib/SimplePie/SimplePie/Category.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Content/Type/Sniffer.php b/lib/SimplePie/SimplePie/Content/Type/Sniffer.php index 20d053dca..a32f47f59 100644 --- a/lib/SimplePie/SimplePie/Content/Type/Sniffer.php +++ b/lib/SimplePie/SimplePie/Content/Type/Sniffer.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Copyright.php b/lib/SimplePie/SimplePie/Copyright.php index 57c535a64..09f22f8df 100644 --- a/lib/SimplePie/SimplePie/Copyright.php +++ b/lib/SimplePie/SimplePie/Copyright.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Core.php b/lib/SimplePie/SimplePie/Core.php index 46d996628..7cf34876f 100644 --- a/lib/SimplePie/SimplePie/Core.php +++ b/lib/SimplePie/SimplePie/Core.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Credit.php b/lib/SimplePie/SimplePie/Credit.php index d3a3442ad..50aef1c68 100644 --- a/lib/SimplePie/SimplePie/Credit.php +++ b/lib/SimplePie/SimplePie/Credit.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Decode/HTML/Entities.php b/lib/SimplePie/SimplePie/Decode/HTML/Entities.php index 069e8d8e5..cde06c884 100644 --- a/lib/SimplePie/SimplePie/Decode/HTML/Entities.php +++ b/lib/SimplePie/SimplePie/Decode/HTML/Entities.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Enclosure.php b/lib/SimplePie/SimplePie/Enclosure.php index 55674379c..fa0217800 100644 --- a/lib/SimplePie/SimplePie/Enclosure.php +++ b/lib/SimplePie/SimplePie/Enclosure.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -942,7 +942,7 @@ class SimplePie_Enclosure * - `height` (integer): The height of the embedded media. Accepts any * numeric pixel value (such as `360`) or `auto`. Defaults to `auto`, * and it is recommended that you use this default. - * - `loop` (boolean): Do you want the media to loop when its done? + * - `loop` (boolean): Do you want the media to loop when it's done? * Defaults to `false`. * - `mediaplayer` (string): The location of the included * `mediaplayer.swf` file. This allows for the playback of Flash Video diff --git a/lib/SimplePie/SimplePie/File.php b/lib/SimplePie/SimplePie/File.php index 063ad955e..b1bbe4420 100644 --- a/lib/SimplePie/SimplePie/File.php +++ b/lib/SimplePie/SimplePie/File.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -64,6 +64,7 @@ class SimplePie_File var $redirects = 0; var $error; var $method = SIMPLEPIE_FILE_SOURCE_NONE; + var $permanent_url; //FreshRSS public function __construct($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false) { @@ -74,9 +75,11 @@ class SimplePie_File $url = SimplePie_Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']); } $this->url = $url; + $this->permanent_url = $url; //FreshRSS $this->useragent = $useragent; if (preg_match('/^http(s)?:\/\//i', $url)) { + syslog(LOG_INFO, 'SimplePie GET ' . $url); //FreshRSS if ($useragent === null) { $useragent = ini_get('user_agent'); @@ -107,7 +110,7 @@ class SimplePie_File curl_setopt($fp, CURLOPT_REFERER, $url); curl_setopt($fp, CURLOPT_USERAGENT, $useragent); curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2); - curl_setopt($fp, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($fp, CURLOPT_SSL_VERIFYPEER, false); //FreshRSS if (!ini_get('open_basedir') && !ini_get('safe_mode') && version_compare(SimplePie_Misc::get_curl_version(), '7.15.2', '>=')) { curl_setopt($fp, CURLOPT_FOLLOWLOCATION, 1); @@ -141,7 +144,10 @@ class SimplePie_File { $this->redirects++; $location = SimplePie_Misc::absolutize_url($this->headers['location'], $url); - return $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen); + $previousStatusCode = $this->status_code; + $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen); + $this->permanent_url = ($previousStatusCode == 301) ? $location : $url; //FreshRSS + return; } } } @@ -223,7 +229,10 @@ class SimplePie_File { $this->redirects++; $location = SimplePie_Misc::absolutize_url($this->headers['location'], $url); - return $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen); + $previousStatusCode = $this->status_code; + $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen); + $this->permanent_url = ($previousStatusCode == 301) ? $location : $url; //FreshRSS + return; } if (isset($this->headers['content-encoding'])) { @@ -283,7 +292,7 @@ class SimplePie_File else { $this->method = SIMPLEPIE_FILE_SOURCE_LOCAL | SIMPLEPIE_FILE_SOURCE_FILE_GET_CONTENTS; - if (!$this->body = file_get_contents($url)) + if (empty($url) || !($this->body = file_get_contents($url))) { $this->error = 'file_get_contents could not read the file'; $this->success = false; diff --git a/lib/SimplePie/SimplePie/HTTP/Parser.php b/lib/SimplePie/SimplePie/HTTP/Parser.php index bff2222b2..2fc3a6779 100644 --- a/lib/SimplePie/SimplePie/HTTP/Parser.php +++ b/lib/SimplePie/SimplePie/HTTP/Parser.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/IRI.php b/lib/SimplePie/SimplePie/IRI.php index d3198c04f..ed0574701 100644 --- a/lib/SimplePie/SimplePie/IRI.php +++ b/lib/SimplePie/SimplePie/IRI.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Item.php b/lib/SimplePie/SimplePie/Item.php index a77574b37..7bd96c15f 100644 --- a/lib/SimplePie/SimplePie/Item.php +++ b/lib/SimplePie/SimplePie/Item.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -821,7 +821,7 @@ class SimplePie_Item if (!empty($this->data['updated']['raw'])) { $parser = $this->registry->call('Parse_Date', 'get'); - $this->data['updated']['parsed'] = $parser->parse($this->data['date']['raw']); + $this->data['updated']['parsed'] = $parser->parse($this->data['updated']['raw']); } else { @@ -1080,7 +1080,7 @@ class SimplePie_Item * * @since Beta 2 * @todo Add support for end-user defined sorting of enclosures by type/handler (so we can prefer the faster-loading FLV over MP4). - * @todo If an element exists at a level, but it's value is empty, we should fall back to the value from the parent (if it exists). + * @todo If an element exists at a level, but its value is empty, we should fall back to the value from the parent (if it exists). * @return array|null List of SimplePie_Enclosure items */ public function get_enclosures() diff --git a/lib/SimplePie/SimplePie/Locator.php b/lib/SimplePie/SimplePie/Locator.php index 57e910c22..90ee7a302 100644 --- a/lib/SimplePie/SimplePie/Locator.php +++ b/lib/SimplePie/SimplePie/Locator.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -277,7 +277,7 @@ class SimplePie_Locator $parsed = $this->registry->call('Misc', 'parse_url', array($href)); if ($parsed['scheme'] === '' || preg_match('/^(http(s)|feed)?$/i', $parsed['scheme'])) { - if ($this->base_location < $link->getLineNo()) + if (method_exists($link, 'getLineNo') && $this->base_location < $link->getLineNo()) { $href = $this->registry->call('Misc', 'absolutize_url', array(trim($link->getAttribute('href')), $this->base)); } diff --git a/lib/SimplePie/SimplePie/Misc.php b/lib/SimplePie/SimplePie/Misc.php index 621f2c062..5a263a2e5 100644 --- a/lib/SimplePie/SimplePie/Misc.php +++ b/lib/SimplePie/SimplePie/Misc.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -79,6 +79,10 @@ class SimplePie_Misc public static function absolutize_url($relative, $base) { + if (substr($relative, 0, 2) === '//') //FreshRSS: disable absolutize_url for "//www.example.net" which will pick HTTP or HTTPS automatically + { + return $relative; + } $iri = SimplePie_IRI::absolutize(new SimplePie_IRI($base), $relative); if ($iri === false) { @@ -124,7 +128,7 @@ class SimplePie_Misc { $attribs[$j][2] = $attribs[$j][1]; } - $return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = SimplePie_Misc::entities_decode(end($attribs[$j]), 'UTF-8'); + $return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = SimplePie_Misc::entities_decode(end($attribs[$j]), 'UTF-8'); //FreshRSS } } } @@ -138,7 +142,7 @@ class SimplePie_Misc foreach ($element['attribs'] as $key => $value) { $key = strtolower($key); - $full .= " $key=\"" . htmlspecialchars($value['data'], ENT_COMPAT, 'UTF-8') . '"'; + $full .= " $key=\"" . htmlspecialchars($value['data'], ENT_COMPAT, 'UTF-8') . '"'; //FreshRSS } if ($element['self_closing']) { @@ -224,6 +228,23 @@ class SimplePie_Misc } } + public static function array_merge_recursive($array1, $array2) + { + foreach ($array2 as $key => $value) + { + if (is_array($value)) + { + $array1[$key] = SimplePie_Misc::array_merge_recursive($array1[$key], $value); + } + else + { + $array1[$key] = $value; + } + } + + return $array1; + } + public static function parse_url($url) { $iri = new SimplePie_IRI($url); @@ -2157,36 +2178,12 @@ function embed_wmedia(width, height, link) { /** * Get the SimplePie build timestamp * - * Uses the git index if it exists, otherwise uses the modification time - * of the newest file. + * Return SimplePie.php modification time. */ public static function get_build() { - $root = dirname(dirname(__FILE__)); - if (file_exists($root . '/.git/index')) - { - return filemtime($root . '/.git/index'); - } - elseif (file_exists($root . '/SimplePie')) - { - $time = 0; - foreach (glob($root . '/SimplePie/*.php') as $file) - { - if (($mtime = filemtime($file)) > $time) - { - $time = $mtime; - } - } - return $time; - } - elseif (file_exists(dirname(__FILE__) . '/Core.php')) - { - return filemtime(dirname(__FILE__) . '/Core.php'); - } - else - { - return filemtime(__FILE__); - } + $mtime = @filemtime(dirname(dirname(__FILE__)) . '/SimplePie.php'); //FreshRSS + return $mtime ? $mtime : filemtime(__FILE__); } /** diff --git a/lib/SimplePie/SimplePie/Net/IPv6.php b/lib/SimplePie/SimplePie/Net/IPv6.php index da80d8aca..2ff1afc90 100644 --- a/lib/SimplePie/SimplePie/Net/IPv6.php +++ b/lib/SimplePie/SimplePie/Net/IPv6.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Parse/Date.php b/lib/SimplePie/SimplePie/Parse/Date.php index d51f500d3..ef800f125 100644 --- a/lib/SimplePie/SimplePie/Parse/Date.php +++ b/lib/SimplePie/SimplePie/Parse/Date.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Parser.php b/lib/SimplePie/SimplePie/Parser.php index d698552ca..7fb7bd9be 100644 --- a/lib/SimplePie/SimplePie/Parser.php +++ b/lib/SimplePie/SimplePie/Parser.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -77,55 +77,86 @@ class SimplePie_Parser public function parse(&$data, $encoding) { - // Use UTF-8 if we get passed US-ASCII, as every US-ASCII character is a UTF-8 character - if (strtoupper($encoding) === 'US-ASCII') - { - $this->encoding = 'UTF-8'; - } - else - { - $this->encoding = $encoding; - } + $xmlEncoding = ''; - // Strip BOM: - // UTF-32 Big Endian BOM - if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") - { - $data = substr($data, 4); - } - // UTF-32 Little Endian BOM - elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00") - { - $data = substr($data, 4); - } - // UTF-16 Big Endian BOM - elseif (substr($data, 0, 2) === "\xFE\xFF") - { - $data = substr($data, 2); - } - // UTF-16 Little Endian BOM - elseif (substr($data, 0, 2) === "\xFF\xFE") + if (!empty($encoding)) { - $data = substr($data, 2); - } - // UTF-8 BOM - elseif (substr($data, 0, 3) === "\xEF\xBB\xBF") - { - $data = substr($data, 3); + // Use UTF-8 if we get passed US-ASCII, as every US-ASCII character is a UTF-8 character + if (strtoupper($encoding) === 'US-ASCII') + { + $this->encoding = 'UTF-8'; + } + else + { + $this->encoding = $encoding; + } + + // Strip BOM: + // UTF-32 Big Endian BOM + if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") + { + $data = substr($data, 4); + } + // UTF-32 Little Endian BOM + elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00") + { + $data = substr($data, 4); + } + // UTF-16 Big Endian BOM + elseif (substr($data, 0, 2) === "\xFE\xFF") + { + $data = substr($data, 2); + } + // UTF-16 Little Endian BOM + elseif (substr($data, 0, 2) === "\xFF\xFE") + { + $data = substr($data, 2); + } + // UTF-8 BOM + elseif (substr($data, 0, 3) === "\xEF\xBB\xBF") + { + $data = substr($data, 3); + } + + if (substr($data, 0, 5) === '<?xml' && strspn(substr($data, 5, 1), "\x09\x0A\x0D\x20") && ($pos = strpos($data, '?>')) !== false) + { + $declaration = $this->registry->create('XML_Declaration_Parser', array(substr($data, 5, $pos - 5))); + if ($declaration->parse()) + { + $xmlEncoding = strtoupper($declaration->encoding); //FreshRSS + $data = substr($data, $pos + 2); + $data = '<?xml version="' . $declaration->version . '" encoding="' . $encoding . '" standalone="' . (($declaration->standalone) ? 'yes' : 'no') . '"?>' . $data; + } + else + { + $this->error_string = 'SimplePie bug! Please report this!'; + return false; + } + } } - if (substr($data, 0, 5) === '<?xml' && strspn(substr($data, 5, 1), "\x09\x0A\x0D\x20") && ($pos = strpos($data, '?>')) !== false) + if ($xmlEncoding === '' || $xmlEncoding === 'UTF-8') //FreshRSS: case of no explicit HTTP encoding, and lax UTF-8 { - $declaration = $this->registry->create('XML_Declaration_Parser', array(substr($data, 5, $pos - 5))); - if ($declaration->parse()) + try { - $data = substr($data, $pos + 2); - $data = '<?xml version="' . $declaration->version . '" encoding="' . $encoding . '" standalone="' . (($declaration->standalone) ? 'yes' : 'no') . '"?>' . $data; + $dom = new DOMDocument(); + $dom->recover = true; + $dom->strictErrorChecking = false; + @$dom->loadXML($data); + $this->encoding = $encoding = $dom->encoding = 'UTF-8'; + $data2 = $dom->saveXML(); + if (function_exists('mb_convert_encoding')) + { + $data2 = mb_convert_encoding($data2, 'UTF-8', 'UTF-8'); + } + if (strlen($data2) > (strlen($data) / 2.0)) + { + $data = $data2; + } + unset($data2); } - else + catch (Exception $e) { - $this->error_string = 'SimplePie bug! Please report this!'; - return false; } } diff --git a/lib/SimplePie/SimplePie/Rating.php b/lib/SimplePie/SimplePie/Rating.php index 8689e5dfb..b5fe80516 100644 --- a/lib/SimplePie/SimplePie/Rating.php +++ b/lib/SimplePie/SimplePie/Rating.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Registry.php b/lib/SimplePie/SimplePie/Registry.php index 1072cdebb..bd9c1f535 100755 --- a/lib/SimplePie/SimplePie/Registry.php +++ b/lib/SimplePie/SimplePie/Registry.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Restriction.php b/lib/SimplePie/SimplePie/Restriction.php index 4ba371bfb..a1d59916d 100644 --- a/lib/SimplePie/SimplePie/Restriction.php +++ b/lib/SimplePie/SimplePie/Restriction.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Sanitize.php b/lib/SimplePie/SimplePie/Sanitize.php index 83a274ced..168a5e2e8 100644 --- a/lib/SimplePie/SimplePie/Sanitize.php +++ b/lib/SimplePie/SimplePie/Sanitize.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -62,6 +62,7 @@ class SimplePie_Sanitize var $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style'); var $encode_instead_of_strip = false; var $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'); + var $add_attributes = array('audio' => array('preload' => 'none'), 'iframe' => array('sandbox' => 'allow-scripts allow-same-origin'), 'video' => array('preload' => 'none')); //FreshRSS var $strip_comments = false; var $output_encoding = 'UTF-8'; var $enable_cache = true; @@ -179,6 +180,25 @@ class SimplePie_Sanitize } } + public function add_attributes($attribs = array('audio' => array('preload' => 'none'), 'iframe' => array('sandbox' => 'allow-scripts allow-same-origin'), 'video' => array('preload' => 'none'))) + { + if ($attribs) + { + if (is_array($attribs)) + { + $this->add_attributes = $attribs; + } + else + { + $this->add_attributes = explode(',', $attribs); + } + } + else + { + $this->add_attributes = false; + } + } + public function strip_comments($strip = false) { $this->strip_comments = (bool) $strip; @@ -247,6 +267,10 @@ class SimplePie_Sanitize if ($type & (SIMPLEPIE_CONSTRUCT_HTML | SIMPLEPIE_CONSTRUCT_XHTML)) { + if (!class_exists('DOMDocument')) + { + throw new SimplePie_Exception('DOMDocument not found, unable to use sanitizer'); + } $document = new DOMDocument(); $document->encoding = 'UTF-8'; $data = $this->preprocess($data, $type); @@ -255,10 +279,11 @@ class SimplePie_Sanitize $document->loadHTML($data); restore_error_handler(); + $xpath = new DOMXPath($document); //FreshRSS + // Strip comments if ($this->strip_comments) { - $xpath = new DOMXPath($document); $comments = $xpath->query('//comment()'); foreach ($comments as $comment) @@ -274,7 +299,7 @@ class SimplePie_Sanitize { foreach ($this->strip_htmltags as $tag) { - $this->strip_tag($tag, $document, $type); + $this->strip_tag($tag, $document, $xpath, $type); } } @@ -282,7 +307,15 @@ class SimplePie_Sanitize { foreach ($this->strip_attributes as $attrib) { - $this->strip_attr($attrib, $document); + $this->strip_attr($attrib, $xpath); + } + } + + if ($this->add_attributes) + { + foreach ($this->add_attributes as $tag => $valuePairs) + { + $this->add_attr($tag, $valuePairs, $document); } } @@ -310,7 +343,7 @@ class SimplePie_Sanitize } else { - $file = $this->registry->create('File', array($img['attribs']['src']['data'], $this->timeout, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->force_fsockopen)); + $file = $this->registry->create('File', array($img->getAttribute('src'), $this->timeout, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->force_fsockopen)); $headers = $file->headers; if ($file->success && ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300))) @@ -452,9 +485,8 @@ class SimplePie_Sanitize } } - protected function strip_tag($tag, $document, $type) + protected function strip_tag($tag, $document, $xpath, $type) { - $xpath = new DOMXPath($document); $elements = $xpath->query('body//' . $tag); if ($this->encode_instead_of_strip) { @@ -537,9 +569,8 @@ class SimplePie_Sanitize } } - protected function strip_attr($attrib, $document) + protected function strip_attr($attrib, $xpath) { - $xpath = new DOMXPath($document); $elements = $xpath->query('//*[@' . $attrib . ']'); foreach ($elements as $element) @@ -547,4 +578,16 @@ class SimplePie_Sanitize $element->removeAttribute($attrib); } } + + protected function add_attr($tag, $valuePairs, $document) + { + $elements = $document->getElementsByTagName($tag); + foreach ($elements as $element) + { + foreach ($valuePairs as $attrib => $value) + { + $element->setAttribute($attrib, $value); + } + } + } } diff --git a/lib/SimplePie/SimplePie/Source.php b/lib/SimplePie/SimplePie/Source.php index 51d8e6c25..2613798fd 100644 --- a/lib/SimplePie/SimplePie/Source.php +++ b/lib/SimplePie/SimplePie/Source.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/XML/Declaration/Parser.php b/lib/SimplePie/SimplePie/XML/Declaration/Parser.php index aec19f10a..589e452a2 100644 --- a/lib/SimplePie/SimplePie/XML/Declaration/Parser.php +++ b/lib/SimplePie/SimplePie/XML/Declaration/Parser.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/gzdecode.php b/lib/SimplePie/SimplePie/gzdecode.php index 52e024ea9..6e65f0811 100644 --- a/lib/SimplePie/SimplePie/gzdecode.php +++ b/lib/SimplePie/SimplePie/gzdecode.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie_autoloader.php b/lib/SimplePie_autoloader.php deleted file mode 100644 index 3f67635b0..000000000 --- a/lib/SimplePie_autoloader.php +++ /dev/null @@ -1,86 +0,0 @@ -<?php -/** - * SimplePie - * - * A PHP-Based RSS and Atom Feed Framework. - * Takes the hard work out of managing a complete RSS/Atom solution. - * - * Copyright (c) 2004-2009, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors - * 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 of the SimplePie Team 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 HOLDERS - * AND 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. - * - * @package SimplePie - * @version 1.3.1 - * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue - * @author Ryan Parman - * @author Geoffrey Sneddon - * @author Ryan McCue - * @link http://simplepie.org/ SimplePie - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - */ - - -// autoloader -spl_autoload_register(array(new SimplePie_Autoloader(), 'autoload')); - -if (!class_exists('SimplePie')) -{ - trigger_error('Autoloader not registered properly', E_USER_ERROR); -} - -/** - * Autoloader class - * - * @package SimplePie - * @subpackage API - */ -class SimplePie_Autoloader -{ - /** - * Constructor - */ - public function __construct() - { - $this->path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'SimplePie'; - } - - /** - * Autoloader - * - * @param string $class The name of the class to attempt to load. - */ - public function autoload($class) - { - // Only load the class if it starts with "SimplePie" - if (strpos($class, 'SimplePie') !== 0) - { - return; - } - - $filename = $this->path . DIRECTORY_SEPARATOR . str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php'; - include $filename; - } -} diff --git a/lib/http-conditional.php b/lib/http-conditional.php new file mode 100644 index 000000000..59fbef41f --- /dev/null +++ b/lib/http-conditional.php @@ -0,0 +1,212 @@ +<?php +/* + Enable support for HTTP/1.x conditional requests in PHP. + Goal: Optimisation + - If the client sends a HEAD request, avoid transferring data and return the correct headers. + - If the client already has the same version in its cache, avoid transferring data again (304 Not Modified). + - Possibility to control cache for client and proxies (public or private policy, life time). + - When $feedMode is set to true, in the case of a RSS/ATOM feed, + it puts a timestamp in the global variable $clientCacheDate to allow the sending of only the articles newer than the client's cache. + - When $compression is set to true, compress the data before sending it to the client and persitent connections are allowed. + - When $session is set to true, automatically checks if $_SESSION has been modified during the last generation the document. + + Interface: + - function httpConditional($UnixTimeStamp,$cacheSeconds=0,$cachePrivacy=0,$feedMode=false,$compression=false) + [Required] $UnixTimeStamp: Date of the last modification of the data to send to the client (Unix Timestamp format). + [Implied] $cacheSeconds=0: Lifetime in seconds of the document. If $cacheSeconds<0, cache is disabled. If $cacheSeconds==0, the document will be revalidated each time it is accessed. If $cacheSeconds>0, the document will be cashed and not revalidated against the server for this delay. + [Implied] $cachePrivacy=0: 0=private, 1=normal (public), 2=forced public. When public, it allows a cashed document ($cacheSeconds>0) to be shared by several users. + [Implied] $feedMode=false: Special RSS/ATOM feeds. When true, it sets $cachePrivacy to 0 (private), does not use the modification time of the script itself, and puts the date of the client's cache (or a old date from 1980) in the global variable $clientCacheDate. + [implied] $compression=false: Enable the compression and allows persistant connections (automatic detection of the capacities of the client). + [implied] $session=false: To be turned on when sessions are used. Checks if the data contained in $_SESSION has been modified during the last generation the document. + Returns: True if the connection can be closed (e.g.: the client has already the lastest version), false if the new content has to be send to the client. + + Typical use: + <?php + require_once('http-conditional.php'); + //Date of the last modification of the content (Unix Timestamp format). + //Examples: query the database, or last modification of a static file. + $dateLastModification=...; + if (httpConditional($dateLastModification)) + { + ... //Close database connections, and other cleaning. + exit(); //No need to send anything + } + //Do not send any text to the client before this line. + ... //Rest of the script, just as you would do normally. + ?> + + Version 1.7 beta, 2013-12-02, http://alexandre.alapetite.fr/doc-alex/php-http-304/ + + ------------------------------------------------------------------ + Written by Alexandre Alapetite, http://alexandre.alapetite.fr/cv/ + + Copyright 2004-2013, Licence: Creative Commons "Attribution-ShareAlike 2.0 France" BY-SA (FR), + http://creativecommons.org/licenses/by-sa/2.0/fr/ + http://alexandre.alapetite.fr/divers/apropos/#by-sa + - Attribution. You must give the original author credit + - Share Alike. If you alter, transform, or build upon this work, + you may distribute the resulting work only under a license identical to this one + (Can be included in GPL/LGPL projects) + - The French law is authoritative + - Any of these conditions can be waived if you get permission from Alexandre Alapetite + - Please send to Alexandre Alapetite the modifications you make, + in order to improve this file for the benefit of everybody + + If you want to distribute this code, please do it as a link to: + http://alexandre.alapetite.fr/doc-alex/php-http-304/ +*/ + +//In RSS/ATOM feedMode, contains the date of the clients last update. +$clientCacheDate=0; //Global public variable because PHP4 does not allow conditional arguments by reference +$_sessionMode=false; //Global private variable + +function httpConditional($UnixTimeStamp,$cacheSeconds=0,$cachePrivacy=0,$feedMode=false,$compression=false,$session=false) +{//Credits: http://alexandre.alapetite.fr/doc-alex/php-http-304/ + //RFC2616 HTTP/1.1: http://www.w3.org/Protocols/rfc2616/rfc2616.html + //RFC1945 HTTP/1.0: http://www.w3.org/Protocols/rfc1945/rfc1945.txt + + if (headers_sent()) return false; + + if (isset($_SERVER['SCRIPT_FILENAME'])) $scriptName=$_SERVER['SCRIPT_FILENAME']; + elseif (isset($_SERVER['PATH_TRANSLATED'])) $scriptName=$_SERVER['PATH_TRANSLATED']; + else return false; + + if ((!$feedMode)&&(($modifScript=filemtime($scriptName))>$UnixTimeStamp)) + $UnixTimeStamp=$modifScript; + $UnixTimeStamp=min($UnixTimeStamp,time()); + $is304=true; + $is412=false; + $nbCond=0; + + //rfc2616-sec3.html#sec3.3.1 + $dateLastModif=gmdate('D, d M Y H:i:s \G\M\T',$UnixTimeStamp); + $dateCacheClient='Thu, 10 Jan 1980 20:30:40 GMT'; + + //rfc2616-sec14.html#sec14.19 //='"0123456789abcdef0123456789abcdef"' + if (isset($_SERVER['QUERY_STRING'])) $myQuery='?'.$_SERVER['QUERY_STRING']; + else $myQuery=''; + if ($session&&isset($_SESSION)) + { + global $_sessionMode; + $_sessionMode=$session; + $myQuery.=print_r($_SESSION,true).session_name().'='.session_id(); + } + $etagServer='"'.md5($scriptName.$myQuery.'#'.$dateLastModif).'"'; + + if ((!$is412)&&isset($_SERVER['HTTP_IF_MATCH'])) + {//rfc2616-sec14.html#sec14.24 + $etagsClient=stripslashes($_SERVER['HTTP_IF_MATCH']); + $is412=(($etagClient!=='*')&&(strpos($etagsClient,$etagServer)===false)); + } + if ($is304&&isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) + {//rfc2616-sec14.html#sec14.25 //rfc1945.txt + $nbCond++; + $dateCacheClient=$_SERVER['HTTP_IF_MODIFIED_SINCE']; + $p=strpos($dateCacheClient,';'); + if ($p!==false) + $dateCacheClient=substr($dateCacheClient,0,$p); + $is304=($dateCacheClient==$dateLastModif); + } + if ($is304&&isset($_SERVER['HTTP_IF_NONE_MATCH'])) + {//rfc2616-sec14.html#sec14.26 + $nbCond++; + $etagClient=stripslashes($_SERVER['HTTP_IF_NONE_MATCH']); + $is304=(($etagClient===$etagServer)||($etagClient==='*')); + } + if ((!$is412)&&isset($_SERVER['HTTP_IF_UNMODIFIED_SINCE'])) + {//rfc2616-sec14.html#sec14.28 + $dateCacheClient=$_SERVER['HTTP_IF_UNMODIFIED_SINCE']; + $p=strpos($dateCacheClient,';'); + if ($p!==false) + $dateCacheClient=substr($dateCacheClient,0,$p); + $is412=($dateCacheClient!==$dateLastModif); + } + if ($feedMode) + {//Special RSS/ATOM + global $clientCacheDate; + $clientCacheDate=@strtotime($dateCacheClient); + $cachePrivacy=0; + } + + if ($is412) + {//rfc2616-sec10.html#sec10.4.13 + header('HTTP/1.1 412 Precondition Failed'); + header('Cache-Control: private, max-age=0, must-revalidate'); + header('Content-Type: text/plain'); + echo "HTTP/1.1 Error 412 Precondition Failed: Precondition request failed positive evaluation\n"; + return true; + } + elseif ($is304&&($nbCond>0)) + {//rfc2616-sec10.html#sec10.3.5 + header('HTTP/1.0 304 Not Modified'); + header('Etag: '.$etagServer); + if ($feedMode) header('Connection: close'); //Comment this line under IIS + return true; + } + else + {//rfc2616-sec10.html#sec10.2.1 + //rfc2616-sec14.html#sec14.3 + if ($compression) ob_start('_httpConditionalCallBack'); //Will check HTTP_ACCEPT_ENCODING + //header('HTTP/1.0 200 OK'); + if ($cacheSeconds<0) + { + $cache='private, no-cache, no-store, must-revalidate'; + //header('Expires: 0'); + header('Pragma: no-cache'); + } + else + { + if ($cacheSeconds===0) + { + $cache='private, must-revalidate, '; + //header('Expires: 0'); + } + elseif ($cachePrivacy===0) $cache='private, '; + elseif ($cachePrivacy===2) $cache='public, '; + else $cache=''; + $cache.='max-age='.floor($cacheSeconds); + } + //header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T',time()+$cacheSeconds)); //HTTP/1.0 //rfc2616-sec14.html#sec14.21 + header('Cache-Control: '.$cache); //rfc2616-sec14.html#sec14.9 + header('Last-Modified: '.$dateLastModif); + header('Etag: '.$etagServer); + if ($feedMode) header('Connection: close'); //rfc2616-sec14.html#sec14.10 //Comment this line under IIS + return $_SERVER['REQUEST_METHOD']==='HEAD'; //rfc2616-sec9.html#sec9.4 + } +} + +function _httpConditionalCallBack($buffer,$mode=5) +{//Private function automatically called at the end of the script when compression is enabled + //rfc2616-sec14.html#sec14.11 + //You can adjust the level of compression with zlib.output_compression_level in php.ini + if (extension_loaded('zlib')&&(!ini_get('zlib.output_compression'))) + { + $buffer2=ob_gzhandler($buffer,$mode); //Will check HTTP_ACCEPT_ENCODING and put correct headers such as Vary //rfc2616-sec14.html#sec14.44 + if (strlen($buffer2)>1) //When ob_gzhandler succeeded + $buffer=$buffer2; + } + header('Content-Length: '.strlen($buffer)); //Allows persistant connections //rfc2616-sec14.html#sec14.13 + return $buffer; +} + +function httpConditionalRefresh($UnixTimeStamp) +{//Update HTTP headers if the content has just been modified by the client's request + //See an example on http://alexandre.alapetite.fr/doc-alex/compteur/ + if (headers_sent()) return false; + + if (isset($_SERVER['SCRIPT_FILENAME'])) $scriptName=$_SERVER['SCRIPT_FILENAME']; + elseif (isset($_SERVER['PATH_TRANSLATED'])) $scriptName=$_SERVER['PATH_TRANSLATED']; + else return false; + + $dateLastModif=gmdate('D, d M Y H:i:s \G\M\T',$UnixTimeStamp); + + if (isset($_SERVER['QUERY_STRING'])) $myQuery='?'.$_SERVER['QUERY_STRING']; + else $myQuery=''; + global $_sessionMode; + if ($_sessionMode&&isset($_SESSION)) + $myQuery.=print_r($_SESSION,true).session_name().'='.session_id(); + $etagServer='"'.md5($scriptName.$myQuery.'#'.$dateLastModif).'"'; + + header('Last-Modified: '.$dateLastModif); + header('Etag: '.$etagServer); +} diff --git a/lib/lib_date.php b/lib/lib_date.php new file mode 100644 index 000000000..9533711e3 --- /dev/null +++ b/lib/lib_date.php @@ -0,0 +1,131 @@ +<?php +/** + * Author: Alexandre Alapetite http://alexandre.alapetite.fr + * 2014-06-01 + * License: GNU AGPLv3 http://www.gnu.org/licenses/agpl-3.0.html + * + * Parser of ISO 8601 time intervals http://en.wikipedia.org/wiki/ISO_8601#Time_intervals + * Examples: "2014-02/2014-04", "2014-02/04", "2014-06", "P1M" + */ + +/* +example('2014-03'); +example('201403'); +example('2014-03-30'); +example('2014-05-30T13'); +example('2014-05-30T13:30'); +example('2014-02/2014-04'); +example('2014-02--2014-04'); +example('2014-02/04'); +example('2014-02-03/05'); +example('2014-02-03T22:00/22:15'); +example('2014-02-03T22:00/15'); +example('2014-03/'); +example('/2014-03'); +example('2014-03/P1W'); +example('P1W/2014-05-25T23:59:59'); +example('P1Y/'); +example('P1Y'); +example('P2M/'); +example('P3W/'); +example('P4D/'); +example('PT5H/'); +example('PT6M/'); +example('PT7S/'); +example('P1DT1H/'); + +function example($dateInterval) { + $dateIntervalArray = parseDateInterval($dateInterval); + echo $dateInterval, "\t=>\t", + $dateIntervalArray[0] == null ? 'null' : @date('c', $dateIntervalArray[0]), '/', + $dateIntervalArray[1] == null ? 'null' : @date('c', $dateIntervalArray[1]), "\n"; +} +*/ + +function _dateFloor($isoDate) { + $x = explode('T', $isoDate, 2); + $t = isset($x[1]) ? str_pad($x[1], 6, '0') : '000000'; + return str_pad($x[0], 8, '01') . 'T' . $t; +} + +function _dateCeiling($isoDate) { + $x = explode('T', $isoDate, 2); + $t = isset($x[1]) && strlen($x[1]) > 1 ? str_pad($x[1], 6, '59') : '235959'; + switch (strlen($x[0])) { + case 4: + return $x[0] . '1231T' . $t; + case 6: + $d = @strtotime($x[0] . '01'); + return $x[0] . date('t', $d) . 'T' . $t; + default: + return $x[0] . 'T' . $t; + } +} + +function _noDelimit($isoDate) { + return $isoDate === null || $isoDate === '' ? null : + str_replace(array('-', ':'), '', $isoDate); //FIXME: Bug with negative time zone +} + +function _dateRelative($d1, $d2) { + if ($d2 === null) { + return $d1 !== null && $d1[0] !== 'P' ? $d1 : null; + } elseif ($d2 !== '' && $d2[0] != 'P' && $d1 !== null && $d1[0] !== 'P') { + $y2 = substr($d2, 0, 4); + if (strlen($y2) < 4 || !ctype_digit($y2)) { //Does not start by a year + $d2 = _noDelimit($d2); + return substr($d1, 0, -strlen($d2)) . $d2; //Add prefix from $d1 + } + } + return _noDelimit($d2); +} + +/** + * Parameter $dateInterval is a string containing an ISO 8601 time interval. + * Returns an array with the minimum and maximum Unix timestamp of this interval, + * or null if open interval, or false if error. + */ +function parseDateInterval($dateInterval) { + $dateInterval = trim($dateInterval); + $dateInterval = str_replace('--', '/', $dateInterval); + $dateInterval = strtoupper($dateInterval); + $min = null; + $max = null; + $x = explode('/', $dateInterval, 2); + $d1 = _noDelimit($x[0]); + $d2 = _dateRelative($d1, count($x) > 1 ? $x[1] : null); + if ($d1 !== null && $d1[0] !== 'P') { + $min = @strtotime(_dateFloor($d1)); + } + if ($d2 !== null) { + if ($d2[0] === 'P') { + try { + $di2 = new DateInterval($d2); + $dt1 = @date_create(); //new DateTime() would create an Exception if the default time zone is not defined + if ($min !== null && $min !== false) { + $dt1->setTimestamp($min); + } + $max = $dt1->add($di2)->getTimestamp() - 1; + } catch (Exception $e) { + $max = false; + } + } elseif ($d1 === null || $d1[0] !== 'P') { + $max = @strtotime(_dateCeiling($d2)); + } else { + $max = @strtotime($d2); + } + } + if ($d1 !== null && $d1[0] === 'P') { + try { + $di1 = new DateInterval($d1); + $dt2 = @date_create(); + if ($max !== null && $max !== false) { + $dt2->setTimestamp($max); + } + $min = $dt2->sub($di1)->getTimestamp() + 1; + } catch (Exception $e) { + $min = false; + } + } + return array($min, $max); +} diff --git a/lib/lib_opml.php b/lib/lib_opml.php new file mode 100644 index 000000000..16a9921ea --- /dev/null +++ b/lib/lib_opml.php @@ -0,0 +1,231 @@ +<?php + +/* * + * lib_opml is a free library to manage OPML format in PHP. + * It takes in consideration only version 2.0 (http://dev.opml.org/spec2.html). + * Basically it means "text" attribute for outline elements is required. + * + * lib_opml requires SimpleXML (http://php.net/manual/en/book.simplexml.php) + * + * Usages: + * > include('lib_opml.php'); + * > $filename = 'my_opml_file.xml'; + * > $opml_array = libopml_parse_file($filename); + * > print_r($opml_array); + * + * > $opml_string = [...]; + * > $opml_array = libopml_parse_string($opml_string); + * > print_r($opml_array); + * + * > $opml_array = [...]; + * > $opml_string = libopml_render($opml_array); + * > $opml_object = libopml_render($opml_array, true); + * > echo $opml_string; + * > print_r($opml_object); + * + * If parsing fails for any reason (e.g. not an XML string, does not match with + * the specifications), a LibOPML_Exception is raised. + * + * Author: Marien Fressinaud <dev@marienfressinaud.fr> + * Url: https://github.com/marienfressinaud/lib_opml + * Version: 0.1 + * Date: 2014-03-29 + * License: public domain + * + * */ + +class LibOPML_Exception extends Exception {} + + +// These elements are optional +define('HEAD_ELEMENTS', serialize(array( + 'title', 'dateCreated', 'dateModified', 'ownerName', 'ownerEmail', + 'ownerId', 'docs', 'expansionState', 'vertScrollState', 'windowTop', + 'windowLeft', 'windowBottom', 'windowRight' +))); + + +function libopml_parse_outline($outline_xml) { + $outline = array(); + + // An outline may contain any kind of attributes but "text" attribute is + // required ! + $text_is_present = false; + foreach ($outline_xml->attributes() as $key => $value) { + $outline[$key] = (string)$value; + + if ($key === 'text') { + $text_is_present = true; + } + } + + if (!$text_is_present) { + throw new LibOPML_Exception( + 'Outline does not contain any text attribute' + ); + } + + foreach ($outline_xml->children() as $key => $value) { + // An outline may contain any number of outline children + if ($key === 'outline') { + $outline['@outlines'][] = libopml_parse_outline($value); + } else { + throw new LibOPML_Exception( + 'Body can contain only outline elements' + ); + } + } + + return $outline; +} + + +function libopml_parse_string($xml) { + $dom = new DOMDocument(); + $dom->recover = true; + $dom->strictErrorChecking = false; + $dom->loadXML($xml); + $dom->encoding = 'UTF-8'; + + $opml = simplexml_import_dom($dom); + + if (!$opml) { + throw new LibOPML_Exception(); + } + + $array = array( + 'version' => (string)$opml['version'], + 'head' => array(), + 'body' => array() + ); + + // First, we get all "head" elements. Head is required but its sub-elements + // are optional. + foreach ($opml->head->children() as $key => $value) { + if (in_array($key, unserialize(HEAD_ELEMENTS), true)) { + $array['head'][$key] = (string)$value; + } else { + throw new LibOPML_Exception( + $key . 'is not part of OPML format' + ); + } + } + + // Then, we get body oulines. Body must contain at least one outline + // element. + $at_least_one_outline = false; + foreach ($opml->body->children() as $key => $value) { + if ($key === 'outline') { + $at_least_one_outline = true; + $array['body'][] = libopml_parse_outline($value); + } else { + throw new LibOPML_Exception( + 'Body can contain only outline elements' + ); + } + } + + if (!$at_least_one_outline) { + throw new LibOPML_Exception( + 'Body must contain at least one outline element' + ); + } + + return $array; +} + + +function libopml_parse_file($filename) { + $file_content = file_get_contents($filename); + + if ($file_content === false) { + throw new LibOPML_Exception( + $filename . ' cannot be found' + ); + } + + return libopml_parse_string($file_content); +} + + +function libopml_render_outline($parent_elt, $outline) { + // Outline MUST be an array! + if (!is_array($outline)) { + throw new LibOPML_Exception( + 'Outline element must be defined as array' + ); + } + + $outline_elt = $parent_elt->addChild('outline'); + $text_is_present = false; + foreach ($outline as $key => $value) { + // Only outlines can be an array and so we consider children are also + // outline elements. + if ($key === '@outlines' && is_array($value)) { + foreach ($value as $outline_child) { + libopml_render_outline($outline_elt, $outline_child); + } + } elseif (is_array($value)) { + throw new LibOPML_Exception( + 'Type of outline elements cannot be array: ' . $key + ); + } else { + // Detect text attribute is present, that's good :) + if ($key === 'text') { + $text_is_present = true; + } + + $outline_elt->addAttribute($key, $value); + } + } + + if (!$text_is_present) { + throw new LibOPML_Exception( + 'You must define at least a text element for all outlines' + ); + } +} + + +function libopml_render($array, $as_xml_object = false) { + $opml = new SimpleXMLElement('<opml version="2.0"></opml>'); + + // Create head element. $array['head'] is optional but head element will + // exist in the final XML object. + $head = $opml->addChild('head'); + if (isset($array['head'])) { + foreach ($array['head'] as $key => $value) { + if (in_array($key, unserialize(HEAD_ELEMENTS), true)) { + $head->addChild($key, $value); + } + } + } + + // Check body is set and contains at least one element + if (!isset($array['body'])) { + throw new LibOPML_Exception( + '$array must contain a body element' + ); + } + if (count($array['body']) <= 0) { + throw new LibOPML_Exception( + 'Body element must contain at least one element (array)' + ); + } + + // Create outline elements + $body = $opml->addChild('body'); + foreach ($array['body'] as $outline) { + libopml_render_outline($body, $outline); + } + + // And return the final result + if ($as_xml_object) { + return $opml; + } else { + $dom = dom_import_simplexml($opml)->ownerDocument; + $dom->formatOutput = true; + $dom->encoding = 'UTF-8'; + return $dom->saveXML(); + } +} diff --git a/lib/lib_phpQuery.php b/lib/lib_phpQuery.php index 4aefb70fe..194efa0c6 100644 --- a/lib/lib_phpQuery.php +++ b/lib/lib_phpQuery.php @@ -280,7 +280,7 @@ class DOMDocumentWrapper { // @see http://www.w3.org/International/O-HTTP-charset if (! $documentCharset) { $documentCharset = 'ISO-8859-1'; - $addDocumentCharset = true; + $addDocumentCharset = true; } // Should be careful here, still need 'magic encoding detection' since lots of pages have other 'default encoding' // Worse, some pages can have mixed encodings... we'll try not to worry about that @@ -567,7 +567,7 @@ class DOMDocumentWrapper { // if ($fake === false) // throw new Exception("Error loading documentFragment markup"); // else -// $return = array_merge($return, +// $return = array_merge($return, // $this->import($fake->root->childNodes) // ); // } else { @@ -969,24 +969,24 @@ interface ICallbackNamed { } /** * Callback class introduces currying-like pattern. - * + * * Example: * function foo($param1, $param2, $param3) { * var_dump($param1, $param2, $param3); * } - * $fooCurried = new Callback('foo', - * 'param1 is now statically set', + * $fooCurried = new Callback('foo', + * 'param1 is now statically set', * new CallbackParam, new CallbackParam * ); * phpQuery::callbackRun($fooCurried, * array('param2 value', 'param3 value' * ); - * - * Callback class is supported in all phpQuery methods which accepts callbacks. + * + * Callback class is supported in all phpQuery methods which accepts callbacks. * * @link http://code.google.com/p/phpquery/wiki/Callbacks#Param_Structures * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> - * + * * @TODO??? return fake forwarding function created via create_function * @TODO honor paramStructure */ @@ -995,7 +995,7 @@ class Callback public $callback = null; public $params = null; protected $name; - public function __construct($callback, $param1 = null, $param2 = null, + public function __construct($callback, $param1 = null, $param2 = null, $param3 = null) { $params = func_get_args(); $params = array_slice($params, 1); @@ -1024,11 +1024,11 @@ class Callback } /** * Shorthand for new Callback(create_function(...), ...); - * + * * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> */ class CallbackBody extends Callback { - public function __construct($paramList, $code, $param1 = null, $param2 = null, + public function __construct($paramList, $code, $param1 = null, $param2 = null, $param3 = null) { $params = func_get_args(); $params = array_slice($params, 2); @@ -1038,7 +1038,7 @@ class CallbackBody extends Callback { } /** * Callback type which on execution returns reference passed during creation. - * + * * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> */ class CallbackReturnReference extends Callback @@ -1060,7 +1060,7 @@ class CallbackReturnReference extends Callback } /** * Callback type which on execution returns value passed during creation. - * + * * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> */ class CallbackReturnValue extends Callback @@ -1087,7 +1087,7 @@ class CallbackReturnValue extends Callback } /** * CallbackParameterToReference can be used when we don't really want a callback, - * only parameter passed to it. CallbackParameterToReference takes first + * only parameter passed to it. CallbackParameterToReference takes first * parameter's value and passes it to reference. * * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> @@ -1095,7 +1095,7 @@ class CallbackReturnValue extends Callback class CallbackParameterToReference extends Callback { /** * @param $reference - * @TODO implement $paramIndex; + * @TODO implement $paramIndex; * param index choose which callback param will be passed to reference */ public function __construct(&$reference){ @@ -2994,7 +2994,7 @@ class phpQueryObject } /** * Enter description here... - * + * * @param $code * @return unknown_type */ @@ -3005,7 +3005,7 @@ class phpQueryObject } /** * Enter description here... - * + * * @param $code * @return unknown_type */ @@ -4034,7 +4034,7 @@ class phpQueryObject } /** * Enter description here... - * + * * @param <type> $key * @param <type> $value */ @@ -4051,7 +4051,7 @@ class phpQueryObject } /** * Enter description here... - * + * * @param <type> $key */ public function removeData($key) { @@ -4392,8 +4392,8 @@ if (!function_exists('mb_substr_count')) */ abstract class phpQuery { /** - * XXX: Workaround for mbstring problems - * + * XXX: Workaround for mbstring problems + * * @var bool */ public static $mbstringSupport = true; diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 41e95fc90..31c9cdbc1 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -1,208 +1,246 @@ <?php -// vérifie qu'on est connecté -function is_logged () { - return Session::param ('mail') != false; +if (!function_exists('json_decode')) { + require_once('JSON.php'); + function json_decode($var) { + $JSON = new Services_JSON; + return (array)($JSON->decode($var)); + } } -// vérifie que le système d'authentification est configuré -function login_is_conf ($conf) { - return $conf->mailLogin () != false; +if (!function_exists('json_encode')) { + require_once('JSON.php'); + function json_encode($var) { + $JSON = new Services_JSON; + return $JSON->encodeUnsafe($var); + } } -// tiré de Shaarli de Seb Sauvage -function small_hash ($txt) { - $t = rtrim (base64_encode (hash ('crc32', $txt, true)), '='); - $t = str_replace ('+', '-', $t); // Get rid of characters which need encoding in URLs. - $t = str_replace ('/', '_', $t); - $t = str_replace ('=', '@', $t); - - return $t; +//<Auto-loading> +function classAutoloader($class) { + if (strpos($class, 'FreshRSS') === 0) { + $components = explode('_', $class); + switch (count($components)) { + case 1: + include(APP_PATH . '/' . $components[0] . '.php'); + return; + case 2: + include(APP_PATH . '/Models/' . $components[1] . '.php'); + return; + case 3: //Controllers, Exceptions + @include(APP_PATH . '/' . $components[2] . 's/' . $components[1] . $components[2] . '.php'); + return; + } + } elseif (strpos($class, 'Minz') === 0) { + include(LIB_PATH . '/' . str_replace('_', '/', $class) . '.php'); + } elseif (strpos($class, 'SimplePie') === 0) { + include(LIB_PATH . '/SimplePie/' . str_replace('_', '/', $class) . '.php'); + } } -function timestamptodate ($t, $hour = true) { - $month = Translate::t (date('M', $t)); - if ($hour) { - $date = Translate::t ('format_date_hour', $month); +spl_autoload_register('classAutoloader'); +//</Auto-loading> + +function checkUrl($url) { + if (empty ($url)) { + return ''; + } + if (!preg_match ('#^https?://#i', $url)) { + $url = 'http://' . $url; + } + if (filter_var($url, FILTER_VALIDATE_URL) || + (version_compare(PHP_VERSION, '5.3.3', '<') && (strpos($url, '-') > 0) && //PHP bug #51192 + ($url === filter_var($url, FILTER_SANITIZE_URL)))) { + return $url; } else { - $date = Translate::t ('format_date', $month); + return false; } - - return date ($date, $t); } -function sortEntriesByDate ($entry1, $entry2) { - return $entry2->date (true) - $entry1->date (true); -} -function sortReverseEntriesByDate ($entry1, $entry2) { - return $entry1->date (true) - $entry2->date (true); +function formatNumber($n, $precision = 0) { + return str_replace(' ', ' ', //Espace insécable //TODO: remplacer par une espace _fine_ insécable + number_format($n, $precision, '.', ' ')); //number_format does not seem to be Unicode-compatible } -function get_domain ($url) { - return parse_url($url, PHP_URL_HOST); +function formatBytes($bytes, $precision = 2, $system = 'IEC') { + if ($system === 'IEC') { + $base = 1024; + $units = array('B', 'KiB', 'MiB', 'GiB', 'TiB'); + } elseif ($system === 'SI') { + $base = 1000; + $units = array('B', 'KB', 'MB', 'GB', 'TB'); + } + $bytes = max(intval($bytes), 0); + $pow = $bytes === 0 ? 0 : floor(log($bytes) / log($base)); + $pow = min($pow, count($units) - 1); + $bytes /= pow($base, $pow); + return formatNumber($bytes, $precision) . ' ' . $units[$pow]; } -function opml_export ($cats) { - $txt = ''; - - foreach ($cats as $cat) { - $txt .= '<outline text="' . $cat['name'] . '">' . "\n"; - - foreach ($cat['feeds'] as $feed) { - $txt .= "\t" . '<outline text="' . cleanText ($feed->name ()) . '" type="rss" xmlUrl="' . htmlentities ($feed->url (), ENT_COMPAT, 'UTF-8') . '" htmlUrl="' . htmlentities ($feed->website (), ENT_COMPAT, 'UTF-8') . '" />' . "\n"; - } - - $txt .= '</outline>' . "\n"; +function timestamptodate ($t, $hour = true) { + $month = Minz_Translate::t (date('M', $t)); + if ($hour) { + $date = Minz_Translate::t ('format_date_hour', $month); + } else { + $date = Minz_Translate::t ('format_date', $month); } - - return $txt; -} -function cleanText ($text) { - return preg_replace ('/&[\w]+;/', '', $text); + return @date ($date, $t); } -function opml_import ($xml) { - $opml = @simplexml_load_string ($xml); - - if (!$opml) { - throw new OpmlException (); - } - - $catDAO = new CategoryDAO(); - $catDAO->checkDefault(); - $defCat = $catDAO->getDefault(); - - $categories = array (); - $feeds = array (); - - foreach ($opml->body->outline as $outline) { - if (!isset ($outline['xmlUrl'])) { - // Catégorie - $title = ''; - - if (isset ($outline['text'])) { - $title = (string) $outline['text']; - } elseif (isset ($outline['title'])) { - $title = (string) $outline['title']; - } - - if ($title) { - // Permet d'éviter les soucis au niveau des id : - // ceux-ci sont générés en fonction de la date, - // un flux pourrait être dans une catégorie X avec l'id Y - // alors qu'il existe déjà la catégorie X mais avec l'id Z - // Y ne sera pas ajouté et le flux non plus vu que l'id - // de sa catégorie n'exisera pas - $catDAO = new CategoryDAO (); - $cat = $catDAO->searchByName ($title); - if ($cat === false) { - $cat = new Category ($title); - } - $categories[] = $cat; - - $feeds = array_merge ($feeds, getFeedsOutline ($outline, $cat->id ())); - } +function html_only_entity_decode($text) { + static $htmlEntitiesOnly = null; + if ($htmlEntitiesOnly === null) { + if (version_compare(PHP_VERSION, '5.3.4') >= 0) { + $htmlEntitiesOnly = array_flip(array_diff( + get_html_translation_table(HTML_ENTITIES, ENT_NOQUOTES, 'UTF-8'), //Decode HTML entities + get_html_translation_table(HTML_SPECIALCHARS, ENT_NOQUOTES, 'UTF-8') //Preserve XML entities + )); } else { - // Flux rss sans catégorie, on récupère l'ajoute dans la catégorie par défaut - $feeds[] = getFeed ($outline, $defCat->id()); + $htmlEntitiesOnly = array_map('utf8_encode', array_flip(array_diff( + get_html_translation_table(HTML_ENTITIES, ENT_NOQUOTES), //Decode HTML entities + get_html_translation_table(HTML_SPECIALCHARS, ENT_NOQUOTES) //Preserve XML entities + ))); } } - - return array ($categories, $feeds); + return strtr($text, $htmlEntitiesOnly); } -/** - * import all feeds of a given outline tag - */ -function getFeedsOutline ($outline, $cat_id) { - $feeds = array (); - - foreach ($outline->children () as $child) { - if (isset ($child['xmlUrl'])) { - $feeds[] = getFeed ($child, $cat_id); - } else { - $feeds = array_merge( - $feeds, - getFeedsOutline ($child, $cat_id) - ); - } - } - - return $feeds; +function customSimplePie() { + $simplePie = new SimplePie(); + $simplePie->set_useragent(Minz_Translate::t('freshrss') . '/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ') ' . SIMPLEPIE_NAME . '/' . SIMPLEPIE_VERSION); + $simplePie->set_cache_location(CACHE_PATH); + $simplePie->set_cache_duration(800); + $simplePie->strip_htmltags(array( + 'base', 'blink', 'body', 'doctype', 'embed', + 'font', 'form', 'frame', 'frameset', 'html', + 'link', 'input', 'marquee', 'meta', 'noscript', + 'object', 'param', 'plaintext', 'script', 'style', + )); + $simplePie->strip_attributes(array_merge($simplePie->strip_attributes, array( + 'autoplay', 'onload', 'onunload', 'onclick', 'ondblclick', 'onmousedown', 'onmouseup', + 'onmouseover', 'onmousemove', 'onmouseout', 'onfocus', 'onblur', + 'onkeypress', 'onkeydown', 'onkeyup', 'onselect', 'onchange', 'seamless'))); + $simplePie->add_attributes(array( + 'img' => array('lazyload' => '', 'postpone' => ''), //http://www.w3.org/TR/resource-priorities/ + 'audio' => array('lazyload' => '', 'postpone' => '', 'preload' => 'none'), + 'iframe' => array('lazyload' => '', 'postpone' => '', 'sandbox' => 'allow-scripts allow-same-origin'), + 'video' => array('lazyload' => '', 'postpone' => '', 'preload' => 'none'), + )); + $simplePie->set_url_replacements(array( + 'a' => 'href', + 'area' => 'href', + 'audio' => 'src', + 'blockquote' => 'cite', + 'del' => 'cite', + 'form' => 'action', + 'iframe' => 'src', + 'img' => array( + 'longdesc', + 'src' + ), + 'input' => 'src', + 'ins' => 'cite', + 'q' => 'cite', + 'source' => 'src', + 'track' => 'src', + 'video' => array( + 'poster', + 'src', + ), + )); + return $simplePie; } -function getFeed ($outline, $cat_id) { - $url = (string) $outline['xmlUrl']; - $title = ''; - if (isset ($outline['text'])) { - $title = (string) $outline['text']; - } elseif (isset ($outline['title'])) { - $title = (string) $outline['title']; +function sanitizeHTML($data, $base = '') { + static $simplePie = null; + if ($simplePie == null) { + $simplePie = customSimplePie(); + $simplePie->init(); } - $feed = new Feed ($url); - $feed->_category ($cat_id); - $feed->_name ($title); - return $feed; + return html_only_entity_decode($simplePie->sanitize->sanitize($data, SIMPLEPIE_CONSTRUCT_HTML, $base)); } - /* permet de récupérer le contenu d'un article pour un flux qui n'est pas complet */ function get_content_by_parsing ($url, $path) { + require_once (LIB_PATH . '/lib_phpQuery.php'); + + syslog(LOG_INFO, 'FreshRSS GET ' . $url); $html = file_get_contents ($url); - + if ($html) { $doc = phpQuery::newDocument ($html); $content = $doc->find ($path); - $content->find ('*')->removeAttr ('style') - ->removeAttr ('id') - ->removeAttr ('class') - ->removeAttr ('onload') - ->removeAttr ('target'); - $content->removeAttr ('style') - ->removeAttr ('id') - ->removeAttr ('class') - ->removeAttr ('onload') - ->removeAttr ('target'); - return $content->__toString (); + return sanitizeHTML($content->__toString(), $url); } else { throw new Exception (); } } -/* Télécharge le favicon d'un site, le place sur le serveur et retourne l'URL */ -function dowload_favicon ($website, $id) { - $url = 'http://g.etfv.co/' . $website; - $favicons_dir = PUBLIC_PATH . '/data/favicons'; - $dest = $favicons_dir . '/' . $id . '.ico'; - $favicon_url = '/data/favicons/' . $id . '.ico'; +/** + * Add support of image lazy loading + * Move content from src attribute to data-original + * @param content is the text we want to parse + */ +function lazyimg($content) { + return preg_replace( + '/<((?:img|iframe)[^>]+?)src=[\'"]([^"\']+)[\'"]([^>]*)>/i', + '<$1src="' . Minz_Url::display('/themes/icons/grey.gif') . '" data-original="$2"$3>', + $content + ); +} - if (!is_dir ($favicons_dir)) { - if (!mkdir ($favicons_dir, 0755, true)) { - return $url; - } +function uTimeString() { + $t = @gettimeofday(); + return $t['sec'] . str_pad($t['usec'], 6, '0'); +} + +function uSecString() { + $t = @gettimeofday(); + return str_pad($t['usec'], 6, '0'); +} + +function invalidateHttpCache() { + Minz_Session::_param('touch', uTimeString()); + return touch(LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log'); +} + +function usernameFromPath($userPath) { + if (preg_match('%/([A-Za-z0-9]{1,16})_user\.php$%', $userPath, $matches)) { + return $matches[1]; + } else { + return ''; } +} - if (!file_exists ($dest)) { - $c = curl_init ($url); - curl_setopt ($c, CURLOPT_HEADER, false); - curl_setopt ($c, CURLOPT_RETURNTRANSFER, true); - curl_setopt ($c, CURLOPT_BINARYTRANSFER, true); - $imgRaw = curl_exec ($c); - - if (curl_getinfo ($c, CURLINFO_HTTP_CODE) == 200) { - $file = fopen ($dest, 'w'); - if ($file === false) { - return $url; - } - - fwrite ($file, $imgRaw); - fclose ($file); - } else { - return $url; - } +function listUsers() { + return array_map('usernameFromPath', glob(DATA_PATH . '/*_user.php')); +} - curl_close ($c); +function httpAuthUser() { + return isset($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'] : ''; +} + +function cryptAvailable() { + if (version_compare(PHP_VERSION, '5.3.3', '>=')) { + try { + $hash = '$2y$04$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG'; + return $hash === @crypt('password', $hash); + } catch (Exception $e) { + } } + return false; +} - return $favicon_url; +function is_referer_from_same_domain() { + if (empty($_SERVER['HTTP_REFERER'])) { + return false; + } + $host = parse_url(((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? 'https://' : 'http://') . + (empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] : $_SERVER['HTTP_HOST'])); + $referer = parse_url($_SERVER['HTTP_REFERER']); + if (empty($host['scheme']) || empty($referer['scheme']) || $host['scheme'] !== $referer['scheme'] || + empty($host['host']) || empty($referer['host']) || $host['host'] !== $referer['host']) { + return false; + } + return (isset($host['port']) ? $host['port'] : 0) === (isset($referer['port']) ? $referer['port'] : 0); } diff --git a/lib/lib_text.php b/lib/lib_text.php deleted file mode 100644 index 9792e191e..000000000 --- a/lib/lib_text.php +++ /dev/null @@ -1,96 +0,0 @@ -<?php - -function bbDecode($string) { - - $find = array( - "'\[b\](.*?)\[/b\]'is", - "'\[u\](.*?)\[/u\]'is", - "'\[i\](.*?)\[/i\]'is", - "'\[s\](.*?)\[/s\]'is", - "'\[code\](.*?)\[/code\]'is", - "'\[quote\](.*?)\[/quote\]'is", - "'\[quote=(.*?)\](.*?)\[/quote\]'is", - "'\[span=(.*?)\](.*?)\[/span\]'i", - "'\[div=(.*?)\](.*?)\[/div\]'is", - "'\[h\](.*?)\[/h\]'i", - "'\[url\](.*?)\[/url\]'i", - "'\[url=(.*?)\](.*?)\[/url\]'i", - "'\[video\](.*?)\[/video\]'i", - "'\[video width=(.*?) height=(.*?)\](.*?)\[/video\]'i", - "'\[img\](.*?)\[/img\]'i", - "'\[img title=(.*?) rel=(.*?)\](.*?)\[/img\]'i", - "'\[img title=(.*?)\](.*?)\[/img\]'i", - ); - - $replace = array( - "<strong>\\1</strong>", - "<u>\\1</u>", - "<i>\\1</i>", - "<del>\\1</del>", - "<pre>\\1</pre>", - "<q>\\1</q>", - "<q><span class=\"cite\">\\1 a écrit</span><br />\\2</q>", - "<span class=\"\\1\">\\2</span>", - "<div class=\"\\1\">\\2</div>", - "<b>\\1</b><br />", - "<a href=\"\\1\">\\1</a>", - "<a href=\"\\1\">\\2</a>", - "<object width=\"480\" height=\"387\" class=\"center\"><param name=\"movie\" value=\"\\1\"></param><embed src=\"\\1\" type=\"application/x-shockwave-flash\" width=\"480\" height=\"387\"></embed></object>", - "<object width=\"\\1\" height=\"\\2\" class=\"center\"><param name=\"movie\" value=\"\\3\"></param><embed src=\"\\3\" type=\"application/x-shockwave-flash\" width=\"\\1\" height=\"\\2\"></embed></object>", - "<a href=\"\\1\" rel=\"prettyPhoto\"><img src=\"\\1\" alt=\"\" /></a>", - "<img class=\"illustration\" src=\"\\3\" alt=\"\\1\" />", - "<img src=\"\\2\" alt=\"\\1\" />", - ); - - $string = makeLinks(preg_replace ($find, $replace, $string)); - $string = nl2brPlus ($string); - - return $string; -} - -// do nl2br except when in a <pre> tag -function nl2brPlus($string) { - $string = str_replace("\n", "<br />", $string); - if(preg_match_all('/\<pre\>(.*?)\<\/pre\>/', $string, $match)){ - foreach($match as $a){ - foreach($a as $b){ - $string = str_replace('<pre>'.$b.'</pre>', "<pre>".str_replace("<br />", "", $b)."</pre>", $string); - } - } - } - return $string; -} - -# Transform URL and e-mails into links -function makeLinks($string) { - $string = preg_replace_callback('/\s(http|https|ftp):(\/\/){0,1}([^\"\s]*)/i','splitUri',$string); - return $string; -} - -# Split links, require for makeLinks -function splitUri($matches) { - $uri = $matches[1].':'.$matches[2].$matches[3]; - $t = parse_url($uri); - $link = $matches[3]; - - if (!empty($t['scheme'])) { - return ' <a href="'.$uri.'">'.$link.'</a>'; - } else { - return $uri; - } -} - -// parse la description pour ajouter les liens sur les tags -function parse_tags ($desc) { - $desc_parse = preg_replace ('/#([\w\dÀÇÈÉÊËÎÏÔÙÚÛÜàáâçèéêëîïóùúûü]+)/i', '<a class="linktag" href="?addtag=\\1">\\1</a>', $desc); - - return $desc_parse; -} - -function lazyimg($content) { - return preg_replace( - '/<img([^<]+)src=([\'"])([^"\']*)([\'"])([^<]*)>/i', - '<img$1src="' . Url::display('/data/grey.gif') . '" data-original="$3"$5>', - $content - ); -}
\ No newline at end of file diff --git a/lib/minz/Cache.php b/lib/minz/Cache.php deleted file mode 100644 index 6848e3350..000000000 --- a/lib/minz/Cache.php +++ /dev/null @@ -1,116 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * La classe Cache permet de gérer facilement les pages en cache - */ -class Minz_Cache { - /** - * $expire timestamp auquel expire le cache de $url - */ - private $expire = 0; - - /** - * $file est le nom du fichier de cache - */ - private $file = ''; - - /** - * $enabled permet de déterminer si le cache est activé - */ - private static $enabled = true; - - /** - * Constructeur - */ - public function __construct () { - $this->_fileName (); - $this->_expire (); - } - - /** - * Setteurs - */ - public function _fileName () { - $file = md5 (Request::getURI ()); - - $this->file = CACHE_PATH . '/'.$file; - } - - public function _expire () { - if ($this->exist ()) { - $this->expire = filemtime ($this->file) - + Configuration::delayCache (); - } - } - - /** - * Permet de savoir si le cache est activé - * @return true si activé, false sinon - */ - public static function isEnabled () { - return Configuration::cacheEnabled () && self::$enabled; - } - - /** - * Active / désactive le cache - */ - public static function switchOn () { - self::$enabled = true; - } - public static function switchOff () { - self::$enabled = false; - } - - /** - * Détermine si le cache de $url a expiré ou non - * @return true si il a expiré, false sinon - */ - public function expired () { - return time () > $this->expire; - } - - /** - * Affiche le contenu du cache - * @print le code html du cache - */ - public function render () { - if ($this->exist ()) { - include ($this->file); - } - } - - /** - * Enregistre $html en cache - * @param $html le html à mettre en cache - */ - public function cache ($html) { - file_put_contents ($this->file, $html); - } - - /** - * Permet de savoir si le cache existe - * @return true si il existe, false sinon - */ - public function exist () { - return file_exists ($this->file); - } - - /** - * Nettoie le cache en supprimant tous les fichiers - */ - public static function clean () { - $files = opendir (CACHE_PATH); - - while ($fic = readdir ($files)) { - if ($fic != '.' && $fic != '..') { - unlink (CACHE_PATH.'/'.$fic); - } - } - - closedir ($files); - } -} diff --git a/lib/minz/Configuration.php b/lib/minz/Configuration.php deleted file mode 100755 index ad6df1bcf..000000000 --- a/lib/minz/Configuration.php +++ /dev/null @@ -1,242 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * La classe Configuration permet de gérer la configuration de l'application - */ -class Configuration { - const CONF_PATH_NAME = '/configuration/application.ini'; - - /** - * VERSION est la version actuelle de MINZ - */ - const VERSION = '1.3.1'; - - /** - * valeurs possibles pour l'"environment" - * SILENT rend l'application muette (pas de log) - * PRODUCTION est recommandée pour une appli en production - * (log les erreurs critiques) - * DEVELOPMENT log toutes les erreurs - */ - const SILENT = 0; - const PRODUCTION = 1; - const DEVELOPMENT = 2; - - /** - * définition des variables de configuration - * $sel_application une chaîne de caractères aléatoires (obligatoire) - * $environment gère le niveau d'affichage pour log et erreurs - * $use_url_rewriting indique si on utilise l'url_rewriting - * $base_url le chemin de base pour accéder à l'application - * $title le nom de l'application - * $language la langue par défaut de l'application - * $cacheEnabled permet de savoir si le cache doit être activé - * $delayCache la limite de cache - * $db paramètres pour la base de données (tableau) - * - host le serveur de la base - * - user nom d'utilisateur - * - password mot de passe de l'utilisateur - * - base le nom de la base de données - */ - private static $sel_application = ''; - private static $environment = Configuration::PRODUCTION; - private static $base_url = ''; - private static $use_url_rewriting = false; - private static $title = ''; - private static $language = 'en'; - private static $cache_enabled = false; - private static $delay_cache = 3600; - - private static $db = array ( - 'host' => false, - 'user' => false, - 'password' => false, - 'base' => false - ); - - /* - * Getteurs - */ - public static function selApplication () { - return self::$sel_application; - } - public static function environment () { - return self::$environment; - } - public static function baseUrl () { - return self::$base_url; - } - public static function useUrlRewriting () { - return self::$use_url_rewriting; - } - public static function title () { - return self::$title; - } - public static function language () { - return self::$language; - } - public static function cacheEnabled () { - return self::$cache_enabled; - } - public static function delayCache () { - return self::$delay_cache; - } - public static function dataBase () { - return self::$db; - } - - /** - * Initialise les variables de configuration - * @exception FileNotExistException si le CONF_PATH_NAME n'existe pas - * @exception BadConfigurationException si CONF_PATH_NAME mal formaté - */ - public static function init () { - try { - self::parseFile (); - self::setReporting (); - } catch (BadConfigurationException $e) { - throw $e; - } catch (FileNotExistException $e) { - throw $e; - } - } - - /** - * Parse un fichier de configuration de type ".ini" - * @exception FileNotExistException si le CONF_PATH_NAME n'existe pas - * @exception BadConfigurationException si CONF_PATH_NAME mal formaté - */ - private static function parseFile () { - if (!file_exists (APP_PATH . self::CONF_PATH_NAME)) { - throw new FileNotExistException ( - APP_PATH . self::CONF_PATH_NAME, - MinzException::ERROR - ); - } - $ini_array = parse_ini_file ( - APP_PATH . self::CONF_PATH_NAME, - true - ); - - // [general] est obligatoire - if (!isset ($ini_array['general'])) { - throw new BadConfigurationException ( - '[general]', - MinzException::ERROR - ); - } - $general = $ini_array['general']; - - - // sel_application est obligatoire - if (!isset ($general['sel_application'])) { - throw new BadConfigurationException ( - 'sel_application', - MinzException::ERROR - ); - } - self::$sel_application = $general['sel_application']; - - if (isset ($general['environment'])) { - switch ($general['environment']) { - case 'silent': - self::$environment = Configuration::SILENT; - break; - case 'development': - self::$environment = Configuration::DEVELOPMENT; - break; - case 'production': - self::$environment = Configuration::PRODUCTION; - break; - default: - throw new BadConfigurationException ( - 'environment', - MinzException::ERROR - ); - } - - } - if (isset ($general['base_url'])) { - self::$base_url = $general['base_url']; - } - if (isset ($general['use_url_rewriting'])) { - self::$use_url_rewriting = $general['use_url_rewriting']; - } - - if (isset ($general['title'])) { - self::$title = $general['title']; - } - if (isset ($general['language'])) { - self::$language = $general['language']; - } - if (isset ($general['cache_enabled'])) { - self::$cache_enabled = $general['cache_enabled']; - if (CACHE_PATH === false && self::$cache_enabled) { - throw new FileNotExistException ( - 'CACHE_PATH', - MinzException::ERROR - ); - } - } - if (isset ($general['delay_cache'])) { - self::$delay_cache = $general['delay_cache']; - } - - // Base de données - $db = false; - if (isset ($ini_array['db'])) { - $db = $ini_array['db']; - } - if ($db) { - if (!isset ($db['host'])) { - throw new BadConfigurationException ( - 'host', - MinzException::ERROR - ); - } - if (!isset ($db['user'])) { - throw new BadConfigurationException ( - 'user', - MinzException::ERROR - ); - } - if (!isset ($db['password'])) { - throw new BadConfigurationException ( - 'password', - MinzException::ERROR - ); - } - if (!isset ($db['base'])) { - throw new BadConfigurationException ( - 'base', - MinzException::ERROR - ); - } - - self::$db['type'] = isset ($db['type']) ? $db['type'] : 'mysql'; - self::$db['host'] = $db['host']; - self::$db['user'] = $db['user']; - self::$db['password'] = $db['password']; - self::$db['base'] = $db['base']; - self::$db['prefix'] = isset ($db['prefix']) ? $db['prefix'] : ''; - } - } - - private static function setReporting () { - if (self::environment () == self::DEVELOPMENT) { - error_reporting (E_ALL); - ini_set ('display_errors','On'); - ini_set('log_errors', 'On'); - } elseif (self::environment () == self::PRODUCTION) { - error_reporting(E_ALL); - ini_set('display_errors','Off'); - ini_set('log_errors', 'On'); - } else { - error_reporting(0); - } - } -} diff --git a/lib/minz/Dispatcher.php b/lib/minz/Dispatcher.php deleted file mode 100644 index 0cfdd8e75..000000000 --- a/lib/minz/Dispatcher.php +++ /dev/null @@ -1,152 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * Le Dispatcher s'occupe d'initialiser le Controller et d'executer l'action - * déterminée dans la Request - * C'est un singleton - */ -class Dispatcher { - const CONTROLLERS_PATH_NAME = '/controllers'; - - /* singleton */ - private static $instance = null; - - private $router; - private $controller; - - /** - * Récupère l'instance du Dispatcher - */ - public static function getInstance ($router) { - if (is_null (self::$instance)) { - self::$instance = new Dispatcher ($router); - } - return self::$instance; - } - - /** - * Constructeur - */ - private function __construct ($router) { - $this->router = $router; - } - - /** - * Lance le controller indiqué dans Request - * Remplit le body de Response à partir de la Vue - * @exception MinzException - */ - public function run () { - $cache = new Minz_Cache(); - // Le ob_start est dupliqué : sans ça il y a un bug sous Firefox - // ici on l'appelle avec 'ob_gzhandler', après sans. - // Vraisemblablement la compression fonctionne mais c'est sale - // J'ignore les effets de bord :( - ob_start ('ob_gzhandler'); - - if (Minz_Cache::isEnabled () && !$cache->expired ()) { - ob_start (); - $cache->render (); - $text = ob_get_clean(); - } else { - while (Request::$reseted) { - Request::$reseted = false; - - try { - $this->createController ( - Request::controllerName () - . 'Controller' - ); - - $this->controller->init (); - $this->controller->firstAction (); - $this->launchAction ( - Request::actionName () - . 'Action' - ); - $this->controller->lastAction (); - - if (!Request::$reseted) { - ob_start (); - $this->controller->view ()->build (); - $text = ob_get_clean(); - } - } catch (MinzException $e) { - throw $e; - } - } - - if (Minz_Cache::isEnabled ()) { - $cache->cache ($text); - } - } - - Response::setBody ($text); - } - - /** - * Instancie le Controller - * @param $controller_name le nom du controller à instancier - * @exception FileNotExistException le fichier correspondant au - * > controller n'existe pas - * @exception ControllerNotExistException le controller n'existe pas - * @exception ControllerNotActionControllerException controller n'est - * > pas une instance de ActionController - */ - private function createController ($controller_name) { - $filename = APP_PATH . self::CONTROLLERS_PATH_NAME . '/' - . $controller_name . '.php'; - - if (!file_exists ($filename)) { - throw new FileNotExistException ( - $filename, - MinzException::ERROR - ); - } - require_once ($filename); - - if (!class_exists ($controller_name)) { - throw new ControllerNotExistException ( - $controller_name, - MinzException::ERROR - ); - } - $this->controller = new $controller_name ($this->router); - - if (! ($this->controller instanceof ActionController)) { - throw new ControllerNotActionControllerException ( - $controller_name, - MinzException::ERROR - ); - } - } - - /** - * Lance l'action sur le controller du dispatcher - * @param $action_name le nom de l'action - * @exception ActionException si on ne peut pas exécuter l'action sur - * > le controller - */ - private function launchAction ($action_name) { - if (!Request::$reseted) { - if (!is_callable (array ( - $this->controller, - $action_name - ))) { - throw new ActionException ( - get_class ($this->controller), - $action_name, - MinzException::ERROR - ); - } - call_user_func (array ( - $this->controller, - $action_name - )); - } - } -} diff --git a/lib/minz/FrontController.php b/lib/minz/FrontController.php deleted file mode 100755 index 84e7a07e1..000000000 --- a/lib/minz/FrontController.php +++ /dev/null @@ -1,123 +0,0 @@ -<?php -# ***** BEGIN LICENSE BLOCK ***** -# MINZ - a free PHP Framework like Zend Framework -# Copyright (C) 2011 Marien Fressinaud -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# ***** END LICENSE BLOCK ***** - -/** - * La classe FrontController est le noyau du framework, elle lance l'application - * Elle est appelée en général dans le fichier index.php à la racine du serveur - */ -class FrontController { - protected $dispatcher; - protected $router; - - /** - * Constructeur - * Initialise le router et le dispatcher - */ - public function __construct () { - $this->loadLib (); - - if (LOG_PATH === false) { - $this->killApp ('Path doesn\'t exist : LOG_PATH'); - } - - try { - Configuration::init (); - - Request::init (); - - $this->router = new Router (); - $this->router->init (); - } catch (RouteNotFoundException $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); - Error::error ( - 404, - array ('error' => array ($e->getMessage ())) - ); - } catch (MinzException $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); - $this->killApp (); - } - - $this->dispatcher = Dispatcher::getInstance ($this->router); - } - - /** - * Inclue les fichiers de la librairie - */ - private function loadLib () { - require ('ActionController.php'); - require ('Cache.php'); - require ('Configuration.php'); - require ('Dispatcher.php'); - require ('Error.php'); - require ('Helper.php'); - require ('Log.php'); - require ('Model.php'); - require ('Paginator.php'); - require ('Request.php'); - require ('Response.php'); - require ('Router.php'); - require ('Session.php'); - require ('Translate.php'); - require ('Url.php'); - require ('View.php'); - - require ('dao/Model_pdo.php'); - require ('dao/Model_txt.php'); - require ('dao/Model_array.php'); - - require ('exceptions/MinzException.php'); - } - - /** - * Démarre l'application (lance le dispatcher et renvoie la réponse - */ - public function run () { - try { - $this->dispatcher->run (); - Response::send (); - } catch (MinzException $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); - - if ($e instanceof FileNotExistException || - $e instanceof ControllerNotExistException || - $e instanceof ControllerNotActionControllerException || - $e instanceof ActionException) { - Error::error ( - 404, - array ('error' => array ($e->getMessage ())), - true - ); - } else { - $this->killApp (); - } - } - } - - /** - * Permet d'arrêter le programme en urgence - */ - private function killApp ($txt = '') { - if ($txt == '') { - $txt = 'See logs files'; - } - exit ('### Application problem ###'."\n".$txt); - } -} diff --git a/lib/minz/Helper.php b/lib/minz/Helper.php deleted file mode 100755 index 4f64ba218..000000000 --- a/lib/minz/Helper.php +++ /dev/null @@ -1,22 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * La classe Helper représente une aide pour des tâches récurrentes - */ -class Helper { - /** - * Annule les effets des magic_quotes pour une variable donnée - * @param $var variable à traiter (tableau ou simple variable) - */ - public static function stripslashes_r ($var) { - if (is_array ($var)){ - return array_map (array ('Helper', 'stripslashes_r'), $var); - } else { - return stripslashes($var); - } - } -} diff --git a/lib/minz/Request.php b/lib/minz/Request.php deleted file mode 100644 index bde17f988..000000000 --- a/lib/minz/Request.php +++ /dev/null @@ -1,196 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * Request représente la requête http - */ -class Request { - private static $controller_name = ''; - private static $action_name = ''; - private static $params = array (); - - private static $default_controller_name = 'index'; - private static $default_action_name = 'index'; - - public static $reseted = true; - - /** - * Getteurs - */ - public static function controllerName () { - return self::$controller_name; - } - public static function actionName () { - return self::$action_name; - } - public static function params () { - return self::$params; - } - public static function param ($key, $default = false, $specialchars = false) { - if (isset (self::$params[$key])) { - $p = self::$params[$key]; - if(is_object($p) || $specialchars) { - return $p; - } elseif(is_array($p)) { - return array_map('htmlspecialchars', $p); //TODO: Should use explicit UTF-8 - } else { - return htmlspecialchars($p, ENT_NOQUOTES, 'UTF-8'); - } - } else { - return $default; - } - } - public static function defaultControllerName () { - return self::$default_controller_name; - } - public static function defaultActionName () { - return self::$default_action_name; - } - - /** - * Setteurs - */ - public static function _controllerName ($controller_name) { - self::$controller_name = $controller_name; - } - public static function _actionName ($action_name) { - self::$action_name = $action_name; - } - public static function _params ($params) { - if (!is_array($params)) { - $params = array ($params); - } - - self::$params = $params; - } - public static function _param ($key, $value = false) { - if ($value === false) { - unset (self::$params[$key]); - } else { - self::$params[$key] = $value; - } - } - - /** - * Initialise la Request - */ - public static function init () { - self::magicQuotesOff (); - } - - /** - * Retourn le nom de domaine du site - */ - public static function getDomainName () { - return $_SERVER['HTTP_HOST']; - } - - /** - * Détermine la base de l'url - * @return la base de l'url - */ - public static function getBaseUrl () { - return Configuration::baseUrl (); - } - - /** - * Récupère l'URI de la requête - * @return l'URI - */ - public static function getURI () { - if (isset ($_SERVER['REQUEST_URI'])) { - $base_url = self::getBaseUrl (); - $uri = $_SERVER['REQUEST_URI']; - - $len_base_url = strlen ($base_url); - $real_uri = substr ($uri, $len_base_url); - } else { - $real_uri = ''; - } - - return $real_uri; - } - - /** - * Relance une requête - * @param $url l'url vers laquelle est relancée la requête - * @param $redirect si vrai, force la redirection http - * > sinon, le dispatcher recharge en interne - */ - public static function forward ($url = array (), $redirect = false) { - $url = Url::checkUrl ($url); - - if ($redirect) { - header ('Location: ' . Url::display ($url, 'php')); - exit (); - } else { - self::$reseted = true; - - self::_controllerName ($url['c']); - self::_actionName ($url['a']); - self::_params (array_merge ( - self::$params, - $url['params'] - )); - } - } - - /** - * Permet de récupérer une variable de type $_GET - * @param $param nom de la variable - * @param $default valeur par défaut à attribuer à la variable - * @return $_GET[$param] - * $_GET si $param = false - * $default si $_GET[$param] n'existe pas - */ - public static function fetchGET ($param = false, $default = false) { - if ($param === false) { - return $_GET; - } elseif (isset ($_GET[$param])) { - return $_GET[$param]; - } else { - return $default; - } - } - - /** - * Permet de récupérer une variable de type $_POST - * @param $param nom de la variable - * @param $default valeur par défaut à attribuer à la variable - * @return $_POST[$param] - * $_POST si $param = false - * $default si $_POST[$param] n'existe pas - */ - public static function fetchPOST ($param = false, $default = false) { - if ($param === false) { - return $_POST; - } elseif (isset ($_POST[$param])) { - return $_POST[$param]; - } else { - return $default; - } - } - - /** - * Méthode désactivant les magic_quotes pour les variables - * $_GET - * $_POST - * $_COOKIE - */ - private static function magicQuotesOff () { - if (get_magic_quotes_gpc ()) { - $_GET = Helper::stripslashes_r ($_GET); - $_POST = Helper::stripslashes_r ($_POST); - $_COOKIE = Helper::stripslashes_r ($_COOKIE); - } - } - - public static function isPost () { - return !empty ($_POST) || !empty ($_FILES); - } -} - - diff --git a/lib/minz/Response.php b/lib/minz/Response.php deleted file mode 100644 index fcf53c5b1..000000000 --- a/lib/minz/Response.php +++ /dev/null @@ -1,60 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * Response représente la requête http renvoyée à l'utilisateur - */ -class Response { - private static $header = 'HTTP/1.0 200 OK'; - private static $body = ''; - - /** - * Mets à jour le body de la Response - * @param $text le texte à incorporer dans le body - */ - public static function setBody ($text) { - self::$body = $text; - } - - /** - * Mets à jour le header de la Response - * @param $code le code HTTP, valeurs possibles - * - 200 (OK) - * - 403 (Forbidden) - * - 404 (Forbidden) - * - 500 (Forbidden) -> par défaut si $code erroné - * - 503 (Forbidden) - */ - public static function setHeader ($code) { - switch ($code) { - case 200 : - self::$header = 'HTTP/1.0 200 OK'; - break; - case 403 : - self::$header = 'HTTP/1.0 403 Forbidden'; - break; - case 404 : - self::$header = 'HTTP/1.0 404 Not Found'; - break; - case 500 : - self::$header = 'HTTP/1.0 500 Internal Server Error'; - break; - case 503 : - self::$header = 'HTTP/1.0 503 Service Unavailable'; - break; - default : - self::$header = 'HTTP/1.0 500 Internal Server Error'; - } - } - - /** - * Envoie la Response à l'utilisateur - */ - public static function send () { - header (self::$header); - echo self::$body; - } -} diff --git a/lib/minz/Router.php b/lib/minz/Router.php deleted file mode 100755 index c5d6f5baa..000000000 --- a/lib/minz/Router.php +++ /dev/null @@ -1,209 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * La classe Router gère le routage de l'application - * Les routes sont définies dans APP_PATH.'/configuration/routes.php' - */ -class Router { - const ROUTES_PATH_NAME = '/configuration/routes.php'; - - private $routes = array (); - - /** - * Constructeur - * @exception FileNotExistException si ROUTES_PATH_NAME n'existe pas - * et que l'on utilise l'url rewriting - */ - public function __construct () { - if (Configuration::useUrlRewriting ()) { - if (file_exists (APP_PATH . self::ROUTES_PATH_NAME)) { - $routes = include ( - APP_PATH . self::ROUTES_PATH_NAME - ); - - if (!is_array ($routes)) { - $routes = array (); - } - - $this->routes = array_map ( - array ('Url', 'checkUrl'), - $routes - ); - } else { - throw new FileNotExistException ( - self::ROUTES_PATH_NAME, - MinzException::ERROR - ); - } - } - } - - /** - * Initialise le Router en déterminant le couple Controller / Action - * Mets à jour la Request - * @exception RouteNotFoundException si l'uri n'est pas présente dans - * > la table de routage - */ - public function init () { - $url = array (); - - if (Configuration::useUrlRewriting ()) { - try { - $url = $this->buildWithRewriting (); - } catch (RouteNotFoundException $e) { - throw $e; - } - } else { - $url = $this->buildWithoutRewriting (); - } - - $url['params'] = array_merge ( - $url['params'], - Request::fetchPOST () - ); - - Request::forward ($url); - } - - /** - * Retourne un tableau représentant l'url passée par la barre d'adresses - * Ne se base PAS sur la table de routage - * @return tableau représentant l'url - */ - public function buildWithoutRewriting () { - $url = array (); - - $url['c'] = Request::fetchGET ( - 'c', - Request::defaultControllerName () - ); - $url['a'] = Request::fetchGET ( - 'a', - Request::defaultActionName () - ); - $url['params'] = Request::fetchGET (); - - // post-traitement - unset ($url['params']['c']); - unset ($url['params']['a']); - - return $url; - } - - /** - * Retourne un tableau représentant l'url passée par la barre d'adresses - * Se base sur la table de routage - * @return tableau représentant l'url - * @exception RouteNotFoundException si l'uri n'est pas présente dans - * > la table de routage - */ - public function buildWithRewriting () { - $url = array (); - $uri = Request::getURI (); - $find = false; - - foreach ($this->routes as $route) { - $regex = '*^' . $route['route'] . '$*'; - if (preg_match ($regex, $uri, $matches)) { - $url['c'] = $route['controller']; - $url['a'] = $route['action']; - $url['params'] = $this->getParams ( - $route['params'], - $matches - ); - $find = true; - break; - } - } - - if (!$find && $uri != '/') { - throw new RouteNotFoundException ( - $uri, - MinzException::ERROR - ); - } - - // post-traitement - $url = Url::checkUrl ($url); - - return $url; - } - - /** - * Retourne l'uri d'une url en se basant sur la table de routage - * @param l'url sous forme de tableau - * @return l'uri formatée (string) selon une route trouvée - */ - public function printUriRewrited ($url) { - $route = $this->searchRoute ($url); - - if ($route !== false) { - return $this->replaceParams ($route, $url['params']); - } - - return ''; - } - - /** - * Recherche la route correspondante à une url - * @param l'url sous forme de tableau - * @return la route telle que spécifiée dans la table de routage, - * false si pas trouvée - */ - public function searchRoute ($url) { - foreach ($this->routes as $route) { - if ($route['controller'] == $url['c'] - && $route['action'] == $url['a']) { - // calcule la différence des tableaux de params - $params = array_flip ($route['params']); - $difference_params = array_diff_key ( - $params, - $url['params'] - ); - - // vérifie que pas de différence - // et le cas où $params est vide et pas $url['params'] - if (empty ($difference_params) - && (!empty ($params) || empty ($url['params']))) { - return $route; - } - } - } - - return false; - } - - /** - * Récupère un tableau dont - * - les clés sont définies dans $params_route - * - les valeurs sont situées dans $matches - * Le tableau $matches est décalé de +1 par rapport à $params_route - */ - private function getParams($params_route, $matches) { - $params = array (); - - for ($i = 0; $i < count ($params_route); $i++) { - $param = $params_route[$i]; - $params[$param] = $matches[$i + 1]; - } - - return $params; - } - - /** - * Remplace les éléments de la route par les valeurs contenues dans $params - */ - private function replaceParams ($route, $params_replace) { - $uri = $route['route']; - $params = array(); - foreach($route['params'] as $param) { - $uri = preg_replace('#\((.+)\)#U', $params_replace[$param], $uri, 1); - } - - return stripslashes($uri); - } -} diff --git a/lib/minz/Session.php b/lib/minz/Session.php deleted file mode 100755 index f9c9c6754..000000000 --- a/lib/minz/Session.php +++ /dev/null @@ -1,78 +0,0 @@ -<?php - -/** - * La classe Session gère la session utilisateur - * C'est un singleton - */ -class Session { - /** - * $session stocke les variables de session - */ - private static $session = array (); - - /** - * Initialise la session - */ - public static function init () { - // démarre la session - session_name (md5 (Configuration::selApplication ())); - session_start (); - - if (isset ($_SESSION)) { - self::$session = $_SESSION; - } - } - - - /** - * Permet de récupérer une variable de session - * @param $p le paramètre à récupérer - * @return la valeur de la variable de session, false si n'existe pas - */ - public static function param ($p, $default = false) { - if (isset (self::$session[$p])) { - $return = self::$session[$p]; - } else { - $return = $default; - } - - return $return; - } - - - /** - * Permet de créer ou mettre à jour une variable de session - * @param $p le paramètre à créer ou modifier - * @param $v la valeur à attribuer, false pour supprimer - */ - public static function _param ($p, $v = false) { - if ($v === false) { - unset ($_SESSION[$p]); - unset (self::$session[$p]); - } else { - $_SESSION[$p] = $v; - self::$session[$p] = $v; - - if($p == 'language') { - // reset pour remettre à jour le fichier de langue à utiliser - Translate::reset (); - } - } - } - - - /** - * Permet d'effacer une session - * @param $force si à false, n'efface pas le paramètre de langue - */ - public static function unset_session ($force = false) { - $language = self::param ('language'); - - session_unset (); - self::$session = array (); - - if (!$force) { - self::_param ('language', $language); - } - } -} diff --git a/lib/minz/dao/Model_array.php b/lib/minz/dao/Model_array.php deleted file mode 100755 index 0b9ccf071..000000000 --- a/lib/minz/dao/Model_array.php +++ /dev/null @@ -1,122 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * La classe Model_array représente le modèle interragissant avec les fichiers de type texte gérant des tableaux php - */ -class Model_array extends Model_txt { - /** - * $array Le tableau php contenu dans le fichier $nameFile - */ - protected $array = array (); - - /** - * Ouvre le fichier indiqué, charge le tableau dans $array et le $nameFile - * @param $nameFile le nom du fichier à ouvrir contenant un tableau - * Remarque : $array sera obligatoirement un tableau - */ - public function __construct ($nameFile) { - parent::__construct ($nameFile); - - if (!$this->getLock ('read')) { - throw new PermissionDeniedException ($this->filename); - } else { - $this->array = include ($this->filename); - $this->releaseLock (); - - if (!is_array ($this->array)) { - $this->array = array (); - } - - $this->array = $this->decodeArray ($this->array); - } - } - - /** - * Écrit un tableau dans le fichier $nameFile - * @param $array le tableau php à enregistrer - **/ - public function writeFile ($array) { - if (!$this->getLock ('write')) { - throw new PermissionDeniedException ($this->namefile); - } else { - $this->erase (); - - $this->writeLine ('<?php'); - $this->writeLine ('return ', false); - $this->writeArray ($array); - $this->writeLine (';'); - - $this->releaseLock (); - } - } - - private function writeArray ($array, $profondeur = 0) { - $tab = ''; - for ($i = 0; $i < $profondeur; $i++) { - $tab .= "\t"; - } - $this->writeLine ('array ('); - - foreach ($array as $key => $value) { - if (is_int ($key)) { - $this->writeLine ($tab . "\t" . $key . ' => ', false); - } else { - $this->writeLine ($tab . "\t" . '\'' . $key . '\'' . ' => ', false); - } - - if (is_array ($value)) { - $this->writeArray ($value, $profondeur + 1); - $this->writeLine (','); - } else { - if (is_numeric ($value)) { - $this->writeLine ($value . ','); - } else { - $this->writeLine ('\'' . addslashes ($value) . '\','); - } - } - } - - $this->writeLine ($tab . ')', false); - } - - private function decodeArray ($array) { - $new_array = array (); - - foreach ($array as $key => $value) { - if (is_array ($value)) { - $new_array[$key] = $this->decodeArray ($value); - } else { - $new_array[$key] = stripslashes ($value); - } - } - - return $new_array; - } - - private function getLock ($type) { - if ($type == 'write') { - $lock = LOCK_EX; - } else { - $lock = LOCK_SH; - } - - $count = 1; - while (!flock ($this->file, $lock) && $count <= 50) { - $count++; - } - - if ($count >= 50) { - return false; - } else { - return true; - } - } - - private function releaseLock () { - flock ($this->file, LOCK_UN); - } -} diff --git a/lib/minz/dao/Model_pdo.php b/lib/minz/dao/Model_pdo.php deleted file mode 100755 index fa44038db..000000000 --- a/lib/minz/dao/Model_pdo.php +++ /dev/null @@ -1,58 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * La classe Model_sql représente le modèle interragissant avec les bases de données - * Seul la connexion MySQL est prise en charge pour le moment - */ -class Model_pdo { - /** - * $bd variable représentant la base de données - */ - protected $bd; - - protected $prefix; - - /** - * Créé la connexion à la base de données à l'aide des variables - * HOST, BASE, USER et PASS définies dans le fichier de configuration - */ - public function __construct () { - $db = Configuration::dataBase (); - $driver_options = null; - - try { - $type = $db['type']; - if($type == 'mysql') { - $string = $type - . ':host=' . $db['host'] - . ';dbname=' . $db['base'] - . ';charset=utf8'; - $driver_options = array( - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8' - ); - } elseif($type == 'sqlite') { - $string = $type - . ':/' . PUBLIC_PATH - . '/data/' . $db['base'] . '.sqlite'; //TODO: DEBUG UTF-8 http://www.siteduzero.com/forum/sujet/sqlite-connexion-utf-8-18797 - } - - $this->bd = new PDO ( - $string, - $db['user'], - $db['password'], - $driver_options - ); - - $this->prefix = $db['prefix']; - } catch (Exception $e) { - throw new PDOConnectionException ( - $string, - $db['user'], MinzException::WARNING - ); - } - } -} diff --git a/lib/minz/dao/Model_txt.php b/lib/minz/dao/Model_txt.php deleted file mode 100755 index c9d5cfe77..000000000 --- a/lib/minz/dao/Model_txt.php +++ /dev/null @@ -1,77 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * La classe Model_txt représente le modèle interragissant avec les fichiers de type texte - */ -class Model_txt { - /** - * $file représente le fichier à ouvrir - */ - protected $file; - - /** - * $filename est le nom du fichier - */ - protected $filename; - - /** - * Ouvre un fichier dans $file - * @param $nameFile nom du fichier à ouvrir - * @param $mode mode d'ouverture du fichier ('a+' par défaut) - * @exception FileNotExistException si le fichier n'existe pas - * > ou ne peux pas être ouvert - */ - public function __construct ($nameFile, $mode = 'a+') { - $this->filename = $nameFile; - $this->file = @fopen ($this->filename, $mode); - - if (!$this->file) { - throw new FileNotExistException ( - $this->filename, - MinzException::WARNING - ); - } - } - - /** - * Lit une ligne de $file - * @return une ligne du fichier - */ - public function readLine () { - return fgets ($this->file); - } - - /** - * Écrit une ligne dans $file - * @param $line la ligne à écrire - */ - public function writeLine ($line, $newLine = true) { - $char = ''; - if ($newLine) { - $char = "\n"; - } - - fwrite ($this->file, $line . $char); - } - - /** - * Efface le fichier $file - * @return true en cas de succès, false sinon - */ - public function erase () { - return ftruncate ($this->file, 0); - } - - /** - * Ferme $file - */ - public function __destruct () { - if (isset ($this->file)) { - fclose ($this->file); - } - } -} diff --git a/lib/minz/exceptions/MinzException.php b/lib/minz/exceptions/MinzException.php deleted file mode 100644 index 8fca5ec16..000000000 --- a/lib/minz/exceptions/MinzException.php +++ /dev/null @@ -1,94 +0,0 @@ -<?php - -class MinzException extends Exception { - const ERROR = 0; - const WARNING = 10; - const NOTICE = 20; - - public function __construct ($message, $code = self::ERROR) { - if ($code != MinzException::ERROR - && $code != MinzException::WARNING - && $code != MinzException::NOTICE) { - $code = MinzException::ERROR; - } - - parent::__construct ($message, $code); - } -} - -class PermissionDeniedException extends MinzException { - public function __construct ($file_name, $code = self::ERROR) { - $message = 'Permission is denied for `' . $file_name.'`'; - - parent::__construct ($message, $code); - } -} -class FileNotExistException extends MinzException { - public function __construct ($file_name, $code = self::ERROR) { - $message = 'File doesn\'t exist : `' . $file_name.'`'; - - parent::__construct ($message, $code); - } -} -class BadConfigurationException extends MinzException { - public function __construct ($part_missing, $code = self::ERROR) { - $message = '`' . $part_missing - . '` in the configuration file is missing'; - - parent::__construct ($message, $code); - } -} -class ControllerNotExistException extends MinzException { - public function __construct ($controller_name, $code = self::ERROR) { - $message = 'Controller `' . $controller_name - . '` doesn\'t exist'; - - parent::__construct ($message, $code); - } -} -class ControllerNotActionControllerException extends MinzException { - public function __construct ($controller_name, $code = self::ERROR) { - $message = 'Controller `' . $controller_name - . '` isn\'t instance of ActionController'; - - parent::__construct ($message, $code); - } -} -class ActionException extends MinzException { - public function __construct ($controller_name, $action_name, $code = self::ERROR) { - $message = '`' . $action_name . '` cannot be invoked on `' - . $controller_name . '`'; - - parent::__construct ($message, $code); - } -} -class RouteNotFoundException extends MinzException { - private $route; - - public function __construct ($route, $code = self::ERROR) { - $this->route = $route; - - $message = 'Route `' . $route . '` not found'; - - parent::__construct ($message, $code); - } - - public function route () { - return $this->route; - } -} -class PDOConnectionException extends MinzException { - public function __construct ($string_connection, $user, $code = self::ERROR) { - $message = 'Access to database is denied for `' . $user . '`' - . ' (`' . $string_connection . '`)'; - - parent::__construct ($message, $code); - } -} -class CurrentPagePaginationException extends MinzException { - public function __construct ($page) { - $message = 'Page number `' . $page . '` doesn\'t exist'; - - parent::__construct ($message, self::ERROR); - } -} diff --git a/lib/password_compat.php b/lib/password_compat.php new file mode 100644 index 000000000..e8ec02885 --- /dev/null +++ b/lib/password_compat.php @@ -0,0 +1,279 @@ +<?php +/** + * A Compatibility library with PHP 5.5's simplified password hashing API. + * + * @author Anthony Ferrara <ircmaxell@php.net> + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @copyright 2012 The Authors + */ + +namespace { + +if (!defined('PASSWORD_DEFAULT')) { + + define('PASSWORD_BCRYPT', 1); + define('PASSWORD_DEFAULT', PASSWORD_BCRYPT); + + /** + * Hash the password using the specified algorithm + * + * @param string $password The password to hash + * @param int $algo The algorithm to use (Defined by PASSWORD_* constants) + * @param array $options The options for the algorithm to use + * + * @return string|false The hashed password, or false on error. + */ + function password_hash($password, $algo, array $options = array()) { + if (!function_exists('crypt')) { + trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING); + return null; + } + if (!is_string($password)) { + trigger_error("password_hash(): Password must be a string", E_USER_WARNING); + return null; + } + if (!is_int($algo)) { + trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING); + return null; + } + $resultLength = 0; + switch ($algo) { + case PASSWORD_BCRYPT: + // Note that this is a C constant, but not exposed to PHP, so we don't define it here. + $cost = 10; + if (isset($options['cost'])) { + $cost = $options['cost']; + if ($cost < 4 || $cost > 31) { + trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING); + return null; + } + } + // The length of salt to generate + $raw_salt_len = 16; + // The length required in the final serialization + $required_salt_len = 22; + $hash_format = sprintf("$2y$%02d$", $cost); + // The expected length of the final crypt() output + $resultLength = 60; + break; + default: + trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING); + return null; + } + $salt_requires_encoding = false; + if (isset($options['salt'])) { + switch (gettype($options['salt'])) { + case 'NULL': + case 'boolean': + case 'integer': + case 'double': + case 'string': + $salt = (string) $options['salt']; + break; + case 'object': + if (method_exists($options['salt'], '__tostring')) { + $salt = (string) $options['salt']; + break; + } + case 'array': + case 'resource': + default: + trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING); + return null; + } + if (PasswordCompat\binary\_strlen($salt) < $required_salt_len) { + trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", PasswordCompat\binary\_strlen($salt), $required_salt_len), E_USER_WARNING); + return null; + } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) { + $salt_requires_encoding = true; + } + } else { + $buffer = ''; + $buffer_valid = false; + if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) { + $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM); + if ($buffer) { + $buffer_valid = true; + } + } + if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) { + $buffer = openssl_random_pseudo_bytes($raw_salt_len); + if ($buffer) { + $buffer_valid = true; + } + } + if (!$buffer_valid && @is_readable('/dev/urandom')) { + $f = fopen('/dev/urandom', 'r'); + $read = PasswordCompat\binary\_strlen($buffer); + while ($read < $raw_salt_len) { + $buffer .= fread($f, $raw_salt_len - $read); + $read = PasswordCompat\binary\_strlen($buffer); + } + fclose($f); + if ($read >= $raw_salt_len) { + $buffer_valid = true; + } + } + if (!$buffer_valid || PasswordCompat\binary\_strlen($buffer) < $raw_salt_len) { + $bl = PasswordCompat\binary\_strlen($buffer); + for ($i = 0; $i < $raw_salt_len; $i++) { + if ($i < $bl) { + $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255)); + } else { + $buffer .= chr(mt_rand(0, 255)); + } + } + } + $salt = $buffer; + $salt_requires_encoding = true; + } + if ($salt_requires_encoding) { + // encode string with the Base64 variant used by crypt + $base64_digits = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + $bcrypt64_digits = + './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + $base64_string = base64_encode($salt); + $salt = strtr(rtrim($base64_string, '='), $base64_digits, $bcrypt64_digits); + } + $salt = PasswordCompat\binary\_substr($salt, 0, $required_salt_len); + + $hash = $hash_format . $salt; + + $ret = crypt($password, $hash); + + if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != $resultLength) { + return false; + } + + return $ret; + } + + /** + * Get information about the password hash. Returns an array of the information + * that was used to generate the password hash. + * + * array( + * 'algo' => 1, + * 'algoName' => 'bcrypt', + * 'options' => array( + * 'cost' => 10, + * ), + * ) + * + * @param string $hash The password hash to extract info from + * + * @return array The array of information about the hash. + */ + function password_get_info($hash) { + $return = array( + 'algo' => 0, + 'algoName' => 'unknown', + 'options' => array(), + ); + if (PasswordCompat\binary\_substr($hash, 0, 4) == '$2y$' && PasswordCompat\binary\_strlen($hash) == 60) { + $return['algo'] = PASSWORD_BCRYPT; + $return['algoName'] = 'bcrypt'; + list($cost) = sscanf($hash, "$2y$%d$"); + $return['options']['cost'] = $cost; + } + return $return; + } + + /** + * Determine if the password hash needs to be rehashed according to the options provided + * + * If the answer is true, after validating the password using password_verify, rehash it. + * + * @param string $hash The hash to test + * @param int $algo The algorithm used for new password hashes + * @param array $options The options array passed to password_hash + * + * @return boolean True if the password needs to be rehashed. + */ + function password_needs_rehash($hash, $algo, array $options = array()) { + $info = password_get_info($hash); + if ($info['algo'] != $algo) { + return true; + } + switch ($algo) { + case PASSWORD_BCRYPT: + $cost = isset($options['cost']) ? $options['cost'] : 10; + if ($cost != $info['options']['cost']) { + return true; + } + break; + } + return false; + } + + /** + * Verify a password against a hash using a timing attack resistant approach + * + * @param string $password The password to verify + * @param string $hash The hash to verify against + * + * @return boolean If the password matches the hash + */ + function password_verify($password, $hash) { + if (!function_exists('crypt')) { + trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING); + return false; + } + $ret = crypt($password, $hash); + if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != PasswordCompat\binary\_strlen($hash) || PasswordCompat\binary\_strlen($ret) <= 13) { + return false; + } + + $status = 0; + for ($i = 0; $i < PasswordCompat\binary\_strlen($ret); $i++) { + $status |= (ord($ret[$i]) ^ ord($hash[$i])); + } + + return $status === 0; + } +} + +} + +namespace PasswordCompat\binary { + /** + * Count the number of bytes in a string + * + * We cannot simply use strlen() for this, because it might be overwritten by the mbstring extension. + * In this case, strlen() will count the number of *characters* based on the internal encoding. A + * sequence of bytes might be regarded as a single multibyte character. + * + * @param string $binary_string The input string + * + * @internal + * @return int The number of bytes + */ + function _strlen($binary_string) { + if (function_exists('mb_strlen')) { + return mb_strlen($binary_string, '8bit'); + } + return strlen($binary_string); + } + + /** + * Get a substring based on byte limits + * + * @see _strlen() + * + * @param string $binary_string The input string + * @param int $start + * @param int $length + * + * @internal + * @return string The substring + */ + function _substr($binary_string, $start, $length) { + if (function_exists('mb_substr')) { + return mb_substr($binary_string, $start, $length, '8bit'); + } + return substr($binary_string, $start, $length); + } + +} |
