Suchen
Inside Wiki
Nützliche Links




 
phpforum.de bei Facebook
 
phpforum.de bei Twitter
 

Zurück   PHP Forum: phpforum.de > phpforum.de Wiki > phpforum.de Wiki

PHP Wiki Dieses Wiki sammelt Lösungen, zu Problemen, welche immer wieder im Forum auftauchen.

 
 
Artikel-Optionen Ansicht
  #1  

Standard Ressourcen mit PHP ausliefern

 

Inhalte

Um was es geht



Schutz



Manchmal kommt man in die Situation als Programmierer, in der man eigentlich öffentliche Ressourcen (Bilder / Videos / Musik / ...) nur einem bestimmten Publikum oder User zugänglich machen will. Dateien sollen also geschützt werden.

Öffentliche Ressourcen deshalb, weil sie normalerweise im oder unter dem Doc-Root abgelegt werden und so über die URL direkt zugänglich sind.

Weitere Möglichkeiten



Vielleicht möchte man eine Statistik über die Anzahl Downloads führen. Das mühsam über irgendwelche Log-Files vom Server zu machen verbraucht unnötig Ressourcen und ist unzuverlässig.

Oder man will einfach noch weitere Aufgaben in PHP erledigen (Logs schreiben, Cache leeren, …) wenn eine Datei angefordert wird oder z.B. ein dynamisches Bild ausliefern.

Wie schaffen wir das?



Dynamische Bilder



Bei Bildern muss einem noch kurz was klar werden: Pro HTTP-Request kann nur ein Content-Type ausgeliefert werden. Es ist also technisch nicht möglich ein Content-Type "text/html" (Unsere HTML Seite) sowie ein Bild (Content-Type "image/jpeg") im selben Vorganz auszuliefern. Oft wird versucht über require/include das PHP Script welches das Bild generiert gleich an Ort und Stelle einzubinden. Das ist jedoch Unsinn.

Ausnahme: Inline-Images

Wollen wir also ein dynamisch generiertes Bild von PHP in eine HTML Seite einbinden, verwenden wir eine separate, von aussen zugängliche PHP Datei (z.B. generate_image.php) und eine index.html.

Das HTML für das Bild:
HTML Quellcode:
<img src="get_image.php" alt="Test-Bild" />


Eine mögliche get_image.php: (1:1 aus der PHP-Dok für imagecreate)

PHP Quellcode:
header ("Content-type: image/png");
$im = @ImageCreate (50, 100)
      or die ("Kann keinen neuen GD-Bild-Stream erzeugen");
$background_color = ImageColorAllocate ($im, 255, 255, 255);
$text_color = ImageColorAllocate ($im, 233, 14, 91);
ImageString ($im, 1, 5, 5, "Ein Test-String", $text_color);
ImagePNG ($im);


Im Browser wird zwar nun alles auf einer Seite dargestellt, trotzdem hat das src Attribut im Image-Tag dazu geführt, dass der Browser einen zweiten Request gestartet und sich so das Bild separat vom ersten geholt hat.

Das kann man sich vorstellen als ob ein zweiter Tab mit der URL zum Bild im Browser geöffnet wird. Dann wird das Bild an der gewünschte Stelle im 1. Tab reinkopiert und der zweite Tab wird wieder geschlossen.

Tipp:

Das Selbe passiert auch bei externen JavaScript Dateien, CSS-Dateien, Favicons usw.

Wer sich das mal genauer ansehen möchte kann das Addon LiveHTTP Headers für Firefox installieren oder im Chrome auf "Google Chrome anpassen" -> "Tools" -> "Entwicklertools" klicken.

Auch AJAX basiert auf diesem Prinzip. Nur wird hier nicht über HTML dem Browser mitgeteilt, dass separater Content mit einem weiteren Request geholt werden soll, sondern JavaScript gibt hier den Befehl dem Browser weiter.


Andere Dateien



Bei anderen Dateien erscheint es einem irgendwie logischer. Wird das HTML ausgeliefert kann ja nicht gleichzeitig im gleichen Request noch ein Video heruntergeladen werden. Entweder oder bzw. eins nach dem anderen ist hier also gefragt. Logisch also, dass wir mindestens 2 Dateien brauchen. Eine HTML Datei sowie eine die Daten ausliefert.

Aufgabenstellung



