Animated GIF Creation Install Paths Tutorial

Animated GIF Creation Install Paths Tutorial

Animated GIF Creation Install Paths Tutorial

We dislike hardcoding paths to installed …

… but if your PHP is flaky with exec and shell_exec as our MAMP Windows setup is, you might understand our kludginess here.

But, onto yesterday’s Animated GIF Creation on Windows MAMP via PDF Tutorial we did a bit if globish research and ended up with the changed php_calls_pdfimages.php (also a standalone proposition) PHP code to help here …

<?php

// Does not support flag GLOB_BRACE
function rglob($pattern, $flags = 0) { // thanks to https://stackoverflow.com/questions/17160696/php-glob-scan-in-subfolders-for-a-file
$files = glob($pattern, $flags);
foreach (glob(str_replace(DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR,DIRECTORY_SEPARATOR,dirname($pattern).DIRECTORY_SEPARATOR).'*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) {
$files = array_merge(
[],
[$files, rglob($dir . DIRECTORY_SEPARATOR . basename($pattern), $flags)]
);
}
return $files;
}

if (isset($_GET['magick'])) {
$fnd=false;
if (PHP_OS =='WINNT' || PHP_OS =='WIN32' || PHP_OS =='Windows') {
if (file_exists('./magick.txt')) {
$pplace=file_get_contents('./magick.txt');
if (strpos($pplace, 'magick.exe') !== false) {
$fnd=true;
}
}
if (!$fnd) {
$huhexe=shell_exec("where magick.exe");
if ($huhexe == '') {
$huhexes=rglob("C:\\magick.exe"); //shell_exec("where magick.exe");
if (sizeof($huhexes) > 0) {
file_put_contents('./magick.txt', $huhexes[0]);
}
}
}
}
exit;
}

if (isset($_GET['pdfimages'])) {
$fnd=false;
if (PHP_OS =='WINNT' || PHP_OS =='WIN32' || PHP_OS =='Windows') {
if (file_exists('./pdfimages.txt')) {
$pplace=file_get_contents('./pdfimages.txt');
if (strpos($pplace, 'pdfimages.exe') !== false) {
$fnd=true;
}
}
if (!$fnd) {
$huhexe=shell_exec("where pdfimages.exe");
if ($huhexe == '') {
$huhexes=rglob("C:\\pdfimages.exe"); //shell_exec("where pdfimages.exe");
if (sizeof($huhexes) > 0) {
file_put_contents('./pdfimages.txt', $huhexes[0]);
}
}
}
}
exit;
}

if (isset($_GET['ffmpeg'])) {
$fnd=false;
if (PHP_OS =='WINNT' || PHP_OS =='WIN32' || PHP_OS =='Windows') {
if (file_exists('./ffmpeg.txt')) {
$pplace=file_get_contents('./ffmpeg.txt');
if (strpos($pplace, 'ffmpeg.exe') !== false) {
$fnd=true;
}
}
if (!$fnd) {
$huhexe=shell_exec("where ffmpeg.exe");
if ($huhexe == '') {
$huhexes=rglob("C:\\ffmpeg.exe"); //shell_exec("where pdfimages.exe");
if (sizeof($huhexes) > 0) {
file_put_contents('./ffmpeg.txt', $huhexes[0]);
}
}
}
}
exit;
}

$ffmpegpre='';
$ffmpegsuf='';
$pdfimagespre='';
$pdfimagessuf='';
$wherepdfimages="?infilegetsize=";
$whereffmpeg="?infilegetsize=";
$wheremagick="?infilegetsize=";
$magickverb='convert';
$magickpre='';
$magicksuf='';
if (PHP_OS == "Darwin") {
$dn=' /dev/null';
} else if (PHP_OS =='WINNT' || PHP_OS =='WIN32' || PHP_OS =='Windows') {
$ffmpegpre="\"C:\\Program Files\\ImageMagick-7.1.0-Q16-HDRI\\";
$ffmpegsuf=".exe\"";
if (!file_exists($ffmpegpre . 'ffmpeg' . $ffmpegsuf)) {
if (file_exists('./ffmpeg.txt')) {
if (!file_exists(file_get_contents('./ffmpeg.txt'))) {
unlink('./ffmpeg.txt');
$whereffmpeg="?ffmpeg=where";
} else {
$ffmpegpre="\"" . str_replace('ffmpeg.exe','',file_get_contents('./ffmpeg.txt')) . "\"";
}
}
if (file_exists('ffmpeg' . $ffmpegsuf)) {
$ffmpegpre="";
} else if (!file_exists('./ffmpeg.txt')) {
$huhexe=shell_exec("where ffmpeg.exe");
if (strpos($huhexe, 'ffmpeg.exe') === false) {
$whereffmpeg="?ffmpeg=where";
} else {
$ffmpegpre=str_replace('ffmpeg.exe','',$huhexe);
}
}
}
$pdfimagespre="\"C:\\MAMP\\htdocs\\xpdf-tools-win-4.04\\bin32\\";
$pdfimagessuf=".exe\"";
if (!file_exists($pdfimagespre . 'pdfimages' . $pdfimagessuf)) {
if (file_exists('./pdfimages.txt')) {
if (!file_exists(file_get_contents('./pdfimages.txt'))) {
unlink('./pdfimages.txt');
$wherepdfimages="?pdfimages=where";
} else {
$pdfimagespre="\"" . str_replace('pdfimages.exe','',file_get_contents('./pdfimages.txt')) . "\"";
}
}
if (file_exists('pdfimages' . $pdfimagessuf)) {
$pdfimagespre="";
} else if (!file_exists('./pdfimages.txt')) {
$huhexe=shell_exec("where pdfimages.exe");
if (strpos($huhexe, 'pdfimages.exe') === false) {
$wherepdfimages="?pdfimages=where";
} else {
$pdfimagespre=str_replace('pdfimages.exe','',$huhexe);
}
}
}

$magickverb='magick';
$magickpre="\"C:\\Program Files\\ImageMagick-7.1.0-Q16-HDRI\\";
$magicksuf=".exe\"";
if (!file_exists($magickpre . 'magick' . $magicksuf)) {
if (file_exists('./magick.txt')) {
if (!file_exists(file_get_contents('./magick.txt'))) {
unlink('./magick.txt');
$wheremagick="?magick=where";
} else {
$magickpre="\"" . str_replace('magick.exe','',file_get_contents('./magick.txt')) . "\"";
}
}
if (file_exists('magick' . $ffmpegsuf)) {
$magickpre="";
} else if (!file_exists('./magick.txt')) {
$huhexe=shell_exec("where magick.exe");
if (strpos($huhexe, 'magick.exe') === false) {
$wheremagick="?magick=where";
} else {
$magickpre=str_replace('magick.exe','',$huhexe);
}
}
}

}

if ($ffmpegpre != '' && strpos($ffmpegpre, ' ') === false) {
$ffmpegsuf=str_replace('"','',$ffmpegsuf);
$ffmpegpre=str_replace('"','',$ffmpegpre);
}

if ($pdfimagespre != '' && strpos($pdfimagespre, ' ') === false) {
$pdfimagessuf=str_replace('"','',$pdfimagessuf);
$pdfimagespre=str_replace('"','',$pdfimagespre);
}

if ($magickpre != '' && strpos($magickpre, ' ') === false) {
$magicksuf=str_replace('"','',$magicksuf);
$magickpre=str_replace('"','',$magickpre);
}

?>


Previous relevant Animated GIF Creation on Windows MAMP via PDF Tutorial is shown below.

Animated GIF Creation on Windows MAMP via PDF Tutorial

Animated GIF Creation on Windows MAMP via PDF Tutorial

Today’s lesson, further to yesterday’s Animated GIF Creation on Windows MAMP Tutorial first day of Windows integration work with our Animated GIF Creator is …

Try not to rely on exec or shell_exec always, if there is a way to proceed using PHP native functionality

Is this a doh! moment, or is this on a case by case basis? We’d say the latter, and it could be to do with what permissions your php.ini file specifies regarding this.

No matter how we tried, we could not get a Windows command line command like …


forfiles /P "[PathToFileBestGuess]" /S /M "[FileBaseName]" /C “cmd /c echo @path@fsize" | find "[FileSizeInBytes]"

… to work out a file path when supplied a file base name and a file size and you call as above with starting folders. That works well (for deriverability (if that is a word!)) in the “cmd” window but not when called under the auspices of PHP exec or shell_exec. It could be that you lose a lot of a Windows user environment when asking PHP to do some operating system work.

Anyway, we don’t want to recommend any procedures that ask you to change php.ini in any way, and we’d much rather point you towards some PHP glob alternative ideas …

<?php

function scandir_through($dir, $bname, $bsize) { // thanks to https://www.php.net/manual/en/function.glob.php
$ds=substr((DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR),0,1);
if (strpos(('!@' . strtoupper($dir)), '!@C:') !== false) { $dir=str_replace('/','',$dir); }
if (substr(strrev($dir),0,1) == substr((DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR),0,1)) {
$ds='';
}
$items = glob($dir . $ds . str_replace(' ','*',$bname));

for ($i = 0; $i < count($items); $i++) {
if (is_dir($items[$i])) {
$add = glob($items[$i] . $ds . str_replace(' ','*',$bname));
if (filesize($add) == $bsize) {
//echo $add;
$items = array_merge($items, $add);
} //else {
//echo 'x' . $add . 'x';
//}
}
}

return $items;
}

function ourshell_exec($onea, $twoa = NULL, $threea = NULL) {
$folder='';
$pattern='';
$size='';
$filesa=[];
if (PHP_OS =='WINNT' || PHP_OS =='WIN32' || PHP_OS =='Windows' || (strpos(('~@!' . $onea), '~@!forfiles /P "') !== false && strpos(('~@!' . $onea), '/M "') !== false && strpos(('~@!' . $onea), 'find "') !== false)) {
if (strpos(('~@!' . $onea), '~@!forfiles /P "') !== false && strpos(('~@!' . $onea), '/M "') !== false && strpos(('~@!' . $onea), 'find "') !== false) {
$folder=explode('"', explode('forfiles /P "', $onea)[1])[0] . substr(DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR, 0, 1);
if (strpos($folder, ' ') !== false) { $folder='"' . $folder . '"'; }
$pattern=explode('"', explode('/M "', $onea)[1])[0];
if (strpos($pattern, ' ') !== false || substr(($folder . ' '),0,1) == '"') { $pattern='"' . $pattern . '"'; if (substr(($folder . ' '),0,1) != '"') { $folder='"' . $folder . '"'; } }
$size=explode('"', explode('find "', $onea)[1])[0];
//$basis=str_replace('""','',$folder . str_replace(str_replace('"','',explode('.',$pattern)[0]),'*',$pattern));
$basis=str_replace('""','',$folder . str_replace(str_replace('"','',explode('.',$pattern)[0]),str_replace(' ','*',str_replace('"','',explode('.',$pattern)[0])),$pattern));

if (strpos($basis, ' ') === false) { $basis=str_replace('"','',$basis); }
// echo "swqzzui " . str_replace('""','',$folder . str_replace(str_replace('"','',explode('.',$pattern)[0]),'*',$pattern)) . ' ... ' . $basis;
// exit;
$filesa=scandir_through(str_replace('"','',$folder), str_replace('"','',$pattern), $size);
if (sizeof($filesa) == 0) { return ''; }
return $filesa[0];


}
}
}
}
}
return shell_exec($onea, $twoa, $threea);
}

