find and replace all occurrences of string [php shortcodes] - php

i'm using this code to replace shortcodes in a CMS with links including images but it replaces only the first shortcode
$string = $row['Content'];
if(stristr($string,'[gal=')){
$startTag = "[gal=";
$endTag = "]";
$pos1 = strpos($string, $startTag) + strlen($startTag);
$pos2 = strpos($string, $endTag);
$gal = substr($string, $pos1, $pos2-$pos1);
$q=$db->prepare("select * from images where Gal_ID = :gal");
$q->execute(["gal"=>$gal]);
$imgs='';
while($r=$q->fetch(PDO::FETCH_ASSOC)){
$images[] = $r['Image'];
}
foreach($images as $val){
$imgs .= "<a href='gallery/large/$val' class='fancybox-thumbs' rel='gallery'><img src='gallery/thumb/$val'></a>";
}
$result = substr_replace($string, $imgs, $pos1, $pos2-$pos1);
$result = str_replace($startTag,'',$result);
$result = str_replace($endTag,'',$result);
echo $result;
}
else{
echo $string;
}
string contains some paragraphs and 2 shortcodes
[gal=36] and [gal=37]
the result is replacing only the first shortcode with links and images but the second shortcode is displayed like this: "37" just the number. So how to loop through all shortcodes to replace them with links not only the first shortcode

Here is a full example how I described above.
//get matches
if(preg_match_all('/\[gal=(\d+)\]/i', $string, $matches) > 0){
//query for all images. You could/should bind this, but since the expression
//matches only numbers, it is technically not possible to inject anything.
//However best practices are going to be "always bind".
$q=$db->prepare("select Gal_ID, Image from images where Gal_ID in (".implode(',', $matches[1]).")");
$q->execute();
//format the images into an array
$images = array();
while($r=$q->fetch(PDO::FETCH_ASSOC)){
$images[$r['Gal_ID']][] = "<a href='gallery/large/{$r['Image']}' class='fancybox-thumbs' rel='gallery'><img src='gallery/thumb/{$r['Image']}'></a>";
}
//replace shortcode with images
$result = preg_replace_callback('/\[gal=(\d+)\]/i', function($match) use ($images){
if(isset($images[$match[1]])){
return implode('', $images[$match[1]]);
} else {
return $match[0];
}
}, $string);
echo $result;
}
I tested it as much as I could, but I don't have PDO and/or your tables. This should work as a pretty much drop in replacement for what you have above.

Related

Multiple occurances of delimeters within a HTML template

I am facing a problem that I can't get my head around. I thought I would turn to the experts once again to shine some light.
I have a HTML template and within the template I have delimiters like:
[has_image]<p>The image is <img src="" /></p>[/has_image]
These delimiters may have multiple occurances within the template and below is what I am trying to achieve:
Find all occurances of these delimiters and replace the content between these delimiters with an image source or replace it empty if image doesn't exist but still keep the value/content of the remaining template.
Below is my code that works only for one occurance but struggling to accomplish it for multiple occurances.
function replace_text_template($template_body, $start_tag, $end_tag, $replacement = ''){
$occurances = substr_count($template_body, $start_tag);
$x = 1;
while($x <= $occurances) {
$start = strpos($template_body, $start_tag);
$stop = strpos($template_body, $end_tag);
$template_body = substr($template_body, 0, $start) . $start_tag . $replacement . substr($template_body, $stop);
$x++;
}
return $template_body;
}
$template_body will have HTML code with delimiters
replace_text_template($template_body, "[has_image]", "[/has_image]");
Whether I remove the while loop it still works for a single delimiter.
I have managed to solve the problem. If anybody finds this useful please feel free to use the code. However, if anyone finds a better way please do share it.
function replace_text_template($template_body, $start_tag, $end_tag, $replacement = ''){
$occurances = substr_count($template_body, $start_tag);
$x = 1;
while($x <= $occurances) {
$start = strpos($template_body, $start_tag);
$stop = strpos($template_body, $end_tag);
$template_body = substr($template_body, 0, $start) . $start_tag . $replacement . substr($template_body, $stop);
$template_body = str_replace($start_tag.''.$end_tag, '', $template_body); // replace the tags so on next loop the position will be correct
$x++;
}
return $template_body;
}
function replace_text_template($template_body, $start_tag, $replacement = '') {
return preg_replace_callback("~\[".preg_quote($start_tag)."\].*?\[\/".preg_quote($start_tag)."\]~i", function ($matches) use ($replacement) {
if(preg_match('~<img.*?src="([^"]+)"~i', $matches[0], $match)) {
if (is_array(getimagesize($match[1]))) return $match[1];
}
return $replacement;
}, $template_body);
}
$template_body = <<<EOL
text
[has_image]<p>The image is <img src="" /></p>[/has_image]
abc [has_image]<p>The image is <img src="http://blog.stackoverflow.com/wp-content/themes/se-company/images/logo.png" /></p>[/has_image]xyz
EOL;
echo replace_text_template($template_body, "has_image", "replacement");
Returns:
text
replacement
abc http://blog.stackoverflow.com/wp-content/themes/se-company/images/logo.pngxyz

