Bitte beim Beispiel unten die Abhängigkeiten zu meinen anderen Ressourcen berücksichtigen. Der SimpleLogger und der Errorhandler kommen zum Einsatz.
Zu erst die Klasse Fetch.php
Und eine Beispiel-Anwendung:
Zu erst die Klasse Fetch.php
PHP:
<?php
class CheckRemoteException extends Exception{};
class FetchException extends Exception{};
class FileNotExistsException extends Exception{};
class WriteLocalFileException extends Exception{};
class ReadLocalFileException extends Exception{};
class InvalidFileDataException extends Exception{};
// Needed by DateTime class
date_default_timezone_set ( 'Europe/Berlin' );
/**
* Fetch data from remote, store it locally and shrink the image data
*
* @author saftmeister
*/
class Fetch
{
/**
* The url where the data exists to retrieve
*
* @var string
*/
private $url;
/**
* The percentage to shrink the image down to
*
* @var number
*/
private $shrinkTo;
/**
* The locale file name
*
* @var Optional, if not given, the file name from
* remote will be taken
*/
private $imageFileName;
/**
* Flag which indicates to retrieve the remote data
*
* @var boolean
*/
private $needToFetch;
/**
* Flag which indicates to shrink the local image
*
* @var boolean
*/
private $needToShrink;
/**
* Number of seconds, the local image can stay unmodified
*
* @var number
*/
private $localMaxAge;
/**
* Path where to store the archive images
*
* @var string
*/
private $archivePath;
/**
* Create a new Fetch instance
*
* @param string $url
* @param number|array $shrinkTo
* Either percentage as number or fixed dimensions as array('w' => number,'h' => number)
* @param string $imageFileName
* @param number $maxAge
* The number of seconds when the local image expires
* @param string $archivePath
* The path where to store archive images
*/
public function __construct($url, $shrinkTo, $imageFileName = null, $maxAge = 0, $archivePath = null)
{
if (! function_exists ( 'curl_init' ))
{
throw new FetchException ( "Die curl-Erweiterung ist nicht geladen. Bitte php.ini entsprechend konfigurieren." );
}
if (! function_exists ( 'imagecreatefromjpeg' ))
{
throw new FetchException ( "Die GD2-Erweiterung nicht nicht geladen. Bitte php.ini entsprechend konfigurieren." );
}
clearstatcache ();
$this->needToFetch = true;
$this->needToShrink = true;
$this->url = $url;
$this->shrinkTo = $shrinkTo;
$this->imageFileName = $imageFileName;
$this->localMaxAge = $maxAge;
$this->archivePath = $archivePath;
if ($this->imageFileName == null)
{
$parts = parse_url ( $this->url );
if ($parts)
{
$this->imageFileName = basename ( $parts ['path'] );
}
}
if (file_exists ( $this->imageFileName ))
{
$this->needToShrink = false;
}
}
/**
* Check whether the remote file is newer than the local one
*
* @throws CheckRemoteException
* @return boolean
*/
public function checkIsNew()
{
if (! file_exists ( $this->imageFileName ))
{
return $this->needToFetch = true;
}
$current = new DateTime ();
$localDate = new DateTime ();
$localDate->setTimestamp ( filemtime ( $this->imageFileName ) ); // mtime
if ($this->localMaxAge > 0)
{
$expires = clone $localDate;
$expires->setTimestamp ( $expires->getTimestamp () + $this->localMaxAge );
if ($expires->getTimestamp () < $current->getTimestamp ())
{
return $this->needToFetch = true;
}
return $this->needToFetch = false;
}
else
{
$response = get_headers ( $this->url, 1 );
if (! $response)
{
throw new CheckRemoteException ( 'Lesen der Eigenschaften fehlgeschlagen' );
}
if ($response [0] != 'HTTP/1.1 200 OK')
{
throw new CheckRemoteException ( sprintf ( 'Server gab ungültige Antwort "%s" zurück', $response [0] ) );
}
if (isset ( $response ['Last-Modified'] ))
{
$remoteDate = new DateTime ( $response ['Last-Modified'] );
if ($localDate->getTimestamp () < $remoteDate->getTimestamp ())
{
return $this->needToFetch = true;
}
}
else if (isset ( $response ['Expires'] ))
{
$remoteDate = new DateTime ( $response ['Expires'] );
if ($localDate->getTimestamp () > $remoteDate->getTimestamp ())
{
return $this->needToFetch = true;
}
}
}
return $this->needToFetch = false;
}
/**
* Performs the file archivation
*
* @throws WriteLocalFileException
*/
private function archive()
{
if ($this->archivePath != null)
{
if (is_dir ( $this->archivePath ))
{
$mtime = filemtime ( $this->imageFileName );
$pinfo = pathinfo ( $this->imageFileName );
$mDateTime = new DateTime ();
$mDateTime->setTimestamp ( $mtime );
$newName = sprintf ( "%s/%s-%s.%s", $this->archivePath, $pinfo ['filename'], $mDateTime->format ( "YmdHis" ), $pinfo ['extension'] );
if (! copy ( $this->imageFileName, $newName ))
{
throw new WriteLocalFileException ( "Konnte lokale Datei nicht archivieren" );
}
}
else
{
throw new WriteLocalFileException ( "Konnte lokale Datei nicht archivieren, Archiv-Pfad ist kein Verzeichnis" );
}
}
}
/**
* Retrieve the remote file and store it locally
*
* @throws FetchException
* @throws WriteLocalFileException
*/
public function retrieve()
{
if (! $this->needToFetch)
{
return;
}
if (file_exists ( $this->imageFileName ))
{
$this->archive ();
}
$ch = curl_init ( $this->url );
if (! curl_errno ( $ch ))
{
curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt ( $ch, CURLOPT_HEADER, false );
$imageData = curl_exec ( $ch );
if (curl_errno ( $ch ))
{
$error = curl_error ( $ch );
curl_close ( $ch );
throw new FetchException ( "Konnte Daten nicht empfangen: " . $error );
}
curl_close ( $ch );
if (substr ( $imageData, 0, 3 ) != "\xFF\xD8\xFF")
{
throw new InvalidFileDataException ( "Die abgeholten Daten sind keine JPEG-Bild-Daten" );
}
$imageDataLen = strlen ( $imageData );
$fd = fopen ( $this->imageFileName, "wb" );
if ($fd)
{
if (fwrite ( $fd, $imageData, $imageDataLen ) != $imageDataLen)
{
fclose ( $fd );
throw new WriteLocalFileException ( "Konnte Daten nicht in lokale Datei schreiben" );
}
fflush ( $fd );
fclose ( $fd );
}
else
{
throw new WriteLocalFileException ( "Konnte lokale Datei nicht öffnen" );
}
}
else
{
$error = curl_error ( $ch );
throw new FetchException ( "Konnte keine Verbindung initiieren: " . $error );
}
$this->needToFetch = false;
$this->needToShrink = true;
}
/**
* Shrinks the image to the particular size given by percentage
*
* @throws InvalidArgumentException
* @throws FileNotExistsException
* @throws InvalidFileDataException
* @throws WriteLocalFileException
*/
public function shrink()
{
if (! $this->needToShrink)
{
return;
}
if ($this->needToFetch)
{
throw new FetchException ( "Bitte erst entfernte Datei abholen" );
}
if (is_int ( $this->shrinkTo ))
{
if ($this->shrinkTo < 1 || $this->shrinkTo >= 100)
{
throw new InvalidArgumentException ( "Die Schrumpfgröße ist ungültig (0 < erwartet < 100)" );
}
}
else if (is_array ( $this->shrinkTo ))
{
if (! isset ( $this->shrinkTo ['w'] ) || intval ( $this->shrinkTo ['w'] ) < 1 || intval ( $this->shrinkTo ['w'] ) > 6000)
{
throw new InvalidArgumentException ( "Die Breitenabmessungsangabe zum Schrumpfen ist ungültig" );
}
if (! isset ( $this->shrinkTo ['h'] ) || intval ( $this->shrinkTo ['h'] ) < 1 || intval ( $this->shrinkTo ['h'] ) > 5000)
{
throw new InvalidArgumentException ( "Die Breitenabmessungsangabe zum Schrumpfen ist ungültig" );
}
}
if (! file_exists ( $this->imageFileName ))
{
throw new FileNotExistsException ( "Die lokale Datei existiert nicht" );
}
$gdInfo = getimagesize ( $this->imageFileName );
if (! $gdInfo)
{
throw new InvalidFileDataException ( "Konnte Dimensionen der Datei nicht lesen" );
}
$width = intval ( $gdInfo [0] );
$height = intval ( $gdInfo [1] );
if ($width < 1 || $width > 6000 || $height < 1 || $height > 5000)
{
throw new InvalidFileDataException ( "Dimensionen der Remote-Datei sind ungültig (w = " . $width . ", h = " . $height . ")!" );
}
if (is_int ( $this->shrinkTo ))
{
$newWidth = intval ( $width / 100 * $this->shrinkTo );
$newHeight = intval ( $height / 100 * $this->shrinkTo );
}
else
{
$newWidth = intval ( $this->shrinkTo ['w'] );
$newHeight = intval ( $this->shrinkTo ['h'] );
}
if ($newWidth < 1 || $newWidth > 6000 || $newHeight < 1 || $newHeight > 5000)
{
throw new InvalidFileDataException ( "Dimensionen der lokalen Datei sind ungültig (w = " . $newWidth . ", h = " . $newHeight . ")!" );
}
$imgJpg = imagecreatefromjpeg ( $this->imageFileName );
if (! $imgJpg)
{
throw new InvalidFileDataException ( "Konnte Bilddaten nicht lesen" );
}
$imgNew = imagecreatetruecolor ( $newWidth, $newHeight );
if (! $imgNew)
{
imagedestroy ( $imgJpg );
throw new InvalidFileDataException ( "Konnte Puffer für Zieldatei nicht anfordern" );
}
if (! imagecopyresized ( $imgNew, $imgJpg, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height ))
{
imagedestroy ( $imgJpg );
imagedestroy ( $imgNew );
throw new InvalidFileDataException ( "Konnte Daten nicht kopieren" );
}
if (! imagejpeg ( $imgNew, $this->imageFileName ))
{
imagedestroy ( $imgJpg );
imagedestroy ( $imgNew );
throw new WriteLocalFileException ( "Konnte verkleinerte Daten nicht schreiben" );
}
imagedestroy ( $imgJpg );
imagedestroy ( $imgNew );
$this->needToShrink = false;
}
/**
* Send image data to client
*
* @throws ReadLocalFileException
* @throws FileNotExistsException
*/
public function sendToClient()
{
if (file_exists ( $this->imageFileName ))
{
$data = file_get_contents ( $this->imageFileName );
if (! $data)
{
throw new ReadLocalFileException ( "Konnte lokale Datei nicht zum lesen öffnen" );
}
$localDate = new DateTime ( 'UTC' );
$localDate->setTimestamp ( filemtime ( $this->imageFileName ) );
header ( 'Content-Type: image/jpeg' );
header ( 'Content-Length: ' . strlen ( $data ) );
header ( 'Last-Modified: ' . $localDate->format ( 'D, d M Y H:i:s \G\M\T' ) );
header ( 'Cache-Control: public' );
header ( 'ETag: "' . md5 ( $localDate->getTimestamp () . $this->imageFileName ) . '"' );
echo $data;
}
else
{
throw new FileNotExistsException ( "Lokale Datei existiert nicht" );
}
}
/**
* Removes the local file (if it exists)
*/
public function removeLocalFile()
{
if (file_exists ( $this->imageFileName ))
{
unlink ( $this->imageFileName );
}
}
}
Und eine Beispiel-Anwendung:
PHP:
<?php
require 'ErrorHandler.php';
require 'SimpleLogger.php';
require 'Fetch.php';
try
{
// Parameter:
// 1. URL - Bild-Adresse einer Webcam
// 2. entweder Angabe in Prozent oder assoziatives Array mit w für Breite und h für Höhe, Angabe in Pixeln.
// 3. Name für die lokale (auf dem lokalen Server) vorgehaltene Bild-Datei, wenn leer wird Name der URL verwendet.
// 4. Anzahl der Sekunden, für die das Bild lokal vorgehalten werden soll. Wenn 0, wird zuerst Last-Modified und danach Expires-Header geprüft.
// 5. Speicherort (Verzeichnis) für Archivierung. Archivierte Bilder bekommen den Zeitstempel in den Dateinamen, wann sie erstellt wurden.
$fetcher = new Fetch('http://www.tegeler-segel-club.de/webcam/tsc_webcam.jpg', array('w' => 500, 'h' => 400), null, 60, '.');
// Prüfen, ob neue Daten auf dem Remote-Server vorhanden sind
$fetcher->checkIsNew();
// neue Daten ggf. abholen und abhängig von Parameter 5 archivieren oder nicht
$fetcher->retrieve();
// Bild-Daten verkleinern oder vergrößern (möglich durch Dimensionierung)
$fetcher->shrink();
// Fertiges Bild an den Browser senden
$fetcher->sendToClient();
// Ggf. lokales Bild wieder löschen - sollte nicht verwendet werden, um Bandbreite zu sparen.
//$fetcher->removeLocalFile();
}
catch(Exception $ex)
{
SimpleLogger::logException($ex);
header('HTTP/1.1 500 Internal Server Error');
}