Initial import
This commit is contained in:
12
lib/AntiSpam.php
Executable file
12
lib/AntiSpam.php
Executable file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
class AntiSpam {
|
||||
public static function tarpit() {
|
||||
while (true) {
|
||||
print(chr(rand() & 0xff));
|
||||
ob_flush();
|
||||
flush();
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
77
lib/App.php
Executable file
77
lib/App.php
Executable file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
class App {
|
||||
|
||||
static function dispatch($req) {
|
||||
|
||||
$apiroute = Routes::find_api($req->type(), $req->path());
|
||||
if ($apiroute !== false) { // It is an API route. Treat it as such.
|
||||
$req->set_route($apiroute);
|
||||
$out = $apiroute->call($req);
|
||||
header("Content-type: application/json");
|
||||
if ($out instanceof Collection) {
|
||||
print(json_encode($out->all()));
|
||||
return;
|
||||
}
|
||||
if (is_array($out)) { // This is an array return type. Do things depending on the result key
|
||||
|
||||
$status = $out[0];
|
||||
$data = $out[1];
|
||||
if (count($out) > 2) {
|
||||
$headers = $out[2];
|
||||
} else {
|
||||
$headers = [];
|
||||
}
|
||||
|
||||
header("HTTP/1.1 $status");
|
||||
foreach ($headers as $k=>$v) {
|
||||
header("$k: $v");
|
||||
}
|
||||
if (is_array($data)) {
|
||||
print(json_encode($data));
|
||||
} else {
|
||||
print($data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
print(json_encode($out));
|
||||
return;
|
||||
}
|
||||
|
||||
$webroute = Routes::find_web($req->type(), $req->path());
|
||||
if ($webroute !== false) { // It is a WEB route. Treat it as such.
|
||||
$req->set_route($webroute);
|
||||
$out = $webroute->call($req);
|
||||
if (is_array($out)) { // This is an array return type. Do things depending on the result key
|
||||
|
||||
$status = $out[0];
|
||||
$data = $out[1];
|
||||
if (count($out) > 2) {
|
||||
$headers = $out[2];
|
||||
} else {
|
||||
$headers = [];
|
||||
}
|
||||
|
||||
header("HTTP/1.1 $status");
|
||||
foreach ($headers as $k=>$v) {
|
||||
header("$k: $v");
|
||||
}
|
||||
print($data);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($out instanceof File) {
|
||||
$out->set_header("Content-Length", $out->size());
|
||||
$out->emit();
|
||||
return;
|
||||
}
|
||||
|
||||
print($out);
|
||||
return;
|
||||
}
|
||||
|
||||
header("HTTP/1.1 404 Not Found");
|
||||
print(blade("404"));
|
||||
|
||||
}
|
||||
}
|
||||
174
lib/Auth.php
Executable file
174
lib/Auth.php
Executable file
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
class Auth {
|
||||
|
||||
static function routes() {
|
||||
Routes::add_web("GET", "/register", ["Auth", "register"]);
|
||||
Routes::add_web("POST", "/register", ["Auth", "do_register"]);
|
||||
Routes::add_web("GET", "/login", ["Auth", "login"]);
|
||||
Routes::add_web("POST", "/login", ["Auth", "do_login"]);
|
||||
Routes::add_web("GET", "/logout", ["Auth", "logout"]);
|
||||
Routes::add_web("GET", "/account", ["Auth", "account"], ["Auth", "logged_in"]);
|
||||
Routes::add_web("POST", "/account/chpass", ["Auth", "chpass"], ["Auth", "logged_in"]);
|
||||
Routes::add_web("GET", "/account/{tab}", ["Auth", "account"], ["Auth", "logged_in"]);
|
||||
}
|
||||
|
||||
static function chpass($_request) {
|
||||
$session = Session::all_data();
|
||||
$user = get_user();
|
||||
$current = $_request->post("current");
|
||||
$chash = hash("sha256", $current);
|
||||
if ($chash != $user->password) {
|
||||
flash("error", "Wrong password. Try again.");
|
||||
return redirect("/account");
|
||||
}
|
||||
|
||||
$p1 = $_request->post("pass1");
|
||||
$p2 = $_request->post("pass2");
|
||||
|
||||
if ($p1 != $p2) {
|
||||
flash("error", "Your new passwords don't match.");
|
||||
return redirect("/account");
|
||||
}
|
||||
|
||||
if (strlen($p1) < 6) {
|
||||
flash("error", "Your new password is too short. Pick one that is 6 characters or more.");
|
||||
return redirect("/account");
|
||||
}
|
||||
|
||||
|
||||
$user->password = hash("sha256", $p1);
|
||||
$user->save();
|
||||
flash("success", "Your password has been changed.");
|
||||
return redirect("/account");
|
||||
}
|
||||
|
||||
static function account($tab = "account") {
|
||||
$session = Session::all_data();
|
||||
return blade("account", ["tab" => $tab, "session" => $session, "user" => get_user()]);
|
||||
}
|
||||
|
||||
static function can_upload() {
|
||||
$user = get_user();
|
||||
if (!$user) return false;
|
||||
return $user->can_upload == "Y";
|
||||
}
|
||||
|
||||
static function can_moderate() {
|
||||
$user = get_user();
|
||||
if (!$user) return false;
|
||||
return $user->can_moderate == "Y";
|
||||
}
|
||||
|
||||
static function register() {
|
||||
if (array_key_exists("HTTP_REFERER", $_SERVER)) {
|
||||
Session::set("login_return", $_SERVER['HTTP_REFERER']);
|
||||
} else {
|
||||
Session::set("login_return", "/");
|
||||
}
|
||||
return blade("register");
|
||||
}
|
||||
|
||||
static function do_register($_request) {
|
||||
$username = $_request->post("username");
|
||||
$email = $_request->post("email");
|
||||
$password = $_request->post("password");
|
||||
$confirm = $_request->post("confirm");
|
||||
|
||||
if ($username == "") {
|
||||
flash("error", "You haven't provided a username.");
|
||||
return blade("register", ["username" => $username, "email" => $email, "password" => $password, "confirm" => $confirm]);
|
||||
}
|
||||
|
||||
if ($email == "") {
|
||||
flash("error", "You haven't provided an email address.");
|
||||
return blade("register", ["username" => $username, "email" => $email, "password" => $password, "confirm" => $confirm]);
|
||||
}
|
||||
|
||||
if (strlen($password) < 6) {
|
||||
flash("error", "You need to give a password of at least 6 characters.");
|
||||
return blade("register", ["username" => $username, "email" => $email, "password" => $password, "confirm" => $confirm]);
|
||||
}
|
||||
|
||||
if ($password != $confirm) {
|
||||
flash("error", "Your two passwords do not match.");
|
||||
return blade("register", ["username" => $username, "email" => $email, "password" => $password, "confirm" => $confirm]);
|
||||
}
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
flash("error", "Your email address doesn't appear to be valid.");
|
||||
return blade("register", ["username" => $username, "email" => $email, "password" => $password, "confirm" => $confirm]);
|
||||
}
|
||||
|
||||
$exist = User::find([["username", "=", strtolower($username)]])->first();
|
||||
if ($exist) {
|
||||
flash("error", "That username has already been taken. Pick another.");
|
||||
return blade("register", ["username" => $username, "email" => $email, "password" => $password, "confirm" => $confirm]);
|
||||
}
|
||||
|
||||
$exist = User::find([["email", "=", $email]])->first();
|
||||
if ($exist) {
|
||||
flash("error", "An account with that email address already exists.");
|
||||
return blade("register", ["username" => $username, "email" => $email, "password" => $password, "confirm" => $confirm]);
|
||||
}
|
||||
|
||||
$u = new User;
|
||||
$u->username = $username;
|
||||
$u->email = $email;
|
||||
$u->password = hash("sha256", $password);
|
||||
$u->save();
|
||||
|
||||
Session::set("user", $u->id);
|
||||
|
||||
|
||||
flash("success", "Registration successful. You have also been automatically logged in.");
|
||||
$ret = Session::get("login_return");
|
||||
return redirect($ret);
|
||||
|
||||
}
|
||||
|
||||
static function logged_in() {
|
||||
$user = get_user();
|
||||
return $user !== false;
|
||||
}
|
||||
|
||||
static function do_login() {
|
||||
$username = $_POST['username'];
|
||||
$password = $_POST['password'];
|
||||
|
||||
if (!$username) return blade("login");
|
||||
if (!$password) return blade("login");
|
||||
|
||||
$user = User::find([["username", "=", $username]])->first();
|
||||
if (!$user) {
|
||||
$user = User::find([["email", "=", $username]])->first();
|
||||
}
|
||||
|
||||
$pwe = hash("sha256", $password);
|
||||
if ($pwe == $user->password) {
|
||||
Session::set("user", $user->id);
|
||||
flash("success", "Log in successful");
|
||||
$ret = Session::get("login_return");
|
||||
return redirect($ret);
|
||||
}
|
||||
flash("error", "Invalid username or password.");
|
||||
return blade("login");
|
||||
}
|
||||
|
||||
public static function login() {
|
||||
if (array_key_exists("HTTP_REFERER", $_SERVER)) {
|
||||
Session::set("login_return", $_SERVER['HTTP_REFERER']);
|
||||
} else {
|
||||
Session::set("login_return", "/");
|
||||
}
|
||||
return blade("login");
|
||||
}
|
||||
|
||||
public static function logout() {
|
||||
Session::unset("user");
|
||||
return redirect($_SERVER['HTTP_REFERER']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
193
lib/Collection.php
Executable file
193
lib/Collection.php
Executable file
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
class Collection implements Countable,Iterator,ArrayAccess {
|
||||
|
||||
private $records;
|
||||
|
||||
private $position = 0;
|
||||
|
||||
public function __construct($data = null) {
|
||||
if ($data !== null) {
|
||||
$this->records = $data;
|
||||
} else {
|
||||
$this->records = [];
|
||||
}
|
||||
}
|
||||
|
||||
public function add($val) {
|
||||
$this->records[] = $val;
|
||||
}
|
||||
|
||||
public function first() {
|
||||
if (count($this->records) == 0) return null;
|
||||
return $this->records[0];
|
||||
}
|
||||
|
||||
public function last() {
|
||||
if (count($this->records) == 0) return null;
|
||||
return $this->records[count($this->records)-1];
|
||||
}
|
||||
|
||||
public function pop() {
|
||||
if (count($this->records) == 0) return null;
|
||||
return array_pop($this->records);
|
||||
}
|
||||
|
||||
public function shift() {
|
||||
if (count($this->records) == 0) return null;
|
||||
return array_shift($this->records);
|
||||
}
|
||||
|
||||
public function push($val) {
|
||||
return array_push($this->records, $val);
|
||||
}
|
||||
|
||||
public function unshift($val) {
|
||||
return array_unshift($this->records, $val);
|
||||
}
|
||||
|
||||
public function get($n) {
|
||||
return isset($this->records[$offset]) ? $this->records[$offset] : null;
|
||||
}
|
||||
|
||||
public function all() {
|
||||
return $this->records;
|
||||
}
|
||||
|
||||
// Countable
|
||||
public function count() : int {
|
||||
return count($this->records);
|
||||
}
|
||||
|
||||
// Iterator
|
||||
public function rewind() : void {
|
||||
$this->position = 0;
|
||||
}
|
||||
|
||||
public function current() : mixed {
|
||||
return $this->records[$this->position];
|
||||
}
|
||||
|
||||
public function key() : mixed {
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function next() : void {
|
||||
++$this->position;
|
||||
}
|
||||
|
||||
public function valid() : bool {
|
||||
return isset($this->records[$this->position]);
|
||||
}
|
||||
|
||||
// ArrayAccess
|
||||
public function offsetSet($offset, $value) : void {
|
||||
if (is_null($offset)) {
|
||||
$this->push($value);
|
||||
} else {
|
||||
$this->records[$offset] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function offsetExists($offset) : bool {
|
||||
return isset($this->records[$offset]);
|
||||
}
|
||||
|
||||
public function offsetUnset($offset) : void {
|
||||
unset($this->records[$offset]);
|
||||
}
|
||||
|
||||
public function offsetGet($offset) : mixed {
|
||||
return isset($this->records[$offset]) ? $this->records[$offset] : null;
|
||||
}
|
||||
|
||||
public function each($f) {
|
||||
if ($f instanceof Closure) {
|
||||
foreach ($this->records as $r) {
|
||||
$f->call($this, $r);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_array($f)) {
|
||||
if (method_exists($f[0], $f[1])) {
|
||||
$cl = $f[0];
|
||||
$fun = $f[1];
|
||||
foreach ($this->records as $r) {
|
||||
$cl::$fun($r);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public function sort($field, $ins=false) {
|
||||
usort($this->records, function ($a, $b) use ($field,$ins) {
|
||||
|
||||
$aa = $a->$field;
|
||||
$bb = $b->$field;
|
||||
|
||||
if ($ins) {
|
||||
$aa = strtolower_null($aa);
|
||||
$bb = strtolower_null($bb);
|
||||
}
|
||||
|
||||
if ($aa > $bb) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($aa < $bb) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
public function sort_with_function($func) {
|
||||
usort($this->records, $func);
|
||||
}
|
||||
|
||||
|
||||
public function range($start, $len = 0) {
|
||||
|
||||
if ($len == 0) {
|
||||
// 0 .. $start
|
||||
return array_splice($this->records, 0, $start);
|
||||
}
|
||||
|
||||
return array_splice($this->records, $start, $len);
|
||||
}
|
||||
|
||||
public function glob($key, $pattern) {
|
||||
$nc = new Collection;
|
||||
|
||||
foreach ($this->records as $r) {
|
||||
if (fnmatch(strtolower_null($pattern), strtolower_null($r->$key))) {
|
||||
$nc->push($r);
|
||||
}
|
||||
}
|
||||
return $nc;
|
||||
}
|
||||
|
||||
public function merge($other) {
|
||||
$o = new Collection;
|
||||
foreach ($this->records as $r) {
|
||||
$o->push($r);
|
||||
}
|
||||
foreach ($other->all() as $r) {
|
||||
$o->push($r);
|
||||
}
|
||||
return $o;
|
||||
}
|
||||
|
||||
public static function from_array($arr) {
|
||||
$c = new Collection();
|
||||
foreach ($arr as $k=>$v) {
|
||||
$ob = new stdClass;
|
||||
$ob->key = $k;
|
||||
$ob->value = $v;
|
||||
$c->pusk($ob);
|
||||
}
|
||||
return $c;
|
||||
}
|
||||
}
|
||||
21
lib/Config.php
Executable file
21
lib/Config.php
Executable file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
class Config {
|
||||
private static $config = null;
|
||||
|
||||
private function __construct() {
|
||||
}
|
||||
|
||||
public static function get(string $val, mixed $default = null) : mixed {
|
||||
if (Config::$config == null) {
|
||||
Config::$config = parse_ini_file(__DIR__ . "/../.env");
|
||||
}
|
||||
if (array_key_exists($val, Config::$config)) {
|
||||
return Config::$config[$val];
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
141
lib/DB.php
Executable file
141
lib/DB.php
Executable file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
class DB {
|
||||
|
||||
private static $instance = null;
|
||||
private $db = null;
|
||||
|
||||
private function __construct() {
|
||||
}
|
||||
|
||||
public static function getInstance() {
|
||||
if (DB::$instance == null) {
|
||||
DB::$instance = new DB;
|
||||
}
|
||||
return DB::$instance;
|
||||
}
|
||||
|
||||
public function connect($dbuser, $dbpass, $dbhost, $dbname) {
|
||||
$this->db = new \PDO("mysql:dbname=$dbname;host=$dbhost;charset=utf8mb4",$dbuser,$dbpass);
|
||||
//$this->query("SET NAMES 'utf8' COLLATE 'utf8_general_ci'");
|
||||
}
|
||||
|
||||
function query($query,$params = array()) {
|
||||
$q = $this->db->prepare($query);
|
||||
$q->execute($params);
|
||||
$e = $q->errorInfo();
|
||||
if($e[0]!='00000') {
|
||||
print "<span class='error'>";
|
||||
print $e[2];
|
||||
print "</span>";
|
||||
return false;
|
||||
}
|
||||
return $q;
|
||||
}
|
||||
|
||||
function nextRecord($query) {
|
||||
$next = $query->fetchObject();
|
||||
return $next;
|
||||
}
|
||||
|
||||
function id() {
|
||||
return $this->db->lastInsertId();
|
||||
}
|
||||
|
||||
function update($table,$id,$data) {
|
||||
$values = array();
|
||||
foreach($data as $k=>$v) {
|
||||
$values[] = "`" . $k . "`" . "=:" . $k;
|
||||
}
|
||||
$query = sprintf("UPDATE `%s` set " . implode(",",$values) . " WHERE id=:id",$table);
|
||||
$data['id'] = $id;
|
||||
|
||||
$q = $this->query($query,$data);
|
||||
$id = $this->id();
|
||||
return $id;
|
||||
}
|
||||
|
||||
function insert($table,$data) {
|
||||
$fields = array();
|
||||
$values = array();
|
||||
foreach($data as $k=>$v) {
|
||||
$fields[] = $k;
|
||||
$values[] = ":" . $k;
|
||||
}
|
||||
$query = sprintf("INSERT IGNORE INTO `%s` (" . implode(",",$fields) . ") VALUES (" . implode(",",$values) . ")",$table);
|
||||
$q = $this->query($query,$data);
|
||||
$id = $this->id();
|
||||
return $id;
|
||||
}
|
||||
|
||||
function set($table,$record,$field,$value) {
|
||||
$this->query("UPDATE `$table` SET `$field`=:f WHERE id=:i",array(
|
||||
'f'=>$value,
|
||||
'i'=>$record
|
||||
));
|
||||
}
|
||||
|
||||
function select($table,$record) {
|
||||
$query = sprintf("SELECT * FROM `%s` WHERE id=:id",$table);
|
||||
$q = $this->query($query,array("id" => $record));
|
||||
$r = $this->nextRecord($q);
|
||||
return $r;
|
||||
}
|
||||
|
||||
function getTableStructure($table) {
|
||||
$query = sprintf("DESCRIBE `%s`", $table);
|
||||
$q = $this->query($query);
|
||||
$fields = [];
|
||||
|
||||
while ($r = $this->nextRecord($q)) {
|
||||
$field = $r['Field'];
|
||||
$type = $r['Type'];
|
||||
$extra = $r['Extra'];
|
||||
|
||||
$fingerprint = trim("$type $extra");
|
||||
|
||||
$f = new stdClass;
|
||||
$f->name = $field;
|
||||
|
||||
if (preg_match('/^(.*)\((\d+)\)(.*)$/', $fingerprint, $m)) {
|
||||
$fingerprint = $m[1] . $m[3];
|
||||
$f->length = $m[2];
|
||||
}
|
||||
|
||||
|
||||
$f->type == -1;
|
||||
$f->unsigned = false;
|
||||
|
||||
switch ($fingerprint) {
|
||||
case "bigint unsigned auto_increment": $f->type = MODEL_SERIAL; break;
|
||||
|
||||
case "text": $f->type = MODEL_TEXT; break;
|
||||
case "longtext": $f->type = MODEL_TEXT; break;
|
||||
|
||||
case "blob": $f->type = MODEL_TEXT; break;
|
||||
case "longblob": $f->type = MODEL_TEXT; break;
|
||||
|
||||
case "tinyint": $f->type = MODEL_TINYINT; break;
|
||||
case "tinyint unsigned": $f->type = MODEL_TINYINT; $f->unsigned = true; break;
|
||||
|
||||
case "int": $f->type = MODEL_INT; break;
|
||||
case "int unsigned": $f->type = MODEL_INT; $f->unsigned = true; break;
|
||||
|
||||
case "bigint": $f->type = MODEL_BIGINT; break;
|
||||
case "bigint unsigned": $f->type = MODEL_BIGINT; $f->unsigned = true; break;
|
||||
|
||||
case "char": $f->type = MODEL_CHAR; break;
|
||||
case "varchar": $f->type = MODEL_VARCHAR; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function all($query) {
|
||||
$o = new Collection;
|
||||
while ($r = $this->nextRecord($query)) {
|
||||
$o->push($r);
|
||||
}
|
||||
return $o;
|
||||
}
|
||||
}
|
||||
|
||||
103
lib/ErrorHandler.php
Executable file
103
lib/ErrorHandler.php
Executable file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
require_once("Config.php");
|
||||
|
||||
class ErrorHandler {
|
||||
public static function checkForFatalCrash() {
|
||||
$error = error_get_last();
|
||||
if ($error) {
|
||||
if ($error['type'] == E_ERROR) {
|
||||
ErrorHandler::handleException(new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function printErrorEntry($type, $message, $file, $line) {
|
||||
?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public static function getFileLines($file, $start, $end) {
|
||||
$f = file_get_contents($file);
|
||||
$l = explode("\n", $f);
|
||||
if ($start < 0) $start = 0;
|
||||
if ($end >= count($l)) $end = count($l) -1;
|
||||
|
||||
$out = [];
|
||||
for ($i = $start; $i <= $end; $i++) {
|
||||
$out[] = $l[$i];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public static function handleException($e) {
|
||||
header("Content-Type: text/html");
|
||||
header("X-Error-Message: " . $e->getMessage());
|
||||
header("X-Error-Line: " . $e->getLine());
|
||||
header("X-Error-File: " . $e->getFile());
|
||||
header("X-Error-Code: " . $e->getCode());
|
||||
$trace = $e->getTrace();
|
||||
array_shift($trace);
|
||||
|
||||
$i = 0;
|
||||
foreach ($trace as $t) {
|
||||
$i++;
|
||||
header("X-Error-Trace-" . $i . ": " . $t['file'] . "(" . $t['line'] . ")");
|
||||
}
|
||||
|
||||
print("<div style='background-color: #ffaaaa;'>");
|
||||
print("<h3>" . $e->getMessage() . "</h3>");
|
||||
print("At Line " . $e->getLine() . " of " . $e->getFile() . "<br>");
|
||||
|
||||
$line = $e->getLine() - 1;
|
||||
|
||||
$pre = ErrorHandler::getFileLines($e->getFile(), $line - 5, $line - 1);
|
||||
$line = ErrorHandler::getFileLines($e->getFile(), $line, $line);
|
||||
$post = ErrorHandler::getFileLines($e->getFile(), $line + 1, $line + 5);
|
||||
|
||||
print("<pre>");
|
||||
|
||||
foreach ($pre as $l) {
|
||||
print($l . "\n");
|
||||
}
|
||||
print($line[0] . " <---\n");
|
||||
foreach ($post as $l) {
|
||||
print($l . "\n");
|
||||
}
|
||||
|
||||
print("</pre>");
|
||||
print("</div>");
|
||||
|
||||
$trace = $e->getTrace();
|
||||
array_shift($trace);
|
||||
|
||||
foreach ($trace as $t) {
|
||||
if (array_key_exists("class", $t) && array_key_exists("function", $t)) {
|
||||
print("<div>");
|
||||
print("<h5>From " . $t['class'] . "::" . $t['function'] . "</h5>");
|
||||
print("At Line " . $t['line'] . " of " . $t['file'] . "<br>");
|
||||
print("</div>");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static function handleError($num, $str, $file, $line, $context = null) {
|
||||
ErrorHandler::handleException(new ErrorException($str, 0, $num, $file, $line));
|
||||
}
|
||||
|
||||
public static function hook() {
|
||||
|
||||
if (Config::get("DEBUG")) {
|
||||
ini_set("display_errors", "on");
|
||||
error_reporting(E_ALL);
|
||||
} else {
|
||||
ini_set("display_errors", "off");
|
||||
}
|
||||
// register_shutdown_function("ErrorHandler::checkForFatalCrash");
|
||||
// set_error_handler("ErrorHandler::handleError");
|
||||
// set_exception_handler("ErrorHandler::handleException");
|
||||
}
|
||||
|
||||
}
|
||||
128
lib/File.php
Executable file
128
lib/File.php
Executable file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
class File {
|
||||
private $_path = null;
|
||||
private $_headers = [];
|
||||
|
||||
public function __construct($path = null) {
|
||||
if ($path instanceof File) {
|
||||
$this->_path = $path->path();
|
||||
}
|
||||
|
||||
$this->_path = $path;
|
||||
}
|
||||
|
||||
public function basename() {
|
||||
return pathinfo($this->_path, PATHINFO_BASENAME);
|
||||
}
|
||||
|
||||
public function dirname() {
|
||||
return pathinfo($this->_path, PATHINFO_DIRNAME);
|
||||
}
|
||||
|
||||
public function extension() {
|
||||
return pathinfo($this->_path, PATHINFO_EXTENSION);
|
||||
}
|
||||
|
||||
public function filename() {
|
||||
return pathinfo($this->_path, PATHINFO_FILENAME);
|
||||
}
|
||||
|
||||
public function delete() { $this->unlink(); }
|
||||
public function unlink() {
|
||||
unlink($this->_path);
|
||||
}
|
||||
|
||||
public function content() {
|
||||
return get_file_contents($this->_path);
|
||||
}
|
||||
|
||||
public function emit() {
|
||||
$headers = $this->_headers;
|
||||
|
||||
$headers['ETag'] = "\"" . $this->hash() . "\"";
|
||||
|
||||
foreach ($headers as $k=>$v) {
|
||||
header($k . ": " . $v);
|
||||
}
|
||||
readfile($this->_path);
|
||||
}
|
||||
|
||||
public function set_headers($data) {
|
||||
$this->_headers = $data;
|
||||
}
|
||||
|
||||
public function get_headers() {
|
||||
return $this->_headers;
|
||||
}
|
||||
|
||||
public function set_header($k, $v) {
|
||||
$this->_headers[trim($k)] = trim($v);
|
||||
}
|
||||
|
||||
public function exists() {
|
||||
return file_exists($this->_path);
|
||||
}
|
||||
|
||||
public function size() {
|
||||
return stat($this->_path)["size"];
|
||||
}
|
||||
|
||||
public function path() {
|
||||
return $this->_path;
|
||||
}
|
||||
|
||||
public function mime() {
|
||||
if (!file_exists($this->_path)) {
|
||||
return false;
|
||||
}
|
||||
return mime_content_type($this->_path);
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return $this->_path;
|
||||
}
|
||||
|
||||
public function parent() {
|
||||
$p = $this->dirname();
|
||||
if ($p == "") {
|
||||
return false;
|
||||
}
|
||||
return new File($p);
|
||||
}
|
||||
|
||||
public function mkdir() {
|
||||
$p = $this->parent();
|
||||
if ($p) {
|
||||
if (!$p->exists()) {
|
||||
$p->mkdir();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->exists()) {
|
||||
mkdir($this->_path, 0777);
|
||||
}
|
||||
}
|
||||
|
||||
public function hash($type = "sha256") {
|
||||
return hash_file($type, $this->_path);
|
||||
}
|
||||
|
||||
public function rename($to, $over=false) {
|
||||
if ($to == $this->path()) return false;
|
||||
if ((!$over) && file_exists($to)) return false;
|
||||
copy($this->path(), $to);
|
||||
if (!file_exists($to)) return false;
|
||||
unlink($this->path());
|
||||
$this->_path = $to;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function get_chunk($from, $len) {
|
||||
$f = fopen($this->_path, "r");
|
||||
fseek($f, $from);
|
||||
$data = fread($f, $len);
|
||||
fclose($f);
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
44
lib/Form.php
Executable file
44
lib/Form.php
Executable file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
class Form {
|
||||
|
||||
public static function input($title, $name, $value, $attrs = []) {
|
||||
$at = [];
|
||||
foreach ($attrs as $k=>$v) {
|
||||
$at[] = "$k = \"$v\"";
|
||||
}
|
||||
|
||||
return blade("forms.input", [
|
||||
"title" => $title,
|
||||
"name" => $name,
|
||||
"value" => $value,
|
||||
"attrs" => implode(" ", $at)
|
||||
]);
|
||||
}
|
||||
|
||||
public static function password($title, $name, $value, $attrs = []) {
|
||||
$at = [];
|
||||
foreach ($attrs as $k=>$v) {
|
||||
$at[] = "$k = \"$v\"";
|
||||
}
|
||||
|
||||
return blade("forms.password", [
|
||||
"title" => $title,
|
||||
"name" => $name,
|
||||
"value" => $value,
|
||||
"attrs" => implode(" ", $at)
|
||||
]);
|
||||
}
|
||||
|
||||
public static function submit($title, $attrs = []) {
|
||||
$at = [];
|
||||
foreach ($attrs as $k=>$v) {
|
||||
$at[] = "$k = \"$v\"";
|
||||
}
|
||||
|
||||
return blade("forms.submit", [
|
||||
"title" => $title,
|
||||
"attrs" => implode(" ", $at)
|
||||
]);
|
||||
}
|
||||
}
|
||||
240
lib/Gemini.php
Normal file
240
lib/Gemini.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
require_once(__DIR__ . "/File.php");
|
||||
require_once(__DIR__ . "/HTTPRequest.php");
|
||||
|
||||
class Gemini {
|
||||
|
||||
public $file_uri = "";
|
||||
|
||||
public $root = "https://generativelanguage.googleapis.com";
|
||||
|
||||
public $verbose = false;
|
||||
|
||||
private $_upload_callback = false;
|
||||
private $_process_callback = false;
|
||||
|
||||
public static function get_key() {
|
||||
$i = 0;
|
||||
$keys = [];
|
||||
|
||||
while ($k = Config::get("GEMINI_KEY_" . $i)) {
|
||||
$keys[] = $k;
|
||||
$i++;
|
||||
}
|
||||
|
||||
$keyid = rand(0, count($keys) - 1);
|
||||
|
||||
$key = $keys[$keyid];
|
||||
return $key;
|
||||
}
|
||||
|
||||
public function upload_file(File $file, $key = false) {
|
||||
|
||||
set_time_limit(300);
|
||||
|
||||
if ($this->verbose) {
|
||||
print("Uploading file...\n");
|
||||
flush();
|
||||
}
|
||||
$cb = false;
|
||||
|
||||
if ($this->_upload_callback) {
|
||||
$cb = $this->_upload_callback;
|
||||
}
|
||||
|
||||
if ($cb) $cb(0);
|
||||
|
||||
|
||||
if ($key == false) {
|
||||
$key = Gemini::get_key();
|
||||
}
|
||||
|
||||
$json = "{'file': {'display_name': '" . $file->basename() . "'}}";
|
||||
|
||||
if ($this->verbose) {
|
||||
print("Upload data: " . $json . "\n");
|
||||
flush();
|
||||
}
|
||||
|
||||
|
||||
$h = new HTTPRequest();
|
||||
|
||||
$r = $h->post($this->root . "/upload/v1beta/files", $json, [
|
||||
"x-goog-api-key: " . $key,
|
||||
"Content-Type: application/json",
|
||||
"X-Goog-Upload-Protocol: resumable",
|
||||
"X-Goog-Upload-Command: start",
|
||||
"X-Goog-Upload-Header-Content-Length: " . $file->size(),
|
||||
"X-Goog-Upload-Header-Content-Type: " . $file->mime()
|
||||
]);
|
||||
|
||||
if ($this->verbose) {
|
||||
print("Request data:\n");
|
||||
print_r($h);
|
||||
flush();
|
||||
}
|
||||
|
||||
if ($r != 200) {
|
||||
throw new Exception("Error uploading file to Gemini", $r);
|
||||
}
|
||||
|
||||
$url = $h->headers['x-goog-upload-url'];
|
||||
$chunksize = $h->headers['x-goog-upload-chunk-granularity'];
|
||||
|
||||
$size = $file->size();
|
||||
$s = $size;
|
||||
|
||||
$pos = 0;
|
||||
|
||||
while ($s > 0) {
|
||||
|
||||
$chunk = min($s, $chunksize);
|
||||
|
||||
$pct = round($pos / $size * 100);
|
||||
if ($cb) $cb($pct);
|
||||
|
||||
if ($this->verbose) {
|
||||
printf("***** %d%%\n", round($pct));
|
||||
flush();
|
||||
}
|
||||
|
||||
$data = $file->get_chunk($pos, $chunk);
|
||||
$s -= $chunk;
|
||||
|
||||
$fin = $s == 0 ? ", finalize" : "";
|
||||
|
||||
$r = $h->post($url, $data, [
|
||||
"Content-Length: " . $chunk,
|
||||
"X-Goog-Upload-Offset: " . $pos,
|
||||
"X-Goog-Upload-Command: upload$fin"
|
||||
]);
|
||||
|
||||
if ($r != 200) {
|
||||
throw new Exception("Error uploading chunk $pos to Gemini", $r);
|
||||
}
|
||||
|
||||
if ($this->verbose) {
|
||||
print_r($r);
|
||||
flush();
|
||||
}
|
||||
|
||||
$pos += $chunk;
|
||||
}
|
||||
|
||||
$d = json_decode($h->body);
|
||||
|
||||
if ($this->verbose) {
|
||||
print("Final returned body:\n");
|
||||
print_r($d);
|
||||
}
|
||||
|
||||
if ($cb) $cb(100);
|
||||
return $d->file->uri;
|
||||
}
|
||||
|
||||
|
||||
public function geminiOverview(File $file) {
|
||||
|
||||
$cb = false;
|
||||
if ($this->_process_callback) $cb = $this->_process_callback;
|
||||
|
||||
if ($cb) $cb("Uploading file");
|
||||
|
||||
if ($this->verbose) {
|
||||
print("<pre>");
|
||||
}
|
||||
|
||||
$key = Gemini::get_key();
|
||||
|
||||
$uri = $this->upload_file($file, $key);
|
||||
|
||||
|
||||
$ob = [
|
||||
"contents" => [
|
||||
[
|
||||
"parts" => [
|
||||
[
|
||||
"text" => "Summarize this document. In addition provide me the title of the document as the first line, surrounded by { and }. If it is all in capitals, re-capitalize it to make it easier to read. On the second line give the document order number surrounded by [ and ].",
|
||||
],
|
||||
[
|
||||
"file_data" => [
|
||||
"mime_type" => $file->mime(),
|
||||
"file_uri" => $uri
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$json = json_encode($ob, JSON_PRETTY_PRINT);
|
||||
|
||||
if ($this->verbose) {
|
||||
print("AI reuqest: " . $json . "\n");
|
||||
flush();
|
||||
}
|
||||
set_time_limit(300);
|
||||
|
||||
|
||||
if ($cb) $cb("Sending request");
|
||||
|
||||
$h = new HTTPRequest();
|
||||
$r = $h->post($this->root . "/v1beta/models/gemini-2.5-flash-lite:generateContent", $json, [
|
||||
"x-goog-api-key: " . $key,
|
||||
"Content-Type: application/json"
|
||||
]);
|
||||
|
||||
if ($cb) $cb("Processing response");
|
||||
|
||||
if ($this->verbose) {
|
||||
print("AI Response:\n");
|
||||
print_r($h);
|
||||
flush();
|
||||
}
|
||||
|
||||
$resp = json_decode($h->body);
|
||||
|
||||
if ($r != 200) {
|
||||
if (@$resp->error) {
|
||||
throw new Exception($resp->error->message, $resp->error->code);
|
||||
} else {
|
||||
throw new Exception("HTTP Error $r requesting AI assistance", $r);
|
||||
}
|
||||
}
|
||||
|
||||
if (property_exists($resp, "candidates")) {
|
||||
$text = $resp->candidates[0]->content->parts[0]->text;
|
||||
$lines = explode("\n", $text);
|
||||
|
||||
$lastLine = "";
|
||||
$out = [];
|
||||
foreach ($lines as $line) {
|
||||
if (str_starts_with($line, "* ") and !str_starts_with($lastLine, "* ")) {
|
||||
$lastLine = $line;
|
||||
$line = "\n" . $line;
|
||||
} else if (str_starts_with($line, "1. ") and (trim($lastLine) != "")) {
|
||||
$lastLine = $line;
|
||||
$line = "\n" . $line;
|
||||
} else {
|
||||
$lastLine = $line;
|
||||
}
|
||||
|
||||
$out[] = $line;
|
||||
}
|
||||
|
||||
|
||||
return implode("\n", $out);
|
||||
}
|
||||
|
||||
throw new Exception("Content missing processing AI data", 0);
|
||||
}
|
||||
|
||||
public function upload_callback(callable $cb) {
|
||||
$this->_upload_callback = $cb;
|
||||
}
|
||||
|
||||
public function process_callback(callable $cb) {
|
||||
$this->_process_callback = $cb;
|
||||
}
|
||||
}
|
||||
112
lib/HTTPRequest.php
Normal file
112
lib/HTTPRequest.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
class HTTPRequest {
|
||||
|
||||
public $body = "";
|
||||
public $headers = [];
|
||||
public $status = 0;
|
||||
public $version = "1.0";
|
||||
|
||||
public $connect_timeout = 2;
|
||||
public $transfer_timeout = 300;
|
||||
|
||||
public $ch = null;
|
||||
|
||||
public function __construct() {
|
||||
$this->ch = curl_init();
|
||||
}
|
||||
|
||||
public function get($url, $headers = [], $headersonly = false) {
|
||||
curl_reset($this->ch);
|
||||
curl_setopt($this->ch, CURLOPT_URL, $url);
|
||||
curl_setopt($this->ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:138.0) Gecko/20100101 Firefox/138.0");
|
||||
curl_setopt($this->ch, CURLOPT_FILETIME, true);
|
||||
curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, $this->connect_timeout);
|
||||
curl_setopt($this->ch, CURLOPT_TIMEOUT, $this->transfer_timeout);
|
||||
curl_setopt($this->ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($this->ch, CURLOPT_NOBODY, $headersonly);
|
||||
curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($this->ch, CURLOPT_HEADER, true);
|
||||
curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
|
||||
$this->status = 0;
|
||||
$response = curl_exec($this->ch);
|
||||
|
||||
$header_size = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE);
|
||||
$header = substr($response, 0, $header_size);
|
||||
$this->body = substr($response, $header_size);
|
||||
$this->headers = [];
|
||||
|
||||
foreach ($hl as $h) {
|
||||
$h = trim($h);
|
||||
if (preg_match('/^HTTP\/([^\s]+)\s+(\d+)\s*/', $h, $m)) {
|
||||
$this->version = $m[1];
|
||||
$this->status = $m[2];
|
||||
continue;
|
||||
}
|
||||
if (str_starts_with($h, " ")) {
|
||||
$this->headers[$curr] .= " " . trim($h);
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^([^:]+):\s+(.*)$/', $h, $m)) {
|
||||
$curr = strtolower($m[1]);
|
||||
$this->headers[$curr] = $m[2];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->status;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function post($url, $data, $headers = [], $headersonly = false) {
|
||||
|
||||
|
||||
curl_reset($this->ch);
|
||||
curl_setopt($this->ch, CURLOPT_URL, $url);
|
||||
curl_setopt($this->ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:138.0) Gecko/20100101 Firefox/138.0");
|
||||
curl_setopt($this->ch, CURLOPT_POST, true);
|
||||
curl_setopt($this->ch, CURLOPT_FILETIME, true);
|
||||
curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, $this->connect_timeout);
|
||||
curl_setopt($this->ch, CURLOPT_TIMEOUT, $this->transfer_timeout);
|
||||
curl_setopt($this->ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($this->ch, CURLOPT_NOBODY, $headersonly);
|
||||
curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($this->ch, CURLOPT_HEADER, true);
|
||||
curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($this->ch, CURLOPT_POSTFIELDS, $data);
|
||||
|
||||
$this->status = 0;
|
||||
|
||||
$response = curl_exec($this->ch);
|
||||
|
||||
$header_size = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE);
|
||||
$header = substr($response, 0, $header_size);
|
||||
$this->body = substr($response, $header_size);
|
||||
$this->headers = [];
|
||||
|
||||
$hl = explode("\n", $header);
|
||||
|
||||
foreach ($hl as $h) {
|
||||
$h = trim($h);
|
||||
if (preg_match('/^HTTP\/([^\s]+)\s+(\d+)\s*/', $h, $m)) {
|
||||
$this->version = $m[1];
|
||||
$this->status = $m[2];
|
||||
continue;
|
||||
}
|
||||
if (str_starts_with($h, " ")) {
|
||||
$this->headers[$curr] .= " " . trim($h);
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^([^:]+):\s+(.*)$/', $h, $m)) {
|
||||
$curr = strtolower($m[1]);
|
||||
$this->headers[$curr] = $m[2];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->status;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
124
lib/Image.php
Executable file
124
lib/Image.php
Executable file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
require_once(__DIR__ . "/File.php");
|
||||
|
||||
class Image extends File {
|
||||
private $_img = null;
|
||||
private $_type = null;
|
||||
|
||||
public function __construct($w, $h = null) {
|
||||
|
||||
if ($w instanceof File) {
|
||||
$w = $w->path();
|
||||
}
|
||||
|
||||
if ($h === null) {
|
||||
parent::__construct($w);
|
||||
$this->_type = $this->mime();
|
||||
} else {
|
||||
$this->_img = ImageCreateTrueColor($w, $h);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getImage() {
|
||||
if ($this->_img == null) {
|
||||
if ($this->_type === false) {
|
||||
$this->_img = ImageCreateTrueColor(1, 1);
|
||||
} else {
|
||||
switch ($this->_type) {
|
||||
case "image/png":
|
||||
$this->_img = ImageCreateFromPNG($this->path());
|
||||
break;
|
||||
case "image/jpeg":
|
||||
$this->_img = ImageCreateFromJPEG($this->path());
|
||||
break;
|
||||
case "image/gif":
|
||||
$this->_img = ImageCreateFromGIF($this->path());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->_img;
|
||||
}
|
||||
|
||||
public function save($fn = null, $type = null) {
|
||||
$newimg = true;
|
||||
if ($fn == null) {
|
||||
$fn = $this->path();
|
||||
$newimg = false;
|
||||
}
|
||||
if ($type == null) {
|
||||
$type = $this->_type;
|
||||
$newimg = false;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case "image/png":
|
||||
ImagePNG($this->getImage(), $fn);
|
||||
break;
|
||||
case "image/jpeg":
|
||||
ImageJPEG($this->getImage(), $fn);
|
||||
break;
|
||||
case "image/gif":
|
||||
ImageGIF($this->getImage(), $fn);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Invalid mime type $type specified");
|
||||
}
|
||||
if ($newimg) {
|
||||
return new Image($fn);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function width() {
|
||||
return ImageSX($this->getImage());
|
||||
}
|
||||
|
||||
public function height() {
|
||||
return ImageSY($this->getImage());
|
||||
}
|
||||
|
||||
public function scale($w, $h = 0) {
|
||||
|
||||
$sx = $this->width();
|
||||
$sy = $this->height();
|
||||
$dx = $w;
|
||||
|
||||
if ($h == 0) {
|
||||
$aspect = $sx / $sy;
|
||||
$dy = (int)($dx / $aspect);
|
||||
} else {
|
||||
$dh = $h;
|
||||
}
|
||||
|
||||
$new = ImageCreateTrueColor($dx, $dy);
|
||||
ImageCopyResampled($new, $this->getImage(), 0, 0, 0, 0, (int)$dx, (int)$dy, (int)$sx, (int)$sy);
|
||||
ImageDestroy($this->getImage());
|
||||
|
||||
$this->_img = $new;
|
||||
|
||||
}
|
||||
|
||||
public function emit() {
|
||||
$this->set_header("Content-Type", $this->_type);
|
||||
$this->set_header("Cache-Control", "public, max-age=86400, must-revalidate");
|
||||
$this->set_header("Content-Disposition", "inline; filename=" . $this->basename());
|
||||
|
||||
parent::emit();
|
||||
}
|
||||
|
||||
public function color($r, $g, $b) {
|
||||
return ImageColorAllocate($this->getImage(), $r, $g, $b);
|
||||
}
|
||||
|
||||
public function clear($c) {
|
||||
ImageFilledRectangle($this->getImage(), 0, 0, $this->width(), $this->height(), $c);
|
||||
}
|
||||
|
||||
public function text($x, $y, $t, $c, $f) {
|
||||
ImageString($this->getImage(), $f, $x, $y, $t, $c);
|
||||
}
|
||||
|
||||
}
|
||||
119
lib/Job.php
Normal file
119
lib/Job.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
abstract class Job {
|
||||
private $_jobid = 0;
|
||||
private $_source = "unknown";
|
||||
|
||||
public function __construct($source = "unknown") {
|
||||
$this->_source = $source;
|
||||
}
|
||||
|
||||
// Implement this function to actually do the job
|
||||
abstract public function run();
|
||||
|
||||
// Place the current job onto the queue ready for processing.
|
||||
public function queue() {
|
||||
$class = get_called_class();
|
||||
$data = serialize($this);
|
||||
$db = DB::getInstance();
|
||||
|
||||
$this->_jobid = $db->insert("job", [
|
||||
"class" => $class,
|
||||
"data" => $data,
|
||||
"queued" => time(),
|
||||
"source" => $this->_source,
|
||||
"status" => "Queued",
|
||||
]);
|
||||
|
||||
return $this->_jobid;
|
||||
}
|
||||
|
||||
public function getJobClass() {
|
||||
$class = get_called_class();
|
||||
return $class;
|
||||
}
|
||||
|
||||
public function jobID() {
|
||||
return $this->_jobid;
|
||||
}
|
||||
|
||||
public function setJobID($id) {
|
||||
$this->_jobid = $id;
|
||||
}
|
||||
|
||||
// Set the current status message
|
||||
public function status($txt) {
|
||||
$db = DB::getInstance();
|
||||
$db->update("job", $this->_jobid, [
|
||||
"status" => $txt
|
||||
]);
|
||||
}
|
||||
|
||||
// Mark the job as completed successfully
|
||||
public function finish() {
|
||||
$db = DB::getInstance();
|
||||
$db->update("job", $this->_jobid, [
|
||||
"finished" => time(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Mark the job as failed miserably
|
||||
public function fail() {
|
||||
$db = DB::getInstance();
|
||||
$db->update("job", $this->_jobid, [
|
||||
"failed" => time(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Restart the job.
|
||||
public function retry() {
|
||||
$db = DB::getInstance();
|
||||
$db->update("job", $this->_jobid, [
|
||||
"failed" => 0,
|
||||
"finished" => 0,
|
||||
"started" => 0,
|
||||
"status" => "Retrying"
|
||||
]);
|
||||
}
|
||||
|
||||
// Restart the job and defer it to a later time.
|
||||
public function defer($when) {
|
||||
$db = DB::getInstance();
|
||||
$db->update("job", $this->_jobid, [
|
||||
"failed" => 0,
|
||||
"finished" => 0,
|
||||
"started" => 0,
|
||||
"queued" => $when,
|
||||
"status" => "Deferred until " . gmdate("Y-m-d\TH:i:s\Z", $when)
|
||||
]);
|
||||
}
|
||||
|
||||
// Look for the next available job that optionally has
|
||||
// the requested class. Mark it as started, deserialize
|
||||
// it, and return the job runner object.
|
||||
// Returns false if no job available.
|
||||
public static function consumeNextJob($class = null) {
|
||||
$db = DB::getInstance();
|
||||
|
||||
$db->query("lock table job write");
|
||||
if ($class == null) {
|
||||
$q = $db->query("select * from job where started=0 and queued < unix_timestamp(now()) order by queued limit 1");
|
||||
} else {
|
||||
$q = $db->query("select * from job where started=0 and queued < unix_timestamp(now()) and class=:class order by queued limit 1", ["class" => $class]);
|
||||
}
|
||||
$r = $db->nextRecord($q);
|
||||
if (!$r) {
|
||||
$db->query("unlock tables");
|
||||
return false;
|
||||
}
|
||||
|
||||
$db->update("job", $r->id, [
|
||||
"started" => time()
|
||||
]);
|
||||
$db->query("unlock tables");
|
||||
|
||||
$ob = unserialize($r->data);
|
||||
$ob->setJobID($r->id);
|
||||
return $ob;
|
||||
}
|
||||
}
|
||||
491
lib/Model.php
Executable file
491
lib/Model.php
Executable file
@@ -0,0 +1,491 @@
|
||||
<?php
|
||||
|
||||
// Special data types
|
||||
define("MODEL_SERIAL", 0x1000); // auto increment id
|
||||
define("MODEL_OBJECT", 0x1001); // Link to another object type by id
|
||||
define("MODEL_CREATED", 0x1002); // CREATED timestamp
|
||||
define("MODEL_UPDATED", 0x1003); // UPDATED timestamp
|
||||
|
||||
// Numeric data types
|
||||
define("MODEL_BIT", 0x2000);
|
||||
define("MODEL_TINYINT", 0x2001);
|
||||
define("MODEL_BOOL", 0x2002);
|
||||
define("MODEL_SMALLINT", 0x2003);
|
||||
define("MODEL_MEDIUMINT", 0x2004);
|
||||
define("MODEL_INT", 0x2005);
|
||||
define("MODEL_BIGINT", 0x2006);
|
||||
define("MODEL_FLOAT", 0x2007);
|
||||
define("MODEL_DOUBLE", 0x2008);
|
||||
define("MODEL_DECIMAL", 0x2009);
|
||||
|
||||
// String data types
|
||||
define("MODEL_CHAR", 0x3000);
|
||||
define("MODEL_VARCHAR", 0x3001);
|
||||
define("MODEL_BINARY", 0x3002);
|
||||
define("MODEL_VARBINARY", 0x3003);
|
||||
define("MODEL_TINYBLOB", 0x3004);
|
||||
define("MODEL_TINYTEXT", 0x3005);
|
||||
define("MODEL_TEXT", 0x3006);
|
||||
define("MODEL_BLOB", 0x3007);
|
||||
define("MODEL_MEDIUMTEXT", 0x3008);
|
||||
define("MODEL_MEDIUMBLOB", 0x3009);
|
||||
define("MODEL_LONGTEXT", 0x300A);
|
||||
define("MODEL_LONGBLOB", 0x300B);
|
||||
define("MODEL_ENUM", 0x300C);
|
||||
define("MODEL_SET", 0x300D);
|
||||
|
||||
// Date types
|
||||
define("MODEL_DATE", 0x4000);
|
||||
define("MODEL_DATETIME", 0x4001);
|
||||
define("MODEL_TIMESTAMP", 0x4002);
|
||||
define("MODEL_TIME", 0x4003);
|
||||
define("MODEL_YEAR", 0x4004);
|
||||
|
||||
define("MODEL_INDEX", 12);
|
||||
define("MODEL_FULLTEXT", 13);
|
||||
define("MODEL_UNIQUE", 14);
|
||||
|
||||
|
||||
|
||||
class Model implements JsonSerializable {
|
||||
|
||||
public $_fields = []; // List of fields in the DB table
|
||||
public $_virgin = []; // Virgin, untouched data loaded from the DB
|
||||
public $_data = []; // Current (modified) data as stored in the DB
|
||||
public $_objects = []; // Objects constructed from the values in _data
|
||||
|
||||
public $_timeout = 1;
|
||||
|
||||
public $_valid = false;
|
||||
public $_loaded = false;
|
||||
|
||||
public static $_cache = null;
|
||||
|
||||
static protected $_raw = false;
|
||||
|
||||
public function __construct($id = null, $deep = true) {
|
||||
$this->_valid = false;
|
||||
if ($id !== null) {
|
||||
$this->_load_record($id);
|
||||
$this->_loaded = $this->_valid;
|
||||
} else {
|
||||
$this->_get_fields();
|
||||
$this->_loaded = false;
|
||||
$this->_valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static function cache_connect($host, $port, $weight = 50) {
|
||||
if (Model::$_cache === null) {
|
||||
Model::$_cache = new Memcached();
|
||||
}
|
||||
Model::$_cache->addServer($host, $port, $weight);
|
||||
}
|
||||
|
||||
private function _load_record($id) {
|
||||
|
||||
$class = get_called_class();
|
||||
if (property_exists($class, "table")) {
|
||||
$v = get_class_vars($class);
|
||||
$table = $v["table"];
|
||||
} else {
|
||||
$table = strtolower($class);
|
||||
}
|
||||
|
||||
$r = DB::getInstance()->select($table, $id);
|
||||
|
||||
if ($r === false) {
|
||||
$this->_valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
foreach ($r as $k=>$v) {
|
||||
$this->_fields[] = $k;
|
||||
$this->_virgin[$k] = $v;
|
||||
}
|
||||
|
||||
if (property_exists($class, "transform")) {
|
||||
foreach ($this->_fields as $k) {
|
||||
if (array_key_exists($k, $this->transform)) {
|
||||
$v = $this->transform[$k];
|
||||
$f = "from_" . $v;
|
||||
if (method_exists($this, $f)) {
|
||||
$this->_data[$k] = $this->$f($this->_virgin[$k]);
|
||||
} else {
|
||||
$this->_data[$k] = $this->_virgin[$k];
|
||||
}
|
||||
} else {
|
||||
$this->_data[$k] = $this->_virgin[$k];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($this->_fields as $k) {
|
||||
$this->_data[$k] = $this->_virgin[$k];
|
||||
}
|
||||
}
|
||||
$this->_valid = true;
|
||||
}
|
||||
|
||||
public static function find($where = []) {
|
||||
$class = get_called_class();
|
||||
if (property_exists($class, "table")) {
|
||||
$v = get_class_vars($class);
|
||||
$table = $v["table"];
|
||||
} else {
|
||||
$table = strtolower($class);
|
||||
}
|
||||
return new Query($class, $table, $where);
|
||||
}
|
||||
|
||||
|
||||
private function _get_fields() {
|
||||
$class = get_called_class();
|
||||
if ($class == "Model") { // Light model being used for xfer
|
||||
return;
|
||||
}
|
||||
if (property_exists($class, "table")) {
|
||||
$v = get_class_vars($class);
|
||||
$table = $v["table"];
|
||||
} else {
|
||||
$table = strtolower($class);
|
||||
}
|
||||
|
||||
$this->_fields = [];
|
||||
|
||||
$q = DB::getInstance()->query("describe `" . $table . "`");
|
||||
while ($r = DB::getInstance()->nextRecord($q)) {
|
||||
$this->_fields[] = $r->Field;
|
||||
$this->_data[$r->Field] = null;
|
||||
}
|
||||
$this->_valid = true;
|
||||
}
|
||||
|
||||
public function from_json($data) {
|
||||
$d = json_decode($data);
|
||||
if ($d) return $d;
|
||||
return new stdClass;
|
||||
}
|
||||
|
||||
public function to_json($data) {
|
||||
return json_encode($data);
|
||||
}
|
||||
|
||||
public function valid() {
|
||||
return $this->_valid;
|
||||
}
|
||||
|
||||
public function loaded() {
|
||||
return $this->_loaded;
|
||||
}
|
||||
|
||||
public function save() {
|
||||
Model::raw_on();
|
||||
$class = get_called_class();
|
||||
if (property_exists($class, "table")) {
|
||||
$v = get_class_vars($class);
|
||||
$table = $v["table"];
|
||||
} else {
|
||||
$table = strtolower($class);
|
||||
}
|
||||
|
||||
$_save = [];
|
||||
|
||||
if (property_exists($class, "transform")) {
|
||||
foreach ($this->_fields as $k) {
|
||||
if (array_key_exists($k, $this->transform)) {
|
||||
$v = $this->transform[$k];
|
||||
$f = "to_" . $v;
|
||||
if (method_exists($this, $f)) {
|
||||
$_save[$k] = $this->$f(@$this->$k);
|
||||
} else {
|
||||
$v = __unref($this->$k);
|
||||
if (is_string($v)) $v = trim($v);
|
||||
$_save[$k] = $v;
|
||||
}
|
||||
} else {
|
||||
$v = __unref($this->$k);
|
||||
if (is_string($v)) $v = trim($v);
|
||||
$_save[$k] = $v;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($this->_fields as $k) {
|
||||
$v = __unref($this->$k);
|
||||
if (is_string($v)) $v = trim($v);
|
||||
$_save[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
$_update = [];
|
||||
|
||||
foreach ($_save as $k=>$v) {
|
||||
if (array_key_exists($k, $this->_virgin)) {
|
||||
if ($_save[$k] != $this->_virgin[$k]) {
|
||||
$_update[$k] = $v;
|
||||
$this->_virgin[$k] = $v;
|
||||
}
|
||||
} else {
|
||||
$_update[$k] = $v;
|
||||
$this->_virgin[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
$triggers = [];
|
||||
if ($this->_loaded) {
|
||||
if (count($_update) > 0) {
|
||||
if (in_array("updated", $this->_fields)) {
|
||||
$_update['updated'] = time();
|
||||
}
|
||||
|
||||
DB::getInstance()->update($table, $this->id, $_update);
|
||||
}
|
||||
} else {
|
||||
$_update = $this->_virgin;
|
||||
if (in_array("created", $this->_fields)) {
|
||||
$_update['created'] = time();
|
||||
}
|
||||
$this->id = DB::getInstance()->insert($table, $_update);
|
||||
$this->_loaded = true;
|
||||
}
|
||||
|
||||
foreach ($_update as $k=>$v) {
|
||||
if (property_exists($class, "_triggers")) {
|
||||
if (array_key_exists($k, $this->_triggers)) {
|
||||
$triggers[$this->_triggers[$k]] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($triggers as $trigger=>$count) {
|
||||
|
||||
if ($trigger instanceof \Closure) {
|
||||
$trigger->call($this);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($trigger)) {
|
||||
$c = $trigger[0];
|
||||
$f = $trigger[1];
|
||||
$c::$f();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_string($trigger)) {
|
||||
$this->$trigger();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Model::raw_off();
|
||||
|
||||
}
|
||||
|
||||
public static function raw_on() {
|
||||
Model::$_raw = true;
|
||||
}
|
||||
|
||||
public static function raw_off() {
|
||||
Model::$_raw = false;
|
||||
}
|
||||
|
||||
static public function raw() {
|
||||
return Model::$_raw;
|
||||
}
|
||||
|
||||
|
||||
public function delete() {
|
||||
$class = get_called_class();
|
||||
if (property_exists($class, "table")) {
|
||||
$v = get_class_vars($class);
|
||||
$table = $v["table"];
|
||||
} else {
|
||||
$table = strtolower($class);
|
||||
}
|
||||
|
||||
if (method_exists($this, "on_delete")) {
|
||||
$this->on_delete();
|
||||
}
|
||||
|
||||
DB::getInstance()->query("delete from `" . $table . "` where id=:id", ["id" => $this->_data["id"]]);
|
||||
}
|
||||
|
||||
|
||||
public function __toString() {
|
||||
return "" . $this->_data["id"];
|
||||
}
|
||||
|
||||
public function __toInt() {
|
||||
return (int)$this->_data["id"];
|
||||
}
|
||||
|
||||
public function jsonSerialize() : mixed {
|
||||
|
||||
$out = [];
|
||||
|
||||
foreach ($this->_fields as $f) {
|
||||
$out[$f] = $this->_data["$f"];
|
||||
}
|
||||
|
||||
foreach ($this->_fields as $f) {
|
||||
if (array_key_exists($f, $this->_objects)) {
|
||||
$out[$f] = $this->_objects["$f"];
|
||||
}
|
||||
}
|
||||
|
||||
if (property_exists($this, "_computed")) {
|
||||
foreach ($this->_computed as $k=>$v) {
|
||||
$out[$k] = $this->$v();
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function __get_field_class($f) {
|
||||
if (!property_exists($this, "_classes")) return null;
|
||||
if (!array_key_exists($f, $this->_classes)) return null;
|
||||
return $this->_classes[$f];
|
||||
}
|
||||
|
||||
public function __get($k) {
|
||||
if (property_exists($this, "_computed")) {
|
||||
if (array_key_exists($k, $this->_computed)) {
|
||||
|
||||
$c = $this->cache_get($k);
|
||||
if ($c) return $c;
|
||||
|
||||
$func = $this->_computed[$k];
|
||||
$c = $this->$func();
|
||||
$this->cache_set($k, $c);
|
||||
return $c;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!in_array($k, $this->_fields)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$class = $this->__get_field_class($k);
|
||||
if ($class != null) {
|
||||
if (array_key_exists($k, $this->_objects)) return $this->_objects[$k];
|
||||
}
|
||||
return $this->_data[$k];
|
||||
}
|
||||
|
||||
public function __set($key, $val) {
|
||||
if (!in_array($key, $this->_fields)) return false;
|
||||
$class = $this->__get_field_class($key);
|
||||
|
||||
if ($class != null) {
|
||||
if ($val instanceof Model) {
|
||||
|
||||
if ($val instanceof $class) {
|
||||
$this->_data[$val] = $val->id;
|
||||
$this->_objects[$key] = $val;
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new Exception('Class mismatch');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_numeric($val)) {
|
||||
$this->_data[$key] = $val;
|
||||
$this->_objects[$key] = new $class($val);
|
||||
return true;
|
||||
}
|
||||
|
||||
$val = (int)$val;
|
||||
$this->_data[$key] = $val;
|
||||
$this->_objects[$key] = new $class($val);
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->_data[$key] = $val;
|
||||
unset($this->_objects[$key]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function __isset($key) {
|
||||
if (!in_array($key, $this->_fields)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function __unset($key) {
|
||||
if (!in_array($key, $this->_fields)) return;
|
||||
$this->_data[$key] = null;
|
||||
unset($this->_objects[$key]);
|
||||
}
|
||||
|
||||
public function load($key = null) {
|
||||
|
||||
if ($key === null) {
|
||||
foreach ($this->_fields as $key) {
|
||||
$this->load($key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!in_array($key, $this->_fields)) {
|
||||
return false;
|
||||
}
|
||||
$class = $this->__get_field_class($key);
|
||||
if ($class == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->_data[$key] === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ob = new $class($this->_data[$key]);
|
||||
if (!$ob->valid()) return false;
|
||||
$this->_objects[$key] = $ob;
|
||||
return true;
|
||||
}
|
||||
|
||||
private function cache_key($key) {
|
||||
$class = get_called_class();
|
||||
return sprintf("%s[%d]::%s", $class, $this->id, $key);
|
||||
}
|
||||
|
||||
public function cache_set($key, $val) {
|
||||
$key = $this->cache_key($key);
|
||||
Model::$_cache->set($key, $val, $this->_timeout);
|
||||
}
|
||||
|
||||
public function cache_get($key) {
|
||||
$key = $this->cache_key($key);
|
||||
return Model::$_cache->get($key);
|
||||
}
|
||||
|
||||
public function cache_invalidate($key) {
|
||||
$key = $this->cache_key($key);
|
||||
Model::$_cache->delete($key);
|
||||
|
||||
}
|
||||
|
||||
public static function get_all_models() {
|
||||
$res = [];
|
||||
foreach (get_declared_classes() as $class) {
|
||||
if (is_subclass_of($class, "Model")) {
|
||||
$res[] = $class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function create_or_update_table() {
|
||||
|
||||
}
|
||||
|
||||
public function get_table_name() {
|
||||
$class = get_called_class();
|
||||
if (property_exists($class, "table")) {
|
||||
$v = get_class_vars($class);
|
||||
return($v["table"]);
|
||||
}
|
||||
return (strtolower($class));
|
||||
}
|
||||
|
||||
}
|
||||
145
lib/PDF.php
Executable file
145
lib/PDF.php
Executable file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
require_once(__DIR__ . "/File.php");
|
||||
|
||||
define("PDF_SCREEN", 0);
|
||||
define("PDF_EBOOK", 1);
|
||||
define("PDF_PRINT", 2);
|
||||
define("PDF_PREPRESS", 3);
|
||||
|
||||
class PDF extends File {
|
||||
|
||||
private $_force_download = false;
|
||||
private $_fake_filename = null;
|
||||
|
||||
public function __construct($f) {
|
||||
parent::__construct($f);
|
||||
}
|
||||
|
||||
public function extract_page($page, $file, $dpi = 300) {
|
||||
|
||||
$m = Config::get("MAGICK");
|
||||
|
||||
$p = new Process($m);
|
||||
$p->arg("-density"); $p->arg($dpi);
|
||||
$p->arg(sprintf("%s[%d]", $this->path(), $page));
|
||||
$p->arg("-alpha"); $p->arg("remove");
|
||||
$p->arg($file);
|
||||
|
||||
$rv = $p->execute();
|
||||
|
||||
if ($rv != 0) {
|
||||
$img = new Image(640, 640);
|
||||
|
||||
$y = 40;
|
||||
$x = 10;
|
||||
|
||||
$b = $img->color(255,255,255);
|
||||
$f = $img->color(0, 0, 0);
|
||||
$img->clear($b);
|
||||
|
||||
$e = implode("\n", $p->stderr());
|
||||
$e = wordwrap($e, 80);
|
||||
$e = explode("\n", $e);
|
||||
|
||||
foreach ($e as $l) {
|
||||
$img->text($x, $y, $l, $f, 3);
|
||||
$y += 20;
|
||||
}
|
||||
|
||||
$img->save($file, "image/jpeg");
|
||||
return $img;
|
||||
|
||||
}
|
||||
|
||||
return new Image($file);
|
||||
}
|
||||
|
||||
public function force_download() {
|
||||
$this->_force_download = true;
|
||||
}
|
||||
|
||||
public function fake_filename($f) {
|
||||
$this->_fake_filename = $f;
|
||||
}
|
||||
|
||||
public function emit() {
|
||||
|
||||
$filename = $this->_fake_filename;
|
||||
if ($filename == null) {
|
||||
$filename = $this->basename();
|
||||
}
|
||||
|
||||
if ($this->_force_download) {
|
||||
$this->set_header("Content-Type", "application/octet-stream");
|
||||
$this->set_header("Cache-Control", "public, max-age=31560000, immutable");
|
||||
$this->set_header("Content-Disposition", "attachment; filename=\"$filename\"");
|
||||
} else {
|
||||
$this->set_header("Content-Type", "application/pdf");
|
||||
$this->set_header("Cache-Control", "public, max-age=31560000, immutable");
|
||||
$this->set_header("Content-Disposition", "inline; filename=\"$filename\"");
|
||||
}
|
||||
|
||||
parent::emit();
|
||||
}
|
||||
|
||||
|
||||
public function info() {
|
||||
$infolines = array();
|
||||
$info = array();
|
||||
$proc = new Process("pdfinfo");
|
||||
$proc->arg($this->path());
|
||||
$proc->execute();
|
||||
|
||||
$infolines = $proc->stdout();
|
||||
foreach ($infolines as $line) {
|
||||
if (preg_match('/^([^:]+):\s+(.*)$/', $line, $m)) {
|
||||
$info[$m[1]] = $m[2];
|
||||
}
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
|
||||
public function geminiOverview($verbose=false) {
|
||||
$g = new Gemini();
|
||||
$g->verbose = $verbose;
|
||||
try {
|
||||
return $g->geminiOverview($this);
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function recompress($dest, $size=PDF_SCREEN) {
|
||||
$proc = new Process("gs");
|
||||
|
||||
$proc->arg("-sDEVICE=pdfwrite");
|
||||
$proc->arg("-dCompatibilityLevel=1.6");
|
||||
switch ($size) {
|
||||
case PDF_SCREEN:
|
||||
$proc->arg("-dPDFSETTINGS=/screen");
|
||||
break;
|
||||
case PDF_EBOOK:
|
||||
$proc->arg("-dPDFSETTINGS=/ebook");
|
||||
break;
|
||||
case PDF_PRINT:
|
||||
$proc->arg("-dPDFSETTINGS=/printer");
|
||||
break;
|
||||
case PDF_PREPRESS:
|
||||
$proc->arg("-dPDFSETTINGS=/prepress");
|
||||
break;
|
||||
}
|
||||
$proc->arg("-dNOPAUSE");
|
||||
$proc->arg("-dQUIET");
|
||||
$proc->arg("-dBATCH");
|
||||
$proc->arg("-sOutputFile=" . $dest);
|
||||
$proc->arg($this->path());
|
||||
$rv = $proc->execute();
|
||||
|
||||
if ($rv == 0) {
|
||||
return new PDF($dest);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
82
lib/Process.php
Executable file
82
lib/Process.php
Executable file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
class Process {
|
||||
|
||||
private $_command = null;
|
||||
private $_args = [];
|
||||
private $_env = [];
|
||||
private $_cwd = null;
|
||||
|
||||
private $_pipes = [];
|
||||
|
||||
private $_stdout = [];
|
||||
private $_stderr = [];
|
||||
|
||||
private $_fd = null;
|
||||
private $_status = null;
|
||||
|
||||
public function __construct($command) {
|
||||
$this->_command = $command;
|
||||
$this->_env = $_ENV;
|
||||
}
|
||||
|
||||
public function arg($v) {
|
||||
$this->_args[] = $v;
|
||||
}
|
||||
|
||||
public function env($k, $v) {
|
||||
$this->_env[$k] = $v;
|
||||
}
|
||||
|
||||
public function cwd($p) {
|
||||
$this->_cwd = $p;
|
||||
}
|
||||
|
||||
public function execute() {
|
||||
$command = [];
|
||||
$command[] = escapeshellcmd($this->_command);
|
||||
foreach ($this->_args as $a) {
|
||||
$command[] = escapeshellarg($a);
|
||||
}
|
||||
|
||||
$desc = [
|
||||
["pipe", "r"],
|
||||
["pipe", "w"],
|
||||
["pipe", "w"]
|
||||
];
|
||||
|
||||
$this->_fd = proc_open(
|
||||
implode(" ", $command),
|
||||
$desc,
|
||||
$this->_pipes,
|
||||
$this->_cwd,
|
||||
$this->_env
|
||||
);
|
||||
|
||||
fclose($this->_pipes[0]); // stdin
|
||||
|
||||
$this->_status = proc_get_status($this->_fd);
|
||||
while ($this->_status["running"]) {
|
||||
|
||||
$this->_stdout[] = stream_get_contents($this->_pipes[1]);
|
||||
$this->_stderr[] = stream_get_contents($this->_pipes[2]);
|
||||
|
||||
$this->_status = proc_get_status($this->_fd);
|
||||
}
|
||||
|
||||
proc_close($this->_fd);
|
||||
|
||||
return $this->_status["exitcode"];
|
||||
}
|
||||
|
||||
|
||||
public function stdout() {
|
||||
return explode("\n", implode("", $this->_stdout));
|
||||
}
|
||||
|
||||
public function stderr() {
|
||||
return explode("\n", implode("", $this->_stderr));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
112
lib/Query.php
Executable file
112
lib/Query.php
Executable file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
class Query {
|
||||
|
||||
private $class = "";
|
||||
private $table = "";
|
||||
private $where = [];
|
||||
private $order = [];
|
||||
private $limit = "";
|
||||
|
||||
private $query;
|
||||
|
||||
public function __construct($class, $table, $where = []) {
|
||||
$this->where = $where;
|
||||
$this->table = $table;
|
||||
$this->class = $class;
|
||||
}
|
||||
|
||||
public function where($where) {
|
||||
foreach ($where as $w) {
|
||||
$this->where[] = $w;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function orderBy($order) {
|
||||
$this->order[] = $order;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function orderByDesc($order) {
|
||||
$this->order[] = "$order desc";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function limit($a, $b=null) {
|
||||
if ($b == null) {
|
||||
$this->limit = $a;
|
||||
} else {
|
||||
$this->limit = "$a,$b";
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
function all() {
|
||||
$this->__run();
|
||||
$cl = $this->class;
|
||||
|
||||
$out = new Collection;
|
||||
while ($r = DB::getInstance()->nextRecord($this->query)) {
|
||||
$ob = new $cl($r->id);
|
||||
$out->push($ob);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
function first() {
|
||||
$this->__run();
|
||||
$cl = $this->class;
|
||||
|
||||
$out = new Collection;
|
||||
if ($r = DB::getInstance()->nextRecord($this->query)) {
|
||||
$ob = new $cl($r->id);
|
||||
return $ob;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function next() {
|
||||
$cl = $this->class;
|
||||
|
||||
$out = new Collection;
|
||||
if ($r = DB::getInstance()->nextRecord($this->query)) {
|
||||
$ob = new $cl($r->id);
|
||||
return $ob;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Run the query but don't retrieve anything.
|
||||
private function __run() {
|
||||
$args = [];
|
||||
$ac = 1;
|
||||
$q = "select id from `" . $this->table . "`";
|
||||
|
||||
if (count($this->where) > 0) {
|
||||
$q .= " where";
|
||||
|
||||
$first = true;
|
||||
foreach ($this->where as $w) {
|
||||
if (!$first) {
|
||||
$q .= " and";
|
||||
}
|
||||
$q .= sprintf(" `%s` %s :arg%d", $w[0], $w[1], $ac);
|
||||
$args["arg" . $ac] = $w[2];
|
||||
$first = false;
|
||||
$ac++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($this->order) > 0) {
|
||||
$q .= " order by ";
|
||||
$q .= implode(",", $this->order);
|
||||
}
|
||||
|
||||
if ($this->limit != "") {
|
||||
$q .= sprintf(" limit %s", $this->limit);
|
||||
}
|
||||
|
||||
$this->query = DB::getInstance()->query($q, $args);
|
||||
}
|
||||
}
|
||||
112
lib/Request.php
Executable file
112
lib/Request.php
Executable file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
class Request {
|
||||
private $type = "";
|
||||
private $headers = [];
|
||||
private $referer = "";
|
||||
private $get = [];
|
||||
private $post = [];
|
||||
private $put = [];
|
||||
private $peer = "";
|
||||
private $path = "";
|
||||
private $route = null;
|
||||
private $files = [];
|
||||
|
||||
public function __construct() {
|
||||
$this->get = $_GET;
|
||||
$this->post = $_POST;
|
||||
$this->files = $_FILES;
|
||||
$this->type = strtoupper($_SERVER['REQUEST_METHOD']);
|
||||
$this->peer = $_SERVER['REMOTE_ADDR'];
|
||||
if (array_key_exists("REDIRECT_URL", $_SERVER)) {
|
||||
$this->path = $_SERVER['REDIRECT_URL'];
|
||||
} else {
|
||||
$this->path = "/";
|
||||
}
|
||||
|
||||
if ($this->type == "PUT") {
|
||||
$data = file_get_contents("php://input");
|
||||
$arr = [];
|
||||
parse_str($data, $arr);
|
||||
foreach ($arr as $k=>$v) {
|
||||
$this->put[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($_SERVER as $k=>$v) {
|
||||
if (str_starts_with($k, "HTTP_")) {
|
||||
$header = substr($k, 5);
|
||||
$header = strtolower($header);
|
||||
$header = str_replace("_", " ", $header);
|
||||
$header = ucwords($header);
|
||||
$header = str_replace(" ", "-", $header);
|
||||
$this->headers[$header] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists("HTTP_REFERER", $_SERVER)) {
|
||||
$this->referer = $_SERVER['HTTP_REFERER'];
|
||||
} else {
|
||||
$this->referer = "";
|
||||
}
|
||||
}
|
||||
|
||||
public function type() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function headers() {
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
public function header($h) {
|
||||
if (array_key_exists($h, $this->headers)) {
|
||||
return $this->headers[$h];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function file($name, $num = false) {
|
||||
if ($num === false) {
|
||||
return $this->files[$name];
|
||||
}
|
||||
if ($num >= count($this->files[$name]["name"])) {
|
||||
return false;
|
||||
}
|
||||
return [
|
||||
"name" => $this->files[$name]["name"][$num],
|
||||
"full_path" => $this->files[$name]["full_path"][$num],
|
||||
"type" => $this->files[$name]["type"][$num],
|
||||
"tmp_name" => $this->files[$name]["tmp_name"][$num],
|
||||
"error" => $this->files[$name]["error"][$num],
|
||||
"size" => $this->files[$name]["size"][$num],
|
||||
];
|
||||
}
|
||||
|
||||
public function get($k) {
|
||||
if (!array_key_exists($k, $this->get)) return false;
|
||||
return $this->get[$k];
|
||||
}
|
||||
|
||||
public function post($k) {
|
||||
if (!array_key_exists($k, $this->post)) return false;
|
||||
return $this->post[$k];
|
||||
}
|
||||
|
||||
public function put($k) {
|
||||
if (!array_key_exists($k, $this->put)) return false;
|
||||
return $this->put[$k];
|
||||
}
|
||||
|
||||
public function set_route($r) {
|
||||
$this->route = $r;
|
||||
}
|
||||
|
||||
public function route() {
|
||||
return $this->route;
|
||||
}
|
||||
|
||||
public function path() {
|
||||
return $this->path;
|
||||
}
|
||||
}
|
||||
98
lib/Route.php
Executable file
98
lib/Route.php
Executable file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
class Route {
|
||||
public $method = "GET";
|
||||
public $pattern = "";
|
||||
public $function = null;
|
||||
public $auth = false;
|
||||
public $args = [];
|
||||
|
||||
function __construct($m, $p, $f, $a = false) {
|
||||
$this->method = $m;
|
||||
$this->pattern = $p;
|
||||
$this->function = $f;
|
||||
$this->auth = $a;
|
||||
}
|
||||
|
||||
function matches($m, $path) {
|
||||
|
||||
if ($this->auth != false) {
|
||||
|
||||
if ($this->auth instanceof \Closure) {
|
||||
if (!$this->auth->call($this)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($this->auth)) {
|
||||
$c = $this->auth[0];
|
||||
$f = $this->auth[1];
|
||||
if (!$c::$f()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (strtolower($m) != strtolower($this->method)) return false;
|
||||
$src_parts = explode("/", $path);
|
||||
$dst_parts = explode("/", $this->pattern);
|
||||
|
||||
if (count($src_parts) != count($dst_parts)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->args = [];
|
||||
for ($i = 0; $i < count($src_parts); $i++) {
|
||||
$sp = $src_parts[$i];
|
||||
$dp = $dst_parts[$i];
|
||||
if (preg_match('/^{(.*)}$/', $dp, $m)) {
|
||||
$this->args[$m[1]] = $sp;
|
||||
continue;
|
||||
}
|
||||
if ($sp != $dp) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function get_args() {
|
||||
return $this->args;
|
||||
}
|
||||
|
||||
function call($req) {
|
||||
if ($this->function instanceof \Closure) {
|
||||
|
||||
// $fargs = func_get_args($this->function);
|
||||
// if (in_array("_request", $fargs)) {
|
||||
// $this->args["_request"] = $req;
|
||||
// }
|
||||
|
||||
$ref = new ReflectionFunction($this->function);
|
||||
foreach ($ref->getParameters() as $arg) {
|
||||
if ($arg->name == "_request") {
|
||||
$this->args["_request"] = $req;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->function->call($this, ...$this->args);
|
||||
}
|
||||
|
||||
if (is_array($this->function)) {
|
||||
$c = $this->function[0];
|
||||
$f = $this->function[1];
|
||||
$ref = new ReflectionMethod($c, $f);
|
||||
foreach ($ref->getParameters() as $arg) {
|
||||
if ($arg->name == "_request") {
|
||||
$this->args["_request"] = $req;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $c::$f(...$this->args);
|
||||
}
|
||||
|
||||
return [404, "Not Found"];
|
||||
}
|
||||
}
|
||||
35
lib/Routes.php
Executable file
35
lib/Routes.php
Executable file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
class Routes {
|
||||
public static $api = [];
|
||||
public static $web = [];
|
||||
|
||||
static function add_api($method, $path, $function, $auth = false) {
|
||||
$r = new Route($method, $path, $function, $auth);
|
||||
Routes::$api[] = $r;
|
||||
}
|
||||
|
||||
static function add_web($method, $path, $function, $auth = false) {
|
||||
$r = new Route($method, $path, $function, $auth);
|
||||
Routes::$web[] = $r;
|
||||
}
|
||||
|
||||
static function find_api($m, $path) {
|
||||
foreach (Routes::$api as $route) {
|
||||
if ($route->matches($m, $path)) {
|
||||
return $route;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static function find_web($m, $path) {
|
||||
foreach (Routes::$web as $route) {
|
||||
if ($route->matches($m, $path)) {
|
||||
return $route;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
71
lib/Session.php
Executable file
71
lib/Session.php
Executable file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
class Session {
|
||||
|
||||
public static $cache = null;
|
||||
public static $id = null;
|
||||
|
||||
public static function getID() {
|
||||
|
||||
|
||||
if (array_key_exists("SESSION_ID", $_COOKIE)) {
|
||||
Session::$id = $_COOKIE["SESSION_ID"];
|
||||
}
|
||||
|
||||
if (Session::$id) return Session::$id;
|
||||
|
||||
Session::$id = uniqid("decpdf", true);
|
||||
|
||||
setcookie("SESSION_ID", Session::$id, time() + 60*60*24*30);
|
||||
return Session::$id;
|
||||
}
|
||||
|
||||
public static function init($host, $port, $weight = 50) {
|
||||
if (Session::$cache == null) {
|
||||
Session::$cache = new Memcached();
|
||||
}
|
||||
Session::$cache->addServer($host, $port, $weight);
|
||||
}
|
||||
|
||||
public static function get(string $key) : mixed {
|
||||
$sid = Session::getID();
|
||||
|
||||
$key = "Session::" . $sid . "::" . $key;
|
||||
|
||||
$t = Session::$cache->get($key);
|
||||
if ($t) {
|
||||
return unserialize($t);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function set(string $key, mixed $value) : void {
|
||||
$sid = Session::getID();
|
||||
$fkey = "Session::" . $sid . "::" . $key;
|
||||
Session::$cache->set($fkey, serialize($value));//, 60*60*24*30);
|
||||
}
|
||||
|
||||
public static function unset(string $key) : void {
|
||||
$sid = Session::getID();
|
||||
$fkey = "Session::" . $sid . "::" . $key;
|
||||
Session::$cache->delete($fkey);
|
||||
}
|
||||
|
||||
public static function all_data() {
|
||||
$sid = Session::getID();
|
||||
$fkey = "Session::" . $sid . "::";
|
||||
$keys = Session::$cache->getAllKeys();
|
||||
$data = [];
|
||||
foreach ($keys as $k) {
|
||||
if (str_starts_with($k, $fkey)) {
|
||||
$d = Session::get($k);
|
||||
if (!$d) {
|
||||
$d = [];
|
||||
}
|
||||
$out[$k] = $data;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user