summaryrefslogtreecommitdiff
path: root/common/db_classes.php
blob: dde26eb54ae61594871dcceec65880b4f30fe140 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
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"]);
		}
	}
}
?>