Location: PHPKode > projects > OpenRat CMS > openrat/actionClasses/Action.class.php
<?php
// ---------------------------------------------------------------------------
// $Id$
// ---------------------------------------------------------------------------
// OpenRat Content Management System
// Copyright (C) 2002-2004 Jan Dankert, hide@address.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


define('OR_NOTICE_OK'   ,'ok'     );
define('OR_NOTICE_WARN' ,'warning');
define('OR_NOTICE_ERROR','error'  );

define('OR_FILTER_ALPHA'   ,'abc'   );
define('OR_FILTER_ALPHANUM','abc123');
define('OR_FILTER_FILENAME','file'  );
define('OR_FILTER_MAIL'    ,'mail'  );
define('OR_FILTER_TEXT'    ,'text'  );
define('OR_FILTER_FULL'    ,'full'  );
define('OR_FILTER_NUMBER'  ,'123'   );
define('OR_FILTER_RAW'     ,'raw'   );
define('OR_FILTER_ALL'     ,'all'   );

/**
 * Eltern-Klasse fuer alle Actions.
 *
 * Diese Klasse stellt grundlegende action-uebergreifende Methoden
 * bereit.
 * Dient als Ueberklasse fuer alle abgeleiteten Action-Klassen in
 * diesem Package bzw. Verzeichnis.
 *
 * @author Jan Dankert
 * @package openrat.actions
 * @abstract 
 */
class Action
{
	var $db;
	var $templateVars = Array();
	var $actionName;
	var $subActionName;
	var $actionClassName;

	var $writable;
	var $publishing;
	var $actionConfig;
	
	/**
	 * Aktuell angemeldeter Benutzer.<br>
	 * Wird ind er Funktion "init()" gesetzt.
	 *
	 * @var Object Benutzer
	 */
	var $currentUser;


	/**
	 * Wird durch das Controller-Skript (do.php) nach der Kontruierung des Objektes aufgerufen.
	 * So koennen Unterklassen ihren eigenen Kontruktor besitzen, ohne den Superkontruktor
	 * (=diese Funktion) aufrufen zu m�ssen.
	 */
	function init()
	{
		global $conf;
		$this->writable    = !$conf['security']['readonly' ];
		$this->publishing  = !$conf['security']['nopublish'];
		$this->currentUser = Session::getUser();
		
		$this->templateVars['errors' ] = array();
		$this->templateVars['notices'] = array();

		if	( !$this->isEditable() || isset($_COOKIE['or_always_edit']) )
			$this->templateVars['mode'] = 'edit';
		else 
			$this->templateVars['mode'] = $this->getRequestVar('mode');
		
		header('Content-Language: '.$conf['language']['language_code']);
	}


	/**
	 * Liest eine Session-Variable
	 *
	 * @param String $varName Schl�ssel
	 * @return mixed
	 */
	function getSessionVar( $varName )
	{
		global $SESS;

		if	( !isset($SESS[ $varName ]) )
			return '';
		else	return $SESS[ $varName ];
	}


	/**
	 * Setzt eine Session-Variable
	 *
	 * @param Sring $varName Schl�ssel
	 * @param mixed $value Inhalt
	 * @return mixed
	 */
	function setSessionVar( $varName,$value )
	{
		global $SESS;

		$SESS[ $varName ] = $value;
	}


