Moblogscript in PHP

Eigentlich wollte ich ja schon seit längerem etwas über mein Moblog-Script schreiben. Eigentlich seit dem Wunsch von Znuk, aber leider ist es bisher immer beim "wollen" geblieben. Da ich nun mein Script etwas verbessert habe, greife ich die Gelegenheit gleich beim Schopf und erkläre die Funktionsweise kurz.

Funktionsweise

In der Theorie sieht das ganze sehr simpel aus. Ich mache auf meinem Handy (Samsung SGH-600E) ein Foto, und sende es per Mail an eine (geheime) Mailadresse. Auf dem Webserver wird nun alle 15 Minuten ein PHP-Script aufgerufen, welches überprüft ob in der Mailbox neue Mails sind, diese dann verarbeitet und schlussendlich einen neuen Beitrag auf meiner Homepage erstellt.

Spammails

Ein Problem an der Sache sind die Spammails. Das Problem kann man aber einfach umgehen, indem man eine Mailadresse nimmt, die nicht so schnell erraten werden kann, diese nirgens publiziert und im PHP-Script noch eine Passwortabfrage einbaut. Ich bin hier auf Nummer sicher gegangen und habe alle beiden Methoden eingesetzt, den schliesslich möchte ich keine Spammails auf meiner Homepage.

Mailaufbau

Damit das ganze mit dem Passwortschutz und der zuordnung zum richtigen Thema auch funktioniert, muss das E-Mail natürlich auch immer gleich aufgebaut werden. Bei mir sieht das nun folgendermassen aus:
passwort
Thema
Ort
Text für die Homepage
Auf der ersten Zeile steht das Passwort, welches auch als erstes geprüft wird, darauf gehe ich aber spätzer genauer ein. Darauf folgt das Thema, in welches der Aktrikel gehört. Zum Bsp. "Bike", "Diverses" usw. Da ich Moblogs ja immer von unterwegs versende ist es interessant von wo das Mail versendet wurde. Mein Handy hat noch keinen GPS Empfänger und so kann ich auch keine Koordinaten angeben, also muss ich mich auf den Ortsnamen beschränken. Aber das reicht im Normalfall bestens aus. Zum Schluss folgt der Text, welcher auf der Homepage stehen soll.
Das Problem, welches ich meistens habe, ist das Tippen auf der kleinen "Tastatur" auf dem Handy, aber für kurze Nachrichten reicht es meist und wirklich wichtig ist ja das Bild.

Das Script

Nun zum wirklich spannenden Teil dieses Beitrags. Das Script ist in PHP geschrieben und wird alle 15 Minuten aufgerufen. Wenn ein neues Mail vorhanden ist wird das Passwort geprüft und der Artikel erstellt, sofern das Passwort natürlich korrekt ist.
Die Verbindung zur Datenbank und das schreiben der Daten in die DB habe ich hier ausgelassen, da dies ja je nach Blogsoftware bzw. Tabellenstruktur variert. Das Script gibt einfach Variabeln mit dem Inhalt zurück und diese können dann nach belieben in die DB geschrieben werden.

Voraussetzungen

Damit das Script richtig funktioniert, bzw die Mails auch lesen und bearbeiten kann müssen die Imap-Funktionen von PHP Installiert sein. Sonst wird es zu kompliziert.

Ablauf

Damit man einen groben Überblick über das Script bekommt fasse ich hier den Ablauf des Scripts etwas zusammen.
  1. Überprüfen ob neue Mails vorhanden sind
  2. Passwort des Mails überprüfen
  3. Headerdaten auslesen (Betreff, Datum & Zeit)
  4. Text auslesen
  5. Attachments auslesen
  6. Text aufteilen (Thema, Ort und Text)
  7. Bilder speichern
  8. (Daten in die Datenbank schreiben)
  9. E-Mail löschen
  10. Verbindungen trennen

Code

Zuerst werden einige Konstanten definiert, die Hostname vom Mailserver, Benutzername, Passwort usw. beinhalten.
<?
// Konfiguration
define("MAIL_HOST","{mail.server.ch:143}");                // Hostname und Port vom Mailserver
define("MAIL_USER","moblog@adresse.ch");                   // Der Benutzername
define("MAIL_PASS","passwort");                            // Passwort vom Mailaccount

