Given a url, and a query string, how can I get the url resulting from the combination of the query string with the url?
I'm looking for functionality similar to .htaccess's qsa. I realize this would be fairly trivial to implement completely by hand, however are there built-in functions that deal with query strings which could either simplify or completely solve this?
Example input/result sets:
Url="http://www.example.com/index.php/page?a=1"
QS ="?b=2"
Result="http://www.example.com/index.php/page?a=1&b=2"
-
Url="page.php"
QS ="?b=2"
Result="page.php?b=2"
How about something that uses no PECL extensions and isn't a huge set of copied-and-pasted functions? It's still a tad complex because you're splicing together two query strings and want to do it in a way that isn't just $old .= $new;
We'll use parse_url to extract the query string from the desired url, parse_str to parse the query strings you wish to join, array_merge to join them together, and http_build_query to create the new, combined string for us.
// Parse the URL into components
$url = 'http://...';
$url_parsed = parse_url($url);
$new_qs_parsed = array();
// Grab our first query string
parse_str($url_parsed['query'], $new_qs_parsed);
// Here's the other query string
$other_query_string = 'that=this&those=these';
$other_qs_parsed = array();
parse_str($other_query_string, $other_qs_parsed);
// Stitch the two query strings together
$final_query_string_array = array_merge($new_qs_parsed, $other_qs_parsed);
$final_query_string = http_build_query($final_query_string_array);
// Now, our final URL:
$new_url = $url_parsed['scheme']
. '://'
. $url_parsed['host']
. $url_parsed['path']
. '?'
. $final_query_string;
You can get the query string part from url using:
$_SERVER['QUERY_STRING']
and then append it to url normally.
If you want to specify your own custom variables in query string, have a look at:
http_build_query
This is a series of functions taken from the WordPress "framework" that will do it, but this could quite well be too much:
add_query_arg()
/**
* Retrieve a modified URL query string.
*
* You can rebuild the URL and append a new query variable to the URL query by
* using this function. You can also retrieve the full URL with query data.
*
* Adding a single key & value or an associative array. Setting a key value to
* emptystring removes the key. Omitting oldquery_or_uri uses the $_SERVER
* value.
*
* #since 1.0
*
* #param mixed $param1 Either newkey or an associative_array
* #param mixed $param2 Either newvalue or oldquery or uri
* #param mixed $param3 Optional. Old query or uri
* #return string New URL query string.
*/
public function add_query_arg() {
$ret = '';
if ( is_array( func_get_arg(0) ) ) {
$uri = ( #func_num_args() < 2 || false === #func_get_arg( 1 ) ) ? $_SERVER['REQUEST_URI'] : #func_get_arg( 1 );
} else {
$uri = ( #func_num_args() < 3 || false === #func_get_arg( 2 ) ) ? $_SERVER['REQUEST_URI'] : #func_get_arg( 2 );
}
if ( $frag = strstr( $uri, '#' ) ) {
$uri = substr( $uri, 0, -strlen( $frag ) );
} else {
$frag = '';
}
if ( preg_match( '|^https?://|i', $uri, $matches ) ) {
$protocol = $matches[0];
$uri = substr( $uri, strlen( $protocol ) );
} else {
$protocol = '';
}
if ( strpos( $uri, '?' ) !== false ) {
$parts = explode( '?', $uri, 2 );
if ( 1 == count( $parts ) ) {
$base = '?';
$query = $parts[0];
} else {
$base = $parts[0] . '?';
$query = $parts[1];
}
} elseif ( !empty( $protocol ) || strpos( $uri, '=' ) === false ) {
$base = $uri . '?';
$query = '';
} else {
$base = '';
$query = $uri;
}
parse_str( $query, $qs );
if ( get_magic_quotes_gpc() )
$qs = format::stripslashes_deep( $qs );
$qs = format::urlencode_deep( $qs ); // this re-URL-encodes things that were already in the query string
if ( is_array( func_get_arg( 0 ) ) ) {
$kayvees = func_get_arg( 0 );
$qs = array_merge( $qs, $kayvees );
} else {
$qs[func_get_arg( 0 )] = func_get_arg( 1 );
}
foreach ( ( array ) $qs as $k => $v ) {
if ( $v === false )
unset( $qs[$k] );
}
$ret = http_build_query( $qs, '', '&' );
$ret = trim( $ret, '?' );
$ret = preg_replace( '#=(&|$)#', '$1', $ret );
$ret = $protocol . $base . $ret . $frag;
$ret = rtrim( $ret, '?' );
return $ret;
}
stripslashes_deep()
/**
* Navigates through an array and removes slashes from the values.
*
* If an array is passed, the array_map() function causes a callback to pass the
* value back to the function. The slashes from this value will removed.
*
* #since 1.0
*
* #param array|string $value The array or string to be stripped
* #return array|string Stripped array (or string in the callback).
*/
function stripslashes_deep( $value ) {
return is_array( $value ) ? array_map( array('self', 'stripslashes_deep'), $value ) : stripslashes( $value );
}
urlencode_deep()
/**
* Navigates through an array and encodes the values to be used in a URL.
*
* Uses a callback to pass the value of the array back to the function as a
* string.
*
* #since 1.0
*
* #param array|string $value The array or string to be encoded.
* #return array|string $value The encoded array (or string from the callback).
*/
public function urlencode_deep( $value ) {
return is_array($value) ? array_map( array('self', 'urlencode_deep'), $value) : urlencode($value);
}
THere is no built-in function to do this. However, you can use this function from http PECL extension,
http://usphp.com/manual/en/function.http-build-url.php
For example,
$url = http_build_url("http://www.example.com/index.php/page?a=1",
array(
"b" => "2"
)
);
So what happens if the urls conflict? If both urls contain a b= component in the querystring? You'd need to decided which holds sway.
Here's a chunk of code that does what you want, parsing each string as a url, then extracting the query url part and implode() ing them back together.
$url="http://www.example.com/index.php/page?a=1";
$qs ="?b=2";
$url_parsed = parse_url($url);
$qs_parsed = parse_url($qs);
$args = array(
$url_parsed['query'],
$qs_parsed['query'],
);
$new_url = $url_parsed['scheme'];
$new_url .= '://';
$new_url .= $url_parsed['host'];
$new_url .= $url_parsed['path'];
$new_url .= '?';
$new_url .= implode('&', $args);
print $new_url;
Related
I have to change filenames to images/filename and files/filename in Wordpress and I ended up using WP-CLI's search-replace function.
Here is the shell command I execute:
wp search-replace '(")(5\.jpg)' 'images/${2}' wp_postmeta --regex --regex-delimiter='/' --dry-run --skip-columns=meta_key --debug=true
This gives me back message: 0 replacements to be made.
Here is one entry from the database:
a:5:{s:5:"width";i:2048;s:6:"height";i:1536;s:4:"file";s:5:"5.jpg";s:5:"sizes";a:4:{s:9:"thumbnail";a:4:{s:4:"file";s:13:"5-150x150.jpg";s:5:"width";i:150;s:6:"height";i:150;s:9:"mime-type";s:10:"image/jpeg";}s:6:"medium";a:4:{s:4:"file";s:13:"5-300x225.jpg";s:5:"width";i:300;s:6:"height";i:225;s:9:"mime-type";s:10:"image/jpeg";}s:12:"medium_large";a:4:{s:4:"file";s:13:"5-768x576.jpg";s:5:"width";i:768;s:6:"height";i:576;s:9:"mime-type";s:10:"image/jpeg";}s:5:"large";a:4:{s:4:"file";s:14:"5-1024x768.jpg";s:5:"width";i:1024;s:6:"height";i:768;s:9:"mime-type";s:10:"image/jpeg";}}s:10:"image_meta";a:12:{s:8:"aperture";s:1:"0";s:6:"credit";s:0:"";s:6:"camera";s:0:"";s:7:"caption";s:0:"";s:17:"created_timestamp";s:1:"0";s:9:"copyright";s:0:"";s:12:"focal_length";s:1:"0";s:3:"iso";s:1:"0";s:13:"shutter_speed";s:1:"0";s:5:"title";s:0:"";s:11:"orientation";s:1:"0";s:8:"keywords";a:0:{}}}
I created a custom PHP script as the following:
$data = 'a:5:{s:5:"width";i:2048;s:6:"height";i:1536;s:4:"file";s:5:"5.jpg";s:5:"sizes";a:4:{s:9:"thumbnail";a:4:{s:4:"file";s:13:"5-150x150.jpg";s:5:"width";i:150;s:6:"height";i:150;s:9:"mime-type";s:10:"image/jpeg";}s:6:"medium";a:4:{s:4:"file";s:13:"5-300x225.jpg";s:5:"width";i:300;s:6:"height";i:225;s:9:"mime-type";s:10:"image/jpeg";}s:12:"medium_large";a:4:{s:4:"file";s:13:"5-768x576.jpg";s:5:"width";i:768;s:6:"height";i:576;s:9:"mime-type";s:10:"image/jpeg";}s:5:"large";a:4:{s:4:"file";s:14:"5-1024x768.jpg";s:5:"width";i:1024;s:6:"height";i:768;s:9:"mime-type";s:10:"image/jpeg";}}s:10:"image_meta";a:12:{s:8:"aperture";s:1:"0";s:6:"credit";s:0:"";s:6:"camera";s:0:"";s:7:"caption";s:0:"";s:17:"created_timestamp";s:1:"0";s:9:"copyright";s:0:"";s:12:"focal_length";s:1:"0";s:3:"iso";s:1:"0";s:13:"shutter_speed";s:1:"0";s:5:"title";s:0:"";s:11:"orientation";s:1:"0";s:8:"keywords";a:0:{}}}';
if (preg_match('/(")(5\.jpg)/', $data)) {
echo "A match was found.";
} else {
echo "A match was not found.";
}
echo '<br>';
$data = preg_replace( '/(")(5\.jpg)/', 'images/${2}', $data );
var_dump($data);
This replaces the data fine, I get result as
a:5:{s:5:"width";i:2048;s:6:"height";i:1536;s:4:"file";s:5:"images/5.jpg" ...
This is the source code of WP-CLI search-replace:
<?php
namespace WP_CLI;
class SearchReplacer {
private $from, $to;
private $recurse_objects;
private $regex;
private $regex_flags;
private $regex_delimiter;
private $logging;
private $log_data;
private $max_recursion;
/**
* #param string $from String we're looking to replace.
* #param string $to What we want it to be replaced with.
* #param bool $recurse_objects Should objects be recursively replaced?
* #param bool $regex Whether `$from` is a regular expression.
* #param string $regex_flags Flags for regular expression.
* #param string $regex_delimiter Delimiter for regular expression.
* #param bool $logging Whether logging.
*/
function __construct( $from, $to, $recurse_objects = false, $regex = false, $regex_flags = '', $regex_delimiter = '/', $logging = false ) {
$this->from = $from;
$this->to = $to;
$this->recurse_objects = $recurse_objects;
$this->regex = $regex;
$this->regex_flags = $regex_flags;
$this->regex_delimiter = $regex_delimiter;
$this->logging = $logging;
$this->clear_log_data();
// Get the XDebug nesting level. Will be zero (no limit) if no value is set
$this->max_recursion = intval( ini_get( 'xdebug.max_nesting_level' ) );
}
/**
* Take a serialised array and unserialise it replacing elements as needed and
* unserialising any subordinate arrays and performing the replace on those too.
* Ignores any serialized objects unless $recurse_objects is set to true.
*
* #param array|string $data The data to operate on.
* #param bool $serialised Does the value of $data need to be unserialized?
*
* #return array The original array with all elements replaced as needed.
*/
function run( $data, $serialised = false ) {
return $this->_run( $data, $serialised );
}
/**
* #param int $recursion_level Current recursion depth within the original data.
* #param array $visited_data Data that has been seen in previous recursion iterations.
*/
private function _run( $data, $serialised, $recursion_level = 0, $visited_data = array() ) {
// some unseriliased data cannot be re-serialised eg. SimpleXMLElements
try {
if ( $this->recurse_objects ) {
// If we've reached the maximum recursion level, short circuit
if ( $this->max_recursion != 0 && $recursion_level >= $this->max_recursion ) {
return $data;
}
if ( is_array( $data ) || is_object( $data ) ) {
// If we've seen this exact object or array before, short circuit
if ( in_array( $data, $visited_data, true ) ) {
return $data; // Avoid infinite loops when there's a cycle
}
// Add this data to the list of
$visited_data[] = $data;
}
}
if ( is_string( $data ) && ( $unserialized = #unserialize( $data ) ) !== false ) {
$data = $this->_run( $unserialized, true, $recursion_level + 1 );
}
elseif ( is_array( $data ) ) {
$keys = array_keys( $data );
foreach ( $keys as $key ) {
$data[ $key ]= $this->_run( $data[$key], false, $recursion_level + 1, $visited_data );
}
}
elseif ( $this->recurse_objects && is_object( $data ) ) {
foreach ( $data as $key => $value ) {
$data->$key = $this->_run( $value, false, $recursion_level + 1, $visited_data );
}
}
else if ( is_string( $data ) ) {
if ( $this->logging ) {
$old_data = $data;
}
if ( $this->regex ) {
$search_regex = $this->regex_delimiter;
$search_regex .= $this->from;
$search_regex .= $this->regex_delimiter;
$search_regex .= $this->regex_flags;
$data = preg_replace( $search_regex, $this->to, $data );
} else {
$data = str_replace( $this->from, $this->to, $data );
}
if ( $this->logging && $old_data !== $data ) {
$this->log_data[] = $old_data;
}
}
if ( $serialised )
return serialize( $data );
} catch( Exception $error ) {
}
return $data;
}
/**
* Gets existing data saved for this run when logging.
* #return array Array of data strings, prior to replacements.
*/
public function get_log_data() {
return $this->log_data;
}
/**
* Clears data stored for logging.
*/
public function clear_log_data() {
$this->log_data = array();
}
}
I use the exact same preg_replace as used here,
$data = preg_replace( $search_regex, $this->to, $data );
Why this is not working in WP-CLI?
I have two URLs and am looking for the best way to decide if they are identical.
Example:
$url1 = 'http://example.com/page.php?tab=items&msg=3&sort=title';
$url2 = 'http://example.com/page.php?tab=items&sort=title&msg=3';
In the two URLs only the sort and msg param are switched, so I consider them equal.
However I cannot simply do if ( $url1 == $url2 ) { … }
I'm having a list of URLs and need to find duplicates, so the code should be fast as it is run inside a loop. (As a side note: The domain/page.php will always be same, it's only about finding URLs by params.)
Maybe like this?
function compare_url($url1, $url2){
return (parse_url($url1,PHP_URL_QUERY) == parse_url($url2,PHP_URL_QUERY));
}
It's not as easy as it might sound to find out if an URI is identical or not, especially as you take the query parameter into account here.
One common way to do this is to have a function that normalizes the URL and then compare the normalized URIs:
$url1 = 'http://example.com/page.php?tab=items&msg=3&sort=title';
$url2 = 'http://example.com/page.php?tab=items&sort=title&msg=3';
var_dump(url_nornalize($url1) == url_nornalize($url2)); # bool(true)
Into such a normalization function you put in your requirements. First of all the URL should be normalized according to the specs:
function url_nornalize($url, $separator = '&')
{
// normalize according RFC 3986
$url = new Net_URL2($url);
$url->normalize();
And then you can take care of additional normalization steps, for example, sorting the sub-parts of the query:
// normalize query if applicable
$query = $url->getQuery();
if (false !== $query) {
$params = explode($separator, $query);
sort($params);
$query = implode($separator, $params);
$url->setQuery($query);
}
Additional steps can be though of, like removing default parameters or not allowed ones, or duplicate ones and what not.
Finally the string of normalized URL is returned
return (string) $url;
}
Using an array/hash-map for the parameters isn't bad as well, I just wanted to show an alternative approach. Full example:
<?php
/**
* http://stackoverflow.com/questions/27667182/are-two-urls-identical-ignore-the-param-order
*/
require_once 'Net/URL2.php';
function url_nornalize($url, $separator = '&')
{
// normalize according RFC 3986
$url = new Net_URL2($url);
$url->normalize();
// normalize query if applicable
$query = $url->getQuery();
if (false !== $query) {
$params = explode($separator, $query);
// remove empty parameters
$params = array_filter($params, 'strlen');
// sort parameters
sort($params);
$query = implode($separator, $params);
$url->setQuery($query);
}
return (string)$url;
}
$url1 = 'http://EXAMPLE.com/p%61ge.php?tab=items&&&msg=3&sort=title';
$url2 = 'http://example.com:80/page.php?tab=items&sort=title&msg=3';
var_dump(url_nornalize($url1) == url_nornalize($url2)); # bool(true)
To make sure that both URLs are identical, we need to compare at least 4 elements:
The scheme(e.g. http, https, ftp)
The host, i.e. the domain name of the URL
The path, i.e. the "file" that was requested
Query parameters of the request.
Some notes:
(1) and (2) are case-insensitive, which means http://example.org is identical to HTTP://EXAMPLE.ORG.
(3) can have leading or trailing slashes, that should be ignored: example.org is identical to example.org/
(4) could include parameters in varying order.
We can safely ignore anchor text, or "fragment" (#anchor after the query parameters), as they are only parsed by the browser.
URLs can also include port-numbers, a username and password - I think we can ignore those elements, as they are used so rarely that they do not need to be checked here.
Solution:
Here's a complete function that checks all those details:
/**
* Check if two urls match while ignoring order of params
*
* #param string $url1
* #param string $url2
* #return bool
*/
function do_urls_match( $url1, $url2 ) {
// Parse urls
$parts1 = parse_url( $url1 );
$parts2 = parse_url( $url2 );
// Scheme and host are case-insensitive.
$scheme1 = strtolower( $parts1[ 'scheme' ] ?? '' );
$scheme2 = strtolower( $parts2[ 'scheme' ] ?? '' );
$host1 = strtolower( $parts1[ 'host' ] ?? '' );
$host2 = strtolower( $parts2[ 'host' ] ?? '' );
if ( $scheme1 !== $scheme2 ) {
// URL scheme mismatch (http <-> https): URLs are not identical.
return false;
}
if ( $host1 !== $host2 ) {
// Different host (domain name): Not identical.
return false;
}
// Remvoe leading/trailing slashes, url-decode special characters.
$path1 = trim( urldecode( $parts1[ 'path' ] ?? '' ), '/' );
$path2 = trim( urldecode( $parts2[ 'path' ] ?? '' ), '/' );
if ( $path1 !== $path2 ) {
// The request-path is different: Different URLs.
return false;
}
// Convert the query-params into arrays.
parse_str( $parts1['query'] ?? '', $query1 );
parse_str( $parts2['query'] ?? '', $query2 );
if ( count( $query1 ) !== count( $query2 ) ) {
// Both URLs have a differnt number of params: They cannot match.
return false;
}
// Only compare the query-arrays when params are present.
if (count( $query1 ) > 0 ) {
ksort( $query1 );
ksort( $query2 );
if ( array_diff( $query1, $query2 ) ) {
// Query arrays have differencs: URLs do not match.
return false;
}
}
// All checks passed, URLs are identical.
return true;
} // End do_urls_match()
Test cases:
$base_urls = [
'https://example.org/',
'https://example.org/index.php?sort=asc&field=id&filter=foo',
'http://EXAMPLE.com/p%61ge.php?tab=items&&&msg=3&sort=title',
];
$compare_urls = [
'https://example.org/',
'https://Example.Org',
'https://example.org/index.php?sort=asc&&field=id&filter=foo',
'http://example.org/index.php?sort=asc&field=id&filter=foo',
'https://company.net/page.php?sort=asc&field=id&filter=foo',
'https://example.org/index.php?sort=asc&&&field=id&filter=foo#anchor',
'https://example.org/index.php?field=id&filter=foo&sort=asc',
'http://example.com:80/page.php?tab=items&sort=title&msg=3',
];
foreach ( $base_urls as $url1 ) {
printf( "\n\n%s", $url1 );
foreach ( $compare_urls as $url2 ) {
if (do_urls_match( $url1, $url2 )) {
printf( "\n [MATCHES] %s", $url2 );
}
}
}
/* Output:
https://example.org/
[MATCHES] https://example.org/
[MATCHES] https://Example.Org
https://example.org/index.php?sort=asc&field=id&filter=foo
[MATCHES] https://example.org/index.php?sort=asc&&field=id&filter=foo
[MATCHES] https://example.org/index.php?sort=asc&&&field=id&filter=foo#anchor
[MATCHES] https://example.org/index.php?field=id&filter=foo&sort=asc
http://EXAMPLE.com/p%61ge.php?tab=items&&&msg=3&sort=title
[MATCHES] http://example.com:80/page.php?tab=items&sort=title&msg=3
*/
For example, I have this kind of code:
<?php
/**
* Order
*
* The WooCommerce order class handles order data.
*
* #class WC_Order
* #version 1.6.4
* #package WooCommerce/Classes
* #category Class
* #author WooThemes
*/
class WC_Order {
/** #public int Order (post) ID */
public $id;
/** #public string Order status. */
public $status;
/** #public string Order date (placed). */
public $order_date;
/** #public string Order date (paid). */
public $modified_date;
/** #public string Note added by the customer. */
public $customer_note;
/** #public array Order (post) meta/custom fields. */
public $order_custom_fields;
global $wpdb, $woocommerce;
if ( empty( $type ) )
$type = array( 'line_item' );
if ( ! is_array( $type ) )
$type = array( $type );
$items = $this->get_items( $type );
$count = 0;
foreach ( $items as $item ) {
if ( ! empty( $item['qty'] ) )
$count += $item['qty'];
else
$count ++;
}
return apply_filters( 'woocommerce_get_item_count', $count, $type, $this );
}
/**
* Return an array of fees within this order.
*
* #access public
* #return array
*/
public function get_fees() {
return $this->get_items( 'fee' );
}
/**
* Return an array of taxes within this order.
*
* #access public
* #return void
*/
public function get_taxes() {
return $this->get_items( 'tax' );
}
/**
* Get taxes, merged by code, formatted ready for output.
*
* #access public
* #return void
*/
public function get_tax_totals() {
$taxes = $this->get_items( 'tax' );
$tax_totals = array();
foreach ( $taxes as $key => $tax ) {
$code = $tax[ 'name' ];
if ( ! isset( $tax_totals[ $code ] ) ) {
$tax_totals[ $code ] = new stdClass();
$tax_totals[ $code ]->amount = 0;
}
$tax_totals[ $code ]->is_compound = $tax[ 'compound' ];
$tax_totals[ $code ]->label = isset( $tax[ 'label' ] ) ? $tax[ 'label' ] : $tax[ 'name' ];
$tax_totals[ $code ]->amount += $tax[ 'tax_amount' ] + $tax[ 'shipping_tax_amount' ];
$tax_totals[ $code ]->formatted_amount = woocommerce_price( $tax_totals[ $code ]->amount );
}
return apply_filters( 'woocommerce_order_tax_totals', $tax_totals, $this );
}
/**
* has_meta function for order items.
*
* #access public
* #return array of meta data
*/
public function has_meta( $order_item_id ) {
global $wpdb;
return $wpdb->get_results( $wpdb->prepare("SELECT meta_key, meta_value, meta_id, order_item_id
FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id = %d
ORDER BY meta_key,meta_id", absint( $order_item_id ) ), ARRAY_A );
}
/**
* Get order item meta.
*
* #access public
* #param mixed $item_id
* #param string $key (default: '')
* #param bool $single (default: false)
* #return void
*/
public function get_item_meta( $order_item_id, $key = '', $single = false ) {
return get_metadata( 'order_item', $order_item_id, $key, $single );
}
I want to match all Wordpress hooks: "do_action" and "apply_filters" with three options:
apply_filters( 'woocommerce_order_tax_totals', $tax_totals, $this ), file, line number
An example of what i'm trying to do can be seen here:
http://etivite.com/api-hooks/buddypress/trigger/apply_filters/bp_get_total_mention_count_for_user/
http://adambrown.info/p/wp_hooks/hook/activated_plugin?version=3.6&file=wp-admin/includes/plugin.php
I did try to pull something out but with no success:
<?php
$path = $_SERVER['DOCUMENT_ROOT'] . '/wp-content/plugins/iphorm-form-builder';
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)) as $filename) {
if (substr($filename, -3) == 'php') {
$file = file($filename);
if ($file !== false) {
$matches1 = preg_grep( '/do_action\((.+)\);/', $file);
$matches2 = preg_grep( '/apply_filters\((.+)\);/', $file );
$arr = array_filter(array_merge($matches1, $matches2));
$out = '';
echo "found in $filename:";
echo "<pre>";
foreach ($arr as $key => $value) {
$out .= $file[$key-2];
$out .= $file[$key-1];
$out .= $file[$key];
$out .= $file[$key+1];
$out .= $file[$key+2];
}
echo htmlentities($out);
echo "</pre>";
} else {
echo "failed reading to array";
}
}
}
This can be done very simply by taking advantage of built-in shell commands.
<?php
$path = $_SERVER['DOCUMENT_ROOT'] . '/wp-content/plugins/iphorm-form-builder';
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)) as $filename) {
if (substr($filename, -3) == 'php') {
$context = shell_exec('grep -H -n -C4 do_action ' . escapeshellarg($filename));
if (!empty($context)) {
echo "found in $filename:";
echo "<pre>";
echo htmlentities($context);
echo "</pre>";
} else {
echo "failed reading to array";
}
}
}
Relevant documentation:
php shell_exec
Execute command via shell and return the complete output as a string
php escapeshellarg
Escape a string to be used as a shell argument
bash grep
Grep searches the named input FILEs (or standard input if no files are
named, or the file name - is given) for lines containing a match to the
given PATTERN. By default, grep prints the matching lines.
-C NUM, --context=NUM
Print NUM lines of output context. Places a line containing --
between contiguous groups of matches.
-H, --with-filename
Print the filename for each match.
-n, --line-number
Prefix each line of output with the line number within its input
file.
Edit
Depending on how many directories and files you have in your project, it may not be performant. You're basically creating a new process in a new shell for each file. That's not great. If you'd rather get a big dump of data and parse it out later, do this instead:
<?php
$path = $_SERVER['DOCUMENT_ROOT'] . '/wp-content/plugins/iphorm-form-builder';
$grep = shell_exec('grep --include=*.php -RHn -C4 do_action ' . escapeshellarg($path));
$matches = explode("\n--\n", $grep);
if (empty($matches)) {
echo "failed reading to array";
}
else {
foreach ($matches as $match) {
echo "<pre>";
echo htmlentities($match);
echo "</pre>";
}
}
You don't need to loop through file data line by line. Just read the data in a variable and apply regex:
$data = file_get_contents( $filename );
if (preg_match_all(
'~((?:[^\n]*\n){0,4}) *do_action\s*\(\s*([^)]+)\s*\)\s*;[^\n]*\n((?:[^\n]*\n){0,4})~',
$data, $arr)) {
// match found, now dump the results
// $arr[1] will print 4 lines before the match
// $arr[2] will print function arguments for do_action
// $arr[3] will print 4 lines after the match
print_r($arr);
}
This can not be done by regular expressions alone given the limitations of PHP's regular expression engine. Specifically, and it came as a surprise to me, you can not have variable length look behinds.
The reason you need look behinds is if you had the occurrence of do_action or apply_filters on consecutive lines, then the first match will prevent the second match if you had no look aheads, and even if you use look aheads, there is no way to get the previous two rows of the second match without the look behinds. Not to mention I don't even know if you can even include the contents of a look around assertion in the result.
This solution creates a regular expression to find the lines in which the function occurs, and then uses two more regexes to find the lines before and after. I took care to watch out for the start and end of file when designing the regexes.
$offset = 0;
while (preg_match('/^.*?(?:apply_filters|do_action)\s*\(.+?\)\s*;.*(?:\n\r|\n|\r|$)/m', $file, $match, PREG_OFFSET_CAPTURE, $offset))
{
$index = $match[0][1];
$offset = $index + strlen($match[0][0]);
$hasBefore = preg_match('/(?:.*(?:\n\r|\n|\r).*$)|[^\n\r].*$/',
substr($file, 0, $index), $beforeMatch);
$hasAfter = preg_match('/^(?:.*(?:\n\r|\n|\r|$)){0,2}/',
substr($file, $offset), $afterMatch);
if ($hasBefore) print_r($beforeMatch);
print_r($match[0][0]);
if ($hasAfter) print_r($afterMatch);
}
phpFiddle
This only displays two lines before and two lines after. You can use repetition if you want more, but it appears to me from the attempted solution that this was what the o.p. really wanted.
I've been trying to build this recursive function for the better part of a day now, but I just can't seem to get it to work the way I want.
First, I have a property which holds some data that the function have to access:
$this->data
And then I have this string which the intention is to turn into a relative path:
$path = 'path.to.%id%-%folder%.containing.%info%';
The part of the string that are like this: %value% will load some dynamic values found in the $this->data property (like so: $this->data['id']; or $this->data['folder'];
and to make things really interesting, the property can reference itself again like so: $this->data['folder'] = 'foldername.%subfolder%'; and also have two %values% separated by a - that would have to be left alone.
So to the problem, I've been trying to make a recursive function that will load the dynamic values from the data property, and then again if the new value contains another %value% and so on until no more %value%'s are loaded.
So far, this is what I've been able to come up with:
public function recursiveFolder( $folder, $pathArr = null )
{
$newPathArr = explode( '.', $folder );
if ( count ( $newPathArr ) !== 1 )
{
foreach( $newPathArr as $id => $folder )
{
$value = $this->recursiveFolder( $folder, $newPathArr );
$resultArr = explode( '.', $value );
if ( count ( $resultArr ) !== 1 )
{
foreach ( $resultArr as $nid => $result )
{
$nvalue = $this->recursiveFolder( $result, $newPathArr );
$resultArr[$nid] = $nvalue;
}
}
$resultArr = implode( '.',$resultArr );
$newPathArr[$id] = $resultArr;
}
}
else
{
$pattern = '/%(.*?)%/si';
preg_match_all( $pattern, $folder, $matches );
if ( empty( $matches[0] ) )
{
return $folder;
}
foreach ( $matches[1] as $mid => $match )
{
if ( isset( $this->data[$match] ) && $this->data[$match] != '' )
{
$folder = str_replace( $matches[0][$mid], $this->data[$match], $folder );
return $folder;
}
}
}
return $newPathArr;
}
Unfortunately it is not a recursive function at all as it grinds to a halt when it has multiple layers of %values%, but works with two layers -barely-. (I just coded it so that it would work at a bare minimalistic level this point).
Here's how it should work:
It should turn:
'files.%folder%.blog-%type%.and.%time%'
into:
'files.foldername.blog-post.and.2013.feb-12th.09'
based on this:
$data['folder'] = 'foldername';
$data['type'] = 'post';
$data['time'] = '%year%.%month%-%day%';
$data['year'] = 2013;
$data['month'] = 'feb';
$data['day'] = '12th.%hour%';
$data['hour'] = '09';
Hope you can help!
Jay
I don't see the need for this too be solved recursively:
<?php
function putData($str, $data)
{
// Repeat the replacing process until no more matches are found:
while (preg_match("/%(.*?)%/si", $str, $matches))
{
// Use $matches to make your replaces
}
return $str;
}
?>
PHP automatically creates arrays in $_GET, when the parameter name is followed by [] or [keyname].
However for a public API I'd love to have the same behaviour without explicit brackets in the URL. For example, the query
?foo=bar&foo=baz
should result in a $_GET (or similar) like this:
$_GET["foo"] == array("bar", "baz");
Is there any possibility to get this behaviour in PHP easily? I.e., not parsing $_SERVER['QUERY_STRING'] myself or preg_replacing = with []= in the query string before feeding it to parse_str()?
There's no built in way to support ?foo=bar&foo=baz.
Daniel Morell proposed a solution which manually parses the URL string and iteratively builds up an array when multiple instances of the parameter exist, or returns a string when only one parameter exists (ie; matches the default behaviour).
It supports both types of URLs, with and without a bracket:
?foo=bar&foo=baz // works
?foo[]=bar&foo[]=baz // works
/**
* Parses GET and POST form input like $_GET and $_POST, but without requiring multiple select inputs to end the name
* in a pair of brackets.
*
* #param string $method The input method to use 'GET' or 'POST'.
* #param string $querystring A custom form input in the query string format.
* #return array $output Returns an array containing the input keys and values.
*/
function bracketless_input( $method, $querystring=null ) {
// Create empty array to
$output = array();
// Get query string from function call
if( $querystring !== null ) {
$query = $querystring;
// Get raw POST data
} elseif ($method == 'POST') {
$query = file_get_contents('php://input');
// Get raw GET data
} elseif ($method == 'GET') {
$query = $_SERVER['QUERY_STRING'];
}
// Separerate each parameter into key value pairs
foreach( explode( '&', $query ) as $params ) {
$parts = explode( '=', $params );
// Remove any existing brackets and clean up key and value
$parts[0] = trim(preg_replace( '(\%5B|\%5D|[\[\]])', '', $parts[0] ) );
$parts[0] = preg_replace( '([^0-9a-zA-Z])', '_', urldecode($parts[0]) );
$parts[1] = urldecode($parts[1]);
// Create new key in $output array if param does not exist.
if( !key_exists( $parts[0], $output ) ) {
$output[$parts[0]] = $parts[1];
// Add param to array if param key already exists in $output
} elseif( is_array( $output[$parts[0]] ) ) {
array_push( $output[$parts[0]], $parts[1] );
// Otherwise turn $output param into array and append current param
} else {
$output[$parts[0]] = array( $output[$parts[0]], $parts[1] );
}
}
return $output;
}
you can try something like this:
foreach($_GET as $slug => $value) {
#whatever you want to do, for example
print $_GET[$slug];
}