Implemented MySQL database support

Former-commit-id: 4c96328526
This commit is contained in:
Jérôme Schneider 2012-08-06 23:08:04 +02:00
parent b73dfe3624
commit 88859f3347
20 changed files with 523 additions and 98 deletions

View file

@ -61,7 +61,7 @@ class Tools {
# Asserting DB file exists
if(!file_exists(PROJECT_SQLITE_FILE)) {
throw new \Exception("DB file does not exist. To create it, please copy 'Core/Resources/db.empty.sqlite' to '" . PROJECT_SQLITE_FILE . "'");
throw new \Exception("DB file does not exist. To create it, please copy 'Core/Resources/Db/db.empty.sqlite' to '" . PROJECT_SQLITE_FILE . "'");
}
# Asserting DB file is readable

View file

@ -202,7 +202,7 @@ abstract class Config extends \Flake\Core\Model\NoDb {
asort($aNewConfig);
asort($aWrittenConfig);
if($aNewConfig != $aWrittenConfig) {
throw new \Exception("New config does not correspond to expected config. Aborting, nothing has been changed.");
}

View file

@ -50,6 +50,21 @@ class System extends \Baikal\Model\Config {
"PROJECT_SQLITE_FILE" => array(
"type" => "litteral",
),
"PROJECT_DB_MYSQL" => array(
"type" => "boolean",
),
"PROJECT_DB_MYSQL_HOST" => array(
"type" => "string",
),
"PROJECT_DB_MYSQL_DBNAME" => array(
"type" => "string",
),
"PROJECT_DB_MYSQL_USERNAME" => array(
"type" => "string",
),
"PROJECT_DB_MYSQL_PASSWORD" => array(
"type" => "string",
),
);
protected $aData = array(
@ -60,6 +75,11 @@ class System extends \Baikal\Model\Config {
"BAIKAL_STANDALONE_ALLOWED" => "",
"BAIKAL_STANDALONE_PORT" => "",
"PROJECT_SQLITE_FILE" => "",
"PROJECT_DB_MYSQL" => "",
"PROJECT_DB_MYSQL_HOST" => "",
"PROJECT_DB_MYSQL_DBNAME" => "",
"PROJECT_DB_MYSQL_USERNAME" => "",
"PROJECT_DB_MYSQL_PASSWORD" => "",
);
public function formMorphologyForThisModelInstance() {
@ -130,7 +150,35 @@ class System extends \Baikal\Model\Config {
"inputclass" => "input-xxlarge",
"help" => "The absolute server path to the SQLite file",
)));
$oMorpho->add(new \Formal\Element\Checkbox(array(
"prop" => "PROJECT_DB_MYSQL",
"label" => "Use MySQL",
"help" => "If checked, Baïkal will use MySQL instead of SQLite.",
"refreshonchange" => TRUE,
)));
$oMorpho->add(new \Formal\Element\Text(array(
"prop" => "PROJECT_DB_MYSQL_HOST",
"label" => "MySQL host",
"help" => "Host ip or name, including ':portnumber' if port is not the default one (3306)"
)));
$oMorpho->add(new \Formal\Element\Text(array(
"prop" => "PROJECT_DB_MYSQL_DBNAME",
"label" => "MySQL database name",
)));
$oMorpho->add(new \Formal\Element\Text(array(
"prop" => "PROJECT_DB_MYSQL_USERNAME",
"label" => "MySQL username",
)));
$oMorpho->add(new \Formal\Element\Password(array(
"prop" => "PROJECT_DB_MYSQL_PASSWORD",
"label" => "MySQL password",
)));
return $oMorpho;
}

View file

@ -201,6 +201,21 @@ define("BAIKAL_CAL_BASEURI", PROJECT_BASEURI . "cal.php/");
# Define path to Baïkal Database SQLite file
define("PROJECT_SQLITE_FILE", PROJECT_PATH_SPECIFIC . "db/db.sqlite");
# Mysql > Use mysql instead of SQLite ?
define("PROJECT_DB_MYSQL", FALSE);
# MySQL > Host, including ':portnumber' if port is not the default one (3306)
define("PROJECT_DB_MYSQL_HOST", "");
# MySQL > Database name
define("PROJECT_DB_MYSQL_DBNAME", "");
# MySQL > Username
define("PROJECT_DB_MYSQL_USERNAME", "");
# MySQL > Password
define("PROJECT_DB_MYSQL_PASSWORD", "");
CODE;
$sCode = trim($sCode);
return $sCode;

View file

@ -37,7 +37,9 @@ class System extends \Flake\Core\Controller {
}
$this->oForm = $this->oModel->formForThisModelInstance(array(
"close" => FALSE
"close" => FALSE,
"hook.morphology" => array($this, "morphologyHook"),
"hook.validation" => array($this, "validationHook"),
));
if($this->oForm->submitted()) {
@ -58,4 +60,52 @@ class System extends \Flake\Core\Controller {
return $oView->render();
}
public function morphologyHook(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) {
if($oForm->submitted()) {
$bMySQL = (intval($oForm->postValue("PROJECT_DB_MYSQL")) === 1);
} else {
$bMySQL = PROJECT_DB_MYSQL;
}
if($bMySQL === TRUE) {
$oMorpho->remove("PROJECT_SQLITE_FILE");
} else {
$oMorpho->remove("PROJECT_DB_MYSQL_HOST");
$oMorpho->remove("PROJECT_DB_MYSQL_DBNAME");
$oMorpho->remove("PROJECT_DB_MYSQL_USERNAME");
$oMorpho->remove("PROJECT_DB_MYSQL_PASSWORD");
}
}
public function validationHook(\Formal\Form $oForm, \Formal\Form\Morphology $oMorpho) {
if(intval($oForm->modelInstance()->get("PROJECT_DB_MYSQL")) === 1) {
# We have to check the MySQL connection
$sHost = $oForm->modelInstance()->get("PROJECT_DB_MYSQL_HOST");
$sDbName = $oForm->modelInstance()->get("PROJECT_DB_MYSQL_DBNAME");
$sUsername = $oForm->modelInstance()->get("PROJECT_DB_MYSQL_USERNAME");
$sPassword = $oForm->modelInstance()->get("PROJECT_DB_MYSQL_PASSWORD");
try {
$oDB = new \Flake\Core\Database\Mysql(
$sHost,
$sDbName,
$sUsername,
$sPassword
);
unset($oDB);
} catch(\Exception $e) {
$sMessage = "<strong>MySQL error:</strong> " . $e->getMessage();
$sMessage .= "<br /><strong>Nothing has been saved</strong>";
$oForm->declareError($oMorpho->element("PROJECT_DB_MYSQL_HOST"), $sMessage);
$oForm->declareError($oMorpho->element("PROJECT_DB_MYSQL_DBNAME"));
$oForm->declareError($oMorpho->element("PROJECT_DB_MYSQL_USERNAME"));
$oForm->declareError($oMorpho->element("PROJECT_DB_MYSQL_PASSWORD"));
}
}
}
}

View file

@ -51,13 +51,14 @@ class Collection extends \Flake\Core\FLObject implements \Iterator {
return ($key !== NULL && $key !== FALSE);
}
public function getForKey($sKey) {
public function &getForKey($sKey) {
$aKeys = $this->keys();
if(!in_array($sKey, $aKeys)) {
throw new \Exception("\Flake\Core\Collection->getForKey(): key '" . $sKey . "' not found in Collection");
}
return $this->aCollection[$sKey];
$oRes = $this->aCollection[$sKey];
return $oRes;
}
public function &each() {
@ -155,6 +156,16 @@ class Collection extends \Flake\Core\FLObject implements \Iterator {
return $oNewColl;
}
public function remove($sKey) {
$aKeys = $this->keys();
if(!in_array($sKey, $aKeys)) {
throw new \Exception("\Flake\Core\Collection->remove(): key '" . $sKey . "' not found in Collection");
}
unset($this->aCollection[$sKey]);
$this->aCollection = array_values($this->aCollection);
}
public function &__call($sName, $aArguments) {
if(
strlen($sName) > 7 &&

View file

@ -30,16 +30,16 @@ abstract class Database extends \Flake\Core\FLObject {
/* common stuff */
function messageAndDie($sMessage) {
protected function messageAndDie($sMessage) {
$sError = "<h2>" . get_class($this) . ": " . $sMessage . "</h2>";
die($sError);
}
function exec_INSERTquery($table,$fields_values,$no_quote_fields=FALSE) {
public function exec_INSERTquery($table,$fields_values,$no_quote_fields=FALSE) {
return $this->query($this->INSERTquery($table,$fields_values,$no_quote_fields));
}
function INSERTquery($table,$fields_values,$no_quote_fields=FALSE) {
public function INSERTquery($table,$fields_values,$no_quote_fields=FALSE) {
// Table and fieldnames should be "SQL-injection-safe" when supplied to this function (contrary to values in the arrays which may be insecure).
if (is_array($fields_values) && count($fields_values)) {
@ -63,11 +63,11 @@ abstract class Database extends \Flake\Core\FLObject {
}
}
function exec_UPDATEquery($table,$where,$fields_values,$no_quote_fields=FALSE) {
public function exec_UPDATEquery($table,$where,$fields_values,$no_quote_fields=FALSE) {
return $this->query($this->UPDATEquery($table,$where,$fields_values,$no_quote_fields));
}
function UPDATEquery($table,$where,$fields_values,$no_quote_fields=FALSE) {
public function UPDATEquery($table,$where,$fields_values,$no_quote_fields=FALSE) {
// Table and fieldnames should be "SQL-injection-safe" when supplied to this function (contrary to values in the arrays which may be insecure).
if (is_string($where)) {
@ -99,11 +99,11 @@ abstract class Database extends \Flake\Core\FLObject {
}
}
function exec_DELETEquery($table,$where) {
public function exec_DELETEquery($table,$where) {
return $this->query($this->DELETEquery($table,$where));
}
function DELETEquery($table,$where) {
public function DELETEquery($table,$where) {
if (is_string($where)) {
// Table and fieldnames should be "SQL-injection-safe" when supplied to this function
@ -119,11 +119,11 @@ abstract class Database extends \Flake\Core\FLObject {
}
}
function exec_SELECTquery($select_fields,$from_table,$where_clause,$groupBy='',$orderBy='',$limit='') {
public function exec_SELECTquery($select_fields,$from_table,$where_clause,$groupBy='',$orderBy='',$limit='') {
return $this->query($this->SELECTquery($select_fields,$from_table,$where_clause,$groupBy,$orderBy,$limit));
}
function SELECTquery($select_fields,$from_table,$where_clause,$groupBy='',$orderBy='',$limit='') {
public function SELECTquery($select_fields,$from_table,$where_clause,$groupBy='',$orderBy='',$limit='') {
// Table and fieldnames should be "SQL-injection-safe" when supplied to this function
// Build basic query:
@ -154,11 +154,11 @@ abstract class Database extends \Flake\Core\FLObject {
return $query;
}
function fullQuote($str, $table) {
public function fullQuote($str, $table) {
return '\''.$this->quote($str, $table).'\'';
}
function fullQuoteArray($arr, $table, $noQuote=FALSE) {
public function fullQuoteArray($arr, $table, $noQuote=FALSE) {
if (is_string($noQuote)) {
$noQuote = explode(',',$noQuote);
} elseif (!is_array($noQuote)) { // sanity check
@ -173,11 +173,34 @@ abstract class Database extends \Flake\Core\FLObject {
return $arr;
}
/* fonctions abstraites */
/* Should be abstract, but we provide a body anyway as PDO abstracts these methods for us */
abstract function query($sSql);
public function query($sSql) {
if(($stmt = $this->oDb->query($sSql)) === FALSE) {
$sMessage = print_r($this->oDb->errorInfo(), TRUE);
throw new \Exception("SQL ERROR in: '" . $sSql . "'; Message: " . $sMessage);
}
return new \Flake\Core\Database\Statement($stmt);
}
abstract function lastInsertId();
public function lastInsertId() {
return $this->oDb->lastInsertId();
}
abstract function quote($str);
public function quote($str) {
return substr($this->oDb->quote($str), 1, -1); # stripping first and last quote
}
public function getPDO() {
return $this->oDb;
}
public function close() {
$this->oDb = null;
}
public function __destruct() {
$this->close();
}
}

View file

@ -0,0 +1,52 @@
<?php
#################################################################
# Copyright notice
#
# (c) 2012 Jérôme Schneider <mail@jeromeschneider.fr>
# All rights reserved
#
# http://flake.codr.fr
#
# This script is part of the Flake project. The Flake
# project 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.
#
# The GNU General Public License can be found at
# http://www.gnu.org/copyleft/gpl.html.
#
# This script 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.
#
# This copyright notice MUST APPEAR in all copies of the script!
#################################################################
namespace Flake\Core\Database;
class Mysql extends \Flake\Core\Database {
protected $oDb = FALSE; // current DB link
protected $debugOutput = FALSE;
protected $store_lastBuiltQuery = TRUE;
protected $debug_lastBuiltQuery = "";
protected $sHost = "";
protected $sDbName = "";
protected $sUsername = "";
protected $sPassword = "";
public function __construct($sHost, $sDbName, $sUsername, $sPassword) {
$this->sHost = $sHost;
$this->sDbName = $sDbName;
$this->sUsername = $sUsername;
$this->sPassword = $sPassword;
$this->oDb = new \PDO(
'mysql:host=' . $this->sHost . ';dbname=' . $this->sDbName,
$this->sUsername,
$this->sPassword
);
}
}

View file

@ -37,44 +37,5 @@ class Sqlite extends \Flake\Core\Database {
public function __construct($sDbPath) {
$this->sDbPath = $sDbPath;
$this->oDb = new \PDO('sqlite:' . $this->sDbPath);
# $this->oDb->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
}
public function query($sSql) {
if(($stmt = $this->oDb->query($sSql)) === FALSE) {
$sMessage = print_r($this->oDb->errorInfo(), TRUE);
throw new \Exception("SQL ERROR in: '" . $sSql . "'; Message: " . $sMessage);
}
return new \Flake\Core\Database\SqliteStatement($stmt);
}
public function lastInsertId() {
return $this->oDb->lastInsertId();
}
public function quote($str) {
return substr($this->oDb->quote($str), 1, -1); # stripping first and last quote
}
public function getPDO() {
return $this->oDb;
}
}
Class SqliteStatement {
protected $stmt = null;
public function __construct($stmt) {
$this->stmt = $stmt;
}
public function fetch() {
if($this->stmt !== FALSE) {
return $this->stmt->fetch(\PDO::FETCH_ASSOC, \PDO::FETCH_ORI_FIRST);
}
return FALSE;
}
}

View file

@ -0,0 +1,43 @@
<?php
#################################################################
# Copyright notice
#
# (c) 2012 Jérôme Schneider <mail@jeromeschneider.fr>
# All rights reserved
#
# http://flake.codr.fr
#
# This script is part of the Flake project. The Flake
# project 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.
#
# The GNU General Public License can be found at
# http://www.gnu.org/copyleft/gpl.html.
#
# This script 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.
#
# This copyright notice MUST APPEAR in all copies of the script!
#################################################################
namespace Flake\Core\Database;
class Statement extends \Flake\Core\FLObject {
protected $stmt = null;
public function __construct($stmt) {
$this->stmt = $stmt;
}
public function fetch() {
if($this->stmt !== FALSE) {
return $this->stmt->fetch(\PDO::FETCH_ASSOC, \PDO::FETCH_ORI_FIRST);
}
return FALSE;
}
}

View file

@ -166,6 +166,14 @@ class Framework extends \Flake\Core\Framework {
protected static function initDb() {
if(defined("PROJECT_DB_MYSQL") && PROJECT_DB_MYSQL === TRUE) {
self::initDbMysql();
} else {
self::initDbSqlite();
}
}
protected static function initDbSqlite() {
# Asserting DB filepath is set
if(!defined("PROJECT_SQLITE_FILE")) {
return;
@ -173,7 +181,7 @@ class Framework extends \Flake\Core\Framework {
# Asserting DB file exists
if(!file_exists(PROJECT_SQLITE_FILE)) {
die("<h3>DB file does not exist. To create it, please copy '<span style='font-family: monospace; background: yellow;'>Core/Resources/db.empty.sqlite</span>' to '<span style='font-family: monospace;background: yellow;'>" . PROJECT_SQLITE_FILE . "</span>'</h3>");
die("<h3>DB file does not exist. To create it, please copy '<span style='font-family: monospace; background: yellow;'>Core/Resources/Db/db.empty.sqlite</span>' to '<span style='font-family: monospace;background: yellow;'>" . PROJECT_SQLITE_FILE . "</span>'</h3>");
}
# Asserting DB file is readable
@ -190,4 +198,37 @@ class Framework extends \Flake\Core\Framework {
$GLOBALS["DB"] = new \Flake\Core\Database\Sqlite(PROJECT_SQLITE_FILE);
}
}
protected static function initDbMysql() {
if(!defined("PROJECT_DB_MYSQL_HOST")) {
die("<h3>The constant PROJECT_DB_MYSQL_HOST, containing the MySQL host name, is not set.<br />You should set it in Specific/config.system.php</h3>");
}
if(!defined("PROJECT_DB_MYSQL_DBNAME")) {
die("<h3>The constant PROJECT_DB_MYSQL_DBNAME, containing the MySQL database name, is not set.<br />You should set it in Specific/config.system.php</h3>");
}
if(!defined("PROJECT_DB_MYSQL_USERNAME")) {
die("<h3>The constant PROJECT_DB_MYSQL_USERNAME, containing the MySQL database username, is not set.<br />You should set it in Specific/config.system.php</h3>");
}
if(!defined("PROJECT_DB_MYSQL_PASSWORD")) {
die("<h3>The constant PROJECT_DB_MYSQL_PASSWORD, containing the MySQL database password, is not set.<br />You should set it in Specific/config.system.php</h3>");
}
try {
$GLOBALS["DB"] = new \Flake\Core\Database\Mysql(
PROJECT_DB_MYSQL_HOST,
PROJECT_DB_MYSQL_DBNAME,
PROJECT_DB_MYSQL_USERNAME,
PROJECT_DB_MYSQL_PASSWORD
);
} catch(\Exception $e) {
die("<h3>Baïkal was not able to establish a connexion to the configured MySQL database (as configured in Specific/config.system.php).</h3>");
}
# We now setup the connexion to use UTF8
$GLOBALS["DB"]->query("SET NAMES UTF8");
}
}

View file

@ -105,12 +105,29 @@ class Tools extends \Flake\Core\FLObject {
public static function debug($var="",$brOrHeader=0) {
if($brOrHeader === 0) {
$trail = debug_backtrace();
$trail = array_reverse($trail);
array_pop($trail); // la ligne d'appel à debug
array_pop($trail); // la ligne d'appel à debug
$aLastNode = array_pop($trail); // l'appel qui nous intéresse
$brOrHeader = @strval($aLastNode['class']).@strval($aLastNode['type']).@strval($aLastNode['function']);
try {
$trail = debug_backtrace();
$trail = array_reverse($trail);
array_pop($trail); // la ligne d'appel à debug
array_pop($trail); // la ligne d'appel à debug
$aLastNode = array_pop($trail); // l'appel qui nous intéresse
if(array_key_exists("class", $aLastNode)) {
$sClass = @strval($aLastNode["class"]);
} else {
$sClass = "";
}
if(array_key_exists("type", $aLastNode)) {
$sType = @strval($aLastNode["type"]);
} else {
$sType = "";
}
$brOrHeader = $sClass.$sType.@strval($aLastNode['function']);
} catch(\Exception $e) {
$brOrHeader = "Undetermined context";
}
}
if ($brOrHeader) {

View file

@ -37,6 +37,7 @@ abstract class Element {
"placeholder" => "",
"help" => "",
"popover" => "",
"refreshonchange" => FALSE,
);
protected $sValue = "";
@ -81,5 +82,19 @@ abstract class Element {
return get_class($this) . "<" . $this->option("label") . ">";
}
public function renderWitness() {
return '<input type="hidden" name="witness[' . $this->option("prop") . ']" value="1" />';
}
public function posted() {
$aPost = \Flake\Util\Tools::POST("witness");
if(is_array($aPost)) {
$sProp = $this->option("prop");
return (array_key_exists($sProp, $aPost)) && (intval($aPost[$sProp]) === 1);
}
return FALSE;
}
public abstract function render();
}

View file

@ -37,14 +37,15 @@ class Checkbox extends \Formal\Element {
$disabled = "";
$inputclass = "";
$groupclass = "";
$onchange = "";
$helpblock = "";
$popover = "";
$value = $this->value();
$checked = ($value === TRUE ? " checked=\"checked\" " : "");
$label = $this->option("label");
$prop = $this->option("prop");
$helpblock = "";
$popover = "";
if($this->option("readonly") === TRUE) {
$inputclass .= " disabled";
@ -65,15 +66,19 @@ class Checkbox extends \Formal\Element {
$popover .= " data-content=\"" . htmlspecialchars($aPopover["content"]) . "\" ";
}
if($this->option("refreshonchange") === TRUE) {
$onchange = " onchange=\"document.getElementsByTagName('form')[0].submit();\" ";
}
$sHtml =<<<HTML
<div class="control-group{$groupclass}">
<label class="control-label" for="{$prop}">{$label}</label>
<div class="controls">
<input type="checkbox" class="input-xlarge{$inputclass}" id="{$prop}" name="{$prop}" value="1"{$checked}{$disabled}{$popover}/>
<input type="checkbox" class="input-xlarge{$inputclass}" id="{$prop}" name="data[{$prop}]" value="1"{$checked}{$disabled}{$popover}{$onchange}/>
{$helpblock}
</div>
</div>
HTML;
return $sHtml;
return $sHtml . $this->renderWitness();
}
}

View file

@ -92,13 +92,13 @@ class Listbox extends \Formal\Element {
<div class="control-group{$groupclass}">
<label class="control-label" for="{$prop}">{$label}</label>
<div class="controls">
<select class="{$inputclass}" id="{$prop}" name="{$prop}"{$disabled}{$popover}>
<select class="{$inputclass}" id="{$prop}" name="data[{$prop}]"{$disabled}{$popover}>
{$sRenderedOptions}
</select>
{$helpblock}
</div>
</div>
HTML;
return $sHtml;
return $sHtml . $this->renderWitness();
}
}

View file

@ -91,11 +91,11 @@ class Text extends \Formal\Element {
<div class="control-group{$groupclass}">
<label class="control-label" for="{$prop}">{$label}</label>
<div class="controls">
<input type="{$sInputType}" class="{$inputclass}" id="{$prop}" name="{$prop}" value="{$clientvalue}"{$disabled}{$placeholder}{$popover}/>
<input type="{$sInputType}" class="{$inputclass}" id="{$prop}" name="data[{$prop}]" value="{$clientvalue}"{$disabled}{$placeholder}{$popover}/>
{$helpblock}
</div>
</div>
HTML;
return $sHtml;
return $sHtml . $this->renderWitness();
}
}

View file

@ -33,6 +33,8 @@ class Form {
"action" => "",
"close" => TRUE,
"closeurl" => "",
"hook.validation" => FALSE,
"hook.morphology" => FALSE,
);
protected $oModelInstance = null;
protected $oElements = null;
@ -42,6 +44,8 @@ class Form {
protected $sDisplayTitle = ""; # Displayed form title; generated in setModelInstance()
protected $sDisplayMessage = ""; # Displayed confirm message; generated in execute()
protected $oMorpho = null;
public function __construct($sModelClass, $aOptions = array()) {
$this->sModelClass = $sModelClass;
$this->aOptions = array_merge($this->aOptions, $aOptions);
@ -66,6 +70,21 @@ class Form {
return $aOptions;
}
public function getMorpho() {
if(!is_null($this->oMorpho)) {
return $this->oMorpho;
}
$this->oMorpho = $this->modelInstance()->formMorphologyForThisModelInstance();
# Calling validation hook if defined
if(($aHook = $this->option("hook.morphology")) !== FALSE) {
call_user_func($aHook, $this, $this->oMorpho);
}
return $this->oMorpho;
}
public function setModelInstance($oModelInstance) {
if(!\Flake\Util\Tools::is_a($oModelInstance, $this->sModelClass)) {
throw new \Exception("\Formal\Core->setModelInstance(): Given instance is not of class '" . $this->sModelClass . "'");
@ -104,7 +123,7 @@ class Form {
public function execute() {
# Obtaining morphology from model object
$oMorpho = $this->modelInstance()->formMorphologyForThisModelInstance();
$oMorpho = $this->getMorpho();
$this->aErrors = array();
$oMorpho->elements()->reset();
@ -117,15 +136,24 @@ class Form {
$sPropName = $oElement->option("prop");
# posted value is fetched, then passes to element before persistance
$sPostValue = $this->postValue($sPropName);
$oElement->setValue($sPostValue);
$sValue = $oElement->value();
$this->modelInstance()->set(
$sPropName,
$sValue
);
if($oElement->posted()) {
$sPostValue = $this->postValue($sPropName);
$oElement->setValue($sPostValue);
$sValue = $oElement->value();
$this->modelInstance()->set(
$sPropName,
$sValue
);
} else {
$oElement->setValue(
$this->modelInstance()->get(
$sPropName
)
);
}
}
$oMorpho->elements()->reset();
@ -162,18 +190,17 @@ class Form {
}
if($mValid !== TRUE) {
$this->aErrors[] = array(
"element" => $oElement,
"message" => $mValid,
);
$oElement->setOption("error", TRUE);
$this->declareError($oElement, $mValid);
break; # one error per element per submit
}
}
}
# Calling validation hook if defined
if(($aHook = $this->option("hook.validation")) !== FALSE) {
call_user_func($aHook, $this, $oMorpho);
}
if(empty($this->aErrors)) {
# Model object is persisted
@ -198,7 +225,7 @@ class Form {
$this->modelInstance()->persist();
if($bWasFloating === FALSE) {
# Title is generated now, as submitted data might have changed the model instance label
$this->sDisplayTitle = "Editing " . $this->modelInstance()->humanName() . "<strong><i class=" . $this->modelInstance()->mediumicon() . "></i><strong>" . $this->modelInstance()->label() . "</strong>";
$this->sDisplayTitle = "Editing " . $this->modelInstance()->humanName() . "<i class=" . $this->modelInstance()->mediumicon() . "></i><strong>" . $this->modelInstance()->label() . "</strong>";
}
$this->bPersisted = TRUE;
} else {
@ -206,6 +233,16 @@ class Form {
}
}
# public, as it may be called from a hook
public function declareError(\Formal\Element $oElement, $sMessage = "") {
$this->aErrors[] = array(
"element" => $oElement,
"message" => $sMessage,
);
$oElement->setOption("error", TRUE);
}
public function persisted() {
if($this->submitted()) {
if(is_null($this->bPersisted)) {
@ -279,13 +316,19 @@ class Form {
}
public function postValue($sPropName) {
return \Flake\Util\Tools::POST($sPropName);
$aData = \Flake\Util\Tools::POST("data");
if(is_array($aData) && array_key_exists($sPropName, $aData)) {
return $aData[$sPropName];
}
return "";
}
public function render() {
$aHtml = array();
$oMorpho = $this->modelInstance()->formMorphologyForThisModelInstance();
$oMorpho = $this->getMorpho();
$oMorpho->elements()->reset();
foreach($oMorpho->elements() as $oElement) {
@ -320,6 +363,10 @@ class Form {
$aMessages = array();
reset($this->aErrors);
foreach($this->aErrors as $aError) {
if(trim($aError["message"]) === "") {
continue;
}
$aMessages[] = $aError["message"];
}

View file

@ -38,18 +38,35 @@ class Morphology {
$this->oElements->push($oElement);
}
public function element($sPropName) {
protected function keyForPropName($sPropName) {
$aKeys = $this->oElements->keys();
reset($aKeys);
foreach($aKeys as $sKey) {
$oElement = $this->oElements->getForKey($sKey);
if($oElement->option("prop") === $sPropName) {
return $oElement;
return $sKey;
}
}
throw new \Exception("\Formal\Form\Morphology->element(): Element prop='" . $sPropName . "' not found");
return FALSE;
}
public function &element($sPropName) {
if(($sKey = $this->keyForPropName($sPropName)) === FALSE) {
throw new \Exception("\Formal\Form\Morphology->element(): Element prop='" . $sPropName . "' not found");
}
$oElement = $this->oElements->getForKey($sKey);
return $oElement;
}
public function remove($sPropName) {
if(($sKey = $this->keyForPropName($sPropName)) === FALSE) {
throw new \Exception("\Formal\Form\Morphology->element(): Element prop='" . $sPropName . "' not found");
}
$this->oElements->remove($sKey);
}
public function elements() {

View file

@ -0,0 +1,80 @@
#
# This is the empty database schema for Baïkal
# Corresponds to the MySQL Schema definition of project SabreDAV 1.6.4
# http://code.google.com/p/sabredav/
#
CREATE TABLE users (
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50),
digesta1 VARCHAR(32),
UNIQUE(username)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE principals (
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
uri VARCHAR(200) NOT NULL,
email VARCHAR(80),
displayname VARCHAR(80),
vcardurl VARCHAR(80),
UNIQUE(uri)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE groupmembers (
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
principal_id INTEGER UNSIGNED NOT NULL,
member_id INTEGER UNSIGNED NOT NULL,
UNIQUE(principal_id, member_id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE locks (
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
owner VARCHAR(100),
timeout INTEGER UNSIGNED,
created INTEGER,
token VARCHAR(100),
scope TINYINT,
depth TINYINT,
uri text
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE calendarobjects (
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
calendardata MEDIUMBLOB,
uri VARCHAR(200),
calendarid INTEGER UNSIGNED NOT NULL,
lastmodified INT(11),
UNIQUE(calendarid, uri)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE calendars (
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
principaluri VARCHAR(100),
displayname VARCHAR(100),
uri VARCHAR(200),
ctag INTEGER UNSIGNED NOT NULL DEFAULT '0',
description TEXT,
calendarorder INTEGER UNSIGNED NOT NULL DEFAULT '0',
calendarcolor VARCHAR(10),
timezone TEXT,
components VARCHAR(20),
UNIQUE(principaluri, uri)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE addressbooks (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
principaluri VARCHAR(255),
displayname VARCHAR(255),
uri VARCHAR(200),
description TEXT,
ctag INT(11) UNSIGNED NOT NULL DEFAULT '1',
UNIQUE(principaluri, uri)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE cards (
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
addressbookid INT(11) UNSIGNED NOT NULL,
carddata MEDIUMBLOB,
uri VARCHAR(200),
lastmodified INT(11) UNSIGNED
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;