function rsearch($folder, $pattern, $size) { // thanks, anyway, to https://stackoverflow.com/questions/17160696/php-glob-scan-in-subfolders-for-a-file
$didea='';
$outputa=[];
$retz='';
if (PHP_OS == "Darwin") {

//file_put_contents("x.ksh", "find " . $folder . " -type f -name \"" . $pattern . "\" 2> /dev/null -exec wc -c {} + | egrep '^ " . $size . " ' | sed '/ " . $size . " /s///g'");
//if ($folder == substr((DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR),0,1)) {
$didea=shell_exec("find \$HOME/Downloads -type f -name \"" . $pattern . "\" 2> /dev/null -exec wc -c {} + | egrep '^ " . $size . " ' | sed '/ " . $size . " /s///g'");
//file_put_contents("xx.ksh", $didea);
if ($didea != '') { return $didea; }
$didea=shell_exec("find " . rtrim(dirname(__FILE__), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . " -type f -name \"" . $pattern . "\" 2> /dev/null -exec wc -c {} + | egrep '^ " . $size . " ' | sed '/ " . $size . " /s///g'");
//file_put_contents("xxx.ksh", $didea);
if ($didea != '') { return $didea; }

$xc=getenv('HOME');
if ($xc == '') {
$dirsa = glob('/Users/*', GLOB_ONLYDIR);
for ($ia=0; $ia<sizeof($dirsa); $ia++) {
//echo "2:" . $dirsa[$ia] . str_replace('//','/',DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR) . "Downloads";
//exit;
if ($didea == '') {
$didea=ourshell_exec("forfiles /P \"" . str_replace('//','/',$dirsa[$ia] . "/") . "Downloads\" /S /M \"" . $pattern . "\" /C \“cmd /c echo @path@fsize | find \"" . $size . "\" 2>&1", $outputa, $retz);
if ($didea != '') { return $didea; }
}
}
if ($didea != '') { return $didea; }
} else {
$didea=ourshell_exec("forfiles /P \"" . $xc . str_replace('//','/',DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR) . "Downloads\" /S /M \"" . $pattern . "\" /C \“cmd /c echo @path@fsize | find \"" . $size . "\" 2>&1", $outputa, $retz);
if ($didea != '') { return $didea; }
}

return shell_exec("find " . $folder . " -type f -name \"" . $pattern . "\" 2> /dev/null -exec wc -c {} + | egrep '^ " . $size . " ' | sed '/ " . $size . " /s///g'");
} else if (PHP_OS =='WINNT' || PHP_OS =='WIN32' || PHP_OS =='Windows') {
$xc=getenv('HOMEDRIVE') . getenv('HOMEPATH');
if ($xc == '') { $xc=getenv('USERPROFILE'); }
$xc='';
if ($xc == '') {
$dirsa = glob("C:\\Users\\*", GLOB_ONLYDIR);
for ($ia=0; $ia<sizeof($dirsa); $ia++) {
if ($didea == '') {
$didea=ourshell_exec("forfiles /P \"" . $dirsa[$ia] . "\\Downloads\" /S /M \"" . $pattern . "\" /C \"cmd /c echo @path@fsize | find \"" . $size . "\" 2>&1", $outputa, $retz);
if ($didea != '') { return $didea; }
}
}
if ($didea != '') { return $didea; }
} else {
$didea=ourshell_exec("forfiles /P \"" . $xc . str_replace('//','/',DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR) . "Downloads\" /S /M \"" . $pattern . "\" /C \“cmd /c echo @path@fsize | find \"" . $size . "\" 2>&1", $outputa, $retz);
if ($didea != '') { return $didea; }
}
if ($didea != '') { return $didea; }
$didea=ourshell_exec("forfiles /P \"" . $_SERVER['DOCUMENT_ROOT'] . "\" /S /M \"" . $pattern . "\" /C \"cmd /c echo @path@fsize\" | find \"" . $size . "\" 2>&1", $outputa);
if ($didea != '') { return $didea; }
$didea=ourshell_exec("forfiles /P \"%USERPROFILE%" . str_replace('//','/',DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR) . "Downloads\" /S /M \"" . $pattern . "\" /C \"cmd /c echo @path@fsize | find \"" . $size . "\" 2>&1", $outputa, $retz);
if ($didea != '') { return $didea; }
//$didea=shell_exec('forfiles /P C:' . substr("\\",0,1) . 'Downloads /S /M * /C “cmd /c if @fsize EQU ' . $size . ' if @file EQU ' . $pattern . " echo @path");
//if ($didea != '') { return $didea; }
//$didea=shell_exec('forfiles /P C:' . substr("\\",0,1) . ' /S /M * /C “cmd /c if @fsize EQU ' . $size . ' if @file EQU ' . $pattern . " echo @path 2>nul");
$didea=ourshell_exec("forfiles /P \"C:" . substr("\\",0,1) . "\" /S /M \"" . $pattern . "\" /C \"cmd /c echo @path@fsize\" | find \"" . $size . "\" 2>&1");
if ($didea != '') { return $didea; }
}
// echo "QQghfjhg";
//echo "forfiles /P \"" . $_SERVER['DOCUMENT_ROOT'] . "\" /S /M \"" . $pattern . "\" /C \"cmd /c echo @path@fsize | find \"" . $size . "\" 2>&1";
// exit;
//$iti = new RecursiveDirectoryIterator($folder);
//foreach (new RecursiveIteratorIterator($iti) as $file) {
//foreach (glob($folder . $pattern) as $file) {
// if (strpos($file , $pattern) !== false && filesize($file) == $size) {
// return $file;
// }
//}
return '';
}

if (isset($_GET['filename']) && isset($_GET['filesize']) && !isset($_GET['filepath'])) {
if (file_exists(str_replace('+',' ',urldecode($_GET['filename'])))) {
if (filesize(str_replace('+',' ',urldecode($_GET['filename']))) == $_GET['filesize']) {
$filepath=rtrim(dirname(__FILE__), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . str_replace('+',' ',urldecode($_GET['filename']));
}
}
if ($filepath == '') {
$filepath = rsearch(DIRECTORY_SEPARATOR, str_replace('+',' ',urldecode($_GET['filename'])), $_GET['filesize']);
//echo $filepath;
//exit;
}
//echo "<html><body onload=\"if (window.parent) { if (parent.document.getElementById('path')) { if (parent.document.getElementById('path').value == '') { parent.document.getElementById('path').value='" . explode(str_replace('+',' ',urldecode($_GET['filename'])), $filepath)[0] . "'; } } }\">" . $filepath . "</body></html>";
echo "<html><body onload='if (window.parent) { if (parent.document.getElementById(\"path\")) { if (parent.document.getElementById(\"path\").value == \"\") { parent.document.getElementById(\"path\").value=\"" . str_replace("\\","\\\\",explode(str_replace('+',' ',urldecode($_GET['filename'])), $filepath)[0]) . "\"; } } }'></body></html>";
exit;
} else if (isset($_GET['filename']) && isset($_GET['filesize']) && $_GET['filepath'] == '') {
if (file_exists(str_replace('+',' ',urldecode($_GET['filename'])))) {
if (filesize(str_replace('+',' ',urldecode($_GET['filename']))) == $_GET['filesize']) {
$filepath=rtrim(dirname(__FILE__), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . str_replace('+',' ',urldecode($_GET['filename']));
}
}
if ($filepath == '') {
$filepath = rsearch(DIRECTORY_SEPARATOR, str_replace('+',' ',urldecode($_GET['filename'])), $_GET['filesize']);
}
//echo "<html><body onload=\"if (window.parent) { if (parent.document.getElementById('path')) { if (parent.document.getElementById('path').value == '') { parent.document.getElementById('path').value='" . explode(str_replace('+',' ',urldecode($_GET['filename'])), $filepath)[0] . "'; } } }\">" . $filepath . "</body></html>";
echo "<html><body onload='if (window.parent) { if (parent.document.getElementById(\"path\")) { if (parent.document.getElementById(\"path\").value == \"\") { parent.document.getElementById(\"path\").value=\"" . str_replace("\\","\\\\",explode(str_replace('+',' ',urldecode($_GET['filename'])), $filepath)[0]) . "\"; } } }'></body></html>";
exit;
} else if (isset($_GET['filename']) && isset($_GET['filesize']) && isset($_GET['filepath'])) {
$anyextrac='';
if (substr(str_replace('+',' ',urldecode($_GET['filepath'])), -1, 1) != DIRECTORY_SEPARATOR) {
$anyextrac=DIRECTORY_SEPARATOR;
}
//echo "<html><body onload=\"if (window.parent) { if (parent.document.getElementById('path')) { if (parent.document.getElementById('path').value == '') { parent.document.getElementById('path').value='" . str_replace('+',' ',urldecode($_GET['filepath'])) . $anyextac . "'; } } }\">" . $filepath . "</body></html>";
echo "<html><body onload='if (window.parent) { if (parent.document.getElementById(\"path\")) { if (parent.document.getElementById(\"path\").value == \"\") { parent.document.getElementById('path').value=\"" . str_replace("\\","\\\\",str_replace('+',' ',urldecode($_GET['filepath'])) . $anyextac) . "\"; } } }'></body></html>";
exit;
}

?>

… in …

… moving closer to a happy Windows setup.


Previous relevant Animated GIF Creation on Windows MAMP Tutorial is shown below.

Animated GIF Creation on Windows MAMP Tutorial

Animated GIF Creation on Windows MAMP Tutorial

It was surprising today, finally getting around to testing Animated GIF creation on a local Windows (desktop operating system) MAMP environment, how much there was to do with the relationships amongst …

  • string values and manipulations
  • directory delimiters \ for Windows and / for others
  • string delimiters ” versus ‘ usage

It wasn’t that we’ve left hardcodings in the PHP code. We’d used a lot of DIRECTORY_SEPARATOR PHP global throughout. But that is not the whole story because …

  • usage like …

    $varis = 'C:\MAMP\htdocs';

    … can cause problems whereas, counterintuitively …
  • usage like …

    $varis = "C:\\MAMP\\htdocs";

    … causes much less hassle

… all stemming from the fact that the backslash \ Windows directory delimiter is also the Javascript escaping character.

And so, for this first tranche of Windows related changes onto yesterday’s Animated GIF via PDF Input Tutorial work we have …

… now suiting download to Windows MAMP (local Apache web server), around here using URLs such as HTTP://localhost/PHP/animegif/tutorial_to_animated_gif.php whose Document Root around here is C:\MAMP\htdocs


Previous relevant Animated GIF via PDF Input Tutorial is shown below.

Animated GIF via PDF Input Tutorial

Animated GIF via PDF Input Tutorial

As well as adding more of those dimension options as flagged at yesterday’s Animated GIF Dimensions Programmatic Help Tutorial, today, the big job with our Animated GIF creator was to allow for …


PDF input to Animated GIF output

… whether that be via …

  • browsed for PDF file (from your local device file system) …
  • textbox entry of a local device PDF absolute URL …
  • textbox entry of a local device PDF file name processed via the changed php_calls_pdfimages.php (also a standalone proposition) MAMP local Apache/PHP/MySql web server’s document root ( ie. using a MAMP macOS URL HTTP://localhost:8888/php_calls_pdfimages.php and may take you there ) …
  • textbox entry of an RJM Programming web server PDF file name still processed via the changed php_calls_pdfimages.php MAMP local Apache/PHP/MySql web server’s document root ( ie. using a MAMP macOS URL HTTP://localhost:8888/php_calls_pdfimages.php and may take you there )

… the “intranet feeling” needs of all those options, actually, being to do with how our favourite ffmpeg command line tool which helps here, does not exist up at the RJM Programming domain web server, but you are welcome to install it onto your local device along with …

… along with our changed PHP tutorial_to_animated_gif.php (that if you download to MAMP would best go to Document Root PHP/animegif folder along with the wonderful GIFEncoder.class.php … thanks) inhouse animated GIF creator web application.

As usual, when “intranet feeling” work is being done HTML iframe onload event logic is a crucial “traffic light” source of control of the workflow of the web application, and we leave you with two new such HTML iframe …

<?php echo ”

<iframe id=pdfaskfor onload=pdfmmcallol(this); style=display:none; src=" . $ifo . "/php_calls_pdfimages.php></iframe>
<iframe id=pdfsize onload=pdfsizemmcallol(this); style=display:none; src=./tutorial_to_animated_gif.php?infilegetsize=></iframe>

“; ?>

… onload Javascript logic functions introduced to the tutorial_to_animated_gif.php code for this PDF work …

<?php echo ”

function pdfsizemmcallol(iois) {
if (iois != null) {
var aconto = (iois.contentWindow || iois.contentDocument);
if (aconto != null) {
if (aconto.document) { aconto = aconto.document; }
if (aconto.body != null) {
if (aconto.body.innerHTML.trim() != '') {
var acontobodyinnerHTML=aconto.body.innerHTML;
if (acontobodyinnerHTML.indexOf('#') != -1) {
if (acontobodyinnerHTML.split('#')[0].indexOf('http') == 0) {
//alert('HeRe ' + '" . $ifo . "/php_calls_pdfimages.php?inpath=' + encodeURIComponent(acontobodyinnerHTML.split('#')[0]) + '&xconvertthis=' + encodeURIComponent(acontobodyinnerHTML.split('#')[1]) + '&outprefix=IDEAS&pdfbighp=pdftohtml_' + Math.floor(Math.random() * 19876754) + '&sw=' + screen.width + '&delp=');
document.getElementById('pdfaskfor').src='" . $ifo . "/php_calls_pdfimages.php?inpath=' + encodeURIComponent(acontobodyinnerHTML.split('#')[0]) + '&xconvertthis=' + encodeURIComponent(acontobodyinnerHTML.split('#')[1]) + '&outprefix=IDEAS&pdfbighp=pdftohtml_' + Math.floor(Math.random() * 19876754) + '&sw=' + screen.width + '&delp=';
return '';
} else {
//alert(acontobodyinnerHTML);
if (eval('' + acontobodyinnerHTML.split('#').length) == 2) {
pdfjustpathv=pdfjustfile.split(acontobodyinnerHTML.split('#')[1])[0];
pdfjustfile=acontobodyinnerHTML.split('#')[1];
document.getElementById('slideshow').placeholder=pdfjustfile;
document.body.style.cursor='progress';
if (pdfjustpathv == '" . sys_get_temp_dir() . DIRECTORY_SEPARATOR . "') {
document.getElementById('pdfsize').src=document.getElementById('pdfsize').src.split('?')[0] + '?infilegetsize=' + encodeURIComponent('" . sys_get_temp_dir() . DIRECTORY_SEPARATOR . "' + pdfjustfile) + '&infilepathdel=' + encodeURIComponent('" . sys_get_temp_dir() . DIRECTORY_SEPARATOR . "');
}
} else {
pdfjustfile=acontobodyinnerHTML.split('#')[1];
pdfjustpathv=acontobodyinnerHTML.split('#')[2].split(pdfjustfile)[0];
document.getElementById('slideshow').placeholder=pdfjustfile;
document.body.style.cursor='progress';
if (pdfjustpathv == '" . sys_get_temp_dir() . DIRECTORY_SEPARATOR . "') {
document.getElementById('pdfsize').src=document.getElementById('pdfsize').src.split('?')[0] + '?infilegetsize=' + encodeURIComponent('" . sys_get_temp_dir() . DIRECTORY_SEPARATOR . "' + pdfjustfile) + '&infilepathdel=' + encodeURIComponent('" . sys_get_temp_dir() . DIRECTORY_SEPARATOR . "');
}
}
}
}
pdfjustsize=acontobodyinnerHTML.split('#')[0];
//document.getElementById('pdfaskfor').src='" . $ifo . "/php_calls_pdfimages.php?filepath=&filesize=' + pdfjustsize + '&filename=' + encodeURIComponent(pdfjustfile);
document.getElementById('pdfaskfor').src='" . $ifo . "/php_calls_pdfimages.php?inpath=' + encodeURIComponent(pdfjustpathv) + '&convertthis=' + encodeURIComponent(pdfjustfile) + '&outprefix=IDEAS&pdfbighp=pdftohtml_' + Math.floor(Math.random() * 19876754) + '&sw=' + screen.width + '&delp=';
}
}
}
}
}

function pdfmmcallol(iois) {
if (iois != null) {
var paconto = (iois.contentWindow || iois.contentDocument);
if (paconto != null) {
if (paconto.document) { paconto = paconto.document; }
if (paconto.body != null) {
if (iois.src.indexOf('?') == -1) {
candopdf=true;
pdfjustfile=paconto.getElementById('thewords').value;
document.body.style.cursor='progress';
//document.getElementById('slideshow').placeholder=pdfjustfile;
} else if (iois.src.indexOf('&') == -1) {
pdfjustpath=paconto.getElementById('path');
pdfjustsubmit=paconto.getElementById('mysbut');
pdfjustform=paconto.getElementsByTagName('form')[0];
paconto.getElementById('outpath').value='IDEAS';
paconto.getElementById('dovideo').checked=false;
paconto.getElementById('doag').checked=false;
paconto.getElementById('thewords').value='' + decodeURIComponent(iois.src.split('thefilenameis=')[1].split('&')[0].split('#')[0]);
//paconto.getElementById('thewords').blur();
//alert('/php_calls_pdfimages.php?filepath=&filesize=' + pdfjustsize + '&filename=' + iois.src.split('thefilenameis=')[1].split('&')[0].split('#')[0]);
paconto.getElementById('gifif').src='/php_calls_pdfimages.php?filepath=&filesize=' + pdfjustsize + '&filename=' + iois.src.split('thefilenameis=')[1].split('&')[0].split('#')[0];
if (pdfjustpath.value.trim() != '') {
pdfjustpathv=pdfjustpath.value;
pdfjustform.method='GET';
pdfjustform.target='_self';
//alert('/php_calls_pdfimages.php?inpath=' + encodeURIComponent(pdfjustpathv) + '&convertthis=' + encodeURIComponent(pdfjustfile) + '&outprefix=IDEAS&pdfbighp=pdftohtml_' + Math.floor(Math.random() * 19876754) + '&sw=' + screen.width + '&delp=');
if (1 == 1) {
document.getElementById('pdfaskfor').src='" . $ifo . "/php_calls_pdfimages.php?inpath=' + encodeURIComponent(pdfjustpathv) + '&convertthis=' + encodeURIComponent(pdfjustfile) + '&outprefix=IDEAS&pdfbighp=pdftohtml_' + Math.floor(Math.random() * 19876754) + '&sw=' + screen.width + '&delp=';
} else {
pdfjustsubmit.click();
}
} else {
setTimeout(postpdfcall, 3000);
}
} else {
startlook='000';
if (iois.src.indexOf('startlook=') != -1) { startlook=decodeURIComponent(iois.src.split('startlook=')[1].split('&')[0].split('#')[0]); }
//alert(startlook);
newimgdo();
}
}
}
}
}

“; ?>


Previous relevant Animated GIF Dimensions Programmatic Help Tutorial is shown below.

Animated GIF Dimensions Programmatic Help Tutorial

Animated GIF Dimensions Programmatic Help Tutorial

Onto yesterday’s Animated GIF Text SVG Image Slide Tutorial, today, we want to improve the Animated GIF dimensions issue, first by saying, the user should at least visit the dropdown to see, but given this may not happen, we want to help out in two ways …

  1. for HtTp and hTtP URL types pick out the 300,300 animated GIF dimension
  2. for hTTp URL types pick out the maximum dimensions on the dropdown

… but we need to do a better job assessing some media data URI underlying image dimensions to place on that dropdown, as work for the future.

Take a look at …

<?php echo ”

function postatend() {
if (document.getElementById('selwhs')) {
if (document.getElementById('selwhs').value == '') {
var valas=document.getElementById('selwhs').innerHTML.split(' value=\"');
var vmax='', ivmax=0;
for (var ivalas=1; ivalas<valas.length; ivalas++) {
if (valas[ivalas].split('\"')[0].indexOf(',') != -1) {
if (eval(eval(valas[ivalas].split('\"')[0].split(',')[0]) + eval(valas[ivalas].split('\"')[0].split(',')[1])) > ivmax) {
ivmax=eval(eval(valas[ivalas].split('\"')[0].split(',')[0]) + eval(valas[ivalas].split('\"')[0].split(',')[1]));
vmax=valas[ivalas].split('\"')[0];
document.getElementById('selwhs').value=vmax;
}
}
}
if (vmax != '') { document.getElementById('selwhs').value=vmax; setTimeout(postatend, 6000); }
}
}
}

“; ?>

… as our logic to start trying this out in our changed PHP tutorial_to_animated_gif.php inhouse animated GIF creator web application.


Previous relevant Animated GIF Text SVG Image Slide Tutorial is shown below.

Animated GIF Text SVG Image Slide Tutorial

Animated GIF Text SVG Image Slide Tutorial

In the recent Animated GIF Slide QR Code and Webpage Screenshot URL Tutorial we surmised that …

  1. interactive entry of absolute URL starting with HtTp means you want a QR Code … and …
  2. interactive entry of absolute URL starting with hTtP means you want (to involve, along the line, creating an animated QR Code scenario) a Webpage Screenshot

… represented two interesting ways whereby an association between a webpage URL and image (animated GIF slide) data could happen. But we forgot another one, that fact that “any text” can become SVG and so can move on to be associated with an image (animated GIF slide) data.

We’ve been hedging towards more useful ways for our animated GIFs to be “standalone teaching presentation” resources, and starting to be able to create these …

  • black text
  • white background
  • linefeeds via ~~

… new text based slide ideas, perhaps as explanatory “blurbs” between true image (and/or webpage screenshot) based slides, so as to be … more … self … explanatory.

User wise there are three ways to make this happen, the first of which has existed before today’s work …

  • enter into an animated GIF slide textbox SVG that contains valid “text” SVG element data … or …
  • enter the text you want into an animated GIF slide textbox (as you can see happening with today’s animated GIF tutorial picture)
  • for hTTp textbox entries, or via address bar “irefresh” argument URLs, append ++++++++

This adds onto yesterday’s Animated GIF Link Image Slide Tutorial with our changed PHP tutorial_to_animated_gif.php inhouse animated GIF creator web application.


Previous relevant Animated GIF Link Image Slide Tutorial is shown below.

Animated GIF Link Image Slide Tutorial

Animated GIF Link Image Slide Tutorial

Yesterday’s Animated GIF SVG Slide Tutorial had us …

  • taking our animated GIF creator … starting with …
  • SVG user entry functionality … then allow for …
  • other image extraction from HTML user input via + … and today …
  • “a” link to either …
    1. QR Code … via ++ … or …
    2. Webpage Screenshot … via ++++

Do you see the pattern here? If you have a favoured character (ie. “+” here) involved in a user functionality behaviour decision you can give each a …


power of 2 number of characters

… (functionality meaning) and at the Javascript or PHP interpretive end of this arrangement you can know exactly what the user wants (in a way akin to how a bitmap can often be used) … so far this Javascript working (and tailorable into the future with some tweaking) as per …

<?php echo ”

function srchrefit(inbg) {
var outbg=inbg, outbis=[], ibis=0;
var ourblankend=blankend;
if (blankend != '') {
if (eval(eval('' + ourblankend.length) % 2) == 1) { // process img
ourblankend=ourblankend.substring(1);
outbg=outbg.replace(/data\:image\/svg\+xml/g, '!@#$%^&');
outbg=outbg.replace(/data\:image/g, ' SRC=\" data:image');
outbg=outbg.replace(/\!\@\#\$\%\^\&/g, 'data:image/svg+xml');
outbis=outbg.split('<img');
console.log('outbis.length=' + outbis.length + ' and outbg=' + outbg);
for (ibis=1; ibis<eval('' + outbis.length); ibis++) {
if (outbis[ibis].split('>')[0].indexOf(' src=\"') != -1) {
outbg=outbg.replace('<img' + outbis[ibis].split('>')[0], '<img' + outbis[ibis].split('>')[0].replace(' src=\"', ' SRC=\" '));
}
}
}
if (eval('' + ourblankend.length) == 4) { // process "a" links to Webpage Screenshot
outbis=outbg.split('<a');
console.log('outbis.length=' + outbis.length);
for (ibis=1; ibis<eval('' + outbis.length); ibis++) {
if (outbis[ibis].split('>')[0].indexOf(' href=\"') != -1) {
outbg=outbg.replace('<a' + outbis[ibis].split('>')[0], '<a' + outbis[ibis].split('>')[0].replace(' href=\"', ' SRC=\" '));
}
} // ... or ...
} else if (eval('' + ourblankend.length) == 2) { // process "a" links to QR Code
outbis=outbg.split('<a');
console.log('outbis.length=' + outbis.length);
for (ibis=1; ibis<eval('' + outbis.length); ibis++) {
if (outbis[ibis].split('>')[0].indexOf(' href=\"') != -1) {
outbg=outbg.replace('<a' + outbis[ibis].split('>')[0], '<a' + outbis[ibis].split('>')[0].replace(' href=\"', ' SRC=\" '));
}
}
}
return outbg;
}
return inbg;
}

“; ?>

… in our changed PHP tutorial_to_animated_gif.php inhouse animated GIF creator web application

https://www.rjmprogramming.com.au/PHP/animegif/tutorial_to_animated_gif.php?irefresh=hTTp://[HtmlWebpageWithSVG].html+++

… which fills in the slide data (the link above hooking up to the web application featuring in Circle Terminology in Mathematics Tutorial extracting SVG and hidden non-SVG images and one “a” link presented as a QR Code) …

https://www.rjmprogramming.com.au/PHP/animegif/tutorial_to_animated_gif.php?irefresh=hTTps://[HtmlWebpageWithSVG].html+++++

… which fills in the slide data (the link above hooking up to the web application featuring in Circle Terminology in Mathematics Tutorial extracting SVG and hidden non-SVG images and one “a” link presented as a Webpage Screenshot).


Previous relevant Animated GIF SVG Slide Tutorial is shown below.

Animated GIF SVG Slide Tutorial

Animated GIF SVG Slide Tutorial

The PHP GD library we use to help create animated GIFs (along with a whole lot of other help, it goes without saying) is not into vector graphics which is what …

  • Inkscape … as a vector graphics editor … and …
  • SVG … ie. Scalable Vector Graphics

… are really into … ooooohhh, aaaaahhhh … but luckily for us, the great ImageMagick offers functionality to convert a SVG image file into a PNG image file, via …

Non Windows Windows
convert infile.svg outfile.png magick.exe infile.svg outfile.png

… and we’re using that talent ImageMagick has to offer the user the chance, at any animated GIF slide textbox, the chance to enter encodeURIComponent and window.btoa sensitive entries whose (content) format could match (one of) …

… means by which a user can involve SVG input slides into their animated GIF creations in our changed PHP tutorial_to_animated_gif.php inhouse animated GIF creator web application further to yesterday’s Animated GIF Slide QR Code and Webpage Screenshot URL Tutorial.

Below is a new (PHP writes) Javascript iframe (iois object below) onload event function for recognizing hTTps://[HtmlWebpageWithSVG].html as above, and setting the iframe’s “src” attribute to its value …

<?php echo ”

var mm1='', mm2='', mm3='';
var gdgebimm='', gtval='', onealready='';
var tvals=[], thistval=0, thistdelim='', thistid='';


function latermm() {
maybemore(mm3.value, mm2, mm3);
mm1='';
mm2='';
mm3='';
}

function svgmmcallol(iois, tid) {
//alert('TID=' + tid);
var tval='', it=0, dgebimm='';
var tis=document.getElementById(tid);
thistid=tid;
tvals=[];
thistval=0;
thistdelim='';
if (iois != null) {
var aconto = (iois.contentWindow || iois.contentDocument);
if (aconto != null) {
if (aconto.document) { aconto = aconto.document; }
if (aconto.body != null) {
tval=aconto.body.innerHTML;
//alert('Tval=' + tval);
if (tval.indexOf(encodeURIComponent('data:image/svg+xml')) != -1) {
thistval=1;
tvals=tval.split(encodeURIComponent('data:image/svg+xml'));
thistdelim=encodeURIComponent('data:image/svg+xml');
//alert('thistdeliM=' + thistdelim);


tval='data:image/svg+xml' + decodeURIComponent(tvals[thistval].split(String.fromCharCode(34))[0].split(String.fromCharCode(39))[0].split(')')[0].split('&')[0].split('>')[0]);
//alert('tvAl=' + tval);
//alert('tId=' + tid);

mm1=tval;
mm2=tid;
mm3=document.getElementById(tid.replace(/^slideshow1$/g,'slideshow'));
dgebimm='<iframe style=display:none; onload=mmcallol(this); src=\"' + '/PHP/fgc/index.php?askfor=' + encodeURIComponent(tval) + '\"></iframe>';
if (eval('' + dgebimm.length) > 800) {
document.getElementById('myaskfor').value=tval;
document.getElementById('saskfor').click();
} else {
document.getElementById('mmcall').innerHTML=dgebimm;
}
//setTimeout(latermm, 9000);
return '';
} else if (tval.indexOf('data:image/svg+xml') != -1) {
thistval=1;
tvals=tval.split('data:image/svg+xml');
thistdelim='data:image/svg+xml';
tval='data:image/svg+xml' + tvals[thistval].split(String.fromCharCode(34))[0].split(String.fromCharCode(39))[0].split(')')[0].split('&')[0].split('>')[0];

mm1=tval;
mm2=tid;
mm3=document.getElementById(tid.replace(/^slideshow1$/g,'slideshow'));
dgebimm='<iframe style=display:none; onload=mmcallol(this); src=\"' + '/PHP/fgc/index.php?askfor=' + encodeURIComponent(tval) + '\"></iframe>';
if (eval('' + dgebimm.length) > 800) {
document.getElementById('myaskfor').value=tval;
document.getElementById('saskfor').click();
} else {
document.getElementById('mmcall').innerHTML=dgebimm;
}
//setTimeout(latermm, 9000);
return '';
} else if (tval.indexOf(encodeURIComponent('<svg')) != -1 || tval.indexOf(encodeURIComponent('<SVG')) != -1) {
thistval=1;
if (tval.indexOf(encodeURIComponent('<svg')) != -1) {
tvals=tval.split(encodeURIComponent('<svg'));
thistdelim=encodeURIComponent('<svg');
tval='data:image/svg+xml;utf8,<svg' + decodeURIComponent(tvals[thistval].split(encodeURIComponent('</svg>'))[0]) + '</svg>';
} else {
tvals=tval.split(encodeURIComponent('<SVG'));
thistdelim=encodeURIComponent('<SVG');
tval='data:image/svg+xml;utf8,<SVG' + decodeURIComponent(tvals[thistval].split(encodeURIComponent('</SVG>'))[0]) + '</SVG>';
}
mm1=tval;
mm2=tid;
mm3=document.getElementById(tid.replace(/^slideshow1$/g,'slideshow'));
dgebimm='<iframe style=display:none; onload=mmcallol(this); src=\"' + '/PHP/fgc/index.php?askfor=' + encodeURIComponent(tval) + '\"></iframe>';
if (eval('' + dgebimm.length) > 800) {
document.getElementById('myaskfor').value=tval;
document.getElementById('saskfor').click();
} else {
document.getElementById('mmcall').innerHTML=dgebimm;
}
//setTimeout(latermm, 9000);
return '';
} else if (tval.indexOf(window.btoa('<svg')) != -1 || tval.indexOf(window.btoa('<SVG')) != -1) {
thistval=1;
if (tval.indexOf(window.btoa('<svg')) != -1) {
tvals=tval.split(window.btoa('<svg'));
thistdelim=window.btoa('<svg');
tval='data:image/svg+xml;base64,' + window.btoa('<svg') + tvals[thistval].split(window.btoa('</svg>'))[0] + window.btoa('</svg>');
} else {
tvals=tval.split(window.btoa('<SVG'));
thistdelim=window.btoa('<SVG');
tval='data:image/svg+xml;base64,' + window.btoa('<SVG') + tvals[thistval].split(window.btoa('</SVG>'))[0] + window.btoa('</SVG>');
}
mm1=tval;
mm2=tid;
mm3=document.getElementById(tid.replace(/^slideshow1$/g,'slideshow'));
dgebimm='<iframe style=display:none; onload=mmcallol(this); src=\"' + '/PHP/fgc/index.php?askfor=' + encodeURIComponent(tval) + '\"></iframe>';
if (eval('' + dgebimm.length) > 800) {
document.getElementById('myaskfor').value=tval;
document.getElementById('saskfor').click();
} else {
document.getElementById('mmcall').innerHTML=dgebimm;
}
//setTimeout(latermm, 9000);
return '';
} else if (tval.toLowerCase().indexOf('<svg') != -1) {
if (tval.indexOf('<svg') != -1) {
thistdelim='<svg';
tval='data:image/svg+xml;utf8,<svg' + tvals[thistval].split('</svg>')[0] + '</svg>';
} else {
thistdelim='<SVG';
tval='data:image/svg+xml;utf8,<SVG' + tvals[thistval].split('</SVG>')[0] + '</SVG>';
}
//alert('Thistdelim=' + thistdelim);
tvals=tval.split('<svg');
thistval=1;
//alert('1:tval=' + tval);
mm1=tval;
mm2=tid;
mm3=document.getElementById(tid.replace(/^slideshow1$/g,'slideshow'));
dgebimm='<iframe style=display:none; onload=mmcallol(this); src=\"' + '/PHP/fgc/index.php?askfor=' + encodeURIComponent(tval) + '\"></iframe>';
//alert('2:tval=' + tval);
if (eval('' + dgebimm.length) > 800) {
//alert('0: ' + dgebimm);
document.getElementById('myaskfor').value=tval;
document.getElementById('saskfor').click();
} else {
//alert('1: ' + dgebimm);
document.getElementById('mmcall').innerHTML=dgebimm;
}
//alert('3:tval=' + tval);
//setTimeout(latermm, 9000);
return '';
}
}
}
}
}

“; ?>


Previous relevant Animated GIF Slide QR Code and Webpage Screenshot URL Tutorial is shown below.

Animated GIF Slide QR Code and Webpage Screenshot URL Tutorial

Animated GIF Slide QR Code and Webpage Screenshot URL Tutorial

We wanted, today, to channel the (cruel might say “warped”) thinking behind the recent URL …

  1. interactive entry of absolute URL starting with HtTp means you want a QR Code … and …
  2. interactive entry of absolute URL starting with hTtP means you want (to involve, along the line, creating an animated QR Code scenario) a Webpage Screenshot

… we last talked about at Circular Text Around Media Animated QR Code Tutorial, because we feel this is actually a good inhouse idea to hang on to as a principle. Why?! Glad you asked. It is another way to …

  • end up with an image …
  • from any old absolute (but we have not yet researched ? and & get argument(s) regarding) URL

… really suiting the purpose of today’s work, that being the integration of this idea into our changed PHP tutorial_to_animated_gif.php inhouse animated GIF creator web application we last talked about at PdfImages PDF Output Media Zipping via PHP Tutorial. After all, an animated GIF slide is also an image, and it could be used in this way, to …

  1. create animated GIF of QR Code means by which a smart device user using their Camera might navigate to a series of interesting webpage(s) … or …
  2. create animated GIF of “current snapshot looks” of a series of URLs of interest (with even more currency than Google Earth shows your place!)

It might be you design an animated GIF chapter of slides and always want to follow it up with a “further reading” webpage you could present as a QR Code or Webpage Screenshot.

Anyway, at the “onblur” event Javascript function logic we intervened to end up with an image/png data URI substitute for the HtTp or hTtP URL the user enters to re-enter the normal animated GIF image definition workflow of the animated GIF creator …

<?php echo ”

var mm1='', mm2='', mm3='';


function latermm() {
maybemore(mm3.value, mm2, mm3);
mm1='';
mm2='';
mm3='';
}

function maybemore(tval, tid, tis) {
var newi=null, fo=null;
var inmb=0;
if (tval.indexOf('HtTp') == 0) {
mm1=tval;
mm2=tid;
mm3=tis;
document.getElementById('mmcall').innerHTML='<iframe style=display:none; onload=mmcallol(this); src=\"' + '/PHP/fgc/index.php?justcontent=&askfor=' + encodeURIComponent(document.URL.split('//')[0] + '//chart.googleapis.com/chart?chs=300x300&cht=qr&chl=' + encodeURIComponent('http' + encodeURIComponent(tval.substring(4).replace('S:','s:'))) + '&choe=UTF-8') + '\"></iframe>';
//setTimeout(latermm, 9000);
return '';
} else if (tval.indexOf('hTtP') == 0) {
mm1=tval;
mm2=tid;
mm3=tis;
document.getElementById('mmcall').innerHTML='<iframe style=display:none; onload=mmcallol(this); src=\"' + '/PHP/fgc/index.php?askfor=&askyou=' + encodeURIComponent('http' + tval.substring(4).replace('S:','s:')) + '\"></iframe>';
//setTimeout(latermm, 9000);
return '';
}

// rest of maybemore follows ...
}

“; ?>

… to help introduce this new animated GIF slide functionality arrangement.

If this was interesting you may be interested in this too.


If this was interesting you may be interested in this too.


If this was interesting you may be interested in this too.


If this was interesting you may be interested in this too.


If this was interesting you may be interested in this too.


If this was interesting you may be interested in this too.


If this was interesting you may be interested in this too.


If this was interesting you may be interested in this too.


If this was interesting you may be interested in this too.

This entry was posted in Ajax, eLearning, Event-Driven Programming, Tutorials and tagged , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *