From 318d7cdd5ccd2d686cbd6915ff3f58486248c02c Mon Sep 17 00:00:00 2001 From: Simon Holywell Date: Wed, 14 Nov 2012 16:03:52 +0000 Subject: Issue #57 _log_query errors when given raw ? or % Thanks to Jeff Roberson for his regex skills. --- README.markdown | 3 +- idiorm.php | 108 +++++++++++++++++++++++++++++++++++++++++++++++++- test/test_queries.php | 10 ++++- 3 files changed, 118 insertions(+), 3 deletions(-) diff --git a/README.markdown b/README.markdown index f1fb65a..2e5518d 100644 --- a/README.markdown +++ b/README.markdown @@ -25,7 +25,7 @@ Features Changelog --------- -#### 1.2.0 - release 2012-XX-XX +#### 1.2.0 - release 2012-11-14 * Setup composer for installation via packagist (j4mie/idiorm) * Add `order_by_expr` method [[sandermarechal](http://github.com/sandermarechal)] @@ -41,6 +41,7 @@ Changelog * Add `delete_many` method [[CBeerta](https://github.com/CBeerta)] * Allow unsetting of ORM parameters [[CBeerta](https://github.com/CBeerta)] * Add `find_array` to get the records as associative arrays [[Surt](https://github.com/Surt)] - closes issue #17 +* Fix bug in `_log_query` with `?` and `%` supplied in raw where statements etc. - closes issue #57 [[ridgerunner](https://github.com/ridgerunner)] #### 1.1.1 - release 2011-01-30 diff --git a/idiorm.php b/idiorm.php index d2200be..d2b4b17 100644 --- a/idiorm.php +++ b/idiorm.php @@ -278,7 +278,11 @@ $query = str_replace("%", "%%", $query); // Replace placeholders in the query for vsprintf - $query = str_replace("?", "%s", $query); + if(false !== strpos($query, "'") || false !== strpos($query, '"')) { + $query = IdiormString::str_replace_outside_quotes("?", "%s", $query); + } else { + $query = str_replace("?", "%s", $query); + } // Replace the question marks in the query with the parameters $bound_query = vsprintf($query, $parameters); @@ -1382,3 +1386,105 @@ } } + /** + * A class to handle str_replace operations that involve quoted strings + * @example IdiormString::str_replace_outside_quotes('?', '%s', 'columnA = "Hello?" AND columnB = ?'); + * @example IdiormString::value('columnA = "Hello?" AND columnB = ?')->replace_outside_quotes('?', '%s'); + * @author Jeff Roberson + * @author Simon Holywell + * @link http://stackoverflow.com/a/13370709/461813 StackOverflow answer + */ + class IdiormString { + protected $subject; + protected $search; + protected $replace; + + /** + * Get an easy to use instance of the class + * @param string $subject + * @return \self + */ + public static function value($subject) { + return new self($subject); + } + + /** + * Shortcut method: Replace all occurrences of the search string with the replacement + * string where they appear outside quotes. + * @param string $search + * @param string $replace + * @param string $subject + * @return string + */ + public static function str_replace_outside_quotes($search, $replace, $subject) { + return static::value($subject)->replace_outside_quotes($search, $replace); + } + + /** + * Set the base string object + * @param string $subject + */ + public function __construct($subject) { + $this->subject = (string) $subject; + } + + /** + * Replace all occurrences of the search string with the replacement + * string where they appear outside quotes + * @param string $search + * @param string $replace + * @return string + */ + public function replace_outside_quotes($search, $replace) { + $this->search = $search; + $this->replace = $replace; + return $this->_str_replace_outside_quotes(); + } + + /** + * Validate an input string and perform a replace on all ocurrences + * of $this->search with $this->replace + * @author Jeff Roberson + * @link http://stackoverflow.com/a/13370709/461813 StackOverflow answer + * @return string + */ + protected function _str_replace_outside_quotes(){ + $re_valid = '/ + # Validate string having embedded quoted substrings. + ^ # Anchor to start of string. + (?: # Zero or more string chunks. + "[^"\\\\]*(?:\\\\.[^"\\\\]*)*" # Either a double quoted chunk, + | \'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\' # or a single quoted chunk, + | [^\'"\\\\]+ # or an unquoted chunk (no escapes). + )* # Zero or more string chunks. + \z # Anchor to end of string. + /sx'; + if (!preg_match($re_valid, $this->subject)) // Exit if string is invalid. + exit("Error! String not valid."); + $re_parse = '/ + # Match one chunk of a valid string having embedded quoted substrings. + ( # Either $1: Quoted chunk. + "[^"\\\\]*(?:\\\\.[^"\\\\]*)*" # Either a double quoted chunk, + | \'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\' # or a single quoted chunk. + ) # End $1: Quoted chunk. + | ([^\'"\\\\]+) # or $2: an unquoted chunk (no escapes). + /sx'; + return preg_replace_callback($re_parse, array($this, '_str_replace_outside_quotes_cb'), $this->subject); + } + + /** + * Process each matching chunk from preg_replace_callback replacing + * each occurrence of $this->search with $this->replace + * @author Jeff Roberson + * @link http://stackoverflow.com/a/13370709/461813 StackOverflow answer + * @param array $matches + * @return string + */ + protected function _str_replace_outside_quotes_cb($matches) { + // Return quoted string chunks (in group $1) unaltered. + if ($matches[1]) return $matches[1]; + // Process only unquoted chunks (in group $2). + return preg_replace('/'. preg_quote($this->search, '/') .'/', + $this->replace, $matches[2]); + } + } \ No newline at end of file diff --git a/test/test_queries.php b/test/test_queries.php index 1f2acc6..bcd4c7e 100644 --- a/test/test_queries.php +++ b/test/test_queries.php @@ -296,7 +296,15 @@ $widget = ORM::for_table('widget')->select('widget.*')->find_one(); $expected = "SELECT `widget`.* FROM `widget` LIMIT 1"; Tester::check_equal("Issue #12 - incorrect quoting of column wildcard", $expected); - + + $widget = ORM::for_table('widget')->where_raw('username LIKE "ben%"')->find_many(); + $expected = 'SELECT * FROM `widget` WHERE username LIKE "ben%"'; + Tester::check_equal('Issue #57 - _log_query method raises a warning when query contains "%"', $expected); + + $widget = ORM::for_table('widget')->where_raw('comments LIKE "has been released?%"')->find_many(); + $expected = 'SELECT * FROM `widget` WHERE comments LIKE "has been released?%"'; + Tester::check_equal('Issue #57 - _log_query method raises a warning when query contains "?"', $expected); + // Tests that alter Idiorm's config are done last ORM::configure('id_column', 'primary_key'); -- cgit v1.2.3