define("PASS","passwort");                                 // Das Passwort das im Mail stehen soll 
define("IMAGES_PATH","images/");                           // Pfad zum Bilderordner
?>



Damit die allfälligen Fehler oder Mitteilungen vom Script auch sinnvoll Protokolliert werden, habe ich eine kleine Funktion geschrieben, welche das aktuelle Datum sowie eine Fehlermeldung zurückgibt. Wenn das Script mit einem Cronjob aufgerufen wird, wird diese nachricht automatisch ins Logfile geschrieben.
<?
// kleine funktion die eine fehlermeldung mit dem aktuellen datum ausgibt
function _log($text)
{
    echo 
date("r")."    ".$text."\n";
}
?>


Nun kann auch schon die Verbindung zum Mailserver hergestellt werden. Die angaben zum Hostnamen usw, werden aus den oben definierten Konstanten gelesen. Wenn die Verbindung zum Mailserver nicht hergestellt werden kann wird eine Fehlermeldung ausgegeben und das Script abgebrochen.
<?
// verbindung zum mailserver herstellen
if(!$mail=imap_open(MAIL_HOST,MAIL_USER,MAIL_PASS))
{
    
_log("Keine Verbindung zum Mail-Server");
    exit;
}
?>


Wenn die Verbindung zum Server steht kann überprüft werden, ob neue, ungelesen Mails auf der Server sind. Ist dies nicht der Fall wird das Script abgebrochen.
<?
// ueberpruefen, ob mails vorhanden sind
$headerstrings=imap_headers($mail);
$mails=count($headerstrings);                              // Die Anzahl der neuen Mails

if($mails==0)
{
    echo 
"keine mails";
    exit;
}
_log($mails." neue E-Mails auf dem Server.");              // Nachricht ausgeben
?>


Ist bekannt ob und wieviele neue Mails vorhanden sind, gehts ans verarbeiten der Mails. Mit einer Schleife wird jedes Mail durchgegangen und überprüft.
Aus Übersichtlichkeitsgründen habe ich hier den Code in zwei Funktionen ausgelagert. Zum einen gibt es die Funktion _getData(). Diese Funktion list die Daten aus dem Mail aus und gibt sie schön sortiert in einem Array zurück. Dieser kann so aussehen:
Array
(
    [header] => Array
        (
            [date] => datum als timestamp
            [email] => mailadresse des absenders
            [name] => name des absenders
            [subject] => betreff
        )
    [text] => Inhalt des Mails (mit Passwort, Thema, Ort und Text)
    [attachment] => Array
        (
            [0] => Array
                (
                    [filename] => Dateiname des ersten Anhangs
                    [content] => Inhalt des ersten Anhangs
                )
            [1] => Array
                (
                    [filename] => Dateiname des zweiten Anhangs
                    [content] => Inhalt des zweiten Anhangs
                )
        )
)


Die zweite Funktion heisst _getMetaData() und sie unterteilt den Inhalt der Nachricht, überprüft das Passwort und gibt die verschiedenen Angaben (Thema, Ort und Text) geordnet zurück.

Nun aber erstmal der Code, der die Funktionen aufruft:
<?
// mail fuer mail durchgehen, daten auslesen, passwort check machen, daten in die db schreiben und das mail loeschen
foreach($headerstrings as $headerstring)
{
    
preg_match("/[0-9]/",$headerstring,$number);           // Numer des E-Mails auslesen

    
$mailData=_getData($headerstring,$number);             // Inhalt aus dem Mail auslesen

    
$meta=_getMetaData($mailData['text']);                 // Inhalt aufteilen

    
if($meta===FALSE)                                      // wenn $meta FALSE ist, stimmt das Passwort nicht
    
{
        
log("falsches pw");
        continue;
    }

    
$mailData['text']=$meta[0];                            // Text des E-Mails
    
$mailData['meta']=$meta[1];                            // Meta Daten des E-Mails (Thema und Ort)

    
foreach($mailData['attachment'] AS $attachment)        // alle attachments durchgehen
    
{
        
$filename=$attachment['filename'];                 // dateiname auslesen

        
$fp=fOpen(IMAGES_PATH.$filename,"w+");             // das attachment abspeichern
        
fWrite($fp,$attachment['content']);
        
fClose($fp);
    }

    
imap_delete($mail,$number[0]);                         // Mail l&ouml;schen
}
?>