Regex not quite right

I have a site crawler which displays a list of urls, but the problem is I cannot for the life of me get the last regex quite right.
all urls end up listed as:
http://www.website.org/page1.html&--EFTTIUGJ4ITCyh0Frzb_LFXe_eHw
http://website.net/page2/&--EyqBLeFeCkSfmvA7p0cLrsy1Zm1g
http://foobar.website.com/page3.php&--E5WRBxuTOQikDIyBczaVXveOdRFg
The Urls can all be different and the only thing which seems static is the & symbol.
How would go abouts getting rid of the & symbol and everything beyond it to the right?
Here is what I have tried with the above results:
function getresults($sterm) {
$html = file_get_html($sterm);
$result = "";
// find all span tags with class=gb1
foreach($html->find('h3[class="r"]') as $ef)
{
$result .= $ef->outertext . '<br>';
}
return $result;
}
function geturl($url) {
$var = $url;
$result = "";
preg_match_all ("/a[\s]+[^>]*?href[\s]?=[\s\"\/url?q=\']+".
"(.*?)[\"\']+.*?>"."([^<]+|.*?)?<\/a>/",
$var, $matches);
$matches = $matches[1];
foreach($matches as $var)
{
$result .= $var."<br>";
}
echo preg_replace('/sa=U.*?usg=.*?AFQjCN/', "--" , $result);
}
if url are ALWAYS in the same format, use explode :
<?php
$tmp = explode("&", "http://foobar.website.com/page3.php&--E5WRBxuTOQikDIyBczaVXveOdRFg");
?>
$tmp[0] should content "http://foobar.website.com/page3.php" and
$tmp[1] should content "--E5WRBxuTOQikDIyBczaVXveOdRFg"
A simple way to remove everything after the & character:
$result = substr($result, 0, strpos($result, '&'));

PHP - read variable from string then replace with variable from Database

In my PHP forum i want people to insert pictures just by inserting the number/ID of the picture (which they can see in an online photoalbum)
I am looking for a function that can read a string from their posts for example
"bla bla and look at this amazing picture [IMG]234[/IMG] isn't it awesome ..."
then finds the picture in a database with the ID 234 and replaces [IMG]234[/IMG] with
<img src = "path/to/image.jpg" />
preg_replace wouldn't work :( does anyone have an Idea?
thanks for Your help in advance
I would do it in this way:
$images = array();
$post = preg_replace_callback('|\[img\](\d+)\[/img\]|i', function($matches) use(&$images) {
$images[] = $matches[1];
return '__image_' . $matches[1];
}, $post);
if (count($images)) {
// Select images
$imageIds = implode(',', $images);
// DB query
$res = mysql_query("SELECT id, path FROM post_images WHERE id IN ({$imageIds})") or die(mysql_error());
// Replace
while (($row = mysql_fetch_assoc($res))) {
$post = str_replace('__image_'.$row['id'], '<img src=' . $row['path'] . ' />', $post);
}
}
The advantage is that you make only one query to database. This is always important to minimize them to increase performance. if you don't care or you are sure that the number of images in not going to be too high, you can simple use this code:
$post = preg_replace_callback('|\[img\](\d+)\[/img\]|i', function($matches) {
$res = mysql_query("SELECT path FROM post_images WHERE id = {$matches[1]}") or die(mysql_error());
$path = mysql_result($res, 0);
return "<img src='$path' />";
}, $post);
here is what i did:
$find = preg_match_all("!\[img\][0-9]+\[\/img\]!", $post, $matches);
foreach ($matches as $match) {
foreach ($match as $ma) {
$res = str_replace("[/img]","", str_replace("[img]", "",$ma));
$query = "
SELECT
path
FROM
table
WHERE id = '".$res."'
";
$result = mysql_query($query, $conn) or die(mysql_error());
while($line = mysql_fetch_array($result)) {
$path = $line["path"];
$path = "<img src = '".$path. "'></img>";
$post = str_replace ("[img]" . $res . "[/img]", $path, $post);
}
}
}
note: i don't know why preg_match_all creates a 2 depths array
Your code needs to make a db call to get the path from the ID.
Use a tag parser or basic regex to match \[img][0-9]+\[/img]. (preg_match)
Then query your database for the path with the match result as the ID.
Finally, use str_replace to replace the original "[img]$match[/img]" with
Example (pseudo, look these functions up first)
$matches = preg_match_all('\[img][0-9]+\[/img]', $input);
$output = $input;
foreach ($matches as $match) {
$cur = mysql_query("SELECT path FROM table WHERE ID = $match");
$row = mysql_fetch_array($cur);
$output = str_replace('[img]'.$match.'[/img]',$row['path'],$output);
}
Note: this will only work with tags in lowercasse: img and not with IMG, iMG, ImG and so on.
Why doesn't preg_replace work? Not knowing the the details, I would guess that you are not escaping the square brackets. What does your regular expression look like? Try something like
/\[IMG\](\d+)\[/IMG]/
The image id will then be in the group 1, i.e. $1.
Example:
preg_match_all('/\[IMG\](\d+)\[/IMG]/', $post, $matches);
foreach ($matches as $match) {
echo "Image number: " . $match[1] . "\n";
}