Nehmen wir nun also folgende Ausgangslage als gegeben:
  • Im Document-Root befinden sich 2 Ordner, einen geschützten (via .htaccess) "DOC_ROOT/application" Ordner sowie unsere öffentlich zugänglichen Dateien unter "DOC_ROOT/test/RAO".
    Leider ist mir nichts besseres als RAO eingefallen. Read Access Oject bedeutet das Kürzel und soll somit Lese-Zugängliche Objekte suggerieren.
  • Im "application" Ordner haben wir einen "library" Ordner mit unseren Klassen sowie nochmals einen "RAO" Ordner, welcher unsere Dateien die wir ausliefern wollen beherbergt.
  • Wir wollen Text-Dateien und Bilder die sich unter "DOC_ROOT/application/ROA" befinden über Scripte die unter "DOC_ROOT/test/RAO" liegen ausliefern und eventuell zusätzlich schützen.
  • Wir wollen uns die Möglichkeiten offen halten X-beliebigen Inhalt aus den unterschiedlichsten Daten-Quellen ausliefern zu können

Die Implementierung



PHP-Version



Da ich ein grosser Freund von Namespaces und Closures und generell PHP 5.3 bin, werden diese Features hier nun auch eingesetzt. Es mögen nun auch ein paar Einstellungen folgen, die nicht direkt mit dem Thema zu tun haben, für mich aber zum guten Programmier-Stil gehören. Anfänger werden es mir hoffentlich danken.

Bootstrapping



Unter "DOC_ROOT/application" legen wir eine Bootstrap.php an, die von allen aufgerufenen Dateien als erstes inkludiert wird:

PHP Quellcode:
<?php

  /**
   * Error Reporting aktivieren
   */

  error_reporting(E_ALL);
  ini_set('display_errors', true);
 
  /**
   * Pfade definieren
   */

  define('DS',    DIRECTORY_SEPARATOR);
  define('APP_PATH',  __DIR__);
  define('LIB_PATH',  APP_PATH . DS . 'library');
 
  /**
   * Date und Locale einstellen
   */

  date_default_timezone_set('Europe/Zurich');
  setlocale(LC_ALL, 'de_CH.UTF-8');

  /**
   * Autoloader registrieren
   */

  require_once LIB_PATH . DS . 'Autoloader.php';
  Autoload\Autoloader::register();


Wer genau hinsieht, bemerkt die fehlenden ?> am Ende der Datei. Dies ist aber so gewollt und beugt möglichen "Headers already sent" Fehlern vor. Mehr dazu unter Standardantwort zu Headers already sent.

Autoloading



Der Autoloader sorgt dafür, dass neue Klassen die verwendet werden aber noch nicht bekannt sind, automatisch nachgeladen werden. Somit können ab nun einfach neue Objekte instanziert werden ohne vorher die Klassen-Datei per include/require eingebunden zu haben. PHP bemerkt sofort dass die Klassendefinition fehlt und versucht nun mit den vorhandenen Autoloadern die gewünschte Klasse zu laden.

Der Loader liegt in der Datei "DOC_ROOT/application/Autoloader.php" und sieht wie folgt aus:

PHP Quellcode:
<?php

  namespace Autoload;

  class Autoloader {

    public static function register(){
      spl_autoload_register(
        function($classname){
          $dirname  = dirname(__FILE__);
          $fileName = str_replace('\\',DIRECTORY_SEPARATOR,$classname).'.php';
          $file     = $dirname.DIRECTORY_SEPARATOR.$fileName;
          if(is_readable($file))require_once $file;
        }
      );
    }

  }


Ein new PHPWiki\Test(); wirft also meinen Autoloader an. Die Closure wird dann mit dem Klassennamen "PHPWiki\Test" gefüttert. Anhand dieses wird der Pfad aufgedröselt und wenn tatsächlich eine Datei im Ordner PHPWiki mit dem Namen Test.php vorhanden/lesbar ist, wird sie eingebunden. Sollte der Loader fehlschlagen kommt der nächste Loader der mit spl_autoload_register() registriert wurde zum Einsatz und so wird diese Kette/Chain abgearbeitet bis kein Loader mehr da ist oder besser, die Klassendefinition gefunden wurde.

Tipp:

Es gibt zwar eine "magische" Funktion __autoload() seit PHP 5 die fast das Selbe macht. Jedoch verbaut man sich damit die Möglichkeit einer Chain. Wird zum Beispiel eine externe Komponente wie Doctrine eingesetzt, die ihren eigenen Loader mitbringt gibt es nur Komplikationen. Der Doctrine Autoloader kann sich nun nämlich nirgends einhängen um seine eigenen Klassen zu laden.