	/**
	 * Ermittelt den Inhalt der gew�nschten Request-Variablen.
	 * Falls nicht vorhanden, wird "" zur�ckgegeben.
	 *
	 * @param String $varName Schl�ssel
	 * @return String Inhalt
	 */
	function getRequestVar( $varName,$transcode=OR_FILTER_FULL )
	{
		global $REQ;

		if	( !isset($REQ[ $varName ]) )
			return '';
			
		
		switch( $transcode )
		{
			case OR_FILTER_ALPHA:
				$white = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
				break;
				
			case OR_FILTER_ALPHANUM:
				$white = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,_-!?%&/()';
				break;
				
			case OR_FILTER_FILENAME:
				// RFC 1738, Section 2.2:
				// Thus, only alphanumerics, the special characters "$-_.+!*'(),", and
				// reserved characters used for their reserved purposes may be used
				// unencoded within a URL.
				$white = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$-_.+!*(),'."'";
				break;
				
			case OR_FILTER_MAIL:
				$white = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-@';
				break;

			case OR_FILTER_TEXT:
			case OR_FILTER_FULL:
			case OR_FILTER_ALL:
				// Ausfiltern von Control-Chars ( ASCII < 32 außer CR,LF) und HTML (<,>)
				$white = '';
				                                $white .= chr(10).chr(13); // Line-Feed, Carriage-Return
				for ($i =  32; $i <=  59; $i++) $white .= chr($i);  // Zahlen
												// 60: '<'
				                                $white .= chr(61);
												// 62: '>'
				for ($i =  63; $i <= 126; $i++) $white .= chr($i);  // abc
				for ($i = 128; $i <= 255; $i++) $white .= chr($i);  // Sonderzeichen incl. UTF-8, UTF-16 (beginnen mit Bit 1)
				break;
				
			case OR_FILTER_NUMBER:
				$white = '1234567890.';
				break;
				
			case OR_FILTER_RAW:
				return $REQ[ $varName ];
				
			default:
				Http::serverError('Unknown request filter','not found: '.$transcode);
				return '?';
		}
		
		$value  = $REQ[ $varName ];
		$newValue = Text::clean( $value, $white );
		
		if	( strlen($newValue) != strlen($value) )
			$this->addNotice('','','UNEXPECTED_CHARS',OR_NOTICE_WARN);
			
		return $newValue;
	}


	/**
	 * Ermittelt, ob der aktuelle Request eine Variable mit dem
	 * angegebenen Namen enth�lt.
	 *
	 * @param String $varName Schl�ssel
	 * @return boolean true, falls vorhanden.
	 */
	function hasRequestVar( $varName )
	{
		global $REQ;

		return( isset($REQ[$varName]) && (!empty($REQ[$varName]) || $REQ[$varName]=='0') );
	}


	/**
	 * Ermittelt die aktuelle Id aus dem Request.<br>
	 * Um welche ID es sich handelt, ist abh�ngig von der Action.
	 *
	 * @return Integer
	 */
	function getRequestId()
	{
		if	( $this->hasRequestVar('idvar') )
			return intval( $this->getRequestVar( $this->getRequestVar('idvar') ) );
		else
			return intval( $this->getRequestVar( REQ_PARAM_ID ) );
	}



	/**
	 * Setzt eine Variable f�r die Oberfl�che.
	 *
	 * @param String $varName Schl�ssel
	 * @param Mixed $value
	 */
	function setTemplateVar( $varName,$value )
	{
		$this->templateVars[ $varName ] = $value;
	}


	/**
	 * Setzt eine Liste von Variablen f�r die Oberfl�che.
	 *
	 * @param Array $varList Assoziatives Array
	 */
	function setTemplateVars( $varList )
	{
		foreach( $varList as $name=>$value )
		{
			$this->setTemplateVar( $name,$value );
		}
	}


