I have two PHP variables that seem to have the same value, $dynamic which is produced from a function and $test which is just text in single quotes.
When I print their values they're the same, but the $dynamic fails in regex functions.
Is there any way to make a PHP string 'not dynamic' and just plain old text
$test = 'username'; //Evaluates OK
die($test.' '.$dynamic); //Produces: username username
Below is the function, $pattern is the issue. This is actually from an earlier question, but I've since narrowed the issue to the evaluation of $pattern. This also seems to be an issue with other functions besides preg_replace_callback.
public function output() {
if (!file_exists($this->file)) {
return "Error loading template file ($this->file).<br />";
}
$output = file_get_contents($this->file);
foreach ($this->values as $key => $value) {
$tagToReplace = "[#$key]";
$output = str_replace($tagToReplace, $value, $output);
$dynamic = preg_quote($key);
$test = 'username';
$pattern = '%\[if #'.$test.'\](.*?)\[/if\]%'; // produces: %\[if #username\](.*?)\[/if\]%
$output = preg_replace_callback($pattern, array($this, 'if_replace'), $output);
}
return $output;
}
It is possible $dynamic may contain invisible characters. Loop over the string and print the character code of each character (use ord) - and compare that with the result for "username".
var_dump might also give hints as it will show the length of the string as well.
Related
I recently updated my site to PHP 5.6, and since doing that, I started getting errors with the e modifier.
Here is the function causing the issue:
function blockPrepareDisplay() {
static $search = array('/(.)#(.)/se');
static $replace = array('"&#" .sprintf("%03d", ord("\\1")) .";#&#" .sprintf("%03d", ord("\\2")) . ";";');
$resarray = array();
foreach (func_get_args() as $var) {
$var = htmlspecialchars($var, ENT_QUOTES);// Prepare var
$var = preg_replace($search, $replace, $var);
$var = preg_replace('/&#/', '&#', $var);
$var = str_replace(" "," ",$var);
$var = str_replace("&","&",$var);
$resarray[] = $var;// Add to array
}
if (func_num_args() == 1) {// Return vars
return $resarray[0];
} else {
return $resarray;
}
}
I understand that the line static $search = array('/(.)#(.)/se'); is the line with the e modifier and I know that its deprecated. A friend explained that to me, and explained that I wound need to replace the preg_replace with a preg_replace_callback. I have looked over at php.net and all over here, but I think it made me more confuesd than ever.
I have tried for a couple of weeks now many different things and I got one to stop spitting the error, but I am positive that its wrong, and I prefer it to be right than wrong. I did this in replacement of the preg_replace
$var = preg_replace_callback(
$search,
function($replace){
foreach($replace as $replaces){
return $replaces;
}
},
$var
);
Instead of the $var = preg_replace($search, $replace, $var);.
I also did remove the e modifier. Can anyone point out my mistake and give an example of how I should have this to be right?
The argument of the callback function is the array of matching groups.
I don't understand why you are using an array for the search and the replacement parts, I'd do:
$var = "abc#def.org";
$var = preg_replace_callback(
'/(.)#(.)/',
function($match) {
return sprintf("&#%03d;#&#%03d;", ord($match[1]), ord($match[2]));
},
$var
);
echo $var,"\n";
Output:
abc#def.org
I have a code that will produce array of strings.... now my problem is i need to substr each result of the array but i think array is not allowed to be used in substr...
please help:
CODE:
<?php
$file = 'upload/filter.txt';
$searchfor = $_POST['search'];
$btn = $_POST['button'];
$sum = 0;
if($btn == 'search') {
//prevents the browser from parsing this as HTML.
header('Content-Type: text/plain');
// get the file contents, assuming the file to be readable (and exist)
$contents = file_get_contents($file);
// escape special characters in the query
$pattern = preg_quote($searchfor, '/');
// finalise the regular expression, matching the whole line
$pattern = "/^.*$pattern.*\$/m";
// search, and store all matching occurences in $matches
if(preg_match_all($pattern, $contents, $matches)){
echo "Found matches:\n";
$result = implode("\n", $matches[0]);
echo $result;
}
else{
echo "No matches found";
}
}
?>
The $matches there is the array... i need to substr each result of the $matches
you can use array_walk:
function fcn(&$item) {
$item = substr(..do what you want here ...);
}
array_walk($matches, "fcn");
Proper use of array_walk
array_walk( $matches, substr(your area));
Array_map accepts several arrays
array_map(substr(your area), $matches1, $origarray2);
in your case
array_map(substr(your area), $matches);
Read more:
array_map
array_walk
To find a sub string in an array I use this function on a production site, works perfectly.
I convert the array to a collection because it's easier to manage.
public function substrInArray($substr, Array $array) {
$substr = strtolower($substr);
$array = collect($array); // convert array to collection
return $body_types->map(function ($array_item) {
return strtolower($array_item);
})->filter(function ($array_item) use ($substr) {
return substr_count($array_item, $substr);
})->keys()->first();
}
This will return the key from the first match, it's just an example you can tinker. Returns null if nothing found.
In perl, I can do: 1 while $var =~ s/a/b/;, and it will replace all a with b. In many cases, I would use it more like 1 while $var =~ s/^"(.*)"$/$1/; to remove all pairs of double quotes around a string.
Is there a way to do something similar to this in PHP, without having to do
while (preg_match('/^"(.*)"$/', $var)) {
$var = preg_replace('/^"(.*)"$/', '$1', $var, 1);
}
Because apparently,
while ($var = preg_replace('/^"(.*)"$/', '$1', $var, 1)) { 1; }
doesn't work.
EDIT: The specific situation I'm working in involves replacing values in a string with values from an associative array:
$text = "This is [site_name], home of the [people_type]".
$array = ('site_name' => 'StackOverflow.com', 'people_type' => 'crazy coders');
where I would be doing:
while (preg_match('/\[.*?\]/', $text)) {
$text = preg_replace('/\[(.*?)\]/', '$array[\'$1\']', $text, 1);
}
with the intended output being 'This is StackOverflow.com, home of the crazy coders'
preg_replace('#\[(.*?)\]#e', "\$array['$1']", $text);
In all of the cases, you can get rid of the loop by (e.g.) using the /g global replace option or rewriting the regexp:
$var =~ s/a/b/g;
$var =~ s/^("+)(.*)\1$/$2/;
The same patterns should work in PHP. You can also get rid of the $limit argument to preg_replace:
$text = preg_replace('/\[(.*?)\]/e', '$array[\'$1\']', $text);
Regular expressions can handle their own loops. Looping outside the RE is inefficient, since the RE has to process text it already processed in previous iterations.
Could something like this work?
$var = preg_replace('/^("+)(.*)\1$', '$2', $var, 1);
What does your input data look like?
Because you're checking for double quotes only at the head and tail of the string. If that's accurate, then you don't need to capture a backreference at all. Also, that would make sending 1 as the 4th parameter completely superfluous.
$var = '"foo"';
// This works
echo preg_replace( '/^"(.*)"$/', '$1', $var );
// So does this
echo preg_replace( '/^"|"$/', '', $var );
But if your input data looks different, that would change my answer.
EDIT
Here's my take on your actual data
class VariableExpander
{
protected $source;
public function __construct( array $source )
{
$this->setSource( $source );
}
public function setSource( array $source )
{
$this->source = $source;
}
public function parse( $input )
{
return preg_replace_callback( '/\[([a-z_]+)\]/i', array( $this, 'expand' ), $input );
}
protected function expand( $matches )
{
return isset( $this->source[$matches[1]] )
? $this->source[$matches[1]]
: '';
}
}
$text = "This is [site_name], home of the [people_type]";
$data = array(
'site_name' => 'StackOverflow.com'
, 'people_type' => 'crazy coders'
);
$ve = new VariableExpander( $data );
echo $ve->parse( $text );
The class is just for encapsulation - you could do this in a structured way if you wanted.
Use do-while:
do {
$var = preg_replace('/^"(.*)"$/', "$1", $var, 1, $count);
} while ($count == 1);
Requires at least php-5.1.0 due to its use of $count.
You could also write
do {
$last = $var;
$var = preg_replace('/^"(.*)"$/', "$1", $var);
} while ($last != $var);
I've the following method which allows me to protect MySQL entities:
public function Tick($string)
{
$string = explode('.', str_replace('`', '', $string));
foreach ($string as $key => $value)
{
if ($value != '*')
{
$string[$key] = '`' . trim($value) . '`';
}
}
return implode('.', $string);
}
This works fairly well for the use that I make of it.
It protects database, table, field names and even the * operator, however now I also want it to protect function calls, ie:
AVG(database.employees.salary)
Should become:
AVG(`database`.`employees`.`salary`) and not `AVG(database`.`employees`.`salary)`
How should I go about this? Should I use regular expressions?
Also, how can I support more advanced stuff, from:
MAX(AVG(database.table.field1), MAX(database.table.field2))
To:
MAX(AVG(`database`.`table`.`field1`), MAX(`database`.`table`.`field2`))
Please keep in mind that I want to keep this method as simple/fast as possible, since it pretty much iterates over all the entity names in my database.
If this is quoting parts of an SQL statement, and they have only complexity that you descibe, a RegEx is a great approach. On the other hand, if you need to do this to full SQL statements, or simply more complicated components of statements (such as "MAX(AVG(val),MAX(val2))"), you will need to tokenize or parse the string and have a more sophisticated understanding of it to do this quoting accurately.
Given the regular expression approach, you may find it easier to break the function name out as one step, and then use your current code to quote the database/table/column names. This can be done in one RE, but it will be tricker to get right.
Either way, I'd highly recommend writing a few unit test cases. In fact, this is an ideal situation for this approach: it's easy to write the tests, you have some existing cases that work (which you don't want to break), and you have just one more case to add.
Your test can start as simply as:
assert '`ticked`' == Tick('ticked');
assert '`table`.`ticked`' == Tick('table.ticked');
assert 'db`.`table`.`ticked`' == Tick('db.table.ticked');
And then add:
assert 'FN(`ticked`)' == Tick('FN(ticked)');
etc.
Using the test case ndp gave I created a regex to do the hard work for you. The following regex will replace all word boundaries around words that are not followed by an opening parenthesis.
\b(\w+)\b(?!\()
The Tick() functionality would then be implemented in PHP as follows:
function Tick($string)
{
return preg_replace( '/\b(\w+)\b(?!\()/', '`\1`', $string );
}
It's generally a bad idea to pass the whole SQL to the function. That way, you'll always find a case when it doesn't work, unless you fully parse the SQL syntax.
Put the ticks to the names on some previous abstraction level, which makes up the SQL.
Before you explode your string on periods, check if the last character is a parenthesis. If so, this call is a function.
<?php
$string = str_replace('`', '', $string)
$function = "";
if (substr($string,-1) == ")") {
// Strip off function call first
$opening = strpos($string, "(");
$function = substr($string, 0, $opening+1);
$string = substr($string, $opening+1, -1);
}
// Do your existing parsing to $string
if ($function == "") {
// Put function back on string
$string = $function . $string . ")";
}
?>
If you need to cover more advanced situations, like using nested functions, or multiple functions in sequence in one "$string" variable, this would become a much more advanced function, and you'd best ask yourself why these elements aren't being properly ticked in the first place, and not need any further parsing.
EDIT: Updating for nested functions, as per original post edit
To have the above function deal with multiple nested functions, you likely need something that will 'unwrap' your nested functions. I haven't tested this, but the following function might get you on the right track.
<?php
function unwrap($str) {
$pos = strpos($str, "(");
if ($pos === false) return $str; // There's no function call here
$last_close = 0;
$cur_offset = 0; // Start at the beginning
while ($cur_offset <= strlen($str)) {
$first_close = strpos($str, ")", $offset); // Find first deep function
$pos = strrpos($str, "(", $first_close-1); // Find associated opening
if ($pos > $last_close) {
// This function is entirely after the previous function
$ticked = Tick(substr($str, $pos+1, $first_close-$pos)); // Tick the string inside
$str = substr($str, 0, $pos)."{".$ticked."}".substr($str,$first_close); // Replace parenthesis by curly braces temporarily
$first_close += strlen($ticked)-($first_close-$pos); // Shift parenthesis location due to new ticks being added
} else {
// This function wraps other functions; don't tick it
$str = substr($str, 0, $pos)."{".substr($str,$pos+1, $first_close-$pos)."}".substr($str,$first_close);
}
$last_close = $first_close;
$offset = $first_close+1;
}
// Replace the curly braces with parenthesis again
$str = str_replace(array("{","}"), array("(",")"), $str);
}
If you are adding the function calls in your code, as opposed to passing them in through a string-only interface, you can replace the string parsing with type checking:
function Tick($value) {
if (is_object($value)) {
$result = $value->value;
} else {
$result = '`'.str_replace(array('`', '.'), array('', '`.`'), $value).'`';
}
return $result;
}
class SqlFunction {
var $value;
function SqlFunction($function, $params) {
$sane = implode(', ', array_map('Tick', $params));
$this->value = "$function($sane)";
}
}
function Maximum($column) {
return new SqlFunction('MAX', array($column));
}
function Avg($column) {
return new SqlFunction('AVG', array($column));
}
function Greatest() {
$params = func_get_args();
return new SqlFunction('GREATEST', $params);
}
$cases = array(
"'simple'" => Tick('simple'),
"'table.field'" => Tick('table.field'),
"'table.*'" => Tick('table.*'),
"'evil`hack'" => Tick('evil`hack'),
"Avg('database.table.field')" => Tick(Avg('database.table.field')),
"Greatest(Avg('table.field1'), Maximum('table.field2'))" => Tick(Greatest(Avg('table.field1'), Maximum('table.field2'))),
);
echo "<table>";
foreach ($cases as $case => $result) {
echo "<tr><td>$case</td><td>$result</td></tr>";
}
echo "</table>";
This avoids any possible SQL injection while remaining legible to future readers of your code.
You could use preg_replace_callback() in conjunction with your Tick() method to skip at least one level of parens:
public function tick($str)
{
return preg_replace_callback('/[^()]*/', array($this, '_tick_replace_callback'), $str);
}
protected function _tick_replace_callback($str) {
$string = explode('.', str_replace('`', '', $string));
foreach ($string as $key => $value)
{
if ($value != '*')
{
$string[$key] = '`' . trim($value) . '`';
}
}
return implode('.', $string);
}
Are you generating the SQL Query or is it being passed to you? If you generating the query I wouldn't pass the whole query string just the parms/values you want to wrap in the backticks or what ever else you need.
EXAMPLE:
function addTick($var) {
return '`' . $var . '`';
}
$condition = addTick($condition);
$SQL = 'SELECT' . $what . '
FROM ' . $table . '
WHERE ' . $condition . ' = ' . $constraint;
This is just a mock but you get the idea that you can pass or loop through your code and build the query string rather than parsing the query string and adding your backticks.
I have a query string like the one given below:
http://localhost/project/viewMember.php?sort=Y2xhc3M=&class=Mw==&page=9
Now variable: page in query string can be anywhere within the query string either in beginning or middle or at end (like ?page=9 or &page=9& or &page=9).
Now, I need to remove page=9 from my query string and get a valid query string.
Lots of ways this could be done, including regex (as seen below). This is the most robust method I can think of, although it is more complex than the other methods.
Use parse_url to get the query string from the url (or write your own function).
Use parse_str to convert the query string into an array
unset the key that you don't want
Use http_build_query to reassemble the array into a query string
Then reconstruct the Url (if required)
Try:
preg_replace('/page=\d+/', '', $url);
Tried writing a function for this. Seems to work:
<?php
$url = "http://localhost/project/viewMember.php?sort=Y2xhc3M=&class=Mw==&page=9";
// prints http://localhost/project/viewMember.php?sort=Y2xhc3M=&class=Mw==
print changeURL($url) . "\n";
$url = "http://localhost/project/viewMember.php?sort=Y2xhc3M=&page=9&class=Mw==";
// prints http://localhost/project/viewMember.php?sort=Y2xhc3M=&class=Mw==
print changeURL($url) . "\n";
function changeURL($url)
{
$arr = parse_url($url);
$query = $arr['query'];
$pieces = explode('&',$query);
for($i=0;$i<count($pieces);$i++)
{
if(preg_match('/^page=\d+/',$pieces[$i]))
unset($pieces[$i]);
}
$query = implode('&',$pieces);
return "$arr[scheme]://$arr[host]$arr[user]$arr[pass]$arr[path]?$query$arr[fragment]";
}
?>
I created these two functions:
function cleanQuery($queryLabels){
// Filter all items in $_GET which are not in $queryLabels
if(!is_array($queryLabels)) return;
foreach($_GET as $queryLabel => $queryValue)
if(!in_array($queryLabel, $queryLabels) || ($queryValue == ''))
unset($_GET[$queryLabel]);
ksort($_GET);
}
function amendQuery($queryItems = array()){
$queryItems = array_merge($_GET, $queryItems);
ksort($queryItems);
return http_build_query($queryItems);
}
To remove the page part I would use
$_GET = amendQuery(array('page'=>null));
cleanQuery does the opposite. Pass in an array of the terms you want to keep.
function remove_part_of_qs($removeMe)
{
$qs = array();
foreach($_GET as $key => $value)
{
if($key != $removeMe)
{
$qs[$key] = $value;
}
}
return "?" . http_build_query($qs);
}
echo remove_part_of_qs("page");
This should do it, this is my first post on StackOverflow, so go easy!