Nun zu den Funktionen. Als erstes _getData(), die den Inhalt der Mails ausliest und zurückgibt. Hierzu muss ich vieleicht noch sagen, dass jedes Gerät die Mails etwas anders gestaltet und es so zu Problemen bei dieser Funktion kommen könnte. Mit meinem Handy funktioniert es wunderbar, aber bei anderen Geräten muss man vieleicht die eine oder andere Anpassung vornehmen.
<?
// liest die daten aus einem mail aus, verarbeitet sie und gibt sie als array zur&uuml;ck
function _getData($headerstring,$number)
{
    global 
$mail;
    
$mailData=array();                                     // Array, in den schlussendlich die
                                                           // Daten geschrieben werden

    
$header=imap_fetchheader($mail,$number[0]);            // mailheader auslesen

    
preg_match("/Date: (.*)?[\+|-]/",$header,$date);       // datum des mails auselesen
    
$date=htmlEntities($date[1]);
    
$mailData['header']['date']=strToTime($date);

    
$headerinfo=imap_headerinfo($mail,$number[0],256,256); // liest die headerinformationen aus

    
$mailData['header']['email']=$headerinfo->from[0]->mailbox."@".$headerinfo->from[0]->host// mailadresse des absenders auslesen

    
$decode=imap_mime_header_decode($headerinfo->from[0]->personal); // name des absenders auslesen
    
$mailData['header']['name']=$decode[0]->text;

    
$decode=imap_mime_header_decode($headerinfo->fetchsubject); // betreff auslesen
    
$mailData['header']['subject']=$decode[0]->text;


    
// inhalt der mailnachricht auslesen
    
$struct=imap_fetchstructure($mail,$number[0]);
    
    
// mails mit attachments werden immer als multipart, also mail mit verschieden teile versendet
    // sollte eine mail ohne attachment und mit nur als plaintext versendet worden sein, wird nur der text ausgelesen 
    
if(!empty($struct->parts))
    {
        
// durch alle teile gehen
        
for($i=0;$i<count($struct->parts);$i++)
        {
            
$part=$struct->parts[$i];

            
$msg=imap_fetchbody($mail,$number[0],$i+1);    // inhalt des teils

            // wenn der teil ein attachment, bild und jpg ist.
            
if ($part->disposition==ATTACHMENT OR $part->type==TYPEIMAGE OR ($part->type==TYPEAPPLICATION AND $part->subtype!="SMIL"))
            {
                if(
$part->subtype=="JPEG" OR $part->subtype=="JPG" OR $part->subtype=="OCTET-STREAM")
                {
                    
$attachment=array();

                    
// daten in einen array schreiben.
                    
$attachment['filename']=$part->dparameters[0];
                    
$attachment['filename']=$attachment['filename']->value;
                    
$attachment['content']=base64_decode($msg);

                    
$mailData['attachment'][]=$attachment;
                    unSet(
$attachment);
                }
            }

            
// wenn der teil nur text ist
            
elseif(($part->type==TYPETEXT OR $part->type==TYPEMULTIPART OR $part->disposition==INLINE))
            {
                
$mailData['text']=base64_decode($msg);
            }
        }
    }
    else
    {
        
// wenn das mail als plaintext versendet wurde
        
$mailData['text']=imap_body($mail,$number[0]);
    }

    return 
$mailData;                                      // daten zur&uuml;ckgeben
}
?>


Und nun noch zur zweiten Funktion, welche den Text der Nachricht verarbeitet und das Passwort überprüft.
<?
function _getMetaData($text)
{
    
$text=explode("\n",$text);                             // den text aufteilen

    
if(trim($text[0])!=PASS)                               // ueberprufen, ob die erste zeile dem passwort entspricht
    
{
        return 
FALSE;
    }
    unSet(
$text[0]);

    
$metaData['topic']=trim($text[1]);                     // die zweite zeile als thema (topic) abspeichern
    
unSet($text[1]);

    
$metaData['location']=trim($text[2]);                  // die dritte zeile als ort (location) abspeichern
    
unSet($text[2]);

    
$text=implode("\n",$text);                             // den rest zusammenfuegen und als text speichern

    
return array($text,$metaData);                         // den array zurueckgeben
}
?>