Deshalb: Wenn Autoloading, dann richtig! Zend/PEAR Benennungsschema befolgen (am Dateinamen kann der Pfad abgelesen werden wo die Definition steckt), spl_autoload_register() verwenden und kein __autoload() und ganz wichtig: Schmeisst keine Exceptions/Errors o.ä. in eurem Loader: Die ganze Chain wird unbrauchbar wenn PHP abbrechen muss nur weil "euer" Loader die Datei nicht gefunden hat.


ReadAccessObjectType



Die ReadAccessObjectType Klasse ist sehr übersichtlich und enthält lediglich Closure Callbacks um an den Content die Header und die MetaDaten eines ReadAccessObjects zu kommen. Ausserdem ist noch eine weitere Closure die den Access auf das Objekt regelt an Bord.

PHP Quellcode:
<?php

  namespace PHPWiki\RAO;

  class ReadAccessObjectType {
   
    /**
     * @var \Closure
     */

    protected $contentClosure;
   
    /**
     * @var \Closure
     */

    protected $headersClosure;
   
    /**
     * @var \Closure
     */

    protected $metaDataClosure;
   
    /**
     * @var \Closure
     */

    protected $hasAccess;
   
    /**
     * @param \Closure $contentClosure
     * @param \Closure $headersClosure
     * @param \Closure $metaDataClosure
     * @param \Closure $hasAccessClosure
     */

    public function __construct(
      \Closure $contentClosure,
      \Closure $headersClosure,
      \Closure $metaDataClosure = null,
      \Closure $hasAccessClosure  = null
    ){
      if(is_null($metaDataClosure))
        $metaDataClosure = function(){return array();};
      if(is_null($hasAccessClosure))
        $hasAccessClosure = function(){return true;};
      $this->contentClosure = $contentClosure;
      $this->headersClosure = $headersClosure;
      $this->metaDataClosure  = $metaDataClosure;
      $this->hasAccessClosure = $hasAccessClosure;
    }
   
    /**
     * @return \Closure
     */

    public function getContentClosure(){
      return $this->contentClosure;
    }
   
    /**
     * @return \Closure
     */

    public function getHeadersClosure(){
      return $this->headersClosure;
    }
   
    /**
     * @return \Closure
     */

    public function getMetaDataClosure(){
      return $this->metaDataClosure;
    }
   
    /**
     * @return \Closure
     */

    public function getHasAccessClosure(){
      return $this->hasAccessClosure;
    }
   
  }

Dateien ohne MetaDaten geben beim dritten Konstruktor-Parameter einfach null mit. Damit wird eine eigene Closure definiert die ein leeres Array zurück gibt. Das selbe bei der hasAccessClosure. Wird null übergeben, gibt’s ne eigene Closure die immer true zurück liefert (die Datei darf angeschaut werden).

ReadAccessObject



Das ReadAccessObject wird für jedes konkrete Objekt instanziert, das auch ausgegeben werden soll. Es beinhaltet wahlweise direkt den Content, die Headers und MetaDaten oder dann muss ein ReadAccessObjectType vorhanden sein, welches die Closures hat um an die Daten zu kommen.

