summaryrefslogtreecommitdiff
path: root/common/db_classes.php
diff options
context:
space:
mode:
Diffstat (limited to 'common/db_classes.php')
-rw-r--r--common/db_classes.php346
1 files changed, 346 insertions, 0 deletions
diff --git a/common/db_classes.php b/common/db_classes.php
new file mode 100644
index 0000000..dde26eb
--- /dev/null
+++ b/common/db_classes.php
@@ -0,0 +1,346 @@
+<?php
+namespace mcoop;
+
+class TableCreationError extends \Exception {
+ public $table_name = null;
+ public $sql_error_code = null;
+ public $server_error_code = null;
+ public $server_error_message = null;
+
+ function __construct($table_name, $sql_error_code, $server_error_code, $server_error_message) {
+ $this->table_name = $table_name;
+ $this->sql_error_code = $sql_error_code;
+ $this->server_error_code = $server_error_code;
+ $this->server_error_message = $server_error_message;
+
+ $message = "Failed to create table '$table_name': ($sql_error_code, $server_error_code, $server_error_message)";
+ parent::__construct($message);
+ }
+}
+
+interface TableUpgrader {
+ function upgrade($from_version, $to_version);
+}
+
+class BaseIncrementalTableUpgrader implements TableUpgrader {
+ public $upgrade_method_names = array();
+ public $table_name = null;
+ public $conn = null;
+
+ function upgrade($from_version, $to_version) {
+ // first make sure a full update is possible with what's in method_names
+ $full_upgrade_path = true;
+ for ($i = $from_version + 1; $i <= $to_version; $i++) {
+ $method_name = $this->upgrade_method_names[$i];
+ if (!is_callable(array($this, $method_name))) {
+ $full_upgrade_path = false;
+ error_log(get_class($this) . "::upgrade_method_names for $i not callable: " . var_export($method_name, true));
+ }
+ }
+ if (!$full_upgrade_path) {
+ die("couldn't upgrade the database, check error_log");
+ } else {
+ $highest_version = $from_version;
+ for ($i = $from_version + 1; $i <= $to_version; $i++) {
+ $method_name = $this->upgrade_method_names[$i];
+ // XXX/TODO: this really needs trying around, not sure how to deal with upgrades that fail though, might want a downgrade function to rollback if you fail somewhere between, or just leave it in the middle state
+ $this->$method_name();
+ $highest_version = $i;
+ }
+ return $highest_version;
+ }
+ }
+}
+
+class SimpleTableDecl {
+ public $table_name = null;
+ public $statements_sql = array();
+ // it might make sense to make this an array in the future, but for now just make it a simple string for creating the newest version
+ public $create_table_sql = null;
+ public $newest_tableversion = null; // int
+ public $upgrader_classname = null; // string, this should be fully namespaced so it can be created dynamically
+ // TODO: table dependencies variables for creating and/or for the statements?
+
+ function create_table($db) {
+ $conn = $db->conn;
+ $conn->exec($this->create_table_sql);
+ $err_info = $conn->errorInfo();
+ // the SQL errorcode for a table already existing seems to be 42S01
+ // but I think we don't need to check for it specifically here,
+ // just make sure there was no error
+ //print_r($err_info);
+ //var_dump($err_info);
+ if ($err_info[0] == "00000") {
+ $db->statements["set_tableversion"]->execute(array(
+ ":table_name" => $this->table_name,
+ ":version" => $this->newest_tableversion
+ ));
+ } else if ($err_info[0] != "42S01") {
+ $db->statements["insert_unexpected_error"]->execute(array(
+ ":action" => "create_table($this->table_name)",
+ ":sql_code" => $err_info[0],
+ ":server_code" => $err_info[1],
+ ":server_description" => $err_info[2]
+ ));
+ throw new TableCreationError($this->table_name, $err_info[0], $err_info[1], $err_info[2]);
+ }
+ }
+
+ function set_statements($db) {
+ foreach ($this->statements_sql as $name => $sql) {
+ if (array_key_exists($name, $db->statements)) {
+ // TODO: I should probably also raise an exception here
+ error_log("mcoop: table_decl for $this->table_name: key $name already exists in the db's statements, not replacing");
+ } else {
+ $db->statements[$name] = $db->conn->prepare($sql);
+ };
+ }
+ }
+
+ function __construct($table_name, $statements_sql, $create_table_sql, $newest_tableversion, $upgrader_classname) {
+ $this->table_name = $table_name;
+ $this->statements_sql = $statements_sql;
+ $this->create_table_sql = $create_table_sql;
+ $this->newest_tableversion = $newest_tableversion;
+ $this->upgrader_classname = $upgrader_classname;
+ }
+}
+
+class SimpleDBMixin {
+ static function load_mult_with_statement($db, $st_name, $varray, $clsname=null, $keyprop=null) {
+ if ($clsname == null)
+ $clsname = get_called_class();
+ $st = $db->statements[$st_name];
+ $st->execute($varray);
+ $ret = array();
+ $last_obj = null;
+ do {
+ $last_obj = $st->fetchObject($clsname);
+ //var_dump($st_name, $varray, $clsname, $keyprop, $st, $last_obj);
+ //var_dump($clsname);
+ if ($last_obj != FALSE) {
+ if ($keyprop) {
+ $key = $last_obj->$keyprop;
+ // TODO: deal with duplicates
+ $ret[$key] = $last_obj;
+ } else {
+ $ret[] = $last_obj;
+ }
+ }
+ } while ($last_obj != FALSE);
+ return $ret;
+ }
+}
+
+// TODO: all of these classes need a get_json function so you don't serialize the entire thing (or look at how to override json
+// serialization for classes/objects again)
+
+class DBMember extends SimpleDBMixin {
+ // from the members table:
+ //public $db = null;
+ public $userid = null;
+ public $username = null;
+ public $email = null;
+ public $last_updated_table_version = null;
+ public $full_name = null;
+ public $validation_code = null;
+ public $validated = null;
+ public $full_member = null;
+ public $argon2_password_hash = null;
+ public $reset_password_hash = null;
+ public $reset_requested = null;
+ // from the shares table:
+ //public $shares = array();
+ // generated in __construct
+ public $display_name = null;
+
+ function __construct() {
+ if (isset($this->full_name) && $this->full_name)
+ $this->display_name = $this->full_name;
+ else
+ $this->display_name = $this->username;
+ }
+
+ static function load_by($db, $by, $value) {
+ // by should be either "username" or "email" here
+ $st = $db->statements["get_member_by_$by"];
+ $st->execute(array($value));
+ $self = $st->fetchObject("\mcoop\DBMember");
+ if (!$self) {
+ throw new UnknownMember($by, $value);
+ }
+ //$self->db = $db;
+ return $self;
+ }
+
+ static function load_public_info($db, $userid) {
+ $ret = DBMember::load_mult_with_statement($db, "get_public_member_info_by_userid", array(":userid" => $userid));
+ if (!$ret) {
+ throw new UnknownMember("userid", $userid);
+ } else {
+ //$ret[0]->db = $db;
+ return $ret[0];
+ }
+ }
+}
+
+class TccHistoryEntry extends SimpleDBMixin {
+ // tcc_history table values
+ public $from_tdc_id = null; // this should probably index into a table in the task object
+ public $to_claim_id = null; // contains this object, sometimes (the tdc could also contain them, hmm)
+ public $action = null;
+ public $credits = null;
+ public $last_updated_table_version = null;
+
+ static function get_history_by_claimid($db, $claimid, $clsname=null, $keyprop=null) {
+ return TccHistoryEntry::load_mult_with_statement($db, "get_tcc_history_by_claimid", array(":claimid" => $claimid), $clsname, $keyprop);
+ }
+
+ static function get_history_by_tdc_id($db, $tdc_id, $clsname=null, $keyprop=null) {
+ return TccHistoryEntry::load_mult_with_statement($db, "get_tcc_history_by_tdc_id", array(":tdc_id" => $tdc_id), $clsname, $keyprop);
+ }
+}
+
+
+class SimpleTaskClaim extends SimpleDBMixin {
+ // claims table values
+ public $claim_id = null;
+ public $task_id = null;
+ public $userid = null;
+ public $last_updated_table_version = null;
+ public $description = null;
+ // loaded via. userid
+ public $member_public = null;
+ // loaded from tcc_history, see $this->load_tcc_history
+ public $tcc_history = null;
+
+ static function get_claims_by_taskid($db, $taskid, $clsname=null, $keyprop=null) {
+ return SimpleTaskClaim::load_mult_with_statement($db, "get_task_claims_by_taskid", array(":task_id" => $taskid), $clsname, $keyprop);
+ }
+
+ function load_member($db) {
+ $this->member_public = DBMember::load_public_info($db, $this->userid);
+ }
+
+ function load_tcc_history($db) {
+ $this->tcc_history = TccHistoryEntry::get_history_by_claimid($db, $this->claim_id);
+ }
+
+ function load_all($db) {
+ $this->load_member($db);
+ $this->load_tcc_history($db);
+ }
+}
+
+class SimpleTdc extends SimpleDBMixin {
+ // tdc table values
+ public $tdc_id = null;
+ public $task_id = null;
+ public $posted_userid = null;
+ public $posted_by_coop = null;
+ public $total_credits = null;
+ public $remaining_credits = null;
+ public $last_updated_table_version = null;
+ // loaded via. posted_userid, will be null after $this->load_member() if posted_by_coop is true
+ public $member_public = null;
+ // Set to either $member_public->display_name or "By the Co-Operative"
+ // TODO: maybe use the website_name in $config with posted_by_coop
+ public $member_name = null;
+
+ static function get_tdcs_by_taskid($db, $taskid, $clsname=null, $keyprop=null) {
+ return SimpleTdc::load_mult_with_statement($db, "get_tdcs_by_taskid", array(":task_id" => $taskid), $clsname, $keyprop);
+ }
+
+ function load_member($db) {
+ if ($this->posted_by_coop) {
+ $this->member_name = "By the Co-Operative";
+ } else {
+ $this->member_public = DBMember::load_public_info($db, $this->posted_userid);
+ $this->member_name = $this->member_public->display_name;
+ }
+ }
+
+ function load_all($db) {
+ $this->load_member($db);
+ }
+}
+
+class SimpleTask extends SimpleDBMixin {
+ // tasks table values
+ public $taskid = null;
+ public $admin_userid = null;
+ public $last_updated_table_version = null;
+ public $title = null;
+ public $description = null;
+ public $state = null;
+ // loaded via. admin_userid (DBMember with userid, username and full_name filled in, see $this->load_admin -- using a join might also be a good idea but then you have to replicate the display_name code or change DBMember's construct to pass in args directly)
+ public $member_public = null;
+ // loaded from task_dividend_credits, see $this->load_tdcs()
+ public $tdcs = null;
+ // loaded from task_claims, see $this->load_claims() or $this->load_full()
+ public $claims = null;
+
+ static function get_all_simple($db, $clsname=null, $keyprop=null) {
+ return SimpleTask::load_mult_with_statement($db, "get_all_tasks_simple", array(), $clsname, $keyprop);
+ }
+
+ static function get_all_full($db, $clsname=null, $keyprop=null) {
+ return SimpleTask::load_mult_with_statement($db, "get_all_tasks", array(), $clsname, $keyprop);
+ }
+
+ function load_admin($db) {
+ $this->member_public = DBMember::load_public_info($db, $this->admin_userid);
+ }
+
+ function load_claims($db, $full) {
+ $this->claims = SimpleTaskClaim::get_claims_by_taskid($db, $this->taskid, null, "claim_id");
+ if ($full) {
+ foreach ($this->claims as $k => $claim) {
+ $claim->load_all($db);
+ }
+ }
+ }
+
+ function load_tdcs($db, $full) {
+ $this->tdcs = SimpleTdc::get_tdcs_by_taskid($db, $this->taskid, null, "tdc_id");
+ if ($full) {
+ foreach ($this->tdcs as $k => $tdc) {
+ $tdc->load_all($db);
+ }
+ }
+ }
+
+ function load_all($db) {
+ $this->load_admin($db);
+ $this->load_tdcs($db, true);
+ $this->load_claims($db, true);
+ }
+}
+
+class SessionInfo {
+ public $db = null;
+ public $login_member = null;
+
+ function __construct($db, $logout) {
+ $this->db = $db;
+ session_start();
+ if ($logout) {
+ $this->logout();
+ }
+ $this->re_init();
+ }
+
+ function logout() {
+ session_unset();
+ $this->login_member = null;
+ }
+
+ function re_init() {
+ $this->login_member = null;
+ if (isset($_SESSION["logged_in"]) && $_SESSION["logged_in"] && isset($_SESSION["login_username"])) {
+ // TODO: validate login, either based on password, password reset time or possibly allow users to log out sessions explicitly (might require database session storage to make it easier)
+ $this->login_member = DBMember::load_by($this->db, "username", $_SESSION["login_username"]);
+ }
+ }
+}
+?>