Zum Schluss müssen die gelöschten Mails mit imap_expunge() noch definitiv gelöscht und die Verbindung zum Server getrennt werden.
<? 
// mailbox leeren und verbindung zum server trennen
imap_expunge($mail);
imap_close($mail);
?>


Das ist auch schon der ganze Zauber. Wie schon gesagt müssen die Daten noch in die DB geschrieben werden und idealerweise generiert man für die Bilder auch noch ein Thumbnail, aber das sollte ja kein grosses Problem darstellen.
Wer sich das Script nicht aus den einzelnen Teilen zusammensetzen will, kann es sich hier herunterladen.

Trackbackspam mit PHP verhindern

Im September des letzten Jahres habe ich bereits einmal über eine Möglichkeit zum erkennen von Trackbackspam geschrieben. Diese Funktion habe ich nun noch etwas verfeinert und einen zusätzlichen Check eingebaut.

Damit ein Trackback nicht als Spam identifiziert wird, müssen zwei Bedingungen erfüllt sein.

Zum einen muss die Seite die den Trackback sendet, einen Link auf meine Seite haben. Klingt logisch, denn warum sonst sollte man einen Trackback versenden. Das kommt immer mit einem Link zusammen. Nur durch dieses Kriterium filtert man schon einen Grossteil von dem Spamnachrichten, denn die Webseiten der Spamer haben selten einen Backlink auf die eigene Seite.

Die andere Bedingung ist, dass die IP Adresse der Webseite, die angegeben wird, dieselbe sein muss wie die des Servers der den Trackback sendet. Erhalte ich also einen Trackback mit der URL test.ch muss der Trackback auch von dem Server gesendet werden, auf dem test.ch liegt. Mal abgesehen von NAT.

Um zu überprüfen, ob die Webseite einen Link auf die eigene Seite gesetzt hat, muss man den Inhalt der anderen Webseite auslesen und diesen nach einem Link absuchen. In dieser Funktion suche ich nur nach dem String t-error.ch und das erkennt auch die meisten Spamnachrichten.
Der Vergleich der IP-Adresse ist auch relativ simpel. Mit der Funktion getHostbyName() löst man die IP Adresse der URL auf und die IP Adresse des Servers der den Trackback sendet, kann man man mit $_SERVER['REMOTE_ADDR'] auslesen.

Und so sieht die Funktion dann aus.
<?
function trackbackSpamCheck($url)
{
    
// trackback prinzipiell als spam definieren
    
$spam=TRUE;
    
    
// URL in einzelteile zerlegen
    
$url=parse_url(trim(addSlashes($url)));
    
    
// verbindung zum host auf port 80 herstellen
    
$fp=fSockOpen($url['host'],80,$errno,$errstr,30);
    
    
// ueberpruefen, ob die verbindung steht
    
if($fp)
    {
        
// pfad zur zieldatei auslesen
        
$path=$url['path'];
        if(isSet(
$url['query']))
        {
            
$path.="?".$url['query'];
        }

        
// wenn der pfad leer ist '/' verwenden
        
if($path=="")
        {
            
$path="/";
        }

        
// get request an den server senden
        
$req ="GET ".$path." HTTP/1.0\r\n";
        
$req.="Host: ".$url['host']."\r\n\r\n";
        
fPuts($fp,$req);

        
// http headers auslesen
        
while(!feof($fp))
        {
            
$data=fgets($fp,1024);
            if(
trim($data)=="")
            {
                break;
            }
        }

        
// daten auslesen
        
while(!feof($fp))
        {
            
$data=fgets($fp,1024);
            
            
// ueberpruefen, ob t-error.ch darin vorkommt
            // dies kann man noch verfeinern,
            // in dem man nach einem link sucht
            
if(eregi("t-error.ch",$data))
            {
                
$spam=FALSE;
                break;
            }
        }
        
        
// verbindung zum server trennne
        
fclose($fp);
    }

    
// ip adresse des hosts auslesen,
    // auf dem die im trackback angegebene
    // webseite liegt
    
$ip=@getHostByName($url['host']);

    
// ueberpruefen, ob die ip adresse
    // aufgeloest werden konnte.
    // trackback sonst als spam definieren
    
if($ip==$url['host'])
    {
        
$spam=TRUE;
    }
    else
    {
        
// ip adresse der webseite
        // mit der ip des hosts,
        // welcher den trackback gesendet
        // hat vergleichen
        
if($_SERVER['REMOTE_ADDR']!=$ip)
        {
            
$spam=TRUE;
        }
    }
    
    
// spam status zurueckgeben    
    
return $spam;
}
?>

