Related
Hi I'm trying to build a function which will walk through all possible number sequences and pass the sequence through a function and if it return true stops.
Here is the markup:
function sequences($smallest, $biggest, $long, $func) {
$seq = array(); // Will have $long values
/*
* generates the sequence
*/
if (call_user_func($functions[$func])) {
return $seq;
} else {
//generate next sequence.
}
}
The generated sequence will have $long unique values between $smallest integer to $biggest integer and must be sorted example:
/* $long = 4; $smallest = 5, $biggest = 10;
*
* 5,6,7,8
* 5,6,7,9
* 5,6,7,10
* 5,6,8,9
* 5,6,8,10
* ...
* 7,8,9,10
*
*
* $long = 4; $smallest = 15, $biggest = 60;
*
* ...
* 15,41,49,56
* ...
* 37,39,53,60
* ...
*/
I haven’t been able to wrap my head around it, so far the only way I achieve that was to generate numbers randomly and than sorting the array each time.
That's clearly not the best way.
Other programming languages will be great too (c++, C#, js, java).
NOTES
Its not an odometer duplicate numbers in the sequence are not allowed and the values of each index can be bigger than 9.
The function that tests the sequence against a some conditions and will return True or False it doesn't matter what it does the actual problem is generating the sequences one by one without duplicates.
This was a fun challenge to generate the sequences as you specified.
This code below should do what you want (I think). Or at least you should be able to modify it to suit your needs. I wasn't exactly sure if you wanted the sequences() function to return only the first sequence for which the test function $functions[$func] returns true, or all the sequences so far. In this example only the first "match" is returned (or null if no match was found).
This code requires PHP 5.5+ as it uses a generator function (and also short array syntax available in PHP 5.4+). I tested this on PHP 5.5.12 and it seems to work as intended. The code can probably be modified to work on older PHP versions if needed (just avoid using generators/yields). Actually this is the first time I've written a PHP generator function.
sequenceGenerator() is a recursive generator function that you can iterate over using foreach.
I also wrote an echoSequences() function for testing the sequence generation which just outputs all of the generated sequences in order using echo.
function sequenceGenerator(array $items, $long = null, $level = 1, $path = null) {
$itemCount = count($items);
if (empty($long)) $long = $itemCount;
if ($path == null) $path = [];
if ($itemCount > 1) {
foreach ($items as $item) {
$subPath = $path;
$subPath[] = $item;
if ($level == $long) {
yield $subPath;
continue;
}
if (count($subPath) + count($items) > $long) {
$items = array_values(array_diff($items, [$item]));
$iteration = sequenceGenerator($items, $long, $level + 1, $subPath);
foreach ($iteration as $value) yield $value;
}
}
} elseif ($itemCount == 1) {
$path[] = $items[0];
yield $path;
}
}
// Function for testing sequence generation
function echoSequences($smallest, $biggest, $long) {
$items = range($smallest, $biggest);
foreach (sequenceGenerator($items, $long) as $sequence) {
echo implode(',', $sequence)."<br>\n";
}
}
function sequences($smallest, $biggest, $long, $func) {
global $functions;
$items = range($smallest, $biggest);
foreach (sequenceGenerator($items, $long) as $sequence) {
if (call_user_func($functions[$func], $sequence)) {
return $sequence;
}
}
return null; // Return null when $func didn't return true for any sequence
}
//echoSequences(5, 10, 4); // Test sequence generation
$functions = array(
// This test function returns true only for the sequence [5,6,8,10]
'testfunc' => function($sequence) { return ($sequence == [5,6,8,10]); }
);
$sequence = sequences(5, 10, 4, 'testfunc'); // Find the first sequence that 'testfunc' will return true for (or null)
if (!empty($sequence)) {
echo 'Found match: '.implode(',', $sequence);
} else {
echo 'Match not found';
}
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 have a problem with my code that i dont understand.
can anyone help please ?
$query="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
$seq=chunk_split($query,50,"<br />");
$truecol = "<span style=\"color: hsl(0,100%,25%);\">";
function colorSequence ($seq,$position,$truecol,$TFBSlength){
$nucleotides = str_split($seq);
foreach ($nucleotides as $index => $nucl){
if ($index == $position){
echo $truecol;
}
if ($index == $position + $TFBSlength){
echo "</span>";
}
echo $nucl;
}
echo "\n";
}
colorSequence($seq,49,$truecol,1);
?>
This is my code. Basically I wan the output code to look like this:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA(red)A(/red)
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
and it does when i run the function colorSequence($seq,49,$truecol,1);
however if i run colorSequence($seq,49,$truecol,3); i get this:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA< span="">r />AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC<>(all letters from letter 49 are red, even tho i only want 3 letters form 49 to 51 to be red).
can anyone fix this please ?
$query="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
$seq=chunk_split($query,50,'');
$truecol = "<span style=\"color: hsl(0,100%,25%);\">";
function colorSequence ($seq,$position,$truecol,$TFBSlength){
$nucleotides = str_split($seq);
foreach ($nucleotides as $index => $nucl){
if ($index == $position){
echo $truecol;
}
if ($index == $position + $TFBSlength){
echo "</span>";
}
if(($index%50)==0){
echo '<br>';
}
echo $nucl;
}
echo "\n";
}
colorSequence($seq,49,$truecol,1);
echo '<hr>';
colorSequence($seq,49,$truecol,3)
if you print_r($nucleotides), you will see that
Array
(
......
[49] => A
[50] => <
[51] => b
[52] => r
[53] =>
......
)
so you are inserting the <span> into <br /> which destroys the following html...
The span tag added by your function breaks the HTML created by chunk_split.
When you modify the DNS string $query you need to differ between the position of the nucleotides and general string offsets.
While at first both are identical, the more you add to the string, the more this differs.
If you encapsulate the string into an object of it's own that take care of the HTML tags and don't count them (including surrounding whitespace around the tags), things become easy again:
$query = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
// wrap line: 11 lines à 50 nucleotids
$seq = chunk_split($query, 50, "<br />\n");
// get help from sequence object
$sequence = new AmendSequence($seq);
// some HTML comments for demonstration purposes
$sequence->insertAt(0, "<!-- first line -->\n");
$sequence->insertAt(50, "<!-- second line -->\n", TRUE); # TRUE: Place after <br />
$sequence->insertAt(75, "<!-- inside second line -->");
$sequence->insertAt(550, "<!-- at end -->", TRUE); # TRUE: Place after <br />
// colorize
$color = '<span style="color: hsl(0,100%,25%);">';
$sequence->insertAt(49, $color);
$sequence->insertAt(50, '</span>');
printf("Sequence with %d nucleotids:\n", count($sequence)); # count gives length w/o amends
echo $sequence, "\n"; # that prints your sequence with all amends
Which creates the following output:
Sequence with 550 nucleotids:
<!-- first line -->
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<span style="color: hsl(0,100%,25%);">A</span><br />
<!-- second line -->
AAAAAAAAAAAAAAAAAAAAAAAAA<!-- inside second line -->AAAAAAAAAAAAAAAAAAAAAAAAA<br />
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<br />
AAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB<br />
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB<br />
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB<br />
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCC<br />
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC<br />
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC<br />
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC<br />
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC<br />
<!-- at end -->
So the actual magic here is the insertAt method which accepts the offset position of the nucledotide and calculates the string offset for it. It's all encapsulated into a class of it's own which probably is a bit too much for the beginning, however the real action is dividing the string that contains the DNA and the HTML into segements of DNS only and getting their actual string offset. The full source code:
<?php
/**
* #link http://stackoverflow.com/questions/10446162/how-to-wordwrap-with-different-length-string-modification
*/
/**
* Treat text with "tags" as text without tags when insertAt() is called.
*/
class AmendSequence implements IteratorAggregate, Countable
{
/**
* regex pattern for a tag
*/
const TAG = '\s*<[^>]*>\s*';
/**
* #var string
*/
private $query;
/**
* #param string $query
*/
public function __construct($query = '')
{
$this->setQuery($query);
}
/**
* #param int $offset
* #param string $string
* #param bool $after (optional) prefer after next tag instead before
*/
public function insertAt($offset, $string, $after = FALSE)
{
$offset = $this->translate($offset, $after);
$this->query = substr_replace($this->query, $string, $offset, 0);
}
/**
* Translate virtual offset to string offset
* #param int $virtualOffset
* #return int
* #throws InvalidArgumentException
*/
public function translate($virtualOffset, $after)
{
if ($virtualOffset < 0) throw new InvalidArgumentException(sprintf('Offset can not be lower than 0, is %d.', $virtualOffset));
$virtualCurrent = 0;
foreach ($this as $segment) {
list(, $current, $length) = $segment;
$delta = ($virtualOffset - $virtualCurrent) - $length;
if ($delta < 0 || ($delta === 0 && !$after)) {
return $current + $length + $delta;
}
$virtualCurrent += $length;
}
if ($virtualCurrent === $virtualOffset && $after) {
return strlen($this->query);
}
throw new InvalidArgumentException(sprintf('Offset can not be larger than %d, is %d.', $virtualCurrent, $virtualOffset));
}
/**
* #return array
*/
public function getSegments()
{
$segments = preg_split('/' . self::TAG . '/', $this->query, 0, PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_NO_EMPTY);
foreach ($segments as &$segment) {
$segment[2] = strlen($segment[0]);
}
return $segments;
}
public function getSequence()
{
$buffer = '';
foreach ($this as $segment) {
$buffer .= $segment[0];
}
return $buffer;
}
/**
* #return string
*/
public function getQuery()
{
return $this->query;
}
/**
* #param string $query
*/
public function setQuery($query)
{
$this->query = (string)$query;
}
/**
* Retrieve an external iterator
* #link http://php.net/manual/en/iteratoraggregate.getiterator.php
* #return Traversable An instance of an object implementing <b>Iterator</b> or <b>Traversable</b>
*/
public function getIterator()
{
return new ArrayIterator($this->getSegments());
}
/**
* #link http://php.net/manual/en/countable.count.php
* #return int The custom count as an integer.
*/
public function count()
{
$length = 0;
foreach ($this as $segment) {
$length += $segment[2];
}
return $length;
}
/**
* #return string
*/
public function __toString()
{
return $this->query;
}
}
$query = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
// wrap line: 11 lines à 50 nucleotids
$seq = chunk_split($query, 50, "<br />\n");
// get help from sequence object
$sequence = new AmendSequence($seq);
// some HTML comments for demonstration purposes
$sequence->insertAt(0, "<!-- first line -->\n");
$sequence->insertAt(50, "<!-- second line -->\n", TRUE); # TRUE: Place after <br />
$sequence->insertAt(75, "<!-- inside second line -->");
$sequence->insertAt(550, "<!-- at end -->", TRUE); # TRUE: Place after <br />
// colorize
$color = '<span style="color: hsl(0,100%,25%);">';
$sequence->insertAt(49, $color);
$sequence->insertAt(50, '</span>');
printf("Sequence with %d nucleotids:\n", count($sequence)); # count gives length w/o amends
echo $sequence, "\n"; # that prints your sequence with all amends
echo $sequence->getSequence(); # without the amends
Feel free now to colorize as many parts as you like - with or without other HTML already inside the sequence.
I'm trying to add some users to my Ldap DB but I get some errors (invalid dn syntax) when I use some special characters like ",.". I need a function that escape all characters. I try preg_quote but I get some errors in some cases.
Thanks in advance
Code:
$user = 'Test , Name S.L';
if(!(ldap_add($ds, "cn=" . $user . ",".LDAP_DN_BASE, $info))) {
include 'error_new_account.php';
}
EDIT Jan 2013: added support for escaping leading/trailing spaces in DN strings, per RFC 4514. Thanks to Eugenio for pointing out this issue.
EDIT 2014: I added this function to PHP 5.6. The code below is now a like-for-like drop-in replacement for earlier PHP versions.
if (!function_exists('ldap_escape')) {
define('LDAP_ESCAPE_FILTER', 0x01);
define('LDAP_ESCAPE_DN', 0x02);
/**
* #param string $subject The subject string
* #param string $ignore Set of characters to leave untouched
* #param int $flags Any combination of LDAP_ESCAPE_* flags to indicate the
* set(s) of characters to escape.
* #return string
*/
function ldap_escape($subject, $ignore = '', $flags = 0)
{
static $charMaps = array(
LDAP_ESCAPE_FILTER => array('\\', '*', '(', ')', "\x00"),
LDAP_ESCAPE_DN => array('\\', ',', '=', '+', '<', '>', ';', '"', '#'),
);
// Pre-process the char maps on first call
if (!isset($charMaps[0])) {
$charMaps[0] = array();
for ($i = 0; $i < 256; $i++) {
$charMaps[0][chr($i)] = sprintf('\\%02x', $i);;
}
for ($i = 0, $l = count($charMaps[LDAP_ESCAPE_FILTER]); $i < $l; $i++) {
$chr = $charMaps[LDAP_ESCAPE_FILTER][$i];
unset($charMaps[LDAP_ESCAPE_FILTER][$i]);
$charMaps[LDAP_ESCAPE_FILTER][$chr] = $charMaps[0][$chr];
}
for ($i = 0, $l = count($charMaps[LDAP_ESCAPE_DN]); $i < $l; $i++) {
$chr = $charMaps[LDAP_ESCAPE_DN][$i];
unset($charMaps[LDAP_ESCAPE_DN][$i]);
$charMaps[LDAP_ESCAPE_DN][$chr] = $charMaps[0][$chr];
}
}
// Create the base char map to escape
$flags = (int)$flags;
$charMap = array();
if ($flags & LDAP_ESCAPE_FILTER) {
$charMap += $charMaps[LDAP_ESCAPE_FILTER];
}
if ($flags & LDAP_ESCAPE_DN) {
$charMap += $charMaps[LDAP_ESCAPE_DN];
}
if (!$charMap) {
$charMap = $charMaps[0];
}
// Remove any chars to ignore from the list
$ignore = (string)$ignore;
for ($i = 0, $l = strlen($ignore); $i < $l; $i++) {
unset($charMap[$ignore[$i]]);
}
// Do the main replacement
$result = strtr($subject, $charMap);
// Encode leading/trailing spaces if LDAP_ESCAPE_DN is passed
if ($flags & LDAP_ESCAPE_DN) {
if ($result[0] === ' ') {
$result = '\\20' . substr($result, 1);
}
if ($result[strlen($result) - 1] === ' ') {
$result = substr($result, 0, -1) . '\\20';
}
}
return $result;
}
}
So you would do:
$user = 'Test , Name S.L';
$cn = ldap_escape($user, '', LDAP_ESCAPE_DN);
if (!ldap_add($ds, "cn={$cn}," . LDAP_DN_BASE, $info)) {
include 'error_new_account.php';
}
PHP 5.6 Beta released ldap_escape() function recently and it is in effect, However, this version is not production ready at present, you can very use it for your development purposes as of now.
Just a heads up if your not on PHP 5.6 yet, you can mirror the exact PHP 5.6 function ldap_escape() using the methods I created below, keep in mind this is meant for use in a class. The above answer doesn't perform exactly like the ldap_escape function, as in it doesn't escape all characters into a hex string if no flags have been given, so this would be more suitable for a drop in replacement for earlier versions of PHP, in an object oriented way.
I've documented every line for an easier understanding on whats going on. Scroll down for output.
Methods (Compatible with PHP 5 or greater):
/**
* Escapes the inserted value for LDAP.
*
* #param string $value The value to escape
* #param string $ignore The characters to ignore
* #param int $flags The PHP flag to use
*
* #return bool|string
*/
public function escapeManual($value, $ignore = '*', $flags = 0)
{
/*
* If a flag was supplied, we'll send the value
* off to be escaped using the PHP flag values
* and return the result.
*/
if($flags) {
return $this->escapeWithFlags($value, $ignore, $flags);
}
// Convert ignore string into an array
$ignores = str_split($ignore);
// Convert the value to a hex string
$hex = bin2hex($value);
/*
* Separate the string, with the hex length of 2,
* and place a backslash on the end of each section
*/
$value = chunk_split($hex, 2, "\\");
/*
* We'll append a backslash at the front of the string
* and remove the ending backslash of the string
*/
$value = "\\" . substr($value, 0, -1);
// Go through each character to ignore
foreach($ignores as $charToIgnore)
{
// Convert the characterToIgnore to a hex
$hexed = bin2hex($charToIgnore);
// Replace the hexed variant with the original character
$value = str_replace("\\" . $hexed, $charToIgnore, $value);
}
// Finally we can return the escaped value
return $value;
}
/**
* Escapes the inserted value with flags. Supplying either 1
* or 2 into the flags parameter will escape only certain values
*
*
* #param string $value The value to escape
* #param string $ignore The characters to ignore
* #param int $flags The PHP flag to use
* #return bool|string
*/
public function escapeWithFlags($value, $ignore = '*', $flags = 0)
{
// Convert ignore string into an array
$ignores = str_split($ignore);
$escapeFilter = ['\\', '*', '(', ')'];
$escapeDn = ['\\', ',', '=', '+', '<', '>', ';', '"', '#'];
switch($flags)
{
case 1:
// Int 1 equals to LDAP_ESCAPE_FILTER
$escapes = $escapeFilter;
break;
case 2:
// Int 2 equals to LDAP_ESCAPE_DN
$escapes = $escapeDn;
break;
case 3:
// If both LDAP_ESCAPE_FILTER and LDAP_ESCAPE_DN are used
$escapes = array_merge($escapeFilter, $escapeDn);
break;
default:
// Customize your own default return value
return false;
}
foreach($escapes as $escape)
{
// Make sure the escaped value isn't inside the ignore array
if( ! in_array($escape, $ignores))
{
$hexed = chunk_split(bin2hex($escape), 2, "\\");
$hexed = "\\" . substr($hexed, 0, -1);
$value = str_replace($escape, $hexed, $value);
}
}
return $value;
}
Tests (be aware that LDAP_ESCAPE constants are only available in PHP 5.6):
// Value to escape
$value = 'testing=+<>"";:#()*\x00';
$php = ldap_escape($value, $ignore = '*');
$man = $this->escapeManual($value, $ignore = '*');
echo $php; // \74\65\73\74\69\6e\67\3d\2b\3c\3e\22\22\3b\3a\23\28\29*\5c\78\30\30
echo $man; // \74\65\73\74\69\6e\67\3d\2b\3c\3e\22\22\3b\3a\23\28\29*\5c\78\30\30
$php = ldap_escape($value, $ignore = '*', LDAP_ESCAPE_DN);
$man = $this->escapeManual($value, $ignore = '*', LDAP_ESCAPE_DN);
echo $php; // testing\3d\2b\3c\3e\22\22\3b:\23()*\5cx00
echo $man; // testing\3d\2b\3c\3e\22\22\3b:\23()*\5cx00
$php = ldap_escape($value, $ignore = '*', LDAP_ESCAPE_FILTER);
$man = $this->escapeManual($value, $ignore = '*', LDAP_ESCAPE_FILTER);
echo $php; // testing=+<>"";:#\28\29*\5cx00
echo $man; // testing=+<>"";:#\28\29*\5cx00
Github Gist link: https://gist.github.com/stevebauman/0db9b5daa414d60fc266
Those characters must escaped to be part of the data of a distinguished name or relative distinguished name. Escape the character (as in all LDAP) with a backslash 2 hex digit, such as \2a. Anything else would not be in compliance with the standards body documents. See RFC4514 for more specific information regarding the string representation of distinguished names.
Now php can't work directly wit Postgresql array. For example, php taking postgresql array like
'{"foo","bar"}'
I need simple php function to create multidimensional postgresql array from php array.
I think that experimental pg_convert() isn't optimal because it needs of extra data to form simple array string for database output, maybe I misunderstood the idea of this function.
For example, I need to convert
$from=array( array( "par_1_1","par_1_2" ), array( "array_2_1", "array_2_2" ) );
$to='{{"par_1_1","par_1_2"},{"par_2_1","par_2_2"}}';
Can I use array_walk_recursive() to convert the deepest elements of array?
Here's a simple function for converting a PHP array to PG array.
function to_pg_array($set) {
settype($set, 'array'); // can be called with a scalar or array
$result = array();
foreach ($set as $t) {
if (is_array($t)) {
$result[] = to_pg_array($t);
} else {
$t = str_replace('"', '\\"', $t); // escape double quote
if (! is_numeric($t)) // quote only non-numeric values
$t = '"' . $t . '"';
$result[] = $t;
}
}
return '{' . implode(",", $result) . '}'; // format
}
A little edit that uses pg_escape_string for quoting and that support PHP NULLS and booleans:
/**
* Converts a php array into a postgres array (also multidimensional)
*
* Each element is escaped using pg_escape_string, only string values
* are enclosed within single quotes, numeric values no; special
* elements as php nulls or booleans are literally converted, so the
* php NULL value is written literally 'NULL' and becomes a postgres
* NULL (the same thing is done with TRUE and FALSE values).
*
* Examples :
* VARCHAR VERY BASTARD ARRAY :
* $input = array('bla bla', 'ehi "hello"', 'abc, def', ' \'VERY\' "BASTARD,\'value"', NULL);
*
* to_pg_array($input) ==>> 'ARRAY['bla bla','ehi "hello"','abc, def',' ''VERY'' "BASTARD,''value"',NULL]'
*
* try to put this value in a query (you will get a valid result):
* select unnest(ARRAY['bla bla','ehi "hello"','abc, def',' ''VERY'' "BASTARD,''value"',NULL]::varchar[])
*
* NUMERIC ARRAY:
* $input = array(1, 2, 3, 8.5, null, 7.32);
* to_pg_array($input) ==>> 'ARRAY[1,2,3,8.5,NULL,7.32]'
* try: select unnest(ARRAY[1,2,3,8.5,NULL,7.32]::numeric[])
*
* BOOLEAN ARRAY:
* $input = array(false, true, true, null);
* to_pg_array($input) ==>> 'ARRAY[FALSE,TRUE,TRUE,NULL]'
* try: select unnest(ARRAY[FALSE,TRUE,TRUE,NULL]::boolean[])
*
* MULTIDIMENSIONAL ARRAY:
* $input = array(array('abc', 'def'), array('ghi', 'jkl'));
* to_pg_array($input) ==>> 'ARRAY[ARRAY['abc','def'],ARRAY['ghi','jkl']]'
* try: select ARRAY[ARRAY['abc','def'],ARRAY['ghi','jkl']]::varchar[][]
*
* EMPTY ARRAY (is different than null!!!):
* $input = array();
* to_pg_array($input) ==>> 'ARRAY[]'
* try: select unnest(ARRAY[]::varchar[])
*
* NULL VALUE :
* $input = NULL;
* to_pg_array($input) ==>> 'NULL'
* the functions returns a string='NULL' (literally 'NULL'), so putting it
* in the query, it becomes a postgres null value.
*
* If you pass a value that is not an array, the function returns a literal 'NULL'.
*
* You should put the result of this functions directly inside a query,
* without quoting or escaping it and you cannot use this result as parameter
* of a prepared statement.
*
* Example:
* $q = 'INSERT INTO foo (field1, field_array) VALUES ($1, ' . to_pg_array($php_array) . '::varchar[])';
* $params = array('scalar_parameter');
*
* It is recommended to write the array type (ex. varchar[], numeric[], ...)
* because if the array is empty or contains only null values, postgres
* can give an error (cannot determine type of an empty array...)
*
* The function returns only a syntactically well-formed array, it does not
* make any logical check, you should consider that postgres gives errors
* if you mix different types (ex. numeric and text) or different dimensions
* in a multidim array.
*
* #param array $set PHP array
*
* #return string Array in postgres syntax
*/
function to_pg_array($set) {
if (is_null($set) || !is_array($set)) {
return 'NULL';
}
// can be called with a scalar or array
settype($set, 'array');
$result = array();
foreach ($set as $t) {
// Element is array : recursion
if (is_array($t)) {
$result[] = to_pg_array($t);
}
else {
// PHP NULL
if (is_null($t)) {
$result[] = 'NULL';
}
// PHP TRUE::boolean
elseif (is_bool($t) && $t == TRUE) {
$result[] = 'TRUE';
}
// PHP FALSE::boolean
elseif (is_bool($t) && $t == FALSE) {
$result[] = 'FALSE';
}
// Other scalar value
else {
// Escape
$t = pg_escape_string($t);
// quote only non-numeric values
if (!is_numeric($t)) {
$t = '\'' . $t . '\'';
}
$result[] = $t;
}
}
}
return 'ARRAY[' . implode(",", $result) . ']'; // format
}
This is the same as mstefano80's answer, but more human-readable, universal and modern (at least for me):
<?php
class Sql
{
/**
* Convert PHP-array to SQL-array
* https://stackoverflow.com/questions/5631387/php-array-to-postgres-array
*
* #param array $data
* #return string
*/
public static function toArray(array $data, $escape = 'pg_escape_string')
{
$result = [];
foreach ($data as $element) {
if (is_array($element)) {
$result[] = static::toArray($element, $escape);
} elseif ($element === null) {
$result[] = 'NULL';
} elseif ($element === true) {
$result[] = 'TRUE';
} elseif ($element === false) {
$result[] = 'FALSE';
} elseif (is_numeric($element)) {
$result[] = $element;
} elseif (is_string($element)) {
$result[] = "'" . $escape($element) . "'";
} else {
throw new \InvalidArgumentException("Unsupported array item");
}
}
return sprintf('ARRAY[%s]', implode(',', $result));
}
}
Tests:
<?php
use Sql;
class SqlTest extends \PHPUnit_Framework_TestCase
{
public function testToArray()
{
$this->assertSame("ARRAY['foo','bar']", Sql::toArray(['foo', 'bar']));
$this->assertSame("ARRAY[1,2]", Sql::toArray([1, 2]));
$this->assertSame("ARRAY[1,2]", Sql::toArray(['1', '2']));
$this->assertSame("ARRAY['foo\\\"bar','bar\'foo']", Sql::toArray(['foo"bar', 'bar\'foo'], function($str){
return addslashes($str);
}));
$this->assertSame("ARRAY[ARRAY['foo\\\"bar'],ARRAY['bar\'foo']]", Sql::toArray([['foo"bar'], ['bar\'foo']], function($str){
return addslashes($str);
}));
}
}