PHP Quellcode:
<?php

  namespace PHPWiki\RAO;

  class ReadAccessObject {
   
    /**
     * @var mixed
     */

    protected $identifier;
   
    /**
     * @var string
     */

    protected $content = null;
   
    /**
     * @var array
     */

    protected $metaData = null;
   
    /**
     * @var array
     */

    protected $headers = null;
   
    /**
     * @var ReadAccessObjectType
     */

    protected $type;
   
    /**
     * @var int
     */

    protected $errorCode = null;
   
    const ERROR_NOT_FOUND = 1;
    const ERROR_NO_ACCESS = 2;

    /**
     * @param mixed $identifier
     * @param ReadAccessObjectType $type
     * @param string $content
     * @param array $headers
     * @param array $metaData
     */

    public function __construct(
      $identifier,
      ReadAccessObjectType $type = null,
      $content = null,
      array $headers = null,
      array $metaData = null
    ){
      $this->identifier   = $identifier;
      $this->type     = $type;
      $this->content    = $content;
      $this->headers    = $headers;
      $this->metaData   = $metaData;
    }
   
    /**
     * @return mixed
     */

    public function getIdentifier(){
      return $this->identifier;
    }
   
    /**
     * @return string
     */

    public function getContent(){
      return $this->getData('content');
    }
   
    /**
     * @return array
     */

    public function getMetaData(){
      return $this->getData('metaData');
    }
   
    /**
     * @return array
     */

    public function getHeaders(){
      return $this->getData('headers');
    }
   
    /**
     * @return mixed
     */

    public function output(){
      if(
        ($headers = $this->getHeaders())
        &&
        ($content = $this->getContent())
      ){
        foreach($headers as $key => $value)
          header($key.': '. $value);
        echo $content;
      }
      return false;      
    }
   
    /**
     * @return bool
     */

    public function hasAccess(){
      if(is_null($this->type))
        return true;
      $method = $this->type->getHasAccessClosure();
      return $method($this);
    }
   
    /**
     * @param int $errorCode
     * @return void
     */

    public function setErrorCode($errorCode){
      $this->errorCode = (int)$errorCode;
    }
   
    /**
     * @return int
     */

    public function getErrorCode(){
      return $this->errorCode;
    }
   
    /**
     * @param string $what
     * @return mixed
     */

    protected function getData($what){
      if(!$this->hasAccess()){
        $this->setErrorCode(self::ERROR_NO_ACCESS);
        return false;
      }
     
      if(!is_null($this->$what))
        return $this->$what;
     
      if(is_null($this->type))
        throw new \Exception("Need ReadAccessObjectType for $what");
     
      $methodName = 'get' . $what . 'Closure';
      $method   = $this->type->$methodName();
     
      return $this->$what = $method($this);
    }
   
  }

Die protected Methode "getData" ist relativ schlampig programmiert. Trotzdem lasse ich sie mal so stehen, da sie nicht public ist und nur direkt aus der Klasse heraus mit validen Parametern aufgerufen wird.

Das Zusammenspiel der Klassen



Simples Beispiel


Text-Dateien ausliefern geht nun sehr fix mit dem ReadAccessObject.

Wir erstellen eine Datei "get_simple_text.php" mit folgendem Inhalt:
PHP Quellcode:
<?php
 
  /**
   * Bootstrap ausführen
   */

  require_once '../../application/Bootstrap.php';
 
  /**
   * Manuell initialisierte Textdatei ohne ObjectType
   */

  use PHPWiki\RAO\ReadAccessObject;
  $text = new ReadAccessObject(
    null,
    null,
    'Simpler Text',
    array (
      'Content-Type' => 'text/plain'
    )
  );
  $text->output();
 
  if($errorCode = $text->getErrorCode()){
    header('Content-Type: text/plain');
    switch($errorCode){
      case ReadAccessObject::ERROR_NOT_FOUND:
        die('Datei nicht gefunden');
      break;
      case ReadAccessObject::ERROR_NO_ACCESS:
        die('Kein Zugriff auf diese Datei');
      break;
      default:
        die('Unbekannter Fehler');
      break;
    }
  }


Dieses Beispiel ist bewusst sehr einfach gehalten. Es wird kein ReadAccessObjectType verwendet sondern wir hardcoden die "Text-Datei" einfach in das ReadAccessObject mit dem 3. und 4. Konstruktor ein. Der 5. Parameter würde optional auch noch ein paar MetaDaten zur Datei aufnehmen können. Da wir aber einfach die Text-Datei ausgeben lassen wollen, reicht das.

Wird die Datei aufgerufen erscheint "Simpler Text" als text/plain im Browser. Thats it!

Erweiterte Beispiele


Nun gehen wir einige Schritte weiter. Wir möchten Texte und Bilder aus dem geschützten Ordner ausgeben wobei die Bilder noch einen Access-Schutz für nur eingeloggte User erhalten und die MetaDaten aus einer Datenbank beziehen.

Hier nun also die ReadAccessObjectType Objekte für Texte und Bilder. Ich lege sie in einer "createObjectTypes.php" Datei ab, welche ich an geforderter Stelle einbinden kann, da ich sie mehrmals brauchen werde. Ein DI-Container wäre natürlicher schöner.

PHP Quellcode:
<?php

  use PHPWiki\RAO\ReadAccessObjectType;
  use PHPWiki\RAO\ReadAccessObject;

  /**
   * Db instanzieren
   */

  $db = new PDO(
    'sqlite:'. APP_PATH . DS . 'RAO' . DS . 'sqlite'. DS .'database.sqlite',
    null,
    null,
    array(
      PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    )
  );
 
  /**
   * Closure die verwendet werden kann um File-Inhalt zu lesen
   * Braucht den Pfad um den Inhalt auszugeben der Datei
   * Braucht das ReadAccessObject um den Error-Code zu setzen, falls Datei nicht gefunden
   */

  $fileGetContentsClosure = function(ReadAccessObject $object, $path){
    if(!file_exists($path) || !is_readable($path)){
      $object->setErrorCode(ReadAccessObject::ERROR_NOT_FOUND);
      return '';
    }
    return file_get_contents($path);
  };
 
  /**
   * Einfache Textausgabe mit dem Identifier des konkreten Objektes
   * Die Text-Datei liegt auf dem Filesystem
   */

  $textObjectType = new ReadAccessObjectType(
     
    function(ReadAccessObject $object) use ($fileGetContentsClosure){
      return $fileGetContentsClosure(
        $object,
        APP_PATH . DS . 'RAO' . DS . 'files' . DS . (int)$object->getIdentifier() .'.txt'
      );
    },
       
    function(ReadAccessObject $object){
      return array (
        'Content-Type' => 'text/plain'
      );
    }
   
  );
 
  /**
   * Bild Ausgabe mit Content auf dem Filesystem
   * und MetaDaten in einer Datenbank
   * hasAccessClosure prüft ob der User eingeloggt ist
   */

  $imageObjectType = new ReadAccessObjectType(
     
    function(ReadAccessObject $object) use ($fileGetContentsClosure){
      return $fileGetContentsClosure(
        $object,
        APP_PATH . DS . 'RAO' . DS .  'images' . DS . (int)$object->getIdentifier() .'.jpg'
      );
    },
       
    function(ReadAccessObject $object){
      return array (
        'Content-Type' => 'image/jpeg'
      );
    },
       
    function(ReadAccessObject $object) use ($db){
      $sql = 'SELECT `id`, `name` FROM `image` WHERE `id` = '. (int)$object->getIdentifier() .' LIMIT 1';
      if(!$result = $db->query($sql)->fetch()){
        $object->setErrorCode(ReadAccessObject::ERROR_NOT_FOUND);
        return array();
      }
      return $result;
    },
       
    function(ReadAccessObject $object){
      if(!isset($_SESSION['loggedin']) || true !== $_SESSION['loggedin']){
        $object->setErrorCode(ReadAccessObject::ERROR_NO_ACCESS);
        return false;
      }
      return true;
    }
   
  );


Und nun nochmals Schritt für Schritt: Ich benötige meine beiden Klassen (Object und Type) und deklariere sie in meinem Namespace mit "use". Danach kann ich sie einfach über "ReadAccessObjectType" und "ReadAccessObject" ansprechen.

Kurz noch eine kleine SQLite Datenbank instanziert, die lediglich eine Tabelle "images" mit der Spalte "id" und "name" enthält. Das soll mal als MetaDaten reichen vorerst.

Dann geht’s los mit PHP 5.3. en masse Eine Funktion $fileGetContentsClosure wird definiert, bzw. in dieser Variable steckt eine Funktion die ab nun mit $fileGetContentsClosure() verwendet werden kann. Sehen wir weiter unten. Sie besteht eigentlich nur, weil ich sonst Code hätte doppelt schreiben müssen. Einerseits lädt ja der textObjectType seinen Content aus dem Filesystem als auch der imageObjectType.

Und nun kommt das erste Mal die Klasse ReadAccessObjectType zum Zuge. Hier halten wir also unsere Closures vor, die bei Bedarf angewendet werden um an die verschiedenen Inhalte zu kommen.

Beim textObjectType geht das relativ simpel von statten. Ich übergebe im Konstruktor 2 Closures. Die erste muss mir den Inhalt einer Textdatei liefern können und die zweite gibt mir die benötigten Headers zurück. Die erste Closure verwendet nun meine $fileGetContentsClosure die anhand eines Pfades und dem konkreten Objekt den Inhalt zurückgibt oder gleich den Error ins Objekt schreibt.

Der imageObjectType ist da ein wenig komplizierter. Die ersten beiden Parameter unterscheiden sich nicht grossartig. Die Closures können wie bei der Textdatei den Inhalt des Bildes sowie das Headers-Array zurückgeben.

Der dritte Parameter kümmert sich um die MetaDaten. Für die Bilder haben wir dazu ja die Datenbank. Ich hole mir also $db über "use" in meine Closure rein und Frage anhand des konkreten Objektes und seinem Identifier meine Datenbank ab.

Der vierte Parameter ist für die Kontrolle zuständig, ob jemand auf die Ressource zugreifen darf. Dazu wird einfach der Sessionkey "loggedin" auf true geprüft.

Einstiegspunkt



Eine index.php die den Identifier eines Objektes (hier überall auf (int) gecasted) entgegennimmt, könnte folgendermassen aussehen:

PHP Quellcode:
<?php

  session_start();
  $loggedin = $_SESSION['loggedin'] = (bool)@$_GET['loggedin'];

  /**
   * Charset setzen
   */

  header('Content-Type: text/html; charset=utf-8');

  /**
   * Bootstrap ausführen
   */

  require_once '../../application/Bootstrap.php';
 
  /**
   * ObjectTypes laden
   */

  require_once 'createObjectTypes.php';
 
  /**
   * Bild einlesen für MetaDaten
   */

  use PHPWiki\RAO\ReadAccessObject;
  $image  = new ReadAccessObject(@$_GET['id'], $imageObjectType);
  $meta = $image->getMetaData();
 
?>

Bild mit der ID <?php echo @$_GET['id']; ?><br />

<?php if($image->hasAccess()){ ?>
  <a href="get_image.php?id=<?php echo @$_GET['id'].'&loggedin='. $loggedin; ?>">
    <img
      alt="<?php echo $meta['name']; ?>"
      src="get_image.php?id=<?php echo @$_GET['id'].'&loggedin='. $loggedin; ?>"
    />
  </a>
<?php }else { ?>
  Keinen Zugriff auf diese Datei!
<?php } ?>

<br />
<br />

<a href="get_text.php?id=<?php echo @$_GET['id']; ?>">
  Text mit der ID <?php echo @$_GET['id']; ?>
</a>


Einfachheitshalber wird die id die über die URL übergeben wird, für das Bild und den Text verwendet. Macht so produktiv aber keinen Sinn natürlich. Und auch der Wert in der Session kann einfach über die URL manipuliert werden.

Aufrufe:
  • index.php (Bild und Text nicht gefunden)
  • index.php?id=1 (Kein Zugriff auf das Bild, Text kann angeschaut werden)
  • index.php?id=1&loggedin=1 (Bild wird direkt angezeigt und ist verlinkt, Text kann angeschaut werden)

Caching


Um das Caching zu beeinflussen werden am besten in den ReadAccessObjectType's zusätzliche Header gesetzt. Welche das sind, können unter php.net - headers nachgelesen werden (Beispiele ansehen).

Durch die Closures können nun noch weitere Funktionalitäten ohne Probleme eingefügt werden.
  • Content-Closure
    • Verschiedene mögliche Speicherorte durchlaufen
    • Application-Cache verwenden
  • Headers-Closure
    • Datei-Name setzen
    • Caching über Headers steuern
  • MetaDaten-Closure
  • Access-Closure
    • Zugang über Captcha regeln
    • Mit einer ACL arbeiten

Viel Spass!


Ganzer Code unter GitHub


Mitwirkende: mYkon
Erstellt von mYkon, 16.08.2011 am 14:40
Zuletzt bearbeitet von mYkon, 16.08.2011 am 14:48
1 Kommentare , 30589 Betrachtungen

Dieser Text steht unter der GNU-Lizenz für freie Dokumentation


 

Lesezeichen

Artikel-Optionen
Ansicht

Forumregeln
Es ist Ihnen nicht erlaubt, neue Themen zu verfassen.
Es ist Ihnen nicht erlaubt, auf Beiträge zu antworten.
Es ist Ihnen nicht erlaubt, Anhänge hochzuladen.
Es ist Ihnen nicht erlaubt, Ihre Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.

Gehe zu
Ähnliche Themen
Thema Autor Forum Antworten Letzter Beitrag
php zum ausliefern von js und css nutzen? cache-problem sebush PHP 8 13.12.2010 00:30
Werte in Arrays vorhalten und ausliefern... John Moule PHP 1 14.10.2008 15:09
Datei Downloads von PHP ausliefern lassen Evilmachine PHP 4 19.07.2008 10:57
Cms Ressourcen themac PHP 1 10.03.2005 13:11
Ressourcen-/rechenzeitlastig? Bomania PHP 3 11.05.2003 21:29


Alle Zeitangaben in WEZ +2. Es ist jetzt 07:26 Uhr.


Powered by vBulletin® Version 3.8.8 (Deutsch)
Copyright ©2000 - 2017, Jelsoft Enterprises Ltd.
Powered by NuWiki v1.3 RC1 Copyright ©2006-2007, NuHit, LLC