Search and replace a string within a given pattern in PHP - php

I am trying to generate html from a given string pattern, similar to a plugin.
There are three patterns, a no arg, a single arg and a multi arg string pattern. I can't change this pattern since it's from a CMS.
{pluginName} or {pluginName=3} or {pluginName id=3|view=simple|arg999=asv}
An example:
<p>Hi this is a html page</p>
<p>The following line should generate html</p>
{pluginName=3}
<p>The following line also should generate html</p>
{pluginName id=3|view=simple|arg999=asv}
My goal is to replace those "tags" with something (it's not relavant for this question the processing per say). However I want to be able to pass the args given to a class/function that should handle that logic.
This is my first attempt, without using regexes since I don't know how I could approach this problem with them (and mainly because they are slower).
<?php
function processPlugins($text, $pos = 0, $start = '{', $end = '}') {
$plugins = array('plugin1', 'plugin2');
while(($pos = strpos($text, $start, $pos)) !== false) {
$startPos = $pos;
$pos += strlen($start);
foreach($plugins as $plugin) {
if(substr($text, $pos, strlen($plugin)) === $plugin
&& ($endPos = strpos($text, $end, $pos + strlen($plugin))) !== false) {
$char = substr($text, $pos + strlen($plugin), 1); // 1 is strlen of (= or ' ')
$pos += strlen($plugin) + 1; // 1 is strlen of (= or ' ')
$argString = substr($text, $pos, $endPos - $pos);
if($char === ' ') { //Multi arg
$params = explode('|', trim($argString));
$paramDict = array();
foreach ($params as $param) {
list($k, $v) = array_pad(explode('=', $param), 2, null);
$paramDict[$k] = $v;
}
//$output = $plugin->processDictionary($paramDict);
var_dump($paramDict);
} elseif ($char === '=') { //One arg
//$output = $plugin->processArg($argString);
echo $argString . "\n";
} elseif ($char === $end) { //No arg
//$output = $plugin->processNoArg();
echo $plugin. "\n";
}
$pos = $endPos + strlen($end);
break;
}
}
}
}
processPlugins('{plugin1}');
processPlugins('{plugin2=3}');
processPlugins('{plugin2 arg1=b|arg2=d}');
The previous code works in a PHP sandbox.
This code seems to work (for now) but it seems sketchy. Would you approach this problem differently? Could I refactor this code somehow?

If you opt for string manipulation functions over regex, why not use explode for stripping the input down to the significant part?
Here is an alternative implementation:
function processPlugins($text, $pos = 0, $start = '{', $end = '}') {
$t = substr($text, $pos);
if($pos > 0) {
echo "$pos chracters removed from the begining: $t" . PHP_EOL;
} else {
echo "Starting with '$t'" . PHP_EOL;
}
$parts = explode($start, $t);
$t = $parts[1];
$parts = explode($end, $t);
$t = $parts[0];
echo "The part between curly braces: '$t'" . PHP_EOL;
$t = str_replace(['plugin1', 'plugin2'], '', $t);
echo "After plugin name has been removed: '$t'" . PHP_EOL;
$n = strlen($t);
if(!$n) {
echo "Processing complete: " . trim($parts[0]) . PHP_EOL . PHP_EOL;
return;
}
$params = explode('|', $t);
echo 'Key-Values: ' . json_encode($params) . PHP_EOL;
$kv = [];
foreach($params as $p) {
list($k, $v) = explode('=', trim($p));
echo " Item: '$p', Key: '$k', Value: '$v'" . PHP_EOL;
if($k === '') {
echo "Processing complete: $v" . PHP_EOL . PHP_EOL;
return;
}
$kv[$k] = $v;
}
echo "Processing complete: " . json_encode($kv) . PHP_EOL . PHP_EOL;
}
echo '<pre>';
processPlugins('{plugin1}');
processPlugins('{plugin2=3}');
processPlugins('{plugin2 arg1=b|arg2=d}');
Of course the echo lines could be thrown away. With them in place we get this output:
Starting with '{plugin1}'
The part between curly braces: 'plugin1'
After plugin name has been removed: ''
Processing complete: plugin1
Starting with '{plugin2=3}'
The part between curly braces: 'plugin2=3'
After plugin name has been removed: '=3'
Key-Values: ["=3"]
Item: '=3', Key: '', Value: '3'
Processing complete: 3
Starting with '{plugin2 arg1=b|arg2=d}'
The part between curly braces: 'plugin2 arg1=b|arg2=d'
After plugin name has been removed: 'arg1=b|arg2=d'
Key-Values: [" arg1=b","arg2=d"]
Item: ' arg1=b', Key: 'arg1', Value: 'b'
Item: 'arg2=d', Key: 'arg2', Value: 'd'
Processing complete: {"arg1":"b","arg2":"d"}

This version works with inputs having more than one plugin token.
function processPlugins($text, $pos = 0, $start = '{', $end = '}') {
$processed = [];
$t = substr($text, $pos);
$parts = explode($start, $t);
array_shift($parts);
foreach($parts as $part) {
$pparts = explode($end, $part);
$t = trim($pparts[0]);
$t = str_replace(['plugin1', 'plugin2'], '', $t);
$n = strlen($t);
if(!$n) {
$processed[] = trim($pparts[0]);
continue;
}
$params = explode('|', $t);
$kv = [];
foreach($params as $p) {
list($k, $v) = explode('=', trim($p));
if(trim($k) === '') {
$processed[] = trim($v);
continue 2;
}
$kv[trim($k)] = trim($v);
}
$processed[] = $kv;
}
return $processed;
}
function test($case) {
$p = processPlugins($case);
echo "$case => " . json_encode($p) . PHP_EOL;
}
$cases = [
'{plugin1}',
'{plugin2=3}',
'{plugin2 arg1=b|arg2=d}',
'text here {plugin1} and more{plugin2=55}here {plugin2 arg1=b|arg2=d} till the end'
];
foreach($cases as $case) {
test($case);
}
The output:
{plugin1} => ["plugin1"]
{plugin2=3} => ["3"]
{plugin2 arg1=b|arg2=d} => [{"arg1":"b","arg2":"d"}]
text here {plugin1} and more{plugin2=55}here {plugin2 arg1=b|arg2=d} till the end => ["plugin1","55",{"arg1":"b","arg2":"d"}]

Related

How to find exact or closest match from a large array, in a big string?

Consider the following. I have a very large array with all the road names in a given country, ordered by string length like this:
$roadNames = ['Ivy Lane','East Road','The Maltings','Greenhill Road', 'Woodlands Close']; //And many, many more
Now i want look for an exact match in a in a long string
$string = "
..... ALOT OF TEXT .....
..... ALOT OF TEXT .....
You can find us at: Greenhill Road 1, 11111, The City
..... ALOT OF TEXT .....
..... ALOT OF TEXT .....
";
To find an exact match, witch is fairly easy, I just do the following:
foreach ($roadNames as $roadName) {
if(stripos($string, $roadName) !== false){
echo 'Exact match: '.$roadName;
break;
}
}
But what if the road name is misspelled by 1 letter, fx. an extra space / a space is missing, a letter less/more, or 1 letter is wrong. Fx. "Greenhil Road", "Greenhilll Road", "GreenhillRoad", "Green hill Road", "Creenhill Road"? How can i now find the best match of all my road names in the array if the the road name in the $string was one of the examples? Is there any mathematics way to do it? Or maybe i can buid a regex?
I am thinking something like this, although it seems like overkill (And does not work)
foreach ($roadNames as $roadName) {
if (stripos($string, $roadName)) {
echo 'Exact match: ' . $roadName;
break;
} else {
$alphabet = range('a', 'z');
$alphabet[] = ' ';
$roadName_split = str_split($roadName);
$test_array = array();
foreach ($roadName_split as $strpos => $letter) {
foreach ($alphabet as $letter_in_alphabet) {
$test_array[] = $letter_in_alphabet;
}
foreach ($test_array as $key => $value) {
$test_array[$key] .= substr($roadName, $strpos, 1);
}
}
echo '<pre>';
print_r($test_array);
echo '</pre>';
die;
foreach ($test_array as $misplled_value) {
if (stripos($string, $misplled_value)) {
echo 'close match found: ' . $roadName;
break;
}
}
// OR Some kind of a regex, dont know how it should be
// $roadName_split = str_split($roadName);
// $re = '';
// foreach ($roadName_split as $strpos => $letter) {
// $re .= "$letter?";
// }
}
}
I finally found a solution with inspiration from: https://stackoverflow.com/a/1720798/2192013
I make a regex from each roadName, allowing 1 letter to be misspelled, 1 letter or space, too short or too much, with this function:
function generateRegex($word) {
$len = strlen($word);
$regex = "/(($word)";
$word_split = str_split($word);
foreach ($word_split as $strpos => $letter) {
$temp = $word;
$temp[$strpos - 1] = '.';
if ($strpos === 0) {
$temp1 = substr($word, ($strpos + 1));
$temp2 = '.' . substr($word, $strpos);
} else {
$temp1 = substr($word, 0, $strpos) . substr($word, ($strpos + 1));
$temp2 = substr($word, 0, $strpos) . '.' . substr($word, $strpos);
}
$regex .= "|($temp)";
$regex .= "|($temp1)";
$regex .= "|($temp2)";
}
$regex = $regex . ")/mi";
return $regex;
}
$temp take cares of 1 letter misspelled (Ex. Creenhill Road) and are
replacing 1 letter with a dot, as many times as possible
$temp1 takes care of 1 letter/space too short (Ex. Grenhill Road OR
GreenhillRoad) and are removing 1 letter, as many times as possible
$temp2 takes care of 1 letter/space too much (Ex. Green hill Road OR
Greennhill Road) and are adding a dot to each letter, as many times
as possible
So the final script looks like this:
function generateRegex($word) {
$len = strlen($word);
$regex = "/(($word)";
$word_split = str_split($word);
foreach ($word_split as $strpos => $letter) {
$temp = $word;
$temp[$strpos - 1] = '.';
if ($strpos === 0) {
$temp1 = substr($word, ($strpos + 1));
$temp2 = '.' . substr($word, $strpos);
} else {
$temp1 = substr($word, 0, $strpos) . substr($word, ($strpos + 1));
$temp2 = substr($word, 0, $strpos) . '.' . substr($word, $strpos);
}
$regex .= "|($temp)";
$regex .= "|($temp1)";
$regex .= "|($temp2)";
}
$regex = $regex . ")/mi";
return $regex;
}
function findBestMatch($roadNames, $string) {
foreach ($roadNames as $roadName) {
if (stripos($string, $roadName)) {
$return = 'Exact match found: ' . $roadName;
break;
} else {
$re = generateRegex($roadName);
if (preg_match($re, $string)) {
$return = 'Close match found: ' . $roadName;
break;
}
}
}
if (!isset($return) OR empty($return)) {
return 'Match not found';
} else {
return $return;
}
}
$roadNames = ['Greenhill Road', 'Ivy Lane', 'East Road', 'The Maltings', 'Woodlands Close']; //And many, many more
$misspelled_examples = ['Greenhill Road', 'Crennhill Road', 'Green hill Road', 'Greennhill Road', 'GreenhillRoad', 'Grenhill Road', 'GrenhilRoad'];
foreach ($misspelled_examples as $value) {
$strings[] = "
..... ALOT OF TEXT .....
..... ALOT OF TEXT .....
You can find us at: $value 1, 11111, The City
..... ALOT OF TEXT .....
..... ALOT OF TEXT .....
";
}
foreach ($strings as $key => $string) {
echo 'Input road-name: ' . $misspelled_examples[$key];
echo '<br>';
echo findBestMatch($roadNames, $string);
echo '<hr>';
}

str_replace when matched and followed by space or special characters

I have a working function that strips profanity words.
The word list is compose of 1700 bad words.
My problem is that it censored
'badwords '
but not
'badwords.' , 'badwords' and the like.
If I chose to remove space after
$badword[$key] = $word;
instead of
$badword[$key] = $word." ";
then I would have a bigger problem because if the bad word is CON then it will stripped a word CONSTANT
My question is, how can i strip a WORD followed by special characters except space?
badword. badword# badword,
.
function badWordFilter($data)
{
$wordlist = file_get_contents("badwordsnew.txt");
$words = explode(",", $wordlist);
$badword = array();
$replacementword = array();
foreach ($words as $key => $word)
{
$badword[$key] = $word." ";
$replacementword[$key] = addStars($word);
}
return str_ireplace($badword,$replacementword,$data);
}
function addStars($word)
{
$length = strlen($word);
return "*" . substr($word, 1, 1) . str_repeat("*", $length - 2)." " ;
}
Assuming that $data is a text that needs to be censored, badWordFilter() will return the text with bad words as *.
function badWordFilter($data)
{
$wordlist = file_get_contents("badwordsnew.txt");
$words = explode(",", $wordlist);
$specialCharacters = ["!","#","#","$","%","^","&","*","(",")","_","+",".",",",""];
$dataList = explode(" ", $data);
$output = "";
foreach ($dataList as $check)
{
$temp = $check;
$doesContain = contains($check, $words);
if($doesContain != false){
foreach($specialCharacters as $character){
if($check == $doesContain . $character || $check == $character . $doesContain ){
$temp = addStars($doesContain);
}
}
}
$output .= $temp . " ";
}
return $output;
}
function contains($str, array $arr)
{
foreach($arr as $a) {
if (stripos($str,$a) !== false) return $a;
}
return false;
}
function addStars($word)
{
$length = strlen($word);
return "*" . substr($word, 1, 1) . str_repeat("*", $length - 2)." " ;
}
Sandbox
I was able to answer my own question with the help of #maxchehab answer, but I can't declared his answer because it has fault at some area. I am posting this answer so others can use this code when they need a BAD WORD FILTER.
function badWordFinder($data)
{
$data = " " . $data . " "; //adding white space at the beginning and end of $data will help stripped bad words located at the begging and/or end.
$badwordlist = "bad,words,here,comma separated,no space before and after the word(s),multiple word is allowed"; //file_get_contents("badwordsnew.txt"); //
$badwords = explode(",", $badwordlist);
$capturedBadwords = array();
foreach ($badwords as $bad)
{
if(stripos($data, $bad))
{
array_push($capturedBadwords, $bad);
}
}
return badWordFilter($data, $capturedBadwords);
}
function badWordFilter($data, array $capturedBadwords)
{
$specialCharacters = ["!","#","#","$","%","^","&","*","(",")","_","+",".",","," "];
foreach ($specialCharacters as $endingAt)
{
foreach ($capturedBadwords as $bad)
{
$data = str_ireplace($bad.$endingAt, addStars($bad), $data);
}
}
return trim($data);
}
function addStars($bad)
{
$length = strlen($bad);
return "*" . substr($bad, 1, 1) . str_repeat("*", $length - 2)." ";
}
$str = 'i am bad words but i cant post it here because it is not allowed by the website some bad words# here with bad. ending in specia character but my code is badly strong so i can captured and striped those bad words.';
echo "$str<br><br>";
echo badWordFinder($str);

Laravel Auto-Link library

I'm looking for a travel auto-link detection.
I'm trying to make a social media website and when my users post URLs I need it so like shows instead of just normal text.
Try Autologin for Laravel by dwightwatson, which provides you to generate URLs that will provide automatic login to your application and then redirect to the appropriate location
As far as I know, there's no equivalent in the Laravel's core for the auto_link() funtion helper from Code Igniter (assuming you are refering to the CI version).
Anyway, it's very simple to grab that code and use it in Laravel for a quick an dirty workaround. I just did casually looking for the same issue.
Put in your App directory a container class for your helpers (or any containter for the matter, it's just need to be discovered by the framework), in this case I put a UrlHelpers.php file. Then, inside of it put this two static functions grabbed for the CI version:
class UrlHelpers
{
static function auto_link($str, $type = 'both', $popup = FALSE)
{
// Find and replace any URLs.
if ($type !== 'email' && preg_match_all('#(\w*://|www\.)[^\s()<>;]+\w#i', $str, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
// Set our target HTML if using popup links.
$target = ($popup) ? ' target="_blank"' : '';
// We process the links in reverse order (last -> first) so that
// the returned string offsets from preg_match_all() are not
// moved as we add more HTML.
foreach (array_reverse($matches) as $match) {
// $match[0] is the matched string/link
// $match[1] is either a protocol prefix or 'www.'
//
// With PREG_OFFSET_CAPTURE, both of the above is an array,
// where the actual value is held in [0] and its offset at the [1] index.
$a = '<a href="' . (strpos($match[1][0], '/') ? '' : 'http://') . $match[0][0] . '"' . $target . '>' . $match[0][0] . '</a>';
$str = substr_replace($str, $a, $match[0][1], strlen($match[0][0]));
}
}
// Find and replace any emails.
if ($type !== 'url' && preg_match_all('#([\w\.\-\+]+#[a-z0-9\-]+\.[a-z0-9\-\.]+[^[:punct:]\s])#i', $str, $matches, PREG_OFFSET_CAPTURE)) {
foreach (array_reverse($matches[0]) as $match) {
if (filter_var($match[0], FILTER_VALIDATE_EMAIL) !== FALSE) {
$str = substr_replace($str, static::safe_mailto($match[0]), $match[1], strlen($match[0]));
}
}
}
return $str;
}
static function safe_mailto($email, $title = '', $attributes = '')
{
$title = (string)$title;
if ($title === '') {
$title = $email;
}
$x = str_split('<a href="mailto:', 1);
for ($i = 0, $l = strlen($email); $i < $l; $i++) {
$x[] = '|' . ord($email[$i]);
}
$x[] = '"';
if ($attributes !== '') {
if (is_array($attributes)) {
foreach ($attributes as $key => $val) {
$x[] = ' ' . $key . '="';
for ($i = 0, $l = strlen($val); $i < $l; $i++) {
$x[] = '|' . ord($val[$i]);
}
$x[] = '"';
}
} else {
for ($i = 0, $l = strlen($attributes); $i < $l; $i++) {
$x[] = $attributes[$i];
}
}
}
$x[] = '>';
$temp = array();
for ($i = 0, $l = strlen($title); $i < $l; $i++) {
$ordinal = ord($title[$i]);
if ($ordinal < 128) {
$x[] = '|' . $ordinal;
} else {
if (count($temp) === 0) {
$count = ($ordinal < 224) ? 2 : 3;
}
$temp[] = $ordinal;
if (count($temp) === $count) {
$number = ($count === 3)
? (($temp[0] % 16) * 4096) + (($temp[1] % 64) * 64) + ($temp[2] % 64)
: (($temp[0] % 32) * 64) + ($temp[1] % 64);
$x[] = '|' . $number;
$count = 1;
$temp = array();
}
}
}
$x[] = '<';
$x[] = '/';
$x[] = 'a';
$x[] = '>';
$x = array_reverse($x);
$output = "<script type=\"text/javascript\">\n"
. "\t//<![CDATA[\n"
. "\tvar l=new Array();\n";
for ($i = 0, $c = count($x); $i < $c; $i++) {
$output .= "\tl[" . $i . "] = '" . $x[$i] . "';\n";
}
$output .= "\n\tfor (var i = l.length-1; i >= 0; i=i-1) {\n"
. "\t\tif (l[i].substring(0, 1) === '|') document.write(\"&#\"+unescape(l[i].substring(1))+\";\");\n"
. "\t\telse document.write(unescape(l[i]));\n"
. "\t}\n"
. "\t//]]>\n"
. '</script>';
return $output;
}
}
The function safe_mailto is used in case there are email links in your string. If you don't need it you are free to modify the code.
Then you could use the helper class like this in any part of your Laravel code as usually (here inside a blade template, but the principle is the same):
<p>{!! \App\Helpers\Helpers::auto_link($string) !!}</p>
Quick and dirty, and It works. Hope to have helped. ¡Good luck!

Crop the string only after closing tag

I want to cut the string, if the string length is greater than 80.My need is if the string contain tag and cut the string in between the opening and closing tag,string crop should be only after closing tag.THis is my code.
<?php
echo $word='hello good morning<span class="em emj2"></span> <span class="em emj13"></span> <span class="em emj19"></span> <span class="em emj13"></span> hai';
$a=strlen($word);
if($a>80)
{
echo substr($word,0,80);
}
else
echo $word;
?>
I know that my answer is not in good ethics as according to stackoverflow, as I dont have time to explain exactly how every part of it works. But this is a function I use to crop strings and maintain the HTML code.
function truncate($text, $length, $suffix = '…', $isHTML = true) {
$i = 0;
$simpleTags=array('br'=>true,'hr'=>true,'input'=>true,'image'=>true,'link'=>true,'meta'=>true);
$tags = array();
if($isHTML){
preg_match_all('/<[^>]+>([^<]*)/', $text, $m, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
foreach($m as $o){
if($o[0][1] - $i >= $length)
break;
$t = substr(strtok($o[0][0], " \t\n\r\0\x0B>"), 1);
if($t[0] != '/' && (!isset($simpleTags[$t])))
$tags[] = $t;
elseif(end($tags) == substr($t, 1))
array_pop($tags);
$i += $o[1][1] - $o[0][1];
}
}
$output = substr($text, 0, $length = min(strlen($text), $length + $i));
$output2 = (count($tags = array_reverse($tags)) ? '</' . implode('></', $tags) . '>' : '');
$pos = (int)end(end(preg_split('/<.*>| /', $output, -1, PREG_SPLIT_OFFSET_CAPTURE)));
$output.=$output2;
$one = substr($output, 0, $pos);
$two = substr($output, $pos, (strlen($output) - $pos));
preg_match_all('/<(.*?)>/s', $two, $tags);
if (strlen($text) > $length) { $one .= $suffix; }
$output = $one . implode($tags[0]);
$output = str_replace('</!-->','',$output);
return $output;
}
Then simply do like so:
truncate($your_string, '80', $suffix = '…', $isHTML = true);

PHP script to extract artist & title from Shoutcast/Icecast stream

I found a script which can extract the artist & title name from an Icecast or Shoutcast stream.
I want the script to update automatically when a song changed, at the moment its working only when i execute it. I'm new to PHP so any help will be appreciated.
Thanks!
define('CRLF', "\r\n");
class streaminfo{
public $valid = false;
public $useragent = 'Winamp 2.81';
protected $headers = array();
protected $metadata = array();
public function __construct($location){
$errno = $errstr = '';
$t = parse_url($location);
$sock = fsockopen($t['host'], $t['port'], $errno, $errstr, 5);
$path = isset($t['path'])?$t['path']:'/';
if ($sock){
$request = 'GET '.$path.' HTTP/1.0' . CRLF .
'Host: ' . $t['host'] . CRLF .
'Connection: Close' . CRLF .
'User-Agent: ' . $this->useragent . CRLF .
'Accept: */*' . CRLF .
'icy-metadata: 1'.CRLF.
'icy-prebuffer: 65536'.CRLF.
(isset($t['user'])?'Authorization: Basic '.base64_encode($t['user'].':'.$t['pass']).CRLF:'').
'X-TipOfTheDay: Winamp "Classic" rulez all of them.' . CRLF . CRLF;
if (fwrite($sock, $request)){
$theaders = $line = '';
while (!feof($sock)){
$line = fgets($sock, 4096);
if('' == trim($line)){
break;
}
$theaders .= $line;
}
$theaders = explode(CRLF, $theaders);
foreach ($theaders as $header){
$t = explode(':', $header);
if (isset($t[0]) && trim($t[0]) != ''){
$name = preg_replace('/[^a-z][^a-z0-9]*/i','', strtolower(trim($t[0])));
array_shift($t);
$value = trim(implode(':', $t));
if ($value != ''){
if (is_numeric($value)){
$this->headers[$name] = (int)$value;
}else{
$this->headers[$name] = $value;
}
}
}
}
if (!isset($this->headers['icymetaint'])){
$data = ''; $metainterval = 512;
while(!feof($sock)){
$data .= fgetc($sock);
if (strlen($data) >= $metainterval) break;
}
$this->print_data($data);
$matches = array();
preg_match_all('/([\x00-\xff]{2})\x0\x0([a-z]+)=/i', $data, $matches, PREG_OFFSET_CAPTURE);
preg_match_all('/([a-z]+)=([a-z0-9\(\)\[\]., ]+)/i', $data, $matches, PREG_SPLIT_NO_EMPTY);
echo '<pre>';var_dump($matches);echo '</pre>';
$title = $artist = '';
foreach ($matches[0] as $nr => $values){
$offset = $values[1];
$length = ord($values[0]{0}) +
(ord($values[0]{1}) * 256)+
(ord($values[0]{2}) * 256*256)+
(ord($values[0]{3}) * 256*256*256);
$info = substr($data, $offset + 4, $length);
$seperator = strpos($info, '=');
$this->metadata[substr($info, 0, $seperator)] = substr($info, $seperator + 1);
if (substr($info, 0, $seperator) == 'title') $title = substr($info, $seperator + 1);
if (substr($info, 0, $seperator) == 'artist') $artist = substr($info, $seperator + 1);
}
$this->metadata['streamtitle'] = $artist . ' - ' . $title;
}else{
$metainterval = $this->headers['icymetaint'];
$intervals = 0;
$metadata = '';
while(1){
$data = '';
while(!feof($sock)){
$data .= fgetc($sock);
if (strlen($data) >= $metainterval) break;
}
//$this->print_data($data);
$len = join(unpack('c', fgetc($sock))) * 16;
if ($len > 0){
$metadata = str_replace("\0", '', fread($sock, $len));
break;
}else{
$intervals++;
if ($intervals > 100) break;
}
}
$metarr = explode(';', $metadata);
foreach ($metarr as $meta){
$t = explode('=', $meta);
if (isset($t[0]) && trim($t[0]) != ''){
$name = preg_replace('/[^a-z][^a-z0-9]*/i','', strtolower(trim($t[0])));
array_shift($t);
$value = trim(implode('=', $t));
if (substr($value, 0, 1) == '"' || substr($value, 0, 1) == "'"){
$value = substr($value, 1);
}
if (substr($value, -1) == '"' || substr($value, -1) == "'"){
$value = substr($value, 0, -1);
}
if ($value != ''){
$this->metadata[$name] = $value;
}
}
}
}
fclose($sock);
$this->valid = true;
}else echo 'unable to write.';
}else echo 'no socket '.$errno.' - '.$errstr.'.';
}
public function print_data($data){
$data = str_split($data);
$c = 0;
$string = '';
echo "<pre>\n000000 ";
foreach ($data as $char){
$string .= addcslashes($char, "\n\r\0\t");
$hex = dechex(join(unpack('C', $char)));
if ($c % 4 == 0) echo ' ';
if ($c % (4*4) == 0 && $c != 0){
foreach (str_split($string) as $s){
//echo " $string\n";
if (ord($s) < 32 || ord($s) > 126){
echo '\\'.ord($s);
}else{
echo $s;
}
}
echo "\n";
$string = '';
echo str_pad($c, 6, '0', STR_PAD_LEFT).' ';
}
if (strlen($hex) < 1) $hex = '00';
if (strlen($hex) < 2) $hex = '0'.$hex;
echo $hex.' ';
$c++;
}
echo " $string\n</pre>";
}
public function __get($name){
if (isset($this->metadata[$name])){
return $this->metadata[$name];
}
if (isset($this->headers[$name])){
return $this->headers[$name];
}
return null;
}
}
$t = new streaminfo('http://64.236.34.196:80/stream/1014'); // get metadata
echo Meta Interval: $t->icymetaint;
echo Current Track: $t->streamtitle;
You will need to constantly query the stream at a set interval to find when the song changes.
This can be best done by scheduling a cron job.
If on Windows, you should use the Windows Task Scheduler
If you want to run the PHP script to keep your meta data up to date (I'm assuming you're making a website and using html audio tags here) you can use the ontimeupdate event with an ajax function. If you're not you probably should look up your audio playback documentation for something similar.
<audio src="http://ip:port/;" ontimeupdate="loadXMLDoc()">
You can find a great example here http://www.w3schools.com/php/php_ajax_php.asp
You want to use the PHP echo function all the relevant information at once using one php variable at the very end of your script.
<?php ....
$phpVar=$streamtitle;
$phpVar2=$streamsong;
$result="I want my string to look like this: <br> {$phpVar} {$phpVar2}";
echo $result;
?>
and then use the function called by the .onreadystatechange to modify the particular elements you want on your website by using the .resonseText (this will contain the same content as your PHP script's echo).
After SCOURING the web for 4 hours, this is the only Shoutcast metadata script I've found that works! Thankyou.
To run this constantly, why not use a setInterval combined with jQuery's AJAX call?
<script>
$(function() {
setInterval(getTrackName,16000);
});
function getTrackName() {
$.ajax({
url: "track_name.php"
})
.done(function( data ) {
$( "#results" ).text( data );
});
}
</script>
Also your last couple 'echo' lines were breaking the script for me. Just put quotes around the Meta Interval, etc....

Categories