diff managedb.php @ 529:ddbc84031a7b

Rename createdb.php to managedb.php
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 10 Dec 2013 17:51:20 +0200
parents createdb.php@dd5c5577c495
children f872843ae396
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/managedb.php	Tue Dec 10 17:51:20 2013 +0200
@@ -0,0 +1,774 @@
+#!/usr/bin/php
+<?
+require_once "mconfig.inc.php";
+require_once "msite.inc.php";
+
+stCheckCLIExec();
+
+$dbVersion = 11;
+
+$dbMeta = array(
+  "dbVersion" => array(VT_INT, $dbVersion, "Database version"),
+);
+
+
+//
+// Site settings and defaults we put in
+//
+$siteDefaults = array(
+  "maxAttendeesHard" => array(VT_INT, 60, "Maximum attendees (HARD limit, <= 0 means no limit)"),
+  "maxAttendeesSoft" => array(VT_INT, 50, "Maximum attendees (soft limit, <= 0 means no limit)"),
+
+  "userTimeout"      => array(VT_INT, 120, "User pages (voting) timeout in minutes"),
+  "admTimeout"       => array(VT_INT, 15, "Administration interface timeout in minutes"),
+
+  "showAdmin"        => array(VT_BOOL, false, "Always show administration interface link on the menu"),
+  "showAttendees"    => array(VT_BOOL, true, "Show attendees list"),
+  "allowRegister"    => array(VT_BOOL, false, "Enable event registration"),
+  "allowVoting"      => array(VT_BOOL, false, "Enable voting (individual compos must be enabled as well)"),
+
+  "showResults"      => array(VT_BOOL, false, "Enable results page"),
+  "showResAuthors"   => array(VT_BOOL, true, "Show entry authors on results page"),
+
+  "requireEMail"     => array(VT_BOOL, false, "Require e-mail address in registrations"),
+
+  "showNews"         => array(VT_BOOL, true, "Enable News link on main menu"),
+  "showNewsOnAbout"  => array(VT_BOOL, true, "Show latest news item on About page"),
+
+  "registerInfoText" => array(VT_TEXT, "<p>
+Only your <b>handle</b> and the answer to the botcheck are strictly required.
+If you plan on joining the IRC channel
+(<a href=\"irc://#fap2013@ircnet\">#fap2013 @ IRCNet</a>) or staying up to date by other means,
+<b>e-mail</b> is not required either.
+</p>", "Registration page info text"),
+
+  "registerPostText" => array(VT_TEXT, "
+<h1>Registration successful</h1>
+<p>Now go make a demo about it!</p>",
+  "Successful post-registration note text"),
+
+  "registerLimitExceeded" => array(VT_TEXT, "
+<h1>Sorry, registration disabled!</h1>
+<p>
+Registration to the event is not available at this time due to
+number of attendees limit having been reached. <b>:(</b>
+</p>
+",
+  "Registration attendee limit exceeded note text"),
+
+  "registerNotEnabled" => array(VT_TEXT, "
+<h1>Sorry, registration disabled!</h1>
+<p>
+Registration to the event is not enabled at this time.
+</p>
+",
+  "Registration not enabled note text"),
+
+  "registerPostNoEmail" => array(VT_TEXT, "
+<h2>By the way ...</h2>
+<p>As you did not specify an e-mail contact address, you'll have to get updates
+and information about the location (if you don't already know it) by 
+some other means (IRC, for example.)</p>
+", "No e-mail address registration note"),
+
+  "eventDescription" => array(VT_TEXT, "
+<h1>Event program &amp; schedule</h1>
+<ul>
+ <li><b>Aegis</b> of DSS and FAG will be performing a DJ gig.</li>
+ <li>.. and possible additional live acts. More info to come.</li>
+</ul>
+
+<h2>Friday 30.11.</h2>
+<ul>
+ <li><b>18:00</b> - <i>Doors open</i>.</li>
+ <li><b>22:00</b> - DJ set by Aegis.</li>
+</ul>
+
+<h2>Saturday 1.12.</h2>
+<ul>
+ <li><b>14:00</b> - Deadline for remote entries.</li>
+ <li><b>18:00</b> - Deadline for the entries delivered on location.</li>
+ <li><b>20:00</b> - Competitions start.</li>
+</ul>
+
+Competition schedule and voting deadline will depend on number of entries.
+
+<h2>Sunday 2.12.</h2>
+<ul>
+ <li><b>12:00</b> - Party over?</li>
+</ul>
+", "Event general description / timetables etc."),
+
+
+  "compoDescription" => array(VT_TEXT, "
+<h1>General</h1>
+<p class=\"notice\">
+YOU <b>MUST</b> HAVE AT LEAST ONE ENTRY TO COMPETITIONS IF YOU COME TO THE PARTY.
+</p>
+
+<p class=\"note\">
+If there are enough entries, then AGA/OCS/ECS demos will be run in separate compos.
+<br />
+Remote entries are welcome!
+</p>
+
+<p>
+The compo machine will be an <b>A1200 with an 060/50 and lots of
+RAM</b>. An <b>A500 1.3 512k/512k</b> will also be available if your
+prod is not AGA compatible.
+</p>
+
+<h1>Compos</h1>
+", "Compo general description"),
+
+
+  "siteInfoText"     => array(VT_TEXT, "
+<a href=\"about\">
+<img src=\"img/fapsm.png\" alt=\"Finnish Amiga Party 2013\" class=\"logo\" /></a>
+<div id=\"date\">
+5.-8.12.2013<br />
+Helsinki, Finland<br />
+@ old location<br />
+<span class=\"notice\">Entry 15 EUR + prod</span>
+</div>", "Site header text"),
+
+
+  "newsHeader"  => array(VT_TEXT, "", "News page header text"),
+
+
+  "aboutDescription"  => array(VT_TEXT, "
+<div style=\"text-align: center;\">
+<img src=\"img/fap.png\" alt=\"FAP\" />
+<p>
+Pure Amiga demoscene party, all traditional Amiga compos and purely Amiga-oriented program.
+<br />
+<span class=\"notice\">
+YOU <b>MUST</b> HAVE AT LEAST ONE ENTRY TO COMPETITIONS IF YOU COME TO THE PARTY.
+</span>
+</p>
+</div>",
+  "About page text"),
+
+  "voteFinishedText" => array(VT_TEXT, "
+<h1>Yay, you have voted!</h1>
+<p>Now go FAP some more! And make a demo about it.</p>",
+  "Message shown after successful voting."),
+
+  "siteMenuHeader" => array(VT_TEXT, "<div>13.FAP:&gt; <span class=\"mblink\">&#9632;</span></div>", "Site menu header text"),
+  "siteMenuFooter" => array(VT_TEXT, "", "Site menu footer text"),
+  "siteExtraHTML" => array(VT_TEXT, "<div id=\"sponsors\">Gentle Eye</div>", "Extra global HTML code (f.e. sponsors box)"),
+);
+
+
+//
+//
+//
+$sqlTables = array(
+  // Database metadata
+  "dbmeta" => array(
+    array("key"          , "VARCHAR(32)", "PRIMARY KEY"),
+    array("vtype"        , "INT"),
+    array("vstr"         , "VARCHAR(128)"),
+    array("vtext"        , "TEXT"),
+    array("vint"         , "INT"),
+    array("sdesc"        , "VARCHAR(128)"),
+  ),
+
+  // Site settings
+  "settings" => array(
+    array("key"          , "VARCHAR(32)", "PRIMARY KEY"),
+    array("vtype"        , "INT"),
+    array("vstr"         , "VARCHAR(128)"),
+    array("vtext"        , "TEXT"),
+    array("vint"         , "INT"),
+    array("sdesc"        , "VARCHAR(128)"),
+  ),
+
+  "news" => array(
+    array("id"           , "INTEGER", "PRIMARY KEY", "AUTOINCREMENT"),
+    array("utime"        , "INT"),
+    array("title"        , "VARCHAR(".SET_LEN_NEWS_TITLE.")"),
+    array("text"         , "VARCHAR(".SET_LEN_NEWS_TEXT.")"),
+    array("author"       , "VARCHAR(".SET_LEN_NEWS_AUTHOR.")"),
+    array("persist"      , "INT", "DEFAULT 0"),
+  ),
+
+  "compos" => array(
+    array("id"           , "INTEGER", "PRIMARY KEY", "AUTOINCREMENT"),
+    array("name"         , "VARCHAR(".SET_LEN_COMPO_NAME.")"),
+    array("description"  , "VARCHAR(".SET_LEN_COMPO_DESC.")"),
+    array("visible"      , "INT", "DEFAULT 0"),
+    array("voting"       , "INT", "DEFAULT 0"),
+    array("showAuthors"  , "INT", "DEFAULT 0"),
+    array("type"         , "INT", "DEFAULT 0"),
+  ),
+  
+  "entries" => array(
+    array("id"           , "INTEGER", "PRIMARY KEY", "AUTOINCREMENT"),
+    array("show_id"      , "INT", "DEFAULT 0"),
+    array("name"         , "VARCHAR(".SET_LEN_ENTRY_NAME.")"),
+    array("author"       , "VARCHAR(".SET_LEN_ENTRY_AUTHOR.")"),
+    array("compo_id"     , "INT", "DEFAULT 0"),
+    array("filename"     , "VARCHAR(".SET_LEN_ENTRY_FILENAME.")", "DEFAULT NULL"),
+    array("info"         , "VARCHAR(".SET_LEN_ENTRY_INFO.")", "DEFAULT NULL"),
+    array("notes"        , "VARCHAR(".SET_LEN_ENTRY_NOTES.")", "DEFAULT NULL"),
+    array("preview_file" , "VARCHAR(".SET_LEN_ENTRY_PREVIEW_FILE.")", "DEFAULT NULL"),
+    array("preview_type" , "INT", "DEFAULT 0"),
+    array("flags"        , "INT", "DEFAULT 0"),
+  ),
+
+  "attendees" => array(
+    array("id"           , "INTEGER", "PRIMARY KEY", "AUTOINCREMENT"),
+    array("regtime"      , "INT"),
+    array("name"         , "VARCHAR(".SET_LEN_USERNAME.")"),
+    array("groups"       , "VARCHAR(".SET_LEN_GROUPS.")"),
+    array("oneliner"     , "VARCHAR(".SET_LEN_ONELINER.")"),
+    array("email"        , "VARCHAR(".SET_LEN_EMAIL.")"),
+    array("key_id"       , "INT", "DEFAULT NULL"),
+  ),
+
+  "votekeys" => array(
+    array("id"           , "INTEGER", "PRIMARY KEY", "AUTOINCREMENT"),
+    array("key"          , "VARCHAR(".SET_LEN_VOTEKEY.")"),
+    array("active"       , "INT", "DEFAULT 0"),
+  ),
+
+  "votes" => array(
+    array("id"           , "INTEGER", "PRIMARY KEY", "AUTOINCREMENT"),
+    array("entry_id"     , "INT", "DEFAULT NULL"),
+    array("key_id"       , "INT", "DEFAULT 0"),
+    array("value"        , "INT", "DEFAULT 0"),
+  ),
+
+
+  // Party information system tables
+  "displayVars" => array(
+    array("key"          , "VARCHAR(32)", "PRIMARY KEY"),
+    array("vtype"        , "INT"),
+    array("vstr"         , "VARCHAR(128)"),
+    array("vtext"        , "TEXT"),
+    array("vint"         , "INT"),
+    array("sdesc"        , "VARCHAR(128)"),
+  ),
+
+  "displaySlides" => array(
+    array("id"           , "INTEGER", "PRIMARY KEY", "AUTOINCREMENT"),
+    array("title"        , "VARCHAR(".SET_LEN_DISP_SLIDE_TITLE.")"),
+    array("text"         , "VARCHAR(".SET_LEN_DISP_SLIDE_TEXT.")"),
+  ),
+
+  "rotationListData" => array(
+    array("id"           , "INTEGER", "PRIMARY KEY", "AUTOINCREMENT"),
+    array("name"         , "VARCHAR(".SET_LEN_ROT_LIST_NAME.")"),
+  ),
+  
+  "rotationListSlides" => array(
+    array("id"           , "INTEGER", "PRIMARY KEY", "AUTOINCREMENT"),
+    array("list_id"      , "INT", "DEFAULT 0"),
+    array("slide_id"     , "INT", "DEFAULT 0"),
+    array("order_num"    , "INT", "DEFAULT 0"),
+  ),
+);
+
+
+//
+// Party information system settings / data
+//
+$siteDisplayVars = array(
+  "showMode"          => array(VT_INT, 0, "Currently active display mode"),
+  "rotateDuration"    => array(VT_INT, 15, "Slide rotation time per slide (seconds)"),
+
+  "tempDuration"      => array(VT_INT, 5, "Temporary slide display time (minutes)"),
+  "tempSlide"         => array(VT_INT, 0, "Temporary slide ID"),
+
+  "compoID"           => array(VT_INT, 0, "Compo ID of current compo"),
+  "compoPrevEntry"    => array(VT_INT, 0, "Previously shown compo entry"),
+  "compoCurrEntry"    => array(VT_INT, 0, "Current / next compo entry to be shown"),
+  
+  // Not user-manageable
+  "tempSlideSet"      => array(VT_BOOL, false, "Temporary slide set"),
+
+  "activeSlideMode"   => array(VT_INT, 0, "Current active slide display mode"),
+  "activeSlide"       => array(VT_INT, 0, "Current active slide"),
+  "activeSlideExpire" => array(VT_INT, 0, "Expiration timestamp of current slide"),
+
+  "rotateList"        => array(VT_INT, 0, "Current rotation list ID"),
+  "rotateListIndex"   => array(VT_INT, 0, "Current index in rotation list"),
+
+  "lastUpdate"        => array(VT_INT, 0, "Timestamp of last slide update"),
+);
+
+
+//
+// Some premade test data
+//
+$siteTestData = array(
+  "news" => array(
+    "utime,title,text,author",
+    time().",%s,%s,%s",
+    array("Today's news", "We. Are. Back.", "orgaz"),
+    array("Good news, everybody!", "...", "The Professor"),
+  ),
+
+  "votekeys" => array(
+    "key",
+    "%s",
+    array("test1"),
+    array("test2"),
+    array("7jjnqt5z"),
+    array("c7jcfk1G"),
+  ),
+
+  "attendees" => array(
+    "regtime,name,groups,oneliner,email",
+    "%d,%s,%s,%s,%s",
+    array(time()-0, "man with no alias", "supergroup", "foo-bar", "c@supergroup.com"),
+    array(time()-15, "man with alias", "supergroup", "hi!", "c@supergroup.com"),
+    array(time()-30, "alias with a man", "supergroup", "mega super kewl rulets", "x@microsoft.com"),
+  ),
+
+  "compos" => array(
+    "name,description,visible,voting",
+    "%s,%s,1,1",
+    array("Graphics", "Anything in standard resolutions."),
+    array("Protracker music", "Standard 4-channel Protracker MOD music."),
+    array("4k intro", "4k intro competition"),
+  ),
+  
+  "entries" => array(
+    "name,author,compo_id,filename,info",
+    "%s,%s,%d,%s,%s",
+    array("Donkey Dong", "electric/extend", 1, "donkey.lbm", "amigaaaah!"),
+    array("Your kondom", "ccr/TNSP", 1, "kondom.lbm", "oh my god, it's full of cocks!"),
+    array("Penis song", "reed/flt", 2, "penis.mod", "laulu rakkaudelle"),
+    array("jenkka", "aegis", 2, "jenkka.mod", ""),
+    array("Fungiform 2", "mfx", 3, "mfx-fungiform2.lzh", "OCS-only"),
+  ),
+  
+  "displaySlides" => array(
+    "title,text",
+    "%s,%s",
+    array("Next Up 4k", "<h1>Next up: 4k intro</h1><h2>4k intro compo is about to begin</h2>... in about 10 minutes."),
+    array("Astu to infodesk", "<b>Astu</b> - please come to info desk!"),
+    array("Gentle Eye mainos", "<b>Buy Amiga stuff!</b><br />Gentle Eye Oy is selling Amiga-related stuff near organizer desk!"),
+  ),
+  
+  "rotationListData" => array(
+    "name",
+    "%s",
+    array("Main rotation"),
+    array("Next Up"),
+  ),
+  
+  "rotationListSlides" => array(
+    "list_id,slide_id",
+    "%d,%d",
+    array(1,2),
+    array(1,3),
+    array(2,1),
+  ),
+);
+
+
+//
+// Helper functions
+//
+function stGetTableSchema($dbh, $data)
+{
+  $res = array();
+  $driver = $dbh->getAttribute(PDO::ATTR_DRIVER_NAME);
+
+  foreach ($data as $col)
+  {
+    $tmp = array();
+
+    switch ($driver)
+    {
+      case "pgsql":
+        foreach ($col as $elem)
+        {
+          if ($elem != "AUTOINCREMENT")
+            $tmp[] = $elem;
+        }
+        break;
+      
+      case "sqlite":
+      case "mysql":
+        $tmp = $col;
+        break;
+      
+      default:
+        die("Don't know how to handle PDO driver '".$driver."' yet.\n");
+    }
+
+    $res[] = implode(" ", $tmp);
+  }
+
+  return implode(", ", $res);
+}
+
+
+function stCreateOneTable($dbh, $name, $schema)
+{
+  return (stDBExecSQL($dbh, "CREATE TABLE IF NOT EXISTS ".$name." (".$schema.")") !== FALSE) ? TRUE : FALSE;
+}
+
+
+function stCreateTables($dbh, $upgrade)
+{
+  global $sqlTables;
+  echo "Creating tables...\n";
+  foreach ($sqlTables as $name => $schemaData)
+  {
+    echo " - '".$name."'\n";
+    if (!stCreateOneTable($dbh, $name, stGetTableSchema($dbh, $schemaData)) && !$upgrade)
+      return FALSE;
+  }
+  return TRUE;
+}
+
+
+$upgradeMappings = array(
+//  "" => array("key" => "", value => ""),
+);
+
+
+function stUpgradeMap($type, $name, $value = FALSE)
+{
+  global $upgradeMappings;
+
+  if (isset($upgradeMappings[$name]))
+  {
+    if (isset($upgradeMappings[$name][$type]))
+    {
+      // XXX TODO ..
+    }
+    else
+      die("Upgrade failed due to missing or invalid upgrade definition: ".$type." ".$name." from version X to Y.\n");
+  }
+  else
+  switch ($type)
+  {
+    case "key": return $name;
+    case "value": return $value;
+  }
+}
+
+
+function stAddSettings($inDB, $outDB, $settings, $table, $upgrade)
+{
+  echo ($upgrade ? "Adding settings to" : "Upgrading settings in")." '".$table."' table.";
+
+  $status = TRUE;
+  stDBExecSQL($outDB, "BEGIN TRANSACTION");
+
+  foreach ($settings as $key => $data)
+  {
+    // Get setting type
+    switch ($data[0])
+    {
+      case VT_TEXT: $type = "%s"; $var = "vtext"; break;
+      case VT_STR:  $type = "%s"; $var = "vstr"; break;
+      case VT_BOOL: $type = "%b"; $var = "vint"; break;
+      case VT_INT:  $type = "%d"; $var = "vint"; break;
+      default:      die("Invalid type in default settings '".$key."', type=".$data[0]."\n");
+    }
+
+    // Map the key, in case the name has changed
+    $inKey = stUpgradeMap("key", $key);
+
+    // Check if we are upgrading
+    if ($upgrade && ($res = stDBFetchSQL($inDB, stDBPrepareSQL($inDB, "SELECT * FROM ".$table." WHERE key=%s", $inKey))) !== FALSE)
+    {
+      // Yup, upgrade the data, if we can
+      echo ".";
+
+      if ($res["vtype"] != $data[0])
+        die("Oops! Data type of '".$key."' does not match in table '".$table.". DB upgrade failed.\n");
+
+      $sql = stDBPrepareSQL($outDB,
+        "INSERT INTO ".$table." (key,vtype,".$var.",sdesc) VALUES (%s,%d,".$type.",%s)",
+        $key, $data[0], stUpgradeMap("value", $key, $res[$var]), $data[2]);
+    }
+    else
+    {
+      // Normal insertion of default data
+      echo "+";
+      $sql = stDBPrepareSQL($outDB,
+        "INSERT INTO ".$table." (key,vtype,".$var.",sdesc) VALUES (%s,%d,".$type.",%s)",
+        $key, $data[0], $data[1], $data[2]);
+    }
+
+    if (stDBExecSQL($outDB, $sql) === FALSE)
+    {
+      $status = FALSE;
+      break;
+    }
+  }
+  echo "\n";
+
+  stDBExecSQL($outDB, "COMMIT");
+  return $status;
+} 
+
+
+function stAddTestData($outDB)
+{
+  global $siteTestData;
+
+  echo "Adding test data.\n";
+
+  $status = TRUE;
+  stDBExecSQL($outDB, "BEGIN TRANSACTION");
+
+  foreach ($siteTestData as $table => $data)
+  {
+    echo " - ".$table."...\n";
+    if (count($data) >= 3)
+    {
+      for ($n = 2; $n < count($data); $n++)
+      {
+        $arr = array_merge(
+          array($outDB, "INSERT INTO ".$table." (".$data[0].") VALUES (".$data[1].")"),
+          $data[$n]);
+
+        $sql = call_user_func_array('stDBPrepareSQL', $arr);
+        if (stDBExecSQL($outDB, $sql) === false)
+        {
+          $status = false;
+          break;
+        }
+      }
+    }
+    else
+    {
+      echo "  Invalid table / data definition.\n";
+    }
+  }
+
+  stDBExecSQL($outDB, "COMMIT");
+  return $status;
+}
+
+
+function stGetSQLTypeParam($dbh, $def, $value)
+{
+  switch (substr($def, 0, 3))
+  {
+    case "INT":
+      return intval($value);
+
+    case "VAR":
+    case "TEX":
+      return $dbh->quote($value);
+
+    default: die("Unknown type ".$col[1].".\n");
+  }
+}
+
+
+function stMigrateTables($inDB, $outDB, $excluded)
+{
+  global $sqlTables;
+
+  echo "Migrating tables...\n";
+  $status = TRUE;
+  stDBExecSQL($outDB, "BEGIN TRANSACTION");
+  
+  foreach ($sqlTables as $name => $schema)
+  if (!in_array($name, $excluded))
+  {
+    echo " - '".$name."' ";
+
+    foreach (stDBExecSQL($inDB, "SELECT * FROM ".$name) as $row)
+    {
+      $avals = array();
+      $acols = array();
+      
+      foreach ($schema as $col)
+      {
+        if (isset($row[$col[0]]))
+        {
+          $avals[] = stGetSQLTypeParam($outDB, $col[1], $row[$col[0]]);
+          $acols[] = $col[0];
+        }
+      }
+
+      $sql = "INSERT INTO ".$name." (".implode(",", $acols).") VALUES (".implode(",", $avals).")";
+      if (stDBExecSQL($outDB, $sql) === false)
+      {
+        $status = FALSE;
+        break;
+      }
+      echo ".";
+    }
+    echo "\n";
+  }
+
+  stDBExecSQL($outDB, "COMMIT");
+
+  return $status;
+}
+
+
+function stSetDBPermissions($spec)
+{
+  if (substr($spec, 0, 7) == "sqlite:")
+  {
+    $filename = substr($spec, 7);
+    echo
+      "NOTICE! It seems you have SQLite database in use, changing permission ".
+      "of the target file '".$filename."' to 0600, for security. You may have to ".
+      "loosen up that for the things to actually work, but be careful. Having your ".
+      "database world-readable in the web is NOT good.\n";
+
+    if (chmod($filename, 0600) === FALSE)
+    {
+      echo "ERROR! Could not set permissions of '".$filename."!\n";
+    }
+  }
+}
+
+
+//
+// Main program starts
+//
+if ($argc < 2)
+{
+  echo
+  "ManageDB - Manage FAPWeb SQL database\n".
+  "(C) Copyright 2012-2013 ccr/TNSP\n".
+  "\n".
+  "Usage: ".$argv[0]." <mode> [args]\n".
+  "Where mode is one of following:\n".
+  "\n".
+  "  new <dbspec>      Create a new database with given PDO spec\n".
+  "                    or default to the one in mconfig.inc.php\n".
+  "\n".
+  "  test <dbspec>     Like new, but add initial test data.\n".
+  "\n".
+  "  upgrade <input_dbspec> <output_dbspec>\n".
+  "                    Upgrade current database, if possible.\n".
+  "                    Output to new database (DO NOT USE SAME as current!)\n".
+  "\n".
+  "  migrate <input_dbspec> <output_dbspec>\n".
+  "                    Like upgrade, but no version check. Creates\n".
+  "                    a copy of the database to the output spec.\n".
+  "\n";
+  exit;
+}
+
+
+// Act according to specified command
+$addTestData = FALSE;
+switch (stCArgLC(1))
+{
+  case "test":
+    $addTestData = TRUE;
+
+  case "new":
+    // Try to connect to database
+    if (($inSpec = stCArg(2)) === false)
+      die("No PDO database spec specified.\n");
+
+    if (($inDB = stConnectSQLDBSpec($inSpec)) === false)
+      die("Could not connect to SQL database '".$inSpec."'.\n");
+
+    echo "Using database spec '".$inSpec."'.\n";
+
+    // Create tables, add defaults
+    if (stCreateTables($inDB, FALSE))
+    {
+      stAddSettings($inDB, $inDB, $dbMeta, "dbmeta", FALSE);
+      stAddSettings($inDB, $inDB, $siteDefaults, "settings", FALSE);
+      stAddSettings($inDB, $inDB, $siteDisplayVars, "displayVars", FALSE);
+    }
+    
+    if ($addTestData)
+      stAddTestData($inDB);
+
+    stSetDBPermissions($inSpec);
+    break;
+
+  case "upgrade":
+  case "migrate":
+    //
+    // Attempt to upgrade database
+    //
+    if ($argc < 4)
+      die("Usage: ".$argv[0]." upgrade <input_dbspec> <output_dbspec>\n");
+
+    $inSpec = stCArg(2);
+    $outSpec = stCArg(3);
+
+    if ($outSpec == $inSpec)
+      die("The input and output databases CAN NOT BE SAME.\nBe VERY CAREFUL to not accidentally specify same db!\n");
+
+    echo "Using database spec '".$inSpec."'.\n";
+
+    if (($inDB = stConnectSQLDBSpec($inSpec)) === false)
+      die("Could not connect to SQL database '".$inSpec."'.\n");
+
+    // Check the current version first ...
+    if (($currVersion = stGetDBMeta($inDB, "dbVersion")) === FALSE)
+      $currVersion = -1;
+
+    if ($currVersion == $dbVersion && stCArgLC(1) == "upgrade")
+    {
+      echo "Database is already version ".$dbVersion.", no upgrading needed.\n";
+    }
+    else
+    {
+      // Okay, we shall create an upgraded version ..
+      if (($outDB = stConnectSQLDBSpec($outSpec)) === false)
+        die("Could not connect to SQL database '".$outSpec."'.\n");
+
+      echo "Database at version ".$currVersion.", upgrading to ".$dbVersion."\n";
+      echo "Using OUTPUT database spec '".$outSpec."'.\n";
+
+      // Possibly bail out incompatible upgrades here
+      
+      // Create tables
+      if (!stCreateTables($outDB, TRUE))
+        exit;
+
+      // Migrate data from setting tables ..
+      if (!stAddSettings($inDB, $outDB, $siteDefaults, "settings", TRUE))
+        exit;
+
+      if (!stAddSettings($inDB, $outDB, $siteDisplayVars, "displayVars", TRUE))
+        exit;
+
+      stAddSettings($inDB, $outDB, $dbMeta, "dbmeta", TRUE);
+
+      // Migrate other tables
+      if (!stMigrateTables($inDB, $outDB, array("settings", "displayVars", "dbmeta")))
+        exit;
+
+      echo "Setting dbVersion.\n";
+      stSetDBMeta($outDB, "dbVersion", $dbVersion);
+      echo "Upgrade complete.\n";
+
+      stSetDBPermissions($inSpec);
+      stSetDBPermissions($outSpec);
+    }
+    break;
+
+  default:
+    echo "ERROR! Invalid operation mode '".stCArg(1)."'.\n";
+    break;
+}
+
+
+//
+// Clean up permissions
+//
+foreach (array("managedb.php") as $filename)
+{
+  if (chmod($filename, 0700) === FALSE)
+  {
+    echo "ERROR! Could not set permissions for '".$filename."'!\n";
+  }
+}
+
+?>
\ No newline at end of file