Gravatare mit PHP cachen

In meinem Blog verwende ich schon lange den Dienst Gravatar. Dadurch wird zu den Kommentaren ein Bild (Avatar) vom Kommentator angezeigt. Vorausgesetzt, eine Mailadresse wird angegeben und ein Avatar ist bei Gravatar auf diese Adresse registriert. Beim Aufruf meiner Seite wir dieser Avatar von der Gravatarseite geholt und angezeigt.

Für den Benutzer hat das den Vorteil, dass er sich nur an einem Ort registrieren muss und dann auf allen Seiten die Gravatar unterstützen einen Avatar hat. Die Mailadresse bleibt dabei immer geschützt, da sie vor der Übertragung in einen MD5 String umgewandelt wird.

Auch für den Webseitenbetreiber hat Gravatar einige Vorteile. So muss man kein eigenes Avatarsystem zusammenbauen und kann einfach Gravatar nutzen. Das ganze ist sehr einfach zu realisieren. Für Wordpress gibt es ja auch Plugins.

Der Nachteil ist allerdings, dass Gravatar halt auf einer anderen Seite läuft. Sollte diese Seite mal Trafficprobleme haben, oder gar ganz down sein, so wir die eigene Seite auch langsam, da die Bilder nicht geladen werden können. Dieses Problem kann man umgehen, indem man die Bilder in einem Cache abspeichert. Ich habe ein PHP Script geschrieben, welches diese Bilder in einen Cache speichert.

Der Ablauf sieht so aus. Zuerst überprüft das Script, ob sich das Bild schon im Cache befindet. Zur Identifizierung wird die Mailadresse als MD5 String verwendet. Ist das Bild schon im Cache und nicht älter als zwei Tage wird das Bild ausgegeben.
Anders sieht es aus, wenn das Bild noch nicht im Cache, oder schon älter als zwei Tage ist. Dann wir eine Verbindung zum Gravatarserver hergestellt, das Bild heruntergeladen und im Cache gespeichert. Dann wird es auch ausgegeben.

Mir ist klar, dass es (noch) keine perfekte Lösung ist, aber es funktioniert. Natürlich kann man so auch andere Bilder (nicht von Gravatar) cachen. Man muss halt einige Angaben anpassen.

Nun müssen sich nur noch mehr Leute bei Gravatar anmelden.

Hier der Code des Scripts:
<?

// Konfiguration
define("CACHE_FOLDER","cache/");                // cache ordner
define("CACHE_DURATION",48);                    // wie lange die bilder im cache bleiben sollen (tage)
define("GRAVATAR_OPTIONS","size=64&amp;");      // optionen (bild groesse, rating usw)

// ueberprufen ob die mailadresse (als md5 string) gesetzt ist
if(!isSet($_GET['address']))
{
    die(
"no address is set");
}

// cache dauer in sekunden umrechnen
$duration_secs=CACHE_DURATION*60*60;

// adresse ueberpruefen
$address=$_GET['address'];
if(!
preg_match("/[a-z0-9]*/i",$address))
{
    die(
"wrong address");
}

define("GRAVATAR_HOST","www.gravatar.com");
define("GRAVATAR_PATH","/avatar.php?".GRAVATAR_OPTIONS."gravatar_id=".$address);
$filename=CACHE_FOLDER.$address.".jpg";

// ueberprufen, ob die datei heruntergeladen werden muss
$download=TRUE;
if(
file_exists($filename))
{
    if(
fileMTime($filename)>(time()-$duration_secs))
    {
        
$download=FALSE;
    }
}

