0251 / 590 837 15
info@a-coding-project.de
;

Exception-System (CMS)

Wenn in unserem CMS derzeit eine Seite nicht gefunden oder der zugriff verweigert wurde, bekommen wir eine PHP-Meldung. Das sollte natürlich so nicht sein. Derzeit werden die Rechte direkt in der index.php bzw. neuerdings in der /content/index.txt überprüft, jedoch funktionierte das auch noch nicht so schön.

Erst habe ich den alten Fehler mit dem Zugriff-Verweigert ausgebssert und wollte Ähnliches für das Seite-Nicht-Gefunden machen. Doch da kam mir eine viel bessere Idee: Wir regeln das über Exceptions. So müssen wir in der index lediglich die Exceptions abfangen und die Fehlernachricht anzeigen.

Für alle ContentLion-Exceptions gibt es die neue Klasse /system/contentlionexception.php. In dieser wird neben der Standard-PHP-Exception auch eine Fehlerseite als Page-Objekt angelgt. So können wir eine sprechende Fehlermeldung auch über unser System anzeigen. Die Basisklasse die wir nun für alle über Contentlion geworfenen Exceptions benutzten sieht so aus:

<?PHP
  class ContentLionException extends Exception{
    protected $errorPage = null;

    public function getErrorPage(){
      return $this->errorPage;
    }

    public function setErrorPage(Page $page){
      $this->errorPage = $page;
    }
  }
?>

Jetzt erben wir von dieser Klasse in der neuen /system/classes/accessdeniedexception.php:

<?PHP
  class AccessDeniedException extends ContentLionException{

    public function __construct($message, $code = 0) {
      parent::__construct($message, $code);
      $page = new Page();
      $page->loadPropertiesById(getSetting("global","global","accessdenied"));
      parent::setErrorPage($page);
    }

  }
?>

Bei der AccessDeniedException können wir die Seite schön über die Datenbank laden. Bei der FileNotFound-Ecxeption sieht es anders aus, denn was passiert, wenn die Fehlerseite auch nicht gefunden werden kann? Richtig: Eine Endos-Schleife. Hier erstmal der Code der Exception (system/classes/filenotfoundexception.php):

<?PHP
  class FileNotFoundException extends ContentLionException{

    public function __construct($message, $code = 0) {
      parent::__construct($message, $code);
      $page = new Page();
      $page->editor = new BlankEditor($page);
      $page->editor->setContent("<h2>Seite nicht gefunden!</h2><p>Diese Seite konnte nicht gefunden werden!</p>");
      parent::setErrorPage($page);
    }

  }
?>

Hier wird eine neue Editor-Klasse verwendet. Ich habe diese sehr simpel aufgebaut, so dass man den Text zuweisen kann und werden Datenbankverbindung noch Zugriff auf Dateisystem benötigt. /system/classes/blankeditor.php:

<?PHP
  class BlankEditor extends Editor{
    protected $content = "";

    function __construct($page){
      $this->page = $page;
    }

    public function display(){
      echo $this->content;
    }

    public function setContent($content){
      $this->content = $content;
    }

    function getHeader(){
    }

    public function getEditableCode(){
    }

    public function save($newPage,$oldPage){
    }

    private function readContent(){
        $filename = getSetting("global","global","root")."content/articles/".$this->page->alias.".php";
        $handle = @fopen ($filename, "rb");
        if($handle && filesize($filename) > 0){
            $contents = fread ($handle, filesize ($filename));
            fclose ($handle);
        }
        else{
            $contents = "";
        }
        return $contents;
    }

    private function deleteContent(){
    }

    function writeContent($content){
    }

    function getBreadcrumb(){
    }

  }
?>

Die Exceptions selbst werden in der /system/classes/page.php ausgelöst. So können wir sicher sein, dass der User immer auch Zugriff auf eine Seite hat, egal wo die Page-Klasse verwendet wird. Da wir hier sowieso prüfen müssen, ob die Seite vorhanden ist, müssen wir auch hier die FileNotFound-Exception werfen:

  function loadProperties($alias){
    if ($alias == "") {
      $alias = "home";
    }
    $this->alias = $alias;
    $row = $GLOBALS['db']->ReadRow("SELECT * FROM {'dbprefix'}pages 
                           WHERE alias = '".$alias."'");
    $this->load($row);
  }

  function loadPropertiesById($id){
    $row = $GLOBALS['db']->ReadRow("SELECT * FROM {'dbprefix'}pages 
                           WHERE id = '".$id."'");
    $this->load($row);
  }

  function load($data){
    if($data){
      $this->id = $data->id;
      $this->title = $data->title;
      $this->ownerid = $data->owner;
      $this->menu = $data->menu;
      $this->alias = $data->alias;
      $this->meta  = new Meta();
      $this->meta->pageid = $this->id;
      $this->meta->load();
      $this->editor = new $data->editor($this);
      if(!$_SESSION['user']->role->canAccess($this)){
        throw new AccessDeniedException("Access denied: ".$this->alias);
      }
    }
    else{
      throw new FileNotFoundException("File Not Found: ".$this->alias);
    }
  }

Zu guter letzt kommt das Abfangen in der /content/index.txt. Diese Datei wurde vor Kurzem vom Daniel dahin verschoben, damit wir bei der Installation auch die index.php aufrufen können. Nach der Installation wird die index.txt zur index.php. Beim Abfangen der Exceptions tauschen wir einfach die aktuelle Seite mit der Seite aus der Exception aus:

<?PHP
  function __autoload($class_name){
      $imported = false;
      $namespaces = split("_",strtolower($class_name));
      if(sizeOf($namespaces) == 3){
        if($namespaces[0] == "plugin"){
          require_once "system/plugins/".$namespaces[1]."/classes/".$namespaces[2].".php";
          $imported = true;
        }
        else if($namespaces[0] == "skin"){
          require_once "system/skins/".$namespaces[1]."/classes/".$namespaces[2].".php";
          $imported = true;
        }
      }
      if(!$imported){
        require_once "system/classes/".strtolower($class_name).".php";
      }
  }
  session_start();
  include("system/settings.php");
  include("system/filterfilename.php");
  GetParamParser::parse();
  $db = new MySQL('system/dbsettings.php');
  $db->Connect();
  $language = new Language();
  if(!isset($_SESSION['user'])) $_SESSION['user'] = new User();
  if(file_exists(getSetting("global","global","root").$_GET['include'].".htm")){
    include(getSetting("global","global","root").$_GET['include'].".htm");
  }
  else if(file_exists(getSetting("global","global","root").$_GET['include'].".html")){
    include(getSetting("global","global","root").$_GET['include'].".html");
  }
  else{
    if(!isset($_GET['include'])){
      $_GET['include'] = '';
    }
    try{
      $currentpage = new Page();
      $currentpage->loadProperties($GLOBALS['db']->EscapeString($_GET['include']));
    }
    catch(ContentLionException $ex){
      $currentpage = $ex->getErrorPage();
    }
    if(!isset($_GET['skin'])){
      include(SkinController::getCurrentSkinPath()."/index.php");
    }
    else{
      include('system/skins/'.$_GET['skin']."/index.php");
    }
  }
?>

Alle weiteren Änderungen stehen bei Google Code in den Revisionen r38 r39 r40 r41 r42 r43 r44 und r45.