<?php
/**
* Class file for generic Driver
*
* PHP Version 4
*
* @author <hide@address.com>
* @copyright Copyright (c) 2006, Benedikt Hallinger
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
/**
* Driver base class
*
* All core driver class files extend this class. It serves a common API used by
* driver loading and such as well as some common methods for validating fields
* and fetching/securing/processing data.
*/
class LCC_Driver
{
/**
* LCC_Core will store a reference of itself here on driver loading
*
* @var LCC_Core
*/
var $lcc_core = null;
/**
* Field Security (custom fields)
*
* This array holds the security parameters checked by LCC_Core
* for fields that are to be read/set.
* All fields that are requested from the selected Design must be defined
* here, otherwise they will not be read from the Driver.
* If fields are to be stored, the data entered must be matched by a
* regular expression defined here.
* Not all fields of an drivertype are required to be mandatory or writable.
*
* Pleade note, that date/time fields must support to a format usable by strtotime()!
*
* Actually not all fields are listed here. LCC uses some internal fields
* that are automatically provided to the view but can never be written
* since it is metadata generated from LCC_Core.
*
* The key is the name of the field as it is requested from the design
* (e.g. $_POST['foobar'] is 'foobar' here). The key holds a associative array
* containing the possible LCC-Settings which are:
* - regex Regular expression that must match if storing data
* - mandatory True or False; says if the field must be filled
* to allow modification / addition of a field
* - writable True or False
* - html True or False, if True, then output will be passed through htmlentities()
*
* Each specific core driver class overwrites this with real values.
*
* @access private
* @see setFieldSecurity(), checkField(), checkMandatory()
* @var array
*/
var $_allowedFields = array();
/**
* Fields managed by LCC internally (metadata)
*
* This is to define the fields used by LCC internally so the core
* can ensure no undefined data is read from the driver.
* Additionally this is used to ensure this fields cannot be
* defined as allowed fields
*
* Each specific core driver class overwrites this with real values.
*
* @access private
* @var array
*/
var $_metaFields = array();
/**
* Methods the class must provide in order to be compatible with the core.
*
* Each specific core driver class overwrites this with real values.
*
* @var array
* @access private
*/
var $_needed_methods = array();
/**
* Add or modify allowed driver fields
*
* All fields that are requested from the selected Design must be defined
* here, otherwise they will not be read from the Driver.
* If fields are to be stored, the data entered for the field
* must be matched by a regular expression defined here.
* Not all fields of an driver are required to be mandatory or writable.
*
* Here you can tune exisiting driver fields or add new ones to LCC.
* It is possible to overwrite only a specific part of a existing field.
*
* The following field parameters exist:
* - regex: (string) Regular expression that must match if storing a field
* - mandatory: (bool) Is the field mandatory for adding/modifying a dataset to the backend (e.g. a new event)?
* - writable: (bool) Is the field writable or read-only?
* - html: (bool) Is HTML allowed? (if not, output will be passed through htmlentities())
* Some Examples:
* <code>
* setFieldSecurity('myField', array('regex' => '/\d\d/', 'mandatory' => true)); // initially add a custom field (THIS WILL FAIL since not all parameters are given)
* setFieldSecurity('myField', array('regex' => '/\d\d/', 'mandatory' => false, 'writable' => true, 'html' => false)); // initially add a custom field (This will succeed because the above error is fixed)
* setFieldSecurity('myField', array('regex' => '/\d+/', 'mandatory' => true)); // modify mandatory flag and regex
* </code>
*
* @param string $field Name of the field
* @param array $param Parameters of that field
* @see $_allowedFields, checkfield()
*/
function setFieldSecurity($field, $params)
{
// Here we define the allowed parameters and a function that is called to ckeck
$possible_parameters = array(
'regex' => 'is_string',
'mandatory' => 'is_bool',
'writable' => 'is_bool',
'html' => 'is_bool'
);
if (!is_string($field) || strlen($field) == 0) die('<b>'.get_class($this).' setFieldSecurity() ERROR:</b> $field is not a string or empty');
if (!is_array($params)) die('<b>'.get_class($this).' setFieldSecurity() ERROR:</b> Field parameters must be an array!');
// Protect metadata fields:
if (in_array($field, $this->_metaFields)) {
die('<b>'.get_class($this).' setFieldSecurity() ERROR:</b> "'.$field.'" is reserved for internal use. Sorry!');
}
// check regular expression, if parameter given
if ($params['regex'] !== null && preg_match($params['regex'], '') === false) die('<b>'.get_class($this).' setFieldSecurity() ERROR:</b> $regex seems not to be a valid regular expression!');
// Check if we want to add a custom field (all parameters must be given then)
if (!array_key_exists($field, $this->_allowedFields)) {
// check if all params are here
$params_not_set = $possible_parameters;
foreach ($params as $key => $value) {
if (array_key_exists($key, $params_not_set)) {
unset($params_not_set[$key]);
}
}
if (count($params_not_set) > 0) {
die('<b>'.get_class($this).' setFieldSecurity() ERROR:</b> Not all field parameters for '.$field.' are given but all parameters are mandatory if adding a new field!');
}
}
// modify or add data
foreach ($params as $key => $value) {
if (!array_key_exists($key, $possible_parameters)) {
die('<b>'.get_class($this).' setFieldSecurity() ERROR:</b> Field parameter "'.$key.'" is not known!');
} else {
if (call_user_func ($possible_parameters[$key], $value)) {
$this->_allowedFields[$field][$key] = $value;
} else {
die('<b>'.get_class($this).' setFieldSecurity() ERROR:</b> Field parameter "'.$key.'" has wrong type! ('.$possible_parameters[$key].'() failed)');
}
}
}
}
/**
* Add a user defined field to this driver
*
* This is a alias to {@link setFieldSecurity()}, so please see the documentation
* of that method for more information on parameters.
* However this is just an alias, you should use this method to add and the other only to
* modify field parameters, so your code will be much more readable.
*
* @param string $field Name of the field
* @param array $param Parameters of that field (All required!)
*/
function addCustomField()
{
$args = func_get_args();
call_user_func_array( array(&$this, 'setFieldSecurity'), $args );
}
/**
* Checks several rights of fields
*
* This function is used by the core to query the allowed fields.
* The following checks are implemented:
* - 'mandatory' See if this field is mandatory for the driver
* - 'writable' See if this field is allowed to be modified
* - 'html' Is HTML allowed?
* - 'validate' Validate input (applies the regular expression)
*
* The following checks need additional parameters:
* - 'validate': $parameter is the value to be checked against the defined regex
*
* @param string $field Name of the field to check
* @param string $check Check to be performed
* @param string $parameter (optional) Parameter of the check
* @see $_allowedFields, setFieldSecurity()
*/
function checkField($field, $check, $parameter = '')
{
if (array_key_exists($field, $this->_allowedFields) && is_string($field) && strlen($field) > 0) {
switch ($check) {
case 'mandatory':
case 'writable':
case 'html':
$return = $this->_allowedFields[$field][$check];
break;
case 'validate':
$return = $this->_validateField($field, $parameter); // call validation method
break;
default:
die('<b>'.get_class($this).' checkField() ERROR:</b> The check "'.$check.'" is not implemented!');
}
} else {
die('<b>'.get_class($this).' checkField() ERROR:</b> The requested field "'.$field.'" is not defined.');
}
return $return;
}
/**
* Default method for validate action 'validate'
*
* Each driver base class amy override this method to provide a
* custom way of field validation.
*
* Here, we look into the field definition of the driver and check the parameter,
* which in this case is the fields value, using the regular expression defined
* in the fields definition.
*
* This method gets called by {@link LCC_Driver::checkField()}
*
* @access private
* @param string $field The field we want to check
* @param string $parameter The given parameter to match
* @return boolean
*/
function _validateField($field, $parameter)
{
if ( strlen($parameter) > 0 ) {
return @preg_match($this->_allowedFields[$field]['regex'], $parameter);
}
else
// Default return is set to 'true' because of allowed empty fields.
return true;
}
/**
* This checks a given data array if all mandatory fields are set
*
* @param array $data Associative array containing data ($fieldname => $value)
* @return array Array containing field names that are mandatory but not set
* @see $_allowedFields, setFieldSecurity()
*/
function checkMandatory($data)
{
// Build list with mandatory fields
$mandatorys = array();
foreach ($this->_allowedFields as $field => $permission) {
if ($permission['mandatory']) {
array_push($mandatorys, $field);
}
}
// Build list with filled fields
$filled_fields = array();
foreach ($data as $field => $value) {
if (strlen($value) > 0) {
$filled_fields[$field] = true;
}
}
// Compare and return
return array_diff($mandatorys, array_keys($filled_fields));
}
/**
* Set the drivers config
*
* With this method you can overwrite parts of the
* drivers default config.
*
* @param array $config parts of drivers config (key => value pairs)
*/
function setConfig($config = array())
{
if (!isset($this->config) || !is_array($this->config)) {
die('<b>'.get_class($this).' ERROR:</b> Your driver "'.get_class($this).'" is missing its default configuration!<br>Make sure the default configuration is stored
in the class variable "$config" and is an array!');
} else {
$subsyntaxcheck = array(); // used to check subsyntax only once
foreach ($config as $key => $value) {
if (array_key_exists($key, $this->config)) {
// Config schema check
if (is_array($this->config[$key]) && !is_array($value)) {
die('<b>'.get_class($this).' ERROR:</b> Configuration key "'.$key.'" is expected to be an array!');
}
if (!is_array($this->config[$key]) && is_array($value)) {
die('<b>'.get_class($this).' ERROR:</b> Configuration key "'.$key.'" is expected to be a string!');
}
// Deal with sub arrays, since some driver may use sub arrays
// Supported are only two dimensional keys (e.g. $foo['bar']['baz'] = 'blabla') in total!
// If the default config of the driver is an empty array, we assume dynamic keys
if (is_array($this->config[$key]) && is_array($value)) {
foreach ($value as $subkey => $subvalue) {
if (!array_key_exists($key, $subsyntaxcheck)) {
// Check this key only the first time, otherwise we run into trouble because new values are already added thus array is not empty
$subsyntaxcheck[$key] = is_array($this->config[$key]) && empty($this->config[$key]);
}
if (array_key_exists($subkey, $this->config[$key]) || $subsyntaxcheck[$key]) {
$this->config[$key][$subkey] = $subvalue;
} else {
echo "<pre>";var_dump($this->config[$key], $subvalue);
die('<b>'.get_class($this).' ERROR:</b> Your driver "'.get_class($this).'" does not know the subconfiguration key "'.$subkey.'"!');
}
}
} else {
$this->config[$key] = $value;
}
} else {
die('<b>'.get_class($this).' ERROR:</b> Your driver "'.get_class($this).'" does not know the configuration key "'.$key.'"!');
}
}
}
}
/**
* Get data from this driver and apply security settings for the fields
*
* This fetches all the drivers data from the backend and applies the settings of
* the field defined in {@link $_allowedFields} to them.
* Additionally, the meta fields defined in {@link $_metaFields} are allowed to pass.
*
* If the driver method getData() returns false, we assume the data doesn't exist
* so we return false.
*
* The specific driver type implement methods that assign additional metadata,
* so this method is not called directly from the core.
*
* @param int $id ID of the data; is passed to $driver->getData()
* @return array|false Secured fields of the driver or false, if no data does exist
*/
function getSecuredData($id)
{
$secured_data = $this->getData($id);
if ($secured_data === false || !is_array($secured_data)) {
return false; // return false because data doesn't exist
} else {
foreach ($secured_data as $field => $value) {
if (in_array($field, $this->_metaFields)) {
// let this field pass because it is internally used
// [TODO] we should make some checks on the fields...
// [TODO] maybe we should implement a getMetadata() method that handles internal fields
$secured_data[$field] = htmlentities($value, ENT_QUOTES); // secure html
} elseif ($this->checkField($field, 'validate', $value)) {
if ($this->checkField($field, 'html')) {
$secured_data[$field] = $value; // return as is
} else {
$secured_data[$field] = htmlentities($value, ENT_QUOTES); // secure html
}
} else {
// We drop a die here since such data should NEVER have entered the backend.
// Input errors in fields in edit view are supposed to handle errors themself, if we
// encounter a error here we can do nothing. Okay, maybe we can just
// return a empty value for that field, but in this case the error
// will never be recognized.
die('<b>'.get_class($this).' getSecuredData() SECURITY ERROR:</b> The validation for format of "'.$field.'" failed!');
}
}
if (count($secured_data) == 0) {
$false = false;
return $false;
} else {
return $secured_data;
}
}
}
/**
* Get and secure data, run post processor on driver
*
* This returns the secured data of the driver and runs a hook on the specific driver,
* so it can assign meta data or do some post processing
*
* @param int $id ID of the event
* @return array|false Array of all data (fields + meta) or false if no event exists
*/
function getAllData($id)
{
// Get secured data
$secured_data = $this->getSecuredData($id);
if (!$secured_data) {
$secured_data = false;
} else {
// Run post processor on driver
$secured_data = $this->_getDataPostprocessor($secured_data, $id);
}
return $secured_data;
}
/**
* Check if the extending class is compatible to the core
*
* Called by the core to check if this driver has everything it needs and dies if not
*
* @access private
*/
function _checkDriver()
{
$defined_methods = get_class_methods(get_class($this));
// PHP4/PHP5 compliance: convert defined methods to lowercase
foreach ($defined_methods as $key => $method) {
$defined_methods[$key] = strtolower($method);
}
foreach ($this->_needed_methods as $method) {
if ( !in_array(strtolower($method), $defined_methods) ) {
die('<b>LCC driver loading error:</b> Your driver "' . get_class($this) . '" is missing the method "' . $method . '" !');
}
}
// checks on class variables
$defined_vars = get_class_vars(get_class($this));
if ( !is_array($defined_vars['config']) ) {
die('<b>LCC driver loading error:</b> Your driver "' . get_class($this) . '" is missing its default configuration array "$config" !');
}
// For upward compatibility: Call custom check hook on driver
// $this->_checkDriverExtension();
}
}
?>