// wenn ja
if($download)
{
    
// zum host verbinden
    
$source=@fSockOpen(GRAVATAR_HOST,80,$errno,$errstr,30);

    
// wen verbindung hergestellt
    
if($source!=FALSE)
    {
        
// zu sendende headers zusammenstellen
        
$req ="GET ".GRAVATAR_PATH." HTTP/1.0\r\n";
        
$req.="Host: ".GRAVATAR_HOST."\r\n";
        
$req.="Connection: Close\r\n\r\n";

        
// headers senden
        
fWrite($source,$req);
        
        
// headers der antwort auslesen
        
while(!fEof($source))
        {
            
$header=trim(fGets($source,1024));
            if(
$header=="")
            {
                break;
            }
        }

        
// zieldatei oeffnen
        
$dest=fOpen($filename,"w+");

        
// daten auslesen und direkt in die zieldatei schreiben
        
while(!fEof($source))
        {
            
$data=fGets($source,1024);
            
fWrite($dest,$data);
        }

        
// verbindung trennen
        
fClose($source);
        
fClose($dest);
    }
    else
    {
        
// wenn gravatar down und kein bild im cache
        // standardbild verwenden
        
$filename="gravatar.jpg";
    }
}

// Bild ausgeben.
header("Content-type: image/jpeg");
echo 
file_get_contents($filename);
?>

Überlange Codezeilen kürzen

Und wieder mal etwas von der HTML/CSS Front. Und zwar habe ich beim erstellen des neuen Designs mit der festen Breite bemerkt, dass gewisse Texte (hauptsächlich Code) zu lang für das Design ist. Sprich, der Code ragt zu weit nach rechts.
Hier ein Beispiel von diesem Artikel.
http://images.t-error.ch/blog/487/div_overflow_standard.jpg


Eine Möglichkeit um das Problem zu lösen wäre das Umbrechen des Textes nach einer bestimmen Anzahl von Buchstaben. Bei einer Schriftart mit einer festen Breite wie Courier würde das auch recht gut funktionieren. Nur macht das den Code völlig unleserlich, wenn da einfach irgendwo umgebrochen wird.
Es muss also eine andere Lösung her. Das CSS Element Overflow bietet sich da an. Mit der Angabe scroll kann man definieren, dass es um das Div Element Scrollbalken hat. Dadurch ist das Element nicht breiter als es sollte. Nur haben so auch Div Element die nicht zu breit sind Scrollbalken und das sieht wiederum schlecht aus.
Mit overflow:auto; kann man dem Browser die Wahl überlassen. Und hier scheinen sich die meisten Browser mal mehr oder weniger einig zu sein. Div Elemente die nicht zu breit sind habe keine Scrollbalken, diejenigen die zu breit sind haben aber Scrollbalken:
http://images.t-error.ch/blog/487/div_overflow_auto.jpg


Dies funktioniert im Firefox, Opera und im IE 6 & 7. Andere Browser habe ich nicht getestet. Der einzige Unterschied ist, dass der IE auch vertikale Scrollbalken anzeigt, obwohl diese nicht nötigt wären. Firefox und Opera verzichten darauf, wie es sich gehört.

Bilder mit PHP unter Linux verkleinern

Hier im Blog poste ich ja sehr oft Bilder. Meistens sind es selbst gemacht. Nur kann ich diese Bilder nicht einfach von der Kamera herunterkopieren und dann auf den Server laden, nein, ich muss sie auch noch etwas bearbeiten. Meistens beschränkt sich dieses Bearbeiten nur auf das verkleinern. Normalerweise gibt es für jedes Bild zwei Bilder. Ein grösseres mit einer Höhe von 600 Pixel und ein Thumbnail mit einer Höhe von 150 Pixel. Nun habe ich diese Bilder immer im Gimp verkleinert, aber das ist mir jetzt zu dumm geworden. Jedes Bild öffnen, verkleinern, speichern, für das Thumbnail nochmals verkleinern und wieder speichern. sehr aufwändig.
Um das ganze nun etwas zu vereinfachen, habe ich mir ein Script geschreiben, welches alle Bilder in einem Ordner verkleinert. Es gibt wieder zwei Bilder mit den schon genannten Grössen. Da ich nur in PHP weiss wie man so etwas macht, hab ich ein kleines PHP Script geschrieben. Der Quelltext ist weiter unten zu finden. Für das Script wird die GD Library benötigt, welche aber standardmässig schon installiert ist. Zudem habe ich noch die CLI (Command Line Interpreter) Version von PHP installiert, so kann ich das Script wie ein normales Shellscript starten. Bei fertigen Script musste ich nur noch die Dateiendung entfernen und die Datei ausführbar machen (chmod 755). Jetzt kann ich die Datei mit einem Doppelklick ausführen und so werden meine Bilder ohne grossen Aufwand verkleinert. Ein hoch auf die Technik ;)

