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"]); +		} +	} +} +?>  | 