	/**
	 * F�gt einen Validierungsfehler hinzu.
	 *
	 * @param String $name Name des validierten Eingabefeldes
	 * @param String Textschl�ssel der Fehlermeldung (optional)
	 */
	function addValidationError( $name,$message="COMMON_VALIDATION_ERROR",$vars=array(),$log=array() )
	{
		if	( !empty($message) )
			$this->addNotice('','',$message,OR_NOTICE_ERROR,$vars,$log);

		$this->templateVars['errors'][] = $name;
	}

	
	/**
	 * F�gt ein Meldung hinzu.
	 *
	 * @param String $type Typ des Objektes, zu dem diese Meldung geh�rt.
	 * @param String $name Name des Objektes, zu dem diese Meldung geh�rt.
	 * @param String $text Textschl�ssel der Fehlermeldung (optional)
	 * @param String $status Einer der Werte OR_NOTICE_(OK|WARN|ERROR)
	 * @param Array  $vars Variablen f�r den Textschl�ssel
	 * @param Array $log Weitere Hinweistexte f�r diese Meldung.
	 */
	function addNotice( $type,$name,$text,$status=OR_NOTICE_OK,$vars=array(),$log=array() )
	{
		if	( $status === true )
			$status = OR_NOTICE_OK;
		elseif	( $status === false )
			$status = OR_NOTICE_ERROR;

		$this->templateVars['notice_status'] = $status;
		
		if	( $status == OR_NOTICE_OK && isset($_COOKIE['or_ignore_ok_notices']))
			return;
			
		if	( !is_array($log))
			$log = array($log);

		if	( !is_array($vars))
			$vars = array($vars);
			
		$this->templateVars['notices'][] = array('type'=>$type,
                                                 'name'=>$name,
		                                         'key'=>'NOTICE_'.$text,
		                                         'vars'=>$vars,
		                                         'text'=>lang('NOTICE_'.$text,$vars),
		                                         'log'=>$log,
		                                         'status'=>$status);
	}


	
	/**
	 * Ausgabe des Templates.<br>
	 * <br>
	 * Erst hier soll die Ausgabe auf die Standardausgabe, also die
	 * Ausgabe f�r den Browser, starten.<br>
	 * <br>
	 *
	 * @param String Wird nicht benutzt!
	 */
	function forward()
	{
		$db = db_connection();

		if	( isset($this->actionConfig[$this->subActionName]['direct']) )
		{
			if	( is_object( $db ) )
				$db->commit();
			exit; // Die Ausgabe ist bereits erfolgt (z.B. Bin�rdateien o. WebDAV)
		}
			
		// Pruefen, ob HTTP-Header gesendet wurden. Dies deutet stark darauf hin, dass eine
		// PHP-Fehlermeldung ausgegeben wurde. In diesem Fall wird hier abgebrochen.
		// Weitere Ausgabe wuerde keinen Sinn machen, da wir nicht wissen, was
		// passiert ist.
		if	( headers_sent() )
		{
			Http::serverError("Some server error messages occured - see above - CMS canceled.");
		}
		
		if	( is_object( $db ) )
			$db->commit();
		
		$expires = substr(gmdate('r'),0,-5).'GMT';
		header('Expires: '      .$expires );
		
		header('X-Content-Security-Policy: '.'allow *; script-src \'self\'; options \'inline-script\'');
		
		
		$httpAccept = getenv('HTTP_ACCEPT');
		$types = explode(',',$httpAccept);
		
		if	( version_compare(PHP_VERSION, '4.3.0', '>=') ) 
			Logger::trace('Output'."\n".print_r($this->templateVars,true));
		
		// Weitere Variablen anreichern.
		$this->templateVars['session'] = array('name'=>session_name(),'id'=>session_id(),'token'=>token() );
		$this->templateVars['version'] = OR_VERSION;
		
		if	( sizeof($types)==1 && in_array('application/php-array',$types) || $this->getRequestVar('output')=='php-array' )
		{
			if	(version_compare(PHP_VERSION, '4.3.0', '<'))
				Http::serverError('application/php-array is only available with PHP >= 4.3');
				
			header('Content-Type: application/php-array');
			echo print_r($this->templateVars,true);
			exit;
		}

		if	( sizeof($types)==1 && in_array('application/php-serialized',$types) || $this->getRequestVar('output')=='php' )
		{
			header('Content-Type: application/php-serialized');
			serialize($this->templateVars);
			exit;
		}

		if	( sizeof($types)==1 && in_array('application/json',$types) || $this->getRequestVar('output')=='json' )
		{
			require_once( OR_SERVICECLASSES_DIR."JSON.class.".PHP_EXT );
			$json = new JSON();
			header('Content-Type: application/json');
			echo $json->encode( $this->templateVars );
			exit;
		}

		if	( sizeof($types)==1 && in_array('application/xml',$types) || $this->getRequestVar('output')=='xml' )
		{
			require_once( OR_SERVICECLASSES_DIR."XML.class.".PHP_EXT );
			$xml = new XML();
			$xml->root = 'server'; // Name des XML-root-Elementes
			header('Content-Type: application/xml');
			echo $xml->encode( $this->templateVars );
			exit;
		}

		$this->setMenu();
		
		$tplName = $this->actionName.'/'.$this->subActionName;

			
		if	( isset($this->actionConfig[$this->subActionName]['action']) )
			$tplName = $this->actionConfig[$this->subActionName]['action'].'/'.$this->subActionName;

		if	(isset($this->actionConfig[$this->subActionName]['alias']))
			$tplName = (method_exists(new ObjectAction(),$this->subActionName)?'object':$this->actionName).'/'.$this->actionConfig[$this->subActionName]['alias'];
			
		if	(isset($this->actionConfig[$this->subActionName]['target']))
			$targetSubActionName = $this->actionConfig[$this->subActionName]['target'];
		else
			$targetSubActionName = $this->subActionName;
		

		if	( isset($this->actionConfig[$this->subActionName]['menu']))
			$windowTitle = 'menu_title_'.$this->actionName.'_'.$this->actionConfig[$this->subActionName]['menu'];

		global $conf;
		global $REQ;
		global $PHP_SELF;
		global $HTTP_SERVER_VARS;
		global $image_dir;
		       
		$tplName = str_replace( '_','/',$tplName );
		
		$tplFileName = $tplName.'.tpl.'.PHP_EXT;
		$conf_php = PHP_EXT;
	
		// ?bertragen der Array-Variablen in den aktuellen Kontext
		//
		extract( $this->templateVars );

		// Falls Eingabefehler, dann Uebertragen der Request-Variablen in den aktuellen Kontext
		if	( count($errors)>0 )
			foreach( $REQ as $requestVar=>$dummy )
				if	( !isset($$requestVar) )
					// Aber achtung, hier geben wir die Request-Variablen einfach so wieder raus!
					$$requestVar = $this->getRequestVar( $requestVar );
				
		// Setzen einiger Standard-Variablen
		//
		$tpl_dir    = OR_THEMES_DIR.$conf['interface']['theme'].'/pages/html/';
		$image_dir  = OR_THEMES_EXT_DIR.$conf['interface']['theme'].'/images/';
	
		$user = Session::getUser();
		
		$root_stylesheet = OR_THEMES_EXT_DIR.$conf['interface']['theme'].'/css/base.css';
			
		if	( !is_object($user) )
			$user_stylesheet = OR_THEMES_EXT_DIR.$conf['interface']['theme'].'/css/'.$conf['interface']['style']['default'].'.css';
		else
			$user_stylesheet = OR_THEMES_EXT_DIR.$conf['interface']['theme'].'/css/'.$user->style.'.css';
		
		$self = $HTTP_SERVER_VARS['PHP_SELF'];
	
		$tplFileName = str_replace( '_','/',$tplFileName );

		if	( !empty($conf['interface']['override_title']) )
			$cms_title = $conf['interface']['override_title'];
		else
			$cms_title = OR_TITLE.' '.OR_VERSION;

		$charset = $this->getCharset();
			
		$showDuration = $conf['interface']['show_duration'];

		$subActionName = $this->subActionName;
		$actionName    = $this->actionName;
		$requestId     = $this->getRequestId();
		
		if	( $conf['theme']['compiler']['enable'] )
		{
			$te = new TemplateEngine();
			$te->compile( $tplName );
			unset($te);
		}

		// Einbinden des Templates
		require( $tpl_dir.$tplFileName );
		
	}
	
	
	/**
	 * Ruft eine weitere Subaction auf.
	 *
	 * @param String $subActionName Name der n�chsten Subaction. Es muss eine Methode mit diesem Namen geben.
	 */
	function callSubAction( $subActionName )
	{
		if	( in_array($this->actionName,array('page','file','link','folder')) )
			Session::setSubaction( $subActionName );

		$this->subActionName = $subActionName;		

		Logger::trace("next subaction is '$subActionName'");
		
		$this->$subActionName();
	}


	/**
	 * Ermitteln, ob Benutzer Administratorrechte besitzt
	 * @return Boolean TRUE, falls der Benutzer ein Administrator ist.
	 */
	function userIsAdmin()
	{
		$user = Session::getUser();
		return $user->isAdmin;
	}



	/**
	 * Ermitteln des Benutzerobjektes aus der Session
	 * @return User
	 */
	function getUserFromSession()
	{
		return Session::getUser();
	}


	
	/**
	 * Benutzen eines sog. "Conditional GET".
	 *
	 * Diese Funktion setzt einen "Last-Modified"-HTTP-Header.
	 * Ist der Inhalt der Seite nicht neuer, so wird der Inhalt
	 * der Seite nicht ausgegeben, sondern nur HTTP-Status 304
	 * ("304 not modified") gesetzt.
	 * Der Rest der Seite muss dann nicht mehr erzeugt werden,
	 * wodurch die Performance stark erhoeht werden kann.
	 * Ggf. kann das Benutzen dieses Mechanismus zu unerw�nschten
	 * Effekten f�hren, dann muss "conditional GET" in der
	 * Konfiguration deaktiviert werden.
	 *
	 * Credits: Danke an Charles Miller
	 * @see http://fishbowl.pastiche.org/2002/10/21/http_conditional_get_for_rss_hackers
	 *
	 * Gefunden auf:
	 * @see http://simon.incutio.com/archive/2003/04/23/conditionalGet
	 *
	 * @param Timestamp Letztes Aenderungsdatum dieser Seite
	 */
	function lastModified( $time )
	{
		$user = Session::getUser();
		if	( $user->loginDate > $time && !isset($this->actionConfig[$this->subActionName]['direct']) )
			// Falls Benutzer-Login nach letzter �nderung.
			// Zweck: Nach einem Login sollte mind. 1x jede Seite neu geladen werden, dies
			// Ist z.B. nach einer Style-�nderung durch den Benutzer notwendig.
			// Falls aus Versehen doch einmal zuviel gecacht wurde, kann man das durch ein
			// Neu-Login beheben. 
			$time = $user->loginDate;

		// Conditional-Get eingeschaltet?
		global $conf;
		if	( ! $conf['cache']['conditional_get'] )
			return;

		$lastModified = substr(date('r',$time -date('Z')),0,-5).'GMT';
		$etag         = '"'.md5($lastModified).'"';

		// Header senden
		header('Last-Modified: '.$lastModified );
		header('ETag: '         .$etag         );
		
		// Die vom Interpreter sonst automatisch gesetzten
		// Header uebersteuern
		header('Cache-Control: must-revalidate');
		header('Pragma:');

		// See if the client has provided the required headers
		$if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : false;
		$if_none_match     = isset($_SERVER['HTTP_IF_NONE_MATCH']    ) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']    ) :	false;

		if	( !$if_modified_since && !$if_none_match )
			return;

		// At least one of the headers is there - check them
		if	( $if_none_match && $if_none_match != $etag )
			return; // etag is there but doesn't match


		if	( $if_modified_since && $if_modified_since != $lastModified )
			return; // if-modified-since is there but doesn't match

		// Der entfernte Browser bzw. Proxy holt die Seite nun aus seinem Cache 
		header('HTTP/1.0 304 Not Modified');
		exit;  // Sofortiges Skript-Ende
	}



	/**
	 * @param max Anzahl der Sekunden, die die Seite im Browsercache bleiben darf
	 */
	function maxAge( $max=3600 ) 
	{
		// Die Header "Last-Modified" und "ETag" wurden bereits in der
		// Methode "lastModified()" gesetzt.
		
		header('Expires: '.substr(date('r',time()-date('Z')+$max),0,-5).'GMT' );
		header('Pragma: '); // 'Pragma' ist Bullshit und
		                    // wird von den meisten Browsern ignoriert.
		header('Cache-Control: public, max-age='.$max.", s-maxage=".$max);
	}	
	
	
	
	function setMenu()
	{
		if	(!isset($this->actionConfig[$this->subActionName]['menu']))
			return;
		$windowMenu = array();
		$name       = $this->actionConfig[$this->subActionName]['menu'];
		$menuList   = explode(',',$this->actionConfig['menu'][$name]);

		if	( isset($this->actionConfig[$this->subActionName]['menuaction']))
			$actionName = $this->actionConfig[$this->subActionName]['menuaction'];
		else
			$actionName = $this->subActionName;
		
		foreach( $menuList as $menuName )
		{
			if	( isset($this->actionConfig[$menuName]['alias']) )
				$menuText = 'menu_'.$this->actionName.'_'.$this->actionConfig[$menuName]['alias'];
			else
				$menuText = 'menu_'.$this->actionName.'_'.$menuName;
			
			
			$menuKey = 'accesskey_window_'.$menuName;
			
//			Logger::trace("testing menu $menuName");
			$menuEntry = array('subaction'=>$menuName,
                               'text'     =>$menuText,
                               'title'    =>$menuText.'_DESC',
                               'key'      =>$menuKey );
                               
			if	( $this->checkMenu($menuName) )
				$menuEntry['url'] = Html::url($actionName,$menuName,$this->getRequestId());
				
			$windowMenu[] = $menuEntry;
		}
		$this->setTemplateVar('windowMenu',$windowMenu);
	}
	
	
	