Nun aber noch der PHP-Code:
#!/usr/bin/php -q
<?
// Ordner
define("FOLDER","./");

// Funktion zum verkleinern der Bilder
function thumb($orig,$thumb,$height)
{
    
// Vorherige Groesse
    
$size=getImageSize(FOLDER.$orig);
    
$orig_width=$size[0];
    
$orig_height=$size[1];

    
// Bild laden
    
$src=imageCreateFromJpeg(FOLDER.$orig);

    
// Neue Groesse bestimmen
    
if($orig_height>$height)
    {
        
$factor=$orig_height/$height;

        
$new_height=round($orig_height/$factor,2);
        
$new_width=round($orig_width/$factor,2);
    }

    
// Zielbild erstellen
    
$dst=imageCreateTrueColor($new_width,$new_height);

    
// Bild kopieren
    
$buffer=imageCopyResized($dst,$src,0,0,0,0,$new_width,$new_height,$orig_width,$orig_height);

    
// Bild speichern
    // Qualitaet ist 85%
    
imageJpeg($dst,FOLDER.$thumb,85);
}

// Order oeffnen
$dir=openDir(FOLDER);

// Ordner durchgehen
while($object=readDir($dir))
{
    
// Wenn Datei im jpg endet
    
if(subStr($object,-3,3)=="jpg")
    {
        
$orig=$object;

        
// Name fuer Thumb bestimmen
        // Aus test.jpg wird test_thumb.jpg
        
$split=explode(".",$object);
        unSet(
$split[count($split)-1]);
        
$thumb=implode(".",$split)."_thumb.jpg";

        
// Bilder verkleinern
        
thumb($orig,$thumb,150);
        
thumb($orig,$orig,600);
    }
}
?>

Bilder mit JavaScript preloaden

Ich versuche momentan eine kleine und simple Galerie zu Programmieren. Ein PHP Script soll die Bilder aus eine Verzeichnis auslesen und dann mit Lightbox anzeigen. Der Nachteil von Lightbox ist aber, dass es erst funktioniert, wenn die Seite komplett geladen ist. Und wenn es da mehrere Bilder gibt, kann das Laden der Seite auch mal länger dauern und die wenigsten Besucher wollen da warten.
Ich versuche diese Problematik nun mit einem Preloader zu umgehen. Bisher ohne grossen Erfolg. Aber immerhin weiss ich jetzt, wie man Bilder mit JavaScript "preloadet."
<script language="JavaScript" type="text/javascript">
    <!--
        if (document.images)
        {
            preload_obj=new Image();
            preload_obj.src="images/test.jpg";
        }
    //-->
</script>

Recht simpel. Es wird ein neues Objekt erstellt und danach mit src die Quelle der Datei zugewiesen. Natürlich kann man so mit PHP auch mehrere Bilder preloaden.

JavaScript Effekte mit Scriptaculous