Highlighting keywords in PHP search script

I have a PHP search script that queries a MySQL database and then parses the results through HTML to allow CSS styling. I want the script to highlight all of the keywords in the results that the user has search for. How can I do this with PHP?
My PHP script is:
<?php
mysql_connect("localhost","username","password");
mysql_select_db("database");
if(!empty($_GET['q'])){
$query=mysql_real_escape_string(trim($_GET['q']));
$searchSQL="SELECT * FROM links WHERE `title` LIKE '%{$query}%' LIMIT 8";
$searchResult=mysql_query($searchSQL);
while ($row=mysql_fetch_assoc($searchResult)){
$results[]="<a href='{$row['url']}' class='webresult'><div class='title'>{$row['title']}</div><div class='desc'>{$row['description']}</div><div class='url'>{$row['url']}</div></a>";
}
if(empty($results)){
echo 'No results were found';
} else {
echo implode($results);
}
}
?>
Simplistically you could adapt the loop here:
$searchvar = trim($_GET['q']);
while ($row=mysql_fetch_assoc($searchResult)){
$description = str_replace($searchvar, '<span class="highlight">'.$searchvar."</span>", $row['description']);
$results .="<a href='{$row['url']}' class='webresult'>
<div class='title'>{$row['title']}</div>
<div class='desc'>{$description}</div>
<div class='url'>{$row['url']}</div></a>";
}
To make it a little better:
$searchvar = explode(" ", trim($_GET['q'])); //puts each space separated word into the array.
while ($row=mysql_fetch_assoc($searchResult)){
$description = $row['description'];
foreach($searchvar as $var) $description = str_replace($var, '<span class="highlight">'.$var."</span>", $description);
$description = str_replace($searchvar, '<span class="highlight">'.$searchvar."</span>", $row['description']);
$results .="<a href='{$row['url']}' class='webresult'>
<div class='title'>{$row['title']}</div>
<div class='desc'>{$description}</div>
<div class='url'>{$row['url']}</div></a>";
}
The benefit of the second one there is that if a user types in "ipod toudch yellow" you will be searching for "ipod", "toudch" and "yellow" which would negate the type and make the results more general.
You would need to exchange the single:
like '%query%'
with
foreach(explode(" ", trim($_GET['q']) as $searchvar) $where[] = "like '%$searchvar%'";
$wheresql = implode(" OR ", $where);
to get each search "word" to be looked for in the sql or you will have a limited search with a unrelated highlight.
i also found this solution to highlight each word of the results:
$text = $searchresults;
class highlight
{
public $output_text;
function __construct($text, $words)
{
$split_words = explode( " " , $words );
foreach ($split_words as $word)
{
$text = preg_replace("|($word)|Ui" , "<b>$1</b>" , $text );
}
$this->output_text = $text;
}
}
$highlight = new highlight($searchresults, $keywords);
echo $highlight;
In case that could help,
Regards,
Max
you can use regex or simply str replace to look for a particular string and add a span around it:
while ($row=mysql_fetch_assoc($searchResult)){
$str ="<a href='".$row['url']."' class='webresult'>";
$str .="<div class='title'>".$row['title']."</div>";
$str .="<div class='desc'>";
$str .= str_replace($query,"<span class='hightlighted'>".$query."</span>",$row['description']);
$str .="</div><div class='url'>".$row['url']."</div></a>";
$result[] = $str;
}
now the css:
span.highlighted {
background-color: yellow;
}
You can use str_replace. For each keyword the user uses, put it into the $search array, and also put it into a $replace array, but surround with with a classed span tag in the latter, which you can style with CSS later. For example:
$search = array('apple', 'orange');
$replace = array();
foreach ($search as $word)
{
$replace[] = "<span class='highlight'>$word</span>";
}
$string = str_replace($search, $replace, $string);
EDIT: assuming that $query just contains keywords delimited by a single whitespace, you could get the search array this way (with explode),
$search = explode(' ', $query);
Or, if you want to add more complex logic for processing the keywords out of the $query variable (like if you use query operators like +), you could use a for loop:
$queryTerms = explode(' ', $query);
$search = array();
foreach ($queryTerms as $term)
{
// do some processing of the $term (like delete "+"?)
// ...
$search[] = $processedTerm;
}

How to wrap user mentions in a HTML link on PHP?

Im working on a commenting web application and i want to parse user mentions (#user) as links. Here is what I have so far:
$text = "#user is not #user1 but #user3 is #user4";
$pattern = "/\#(\w+)/";
preg_match_all($pattern,$text,$matches);
if($matches){
$sql = "SELECT *
FROM users
WHERE username IN ('" .implode("','",$matches[1]). "')
ORDER BY LENGTH(username) DESC";
$users = $this->getQuery($sql);
foreach($users as $i=>$u){
$text = str_replace("#{$u['username']}",
"<a href='#' class='ct-userLink' rel='{$u['user_id']}'>#{$u['username']}</a> ", $text);
}
$echo $text;
}
The problem is that user links are being overlapped:
<a rel="11327" class="ct-userLink" href="#">
<a rel="21327" class="ct-userLink" href="#">#user</a>1
</a>
How can I avoid links overlapping?
Answer Update
Thanks to the answer picked, this is how my new foreach loop looks like:
foreach($users as $i=>$u){
$text = preg_replace("/#".$u['username']."\b/",
"<a href='#' title='{$u['user_id']}'>#{$u['username']}</a> ", $text);
}
Problem seems to be that some usernames can encompass other usernames. So you replace user1 properly with <a>user1</a>. Then, user matches and replaces with <a><a>user</a>1</a>. My suggestion is to change your string replace to a regex with a word boundary, \b, that is required after the username.
The Twitter widget has JavaScript code to do this. I ported it to PHP in my WordPress plugin. Here's the relevant part:
function format_tweet($tweet) {
// add #reply links
$tweet_text = preg_replace("/\B[#@]([a-zA-Z0-9_]{1,20})/",
"#<a class='atreply' href='http://twitter.com/$1'>$1</a>",
$tweet);
// make other links clickable
$matches = array();
$link_info = preg_match_all("/\b(((https*\:\/\/)|www\.)[^\"\']+?)(([!?,.\)]+)?(\s|$))/",
$tweet_text, $matches, PREG_SET_ORDER);
if ($link_info) {
foreach ($matches as $match) {
$http = preg_match("/w/", $match[2]) ? 'http://' : '';
$tweet_text = str_replace($match[0],
"<a href='" . $http . $match[1] . "'>" . $match[1] . "</a>" . $match[4],
$tweet_text);
}
}
return $tweet_text;
}
instead of parsing for '#user' parse for '#user ' (with space in the end) or ' #user ' to even avoid wrong parsing of email addresses (eg: mailaddress#user.com) maybe ' #user: ' should also be allowed. this will only work, if usernames have no whitespaces...
You can go for a custom str replace function which stops at first replace.. Something like ...
function str_replace_once($needle , $replace , $haystack){
$pos = strpos($haystack, $needle);
if ($pos === false) {
// Nothing found
return $haystack;
}
return substr_replace($haystack, $replace, $pos, strlen($needle));
}
And use it like:
foreach($users as $i=>$u){
$text = str_replace_once("#{$u['username']}",
"<a href='#' class='ct-userLink' rel='{$u['user_id']}'>#{$u['username']}</a> ", $text);
}
You shouldn’t replace one certain user mention at a time but all at once. You could use preg_split to do that:
// split text at mention while retaining user name
$parts = preg_split("/#(\w+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
$n = count($parts);
// $n is always an odd number; 1 means no match found
if ($n > 1) {
// collect user names
$users = array();
for ($i=1; $i<$n; $i+=2) {
$users[$parts[$i]] = '';
}
// get corresponding user information
$sql = "SELECT *
FROM users
WHERE username IN ('" .implode("','", array_keys($users)). "')";
$users = array();
foreach ($this->getQuery($sql) as $user) {
$users[$user['username']] = $user;
}
// replace mentions
for ($i=1; $i<$n; $i+=2) {
$u = $users[$parts[$i]];
$parts[$i] = "<a href='#' class='ct-userLink' rel='{$u['user_id']}'>#{$u['username']}</a>";
}
// put everything back together
$text = implode('', $parts);
}
I like dnl solution of parsing ' #user', but maybe is not suitable for you.
Anyway, did you try to use strip_tags function to remove the anchor tags? That way you have the string without the links, and you can parse it building the links again.
strip_tags

Categories