diff options
Diffstat (limited to 'common/db_classes.php')
-rw-r--r-- | common/db_classes.php | 346 |
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"]); + } + } +} +?> |