Ich bin gerade etwas mit den Scriptaculous Libraries am Pröbeln. Eine sehr nette, und einfache Sache. Einige Teile sind sicher etwas Eyecandy, aber das kommt je länger desto mehr. Auch im Webbereich.
Mit Scriptaculous kann man "Aufklappeffekte" wie ich sie bei meiner Tourenverwaltung habe, ganz einfach und noch etwas schöner realisieren. Dazu benötigt man nur die neusten Scriptaculous (blödes Wort) Libraries. Dieso kopiert man zusammen mit der Datei prototype.js in einen Ordner. Vorzugsweise javascript/. Wie man erahnen kann, basiert Scriptaculous auf Prototype, allerdings muss Prototype nicht zusätzlich heruntergeladen werden.
Nun müssen die Libraries noch in die Seite eingebunden werden. Dies funktioniert wie bei jedem JavaScript. Bei Scriptaculous kann noch angegeben werden, welche Module geladen werden sollen. Dies macht man mit dem load Parameter. Wenn dieser Parameter nicht angegeben wird, werden alle Module geladen. Ich benötige aber nur das Effects Modul also lade ich auch nur dieses.
<script type="text/javascript" language="JavaScript" src="js/prototype.js"></script>
<script type="text/javascript" language="JavaScript" src="js/scriptaculous.js?load=effects"></script>
Auf der Scriptaculous Homepage hat es schon diverse Beispiele zu den verschiedenen Effekten. Mich interessierte der Effekt toggle mehr, da es damit möglich ist, ein Element auf und zu zu klappen. Mit einem Klick auf ein Element kann so ein anderes Element geöffnet oder geschlossen werden. Hier ein simples Codebeispiel.
<div onClick="new Effect.toggle($('element'),'blind')">click</div>
<div id="element"><div>test test test</div></div>
Damit kann man mit einem Click das Element element öffnen und wieder schliessen. Hier wird der Effekt blind verwendet. Wichtig ist, dass im zu öffnenden Element noch ein zusätzlicher Divcontaoiner ist, sonst funktionieren einige Effekte nicht korrekt. Das Beispiel sieht mit etwas zusätzlichem CSS dan so aus:
blind
test test test
test test test
test test test
test test test
test test test
test test test
test test test
test test test
test test test
test test test
test test test
test test test
test test test
test test test

Oft möchte man es ja so haben, dass das Element von Anfang an geschlossen ist. Dazu kann man ganz einfach das CSS Element display mit dem Wert none benutzen. Das sieht dann so aus.
blind

Zusätzlich gibt es noch die Effekte slide und appear welche mich persönlich nicht so ansprechen. Mit gefällt blind am besten. Zum Schluss aber noch je ein Beipsiel.
slide

appear

Achtung: Lightbox2 verwendet schon Scriptaculous und Prototype. So muss man diese Libraries nicht mehr laden.

Suchbegriff von Google mit PHP Auslesen

Ich versuche gerade ein Script zu machen, welches bei Besuchern die von der Google Suche zu mir kommen, die Resultate highlighten soll. Wenn nun jemand nach Garmin Edge 305 sucht, kommt er relativ schnell auf meine Seite. Ich möchte nun, dass dann auf dieser Seite der Text "Garmin Edge 305" hervorgehoben wird.
Dazu muss ich aber zuerst herausfinden, nach was der User gesucht hat. Mit der Umgebungsvariable $_SERVER ist das kein grosses Problem.
Momentan habe ich den folgenden Code. Und der funktioniert auch ganz gut, ausser bei Umlauten hat er noch seine Probleme. Aber das kriege ich hoffentlich auch noch in den Griff.
<?
$url
=parse_url($_SERVER['HTTP_REFERER']);

if(isSet(
$url['query']))
{
    
preg_match("/q=(.*?)(&amp;|^)/",$url['query'],$output);
    echo 
urlDecode($output[1]);
}
?>

Wenn ich das ganze dann mit Highlighting in die Homepage eingebaut habe, gibts wieder einen Bericht. Dann aber hoffentlich mit einer Funktion, die auch mit Umlauten funktioniert.

Youtube/Google Videos XHTML valid einbinden

Ein Video von YouTube oder Google Video in die eigene Homepage zu integrieren ist eigentlich gar nicht schwer. Video suchen und dann den HTML kopieren und in der eigenen Seite einfügen.
So erhält man den folgenden Code:
<object width="425" height="350">
<param name="movie" value="http://www.youtube.com/v/ujlMyP17XnM"></param>
<param name="wmode" value="transparent"></param>
<embed src="http://www.youtube.com/v/ujlMyP17XnM" type="application/x-shockwave-flash" wmode="transparent" width="425" height="350"></embed>
</object>

Das Problem darin ist, dass dieser Code nicht XHTML Valid ist. Das Element <embed> gibt es in XHTML gar nicht.

Wenn man aber auf validen Code achtet, kann man den Video auch so einbinden:
<object type="application/x-shockwave-flash" style="width: 425px; height: 350px" data="http://www.youtube.com/v/ujlMyP17XnM">
<param name="movie" value="http://www.youtube.com/v/ujlMyP17XnM" />
</object>

Kommt auf das selbe heraus, ausser, dass der Code valid ist.

Bei Google Video funktioniert es genau gleich, nur die URL muss man halt anpassen. Bei beiden Stellen.