totalprice="amountInCent" paid="amountInCent" status="orderstate">
<Ticket event="eventid" id="ticketid" price="priceInCent" status="ticketstate" />
<Voucher id="voucherid" price="priceInCent" value="remainingValueInCent" />
+ <Shipping price="priceInCent" type="shippingtypeID">Shipping Comment</Shipping>
<DeliveryAddress>deliver address</DeliveryAddress>
<Comment>comment...</Comment>
</Order>
<table frame="1" border="1">
<tr><td><b>Item</b></td><td><b>Description</b></td><td><b>Occurrence</b></td></tr>
-
<tr/>
<tr><td>Order</td><td>Container for one single order or sale</td><td>1</td></tr>
<tr><td> id</td><td>ID of the order, if already known.</td><td>0-1</td></tr>
<tr><td> totalprice</td><td>Total accumulated price of the order. Automatically filled in.</td><td>0-1</td></tr>
<tr><td> paid</td><td>Amount that has already been paid. Automatically filled in.</td><td>0-1</td></tr>
<tr><td> status</td><td>Current status of the order. Automatically filled in. See table below.</td><td>0-1</td></tr>
-
<tr/>
<tr><td>Ticket(*)</td><td>data about a single ticket bought in this order</td><td>0-unlimited</td></tr>
+<tr><td> id</td><td>ID of the ticket</td><td>1</td></tr>
+<tr><td> event</td><td>ID of the event this ticket refers to.</td><td>1</td></tr>
+<tr><td> price</td><td>Price of the ticket</td><td>1</td></tr>
+<tr><td> status</td><td>Status of the ticket (see below)</td><td>1</td></tr>
+<tr/>
+<tr><td>Voucher(*)</td><td>data about a single voucher bought in this order, vouchers that are used to pay for an order are not stored</td><td>0-unlimited</td></tr>
+<tr><td> id</td><td>ID of the voucher</td><td>1</td></tr>
+<tr><td> price</td><td>Price of the voucher (adds to price of the order)</td><td>1</td></tr>
+<tr><td> value</td><td>Value (in cent) of the voucher (does not add to the order, but refers to how much the voucher is worth)</td><td>1</td></tr>
+<tr/>
+<tr><td>Shipping</td><td>information about the kind of shipping used and how much it costs; if text is used it is a copy of the text from the shipping table information - it cannot be changed here</td><td>0-1</td></tr>
+<tr><td> price</td><td>Price of the shipping option</td><td>1</td></tr>
+<tr><td> type</td><td>shipping type ID</td><td>1</td></tr>
+<tr/>
+<tr><td>DeliveryAddress</td><td>the address to send the stuff to, if not present or empty the address of the customer is used</td><td>0-1</td></tr>
+<tr/>
+<tr><td>Comment</td><td>comment entered into the order by customer or seller</td><td>0-1</td></tr>
</table>
(*)At least one of Ticket or Voucher must be used.<p>
<tr><td>Order</td><td>customer</td><td>id(1), customer, seller(3), ordertime(1,4), totalprice(5), paid(1), status(6), senttime(2,4)</td></tr>
<tr><td>Ticket</td><td>event, price(7)</td><td>event, id(1), price, status(6)</td></tr>
<tr><td>Voucher</td><td>value, price(7)</td><td>value, price, id(1)</td></tr>
-<tr><td>DeliveryAddress</td><td>-</td><td>-</td></tr>
-<tr><td>Comment</td><td>-</td><td>-</td></tr>
+<tr><td>Shipping</td><td>type(8), price(9)</td><td>type(10), price, [text](10)</td></tr>
+<tr><td>DeliveryAddress</td><td>[text]</td><td>[text]</td></tr>
+<tr><td>Comment</td><td>[text]</td><td>[text]</td></tr>
</table>
(1)field is optional for checks and has no meaning there, it is recommended the server returns a dummy ID for checks<br/>
(2)the field only exists for sales<br/>
(4)time stamps are automatically filled in with the current server time<br/>
(5)the total price is the accumulated price of all tickets and vouchers in the order<br/>
(6)the status fields have special meanings for checks, see below<br/>
-(7)the price field will only be honoured in the request, if the user also has the privilege to use the changeticketprice transaction<p>
+(7)the price field will only be honoured in the request, if the user also has the privilege to use the changeticketprice transaction<br/>
+(8)the shipping types that are allowed depend on whether the user has the _anyshipping privilege<br/>
+(9)price is optional, if set it is ignored if the user does not have the _repriceshipping privilege<br/>
+(10)the type in the response may be -1 if an error occurred in this case the text contains the error message, otherwise it contains a copy of the shipping option text<p>
Order status for checks:
<table frame="1" border="1">
The response body is empty or optionally contains an error message.
+<h3>Changing the shipping option on an order</h3>
+
+The <tt>orderchangeshipping</tt> transaction can be used to change the shipping information on an order. The request looks like:
+
+<pre>
+<OrderChangeShipping orderid="orderId" type="newShippingType" price="newPriceInCent"/>
+</pre>
+
+If <tt>type</tt> is not present, the price is taken from the new shipping type. If <tt>type</tt> is not present, it is not changed. If <tt>type</tt> is empty it is reset to none (NULL) in the Database. If a price other than 0 is used, the type in the database must be present (!= NULL).<p>
+
+The response contains the updated order object or just an error message.
+
+<h3>Creating/Changing Shipping Options</h3>
+
+The <tt>setshipping</tt> creates or changes a shipping option. The request looks like:
+
+<pre>
+<ShippingOption id="shippingOptionID" price="priceInCent" web="0|1" anyUser="0|1">
+ Text that describes the option
+</ShippingOption>
+</pre>
+
+If the <tt>id</tt> attribute is missing the server creates a new option. Otherwise it changes an existing one. The <tt>web</tt> attribute tells whether the option is available to customers using the web interface. The <tt>anyUser</tt> attribute tells whether the option can be used by anyone who can create an order/sale (=1) or only by users with the "_anyshipping" privilege (=0).<p>
+
+The response is empty or contains an error message.
+
+<h3>Listing Shipping Options</h3>
+
+The <tt>getshipping</tt> transaction can be used to return all shipping options. If the user has the "setshipping" or "_anyshipping" privilege the list will also contain privileged options. The request is empty. The response looks like:
+
+<pre>
+<ShippingList>
+ <ShippingOption id="shippingOptionID" price="priceInCent" web="0|1" anyUser="0|1">
+ Text that describes the option
+ </ShippingOption>
+ ...
+ ...
+</ShippingList>
+</pre>
+
+<h3>Adding Tickets or Vouchers to an Order</h3>
+
+tbd.
+
+
<!-- ************************************************************************************
************************************************************************************
************************************************************************************ -->
?>
<?
-if($db->canUseDb()){
+$canUseDb=$db->canUseDb();
+if($canUseDb){
?>
-Database exists and is usable.
+Database exists and is usable.<p>
+
+<form action="admin.php" method="GET">
+<input type="submit" value="Show SQL Script to create DB." name="ShowCreateDB"/>
+</form>
+
<?
}else{
?>
Database is not usable. Create?<br>
<form action="admin.php" method="GET">
<input type="submit" value="Yes, Create DB now." name="CreateDB"/>
+<input type="submit" value="Show SQL Script to create DB." name="ShowCreateDB"/>
</form>
<?
- exit();
}
?>
+<?
+if(isset($_GET["ShowCreateDB"])){
+ print("Creating Database...<p>");
+ $db->showCreateDb();
+}
+
+//the stuff below does not work if DB unusable
+if(!$canUseDb)
+ exit();
+?>
<h2>Checking for Admin Users</h2>
}
/**returns the version of the DB layout that is required by this version of Magic Smoke*/
- public function needVersion(){return "00.01";}
+ public function needVersion()
+ {
+ global $dbScheme;
+ return $dbScheme->version();
+ }
/**returns whether the table exists; must be implemented by driver*/
public abstract function haveTable($tablename);
- /**begins a transaction; must be implemented by driver*/
+ /**begins a transaction; must be implemented by driver; use sqlBeginTransaction to create the SQL statement!*/
public abstract function beginTransaction();
- /**ends a transaction successfully; must be implemented by driver; returns true on success*/
+ /**ends a transaction successfully; must be implemented by driver; returns true on success; use sqlCommitTransaction to create the SQL statement!*/
public abstract function commitTransaction();
- /**ends a transaction with a rollback; must be implemented by driver; returns true on success*/
+ /**ends a transaction with a rollback; must be implemented by driver; returns true on success; use sqlRollbackTransaction to create the SQL statement!*/
public abstract function rollbackTransaction();
- /**gets some data from the database; $table is the name of the table, $cols is the list of columns to return or "*" for all, $where is the where clause of the SQL-statement, $orderby may contain additional ORDER BY or GROUP BY clauses; returns array of rows, which are in *_fetch_array format; returns false on error*/
+ /**gets some data from the database; $table is the name of the table, $cols is the list of columns to return or "*" for all, $where is the where clause of the SQL-statement, $orderby may contain additional ORDER BY or GROUP BY clauses; returns array of rows, which are in *_fetch_array format; returns false on error; use sqlSelect to create the SQL statement!*/
public abstract function select($table,$cols,$where,$orderby="");
- /**insert values into a table; returns false on failure, the new primary key if a sequence was set, true otherwise*/
+ /**insert values into a table; returns false on failure, the new primary key if a sequence was set, true otherwise; use sqlInsert to create the SQL statement!*/
public abstract function insert($table,array $values);
- /**update database values; returns how many rows have been changed or false for failure*/
+ /**update database values; returns how many rows have been changed or false for failure; use sqlUpdate to create the SQL statement!*/
public abstract function update($table,array $values,$where);
- /**delete database values; returns the amount of rows deleted or false if an error occurred*/
+ /**delete database values; returns the amount of rows deleted or false if an error occurred; use sqlDelete to create the SQL statement!*/
public abstract function deleteRows($table,$where);
/**creates a table; the argument is an array of the form "col-name" => array("col-type", "flags"...); use sqlCreateTable() etc. to create the actual statement*/
protected function tableName($tname){return $tname;}
/**returns the correct type name for the required abstract data type;
- types that must be understood are: int32 (INTEGER), int64 (LONG INTEGER), seq32 (auto-incrementing int), seq64, string:$length (text up to 255 chars, length is optional, default is 255; VARCHAR($length)), text (unlimited text)*/
+ types that must be understood are: int32 (INTEGER), int64 (LONG INTEGER), seq32 (auto-incrementing int), seq64, bool (boolean), string:$length (text up to 255 chars, length is optional, default is 255; VARCHAR($length)), text (unlimited text)*/
protected function dataType($type)
{
if($type=="int32")return "INTEGER";
if($type=="int64")return "LONG INTEGER";
+ if($type=="bool")return "BOOLEAN";
$tpa=explode(":",$type);
if($tpa[0]=="string"){
if(isset($tpa[1]))
protected function columnFlag($flag,$col)
{
if($flag=="primarykey")return "PRIMARY KEY";
+ if($flag=="null")return "NULL";
if($flag=="notnull")return "NOT NULL";
if($flag=="unique")return "NOT NULL UNIQUE";
if($flag=="index")return "INDEX";
return "DEFAULT NULL";
return "DEFAULT ".$this->escapeString($tpa[1]);
}
+ if($tpa[0]=="defaultbool"){
+ if(count($tpa)<2)
+ return "DEFAULT NULL";
+ return "DEFAULT ".$this->escapeBool($tpa[1]);
+ }
}
- /**creates a SQL92 statement for creating a table*/
+ /**creates a SQL92 statement for creating a table; overwrite this to implement DB specific syntax*/
protected function sqlCreateTable($tablename,$table)
{
$ret="CREATE TABLE ".$this->tableName($tablename)." (";
return $ret;
}
- /**This function can be used to amend the column definitions of a table; if overwritten it must return a string*/
+ /**This function can be used to amend the column definitions of a table; if overwritten it must return a string; overwrite this to implement DB specific syntax*/
protected function createTableExtras($tablename,$table)
{
return "";
}
- /**creates primary key statement for sqlCreateTable*/
+ /**creates primary key statement for sqlCreateTable; overwrite this to implement DB specific syntax*/
protected function sqlCreateTablePrimaryKey(array $cols)
{
$ret="PRIMARY KEY(";
return $ret;
}
- /**creates a SQL92 statement for inserts*/
+ /**creates a SQL92 statement for selects; overwrite this to implement DB specific syntax*/
+ public function sqlSelect($table,$cols,$where,$orderby)
+ {
+ $query="SELECT $cols FROM ".$this->tableName($table);
+ if($where!="")$query.=" WHERE ".$where;
+ if($orderby!="")$query.=" ".$orderby;
+ return $query;
+ }
+
+ /**creates a SQL92 statement for inserts; overwrite this to implement DB specific syntax*/
protected function sqlInsert($table,array $values)
{
global $dbScheme;
return $ret;
}
- /**creates a SQL92 statement for deletes*/
+ /**creates a SQL92 statement for deletes; overwrite this to implement DB specific syntax*/
protected function sqlDelete($table,$where)
{
return "DELETE FROM ".$this->tableName($table)." WHERE ".$where;
}
- /**creates a SQL92 statement for updates*/
+ /**creates a SQL92 statement for updates; overwrite this to implement DB specific syntax*/
protected function sqlUpdate($table,array $values,$where)
{
global $dbScheme;
return $ret;
}
+ /**returns the SQL92 statement for beginning a transaction; overwrite this to implement DB specific syntax*/
+ public function sqlBeginTransaction()
+ {
+ return "BEGIN TRANSACTION";
+ }
+
+ /**returns the SQL92 statement for committing a transaction; overwrite this to implement DB specific syntax*/
+ public function sqlCommitTransaction()
+ {
+ return "COMMIT TRANSACTION";
+ }
+
+ /**returns the SQL92 statement for rolling a transaction back; overwrite this to implement DB specific syntax*/
+ public function sqlRollbackTransaction()
+ {
+ return "ROLLBACK TRANSACTION";
+ }
+
/**escapes integers; the default implementation just makes sure it is an int*/
public function escapeInt($i)
{
return round($i + 0);
}
- /**escapes strings; the default uses addslashes and encloses the value in ''*/
+ /**escapes strings; the default uses addslashes and encloses the value in ''; it is recommended to overwrite this with the proper escaping procedure for the target DB*/
public function escapeString($s)
{
if($s === false) return "NULL";
return "'".addslashes($s)."'";
}
- /**escapes blobs; the default uses addslashes and encloses the value in ''*/
+ /**escapes blobs; the default uses addslashes and encloses the value in ''; it is recommended to overwrite this with the proper escaping procedure for the target DB*/
public function escapeBlob($s)
{
if($s === false) return "NULL";
return "'".addslashes($s)."'";
}
+
+ /**escapes a boolean value; the default translates 0, '0', 'f', 'false', 'no' to FALSE and numerics !=0, 't', 'true', 'yes' to TRUE; any other value (incl. the PHP constant false) is translated to NULL (exception: the constant true is treated as 'true')*/
+ public function escapeBool($s)
+ {
+ if(is_numeric($s)){
+ if(($s+0)!=0)return "TRUE";
+ else return "FALSE";
+ }
+ if($s===false)return "NULL";
+ if($s===true)return "TRUE";
+ switch(strtolower($s)){
+ case "t":case "true":case "yes":case "y":
+ return "TRUE";
+ case "f":case "false":case "n":case "no":
+ return "FALSE";
+ default:
+ return "NULL";
+ }
+ }
/**returns a configuration setting*/
public function getConfig($key)
//iterate DB schema and create tables
$tabs=$dbScheme->tableNames();
for($i=0;$i<count($tabs);$i++){
+ //create table
if(!$this->createTable($tabs[$i],$dbScheme->tableDefinition($tabs[$i]))){
print("DB Error while creating ".$tabs[$i].": ".$this->lastError()."<p>\n");
print("Last statement was: ".$this->sqlCreateTable($tabs[$i],$dbScheme->tableDefinition($tabs[$i]))."<p>\n");
$this->rollbackTransaction();
die("Unable to create database.");
}
+ //insert defaults
+ foreach($dbScheme->tableDefaults($tabs[$i]) as $def){
+ $this->insert($tabs[$i],$def);
+ }
}
- //insert some defaults
- $this->insert("config",array("ckey"=>"MagicSmokeVersion","cval"=>$this->needVersion()));
- $this->insert("config",array("ckey"=>"ValidVouchers","cval"=>"10 20 25 50"));
- $this->insert("config",array("ckey"=>"OrderStop","cval"=>"24"));
- $this->insert("config",array("ckey"=>"SaleStop","cval"=>"0"));
- $this->insert("config",array("ckey"=>"TicketIDChars","cval"=>"10"));
- $this->insert("config",array("ckey"=>"VoucherIDChars","cval"=>"10"));
- $this->insert("host",array("hostname"=>"_any"));
- $this->insert("host",array("hostname"=>"_anon"));
- $this->insert("host",array("hostname"=>"_online"));
//close transaction
$this->commitTransaction();
}
+ /**shows how the database would be created, used by admin.php only!!*/
+ public function showCreateDb()
+ {
+ global $dbScheme;
+ print("<h3>Database Creation SQL Script</h3>\n<pre>\n");
+ print(htmlentities($this->sqlBeginTransaction()).";\n");
+ //iterate DB schema and create tables
+ $tabs=$dbScheme->tableNames();
+ for($i=0;$i<count($tabs);$i++){
+ //create table
+ print(htmlentities($this->sqlCreateTable($tabs[$i],$dbScheme->tableDefinition($tabs[$i]))).";\n");
+ //insert defaults
+ foreach($dbScheme->tableDefaults($tabs[$i]) as $def){
+ print(htmlentities($this->sqlInsert($tabs[$i],$def)).";\n");
+ }
+ }
+ //close transaction
+ print(htmlentities($this->sqlCommitTransaction()).";\n<pre>\n");
+ }
+
/**returns the error string of the last operation*/
public abstract function lastError();
};
}
public function beginTransaction()
{
- return mysqli_query($this->dbhdl,"BEGIN");
+ return mysqli_query($this->dbhdl,$this->sqlBeginTransaction());
}
public function commitTransaction()
{
- return mysqli_query($this->dbhdl,"COMMIT");
+ return mysqli_query($this->dbhdl,$this->sqlCommitTransaction());
}
public function rollbackTransaction()
{
- return mysqli_query($this->dbhdl,"ROLLBACK");
+ return mysqli_query($this->dbhdl,$this->sqlRollbackTransaction());
}
+ public function sqlBeginTransaction(){return "BEGIN";}
+
+ public function sqlCommitTransaction(){return "COMMIT";}
+
+ public function sqlRollbackTransaction(){return "ROLLBACK";}
+
+
public function select($table,$cols,$where,$orderby="")
{
- $query="SELECT $cols FROM ".$this->tableName($table);
- if($where!="")$query.=" WHERE ".$where;
- if($orderby!="")$query.=" ".$orderby;
+ $query=$this->sqlSelect($table,$cols,$where,$orderby);
$res=mysqli_query($this->dbhdl,$query);
if($res===false)return false;
$nr=mysqli_num_rows($res);
protected function createTable($tn,$t)
{
+ $sql=$this->sqlCreateTable($tn,$t);
+ return mysqli_query($this->dbhdl,$sql);
+ }
+
+ protected function sqlCreateTable($tn,$t)
+ {
$this->tableappend="";
- $sql=$this->sqlCreateTable($tn,$t)." engine=".$this->engine." default charset=".$this->charset;
+ $sql=parent::sqlCreateTable($tn,$t)." engine=".$this->engine." default charset=".$this->charset;
// print("<pre>\n$sql\n</pre>\n");
- return mysqli_query($this->dbhdl,$sql);
+ return $sql;
}
protected function createTableExtras($tablename,$table)
{
+ //warning: this is rather dangerous, but safe at the moment since there is only
+ // one user of this function
return $this->tableappend;
}
/**This class contains a high-level description of the database structure*/
class DbScheme {
private static $scheme;
+ private static $preset;
+ private static $sversion;
+
function __construct()
{
+ // ////////////////////
+ // version of this scheme
+ $this->sversion="00.02";
+
+ // ////////////////////
//configuration
$this->scheme["config"]=array(
"ckey"=>array("string:32","primarykey"),
"cval"=>array("string")
);
+ $this->preset["config"]=array(
+ array("ckey"=>"MagicSmokeVersion","cval"=>$this->sversion),
+ array("ckey"=>"ValidVouchers","cval"=>"10 20 25 50"),
+ array("ckey"=>"OrderStop","cval"=>"24"),
+ array("ckey"=>"SaleStop","cval"=>"0"),
+ array("ckey"=>"TicketIDChars","cval"=>"10"),
+ array("ckey"=>"VoucherIDChars","cval"=>"10")
+ );
// ////////////////////
// Machine Interface Stuff
//if hostkey is NULL it is a special host (_any, _anon, _online)
"hostkey"=>array("string")
);
+ $this->preset["host"]=array(
+ array("hostname"=>"_any"),
+ array("hostname"=>"_anon"),
+ array("hostname"=>"_online")
+ );
+
//client users (ticket sellers, admins, etc.; for customers and web logins see below)
$this->scheme["users"]=array(
"uname" => array("string:64","primarykey"),
// //////////////////////
- // Shared Stuff
+ // Shared Stuff (web & GUI-client)
//rooms
$this->scheme["room"]=array(
"customerid" => array("int32","unique","foreignkey:customer:customerid"),
"passwd" => array("string:64"),//salted SHA-1 hash of passwd
);
+
+ //kinds of shipping that are available (templates)
+ $this->scheme["shipping"]=array(
+ "shipid" => array("seq32","primarykey"),
+ "cost" => array("int32","notnull"), //default cost of this shipping type
+ "canuseweb" => array("bool","default:false"), //is offered on web interface
+ "canallusers" => array("bool","default:true") //all GUI users may select it
+ );
+
//orders by customers
$this->scheme["order"]=array(
"orderid" => array("seq32","primarykey"),
"comments" => array("text"),
//how much has been paid already (including used vouchers)
//this is for comparison with the price fields in ticket and voucher tables
- "amountpaid" => array("int32")
+ "amountpaid" => array("int32"),
+ //shipping price
+ "shippingcosts" => array("int32","default:0"),
+ //pointer to shipping type (none per default, programmatic default is in config)
+ "shippingtype" => array("int32","null","foreignkey:shipping:shipid")
);
//tickets
$this->scheme["ticket"]=array(
);
}
+ /**return the version of this scheme*/
+ public function version(){return $this->sversion;}
+
/**return the tables to be created in order*/
public function tableNames()
{
return array_keys($this->scheme[$tab]);
}
+ /**return default lines of the table for the initialization; returns empty array if there are none*/
+ public function tableDefaults($tab)
+ {
+ if(isset($this->preset[$tab]))return $this->preset[$tab];
+ else return array();
+ }
+
/**return the type of a column, or false if it does not exist*/
public function columnType($tab,$col)
{