	/**
	 * Ermittelt, ob der Men�punkt aktiv ist.
	 * Ob ein Men�punkt als aktiv angezeigt werden soll, steht meist erst zur Laufzeit fest.
	 * <br>
	 * Diese Methode kann von den Unterklassen �berschrieben werden.
	 * Falls diese Methode nicht �berschrieben wird, sind alle Men�punkte aktiv.
	 *
	 * @param String $name Logischer Name des Men�punktes
	 * @return boolean TRUE, wenn Men�punkt aktiv ist.
	 */
	function checkMenu( $name )
	{
		// Standard: Alle Men�punkt sind aktiv.
		return true;
	}
	
	
	
	/**
	 * Ermitelt den Zeichensatz fuer die Ausgabe.
	 * 
	 * Falls für die Datenbank-Verbindung ein Zeichensatz angegeben ist, so wird
	 * dieser genommen und in HTTP-Response-Header sowieso auch im HTML-Kopf verwendet.
	 * 
	 * Falls nicht vorhanden, wird der Zeichensatz aus der geladenen Sprachdatei verwendet. Diese
	 * ergibt sich dann aus der Sprache, die der Browser anfordert. 
	 *
	 * @return String Zeichensatz
	 */
	function getCharset()
	{
		return charset();
	}
	
	
	/**
	 * Stellt fest, ob die Anzeige dieser Aktion editierbar ist.
	 *
	 * @return boolean
	 */
	function isEditable()
	{
		return isset($this->actionConfig[$this->subActionName]['editable']) && $this->actionConfig[$this->subActionName]['editable']; 
	}

	
	/**
	 * Stellt fest, ob sich die Anzeige im Editier-Modus befindet.
	 *
	 * @return boolean
	 */
	function isEditMode()
	{
		if	( readonly() )
			return false;
			
		return !$this->isEditable() || $this->getRequestVar('mode')=='edit' || isset($_COOKIE['or_always_edit']) || (isset($this->templateVars) && $this->templateVars['mode']=='edit'); 
	}
}

?>
Return current item: OpenRat CMS