Initial import
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
.PHONEY: all component live
|
||||
all:
|
||||
./node_modules/webpack/bin/webpack.js
|
||||
|
||||
component:
|
||||
@if [ -z "$NAME" ]; then echo "Specify NAME="; exit 10 ; fi
|
||||
@cp src/Template.vue src/${NAME}.vue
|
||||
|
||||
live: all
|
||||
rsync -avPp * ../docman/
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
ini_set("display_errors", 1);
|
||||
|
||||
require_once(__DIR__ . "/lib/ErrorHandler.php");
|
||||
|
||||
if (!array_key_exists("SHELL", $_SERVER)) {
|
||||
ErrorHandler::hook();
|
||||
}
|
||||
|
||||
require_once(__DIR__ . "/lib/Session.php");
|
||||
|
||||
Session::init("memcache", 11211);
|
||||
|
||||
define('ROOT', __DIR__);
|
||||
require_once(ROOT . "/vendor/autoload.php");
|
||||
|
||||
$args = array_merge($_GET, $_POST);
|
||||
|
||||
use \Michelf\Markdown;
|
||||
use \Jenssegers\Blade\Blade;
|
||||
|
||||
_add_code_dir(ROOT . "/lib");
|
||||
//$dir = opendir(ROOT . "/lib");
|
||||
//while ($file = readdir($dir)) {
|
||||
// if (str_ends_with($file, ".php")) {
|
||||
// require_once(ROOT . "/lib/" . $file);
|
||||
// }
|
||||
//}
|
||||
//closedir($dir);
|
||||
|
||||
|
||||
DB::getInstance()->connect(
|
||||
Config::get("DB_USER"),
|
||||
Config::get("DB_PASS"),
|
||||
Config::get("DB_HOST"),
|
||||
Config::get("DB_DATABASE")
|
||||
);
|
||||
|
||||
_add_code_dir(ROOT . "/app");
|
||||
//$dir = opendir(ROOT . "/app");
|
||||
//while ($file = readdir($dir)) {
|
||||
// if (str_ends_with($file, ".php")) {
|
||||
// require_once(ROOT . "/app/" . $file);
|
||||
// }
|
||||
//}
|
||||
//closedir($dir);
|
||||
|
||||
require_once("lib/App.php");
|
||||
|
||||
require_once("routes/api.php");
|
||||
require_once("routes/web.php");
|
||||
|
||||
Model::cache_connect("memcache", 11211);
|
||||
|
||||
if (!is_terminal()) {
|
||||
$blade = new Blade(ROOT . '/views', ROOT . '/cache');
|
||||
$req = new Request;
|
||||
App::dispatch($req);
|
||||
}
|
||||
|
||||
function is_terminal() {
|
||||
return ($_SERVER['argc'] > 0);
|
||||
}
|
||||
|
||||
function blade($name, $args = []) {
|
||||
global $blade;
|
||||
|
||||
$args['flash_error'] = Session::get("flash_error");
|
||||
$args['flash_warn'] = Session::get("flash_warn");
|
||||
$args['flash_success'] = Session::get("flash_info");
|
||||
$args['flash_info'] = Session::get("flash_success");
|
||||
|
||||
Session::unset("flash_error");
|
||||
Session::unset("flash_warn");
|
||||
Session::unset("flash_info");
|
||||
Session::unset("flash_success");
|
||||
|
||||
return $blade->render($name, $args);
|
||||
}
|
||||
|
||||
function get_user() {
|
||||
$uid = Session::get("user");
|
||||
if ($uid) {
|
||||
return new User($uid);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function flash($type, $message) {
|
||||
if ($_SERVER['argc'] == 0) {
|
||||
Session::set("flash_" . $type, $message);
|
||||
} else {
|
||||
print("$type: $message\n");
|
||||
}
|
||||
}
|
||||
|
||||
function fmt_date($month, $year) {
|
||||
if (($year < 1900) && ($year > 50)) {
|
||||
$year = 1900 + $year;
|
||||
}
|
||||
|
||||
if (($year < 1900) && ($year <= 50)) {
|
||||
$year = 2000 + $year;
|
||||
}
|
||||
|
||||
if ($month > 0) {
|
||||
$dt = DateTime::createFromFormat("!m/Y", $month . "/" . $year);
|
||||
return $dt->format("F Y");
|
||||
} else {
|
||||
$dt = DateTime::createFromFormat("!Y", $year);
|
||||
return $dt->format("Y");
|
||||
}
|
||||
}
|
||||
|
||||
function format_size($s) {
|
||||
if ($s < 102400) return sprintf("%.1fkB", $s / 1024);
|
||||
if ($s < 10485760) return sprintf("%.1fMB", $s / (1024 * 1024));
|
||||
return sprintf("%dMB", $s / (1024 * 1024));
|
||||
}
|
||||
|
||||
function redirect($url) {
|
||||
return [
|
||||
302, "", [
|
||||
"Location" => $url
|
||||
]
|
||||
];
|
||||
|
||||
// if ($_SERVER['argc'] == 0) {
|
||||
// header("Location: $url");
|
||||
// exit(0);
|
||||
// }
|
||||
}
|
||||
|
||||
function jsredirect($url) {
|
||||
print('<script>document.location="' . $url . '";</script>');
|
||||
exit(0);
|
||||
}
|
||||
|
||||
function back() {
|
||||
return redirect($_SERVER['HTTP_REFERER']);
|
||||
}
|
||||
|
||||
function __unref($v) {
|
||||
if ($v instanceof Model) {
|
||||
return $v->id;
|
||||
}
|
||||
return $v;
|
||||
}
|
||||
|
||||
function _add_code_dir($path) {
|
||||
$dir = opendir($path);
|
||||
while ($file = readdir($dir)) {
|
||||
if (str_starts_with($file, ".")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_dir($path . "/" . $file)) {
|
||||
_add_code_dir($path . "/" . $file);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str_ends_with($file, ".php")) {
|
||||
require_once($path . "/" . $file);
|
||||
}
|
||||
}
|
||||
closedir($dir);
|
||||
}
|
||||
|
||||
function strtolower_null($a) {
|
||||
if (!is_string($a)) return $a;
|
||||
return strtolower($a);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
class CoverController {
|
||||
|
||||
static function get_cover($_request, $id, $filename, $size = false) {
|
||||
|
||||
$rev = new Revision($id);
|
||||
if ($rev === false) return "";
|
||||
|
||||
$path = $rev->path();
|
||||
|
||||
$pdf = new PDF($path . "/doc.pdf");
|
||||
if (!$pdf->exists()) {
|
||||
return [404, "Not Found"];
|
||||
}
|
||||
|
||||
if (!$size) {
|
||||
$imgpath = sprintf("%s/cover.jpg", $path);
|
||||
} else {
|
||||
$imgpath = sprintf("%s/cover-%d.jpg", $path, $size);
|
||||
}
|
||||
|
||||
$file = new File($imgpath);
|
||||
|
||||
if ($file->exists()) {
|
||||
$h = $file->hash();
|
||||
$h = "\"$h\"";
|
||||
if ($h == $_request->header("If-None-Match")) {
|
||||
return [304, "Not Modified", [
|
||||
"ETag" => $h,
|
||||
"Cache-Control" => "public, max-age=86400, must-revalidate",
|
||||
]];
|
||||
}
|
||||
return new Image($file);
|
||||
}
|
||||
|
||||
$cover = new Image($path . "/cover.jpg");
|
||||
if (!$cover->exists()) {
|
||||
$pdf->extract_page(0, $cover->path());
|
||||
$cover = new Image($path . "/cover.jpg");
|
||||
}
|
||||
|
||||
if (!$cover->exists()) {
|
||||
return [404, "Not Found"];
|
||||
}
|
||||
|
||||
if ($size === false) $size = $cover->width();
|
||||
|
||||
if ($size >= $cover->width()) {
|
||||
$h = $cover->hash();
|
||||
$h = "\"$h\"";
|
||||
if ($h == $_request->header("If-None-Match")) {
|
||||
return [304, "Not Modified", [
|
||||
"ETag" => $h,
|
||||
"Cache-Control" => "public, max-age=86400, must-revalidate",
|
||||
]];
|
||||
}
|
||||
return $cover;
|
||||
}
|
||||
|
||||
$cover->scale($size);
|
||||
$img = $cover->save($imgpath, "image/jpeg");
|
||||
return $img;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
<?php
|
||||
|
||||
class DocumentController {
|
||||
|
||||
public static function browse($pid = 0) {
|
||||
|
||||
$product = new Product($pid);
|
||||
|
||||
return blade("documents", ["product" => $product]);
|
||||
}
|
||||
|
||||
public static function show($id) {
|
||||
$doc = new Document($id);
|
||||
$doc->load();
|
||||
|
||||
return blade("document", [
|
||||
"doc" => $doc
|
||||
]);
|
||||
}
|
||||
|
||||
public static function api_get($id) {
|
||||
return new Document($id);
|
||||
}
|
||||
|
||||
public static function api_set($id) {
|
||||
$doc = new Document($id);
|
||||
if ($doc->valid()) {
|
||||
foreach ($_POST as $k=>$v) {
|
||||
if ($k != "products") {
|
||||
$doc->$k = trim($v);
|
||||
}
|
||||
}
|
||||
$doc->save();
|
||||
|
||||
$pids = explode(",", $_POST['products']);
|
||||
|
||||
$dpl = DocProduct::find([["document", "=", $id]])->all();;
|
||||
|
||||
foreach ($dpl as $dp) {
|
||||
$e = array_search($dp->product, $pids);
|
||||
if ($e !== false) {
|
||||
unset($pids[$e]);
|
||||
continue;
|
||||
}
|
||||
$dp->delete();
|
||||
}
|
||||
|
||||
foreach ($pids as $pid) {
|
||||
$dp = new DocProduct;
|
||||
$dp->document = $id;
|
||||
$dp->product = $pid;
|
||||
$dp->save();
|
||||
}
|
||||
|
||||
$doc->cache_invalidate("products");
|
||||
|
||||
}
|
||||
return $doc;
|
||||
}
|
||||
|
||||
public static function api_get_products($id) {
|
||||
$doc = new Document($id);
|
||||
return $doc->products;
|
||||
}
|
||||
|
||||
public static function api_move($id) {
|
||||
$doc = new Document($id);
|
||||
$from = $_POST['from'];
|
||||
$to = $_POST['to'];
|
||||
|
||||
$dp = DocProduct([["document", "=", $id], ["product", "=", $from]])->first();
|
||||
if ($dp) {
|
||||
$dp->product = $to;
|
||||
$dp->save();
|
||||
}
|
||||
|
||||
$doc->cache_invalidate("products");
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public static function del_docproduct($doc, $prod) {
|
||||
$dp = DocProduct::find([["document", "=", $doc], ["product", "=", $prod]])->first();
|
||||
if ($dp) {
|
||||
$dp->delete();
|
||||
}
|
||||
return back();
|
||||
}
|
||||
|
||||
public static function get_by_id($id) {
|
||||
return Document::find([["internal_id", "=", trim($id)]])->first();
|
||||
}
|
||||
|
||||
public static function api_merge($id) {
|
||||
$from = new Document($id);
|
||||
$to = new Document($_POST['to']);
|
||||
|
||||
if ($from->id == $to->id) return [];
|
||||
|
||||
if ($from && $to) {
|
||||
$revs = Revision::find([["document", "=", $from->id]]);
|
||||
for ($rev = $revs->first(); $rev = $revs->next(); ) {
|
||||
$rev->document = $to->id;
|
||||
$rev->save();
|
||||
}
|
||||
|
||||
$from->delete();
|
||||
}
|
||||
return new Document($to->id); // Force refresh
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function api_drag_drop($_request) {
|
||||
$src_id = $_request->post("src_id");
|
||||
$dst_id = $_request->post("dst_id");
|
||||
|
||||
$src_type = $_request->post("src_type");
|
||||
$dst_type = $_request->post("dst_type");
|
||||
|
||||
$src_extra = $_request->post("src_extra");
|
||||
$dst_extra = $_request->post("dst_extra");
|
||||
|
||||
$copy = $_request->post("copy") == "true";
|
||||
|
||||
if (($src_type == "document") && ($dst_type == "document")) {
|
||||
// Merge src into dst
|
||||
$src = new Document($src_id);
|
||||
$dst = new Document($dst_id);
|
||||
|
||||
if ($src->id == $dst->id) return [];
|
||||
|
||||
foreach ($src->revisions as $r) {
|
||||
$r->document = $dst->id;
|
||||
$r->save();
|
||||
}
|
||||
|
||||
$src->delete();
|
||||
$dst->cache_invalidate("revisions");
|
||||
}
|
||||
|
||||
if (($src_type == "document") && ($dst_type == "product")) {
|
||||
|
||||
if ($copy) {
|
||||
$dp = new DocProduct();
|
||||
$dp->document = $src_id;
|
||||
$dp->product = $dst_id;
|
||||
$dp->save();
|
||||
} else {
|
||||
|
||||
// Move document into product
|
||||
$dpl = DocProduct::find([["document", "=", $src_id], ["product", "=", $src_extra]])->all();
|
||||
foreach ($dpl as $dp) {
|
||||
$dp->product = $dst_id;
|
||||
$dp->save();
|
||||
}
|
||||
$prod = new Product($src_extra);
|
||||
$prod->cache_invalidate("documents");
|
||||
}
|
||||
$doc = new Document($src_id);
|
||||
$doc->cache_invalidate("products");
|
||||
$prod = new Product($dst_id);
|
||||
$prod->cache_invalidate("documents");
|
||||
}
|
||||
|
||||
if (($src_type == "product") && ($dst_type == "product")) {
|
||||
|
||||
if ($src_id == $dst_id) return [];
|
||||
// Move product into product
|
||||
$sp = new Product($src_id);
|
||||
$dp = new Product($dst_id);
|
||||
$sp->parent = $dp->id;
|
||||
$sp->save();
|
||||
$sp->cache_invalidate("parent");
|
||||
$sp->cache_invalidate("children");
|
||||
$dp->cache_invalidate("parent");
|
||||
$dp->cache_invalidate("children");
|
||||
}
|
||||
|
||||
|
||||
return [200, ["didit" => "true"]];
|
||||
}
|
||||
|
||||
public static function merge($id) {
|
||||
|
||||
$doc = new Document($id);
|
||||
|
||||
$proc = new Process("pdftk");
|
||||
|
||||
foreach ($doc->revisions as $rev) {
|
||||
$proc->arg($rev->path() . "/doc.pdf");
|
||||
}
|
||||
|
||||
$nr = new Revision;
|
||||
$nr->document = $doc->id;
|
||||
$nr->revno = "NEW";
|
||||
$nr->save();
|
||||
|
||||
|
||||
$out = new File($nr->path() . "/doc.pdf");
|
||||
$out->parent()->mkdir();
|
||||
|
||||
$proc->arg("output");
|
||||
$proc->arg((string)$out);
|
||||
$r = $proc->execute();
|
||||
|
||||
if ($r != 0) {
|
||||
print("<pre>");
|
||||
print_r($proc->stderr());
|
||||
print("</pre>");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
return redirect("/document/" . $doc->id);
|
||||
}
|
||||
|
||||
public static function create_overview($id, $move) {
|
||||
|
||||
$job = new GeminiJob($id, "document:$id");
|
||||
$job->move = ($move == "move");
|
||||
$jobid = $job->queue();
|
||||
flash("success", "Job queued as ID " . $jobid);
|
||||
|
||||
return redirect("/document/" . $id);
|
||||
}
|
||||
|
||||
public static function api_get_title_fragment($_request) {
|
||||
$q = $_request->post("title");
|
||||
$db = DB::getInstance();
|
||||
$q1 = $db->query("
|
||||
SELECT
|
||||
DISTINCT title
|
||||
FROM (
|
||||
SELECT
|
||||
DISTINCT title
|
||||
FROM
|
||||
document
|
||||
WHERE
|
||||
title LIKE :s
|
||||
UNION SELECT
|
||||
DISTINCT subtitle AS title
|
||||
FROM
|
||||
document
|
||||
WHERE
|
||||
subtitle LIKE :s
|
||||
UNION SELECT
|
||||
DISTINCT subsubtitle AS title
|
||||
FROM
|
||||
document
|
||||
WHERE subsubtitle LIKE :s
|
||||
) AS DERIVED", ["s" => $q . "%"]);
|
||||
$o = $db->all($q1);
|
||||
|
||||
if ($o->count() == 0) {
|
||||
return [404, "Not Found"];
|
||||
}
|
||||
if ($o->count() != 1) {
|
||||
return [413, "Content Too Large"];
|
||||
}
|
||||
return $o[0]->title;
|
||||
}
|
||||
|
||||
public static function separate($id) {
|
||||
|
||||
$doc = new Document($id);
|
||||
$firstprod = $doc->products[0];
|
||||
|
||||
$count = 0;
|
||||
|
||||
foreach ($doc->revisions as $rev) {
|
||||
$count++;
|
||||
if ($count == 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newdoc = $doc->duplicate();
|
||||
$newdoc->subsubtitle .= " - $count";
|
||||
$rev->document = $newdoc->id;
|
||||
$rev->save();
|
||||
}
|
||||
|
||||
|
||||
return redirect("/documents/" . $firstprod);
|
||||
}
|
||||
|
||||
public static function api_get_metadata($id) {
|
||||
return DocMeta::find([["document", "=", $id]])->orderBy("metadata")->all();
|
||||
}
|
||||
|
||||
public static function api_new_metadata($_request, $id) {
|
||||
$doc = new Document($id);
|
||||
$doc->set_metadata($_request->put("item_id"), "");
|
||||
return DocMeta::find([["document", "=", $id]])->orderBy("metadata")->all();
|
||||
}
|
||||
|
||||
public static function api_set_metadata($_request, $id, $metadata) {
|
||||
$doc = new Document($id);
|
||||
$doc->set_metadata($metadata, $_request->post('data'));
|
||||
return DocMeta::find([["document", "=", $id]])->orderBy("metadata")->all();
|
||||
}
|
||||
|
||||
public static function api_delete_metadata($id, $metadata) {
|
||||
$doc = new Document($id);
|
||||
$doc->remove_metadata($metadata);
|
||||
return DocMeta::find([["document", "=", $id]])->orderBy("metadata")->all();
|
||||
}
|
||||
|
||||
public static function api_available_metadata($id) {
|
||||
$exist = DocMeta::find([["document", "=", $id]])->orderBy("metadata")->all();
|
||||
$metas = MetaType::find()->all();
|
||||
|
||||
|
||||
$out = new Collection();
|
||||
|
||||
foreach ($metas as $meta) {
|
||||
$e = false;
|
||||
foreach ($exist as $ex) {
|
||||
if ($ex->metadata == $meta->id) {
|
||||
$e = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$e) {
|
||||
$cl = new stdClass;
|
||||
$cl->key = $meta->id;
|
||||
$cl->value = $meta->name;
|
||||
$out->push($cl);
|
||||
}
|
||||
}
|
||||
|
||||
$out->sort("value");
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public static function delete_metadata($id, $metadata) {
|
||||
$doc = new Document($id);
|
||||
$doc->remove_metadata($metadata);
|
||||
return redirect("/document/" . $id);
|
||||
}
|
||||
|
||||
public static function api_guess_docid($id) {
|
||||
$doc = new Document($id);
|
||||
$docid = $doc->guess_docid();
|
||||
|
||||
return new Collection(["id" => $id, "docid" => $docid]);
|
||||
}
|
||||
|
||||
public static function download_attachment($id, $filename) {
|
||||
$doc = new Document($id);
|
||||
$atts = $doc->get_attachments();
|
||||
foreach ($atts as $f) {
|
||||
if ($f->basename() == $filename) {
|
||||
$f->set_header("Content-Disposition", "attachment; filename=\"$filename\"");
|
||||
|
||||
return $f;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function upload_attachment($id) {
|
||||
$doc = new Document($id);
|
||||
return blade("upload.attachment", ["doc" => $doc]);
|
||||
}
|
||||
|
||||
public static function do_upload_attachment($_request, $id) {
|
||||
$doc = new Document($id);
|
||||
mkdir(ROOT . "/attachments/" . $doc->id, 0777);
|
||||
|
||||
$f = 0;
|
||||
while (($file = $_request->file("file", $f)) !== false) {
|
||||
$nf = new File($file['tmp_name']);
|
||||
$nf->rename(ROOT . "/attachments/" . $doc->id . "/" . $file['name']);
|
||||
$f++;
|
||||
}
|
||||
return redirect("/document/" . $doc->id);
|
||||
}
|
||||
|
||||
public static function upload_document($_request) {
|
||||
return blade("upload.document");
|
||||
}
|
||||
|
||||
public static function do_upload_document($_request) {
|
||||
$i = 0;
|
||||
while ($f = $_request->file("file", $i)) {
|
||||
|
||||
$file = new File($f['tmp_name']);
|
||||
$sha256 = $file->hash();
|
||||
|
||||
$rev = Revision::find([["sha256", "=", $sha256]])->first();
|
||||
|
||||
if ($rev) {
|
||||
return redirect("/uploads/duplicate/" . $rev->id);
|
||||
}
|
||||
|
||||
$u = new Upload();
|
||||
$u->filename = $f['name'];
|
||||
$u->sha256 = $sha256;
|
||||
$u->ocr = "N";
|
||||
$u->ai = "N";
|
||||
$u->owner = get_user()->id;
|
||||
$u->save();
|
||||
copy($f['tmp_name'], ROOT . "/uploads/" . $u->id . ".pdf");
|
||||
$j = new ImportGeminiJob($u->id);
|
||||
$j->queue();
|
||||
$j = new ImportOCRJob($u->id);
|
||||
$j->queue();
|
||||
$i++;
|
||||
}
|
||||
return redirect("/uploads");
|
||||
}
|
||||
|
||||
public static function rerun_upload($id) {
|
||||
$u = new Upload($id);
|
||||
if ($u->ocr == "F") {
|
||||
$u->ocr = "N";
|
||||
$u->save();
|
||||
$j = new ImportOCRJob($u->id);
|
||||
$j->queue();
|
||||
}
|
||||
|
||||
if ($u->ai == "F") {
|
||||
$u->ai = "N";
|
||||
$u->save();
|
||||
$j = new ImportGeminiJob($u->id);
|
||||
$j->queue();
|
||||
}
|
||||
return redirect("/uploads");
|
||||
}
|
||||
|
||||
public static function rerun_all() {
|
||||
$ai = Upload::find([["ai", "=", "F"], ["owner", "=", get_user()->id]])->all();
|
||||
foreach ($ai as $u) {
|
||||
$u->ai = "N";
|
||||
$u->save();
|
||||
$j = new ImportGeminiJob($u->id);
|
||||
$j->queue();
|
||||
}
|
||||
$ocr = Upload::find([["ocr", "=", "F"], ["owner", "=", get_user()->id]])->all();
|
||||
foreach ($ocr as $u) {
|
||||
$u->ocr = "N";
|
||||
$u->save();
|
||||
$j = new ImportOCRJob($u->id);
|
||||
$j->queue();
|
||||
}
|
||||
return redirect("/uploads");
|
||||
}
|
||||
|
||||
public static function uploads($_request) {
|
||||
return DocumentController::uploads_page($_request, 0);
|
||||
}
|
||||
|
||||
public static function uploads_page($_request, $page) {
|
||||
$uploads = Upload::find([["owner", "=", get_user()->id]])->orderBy("id")->limit($page * 20, 20)->all();
|
||||
return blade("upload.list", ["uploads" => $uploads, "page" => $page]);
|
||||
}
|
||||
|
||||
public static function discard_upload($_request, $id) {
|
||||
$u = new Upload($id);
|
||||
if ($u->owner != get_user()->id) {
|
||||
return [403, "Not yours, punk!"];
|
||||
}
|
||||
|
||||
DB::getInstance()->query("delete from job where source=:source", ["source" => "upload:" . $u->id]);
|
||||
|
||||
unlink($u->getPDF());
|
||||
$u->delete();
|
||||
return redirect("/uploads");
|
||||
}
|
||||
|
||||
public static function import($_request, $id, $source) {
|
||||
$u = new Upload($id);
|
||||
if ($u->owner != get_user()->id) {
|
||||
return [403, "Not yours, punk!"];
|
||||
}
|
||||
|
||||
$doc = false;
|
||||
|
||||
if ($source == "ai") {
|
||||
$doc = Document::find_by_docid($u->ai_docid);
|
||||
} else if ($source == "ocr") {
|
||||
$doc = Document::find_by_docid($u->ocr_docid);
|
||||
}
|
||||
|
||||
if (!$doc) {
|
||||
$doc = new Document;
|
||||
if ($source == "ai") {
|
||||
$doc->internal_id = $u->ai_docid;
|
||||
} else if ($source == "ocr") {
|
||||
$doc->internal_id = $u->ocr_docid;
|
||||
} else {
|
||||
$doc->internal_id = "XX-XXXXX-XX";
|
||||
}
|
||||
$doc->title = $u->title;
|
||||
$doc->subtitle = $u->subtitle;
|
||||
$doc->subsubtitle = $u->subsubtitle;
|
||||
$doc->overview = $u->overview;
|
||||
$doc->save();
|
||||
|
||||
$incoming = Product::find([["title", "=", "Incoming"]])->first();
|
||||
$dp = new DocProduct();
|
||||
$dp->document = $doc->id;
|
||||
$dp->product = $incoming->id;
|
||||
$dp->save();
|
||||
}
|
||||
|
||||
|
||||
$rev = new Revision();
|
||||
$rev->document = $doc->id;
|
||||
$rev->revid="";
|
||||
$rev->sha256 = $u->sha256;
|
||||
$rev->origtitle = $u->source;
|
||||
$rev->save();
|
||||
|
||||
|
||||
$ocr = new OCR();
|
||||
$ocr->revision = $rev->id;
|
||||
$ocr->body = $u->body;
|
||||
$ocr->save();
|
||||
|
||||
$out = new File($rev->path() . "/doc.pdf");
|
||||
$out->parent()->mkdir();
|
||||
|
||||
$pdf = $u->getPDF();
|
||||
$pdf->rename($out->path());
|
||||
|
||||
$db = DB::getInstance();
|
||||
$db->query("DELETE FROM job WHERE source=:source", ["source" => "upload:" . $u->id]);
|
||||
$u->delete();
|
||||
|
||||
$rev->info = $rev->getPDF()->info();
|
||||
$rev->save();
|
||||
|
||||
$j = new IndexJob($rev->id);
|
||||
$j->queue();
|
||||
|
||||
|
||||
//return redirect("/uploads");
|
||||
return redirect("/document/" . $doc->id);
|
||||
|
||||
}
|
||||
|
||||
public static function delete_upload($_request, $id) {
|
||||
$u = new Upload($id);
|
||||
if ($u->owner != get_user()->id) {
|
||||
return [403, "Not yours, punk!"];
|
||||
}
|
||||
}
|
||||
|
||||
public static function upload_duplicate($id) {
|
||||
$r = new Revision($id);
|
||||
return blade("upload.duplicate", ["rev" => $r]);
|
||||
}
|
||||
|
||||
public static function view_upload($id) {
|
||||
$u = new Upload($id);
|
||||
return $u->getPDF();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
class DownloadController {
|
||||
public static function get_download($id) {
|
||||
return new DownloadJob($id);
|
||||
}
|
||||
|
||||
public static function start_download() {
|
||||
}
|
||||
|
||||
public static function api_downloads() {
|
||||
$s = DownloadJob::find([["processed", "=", 0], ["owner", "=", get_user()->id]])->orderBy("queued")->limit(20);
|
||||
return $s->all();
|
||||
}
|
||||
|
||||
public static function api_add_download($_request) {
|
||||
$url = $_request->put("url");
|
||||
$d = new DownloadJob;
|
||||
$d->url = $url;
|
||||
$d->queued = time();
|
||||
$d->started = 0;
|
||||
$d->finished = 0;
|
||||
$d->processed = 0;
|
||||
$d->owner = get_user()->id;
|
||||
$d->file = sprintf("download/file-%08X-%08X", rand(), time());
|
||||
|
||||
$d->save();
|
||||
return DownloadController::api_downloads();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
class FavouriteController {
|
||||
|
||||
public static function user_set_favourite($doc) {
|
||||
$user = get_user();
|
||||
if ($user) {
|
||||
$user->set_favourite($doc);
|
||||
}
|
||||
return redirect("/document/" . $doc);
|
||||
}
|
||||
|
||||
public static function user_unset_favourite($doc) {
|
||||
$user = get_user();
|
||||
if ($user) {
|
||||
$user->unset_favourite($doc);
|
||||
}
|
||||
return redirect("/document/" . $doc);
|
||||
}
|
||||
|
||||
public static function favourites() {
|
||||
$user = get_user();
|
||||
if (!$user) return [403, "Huh... yeah right"];
|
||||
$f = $user->favourites();
|
||||
$docs = new Collection;
|
||||
foreach ($f as $fav) {
|
||||
$doc = new Document($fav->document);
|
||||
$docs->add($doc);
|
||||
}
|
||||
return blade("/favourites", ["docs" => $docs]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class HomeController {
|
||||
static function index() {
|
||||
return blade("index");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
class ImportController {
|
||||
public static function downloads() {
|
||||
return blade("downloads");
|
||||
}
|
||||
|
||||
public static function imports() {
|
||||
return blade("imports");
|
||||
}
|
||||
|
||||
public static function api_imports() {
|
||||
$is = ProcessJob::find([["imported", "=", 0]])->orderBy("queued")->limit(50)->all();
|
||||
foreach ($is as $i) {
|
||||
$i->load("revision");
|
||||
}
|
||||
return $is;
|
||||
}
|
||||
|
||||
public static function api_delete_import($id) {
|
||||
$d = new ProcessJob($id);
|
||||
if ($d->valid()) {
|
||||
$d->delete();
|
||||
}
|
||||
return ImportController::api_imports();
|
||||
}
|
||||
|
||||
public static function api_set_import($_request, $id) {
|
||||
$d = new ProcessJob($id);
|
||||
if ($d->valid()) {
|
||||
foreach ($_POST as $k=>$v) {
|
||||
$d->$k = $v;
|
||||
}
|
||||
$d->save();
|
||||
}
|
||||
return ImportController::api_imports();
|
||||
}
|
||||
|
||||
public static function api_add_document($_request, $id) {
|
||||
|
||||
$job = new ProcessJob($id);
|
||||
|
||||
$doc = Document::find([["internal_id", "=", $_request->put('internal_id')]])->first();
|
||||
if (!$doc) {
|
||||
$doc = new Document;
|
||||
$doc->internal_id = trim($_request->put('internal_id'));
|
||||
$doc->title = trim($_request->put('title'));
|
||||
$doc->subtitle = trim($_request->put('subtitle'));
|
||||
$doc->subsubtitle = trim($_request->put('subsubtitle'));
|
||||
$doc->overview = trim($_request->put('overview'));
|
||||
$doc->owner = get_user()->id;
|
||||
$doc->save();
|
||||
|
||||
$prods = explode(",", $_request->put('products'));
|
||||
foreach ($prods as $product) {
|
||||
$dp = new DocProduct;
|
||||
$dp->document = $doc->id;
|
||||
$dp->product = $product;
|
||||
$dp->save();
|
||||
}
|
||||
}
|
||||
|
||||
$job->document = $doc->id;
|
||||
|
||||
$rev = new Revision($job->revision);
|
||||
$rev->document = $doc->id;
|
||||
$rev->revno = trim($_request->put('revno'));
|
||||
$rev->month = $_request->put('month');
|
||||
$rev->year = $_request->put('year');
|
||||
$rev->owner = get_user()->id;
|
||||
$rev->save();
|
||||
|
||||
$job->imported = time();
|
||||
$job->save();
|
||||
|
||||
return ImportController::api_imports();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
class JobController {
|
||||
public static function api_get_jobs($source) {
|
||||
$db = DB::getInstance();
|
||||
$q = $db->query("select * from job where source=:source order by queued", ["source" => $source]);
|
||||
|
||||
$c = new Collection();
|
||||
|
||||
while ($r = $db->nextRecord($q)) {
|
||||
$c->push($r);
|
||||
}
|
||||
return $c;
|
||||
}
|
||||
|
||||
public static function api_delete_job($id) {
|
||||
$db = DB::getInstance();
|
||||
$q = $db->query("select * from job where id=:id", ["id" => $id]);
|
||||
$r = $db->nextRecord($q);
|
||||
$q = $db->query("delete from job where id=:id", ["id" => $id]);
|
||||
return JobController::api_get_jobs($r->source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
class PDFController {
|
||||
public static function download($_request, $id, $type, $filename) {
|
||||
|
||||
$rev = new Revision($id);
|
||||
$path = $rev->path();
|
||||
|
||||
$rev->downloads++;
|
||||
$rev->last_download = time();
|
||||
$rev->save();
|
||||
|
||||
$disp = "attachment";
|
||||
$mime = "application/octet-stream";
|
||||
|
||||
$file = null;
|
||||
|
||||
switch ($type) {
|
||||
case "view":
|
||||
$file = new PDF($path . "/doc.pdf");
|
||||
break;
|
||||
case "download":
|
||||
$file = new PDF($path . "/doc.pdf");
|
||||
$file->force_download();
|
||||
break;
|
||||
case "viewocr":
|
||||
$file = new PDF($path . "/ocr.pdf");
|
||||
break;
|
||||
case "downloadocr":
|
||||
$file = new PDF($path . "/ocr.pdf");
|
||||
$file->force_download();
|
||||
break;
|
||||
}
|
||||
$file->fake_filename($filename);
|
||||
|
||||
if ($file == null) {
|
||||
return [404, "Not Found 1"];
|
||||
}
|
||||
|
||||
if (!$file->exists()) {
|
||||
return [404, "Not Found 2"];
|
||||
}
|
||||
|
||||
$file->set_header("link", "<" . Config::get("URL") . "/pdf/" . $id . "/download/" . $filename . '>; rel="canonical"');
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
public static function get_page($id, $page) {
|
||||
$rev = new Revision($id);
|
||||
$page = $rev->get_page($page, 150);
|
||||
if ($page) {
|
||||
return $page;
|
||||
}
|
||||
return [404, "Not Found"];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
class ProductController {
|
||||
public static function api_get_list($list) {
|
||||
$db = DB::getInstance();
|
||||
$pids = explode(",", $list);
|
||||
$out = new Collection();
|
||||
foreach ($pids as $pid) {
|
||||
$q = $db->query("select * from product where id=:id", ["id" => $pid]);
|
||||
if ($r = $db->nextRecord($q)) {
|
||||
$out->push($r);
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public static function api_get_mru() {
|
||||
$out = new Collection;
|
||||
$mru = Session::get("mru");
|
||||
if (!$mru) {
|
||||
$mru = [];
|
||||
}
|
||||
foreach ($mru as $p) {
|
||||
$prod = new Product($p);
|
||||
if ($prod) {
|
||||
$out->push($prod);
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public static function api_add_mru($id) {
|
||||
$mru = Session::get("mru");
|
||||
if (!$mru) {
|
||||
$mru = [];
|
||||
}
|
||||
array_unshift($mru, $id);
|
||||
$mru = array_unique($mru);
|
||||
while (count($mru) > 5) {
|
||||
array_pop($mru);
|
||||
}
|
||||
Session::set("mru", $mru);
|
||||
return ProductController::api_get_mru();
|
||||
}
|
||||
|
||||
public static function api_search() {
|
||||
|
||||
return Product::find([["full_path", "like", "%" . $_POST['search'] . "%"]])->orderBy("full_path")->all();
|
||||
}
|
||||
|
||||
public static function api_add_child($_request, $id) {
|
||||
$p = new Product;
|
||||
$p->parent = $id;
|
||||
$p->title = $_request->put("title");
|
||||
$p->save();
|
||||
return $p;
|
||||
}
|
||||
|
||||
public static function api_set($id) {
|
||||
$p = new Product($id);
|
||||
|
||||
$data = [];
|
||||
foreach ($_POST as $k=>$v) {
|
||||
$p->$k = $v;
|
||||
}
|
||||
$p->save();
|
||||
|
||||
return $p;
|
||||
}
|
||||
|
||||
public static function api_move($id) {
|
||||
$p = new Product($id);
|
||||
$p->parent = $_POST['to'];
|
||||
$p->save();
|
||||
return $p;
|
||||
}
|
||||
|
||||
public static function api_delete($id) {
|
||||
$product = new Product($id);
|
||||
if ($product->valid()) {
|
||||
$laf = Product::find([["title", "=", "Lost and Found"]])->first();
|
||||
|
||||
DB::getInstance()->query("delete from docproduct where product=:pid", ["pid" => $product->id]);
|
||||
|
||||
foreach ($product->children as $c) {
|
||||
$c->parent = $laf;
|
||||
$c->save();
|
||||
}
|
||||
$product->load("parent");
|
||||
$par = $product->parent;
|
||||
$product->delete();
|
||||
return $par;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public static function api_empty_trash($id) {
|
||||
$prod = new Product($id);
|
||||
$docs = $prod->documents;
|
||||
|
||||
foreach ($docs as $doc) {
|
||||
$revs = $doc->revisions;
|
||||
foreach ($revs as $rev) {
|
||||
$rev->delete();
|
||||
}
|
||||
|
||||
$dpl = DocProduct::find([["document", "=", $doc->id]])->all();
|
||||
foreach ($dpl as $dp) {
|
||||
$dp->delete();
|
||||
}
|
||||
|
||||
$doc->delete();
|
||||
}
|
||||
|
||||
$prod->invalidate("documents");
|
||||
|
||||
|
||||
return [];
|
||||
|
||||
}
|
||||
|
||||
public static function api_available_metadata($id) {
|
||||
$prod = new Product($id);
|
||||
$exist = $prod->meta();
|
||||
$metas = MetaType::find()->all();
|
||||
|
||||
$out = new Collection();
|
||||
|
||||
foreach ($metas as $meta) {
|
||||
if (!in_array($meta->id, $exist)) {
|
||||
$cl = new stdClass;
|
||||
$cl->key = $meta->id;
|
||||
$cl->value = $meta->name;
|
||||
$out->push($cl);
|
||||
}
|
||||
}
|
||||
|
||||
$out->sort("value");
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public static function api_add_metadata($_request, $id) {
|
||||
$prod = new Product($id);
|
||||
$prod->add_meta($_request->put('item_id'));
|
||||
return Collection::from_array($prod->meta());
|
||||
}
|
||||
|
||||
public static function api_gemini_all($id, $move) {
|
||||
$prod = new Product($id);
|
||||
|
||||
$c = new Collection();
|
||||
|
||||
foreach ($prod->documents as $doc) {
|
||||
if ($prod->oneliner == "") {
|
||||
$job = new GeminiJob($doc->id, "document:" . $doc->id);
|
||||
$job->move = ($move == "move");
|
||||
$jobid = $job->queue();
|
||||
$c->push(["document" => $doc->id, "job" => $jobid]);
|
||||
}
|
||||
}
|
||||
return $c;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
class RevisionController {
|
||||
|
||||
public static function show($id) {
|
||||
|
||||
$rev = new Revision($id);
|
||||
$rev->load("document");
|
||||
|
||||
|
||||
if (!@$rev->info->Pages) {
|
||||
$rev->info = $rev->getPDF()->info();
|
||||
$rev->save();
|
||||
}
|
||||
|
||||
return blade("revision", ["rev" => $rev]);
|
||||
}
|
||||
|
||||
public static function api_set($id) {
|
||||
$r = new Revision($id);
|
||||
if ($r !== false) {
|
||||
foreach ($_POST as $k=>$v) {
|
||||
$r->$k = trim($v);
|
||||
}
|
||||
$r->save();
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
public static function delete($id) {
|
||||
$r = new Revision($id);
|
||||
$r->load("document");
|
||||
$doc = $r->document;
|
||||
$r->delete();
|
||||
return redirect("/document/" . $doc->id);
|
||||
}
|
||||
|
||||
public static function redownload($id) {
|
||||
$r = new Revision($id);
|
||||
$j = new RedownloadJob($r->id, $r->origtitle, $r->path() . "/doc.pdf");
|
||||
$jobid = $j->queue();
|
||||
flash("success", "Job queued as ID " . $jobid);
|
||||
return redirect("/revision/" . $id);
|
||||
}
|
||||
|
||||
public static function purge($id) {
|
||||
$r = new Revision($id);
|
||||
$r->purge();
|
||||
return redirect("/revision/" . $id);
|
||||
}
|
||||
|
||||
public static function recompress($id) {
|
||||
$r = new Revision($id);
|
||||
$j = new RecompressJob($r->id);
|
||||
$j->queue();
|
||||
flash("info", "Recompression job queued");
|
||||
return redirect("/revision/" . $id);
|
||||
}
|
||||
|
||||
public static function ocr($id) {
|
||||
$j = new OCRJob($id);
|
||||
$j->queue();
|
||||
return redirect("/revision/" . $id);
|
||||
}
|
||||
|
||||
public static function rate_revision($id, $rating) {
|
||||
$user = get_user();
|
||||
if (!$user) {
|
||||
return new Revision($id);
|
||||
}
|
||||
$db = DB::getInstance();
|
||||
$db->query("INSERT INTO revision_rating (owner,revision,rating) values (:owner,:revision,:rating) ON DUPLICATE KEY UPDATE rating=:rating", [
|
||||
"owner" => $user->id,
|
||||
"revision" => $id,
|
||||
"rating" => $rating
|
||||
]);
|
||||
$db->query("UPDATE revision SET rating=(SELECT AVG(rating) FROM revision_rating WHERE revision=:revision) WHERE id=:revision", ["revision" => $id]);
|
||||
|
||||
return new Revision($id);
|
||||
|
||||
}
|
||||
|
||||
public static function split_revision($id) {
|
||||
$r = new Revision($id);
|
||||
$r->load("document");
|
||||
$doc = new Document();
|
||||
$doc->title = $r->document->title;
|
||||
$doc->subtitle = $r->document->subtitle;
|
||||
$doc->subsubtitle = $r->document->subsubtitle;
|
||||
$doc->overview = $r->document->overview;
|
||||
$doc->oneliner = $r->document->oneliner;
|
||||
$doc->internal_id = $r->document->internal_id;
|
||||
$doc->save();
|
||||
foreach ($r->document->products as $prod) {
|
||||
$dp = new DocProduct();
|
||||
$dp->document = $doc->id;
|
||||
$dp->product = $prod->id;
|
||||
$dp->save();
|
||||
}
|
||||
$r->document = $doc->id;
|
||||
$r->save();
|
||||
|
||||
return redirect("/document/" . $doc->id);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
class SearchController {
|
||||
static public function api_title_search() {
|
||||
$out = new Collection;
|
||||
|
||||
$q = DB::getInstance()->query("
|
||||
select
|
||||
id, internal_id, title, subtitle, subsubtitle,
|
||||
match (internal_id, title, subtitle, subsubtitle, overview)
|
||||
against (:search in boolean mode)
|
||||
as rel
|
||||
from
|
||||
document
|
||||
where
|
||||
match (internal_id, title, subtitle, subsubtitle, overview)
|
||||
against (:search in boolean mode)
|
||||
order by
|
||||
rel desc
|
||||
limit
|
||||
10
|
||||
", ["search" => $_POST['search']]);
|
||||
while ($r = DB::getInstance()->nextRecord($q)) {
|
||||
$o = new Document($r->id);
|
||||
$out->push($o);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
static public function search($page = 0) {
|
||||
if (array_key_exists("search", $_POST)) {
|
||||
$q = DB::getInstance()->query("
|
||||
select
|
||||
revision.id as id,
|
||||
match(ocr.body) against (:search) as relevance
|
||||
from
|
||||
revision,ocr
|
||||
where
|
||||
match(ocr.body) against (:search) and
|
||||
ocr.revision = revision.id and
|
||||
not revision.document is null
|
||||
order by
|
||||
relevance desc
|
||||
|
||||
", ["search" => $_POST['search']]);
|
||||
|
||||
$slog = [];
|
||||
|
||||
while ($r = DB::getInstance()->nextRecord($q)) {
|
||||
$slog[] = $r->id;
|
||||
}
|
||||
|
||||
Session::set("search", json_encode($slog));
|
||||
}
|
||||
|
||||
$rpp = 8;
|
||||
|
||||
$offset = $page * $rpp;
|
||||
|
||||
$out = [];
|
||||
|
||||
$slog = json_decode(Session::get("search"));
|
||||
|
||||
for ($i = 0; $i < $rpp; $i++) {
|
||||
if ($offset + $i < count($slog)) {
|
||||
$rev = new Revision($slog[$offset + $i]);
|
||||
if ($rev) {
|
||||
$out[] = $rev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return blade("search", ["page" => $page, "count" => count($slog), "results" => $out, "pages" => ceil(count($slog) / $rpp)]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
class SitemapController {
|
||||
public static function generate() {
|
||||
$docs = Document::find()->all();
|
||||
$revs = Revision::find()->all();
|
||||
return blade("sitemap", ["docs" => $docs, "revs" => $revs]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
class SpiderController {
|
||||
|
||||
public static function spider_pdfs() {
|
||||
return blade("spider_pdfs");
|
||||
}
|
||||
|
||||
public static function spider_pages() {
|
||||
return blade("spider_pages");
|
||||
}
|
||||
|
||||
public static function api_pdfs() {
|
||||
$pdfs = Spider::find([["status", "=", "N"]])->orderBy("id")->limit(20)->all();
|
||||
|
||||
return $pdfs;
|
||||
}
|
||||
|
||||
public static function api_pages() {
|
||||
$pages = SpiderPage::find([["status", "=", "O"], ["title", "!=", ""]])->orderBy("title")->limit(20)->all();
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
public static function api_reject_pdf($id = null) {
|
||||
if ($id == null) return [];
|
||||
|
||||
$i = explode(",", $id);
|
||||
foreach ($i as $id) {
|
||||
$pdf = new Spider($id);
|
||||
if ($pdf) {
|
||||
$pdf->status = "B";
|
||||
$pdf->save();
|
||||
}
|
||||
}
|
||||
return SpiderController::api_pdfs();
|
||||
}
|
||||
|
||||
public static function api_accept_pdf($_request, $id = null) {
|
||||
if ($id == null) return [];
|
||||
|
||||
$i = explode(",", $id);
|
||||
foreach ($i as $id) {
|
||||
$pdf = new Spider($id);
|
||||
if ($pdf) {
|
||||
$pdf->status = "D";
|
||||
$pdf->save();
|
||||
|
||||
$job = new DownloadJob($pdf->url);
|
||||
$job->queue(get_user()->id);
|
||||
}
|
||||
}
|
||||
return SpiderController::api_pdfs();
|
||||
}
|
||||
|
||||
public static function api_reject_page($_request, $id) {
|
||||
$i = explode(",", $id);
|
||||
foreach ($i as $id) {
|
||||
$page = new SpiderPage($id);
|
||||
if ($page) {
|
||||
$page->status = "B";
|
||||
$page->save();
|
||||
}
|
||||
}
|
||||
return SpiderController::api_pages();
|
||||
}
|
||||
|
||||
public static function api_accept_page($id) {
|
||||
|
||||
$i = explode(",", $id);
|
||||
foreach ($i as $id) {
|
||||
$page = new SpiderPage($id);
|
||||
if ($page) {
|
||||
$page->status = "N";
|
||||
$page->save();
|
||||
}
|
||||
}
|
||||
return SpiderController::api_pages();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
class SystemController {
|
||||
public static function status() {
|
||||
$status = [
|
||||
"B" => "Blacklisted",
|
||||
"N" => "Pending",
|
||||
"F" => "Failed",
|
||||
"D" => "Done",
|
||||
"Y" => "Done",
|
||||
"W" => "Postponed",
|
||||
"P" => "Processing",
|
||||
"X" => "Deleted",
|
||||
"O" => "Off-site",
|
||||
"Q" => "Postponed",
|
||||
"R" => "Redirect",
|
||||
];
|
||||
|
||||
$q = DB::getInstance()->query("select count(id) as c, status from pages group by status order by status");
|
||||
$spider = [];
|
||||
while ($r = DB::getInstance()->nextRecord($q)) {
|
||||
$spider[@$status[$r->status]] = $r->c;
|
||||
}
|
||||
|
||||
$q = DB::getInstance()->query("select count(id) as c, status from spider group by status order by status");
|
||||
$pdf = [];
|
||||
while ($r = DB::getInstance()->nextRecord($q)) {
|
||||
$pdf[@$status[$r->status]] = $r->c;
|
||||
}
|
||||
|
||||
$q = DB::getInstance()->query("select count(id) as c, ocr from revision group by ocr order by ocr");
|
||||
$ocr = [];
|
||||
while ($r = DB::getInstance()->nextRecord($q)) {
|
||||
$ocr[@$status[$r->ocr]] = $r->c;
|
||||
}
|
||||
|
||||
$q = DB::getInstance()->query("select count(id) as c, idx from revision group by idx order by idx");
|
||||
$idx = [];
|
||||
while ($r = DB::getInstance()->nextRecord($q)) {
|
||||
$idx[@$status[$r->idx]] = $r->c;
|
||||
}
|
||||
|
||||
return blade("status", ["spider" => $spider, "pdf" => $pdf, "ocr" => $ocr, "idx" => $idx]);
|
||||
}
|
||||
|
||||
|
||||
public static function api_get_idmatches() {
|
||||
return IDMatch::find([["id", ">=", 0]])->orderBy("weight")->all();
|
||||
}
|
||||
|
||||
public static function api_add_idmatch($_request) {
|
||||
$i = new IDMatch;
|
||||
$i->example = $_request->put('example');
|
||||
$i->regex = $_request->put('regex');
|
||||
$i->weight = $_request->put('weight');
|
||||
$i->save();
|
||||
return SystemController::api_get_idmatches();
|
||||
}
|
||||
|
||||
public static function api_set_idmatch($id) {
|
||||
$i = new IDMatch($id);
|
||||
if ($i) {
|
||||
$i->example = $_POST['example'];
|
||||
$i->regex = $_POST['regex'];
|
||||
$i->weight = $_POST['weight'];
|
||||
$i->save();
|
||||
}
|
||||
return SystemController::api_get_idmatches();
|
||||
}
|
||||
|
||||
public static function api_del_idmatch($id) {
|
||||
$i = new IDMatch($id);
|
||||
if ($i) {
|
||||
$i->delete();
|
||||
}
|
||||
return SystemController::api_get_idmatches();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function systems() {
|
||||
$u = get_user();
|
||||
$sys = System::find([["owner", "=", $u->id]])->all();
|
||||
return blade("systems", ["systems" => $sys]);
|
||||
}
|
||||
|
||||
public static function system($id) {
|
||||
$sys = new System($id);
|
||||
$sys->load("documents");
|
||||
return blade("system", ["sys" => $sys]);
|
||||
}
|
||||
|
||||
public static function systems_add() {
|
||||
return blade("systems_add");
|
||||
}
|
||||
|
||||
public static function systems_do_add($_request) {
|
||||
$sys = new System;
|
||||
$sys->name = $_request->post("name");
|
||||
$sys->model = $_request->post("model");
|
||||
$sys->notes = $_request->post("notes");
|
||||
$sys->owner = get_user()->id;
|
||||
$sys->save();
|
||||
return redirect("/system/" . $sys->id);
|
||||
}
|
||||
|
||||
public static function system_add_doc($id, $doc) {
|
||||
$sys = new System($id);
|
||||
if ($sys->owner == get_user()->id) {
|
||||
$exist = SystemDoc::find([
|
||||
["system", "=", $sys->id],
|
||||
["document", "=", $doc]
|
||||
])->first();
|
||||
if (!$exist) {
|
||||
$sd = new SystemDoc;
|
||||
$sd->system = $sys->id;
|
||||
$sd->document = $doc;
|
||||
$sd->save();
|
||||
}
|
||||
return redirect("/system/" . $id);
|
||||
}
|
||||
return [403, "Forbidden"];
|
||||
}
|
||||
|
||||
public static function system_del_doc($id, $doc) {
|
||||
$sys = new System($id);
|
||||
if ($sys->owner == get_user()->id) {
|
||||
$sd = SystemDoc::find([
|
||||
["system", "=", $sys->id],
|
||||
["document", "=", $doc]
|
||||
])->first();
|
||||
if ($sd) {
|
||||
$sd->delete();
|
||||
}
|
||||
return redirect("/system/" . $id);
|
||||
}
|
||||
return [403, "Forbidden"];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
class DownloadJob extends Job {
|
||||
|
||||
public static function jobs() { return 3; }
|
||||
|
||||
public $from = null;
|
||||
public $to = null;
|
||||
|
||||
private $_pct = 0;
|
||||
|
||||
public function __construct($from) {
|
||||
$this->from = $from;
|
||||
parent::__construct("download");
|
||||
}
|
||||
|
||||
public function generate_to() {
|
||||
$filename = ROOT . "/download/file-" . time() . "-" . rand() . ".pdf";
|
||||
return $filename;
|
||||
}
|
||||
|
||||
public function run() {
|
||||
|
||||
|
||||
$this->to = $this->generate_to();
|
||||
$ch = curl_init();
|
||||
$this->status("Downloading: 0%");
|
||||
$fd = fopen($this->to, "w");
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $this->from);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36");
|
||||
curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
|
||||
curl_setopt($ch, CURLOPT_PRIVATE, $this);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 3600);
|
||||
curl_setopt($ch, CURLOPT_FILETIME, true);
|
||||
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, [$this, 'download_progress']);
|
||||
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
|
||||
curl_setopt($ch, CURLOPT_FILE, $fd);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
|
||||
print("Running...\n");
|
||||
$r = curl_exec($ch);
|
||||
fclose($fd);
|
||||
|
||||
$file = new File($this->to);
|
||||
$sha256 = $file->hash();
|
||||
|
||||
$rev = Revision::find([["sha256", "=", $sha256]])->first();
|
||||
|
||||
if ($rev) {
|
||||
$this->status("Duplicate file");
|
||||
$this->fail();
|
||||
print("Finished (duplicate)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
$u = new Upload();
|
||||
$u->filename = basename($this->from);
|
||||
$u->sha256 = $sha256;
|
||||
$u->source = $this->from;
|
||||
$u->ocr = "N";
|
||||
$u->ai = "N";
|
||||
$u->owner = $this->getOwner();
|
||||
$u->save();
|
||||
$file->rename(ROOT . "/uploads/" . $u->id . ".pdf");
|
||||
$j = new ImportGeminiJob($u->id);
|
||||
$j->queue($u->owner);
|
||||
$j = new ImportOCRJob($u->id);
|
||||
$j->queue($u->owner);
|
||||
|
||||
$this->status("Finished");
|
||||
print("Finished\n");
|
||||
$this->finish();
|
||||
}
|
||||
|
||||
|
||||
|
||||
function download_progress($ch, $download_size, $downloaded, $upload_size, $uploaded) {
|
||||
if ($download_size == 0) return;
|
||||
$pct = round($downloaded / $download_size * 100);
|
||||
if ($pct != $this->_pct) {
|
||||
$this->status("Downloading: " . $pct . "%");
|
||||
$this->_pct = $pct;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
class GeminiJob extends Job {
|
||||
|
||||
public static function jobs() { return 1; }
|
||||
|
||||
public $docid;
|
||||
public $move=false;
|
||||
|
||||
public function __construct($docid, $source="unknown") {
|
||||
parent::__construct($source);
|
||||
$this->docid = $docid;
|
||||
}
|
||||
|
||||
public function run() {
|
||||
|
||||
$pdf = false;
|
||||
|
||||
$subs = [
|
||||
"Installation and Operating Information",
|
||||
"Installation and Configuration",
|
||||
"Installing and Getting Started",
|
||||
"Installation/Operator's Manual",
|
||||
"Installation/Operator's Guide",
|
||||
"Programmer's Reference Guide",
|
||||
"Field Maintenance Print Set",
|
||||
"Illustrated Parts Breakdown",
|
||||
"Installation/Owner's Guide",
|
||||
"Installation Information",
|
||||
"Installation/User Guide",
|
||||
"User Documentation Kit",
|
||||
"Programmer Information",
|
||||
"Technical Description",
|
||||
"Operator Information",
|
||||
"Configuration Guide",
|
||||
"Upgrade Information",
|
||||
"Installation Manual",
|
||||
"Service Information",
|
||||
"Installation Guide",
|
||||
"Programming Manual",
|
||||
"Maintenance Manual",
|
||||
"Maintenance Guide",
|
||||
"Technical Summary",
|
||||
"Operator's Guide",
|
||||
"System Reference",
|
||||
"User Information",
|
||||
"Technical Manual",
|
||||
"Language Manual",
|
||||
"Service Manual",
|
||||
"Service Guide",
|
||||
"Read Me First",
|
||||
"Owner's Guide",
|
||||
"Release Notes",
|
||||
"Options Guide",
|
||||
"Users' Manual",
|
||||
"User's Manual",
|
||||
"HiTest Notes",
|
||||
"User's Guide",
|
||||
"Design Guide",
|
||||
"User Manual",
|
||||
"User Guide",
|
||||
|
||||
];
|
||||
|
||||
$doc = new Document($this->docid);
|
||||
|
||||
$minsize = 999999999999999;
|
||||
|
||||
foreach ($doc->revisions as $r) {
|
||||
$p = new PDF($r->path() . "/doc.pdf");
|
||||
if ($p->size() < $minsize) {
|
||||
$minsize = $p->size();
|
||||
$rev = $r;
|
||||
$pdf = $p;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$pdf) {
|
||||
$this->fail("No files to upload");
|
||||
return;
|
||||
}
|
||||
|
||||
$prods = [];
|
||||
$db = DB::getInstance();
|
||||
$q = $db->query("select id, full_path from product");
|
||||
while ($r = $db->nextRecord($q)) {
|
||||
$prods[] = $r->id . "," . $r->full_path;
|
||||
}
|
||||
|
||||
if ($doc->is_incoming()) {
|
||||
$this->move = true;
|
||||
}
|
||||
|
||||
file_put_contents("/tmp/category-list.csv", implode("\n", $prods) . "\n");
|
||||
|
||||
$this->status("Processing document with Gemini");
|
||||
try {
|
||||
$gemini = new Gemini();
|
||||
// $gemini->verbose = true;
|
||||
|
||||
$p = new GeminiParameter("title", "object", "The title of the document (note: none of these parts should include the order number)", true);
|
||||
$p->add_child("main_title", "string", "The primary portion of the title, properly recapitalized to fit English grammar", true);
|
||||
$p->add_child("subtitle", "string", "Any portion of the title that could be used as a subtitle, such as 'user guide' or 'maintenance guide' etc. Remove this from the main title. Should not be the order number.");
|
||||
$p->add_child("subsubtitle", "string", "Any part of the title that could be extra information such as version number or language. Should not be the order number.");
|
||||
$gemini->add_parameter($p);
|
||||
|
||||
$p = new GeminiParameter("category", "integer", "The category the document best fits in from the provided list of categories in the file category-list.csv", true);
|
||||
$gemini->add_parameter($p);
|
||||
|
||||
$gemini->add_parameter(new GeminiParameter("order_number", "string", "The document order number, often in the format XX-YYYYY-ZZ where YYYYY is based on the product code and ZZ is the type of document. Never more than 15 characters long."));
|
||||
$gemini->add_parameter(new GeminiParameter("overview", "string", "A brief one-line overview of the document content in plain text format", true));
|
||||
$gemini->add_parameter(new GeminiParameter("summary", "string", "A longer summary of the document content in markdown format", true));
|
||||
$gemini->upload_callback([$this, "uploadcb"]);
|
||||
$gemini->process_callback([$this, "processcb"]);
|
||||
|
||||
$gemini->attach($pdf->basename(), $pdf);
|
||||
$gemini->attach("category-list.csv", new File("/tmp/category-list.csv"));
|
||||
|
||||
$lines = $gemini->geminiOverview();
|
||||
} catch (Exception $e) {
|
||||
$this->status($e->getMessage());
|
||||
if ($e->getMessage() == "The request timed out. Please try again.") {
|
||||
$this->retry("timeout");
|
||||
} else if ($e->getMessage() == "HTTP Error 0 requesting AI assistance") {
|
||||
$this->retry("error 0");
|
||||
} else if (str_starts_with($e->getMessage(), "You exceeded your current quota, please check your plan and billing details.")) {
|
||||
$this->retries++;
|
||||
if ($this->retries < 5) {
|
||||
$this->retry("quota exceeded");
|
||||
} else {
|
||||
$this->fail("Too many retries");
|
||||
}
|
||||
} else if ($e->getMessage() == "This model is currently experiencing high demand. Spikes in demand are usually temporary. Please try again later.") {
|
||||
$this->retry("overload");
|
||||
} else {
|
||||
$this->fail();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$data = json_decode($lines, true);
|
||||
|
||||
if (!$data) {
|
||||
$this->fail("No response from AI");
|
||||
return;
|
||||
}
|
||||
|
||||
// Reload document since this took a while
|
||||
$doc = new Document($this->docid);
|
||||
|
||||
if (array_key_exists("title", $data)) {
|
||||
$doc->subtitle = "";
|
||||
$doc->subsubtitle = "";
|
||||
if (array_key_exists("main_title", $data["title"])) { $doc->title = $data['title']['main_title']; }
|
||||
if (array_key_exists("subtitle", $data["title"])) { $doc->subtitle = $data['title']['subtitle']; }
|
||||
if (array_key_exists("subsubtitle", $data["title"])) { $doc->subsubtitle = $data['title']['subsubtitle']; }
|
||||
if ($doc->subtitle != "") {
|
||||
$doc->title = str_replace(" " . $doc->subtitle, "", $doc->title);
|
||||
}
|
||||
}
|
||||
if (array_key_exists("order_number", $data)) { $doc->internal_id = substr($data['order_number'], 0, 20); }
|
||||
if (array_key_exists("summary", $data)) { $doc->overview = $data['summary']; }
|
||||
if (array_key_exists("overview", $data)) { $doc->oneliner = $data['overview']; }
|
||||
|
||||
|
||||
|
||||
$doc->save();
|
||||
|
||||
if (array_key_exists("category", $data)) {
|
||||
$cat = $data['category'];
|
||||
$q = $db->query("select * from product where id=:id", ["id" => $cat]);
|
||||
if ($r = $db->nextRecord($q)) {
|
||||
if ($this->move) {
|
||||
$q = $db->query("delete from docproduct where document=:id", ["id" => $doc->id]);
|
||||
}
|
||||
$edp = DocProduct::find([
|
||||
["product", "=", $r->id],
|
||||
["document", "=", $doc->id]
|
||||
])->first();
|
||||
|
||||
if (!$edp) {
|
||||
$dp = new DocProduct;
|
||||
$dp->document = $doc->id;
|
||||
$dp->product = $r->id;
|
||||
$dp->save();
|
||||
}
|
||||
} else {
|
||||
print("--- Incorrect category suggested: " . $cat . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
$this->status("Finished");
|
||||
$this->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
public function uploadcb($percent) {
|
||||
$this->status("Uploading: " . $percent . "% complete");
|
||||
}
|
||||
|
||||
public function processcb($message) {
|
||||
$this->status($message);
|
||||
}
|
||||
|
||||
function cleanup($txt) {
|
||||
$txt = str_replace("Ø", "0", $txt);
|
||||
$txt = str_replace(".", " ", $txt);
|
||||
return $txt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
class ImportGeminiJob extends Job {
|
||||
|
||||
public static function jobs() { return 1; }
|
||||
|
||||
public $upload_id;
|
||||
|
||||
public function __construct($upload_id) {
|
||||
parent::__construct("upload:" . $upload_id);
|
||||
$this->upload_id = $upload_id;
|
||||
}
|
||||
|
||||
public function run() {
|
||||
|
||||
Config::refresh();
|
||||
|
||||
$pdf = false;
|
||||
|
||||
$subs = [
|
||||
"Installation and Operating Information",
|
||||
"Installation and Configuration",
|
||||
"Installing and Getting Started",
|
||||
"Installation/Operator's Manual",
|
||||
"Installation/Operator's Guide",
|
||||
"Programmer's Reference Guide",
|
||||
"Field Maintenance Print Set",
|
||||
"Illustrated Parts Breakdown",
|
||||
"Installation/Owner's Guide",
|
||||
"Installation Information",
|
||||
"Installation/User Guide",
|
||||
"User Documentation Kit",
|
||||
"Programmer Information",
|
||||
"Technical Description",
|
||||
"Operator Information",
|
||||
"Configuration Guide",
|
||||
"Upgrade Information",
|
||||
"Installation Manual",
|
||||
"Service Information",
|
||||
"Installation Guide",
|
||||
"Programming Manual",
|
||||
"Maintenance Manual",
|
||||
"Maintenance Guide",
|
||||
"Technical Summary",
|
||||
"Operator's Guide",
|
||||
"System Reference",
|
||||
"User Information",
|
||||
"Technical Manual",
|
||||
"Language Manual",
|
||||
"Service Manual",
|
||||
"Service Guide",
|
||||
"Read Me First",
|
||||
"Owner's Guide",
|
||||
"Release Notes",
|
||||
"Options Guide",
|
||||
"Users' Manual",
|
||||
"User's Manual",
|
||||
"HiTest Notes",
|
||||
"User's Guide",
|
||||
"Design Guide",
|
||||
"User Manual",
|
||||
"User Guide",
|
||||
|
||||
];
|
||||
|
||||
$doc = new Upload($this->upload_id);
|
||||
$doc->ai = "P";
|
||||
$doc->save();
|
||||
$pdf = $doc->getPDF();
|
||||
|
||||
if (!$pdf) {
|
||||
$this->fail();
|
||||
$doc->ai = "F";
|
||||
$this->status("PDF File Missing");
|
||||
$doc->save();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($pdf->size() > 50000000) {
|
||||
$this->status("File too large");
|
||||
$doc->ai = "F";
|
||||
$doc->save();
|
||||
$this->fail();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->status("Processing " . $doc->filename . " with Gemini");
|
||||
try {
|
||||
$gemini = new Gemini();
|
||||
$gemini->upload_callback([$this, "uploadcb"]);
|
||||
$gemini->process_callback([$this, "processcb"]);
|
||||
$lines = $gemini->geminiOverview($pdf);
|
||||
} catch (Exception $e) {
|
||||
$mess = $e->getMessage();
|
||||
print($mess . "\n");
|
||||
|
||||
switch ($mess) {
|
||||
case "This model is currently experiencing high demand. Spikes in demand are usually temporary. Please try again later.":
|
||||
case "The request timed out. Please try again.":
|
||||
case "HTTP Error 0 requesting AI assistance":
|
||||
$this->retry();
|
||||
print("Retrying\n");
|
||||
break;
|
||||
default:
|
||||
$this->fail();
|
||||
$this->status(substr($mess, 0, 250));
|
||||
$doc->overview = $mess;
|
||||
$doc->ai = "F";
|
||||
$doc->save();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$this->status("Postprocessing returned data");
|
||||
$ld = explode("\n", $lines);
|
||||
if (preg_match('/^\{(.*)\}$/', trim($ld[0]), $m)) {
|
||||
$title = trim($m[1]);
|
||||
|
||||
$title = str_replace("’", "'", $title);
|
||||
|
||||
$newsub = "";
|
||||
$newsubsub = "";
|
||||
|
||||
|
||||
$b = explode(":", $title);
|
||||
if (count($b) > 1) {
|
||||
$title = trim(array_shift($b));
|
||||
$newsub = trim(array_shift($b));
|
||||
$newsubsub = implode(": ", $b);
|
||||
}
|
||||
|
||||
if ($newsub == "") {
|
||||
foreach ($subs as $sub) {
|
||||
if (str_ends_with($title, $sub)) {
|
||||
$newsub = $sub;
|
||||
$title = substr($title, 0, 0 - (strlen($sub) + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$doc->title = $title;
|
||||
$doc->subtitle = $newsub;
|
||||
$doc->subsubtitle = $newsubsub;
|
||||
|
||||
array_shift($ld);
|
||||
if (trim($ld[0]) == "") {
|
||||
array_shift($ld);
|
||||
}
|
||||
}
|
||||
if (preg_match('/^\[(.*)\]$/', trim($ld[0]), $m)) {
|
||||
|
||||
$iid = IDMatch::find_docid($this->cleanup($m[1]));
|
||||
|
||||
if ($iid) {
|
||||
$doc->ai_docid = $iid[0];
|
||||
}
|
||||
array_shift($ld);
|
||||
}
|
||||
$doc->overview = implode("\n", $ld);
|
||||
$doc->ai = "Y";
|
||||
|
||||
$doc->save();
|
||||
$this->status("Finished");
|
||||
$this->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
public function uploadcb($percent) {
|
||||
$this->status("Uploading: " . $percent . "% complete");
|
||||
}
|
||||
|
||||
public function processcb($message) {
|
||||
$this->status($message);
|
||||
}
|
||||
|
||||
function cleanup($txt) {
|
||||
$txt = str_replace("Ø", "0", $txt);
|
||||
$txt = str_replace(".", " ", $txt);
|
||||
return $txt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
class ImportOCRJob extends Job {
|
||||
|
||||
public static function jobs() { return 1; }
|
||||
|
||||
public $upload_id;
|
||||
|
||||
public function __construct($upload_id) {
|
||||
parent::__construct("upload:" . $upload_id);
|
||||
$this->upload_id = $upload_id;
|
||||
}
|
||||
|
||||
public function run() {
|
||||
|
||||
$pdf = false;
|
||||
|
||||
$doc = new Upload($this->upload_id);
|
||||
$doc->ocr = "P";
|
||||
$doc->save();
|
||||
$pdf = $doc->getPDF();
|
||||
|
||||
if (!$pdf) {
|
||||
$this->fail();
|
||||
$doc->ocr = "F";
|
||||
$this->status("PDF File Missing");
|
||||
$doc->save();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->status("Processing " . $doc->filename . " with OCR");
|
||||
|
||||
$body = $pdf->text()->get_text();
|
||||
if ($body == "") {
|
||||
$this->status("Running OCR on PDF file");
|
||||
$ocr = new PDF($pdf->path() . "-ocr.pdf");
|
||||
$pdf->ocr($ocr);
|
||||
$ocr->rename($pdf->path(), true);
|
||||
$pdf = $doc->getPDF();
|
||||
$body = $pdf->text()->get_text();
|
||||
}
|
||||
|
||||
$doc->body = $body;
|
||||
|
||||
$docid = IDMatch::find_docid($doc->filename . " " . $body);
|
||||
if ($docid) {
|
||||
$doc->ocr_docid = $docid[0];
|
||||
}
|
||||
|
||||
$doc->ocr = "Y";
|
||||
$doc->save();
|
||||
|
||||
$this->status("Finished");
|
||||
$this->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
public function uploadcb($percent) {
|
||||
$this->status("Uploading: " . $percent . "% complete");
|
||||
}
|
||||
|
||||
public function processcb($message) {
|
||||
$this->status($message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
class IndexJob extends Job {
|
||||
|
||||
public static function jobs() { return 5; }
|
||||
|
||||
public $revision;
|
||||
|
||||
|
||||
public function __construct($rev) {
|
||||
parent::__construct("revision:" . $rev);
|
||||
$this->revision = $rev;
|
||||
}
|
||||
|
||||
public function run() {
|
||||
|
||||
$db = DB::getInstance();
|
||||
|
||||
$rev = new Revision($this->revision);
|
||||
$rev->load("body");
|
||||
$body = $rev->body;
|
||||
|
||||
if ($body) {
|
||||
$body->counted = 'P';
|
||||
$body->save();
|
||||
|
||||
|
||||
$db->query("DELETE FROM word_index WHERE revision=:rev", ["rev" => $rev->id]);
|
||||
|
||||
$words = $body->count_words();
|
||||
|
||||
foreach ($words as $word=>$hits) {
|
||||
$db->query("INSERT INTO word_index (revision, word, hits) VALUES (:rev, :word, :hits)", [
|
||||
"rev" => $rev->id,
|
||||
"word" => substr($word, 0, 20),
|
||||
"hits" => $hits
|
||||
]);
|
||||
}
|
||||
$body->counted = 'Y';
|
||||
$body->save();
|
||||
$this->finish();
|
||||
} else {
|
||||
$this->fail();
|
||||
$this->status("No body to count");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
class OCRJob extends Job {
|
||||
|
||||
public static function jobs() { return 1; }
|
||||
public $revision;
|
||||
|
||||
public function __construct($rev) {
|
||||
parent::__construct("revision:" . $rev);
|
||||
$this->revision = $rev;
|
||||
}
|
||||
|
||||
public function run() {
|
||||
$ocr = OCR::find([["revision", "=", $this->revision]])->first();
|
||||
if (!$ocr) {
|
||||
$ocr = new OCR;
|
||||
$ocr->revision = $this->revision;
|
||||
$ocr->save();
|
||||
}
|
||||
|
||||
$this->status("Processing");
|
||||
|
||||
$rev = new Revision($this->revision);
|
||||
$pdf = $rev->getPDF();
|
||||
$text = $pdf->text()->get_text();
|
||||
|
||||
print("[" . $text . "]\n");
|
||||
|
||||
if ($text == "") {
|
||||
$this->status("Running OCR on PDF");
|
||||
$newpdf = new PDF($pdf->parent() . "/ocr.pdf");
|
||||
$pdf->ocr($newpdf);
|
||||
$text = $newpdf->text()->get_text();
|
||||
}
|
||||
|
||||
$ocr->body = $text;
|
||||
$ocr->save();
|
||||
$j = new IndexJob($this->revision);
|
||||
$j->queue();
|
||||
$this->status("Complete");
|
||||
$this->finish();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
class ProcessJob extends Job {
|
||||
|
||||
public static function jobs() { return 1; }
|
||||
|
||||
public $docid = 0;
|
||||
|
||||
public function __construct($docid) {
|
||||
parent::__construct("document:$docid");
|
||||
$this->docid = $docid;
|
||||
}
|
||||
|
||||
public function run() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
class RecompressJob extends Job {
|
||||
|
||||
public static function jobs() { return 1; }
|
||||
|
||||
public $RevisionID;
|
||||
|
||||
public function __construct($id) {
|
||||
parent::__construct("revision:" . $id);
|
||||
$this->RevisionID = $id;
|
||||
}
|
||||
public function run() {
|
||||
$this->status("Recompressing");
|
||||
$rev = new Revision($this->RevisionID);
|
||||
$crev = new Revision;
|
||||
|
||||
$crev->revid = $rev->revid;
|
||||
$crev->save();
|
||||
|
||||
$from = $rev->getPDF();
|
||||
$to = $crev->getPDF();
|
||||
|
||||
$to->parent()->mkdir();
|
||||
$from->recompress($to->path());
|
||||
|
||||
$crev->document = $rev->document;
|
||||
$crev->info = $to->info();
|
||||
$crev->sha256 = $to->hash();
|
||||
$crev->save();
|
||||
|
||||
$this->status("New revision " . $crev->id);
|
||||
$this->finish();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
class RedownloadJob extends Job {
|
||||
|
||||
public static function jobs() { return 1; }
|
||||
|
||||
public $from = null;
|
||||
public $to = null;
|
||||
public $revid = null;
|
||||
|
||||
private $_pct = 0;
|
||||
|
||||
public function __construct($revid, $from, $to) {
|
||||
$this->from = $from;
|
||||
$this->to = $to;
|
||||
$this->revid = $revid;
|
||||
parent::__construct("revision:" . $revid);
|
||||
}
|
||||
|
||||
public function run() {
|
||||
$ch = curl_init();
|
||||
$this->status("Downloading: 0%");
|
||||
$fd = fopen($this->to, "w");
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $this->from);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36");
|
||||
curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
|
||||
curl_setopt($ch, CURLOPT_PRIVATE, $this);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 3600);
|
||||
curl_setopt($ch, CURLOPT_FILETIME, true);
|
||||
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, [$this, 'download_progress']);
|
||||
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
|
||||
curl_setopt($ch, CURLOPT_FILE, $fd);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
|
||||
print("Running...\n");
|
||||
$r = curl_exec($ch);
|
||||
fclose($fd);
|
||||
$sha = hash_file("sha256", $this->to);
|
||||
$rev = new Revision($this->revid);
|
||||
$rev->sha256 = $sha;
|
||||
$rev->save();
|
||||
print("Finished\n");
|
||||
$this->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function download_progress($ch, $download_size, $downloaded, $upload_size, $uploaded) {
|
||||
if ($download_size == 0) return;
|
||||
$pct = round($downloaded / $download_size * 100);
|
||||
if ($pct != $this->_pct) {
|
||||
$this->status("Downloading: " . $pct . "%");
|
||||
$this->_pct = $pct;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
class DocMeta extends Model {
|
||||
|
||||
protected $_classes = [
|
||||
"document" => "Document",
|
||||
"metadata" => "MetaType"
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
class DocProduct extends Model {
|
||||
protected $_classes = [
|
||||
"document" => "Document",
|
||||
"product" => "Product"
|
||||
];
|
||||
|
||||
|
||||
protected $_model = [
|
||||
"id" => [MODEL_SERIAL],
|
||||
"created" => [MODEL_BIGINT, 11],
|
||||
"updated" => [MODEL_BIGINT, 11],
|
||||
"document" => [MODEL_OBJECT, "Document"],
|
||||
"product" => [MODEL_OBJECT, "Product"]
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
class Document extends Model {
|
||||
|
||||
protected $_computed = [
|
||||
"related" => "get_related",
|
||||
"revisions" => "get_revisions",
|
||||
"products" => "get_products",
|
||||
"metadata" => "get_metadata",
|
||||
"attachments" => "get_attachments",
|
||||
];
|
||||
|
||||
public function get_revisions() {
|
||||
return Revision::find([["document", "=", $this->id]])->orderByDesc("rating")->all();
|
||||
}
|
||||
|
||||
public function get_related() {
|
||||
$revs = Revision::find([["document", "=", $this->id]])->all();
|
||||
$related = new Collection;
|
||||
foreach ($revs as $rev) {
|
||||
$q = DB::getInstance()->query("
|
||||
select distinct
|
||||
id,
|
||||
internal_id,
|
||||
title,
|
||||
subtitle,
|
||||
subsubtitle
|
||||
from
|
||||
document
|
||||
where
|
||||
internal_id in (
|
||||
select
|
||||
distinct words.word
|
||||
from
|
||||
words,
|
||||
revwords
|
||||
where
|
||||
words.id=revwords.word
|
||||
and
|
||||
revwords.revision=:rev
|
||||
and
|
||||
words.word like '%-%-%'
|
||||
)", array("rev" => $rev->id));
|
||||
while ($r = DB::getInstance()->nextRecord($q)) {
|
||||
if ($r->id != $this->id) {
|
||||
$related->push(new Document($r->id));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $related;
|
||||
}
|
||||
|
||||
public function overview_md() {
|
||||
return \Michelf\Markdown::defaultTransform($this->overview);
|
||||
}
|
||||
|
||||
public function on_delete() {
|
||||
DB::getInstance()->query("delete from docproduct where document=:id", ["id" => $this->id]);
|
||||
}
|
||||
|
||||
private $_products = null;
|
||||
|
||||
public function get_products() {
|
||||
if ($this->_products == null) {
|
||||
|
||||
$this->_products = new Collection;
|
||||
$dpl = DocProduct::find([["document", "=", $this->id]])->all();
|
||||
foreach ($dpl as $dp) {
|
||||
$dp->load("product");
|
||||
$this->_products->push($dp->product);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->_products;
|
||||
}
|
||||
|
||||
public function duplicate() {
|
||||
$newdoc = new Document();
|
||||
$newdoc->title = $this->title;
|
||||
$newdoc->subtitle = $this->subtitle;
|
||||
$newdoc->subsubtitle = $this->subsubtitle;
|
||||
$newdoc->overview = $this->overview;
|
||||
$newdoc->internal_id = $this->internal_id;
|
||||
$newdoc->owner = $this->owner;
|
||||
$newdoc->year = $this->year;
|
||||
$newdoc->month = $this->month;
|
||||
|
||||
$newdoc->save();
|
||||
|
||||
$dpl = DocProduct::find([["document", "=", $this->id]])->all();
|
||||
foreach ($dpl as $dp) {
|
||||
$ndp = new DocProduct();
|
||||
$ndp->document = $newdoc->id;
|
||||
$ndp->product = $dp->product;
|
||||
$ndp->save();
|
||||
}
|
||||
return $newdoc;
|
||||
}
|
||||
|
||||
public function remove_product($id) {
|
||||
$dp = DocProduct::find([["document", "=", $this->id], ["product", "=", $id]])->first();
|
||||
if ($dp) {
|
||||
$dp->delete();
|
||||
}
|
||||
}
|
||||
|
||||
public function set_metadata($metadata, $value) {
|
||||
if (!$metadata) return false;
|
||||
$m = DocMeta::find([["document", "=", $this->id], ["metadata", "=", $metadata]])->first();
|
||||
if (!$m) {
|
||||
$m = new DocMeta();
|
||||
$m->document = $this->id;
|
||||
$m->metadata = $metadata;
|
||||
}
|
||||
$m->data = $value;
|
||||
$m->save();
|
||||
return $m->id;
|
||||
}
|
||||
|
||||
public function get_metadata() {
|
||||
return DocMeta::find([["document", "=", $this->id]])->all();
|
||||
}
|
||||
|
||||
public function remove_metadata($metadata) {
|
||||
$m = DocMeta::find([["document", "=", $this->id], ["metadata", "=", $metadata]])->first();
|
||||
if ($m) {
|
||||
$m->delete();
|
||||
}
|
||||
}
|
||||
|
||||
public function get_metadata_by_id($metadata) {
|
||||
$m = DocMeta::find([["document", "=", $this->id], ["metadata", "=", $metadata]])->first();
|
||||
if (!$m) {
|
||||
return "";
|
||||
}
|
||||
return $m->data;
|
||||
}
|
||||
|
||||
public function guess_docid() {
|
||||
$text = $this->title . " " . $this->subtitle . " " . $this->subsubtitle;
|
||||
$text .= $this->overview;
|
||||
|
||||
$words = $this->get_words($text);
|
||||
|
||||
return IDMatch::find_docid($words)[0];
|
||||
}
|
||||
|
||||
|
||||
function get_words($text) {
|
||||
$text = str_replace("\r", " ", $text);
|
||||
$text = str_replace("\n\n", "\n", $text);
|
||||
$text = str_replace("\n\n", "\n", $text);
|
||||
$text = str_replace("\n\n", "\n", $text);
|
||||
$text = str_replace("\n\n", "\n", $text);
|
||||
$text = str_replace("\n", " ", $text);
|
||||
$text = str_replace("\"", "", $text);
|
||||
$text = str_replace(",", " ", $text);
|
||||
$text = str_replace("—", "-", $text);
|
||||
$text = str_replace("~", "-", $text);
|
||||
$text = str_replace("--", "-", $text);
|
||||
|
||||
$text = preg_replace('/([^a-zA-Z\-0-9]+)/', ' ', $text);
|
||||
|
||||
//$text = preg_replace("/([A-Za-z])(- )/", '$1', $text);
|
||||
|
||||
$words = preg_split('/\s+/', $text);
|
||||
return $words;
|
||||
}
|
||||
|
||||
|
||||
function get_attachments() {
|
||||
$ap = ROOT . "/attachments/" . $this->id;
|
||||
$files = new Collection;
|
||||
if (!file_exists($ap)) {
|
||||
return $files;
|
||||
}
|
||||
if (!is_dir($ap)) {
|
||||
return $files;
|
||||
}
|
||||
|
||||
$dir = opendir($ap);
|
||||
while ($f = readdir($dir)) {
|
||||
if (substr($f, 0, 1) == ".") continue;
|
||||
$f = new File($ap . "/" . $f);
|
||||
$files->push($f);
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
public static function find_by_docid($docid) {
|
||||
$db = DB::getInstance();
|
||||
$q = $db->query("select id from document where internal_id=:docid", ["docid" => $docid]);
|
||||
if ($r = $db->nextRecord($q)) {
|
||||
return new Document($r->id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function is_incoming() {
|
||||
foreach ($this->get_products() as $prod) {
|
||||
if (preg_match("/ Incoming /", $prod->full_path, $m)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
|
||||
class Favourite extends Model {
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
class IDMatch extends Model {
|
||||
|
||||
public static function find_docid($words) {
|
||||
if (!is_array($words)) {
|
||||
$words = IDMatch::get_words($words);
|
||||
}
|
||||
$matches = IDMatch::find()->orderBy("weight")->all();
|
||||
|
||||
foreach ($matches as $match) {
|
||||
$preg = '/^' . $match->regex . '$/';
|
||||
|
||||
foreach ($words as $word) {
|
||||
$word = strtoupper($word);
|
||||
$word = str_replace("(", "C", $word);
|
||||
$word = str_replace("=", "-", $word);
|
||||
$word = str_replace("--", "-", $word);
|
||||
|
||||
if (preg_match($preg, $word, $m)) {
|
||||
return [$m[1], @$m[2]];
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function get_words($text) {
|
||||
$text = str_replace("\r", " ", $text);
|
||||
$text = str_replace("\n\n", "\n", $text);
|
||||
$text = str_replace("\n\n", "\n", $text);
|
||||
$text = str_replace("\n\n", "\n", $text);
|
||||
$text = str_replace("\n\n", "\n", $text);
|
||||
$text = str_replace("\n", " ", $text);
|
||||
$text = str_replace("\"", "", $text);
|
||||
$text = str_replace(",", " ", $text);
|
||||
$text = str_replace("–", "-", $text);
|
||||
$text = str_replace("—", "-", $text);
|
||||
$text = str_replace("~", "-", $text);
|
||||
$text = str_replace("--", "-", $text);
|
||||
|
||||
$text = preg_replace('/([^a-zA-Z\-0-9]+)/', ' ', $text);
|
||||
|
||||
//$text = preg_replace("/([A-Za-z])(- )/", '$1', $text);
|
||||
|
||||
$words = preg_split('/\s+/', $text);
|
||||
return $words;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
class MetaType extends Model {
|
||||
protected $table = "metatypes";
|
||||
|
||||
|
||||
public static function name($id) {
|
||||
$m = new MetaType($id);
|
||||
return $m->name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
class OCR extends Model {
|
||||
|
||||
public function get_words() {
|
||||
$t = new Text($this->body);
|
||||
return $t->words();
|
||||
}
|
||||
|
||||
public function count_words() {
|
||||
$words = $this->get_words();
|
||||
|
||||
$counted = [];
|
||||
|
||||
foreach ($words as $word) {
|
||||
if (strlen($word) > 2) {
|
||||
@$counted[strtoupper($word)] ++;
|
||||
}
|
||||
}
|
||||
|
||||
return $counted;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
class Product extends Model {
|
||||
|
||||
protected $_classes = [
|
||||
"parent" => "Product"
|
||||
];
|
||||
|
||||
protected $_computed = [
|
||||
"documents" => "get_documents",
|
||||
"children" => "get_children",
|
||||
];
|
||||
|
||||
protected $_triggers = [
|
||||
"parent" => "update_path",
|
||||
"title" => "update_path",
|
||||
];
|
||||
|
||||
private $_children = null;
|
||||
public function get_children() {
|
||||
$c = $this->cache_get("children");
|
||||
if ($c) return $c;
|
||||
|
||||
if ($this->_children == null) {
|
||||
$this->_children = Product::find([["parent", "=", $this->id]])->orderBy("title")->all();
|
||||
}
|
||||
$this->cache_set("children", $this->_children);
|
||||
return $this->_children;
|
||||
}
|
||||
|
||||
public function get_full_title() {
|
||||
$tree = $this->get_tree();
|
||||
|
||||
$n = [];
|
||||
foreach ($tree as $t) {
|
||||
$n[] = $t->title;
|
||||
}
|
||||
|
||||
$out = implode(" / ", $n);
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function update_path($ppath = null) {
|
||||
if ($ppath == null) {
|
||||
$this->full_path = $this->get_full_title();
|
||||
} else {
|
||||
$this->full_path = $ppath . " / " . $this->title;
|
||||
}
|
||||
$this->full_path = str_replace("/ / ", "/ ", $this->full_path);
|
||||
$this->save();
|
||||
foreach ($this->get_children() as $child) {
|
||||
$child->update_path($this->fill_path);
|
||||
}
|
||||
}
|
||||
|
||||
public function get_tree() {
|
||||
$out = [];
|
||||
if ($this->load("parent")) {
|
||||
$p = $this->parent;
|
||||
$out = $p->get_tree();
|
||||
}
|
||||
array_push($out, $this);
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function overview_md() {
|
||||
return \Michelf\Markdown::defaultTransform($this->overview);
|
||||
}
|
||||
|
||||
public function on_delete() {
|
||||
DB::getInstance()->query("delete from docproduct where product=:id", ["id" => $this->id]);
|
||||
}
|
||||
|
||||
private $_documents = null;
|
||||
|
||||
public function get_documents() {
|
||||
|
||||
$d = $this->cache_get("documents");
|
||||
if ($d) return $d;
|
||||
|
||||
if ($this->_documents == null) {
|
||||
//$dpl = DocProduct::find([["product", "=", $this->id]])->limit(100)->all();
|
||||
$dpl = DocProduct::find([["product", "=", $this->id]])->all();
|
||||
$this->_documents = new Collection;
|
||||
foreach ($dpl as $dp) {
|
||||
if ($dp->load("document")) {
|
||||
$this->_documents->push($dp->document);
|
||||
}
|
||||
}
|
||||
|
||||
$this->_documents->sort("subsubtitle", true);
|
||||
$this->_documents->sort("subtitle", true);
|
||||
$this->_documents->sort("title", true);
|
||||
}
|
||||
$this->cache_set("documents", $this->_documents);
|
||||
return $this->_documents;
|
||||
}
|
||||
|
||||
public function add_document($doc) {
|
||||
$dp = new DocProduct;
|
||||
$dp->product = $this->id;
|
||||
$dp->document = $doc->id;
|
||||
$dp->save();
|
||||
}
|
||||
|
||||
public function meta() {
|
||||
if ($this->metadata == null) {
|
||||
return [];
|
||||
}
|
||||
return explode(",", $this->metadata);
|
||||
}
|
||||
|
||||
public function add_meta($id, $save = true) {
|
||||
$m = $this->meta();
|
||||
if (!in_array($id, $m)) {
|
||||
$m[] = $id;
|
||||
}
|
||||
$this->metadata = implode(",", $m);
|
||||
if ($save) $this->save();
|
||||
}
|
||||
|
||||
public function del_meta($id, $save = true) {
|
||||
$m = $this->meta();
|
||||
$o = [];
|
||||
foreach ($m as $v) {
|
||||
if ($v != $id) {
|
||||
$o[] = $v;
|
||||
}
|
||||
}
|
||||
$this->metadaya = implode(",", $o);
|
||||
if ($save) $this->save();
|
||||
}
|
||||
|
||||
public function documents_sorted_by_meta() {
|
||||
|
||||
if ($this->metadata == null) {
|
||||
$meta = [];
|
||||
} else {
|
||||
$meta = explode(",", $this->metadata);
|
||||
}
|
||||
|
||||
$docs = $this->get_documents();
|
||||
|
||||
|
||||
while (count($meta) > 0) {
|
||||
$mid = array_pop($meta);
|
||||
$docs->sort_with_function( function($a, $b) use ($mid) {
|
||||
$va = $a->get_metadata_by_id($mid);
|
||||
$vb = $b->get_metadata_by_id($mid);
|
||||
|
||||
if ($va > $vb) return 1;
|
||||
if ($va < $vb) return -1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return $docs;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
class Revision extends Model {
|
||||
|
||||
protected $_classes = [
|
||||
"document" => "Document"
|
||||
];
|
||||
|
||||
protected $_computed = [
|
||||
"body" => "get_body",
|
||||
];
|
||||
|
||||
protected $transform = [
|
||||
"info" => "json"
|
||||
];
|
||||
|
||||
function path() {
|
||||
$rs = sprintf("%011d", $this->id);
|
||||
$d1 = substr($rs, 0, 2);
|
||||
$d2 = substr($rs, 2, 3);
|
||||
$d3 = substr($rs, 5, 3);
|
||||
$d4 = substr($rs, 8, 3);
|
||||
$p = sprintf("%s/pdf/%s/%s/%s/%s", ROOT, $d1, $d2, $d3, $d4);
|
||||
return $p;
|
||||
}
|
||||
|
||||
function filename() {
|
||||
if ($this->document) {
|
||||
if (is_numeric($this->document)) {
|
||||
$this->load("document");
|
||||
}
|
||||
$out = $this->document->internal_id;
|
||||
if (($this->revno != "") && ($this->revno != "0")) {
|
||||
$out .= "-";
|
||||
$out .= $this->revno;
|
||||
}
|
||||
$out .= " ";
|
||||
$out .= $this->document->title . " " . $this->document->subtitle . " " . $this->document->subsubtitle;
|
||||
} else {
|
||||
$out = "doc";
|
||||
}
|
||||
$out = trim($out);
|
||||
$out.= ".pdf";
|
||||
$out = str_replace(" ", "_", $out);
|
||||
$out = str_replace("/", "_", $out);
|
||||
return $out;
|
||||
}
|
||||
|
||||
function create_cover() {
|
||||
$p = $this->path();
|
||||
$pdf = new PDF($p . "/doc.pdf");
|
||||
$cover = $p . "/cover.jpg";
|
||||
$pdf->extract_page(0, $cover);
|
||||
}
|
||||
|
||||
function cover($size = null) {
|
||||
$f = new File($this->path() . "/cover.jpg");
|
||||
if (!$f->exists()) {
|
||||
$this->create_cover();
|
||||
}
|
||||
if ($size == null) {
|
||||
return "/cover/" . $this->id . "/cover.jpg";
|
||||
}
|
||||
return "/cover/" . $this->id . "/" . $size . "/cover.jpg";
|
||||
}
|
||||
|
||||
private $_body = null;
|
||||
|
||||
public function get_body() {
|
||||
if ($this->_body == null) {
|
||||
$ocr = OCR::find([["revision", "=", $this->id]])->first();
|
||||
if (!$ocr) {
|
||||
return false;
|
||||
}
|
||||
$this->_body = $ocr;
|
||||
}
|
||||
return $this->_body;
|
||||
}
|
||||
|
||||
public function get_page($page, $dpi = 300) {
|
||||
$file = new File(sprintf("%s/pages/%04d-%d.jpg", $this->path(), $page, $dpi));
|
||||
if ($file->exists()) {
|
||||
return new Image($file);
|
||||
}
|
||||
|
||||
$dir = sprintf("%s/pages", $this->path());
|
||||
if (!file_exists($dir)) {
|
||||
mkdir($dir, 0777);
|
||||
}
|
||||
|
||||
$pdf = new PDF(sprintf("%s/doc.pdf", $this->path()));
|
||||
$img = $pdf->extract_page($page, $file->path(), $dpi);
|
||||
|
||||
if ($img) {
|
||||
return $img;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function purge() {
|
||||
$dir = opendir($this->path());
|
||||
while ($file = readdir($dir)) {
|
||||
if (str_ends_with($file, ".jpg")) {
|
||||
unlink($this->path() . "/" . $file);
|
||||
}
|
||||
}
|
||||
closedir($dir);
|
||||
|
||||
$dir = opendir($this->path() . "/pages");
|
||||
while ($file = readdir($dir)) {
|
||||
if (str_ends_with($file, ".jpg")) {
|
||||
unlink($this->path() . "/pages/" . $file);
|
||||
}
|
||||
}
|
||||
closedir($dir);
|
||||
}
|
||||
|
||||
public function getPDF() {
|
||||
$p = $this->path();
|
||||
$pdf = new PDF($p . "/doc.pdf");
|
||||
return $pdf;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
|
||||
class Spider extends Model {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
|
||||
class SpiderBanned extends Model {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
|
||||
class SpiderDom extends Model {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
class SpiderPage extends Model {
|
||||
protected $table="pages";
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
class System extends Model {
|
||||
protected $table = "systems";
|
||||
|
||||
protected $_computed = [
|
||||
"documents" => "get_documents",
|
||||
];
|
||||
|
||||
public function get_documents() {
|
||||
$docs = SystemDoc::find([["system", "=", $this->id]])->all();
|
||||
$dl = new Collection;
|
||||
foreach ($docs as $d) {
|
||||
$doc = new Document($d->document);
|
||||
if ($doc) {
|
||||
$dl->add($doc);
|
||||
}
|
||||
}
|
||||
$dl->sort("title");
|
||||
return $dl;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
class SystemDoc extends Model {
|
||||
protected $table = "systemdocs";
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
class Upload extends Model {
|
||||
|
||||
public function getPDF() {
|
||||
return new PDF(ROOT . "/uploads/" . $this->id . ".pdf");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
class User extends Model {
|
||||
public static $table = "users";
|
||||
|
||||
public function is_favourite($doc) {
|
||||
$f = Favourite::find([
|
||||
["owner", "=", $this->id],
|
||||
["document", "=", $doc]])->first();
|
||||
if ($f) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function set_favourite($doc) {
|
||||
$f = Favourite::find([
|
||||
["owner", "=", $this->id],
|
||||
["document", "=", $doc]])->first();
|
||||
if (!$f) {
|
||||
$f = new Favourite;
|
||||
$f->owner = $this->id;
|
||||
$f->document = $doc;
|
||||
$f->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function unset_favourite($doc) {
|
||||
$f = Favourite::find([
|
||||
["owner", "=", $this->id],
|
||||
["document", "=", $doc]])->first();
|
||||
if ($f) {
|
||||
$f->delete();
|
||||
}
|
||||
}
|
||||
|
||||
public function favourites() {
|
||||
$f = Favourite::find([
|
||||
["owner", "=", $this->id],
|
||||
])->all();
|
||||
return $f;
|
||||
}
|
||||
}
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
$opts = getopt("c:j:");
|
||||
|
||||
$class = null;
|
||||
$jobid = null;
|
||||
|
||||
if (array_key_exists("c", $opts)) {
|
||||
$class = $opts['c'];
|
||||
}
|
||||
|
||||
if (array_key_exists("j", $opts)) {
|
||||
$jobid = $opts['j'];
|
||||
}
|
||||
|
||||
require_once(__DIR__ . "/../app.php");
|
||||
|
||||
print("Job Runner Starting\n");
|
||||
|
||||
$running = true;
|
||||
|
||||
while ($running) {
|
||||
$job = Job::consumeNextJob($class);
|
||||
if ($job) {
|
||||
print("Executing " . $job->getJobClass() . " job " . $job->jobID() . "\n");
|
||||
try {
|
||||
$job->run();
|
||||
} catch (PDOException $e) {
|
||||
print_r($e);
|
||||
} catch (Exception $e) {
|
||||
print_r($e);
|
||||
}
|
||||
print("Job finished\n");
|
||||
} else {
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
if (file_exists("/tmp/dpr_jobrunner_quit")) {
|
||||
unlink("/tmp/dpr_jobrunner_quit");
|
||||
$running = false;
|
||||
}
|
||||
}
|
||||
|
||||
print("Job Runner Exited Cleanly\n");
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
file_put_contents("/tmp/dpr_jobrunner_quit", "");
|
||||
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"require": {
|
||||
"michelf/php-markdown": "^2.0",
|
||||
"jenssegers/blade": "^2.0",
|
||||
"illuminate/view": "11.7.0"
|
||||
}
|
||||
}
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
ARG PHP_VERSION=8.4
|
||||
|
||||
FROM php:${PHP_VERSION}-rc-apache-bookworm AS base
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
libmemcached-dev \
|
||||
libssl-dev \
|
||||
libzip-dev \
|
||||
libzip4 \
|
||||
libjpeg-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
zlib1g-dev \
|
||||
ocrmypdf \
|
||||
poppler-utils \
|
||||
mariadb-client \
|
||||
libpng-dev \
|
||||
tmux \
|
||||
imagemagick \
|
||||
pdftk \
|
||||
&& \
|
||||
printf "" | pecl install -D 'with-libmemcached-dir="/usr"' memcached && \
|
||||
docker-php-ext-enable memcached && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY policy.xml /etc/ImageMagick-6
|
||||
|
||||
RUN apt-get update \
|
||||
&& docker-php-ext-install mysqli pdo pdo_mysql \
|
||||
&& docker-php-ext-enable pdo_mysql
|
||||
|
||||
RUN apt-get update \
|
||||
&& docker-php-ext-configure gd --with-jpeg \
|
||||
&& docker-php-ext-install gd \
|
||||
&& docker-php-ext-enable gd
|
||||
|
||||
RUN apt-get update \
|
||||
&& docker-php-ext-configure pcntl --enable-pcntl \
|
||||
&& docker-php-ext-install pcntl \
|
||||
&& docker-php-ext-enable pcntl
|
||||
|
||||
RUN a2enmod rewrite
|
||||
|
||||
ENV APACHE_DOCUMENT_ROOT /var/www/html/public
|
||||
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
|
||||
RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf
|
||||
|
||||
COPY memory.ini /usr/local/etc/php/conf.d
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y internetarchive
|
||||
|
||||
RUN rm -f /var/log/apache2/*
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
import time
|
||||
|
||||
import redis
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
cache = redis.Redis(host='redis', port=6379)
|
||||
|
||||
def get_hit_count():
|
||||
retries = 5
|
||||
while True:
|
||||
try:
|
||||
return cache.incr('hits')
|
||||
except redis.exceptions.ConnectionError as exc:
|
||||
if retries == 0:
|
||||
raise exc
|
||||
retries -= 1
|
||||
time.sleep(0.5)
|
||||
|
||||
@app.route('/')
|
||||
def hello():
|
||||
count = get_hit_count()
|
||||
return f'Hello World! I have been seen {count} times.\n'
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Booting..."
|
||||
|
||||
apachectl start
|
||||
|
||||
while `true`; do
|
||||
sleep 1
|
||||
done
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
pacman -Sy
|
||||
pacman -S --noconfirm --overwrite=/usr/lib/* glibc gcc-libs openssl-1.1
|
||||
ldconfig
|
||||
pacman -S --noconfirm apache mariadb-clients php php-gd php-memcache
|
||||
ls -l /usr/lib/libcrypto.so*
|
||||
pacman -S --needed --noconfirm sudo # Install sudo
|
||||
useradd builduser -m # Create the builduser
|
||||
passwd -d builduser # Delete the buildusers password
|
||||
printf 'builduser ALL=(ALL) ALL\n' | tee -a /etc/sudoers # Allow the builduser passwordless sudo
|
||||
sudo -u builduser bash -c 'cd /tmp && git clone https://aur.archlinux.org/ocrmypdf ocrmypdf && cd ocrmypdf && makepkg -si --noconfirm' # Clone and build a package
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
restart: always
|
||||
ports:
|
||||
- "8000:80"
|
||||
runtime: nvidia
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: 1
|
||||
capabilities: [gpu]
|
||||
volumes:
|
||||
- type: bind
|
||||
source: /data/Web/decpdf.site/
|
||||
target: /var/www/html/
|
||||
db:
|
||||
image: "mariadb"
|
||||
restart: always
|
||||
volumes:
|
||||
- type: bind
|
||||
source: /data/Web/decpdf.site/database
|
||||
target: /var/lib/mysql
|
||||
environment:
|
||||
MARIADB_ROOT_PASSWORD: wibble
|
||||
memcache:
|
||||
image: "memcached"
|
||||
restart: always
|
||||
@@ -0,0 +1,3 @@
|
||||
memory_limit = 2G
|
||||
upload_max_filesize = 2G
|
||||
post_max_size = 2G
|
||||
Executable
+98
@@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE policymap [
|
||||
<!ELEMENT policymap (policy)*>
|
||||
<!ATTLIST policymap xmlns CDATA #FIXED ''>
|
||||
<!ELEMENT policy EMPTY>
|
||||
<!ATTLIST policy xmlns CDATA #FIXED '' domain NMTOKEN #REQUIRED
|
||||
name NMTOKEN #IMPLIED pattern CDATA #IMPLIED rights NMTOKEN #IMPLIED
|
||||
stealth NMTOKEN #IMPLIED value CDATA #IMPLIED>
|
||||
]>
|
||||
<!--
|
||||
Configure ImageMagick policies.
|
||||
|
||||
Domains include system, delegate, coder, filter, path, or resource.
|
||||
|
||||
Rights include none, read, write, execute and all. Use | to combine them,
|
||||
for example: "read | write" to permit read from, or write to, a path.
|
||||
|
||||
Use a glob expression as a pattern.
|
||||
|
||||
Suppose we do not want users to process MPEG video images:
|
||||
|
||||
<policy domain="delegate" rights="none" pattern="mpeg:decode" />
|
||||
|
||||
Here we do not want users reading images from HTTP:
|
||||
|
||||
<policy domain="coder" rights="none" pattern="HTTP" />
|
||||
|
||||
The /repository file system is restricted to read only. We use a glob
|
||||
expression to match all paths that start with /repository:
|
||||
|
||||
<policy domain="path" rights="read" pattern="/repository/*" />
|
||||
|
||||
Lets prevent users from executing any image filters:
|
||||
|
||||
<policy domain="filter" rights="none" pattern="*" />
|
||||
|
||||
Any large image is cached to disk rather than memory:
|
||||
|
||||
<policy domain="resource" name="area" value="1GP"/>
|
||||
|
||||
Use the default system font unless overwridden by the application:
|
||||
|
||||
<policy domain="system" name="font" value="/usr/share/fonts/favorite.ttf"/>
|
||||
|
||||
Define arguments for the memory, map, area, width, height and disk resources
|
||||
with SI prefixes (.e.g 100MB). In addition, resource policies are maximums
|
||||
for each instance of ImageMagick (e.g. policy memory limit 1GB, -limit 2GB
|
||||
exceeds policy maximum so memory limit is 1GB).
|
||||
|
||||
Rules are processed in order. Here we want to restrict ImageMagick to only
|
||||
read or write a small subset of proven web-safe image types:
|
||||
|
||||
<policy domain="delegate" rights="none" pattern="*" />
|
||||
<policy domain="filter" rights="none" pattern="*" />
|
||||
<policy domain="coder" rights="none" pattern="*" />
|
||||
<policy domain="coder" rights="read|write" pattern="{GIF,JPEG,PNG,WEBP}" />
|
||||
-->
|
||||
<policymap>
|
||||
<!-- <policy domain="resource" name="temporary-path" value="/tmp"/> -->
|
||||
<policy domain="resource" name="memory" value="256MiB"/>
|
||||
<policy domain="resource" name="map" value="512MiB"/>
|
||||
<policy domain="resource" name="width" value="16KP"/>
|
||||
<policy domain="resource" name="height" value="16KP"/>
|
||||
<!-- <policy domain="resource" name="list-length" value="128"/> -->
|
||||
<policy domain="resource" name="area" value="128MP"/>
|
||||
<policy domain="resource" name="disk" value="1GiB"/>
|
||||
<!-- <policy domain="resource" name="file" value="768"/> -->
|
||||
<!-- <policy domain="resource" name="thread" value="4"/> -->
|
||||
<!-- <policy domain="resource" name="throttle" value="0"/> -->
|
||||
<!-- <policy domain="resource" name="time" value="3600"/> -->
|
||||
<!-- <policy domain="coder" rights="none" pattern="MVG" /> -->
|
||||
<!-- <policy domain="module" rights="none" pattern="{PS,PDF,XPS}" /> -->
|
||||
<!-- <policy domain="path" rights="none" pattern="@*" /> -->
|
||||
<!-- <policy domain="cache" name="memory-map" value="anonymous"/> -->
|
||||
<!-- <policy domain="cache" name="synchronize" value="True"/> -->
|
||||
<!-- <policy domain="cache" name="shared-secret" value="passphrase" stealth="true"/>
|
||||
<!-- <policy domain="system" name="max-memory-request" value="256MiB"/> -->
|
||||
<!-- <policy domain="system" name="shred" value="2"/> -->
|
||||
<!-- <policy domain="system" name="precision" value="6"/> -->
|
||||
<!-- <policy domain="system" name="font" value="/path/to/font.ttf"/> -->
|
||||
<!-- <policy domain="system" name="pixel-cache-memory" value="anonymous"/> -->
|
||||
<!-- <policy domain="system" name="shred" value="2"/> -->
|
||||
<!-- <policy domain="system" name="precision" value="6"/> -->
|
||||
<!-- not needed due to the need to use explicitly by mvg: -->
|
||||
<!-- <policy domain="delegate" rights="none" pattern="MVG" /> -->
|
||||
<!-- use curl -->
|
||||
<policy domain="delegate" rights="none" pattern="URL" />
|
||||
<policy domain="delegate" rights="none" pattern="HTTPS" />
|
||||
<policy domain="delegate" rights="none" pattern="HTTP" />
|
||||
<!-- in order to avoid to get image with password text -->
|
||||
<policy domain="path" rights="none" pattern="@*"/>
|
||||
<!-- disable ghostscript format types -->
|
||||
<policy domain="coder" rights="none" pattern="PS" />
|
||||
<policy domain="coder" rights="none" pattern="PS2" />
|
||||
<policy domain="coder" rights="none" pattern="PS3" />
|
||||
<policy domain="coder" rights="none" pattern="EPS" />
|
||||
<policy domain="coder" rights="none" pattern="XPS" />
|
||||
</policymap>
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
URL=https://decpdf.site
|
||||
|
||||
DEBUG=true
|
||||
|
||||
DB_HOST=host
|
||||
DB_USER=user
|
||||
DB_PASS=pass
|
||||
DB_DATABASE=db
|
||||
|
||||
GEMINI_KEY=["3fefyhwp98eoyepoytpotywpertferyrtyueoru", "flcs8yli8ty89weylicu4ynkeuhgkdshei8rhcn"]
|
||||
GEMINI_MODEL=gemini-3.1-flash-lite
|
||||
|
||||
MAGICK=convert
|
||||
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
class AntiSpam {
|
||||
public static function tarpit() {
|
||||
while (true) {
|
||||
print(chr(rand() & 0xff));
|
||||
ob_flush();
|
||||
flush();
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+77
@@ -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"));
|
||||
|
||||
}
|
||||
}
|
||||
Executable
+183
@@ -0,0 +1,183 @@
|
||||
<?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", ["username" => "", "email" => "", "password" => "", "confirm" => ""]);
|
||||
}
|
||||
|
||||
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 (!preg_match("#^[a-zA-Z0-9_\-]+$#", $username)) {
|
||||
flash("error", "Username contains invalid characters. Use letters, numbers, _ and - only.");
|
||||
return blade("register", ["username" => $username, "email" => $email, "password" => $password, "confirm" => $confirm]);
|
||||
}
|
||||
|
||||
if (strlen($username) > 20) {
|
||||
flash("error", "Username is too long. Keep it under 20 characters.");
|
||||
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']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Executable
+193
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
<?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", false, INI_SCANNER_RAW);
|
||||
}
|
||||
if (array_key_exists($val, Config::$config)) {
|
||||
$c = Config::$config[$val];
|
||||
try {
|
||||
$j = json_decode($c, false);
|
||||
if ($j) return $j;
|
||||
} catch (Exception $e) {
|
||||
print_r($e);
|
||||
}
|
||||
return $c;
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
public static function refresh() {
|
||||
Config::$config = parse_ini_file(__DIR__ . "/../.env", false, INI_SCANNER_RAW);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Executable
+141
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+103
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+128
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+44
@@ -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)
|
||||
]);
|
||||
}
|
||||
}
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
<?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 $parameters = [];
|
||||
|
||||
public $attachments = [];
|
||||
|
||||
public function add_parameter($param) {
|
||||
$this->parameters[$param->name] = $param;
|
||||
}
|
||||
|
||||
public function get_parameters() {
|
||||
$o = [];
|
||||
foreach ($this->parameters as $name=>$param) {
|
||||
$o[$name] = $param->get_object();
|
||||
}
|
||||
return $o;
|
||||
}
|
||||
|
||||
public function get_required() {
|
||||
$o = [];
|
||||
foreach ($this->parameters as $name=>$param) {
|
||||
if ($param->required) {
|
||||
$o[] = $name;
|
||||
}
|
||||
}
|
||||
return $o;
|
||||
}
|
||||
|
||||
public static function get_key() {
|
||||
Config::refresh();
|
||||
$keys = Config::get("GEMINI_KEY");
|
||||
if (!is_array($keys)) return $keys;
|
||||
return $keys[rand(0, count($keys)-1)];
|
||||
}
|
||||
|
||||
public function attach($name, File $file) {
|
||||
$o = new stdClass;
|
||||
$o->uri = null;
|
||||
$o->name = $name;
|
||||
$o->file = $file;
|
||||
$this->attachments[$name] = $o;
|
||||
}
|
||||
|
||||
public function upload_file($name, File $file, $key = false) {
|
||||
|
||||
if ($file->size() > 50000000) {
|
||||
throw new Exception("File too large");
|
||||
}
|
||||
|
||||
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': '" . $name . "'}}";
|
||||
|
||||
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() {
|
||||
|
||||
$cb = false;
|
||||
if ($this->_process_callback) $cb = $this->_process_callback;
|
||||
|
||||
if ($cb) $cb("Uploading files");
|
||||
|
||||
if ($this->verbose) {
|
||||
print("<pre>");
|
||||
}
|
||||
|
||||
$key = Gemini::get_key();
|
||||
|
||||
if (count($this->attachments) == 0) {
|
||||
throw new Exception("No files to upload");
|
||||
}
|
||||
|
||||
foreach ($this->attachments as $name=>$file) {
|
||||
$file->uri = $this->upload_file($name, $file->file, $key);
|
||||
}
|
||||
|
||||
$system = [];
|
||||
|
||||
$system[] = "Summarize the attached PDF document.";
|
||||
|
||||
|
||||
$parts = [];
|
||||
$text = new stdClass;
|
||||
$text->text = implode("\n", $system);
|
||||
$parts[] = $text;
|
||||
|
||||
foreach ($this->attachments as $name=>$file) {
|
||||
$f = new stdClass;
|
||||
$f->file_data = new stdClass;
|
||||
$f->file_data->mime_type = $file->file->mime();
|
||||
$f->file_data->file_uri = $file->uri;
|
||||
$parts[] = $f;
|
||||
}
|
||||
|
||||
$ob = [
|
||||
"contents" => [[
|
||||
"parts" => $parts
|
||||
]],
|
||||
"generationConfig" => [
|
||||
"responseMimeType" => "application/json",
|
||||
"responseSchema" => [
|
||||
"type" => "object",
|
||||
"properties" => $this->get_parameters(),
|
||||
"required" => $this->get_required(),
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$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/" . Config::get("GEMINI_MODEL") . ":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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
class GeminiParameter {
|
||||
public $name;
|
||||
public $type;
|
||||
public $description;
|
||||
public $required;
|
||||
public $children;
|
||||
public $enum;
|
||||
|
||||
public function __construct($n, $t, $d, $r = false) {
|
||||
$this->name = $n;
|
||||
$this->type = $t;
|
||||
$this->description = $d;
|
||||
$this->required = $r;
|
||||
$this->children = [];
|
||||
$this->enum = [];
|
||||
}
|
||||
|
||||
public function add_enum($e) {
|
||||
$this->enum[] = $e;
|
||||
}
|
||||
|
||||
public function add_child($n, $t, $d, $r = false) {
|
||||
$p = new GeminiParameter($n, $t, $d, $r);
|
||||
$this->children[$n] = $p;
|
||||
return $p;
|
||||
}
|
||||
|
||||
public function get_object() {
|
||||
$o = new stdClass;
|
||||
$o->type = $this->type;
|
||||
$o->description = $this->description;
|
||||
switch ($this->type) {
|
||||
case "string":
|
||||
if (count($this->enum) > 0) {
|
||||
$o->format = "enum";
|
||||
$o->enum = $this->enum;
|
||||
}
|
||||
break;
|
||||
case "object":
|
||||
$o->properties = [];
|
||||
$o->required = [];
|
||||
foreach ($this->children as $cn=>$c) {
|
||||
$o->properties[$cn] = $c->get_object();
|
||||
if ($c->required) {
|
||||
$o->required[] = $cn;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
return $o;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Executable
+124
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
abstract class Job {
|
||||
private $_jobid = 0;
|
||||
private $_source = "unknown";
|
||||
private $_owner = "system";
|
||||
public $retries = 0;
|
||||
|
||||
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($owner = -1) {
|
||||
$class = get_called_class();
|
||||
$data = serialize($this);
|
||||
$db = DB::getInstance();
|
||||
|
||||
if ($owner == -1) {
|
||||
$user = get_user();
|
||||
if ($user) {
|
||||
$owner = $user->id;
|
||||
}
|
||||
}
|
||||
|
||||
$this->_jobid = $db->insert("job", [
|
||||
"class" => $class,
|
||||
"data" => $data,
|
||||
"queued" => time(),
|
||||
"source" => $this->_source,
|
||||
"status" => "Queued",
|
||||
"owner" => $owner
|
||||
]);
|
||||
|
||||
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" => substr($txt, 0, 200)
|
||||
]);
|
||||
if (is_terminal()) {
|
||||
print(" => " . $txt . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
// 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($message = "") {
|
||||
$db = DB::getInstance();
|
||||
if ($message != "") {
|
||||
$db->update("job", $this->_jobid, [
|
||||
"status" => substr($message, 0, 200),
|
||||
"failed" => time(),
|
||||
]);
|
||||
} else {
|
||||
$db->update("job", $this->_jobid, [
|
||||
"failed" => time(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Restart the job.
|
||||
public function retry($msg = "", $when = -1) {
|
||||
if ($when == -1) {
|
||||
$when = rand(60, 600);
|
||||
}
|
||||
$db = DB::getInstance();
|
||||
$status = "Retrying " . date("Y-m-d H:i", time() + $when);
|
||||
if ($msg != "") {
|
||||
$status .= " ($msg)";
|
||||
}
|
||||
|
||||
$db->update("job", $this->_jobid, [
|
||||
"failed" => 0,
|
||||
"finished" => 0,
|
||||
"started" => 0,
|
||||
"status" => $status,
|
||||
"retry" => time() + $when
|
||||
]);
|
||||
}
|
||||
|
||||
// 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 (retry=0 or retry < unix_timestamp(now())) and queued < unix_timestamp(now()) order by queued limit 1");
|
||||
} else {
|
||||
$q = $db->query("select * from job where started=0 and (retry=0 or retry < unix_timestamp(now())) 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);
|
||||
$ob->setOwner($r->owner);
|
||||
return $ob;
|
||||
}
|
||||
|
||||
public function getOwner() {
|
||||
return $this->_owner;
|
||||
}
|
||||
|
||||
public function setOwner($owner) {
|
||||
$this->_owner = $owner;
|
||||
}
|
||||
}
|
||||
Executable
+501
@@ -0,0 +1,501 @@
|
||||
<?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) {
|
||||
if ($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));
|
||||
}
|
||||
|
||||
public function last_modified() {
|
||||
if ($this->updated > 0) {
|
||||
return date("Y-m-d H:i:s", $this->updated);
|
||||
} else {
|
||||
return date("Y-m-d H:i:s", $this->created);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+165
@@ -0,0 +1,165 @@
|
||||
<?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($prods = null, $verbose=false) {
|
||||
$g = new Gemini();
|
||||
$g->verbose = $verbose;
|
||||
try {
|
||||
return $g->geminiOverview($this, $prods);
|
||||
} 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;
|
||||
}
|
||||
|
||||
public function ocr(File $out) {
|
||||
$proc = new Process("ocrmypdf");
|
||||
$proc->arg($this->path());
|
||||
$proc->arg($out->path());
|
||||
$proc->execute();
|
||||
}
|
||||
|
||||
public function text() {
|
||||
$proc = new Process("pdftotext");
|
||||
# $proc->arg("-layout");
|
||||
$proc->arg("-enc");
|
||||
$proc->arg("ASCII7");
|
||||
$proc->arg($this->path());
|
||||
$proc->arg("-");
|
||||
$proc->execute();
|
||||
|
||||
$text = new Text(implode("\n", $proc->stdout()));
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
Executable
+82
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Executable
+112
@@ -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);
|
||||
}
|
||||
}
|
||||
Executable
+112
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+112
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
class Route {
|
||||
public $method = "GET";
|
||||
public $pattern = "";
|
||||
public $function = null;
|
||||
public $auth = false;
|
||||
public $args = [];
|
||||
public $path = "";
|
||||
|
||||
public static $current = false;
|
||||
|
||||
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) {
|
||||
Route::$current = $this;
|
||||
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"];
|
||||
}
|
||||
|
||||
public static function canonical() {
|
||||
$c = Route::$current;
|
||||
if (!$c->path) {
|
||||
return Config::get("URL");
|
||||
}
|
||||
$bits = parse_url($c->path);
|
||||
return Config::get("URL") . $bits["path"];
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
<?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)) {
|
||||
|
||||
$route->path = $path;
|
||||
return $route;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+71
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
class Text {
|
||||
|
||||
private $text;
|
||||
|
||||
public function __construct($text) {
|
||||
if (!$text) {
|
||||
$text = "";
|
||||
}
|
||||
$this->text = $this->cleanup($text);
|
||||
}
|
||||
|
||||
function cleanup($text) {
|
||||
$text = str_replace("", " ", $text);
|
||||
$text = str_replace("\t", " ", $text);
|
||||
$text = str_replace("\r", " ", $text);
|
||||
$text = str_replace("\n", " ", $text);
|
||||
$text = str_replace("\"", "", $text);
|
||||
$text = str_replace(",", " ", $text);
|
||||
$text = str_replace("–", "-", $text);
|
||||
$text = str_replace("—", "-", $text);
|
||||
$text = str_replace("Ø", "0", $text);
|
||||
$text = str_replace("~", "-", $text);
|
||||
$text = str_replace("--", "-", $text);
|
||||
$text = preg_replace('/\s\s+/', ' ', $text);
|
||||
return trim($text);
|
||||
}
|
||||
|
||||
public function get_text() {
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
public function words() {
|
||||
$text = preg_replace('/([^A-Z\-0-9]+)/', ' ', strtoupper($this->text));
|
||||
$words = preg_split('/\s+/', $text);
|
||||
return $words;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"bootstrap": "^5.3.7",
|
||||
"css-loader": "^7.1.2",
|
||||
"expose-loader": "^5.0.1",
|
||||
"jquery": "^3.7.1",
|
||||
"jquery-simple-websocket": "github:jbloemendal/jquery-simple-websocket",
|
||||
"js-image-zoom": "^0.7.0",
|
||||
"marked": "^16.1.1",
|
||||
"mini-css-extract-plugin": "^2.9.2",
|
||||
"popper.js": "^1.16.1",
|
||||
"style-loader": "^4.0.0",
|
||||
"vue": "^3.5.17",
|
||||
"vue-loader": "^17.4.2",
|
||||
"webpack": "^5.99.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack-cli": "^6.0.1"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
require_once(__DIR__ . "/../app.php");
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 270 KiB |
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"short_name": "DECPDF",
|
||||
"name": "DECPDF",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/d192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/d512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
Routes::add_api("GET", "/api/download/{id}", ["DownloadController", "get_download"], ["Auth", "can_upload"]);
|
||||
Routes::add_api("PUT", "/api/download", ["DownloadController", "start_download"], ["Auth", "can_upload"]);
|
||||
Routes::add_api("GET", "/api/downloads", ["DownloadController", "api_downloads"], ["Auth", "can_upload"]);
|
||||
Routes::add_api("PUT", "/api/downloads", ["DownloadController", "api_add_download"], ["Auth", "can_upload"]);
|
||||
|
||||
Routes::add_api("POST", "/api/document/dragdrop", ["DocumentController", "api_drag_drop"], ["Auth", "can_moderate"]);
|
||||
Routes::add_api("GET", "/api/document/{id}", ["DocumentController", "api_get"]);
|
||||
Routes::add_api("POST", "/api/document/{id}", ["DocumentController", "api_set"]);
|
||||
Routes::add_api("GET", "/api/document/{id}/products", ["DocumentController", "api_get_products"]);
|
||||
|
||||
Routes::add_api("GET", "/api/document/{id}/available_metadata", ["DocumentController", "api_available_metadata"]);
|
||||
Routes::add_api("GET", "/api/document/{id}/metadata", ["DocumentController", "api_get_metadata"]);
|
||||
Routes::add_api("PUT", "/api/document/{id}/metadata", ["DocumentController", "api_new_metadata"], ["Auth", "can_moderate"]);
|
||||
Routes::add_api("POST", "/api/document/{id}/metadata/{metadata}", ["DocumentController", "api_set_metadata"], ["Auth", "can_moderate"]);
|
||||
Routes::add_api("DELETE", "/api/document/{id}/metadata/{metadata}", ["DocumentController", "api_delete_metadata"], ["Auth", "can_moderate"]);
|
||||
|
||||
Routes::add_api("DELETE", "/api/product/trash/{id}", ["ProductController", "api_empty_trash"], ["Auth", "can_moderate"]);
|
||||
Routes::add_api("GET", "/api/product/{list}", ["ProductController", "api_get_list"]);
|
||||
Routes::add_api("PUT", "/api/product/{id}", ["ProductController", "api_add_child"], ["Auth", "can_moderate"]);
|
||||
Routes::add_api("POST", "/api/product/{id}", ["ProductController", "api_set"], ["Auth", "can_moderate"]);
|
||||
routes::add_api("DELETE", "/api/product/{id}", ["ProductController", "api_delete"], ["Auth", "can_moderate"]);
|
||||
|
||||
Routes::add_api("GET", "/api/productmru", ["ProductController", "api_get_mru"]);
|
||||
Routes::add_api("POST", "/api/productmru/{id}", ["ProductController", "api_add_mru"]);
|
||||
Routes::add_api("POST", "/api/search/product", ["ProductController", "api_search"]);
|
||||
|
||||
Routes::add_api("POST", "/api/move/document/{id}", ["DocumentController", "api_move"], ["Auth", "can_moderate"]);
|
||||
Routes::add_api("POST", "/api/move/product/{id}", ["ProductController", "api_move"], ["Auth", "can_moderate"]);
|
||||
Routes::add_api("POST", "/api/merge/document/{id}", ["DocumentController", "api_merge"], ["Auth", "can_moderate"]);
|
||||
|
||||
Routes::add_api("POST", "/api/revision/{id}", ["RevisionController", "api_set"]);
|
||||
|
||||
Routes::add_api("POST", "/api/search/title", ["SearchController", "api_title_search"]);
|
||||
|
||||
Routes::add_api("GET", "/api/spider/pdfs", ["SpiderController", "api_pdfs"], ["Auth", "can_moderate"]);
|
||||
Routes::add_api("DELETE", "/api/spider/pdf/{id}", ["SpiderController", "api_reject_pdf"], ["Auth", "can_moderate"]);
|
||||
Routes::add_api("DELETE", "/api/spider/pdf", ["SpiderController", "api_reject_pdf"], ["Auth", "can_moderate"]);
|
||||
Routes::add_api("PUT", "/api/spider/pdf/{id}", ["SpiderController", "api_accept_pdf"], ["Auth", "can_moderate"]);
|
||||
Routes::add_api("PUT", "/api/spider/pdf", ["SpiderController", "api_accept_pdf"], ["Auth", "can_moderate"]);
|
||||
|
||||
Routes::add_api("GET", "/api/spider/pages", ["SpiderController", "api_pages"], ["Auth", "can_moderate"]);
|
||||
Routes::add_api("DELETE", "/api/spider/page/{id}", ["SpiderController", "api_reject_page"], ["Auth", "can_moderate"]);
|
||||
Routes::add_api("PUT", "/api/spider/page/{id}", ["SpiderController", "api_accept_page"], ["Auth", "can_moderate"]);
|
||||
|
||||
Routes::add_api("GET", "/api/imports", ["ImportController", "api_imports"], ["Auth", "can_upload"]);
|
||||
Routes::add_api("DELETE", "/api/import/{id}", ["ImportController", "api_delete_import"], ["Auth", "can_upload"]);
|
||||
Routes::add_api("POST", "/api/import/{id}", ["ImportController", "api_set_import"], ["Auth", "can_upload"]);
|
||||
Routes::add_api("PUT", "/api/import/{id}", ["ImportController", "api_add_document"], ["Auth", "can_upload"]);
|
||||
|
||||
|
||||
Routes::add_api("GET", "/api/documentbyid/{id}", ["DocumentController", "get_by_id"]);
|
||||
|
||||
Routes::add_api("GET", "/api/sys/idmatch", ["SystemController", "api_get_idmatches"], ["Auth", "can_moderate"]);
|
||||
Routes::add_api("PUT", "/api/sys/idmatch", ["SystemController", "api_add_idmatch"], ["Auth", "can_moderate"]);
|
||||
Routes::add_api("POST", "/api/sys/idmatch/{id}", ["SystemController", "api_set_idmatch"], ["Auth", "can_moderate"]);
|
||||
Routes::add_api("DELETE", "/api/sys/idmatch/{id}", ["SystemController", "api_del_idmatch"], ["Auth", "can_moderate"]);
|
||||
|
||||
Routes::add_api("POST", "/api/search/prefix", ["DocumentController", "api_get_title_fragment"]);
|
||||
|
||||
Routes::add_api("GET", "/api/product/{id}/available_metadata", ["ProductController", "api_available_metadata"]);
|
||||
Routes::add_api("PUT", "/api/product/{id}/metadata", ["ProductController", "api_add_metadata"]);
|
||||
|
||||
|
||||
|
||||
|
||||
Routes::add_api("GET", "/api/jobs/{source}", ["JobController", "api_get_jobs"], ["Auth", "can_moderate"]);
|
||||
Routes::add_api("DELETE", "/api/jobs/{id}", ["JobController", "api_delete_job"], ["Auth", "can_moderate"]);
|
||||
|
||||
Routes::add_api("GET", "/api/docid/{id}", ["DocumentController", "api_guess_docid"], ["Auth", "can_moderate"]);
|
||||
|
||||
|
||||
Routes::add_api("GET", "/api/product/gemini_all/{id}/{move}", ["ProductController", "api_gemini_all"], ["Auth", "can_moderate"]);
|
||||
|
||||
|
||||
Routes::add_api("PUT", "/api/revision/rate/{id}/{rating}", ["RevisionController", "rate_revision"], ["Auth", "logged_in"]);
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
Routes::add_web("GET", "/", ["Homecontroller", "index"]);
|
||||
|
||||
Routes::add_web("GET", "/cover/{id}/{size}/{filename}", ["CoverController", "get_cover"]);
|
||||
Routes::add_web("GET", "/cover/{id}/{filename}", ["CoverController", "get_cover"]);
|
||||
|
||||
|
||||
Routes::add_web("GET", "/documents/{pid}", ["DocumentController", "browse"]);
|
||||
Routes::add_web("GET", "/documents", ["DocumentController", "browse"]);
|
||||
Routes::add_web("GET", "/document/{id}", ["DocumentController", "show"]);
|
||||
Routes::add_web("GET", "/overview/create/{id}/{move}", ["DocumentController", "create_overview"], ["Auth", "can_moderate"]);
|
||||
|
||||
Routes::add_web("GET", "/revision/{id}", ["RevisionController", "show"]);
|
||||
|
||||
Routes::add_web("GET", "/pdf/{id}/{type}/{filename}", ["PDFController", "download"]);
|
||||
|
||||
Routes::add_web("GET", "/page/{id}/{page}/page.jpg", ["PDFController", "get_page"]);
|
||||
|
||||
Auth::routes();
|
||||
|
||||
Routes::add_web("POST", "/search", ["SearchController", "search"]);
|
||||
Routes::add_web("GET", "/search", ["SearchController", "search"]);
|
||||
Routes::add_web("POST", "/search/{page}", ["SearchController", "search"]);
|
||||
Routes::add_web("GET", "/search/{page}", ["SearchController", "search"]);
|
||||
|
||||
Routes::add_web("GET", "/spider_pdfs", ["SpiderController", "spider_pdfs"], ["Auth", "can_moderate"]);
|
||||
Routes::add_web("GET", "/spider_pages", ["SpiderController", "spider_pages"], ["Auth", "can_moderate"]);
|
||||
|
||||
Routes::add_web("GET", "/status", ["SystemController", "status"], ["Auth", "can_moderate"]);
|
||||
|
||||
Routes::add_web("GET", "/downloads", ["ImportController", "downloads"], ["Auth", "can_upload"]);
|
||||
|
||||
Routes::add_web("GET", "/del_docproduct/{doc}/{prod}", ["DocumentController", "del_docproduct"], ["Auth", "can_moderate"]);
|
||||
|
||||
Routes::add_web("GET", "/imports", ["ImportController", "imports"], ["Auth", "can_upload"]);
|
||||
|
||||
Routes::add_web("GET", "/idmatch", function() { return blade("idmatch"); }, ["Auth", "can_moderate"]);
|
||||
|
||||
Routes::add_web("GET", "/merge/{id}", ["DocumentController", "merge"], ["Auth", "can_moderate"]);
|
||||
Routes::add_web("GET", "/explode/{id}", ["DocumentController", "separate"], ["Auth", "can_moderate"]);
|
||||
|
||||
|
||||
Routes::add_web("GET", "/test", function($_request) {
|
||||
print("<pre>");
|
||||
print_r($_request);
|
||||
exit(0);
|
||||
});
|
||||
|
||||
|
||||
Routes::add_web("GET", "/privacy", function() { return blade("privacy"); });
|
||||
|
||||
|
||||
Routes::add_web("GET", "/wordpress/wp-admin/setup-config.php", ["AntiSpam", "tarpit"]);
|
||||
Routes::add_web("GET", "/wp-admin/setup-config.php", ["AntiSpam", "tarpit"]);
|
||||
|
||||
Routes::add_web("HEAD", "/", function() { return "OK"; });
|
||||
|
||||
|
||||
Routes::add_web("GET", "/document/{id}/metadata/{metadata}/delete", ["DocumentController", "delete_metadata"], ["Auth", "can_moderate"]);
|
||||
|
||||
Routes::add_web("GET", "/delete/revision/{id}", ["RevisionController", "delete"], ["Auth", "can_moderate"]);
|
||||
|
||||
Routes::add_web("GET", "/redownload/revision/{id}", ["RevisionController", "redownload"], ["Auth", "can_moderate"]);
|
||||
|
||||
|
||||
Routes::add_web("GET", "/purge/revision/{id}", ["RevisionController", "purge"], ["Auth", "can_moderate"]);
|
||||
|
||||
Routes::add_web("GET", "/attachment/{id}/{filename}", ["DocumentController", "download_attachment"]);
|
||||
|
||||
Routes::add_web("GET", "/upload", ["DocumentController", "upload_document"], ["Auth", "can_upload"]);
|
||||
Routes::add_web("POST", "/upload", ["DocumentController", "do_upload_document"], ["Auth", "can_upload"]);
|
||||
Routes::add_web("GET", "/upload/attachment/{id}", ["DocumentController", "upload_attachment"], ["Auth", "can_upload"]);
|
||||
Routes::add_web("POST", "/upload/attachment/{id}", ["DocumentController", "do_upload_attachment"], ["Auth", "can_upload"]);
|
||||
|
||||
Routes::add_web("GET", "/uploads", ["DocumentController", "uploads"], ["Auth", "can_upload"]);
|
||||
Routes::add_web("GET", "/uploads/import/{id}/{source}", ["DocumentController", "import"], ["Auth", "can_upload"]);
|
||||
Routes::add_web("GET", "/uploads/duplicate/{id}", ["DocumentController", "upload_duplicate"], ["Auth", "can_upload"]);
|
||||
Routes::add_web("GET", "/uploads/rerunall", ["DocumentController", "rerun_all"], ["Auth", "can_upload"]);
|
||||
Routes::add_web("GET", "/uploads/rerun/{id}", ["DocumentController", "rerun_upload"], ["Auth", "can_upload"]);
|
||||
Routes::add_web("GET", "/uploads/discard/{id}", ["DocumentController", "discard_upload"], ["Auth", "can_upload"]);
|
||||
|
||||
Routes::add_web("GET", "/uploads/{page}", ["DocumentController", "uploads_page"], ["Auth", "can_upload"]);
|
||||
|
||||
|
||||
|
||||
Routes::add_web("GET", "/generate/sitemap.xml", ["SitemapController", "generate"]);
|
||||
|
||||
|
||||
Routes::add_web("GET", "/recompress/revision/{id}", ["RevisionController", "recompress"], ["Auth", "can_moderate"]);
|
||||
|
||||
Routes::add_web("GET", "/ocr/revision/{id}", ["RevisionController", "ocr"], ["Auth", "can_moderate"]);
|
||||
|
||||
Routes::add_web("GET", "/uploads/view/{id}", ["DocumentController", "view_upload"], ["Auth", "can_upload"]);
|
||||
|
||||
|
||||
|
||||
Routes::add_web("GET", "/document/unfavourite/{doc}", ["FavouriteController", "user_unset_favourite"], ["Auth", "logged_in"]);
|
||||
Routes::add_web("GET", "/document/favourite/{doc}", ["FavouriteController", "user_set_favourite"], ["Auth", "logged_in"]);
|
||||
Routes::add_web("GET", "/favourites", ["FavouriteController", "favourites"], ["Auth", "logged_in"]);
|
||||
|
||||
|
||||
Routes::add_web("GET", "/systems", ["SystemController", "systems"], ["Auth", "logged_in"]);
|
||||
Routes::add_web("GET", "/system/add", ["SystemController", "systems_add"], ["Auth", "logged_in"]);
|
||||
Routes::add_web("POST", "/system/add", ["SystemController", "systems_do_add"], ["Auth", "logged_in"]);
|
||||
Routes::add_web("GET", "/system/{id}", ["SystemController", "system"], ["Auth", "logged_in"]);
|
||||
Routes::add_web("GET", "/system/{id}/add/{doc}", ["SystemController", "system_add_doc"], ["Auth", "logged_in"]);
|
||||
Routes::add_web("GET", "/system/{id}/del/{doc}", ["SystemController", "system_del_doc"], ["Auth", "logged_in"]);
|
||||
|
||||
Routes::add_web("GET", "/extract/revision/{id}", ["RevisionController", "split_revision"], ["Auth", "can_moderate"]);
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue/dist/vue.esm-bundler';
|
||||
|
||||
export default {
|
||||
props: [
|
||||
],
|
||||
|
||||
setup() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
},
|
||||
|
||||
mounted() {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Executable
+90
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div :draggable="draggable" :class="dropclass" @dragover="drag_over" @dragstart="drag_start" @drop="drop" @dragenter="drag_enter" @dragleave="drag_leave">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue/dist/vue.esm-bundler';
|
||||
|
||||
export default {
|
||||
props: [
|
||||
"draggable",
|
||||
"droppable",
|
||||
"object_id",
|
||||
"object_type",
|
||||
"callback",
|
||||
"extra_data",
|
||||
],
|
||||
|
||||
setup(props, context) {
|
||||
return {
|
||||
dropclass: ref(""),
|
||||
content: ref(""),
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
init() {
|
||||
},
|
||||
|
||||
drag_over(e) {
|
||||
e.preventDefault();
|
||||
},
|
||||
|
||||
drag_start(e) {
|
||||
if (this.draggable == "true") {
|
||||
e.dataTransfer.setData("object_id", this.object_id);
|
||||
e.dataTransfer.setData("object_type", this.object_type);
|
||||
e.dataTransfer.setData("extra_data", this.extra_data);
|
||||
}
|
||||
},
|
||||
|
||||
drop(e) {
|
||||
if (this.droppable == "true") {
|
||||
this.dropclass = "";
|
||||
e.preventDefault();
|
||||
console.log(e.dataTransfer.getData("object_id"));
|
||||
console.log(e.dataTransfer.getData("object_type"));
|
||||
console.log(e.dataTransfer.getData("extra_data"));
|
||||
console.log(this);
|
||||
|
||||
$.ajax({
|
||||
url: this.callback,
|
||||
method: 'POST',
|
||||
data: {
|
||||
dst_id: this.object_id,
|
||||
dst_type: this.object_type,
|
||||
dst_extra: this.extra_data,
|
||||
src_id: e.dataTransfer.getData("object_id"),
|
||||
src_type: e.dataTransfer.getData("object_type"),
|
||||
src_extra: e.dataTransfer.getData("extra_data"),
|
||||
copy: e.ctrlKey,
|
||||
}
|
||||
}).done(this.drop_complete);
|
||||
}
|
||||
},
|
||||
|
||||
drop_complete(data) {
|
||||
//console.log(data);
|
||||
window.location.reload();
|
||||
},
|
||||
|
||||
drag_enter(e) {
|
||||
if (this.droppable == "true") {
|
||||
this.dropclass = "bg-warning-subtle";
|
||||
}
|
||||
},
|
||||
|
||||
drag_leave(e) {
|
||||
if (this.droppable == "true") {
|
||||
this.dropclass = "";
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Executable
+195
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" @click="edit" title="Edit Document"><i class='fa fa-pencil'></i></button>
|
||||
|
||||
<div id="docedit" class="modal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Edit Document</h5>
|
||||
<button @click="close" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div v-if="loading" class="text-center">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-12">
|
||||
<label for="internal_id">Order Number</label>
|
||||
<div class="input-group">
|
||||
<input @keyup.enter="save" @keyup.esc="close" @keyup="id_caps" type="text" class="form-control" name="internal_id" v-model="internal_id">
|
||||
<button @click="guess_docid" class="btn btn-secondary" title="Guess document ID"><i class="fa fa-wand-magic-sparkles"></i></button>
|
||||
</div>
|
||||
<label for="title">Title</label>
|
||||
<div class="input-group">
|
||||
<input ref="rtitle" @keyup.enter="save" @keyup.esc="close" type="text" class="form-control" name="title" v-model="title">
|
||||
<button @click="split_title" class="btn btn-secondary" title="Split down on cursor or selection"><i class="fa fa-i-cursor"></i></button>
|
||||
</div>
|
||||
<label for="subtitle">Subtitle</label>
|
||||
<div class="input-group">
|
||||
<input ref="rsubtitle" @keyup.enter="save" @keyup.esc="close" type="text" class="form-control" name="subtitle" v-model="subtitle">
|
||||
<button @click="split_subtitle" class="btn btn-secondary" title="Split down on cursor or selection"><i class="fa fa-i-cursor"></i></button>
|
||||
</div>
|
||||
<label for="subsubtitle">Sub-subtitle</label>
|
||||
<input @keyup.enter="save" @keyup.esc="close" type="text" class="form-control" name="subsubtitle" v-model="subsubtitle">
|
||||
</div>
|
||||
<div class="col-lg-6 col-12">
|
||||
<label for="overview">Overview <small>(Markdown supported)</small></label>
|
||||
<textarea @keyup.esc="close" name="overview" v-model="overview" class="form-control w-100" rows="5"></textarea>
|
||||
<label for="products">Products</label>
|
||||
<productselector v-model="products"></productselector>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button @click="close" type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button @click="save" type="button" class="btn btn-primary">Save changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue/dist/vue.esm-bundler';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
return {
|
||||
loading: ref(true),
|
||||
internal_id: ref(""),
|
||||
title: ref(""),
|
||||
subtitle: ref(""),
|
||||
subsubtitle: ref(""),
|
||||
overview: ref(""),
|
||||
products: ref(""),
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
},
|
||||
|
||||
methods: {
|
||||
edit() {
|
||||
this.loading = true;
|
||||
$('#docedit').show();
|
||||
$.ajax({
|
||||
url: "/api/document/" + this.docid,
|
||||
method: "GET"
|
||||
}).done(this.get_products);
|
||||
},
|
||||
|
||||
get_products(data) {
|
||||
this.internal_id = data.internal_id;
|
||||
this.title = data.title;
|
||||
this.subtitle = data.subtitle;
|
||||
this.subsubtitle = data.subsubtitle;
|
||||
this.overview = data.overview;
|
||||
$.ajax({
|
||||
url: "/api/document/" + this.docid + "/products",
|
||||
method: "GET"
|
||||
}).done(this.fill_products);
|
||||
},
|
||||
|
||||
fill_products(data) {
|
||||
this.products = "";
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
if (this.products != "") {
|
||||
this.products += ",";
|
||||
}
|
||||
this.products += data[i].id;
|
||||
}
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
close() {
|
||||
$('#docedit').hide();
|
||||
},
|
||||
|
||||
save() {
|
||||
$.ajax({
|
||||
url: "/api/document/" + this.docid,
|
||||
method: "POST",
|
||||
data: {
|
||||
internal_id: this.internal_id,
|
||||
title: this.title,
|
||||
subtitle: this.subtitle,
|
||||
subsubtitle: this.subsubtitle,
|
||||
overview: this.overview,
|
||||
products: this.products,
|
||||
}
|
||||
}).done(this.refresh);
|
||||
},
|
||||
|
||||
refresh() {
|
||||
window.location.reload();
|
||||
},
|
||||
|
||||
id_caps() {
|
||||
this.internal_id = this.internal_id.toUpperCase();
|
||||
this.internal_id = this.internal_id.replace("–", "-");
|
||||
},
|
||||
|
||||
get_caret_position(oField) {
|
||||
// Initialize
|
||||
var iCaretPos = 0;
|
||||
|
||||
if (document.selection) {
|
||||
|
||||
// Set focus on the element
|
||||
oField.focus();
|
||||
|
||||
// To get cursor position, get empty selection range
|
||||
var oSel = document.selection.createRange();
|
||||
|
||||
// Move selection start to 0 position
|
||||
oSel.moveStart('character', -oField.value.length);
|
||||
|
||||
// The caret position is selection length
|
||||
iCaretPos = oSel.text.length;
|
||||
} else if (oField.selectionStart || oField.selectionStart == '0') {
|
||||
iCaretPos = oField.selectionDirection=='backward' ? oField.selectionStart : oField.selectionEnd;
|
||||
}
|
||||
return iCaretPos;
|
||||
},
|
||||
|
||||
split_title() {
|
||||
var pos = this.get_caret_position(this.$refs.rtitle);
|
||||
var l = this.title.substring(0, pos);
|
||||
var r = this.title.substring(pos);
|
||||
this.title = l;
|
||||
this.subtitle = r;
|
||||
console.log(pos);
|
||||
},
|
||||
|
||||
split_subtitle() {
|
||||
var pos = this.get_caret_position(this.$refs.rsubtitle);
|
||||
var l = this.subtitle.substring(0, pos);
|
||||
var r = this.subtitle.substring(pos);
|
||||
this.subtitle = l;
|
||||
this.subsubtitle = r;
|
||||
console.log(pos);
|
||||
},
|
||||
|
||||
guess_docid() {
|
||||
$.ajax({
|
||||
url: "/api/docid/" + this.docid,
|
||||
method: "GET"
|
||||
}).done(this.guessed_docid);
|
||||
},
|
||||
|
||||
guessed_docid(d) {
|
||||
this.internal_id = d.docid;
|
||||
},
|
||||
},
|
||||
|
||||
props: [
|
||||
"docid",
|
||||
],
|
||||
}
|
||||
</script>
|
||||
Executable
+127
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="progress {{pstyle}}" role="progressbar" aria-label="Download progress" style="--bs-progress-bar-transition: 0.01s;">
|
||||
<div ref="pg" :class="pbstyle" :style="{'width': percent + '%'}">{{status}}</div>
|
||||
</div>
|
||||
<div class="text-truncate">
|
||||
<small>{{srcurl}}</small>
|
||||
</div>
|
||||
<div v-if="failed" class="alert alert-danger">
|
||||
The download has failed with error code <strong>{{errorcode}}</strong>. Please try again.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue/dist/vue.esm-bundler'
|
||||
|
||||
export default {
|
||||
emits: [
|
||||
'complete',
|
||||
'failure'
|
||||
],
|
||||
props: [
|
||||
'jobid',
|
||||
'url'
|
||||
],
|
||||
setup() {
|
||||
return {
|
||||
percent: ref(100),
|
||||
pbstyle: ref(""),
|
||||
pstyle: ref(""),
|
||||
outfile: ref(""),
|
||||
srcurl: ref(""),
|
||||
failed: ref(false),
|
||||
errorcode: ref(""),
|
||||
status: ref(""),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
get_status() {
|
||||
$.ajax({
|
||||
url: "/api/download/" + this.jobid,
|
||||
method: "get",
|
||||
}) .fail(this.get_status_fail)
|
||||
.done(this.got_status);
|
||||
},
|
||||
get_status_fail(data, status, xhr) {
|
||||
this.errorcode = xhr;
|
||||
this.failed = true;
|
||||
},
|
||||
|
||||
got_status(data, status, xhr) {
|
||||
this.update_bar(data);
|
||||
if (data.done != data.size) {
|
||||
setTimeout(this.get_status, 500);
|
||||
} else {
|
||||
if (data.size == 0) {
|
||||
setTimeout(this.get_status, 2000);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
submit(url) {
|
||||
$.ajax({
|
||||
url: "/api/download",
|
||||
method: "put",
|
||||
data: {
|
||||
url: this.url
|
||||
}
|
||||
}) .fail(this.submit_fail)
|
||||
.done(this.submitted);
|
||||
},
|
||||
|
||||
submit_fail(data, status, xhr) {
|
||||
this.errorcode = xhr;
|
||||
this.failed = true;
|
||||
this.$emit("failed");
|
||||
},
|
||||
|
||||
submitted(data, status, xhr) {
|
||||
this.update_bar(data);
|
||||
setTimeout(this.get_status, 500);
|
||||
},
|
||||
|
||||
update_bar(data) {
|
||||
this.outfile = data.file;
|
||||
this.srcurl = data.url;
|
||||
|
||||
if (data.started == 0) {
|
||||
this.status = "Queued";
|
||||
this.pbstyle = "progress-bar bg-secondary progress-bar-striped progress-bar-animated";
|
||||
this.percent = 100;
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.finished != 0) {
|
||||
this.status = "Completed";
|
||||
this.pbstyle = "progress-bar bg-success";
|
||||
this.percent = 100;
|
||||
this.$emit("complete");
|
||||
let event = new CustomEvent("complete", { bubbles: true, detail: data });
|
||||
document.dispatchEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.size == 0) {
|
||||
this.status = "Starting...";
|
||||
} else {
|
||||
this.percent = data.done / data.size * 100;
|
||||
this.pbstyle = "progress-bar bg-primary";
|
||||
this.status = Math.round(this.percent) + "%";
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.status = "Please wait...";
|
||||
this.pbstyle = "progress-bar bg-secondary progress-bar-striped progress-bar-animated";
|
||||
this.percent = 100;
|
||||
|
||||
if (this.jobid != "") {
|
||||
this.get_status();
|
||||
} else if (this.url != "") {
|
||||
this.submit(url);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">Download PDF URL:</span>
|
||||
<input type="text" class="form-control" v-model="url">
|
||||
<button class="btn btn-primary" @click="add_download">Download</button>
|
||||
</div>
|
||||
|
||||
<ul v-for="download in downloads" class="list-group">
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col-12 text-truncate">
|
||||
{{ download.url }}
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<download :jobid="download.id"></download>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue/dist/vue.esm-bundler';
|
||||
|
||||
export default {
|
||||
props: [
|
||||
],
|
||||
|
||||
setup() {
|
||||
return {
|
||||
downloads: ref([]),
|
||||
busy: ref(false),
|
||||
url: ref(""),
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
init() {
|
||||
this.update();
|
||||
},
|
||||
|
||||
update() {
|
||||
this.busy = true;
|
||||
$.ajax({
|
||||
url: "/api/downloads",
|
||||
method: "GET"
|
||||
}).done(this.got_update);
|
||||
},
|
||||
|
||||
add_download() {
|
||||
this.busy = true;
|
||||
$.ajax({
|
||||
url: "/api/downloads",
|
||||
method: "PUT",
|
||||
data: {
|
||||
url: this.url
|
||||
}
|
||||
}).done(this.got_update);
|
||||
},
|
||||
|
||||
got_update(data) {
|
||||
this.downloads = data;
|
||||
this.busy = false;
|
||||
|
||||
setTimeout(this.update, 10000);
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Executable
+249
@@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">Test String:</span>
|
||||
<input @keyup="refresh" class="form-control" type="text" v-model="teststring">
|
||||
<span class="input-group-text"><i class="fa fa-arrow-left mx-1"></i> Type here to test matches</span>
|
||||
<button class="btn btn-success" @click="addnew">Add New Match</button>
|
||||
</div>
|
||||
<table class="table table-hover w-100">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Weight</th>
|
||||
<th>Regular Expression</th>
|
||||
<th>Example</th>
|
||||
<th>Test Result</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="match in matches" :class="matchtest(match.regex)" @click="edit_reg(match)">
|
||||
<td>{{ match.weight }}</td>
|
||||
<td>/^{{ match.regex }}$/</td>
|
||||
<td>{{ match.example }}</td>
|
||||
<td>{{ teststring != "" ? matchresult(match.regex, teststring) : matchresult(match.regex, match.example)}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<div id="newreg" class="modal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Add New ID Match</h5>
|
||||
<button @click="addclose" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" :disabled="saving"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<label for="example">Example ID</label>
|
||||
<input type="text" name="example" class="form-control" v-model="add_example" @keyup="do_add_test">
|
||||
|
||||
<label for="regex">Regular Expression</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">/^</span>
|
||||
<input type="text" name="regex" class="form-control" v-model="add_regex" @keyup="do_add_test">
|
||||
<span class="input-group-text">$/</span>
|
||||
</div>
|
||||
|
||||
<label for="weight">Weight (lower is higher priority)</label>
|
||||
<input type="text" name="weight" class="form-control" v-model="add_weight">
|
||||
|
||||
Test Result: {{ add_test }}
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button @click="addclose" type="button" class="btn btn-secondary" data-bs-dismiss="modal" :disabled="saving">Close</button>
|
||||
<button @click="addsave" type="button" class="btn btn-primary" :disabled="saving">
|
||||
<span v-if="!saving">Save changes</span>
|
||||
<span class="spinner-border spinner-border-sm" role="status" v-if="saving"></span>
|
||||
<span v-if="saving" class="mx-1">Saving...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="editreg" class="modal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Edit ID Match</h5>
|
||||
<button @click="edit_close" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" :disabled="saving"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<label for="example">Example ID</label>
|
||||
<input type="text" name="example" class="form-control" v-model="edit_example" @keyup="do_edit_test">
|
||||
|
||||
<label for="regex">Regular Expression</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">/^</span>
|
||||
<input type="text" name="regex" class="form-control" v-model="edit_regex" @keyup="do_edit_test">
|
||||
<span class="input-group-text">$/</span>
|
||||
</div>
|
||||
|
||||
<label for="weight">Weight (lower is higher priority)</label>
|
||||
<input type="text" name="weight" class="form-control" v-model="edit_weight">
|
||||
|
||||
Test Result: {{ edit_test }}
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button @click="edit_delete" type="button" class="btn btn-danger" :disabled="saving">Delete</button>
|
||||
<button @click="edit_close" type="button" class="btn btn-secondary" data-bs-dismiss="modal" :disabled="saving">Close</button>
|
||||
<button @click="edit_save" type="button" class="btn btn-primary" :disabled="saving">
|
||||
<span v-if="!saving">Save changes</span>
|
||||
<span class="spinner-border spinner-border-sm" role="status" v-if="saving"></span>
|
||||
<span v-if="saving" class="mx-1">Saving...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue/dist/vue.esm-bundler';
|
||||
|
||||
export default {
|
||||
props: [
|
||||
],
|
||||
|
||||
setup() {
|
||||
return {
|
||||
matches: ref([]),
|
||||
teststring: ref(""),
|
||||
saving: ref(false),
|
||||
add_example: ref(""),
|
||||
add_regex: ref(""),
|
||||
add_weight: ref(0),
|
||||
add_test: ref(""),
|
||||
edit_example: ref(""),
|
||||
edit_regex: ref(""),
|
||||
edit_weight: ref(0),
|
||||
edit_test: ref(""),
|
||||
edit_id: ref(0),
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
init() {
|
||||
this.update();
|
||||
},
|
||||
|
||||
refresh() {
|
||||
var m = this.matches;
|
||||
this.matches = [];
|
||||
this.matches = m;
|
||||
},
|
||||
|
||||
update() {
|
||||
$.ajax({
|
||||
url: "/api/sys/idmatch",
|
||||
method: "GET"
|
||||
}).done(this.got_update);
|
||||
},
|
||||
|
||||
got_update(data) {
|
||||
this.matches = data;
|
||||
},
|
||||
|
||||
matchresult(regex, example) {
|
||||
try {
|
||||
const re = new RegExp("^" + regex + "$");
|
||||
var res = example.match(re);
|
||||
if (res == null) {
|
||||
return "FAILED";
|
||||
}
|
||||
return res[1];
|
||||
} catch (E) {
|
||||
return "Bad Syntax";
|
||||
}
|
||||
},
|
||||
|
||||
matchtest(regex) {
|
||||
var teststring = this.teststring.trim();
|
||||
if (teststring == "") return "";
|
||||
if (this.matchresult(regex, teststring) != "FAILED") {
|
||||
return "table-success";
|
||||
}
|
||||
return "table-danger";
|
||||
},
|
||||
|
||||
addnew() {
|
||||
this.add_example = "";
|
||||
this.add_regex = "";
|
||||
this.add_weight = 0;
|
||||
$('#newreg').show();
|
||||
},
|
||||
|
||||
|
||||
do_add_test() {
|
||||
this.add_test = this.matchresult(this.add_regex, this.add_example);
|
||||
},
|
||||
|
||||
addclose() {
|
||||
$('#newreg').hide();
|
||||
},
|
||||
|
||||
addsave() {
|
||||
$.ajax({
|
||||
url: "/api/sys/idmatch",
|
||||
method: "PUT",
|
||||
data: {
|
||||
example: this.add_example,
|
||||
regex: this.add_regex,
|
||||
weight: this.add_weight
|
||||
}
|
||||
}).done(this.got_update);
|
||||
$('#newreg').hide();
|
||||
},
|
||||
|
||||
|
||||
edit_reg(m) {
|
||||
this.edit_id = m.id;
|
||||
this.edit_example = m.example;
|
||||
this.edit_regex = m.regex;
|
||||
this.edit_weight = m.weight;
|
||||
$('#editreg').show();
|
||||
},
|
||||
|
||||
edit_close() {
|
||||
$('#editreg').hide();
|
||||
},
|
||||
|
||||
do_edit_test() {
|
||||
this.edit_test = this.matchresult(this.edit_regex, this.edit_example);
|
||||
},
|
||||
|
||||
edit_save() {
|
||||
$.ajax({
|
||||
url: "/api/sys/idmatch/" + this.edit_id,
|
||||
method: "POST",
|
||||
data: {
|
||||
example: this.edit_example,
|
||||
regex: this.edit_regex,
|
||||
weight: this.edit_weight
|
||||
}
|
||||
}).done(this.got_update);
|
||||
$('#editreg').hide();
|
||||
},
|
||||
|
||||
edit_delete() {
|
||||
$.ajax({
|
||||
url: "/api/sys/idmatch/" + this.edit_id,
|
||||
method: "DELETE",
|
||||
}).done(this.got_update);
|
||||
$('#editreg').hide();
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Executable
+550
@@ -0,0 +1,550 @@
|
||||
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ul class="list-group">
|
||||
<li v-for="i in imports" :class="fancyrow(i)">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<a v-if="i.revision != null" :href='"/cover/" + i.revision.id + "/cover.jpg"'><img class="w-100" :src='"/cover/" + i.revision.id + "/100/cover.jpg"'></a>
|
||||
</div>
|
||||
<div class="col-11">
|
||||
<div class="row">
|
||||
<div class="col-lg-2 col-6">
|
||||
<strong v-if="i.imported > 0">Imported</strong>
|
||||
<strong v-else-if="i.completed > 0 && i.revision">Completed</strong>
|
||||
<strong v-else-if="i.completed > 0 && (!i.revision)">Failed</strong>
|
||||
<strong v-else-if="i.started > 0">Processing</strong>
|
||||
<strong v-else>Queued</strong>
|
||||
</div>
|
||||
<div class="col-lg-2 col-6">
|
||||
<a v-if="i.revision != null" :href='"/revision/" + i.revision.id'>{{ i.revision.id }}</a>
|
||||
</div>
|
||||
<div class="col-lg-2 col-3">
|
||||
{{ i.guessed_id }}
|
||||
</div>
|
||||
<div class="col-lg-2 col-3">
|
||||
{{ i.guessed_revno }}
|
||||
</div>
|
||||
<div class="col-lg-2 col-6">
|
||||
{{ i.guessed_month }}/{{ i.guessed_year }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-9 text-truncate">
|
||||
{{ i.guessed_title }}
|
||||
</div>
|
||||
<div class="col-lg-3 col-12 btn-group">
|
||||
<button :disabled="busy" class="btn btn-danger" @click="del(i)"><i class="fa-solid fa-trash"></i></button>
|
||||
<button :disabled="busy || (i.revision == null)" class="btn btn-secondary" @click="download(i)"><i class="fa-solid fa-download"></i></button>
|
||||
<button :disabled="(i.started == 0) || busy" class="btn btn-primary" @click="restart(i)"><i class="fa-solid fa-rotate"></i></button>
|
||||
<button class="btn btn-success" :disabled="(i.completed == 0 || (!i.revision)) || busy" @click="import_rev(i)"><i class="fa-solid fa-file-import"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
{{ i.origfile }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div id="docedit" class="modal" tabindex="-1">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Create Document</h5>
|
||||
<button @click="close" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" :disabled="saving"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-12 alert alert-info">
|
||||
{{ i_origfile }}
|
||||
</div>
|
||||
</div>
|
||||
<pdf :rev="i_revision"></pdf>
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-12">
|
||||
<label for="i_id">Order Number</label>
|
||||
<div class="input-group">
|
||||
<input @blur="upper_id" @keyup.enter="save" ref="orderno" @keyup.esc="close" type="text" @keyup="find_doc" :class="'form-control' + ((i_id.length <= 25) ? ' is-valid' : ' is-invalid')" name="i_id" v-model="i_id" :disabled="saving">
|
||||
<button class="btn btn-secondary" @click="paste_id"><i class="fa-solid fa-paste"></i></button>
|
||||
</div>
|
||||
|
||||
<div v-if="newdoc" class="alert alert-warning">
|
||||
A new document will be created with this order number.
|
||||
</div>
|
||||
|
||||
<div v-else class="alert alert-success">
|
||||
This PDF will be added to {{ existing_doc.title }} {{ existing_doc.subtitle }} {{ existing_doc.subsubtitle }} as a new revision.
|
||||
</div>
|
||||
|
||||
<div v-if="newdoc">
|
||||
<label for="i_title">Title</label>
|
||||
<div class="input-group">
|
||||
<input ref="rtitle" @keyup.enter="save" @keyup.esc="close" @keyup="try_title" type="text" :class="'form-control' + (i_title == '' ? ' is-invalid' : ' is-valid')" name="i_title" v-model="i_title" :disabled="saving">
|
||||
<button class="btn btn-secondary" @click="split_title" title="Split down on cursor position"><i class='fa fa-i-cursor'></i></button>
|
||||
</div>
|
||||
<label for="i_subtitle">Subtitle</label>
|
||||
<div class="input-group">
|
||||
<input ref="rsubtitle" @keyup.enter="save" @keyup.esc="close" @keyup="try_subtitle" type="text" class="form-control" name="i_subtitle" v-model="i_subtitle" :disabled="saving">
|
||||
<button class="btn btn-secondary" @click="split_subtitle" title="Split down on cursor position"><i class='fa fa-i-cursor'></i></button>
|
||||
</div>
|
||||
<label for="i_subsubtitle">Sub-subtitle</label>
|
||||
<input @keyup.enter="save" @keyup.esc="close" @keyup="try_subsubtitle" type="text" class="form-control" name="i_subsubtitle" v-model="i_subsubtitle" :disabled="saving">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-lg-6 col-12">
|
||||
|
||||
<label for="i_revno">Revision / Version Number</label>
|
||||
<input @keyup.enter="save" @keyup.esc="close" type="text" class="form-control" name="i_revno" v-model="i_revno" :disabled="saving">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<label for="i_month">Month</label>
|
||||
<input @keyup.enter="save" @keyup.esc="close" type="text" class="form-control" name="i_month" v-model="i_month" :disabled="saving">
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<label for="i_year">Year</label>
|
||||
<input @keyup.enter="save" @keyup.esc="close" type="text" class="form-control" name="i_year" v-model="i_year" :disabled="saving">
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="newdoc">
|
||||
<label for="i_overview">Overview <small>(Markdown supported)</small></label>
|
||||
<textarea @keyup.esc="close" name="i_overview" v-model="i_overview" class="form-control w-100" rows="5" :disabled="saving"></textarea>
|
||||
<label for="i_products">Products</label>
|
||||
<productselector id="ps" v-model="i_products"></productselector>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<textarea editable="false" v-model="i_covertext" class="form-control"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button @click="close" type="button" class="btn btn-secondary" data-bs-dismiss="modal" :disabled="saving">Close</button>
|
||||
<button @click="save" type="button" class="btn btn-primary" :disabled="saving || (newdoc && ((i_products == '') || (i_title == '')))">
|
||||
<span v-if="!saving">Save changes</span>
|
||||
<span class="spinner-border spinner-border-sm" role="status" v-if="saving"></span>
|
||||
<span v-if="saving" class="mx-1">Saving...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue/dist/vue.esm-bundler';
|
||||
|
||||
export default {
|
||||
props: [
|
||||
],
|
||||
|
||||
setup() {
|
||||
return {
|
||||
imports: ref([]),
|
||||
busy: ref(false),
|
||||
i_products: ref(""),
|
||||
i_id: ref(""),
|
||||
i_title: ref(""),
|
||||
i_subtitle: ref(""),
|
||||
i_subsubtitle: ref(""),
|
||||
i_revno: ref(""),
|
||||
i_month: ref(0),
|
||||
i_year: ref(0),
|
||||
i_overview: ref(""),
|
||||
i_cover: ref(""),
|
||||
i_fullcover: ref(""),
|
||||
i_origfile: ref(""),
|
||||
i_covertext: ref(""),
|
||||
i_revision: ref(0),
|
||||
|
||||
triglock: ref(false),
|
||||
|
||||
saving: ref(false),
|
||||
|
||||
importing: ref({
|
||||
id: 0
|
||||
}),
|
||||
|
||||
newdoc: ref(true),
|
||||
existing_doc: ref({
|
||||
title: '',
|
||||
subtitle: '',
|
||||
subsubtitle: '',
|
||||
}),
|
||||
timer: ref(null),
|
||||
zoom: ref(null),
|
||||
|
||||
fsel_start: ref(0),
|
||||
fsel_end: ref(0),
|
||||
fsel_el: ref(null),
|
||||
keytimer : ref(false),
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
init() {
|
||||
this.update();
|
||||
var that = this;
|
||||
},
|
||||
|
||||
focus() {
|
||||
this.$refs.rtitle.focus();
|
||||
this.zoom = new ImageZoom(document.getElementById("cover"), {
|
||||
fillContainer: true,
|
||||
offset: { vertical: 0, horizontal: 10 },
|
||||
zoomStyle: "z-index: 10;",
|
||||
});
|
||||
},
|
||||
|
||||
update() {
|
||||
this.timer = false;
|
||||
$.ajax({
|
||||
url: "/api/imports",
|
||||
method: "GET"
|
||||
}).done(this.got_update);
|
||||
},
|
||||
|
||||
got_update(data) {
|
||||
this.imports = data;
|
||||
this.busy = false;
|
||||
|
||||
var r = 0;
|
||||
|
||||
for (var i = 0; i < this.imports.length; i++) {
|
||||
if (this.imports[i].completed == 0) {
|
||||
r = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (r) {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer=null;
|
||||
}
|
||||
this.timer = setTimeout(this.update, 1000);
|
||||
}
|
||||
},
|
||||
|
||||
del(rev) {
|
||||
this.busy = true;
|
||||
$.ajax({
|
||||
url: "/api/import/" + rev.id,
|
||||
method: "DELETE"
|
||||
}).done(this.got_update);
|
||||
},
|
||||
|
||||
restart(rev) {
|
||||
this.busy = true;
|
||||
$.ajax({
|
||||
url: "/api/import/" + rev.id,
|
||||
method: "POST",
|
||||
data: {
|
||||
started: 0,
|
||||
completed: 0,
|
||||
imported: 0,
|
||||
}
|
||||
}).done(this.got_update);
|
||||
},
|
||||
|
||||
import_rev(rev) {
|
||||
this.importing = rev;
|
||||
this.i_id = rev.guessed_id;
|
||||
this.i_title = rev.guessed_title;
|
||||
this.i_revno = rev.guessed_revno;
|
||||
this.i_subtitle = "";
|
||||
this.i_subsubtitle = "";
|
||||
this.i_month = rev.guessed_month;
|
||||
this.i_year = rev.guessed_year;
|
||||
this.i_overview = rev.overview;
|
||||
this.i_products = "";
|
||||
this.i_cover = "/cover/" + rev.revision.id + "/cover.jpg";
|
||||
this.i_fullcover = "/cover/" + rev.revision.id + "/cover.jpg";
|
||||
this.i_origfile = rev.origfile;
|
||||
this.i_covertext = rev.covertext;
|
||||
this.i_revision = rev.revision.id;
|
||||
this.find_doc();
|
||||
|
||||
$(document).on('hide.bs.modal', '#docedit', function(e) {alert("doc"); });
|
||||
$('#docedit').on('hide.bs.modal', function(e) {alert("modal"); });
|
||||
|
||||
$('#docedit').show();
|
||||
setTimeout(this.focus, 10);
|
||||
},
|
||||
|
||||
test(e) {
|
||||
console.log(e);
|
||||
},
|
||||
|
||||
close() {
|
||||
$('#docedit').hide();
|
||||
},
|
||||
|
||||
save() {
|
||||
|
||||
if (this.busy) return;
|
||||
if (this.saving) return;
|
||||
if (this.newdoc && (this.i_products == '')) return;
|
||||
if (this.newdoc && (this.i_title == '')) return;
|
||||
|
||||
this.busy = true;
|
||||
this.saving = true;
|
||||
$.ajax({
|
||||
url: "/api/import/" + this.importing.id ,
|
||||
method: "PUT",
|
||||
data: {
|
||||
internal_id: this.i_id,
|
||||
title: this.i_title,
|
||||
subtitle: this.i_subtitle,
|
||||
subsubtitle: this.i_subsubtitle,
|
||||
month: this.i_month,
|
||||
year: this.i_year,
|
||||
overview: this.i_overview,
|
||||
products: this.i_products,
|
||||
revno: this.i_revno,
|
||||
}
|
||||
}).done(this.saved);
|
||||
},
|
||||
|
||||
saved(data) {
|
||||
this.got_update(data);
|
||||
this.saving = false;
|
||||
$('#docedit').hide();
|
||||
delete this.zoom;
|
||||
},
|
||||
|
||||
find_doc() {
|
||||
|
||||
var i_id = this.i_id.toUpperCase();
|
||||
|
||||
$.ajax({
|
||||
url: "/api/documentbyid/" + i_id,
|
||||
method: "GET"
|
||||
}).done(this.got_doc);
|
||||
},
|
||||
|
||||
got_doc(data) {
|
||||
if (data == false) {
|
||||
this.newdoc = true;
|
||||
} else {
|
||||
this.newdoc = false;
|
||||
this.existing_doc = data;
|
||||
}
|
||||
},
|
||||
|
||||
fancyrow(i) {
|
||||
if (i.completed > 0) {
|
||||
if (!i.revision) {
|
||||
return "list-group-item list-group-item-danger";
|
||||
} else {
|
||||
return "list-group-item list-group-item-success";
|
||||
}
|
||||
}
|
||||
if (i.started > 0) return "list-group-item list-group-item-warning";
|
||||
if (i.queued > 0) return "list-group-item list-group-item-primary";
|
||||
return "list-group-item";
|
||||
},
|
||||
|
||||
get_caret_position(oField) {
|
||||
// Initialize
|
||||
var iCaretPos = 0;
|
||||
|
||||
if (document.selection) {
|
||||
|
||||
// Set focus on the element
|
||||
oField.focus();
|
||||
|
||||
// To get cursor position, get empty selection range
|
||||
var oSel = document.selection.createRange();
|
||||
|
||||
// Move selection start to 0 position
|
||||
oSel.moveStart('character', -oField.value.length);
|
||||
|
||||
// The caret position is selection length
|
||||
iCaretPos = oSel.text.length;
|
||||
} else if (oField.selectionStart || oField.selectionStart == '0') {
|
||||
iCaretPos = oField.selectionDirection=='backward' ? oField.selectionStart : oField.selectionEnd;
|
||||
}
|
||||
return iCaretPos;
|
||||
},
|
||||
|
||||
select_to_end(oField, from) {
|
||||
if (document.selection) {
|
||||
oField.focus();
|
||||
var oSel = document.selection.createRange();
|
||||
oSel.moveStart('character', from);
|
||||
oSel.moveEnd('character', oField.value.length);
|
||||
} else {
|
||||
oField.setSelectionRange(from, oField.value.length);
|
||||
}
|
||||
},
|
||||
|
||||
split_title() {
|
||||
var pos = this.get_caret_position(this.$refs.rtitle);
|
||||
var l = this.i_title.substring(0, pos);
|
||||
var r = this.i_title.substring(pos);
|
||||
this.i_title = l;
|
||||
this.i_subtitle = r;
|
||||
},
|
||||
|
||||
split_subtitle() {
|
||||
var pos = this.get_caret_position(this.$refs.rsubtitle);
|
||||
var l = this.i_subtitle.substring(0, pos);
|
||||
var r = this.i_subtitle.substring(pos);
|
||||
this.i_subtitle = l;
|
||||
this.i_subsubtitle = r;
|
||||
},
|
||||
|
||||
upper_id() {
|
||||
this.i_id = this.i_id.toUpperCase();
|
||||
},
|
||||
|
||||
paste_id() {
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.readText().then(this.do_set_id);
|
||||
}
|
||||
},
|
||||
|
||||
do_set_id(data) {
|
||||
this.i_id = data;
|
||||
this.find_doc();
|
||||
},
|
||||
|
||||
download(data) {
|
||||
document.location="/pdf/" + data.revision.id + "/download/doc.pdf";
|
||||
},
|
||||
|
||||
try_title(e) {
|
||||
if (e.key == "Shift") return;
|
||||
if (e.key == "ArrowLeft") return;
|
||||
if (e.key == "ArrowRight") return;
|
||||
if (e.key == "ArrowUp") return;
|
||||
if (e.key == "ArrowDown") return;
|
||||
if (e.key == "Backspace") return;
|
||||
if (this.triglock) return;
|
||||
|
||||
if (this.keytimer) {
|
||||
clearTimeout(this.keytimer);
|
||||
}
|
||||
this.keytimer = setTimeout(this.try_title2, 250);
|
||||
},
|
||||
|
||||
try_title2() {
|
||||
this.keytimer = false
|
||||
$.ajax({
|
||||
url: "/api/search/prefix",
|
||||
method: "POST",
|
||||
data: {
|
||||
title: this.i_title
|
||||
}
|
||||
}).done(this.fill_title);
|
||||
},
|
||||
|
||||
fill_title(data, status) {
|
||||
if (status == "success") {
|
||||
this.triglock = true;
|
||||
this.fsel_start = this.get_caret_position(this.$refs.rtitle);
|
||||
this.fsel_el = this.$refs.rtitle;
|
||||
this.i_title = data;
|
||||
this.triglock = false;
|
||||
setTimeout(this.fsel, 10);
|
||||
}
|
||||
},
|
||||
|
||||
try_subtitle(e) {
|
||||
if (e.key == "Shift") return;
|
||||
if (e.key == "ArrowLeft") return;
|
||||
if (e.key == "ArrowRight") return;
|
||||
if (e.key == "ArrowUp") return;
|
||||
if (e.key == "ArrowDown") return;
|
||||
if (e.key == "Backspace") return;
|
||||
if (this.triglock) return;
|
||||
|
||||
if (this.keytimer) {
|
||||
clearTimeout(this.keytimer);
|
||||
}
|
||||
this.keytimer = setTimeout(this.try_subtitle2, 250);
|
||||
},
|
||||
|
||||
try_subtitle2() {
|
||||
this.keytimer = false
|
||||
$.ajax({
|
||||
url: "/api/search/prefix",
|
||||
method: "POST",
|
||||
data: {
|
||||
title: this.i_subtitle
|
||||
}
|
||||
}).done(this.fill_subtitle);
|
||||
},
|
||||
|
||||
fill_subtitle(data, status) {
|
||||
if (status == "success") {
|
||||
this.triglock = true;
|
||||
this.fsel_start = this.get_caret_position(this.$refs.rsubtitle);
|
||||
this.fsel_el = this.$refs.rsubtitle;
|
||||
this.i_subtitle = data;
|
||||
this.triglock = false;
|
||||
setTimeout(this.fsel, 10);
|
||||
}
|
||||
},
|
||||
|
||||
try_subsubtitle(e) {
|
||||
if (e.key == "Shift") return;
|
||||
if (e.key == "ArrowLeft") return;
|
||||
if (e.key == "ArrowRight") return;
|
||||
if (e.key == "ArrowUp") return;
|
||||
if (e.key == "ArrowDown") return;
|
||||
if (e.key == "Backspace") return;
|
||||
if (this.triglock) return;
|
||||
|
||||
if (this.keytimer) {
|
||||
clearTimeout(this.keytimer);
|
||||
}
|
||||
this.keytimer = setTimeout(this.try_subsubtitle2, 250);
|
||||
},
|
||||
|
||||
try_subsubtitle2() {
|
||||
this.keytimer = false
|
||||
$.ajax({
|
||||
url: "/api/search/prefix",
|
||||
method: "POST",
|
||||
data: {
|
||||
title: this.i_subsubtitle
|
||||
}
|
||||
}).done(this.fill_subsubtitle);
|
||||
},
|
||||
|
||||
fill_subsubtitle(data, status) {
|
||||
if (status == "success") {
|
||||
this.triglock = true;
|
||||
this.fsel_start = this.get_caret_position(this.$refs.rsubsubtitle);
|
||||
this.fsel_el = this.$refs.rsubsubtitle;
|
||||
this.i_subsubtitle = data;
|
||||
this.triglock = false;
|
||||
setTimeout(this.fsel, 10);
|
||||
}
|
||||
},
|
||||
|
||||
fsel() {
|
||||
this.select_to_end(this.fsel_el, this.fsel_start);
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Executable
+85
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="editing" class="input-group">
|
||||
|
||||
<input
|
||||
ref="edval"
|
||||
class="form-control"
|
||||
type="text"
|
||||
v-model="text"
|
||||
@keyup.enter="submit_edit"
|
||||
@keyup.esc="cancel_edit"
|
||||
>
|
||||
|
||||
<button @click="cancel_edit" class="btn btn-danger px-1 py-0">
|
||||
<i class="fa fa-close"></i>
|
||||
</button>
|
||||
<button @click="submit_edit" class="btn btn-success px-1 py-0">
|
||||
<i class="fa fa-check"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-else class="d-flex justify-content-between align-middle">
|
||||
<span class="align-middle my-auto">{{ text }}</span>
|
||||
<button @click="enable_edit" class="btn btn-secondary px-1 py-0">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue/dist/vue.esm-bundler';
|
||||
|
||||
|
||||
export default {
|
||||
props: [
|
||||
"value",
|
||||
"url",
|
||||
"field",
|
||||
],
|
||||
|
||||
setup() {
|
||||
return {
|
||||
editing: ref(false),
|
||||
text: ref(""),
|
||||
saved: ref(""),
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
enable_edit() {
|
||||
this.editing = true;
|
||||
this.saved = this.text;
|
||||
var that = this;
|
||||
setTimeout(function() { $(that.$refs.edval).focus() }, 100);
|
||||
},
|
||||
|
||||
cancel_edit() {
|
||||
this.text = this.saved;
|
||||
this.editing = false;
|
||||
},
|
||||
|
||||
submit_edit() {
|
||||
|
||||
var data = {};
|
||||
data[this.field] = this.text;
|
||||
|
||||
console.log(data);
|
||||
|
||||
$.ajax({
|
||||
url: this.url,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
this.editing = false;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.text = this.value;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Executable
+114
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="editing">
|
||||
|
||||
<textarea
|
||||
ref="edval"
|
||||
class="form-control w-100"
|
||||
rows="5"
|
||||
type="text"
|
||||
v-model="text"
|
||||
@keyup.esc="cancel_edit"
|
||||
></textarea>
|
||||
|
||||
<button @click="cancel_edit" class="btn btn-danger">
|
||||
<i class="fa fa-close"></i>
|
||||
</button>
|
||||
<button @click="submit_edit" class="btn btn-success">
|
||||
<i class="fa fa-check"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<div class="align-middle my-auto" v-html="md()"></div>
|
||||
<button @click="enable_edit" class="btn btn-secondary">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div ref="slotData" class="visually-hidden">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useSlots, ref } from 'vue/dist/vue.esm-bundler';
|
||||
import { marked } from 'marked';
|
||||
|
||||
|
||||
export default {
|
||||
props: [
|
||||
"url",
|
||||
"field",
|
||||
],
|
||||
|
||||
setup() {
|
||||
return {
|
||||
editing: ref(false),
|
||||
text: ref(""),
|
||||
saved: ref(""),
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
init() {
|
||||
this.load();
|
||||
},
|
||||
|
||||
enable_edit() {
|
||||
this.editing = true;
|
||||
this.saved = this.text;
|
||||
var that = this;
|
||||
setTimeout(function() { $(that.$refs.edval).focus() }, 100);
|
||||
},
|
||||
|
||||
cancel_edit() {
|
||||
this.text = this.saved;
|
||||
this.editing = false;
|
||||
},
|
||||
|
||||
submit_edit() {
|
||||
|
||||
var data = {};
|
||||
data[this.field] = this.text;
|
||||
|
||||
$.ajax({
|
||||
url: this.url,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
this.editing = false;
|
||||
},
|
||||
|
||||
load() {
|
||||
$.ajax({
|
||||
url: this.url,
|
||||
method: "GET"
|
||||
}).done(this.loaded);
|
||||
},
|
||||
|
||||
loaded(data) {
|
||||
this.text = data[0][this.field];
|
||||
},
|
||||
|
||||
md() {
|
||||
var text = this.text;
|
||||
if (text == null) {
|
||||
text = "Loading...";
|
||||
}
|
||||
var txt = marked.parse(text);
|
||||
return txt;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
// var div = document.createElement("div");
|
||||
// div.innerHTML = this.$slots.default()[0].children;
|
||||
// this.text = div.textContent;
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="editing" class="input-group">
|
||||
|
||||
<select class="form-control" v-model="sel">
|
||||
<option v-for='i in items' :value='i.key'>{{ i.value }}</option>
|
||||
</select>
|
||||
|
||||
<button @click="cancel_edit" class="btn btn-danger">
|
||||
<i class="fa fa-close"></i>
|
||||
</button>
|
||||
<button @click="submit_edit" class="btn btn-success">
|
||||
<i class="fa fa-check"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<button @click="enable_edit" class="btn btn-secondary">
|
||||
<i class="fa fa-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue/dist/vue.esm-bundler';
|
||||
|
||||
|
||||
export default {
|
||||
props: [
|
||||
"list",
|
||||
"target",
|
||||
"method",
|
||||
],
|
||||
|
||||
setup() {
|
||||
return {
|
||||
editing: ref(false),
|
||||
sel: ref(""),
|
||||
items: ref([]),
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
enable_edit() {
|
||||
$.ajax({
|
||||
url: this.list,
|
||||
method: "GET"
|
||||
}).done(this.start_edit);
|
||||
},
|
||||
|
||||
start_edit(d) {
|
||||
this.items = d;
|
||||
this.editing = true;
|
||||
var that = this;
|
||||
},
|
||||
|
||||
cancel_edit() {
|
||||
this.text = this.saved;
|
||||
this.editing = false;
|
||||
},
|
||||
|
||||
submit_edit() {
|
||||
|
||||
var data = {};
|
||||
data["item_id"] = this.sel;
|
||||
|
||||
$.ajax({
|
||||
url: this.target,
|
||||
method: this.method,
|
||||
data: data
|
||||
}).done(this.fin);
|
||||
this.editing = false;
|
||||
},
|
||||
|
||||
fin() {
|
||||
location.reload();
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.text = this.value;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div>
|
||||
<ul class="list-group">
|
||||
<li v-for="job in jobs" :class="'d-flex justify-content-between list-group-item ' + get_group_color(job)">
|
||||
<span>
|
||||
{{ job.id }} - {{ job.class }} - {{ job.status }}
|
||||
</span>
|
||||
<small v-if='(job.finished + job.failed) > 0'><button @click='delete_job(job)' class='px-1 py-0 btn btn-danger'><i class='fa fa-close'></i></button></small>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue/dist/vue.esm-bundler';
|
||||
|
||||
export default {
|
||||
props: [
|
||||
"source"
|
||||
],
|
||||
|
||||
setup() {
|
||||
return {
|
||||
jobs: ref([]),
|
||||
speed: ref(1000),
|
||||
timeout: ref(null),
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
init() {
|
||||
this.update();
|
||||
},
|
||||
|
||||
update() {
|
||||
$.ajax({
|
||||
url: "/api/jobs/" + this.source,
|
||||
method: "GET"
|
||||
}).done(this.updated);
|
||||
},
|
||||
|
||||
updated(d) {
|
||||
this.refresh(d);
|
||||
setTimeout(this.update, this.speed);
|
||||
},
|
||||
|
||||
refresh(d) {
|
||||
this.jobs = d;
|
||||
|
||||
this.speed = 10000;
|
||||
for (var i = 0; i < this.jobs.length; i++) {
|
||||
if ((this.jobs[i].finished == 0) && (this.jobs[i].failed == 0)) {
|
||||
this.speed = 1000;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get_group_color(j) {
|
||||
if (j.failed > 0) {
|
||||
return "list-group-item-danger";
|
||||
}
|
||||
if (j.finished > 0) {
|
||||
return "list-group-item-success";
|
||||
}
|
||||
if (j.started > 0) {
|
||||
return "list-group-item-warning";
|
||||
}
|
||||
return "list-group-item-info";
|
||||
},
|
||||
|
||||
delete_job(j) {
|
||||
$.ajax({
|
||||
url: "/api/jobs/" + j.id,
|
||||
method: "DELETE"
|
||||
}).done(this.refresh);
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div style="display: none;">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue/dist/vue.esm-bundler';
|
||||
|
||||
|
||||
export default {
|
||||
props: [
|
||||
"keyname",
|
||||
"target",
|
||||
],
|
||||
|
||||
setup() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handler(e) {
|
||||
console.log(e.key);
|
||||
if (e.key == this.keyname) {
|
||||
document.location=this.target;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
window.addEventListener('keypress', this.handler);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Executable
+89
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row m-3 bg-white">
|
||||
<div class="col-6 m-0 p-0">
|
||||
<img @click="nav_left" :class="leftclass()" :src="left">
|
||||
</div>
|
||||
<div class="col-6 m-0 p-0">
|
||||
<img @click="nav_right" :class="rightclass()" :src="right">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue/dist/vue.esm-bundler';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
rev: ref(""),
|
||||
pages: ref(0)
|
||||
},
|
||||
|
||||
setup(props, emit) {
|
||||
return {
|
||||
left: ref(""),
|
||||
right: ref(""),
|
||||
leftpage : ref(0),
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
init() {
|
||||
this.leftpage = 0;
|
||||
this.update();
|
||||
this.$watch('rev', this.show);
|
||||
},
|
||||
|
||||
update() {
|
||||
this.left = "/page/" + this.rev + "/" + this.leftpage + "/page.jpg";
|
||||
this.right = "/page/" + this.rev + "/" + (this.leftpage + 1) + "/page.jpg";
|
||||
},
|
||||
|
||||
leftclass() {
|
||||
if (this.leftpage > 0) {
|
||||
return 'page-left w-100 m-0 p-0';
|
||||
} else {
|
||||
return 'page-end w-100 m-0 p-0';
|
||||
}
|
||||
},
|
||||
|
||||
rightclass() {
|
||||
if ((this.leftpage + 1) >= this.pages) {
|
||||
return "invisible w-100 m-0 p-0";
|
||||
}
|
||||
if (this.leftpage < this.pages - 2) {
|
||||
return 'page-right w-100 m-0 p-0';
|
||||
} else {
|
||||
return 'page-end w-100 m-0 p-0';
|
||||
}
|
||||
},
|
||||
|
||||
show() {
|
||||
this.left = '';
|
||||
this.right = '';
|
||||
this.leftpage = 0;
|
||||
this.update();
|
||||
},
|
||||
|
||||
nav_right() {
|
||||
if (this.leftpage < this.pages - 2) {
|
||||
this.leftpage += 2;
|
||||
}
|
||||
this.update();
|
||||
},
|
||||
|
||||
nav_left() {
|
||||
this.leftpage -= 2;
|
||||
if (this.leftpage < 0) {
|
||||
this.leftpage = 0;
|
||||
}
|
||||
this.update();
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Executable
+123
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div>
|
||||
<ul class="list-group">
|
||||
<li v-for="pdf in pdfs" class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col-lg-11 col-12">
|
||||
<div class="row">
|
||||
<div class="col-12 text-center">
|
||||
{{ pdf.title }}
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<a class="btn w-100 text-start" :href="pdf.url" target="_blank">{{ pdf.url }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group col-lg-1 col-12 text-center">
|
||||
<button :disabled="busy" @click="reject(pdf)" class="btn btn-danger"><i class="fa fa-close"></i></button>
|
||||
<button :disabled="busy" @click="accept(pdf)" class="btn btn-success"><i class="fa fa-check"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col-lg-11 col-12 text-center">
|
||||
<a class="btn w-100" target="_blank">Entire List</a>
|
||||
</div>
|
||||
<div class="btn-group col-lg-1 col-12">
|
||||
<button :disabled="busy" @click="reject_all" class="btn btn-danger"><i class="fa fa-close"></i></button>
|
||||
<button :disabled="busy" @click="accept_all" class="btn btn-success"><i class="fa fa-check"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue/dist/vue.esm-bundler';
|
||||
|
||||
export default {
|
||||
props: [
|
||||
],
|
||||
|
||||
setup() {
|
||||
return {
|
||||
pdfs: ref([]),
|
||||
busy: ref(false),
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
init() {
|
||||
this.update();
|
||||
},
|
||||
|
||||
update() {
|
||||
this.busy = true;
|
||||
$.ajax({
|
||||
url: "/api/spider/pdfs",
|
||||
method: "GET"
|
||||
}).done(this.got_update);
|
||||
},
|
||||
|
||||
got_update(data) {
|
||||
this.pdfs = data;
|
||||
this.busy = false;
|
||||
},
|
||||
|
||||
accept(pdf) {
|
||||
if (this.busy) return;
|
||||
this.busy = true;
|
||||
$.ajax({
|
||||
url: '/api/spider/pdf/' + pdf.id,
|
||||
method: "PUT"
|
||||
}).done(this.got_update);
|
||||
},
|
||||
|
||||
reject(pdf) {
|
||||
if (this.busy) return;
|
||||
this.busy = true;
|
||||
$.ajax({
|
||||
url: '/api/spider/pdf/' + pdf.id,
|
||||
method: "DELETE"
|
||||
}).done(this.got_update);
|
||||
},
|
||||
|
||||
accept_all() {
|
||||
if (this.busy) return;
|
||||
this.busy = true;
|
||||
|
||||
var l = [];
|
||||
for (var i = 0; i < this.pdfs.length; i++) {
|
||||
l.push(this.pdfs[i].id);
|
||||
}
|
||||
$.ajax({
|
||||
url: '/api/spider/pdf/' + l.join(","),
|
||||
method: "PUT"
|
||||
}).done(this.got_update);
|
||||
},
|
||||
|
||||
reject_all() {
|
||||
if (this.busy) return;
|
||||
this.busy = true;
|
||||
|
||||
var l = [];
|
||||
for (var i = 0; i < this.pdfs.length; i++) {
|
||||
l.push(this.pdfs[i].id);
|
||||
}
|
||||
$.ajax({
|
||||
url: '/api/spider/pdf/' + l.join(","),
|
||||
method: "DELETE"
|
||||
}).done(this.got_update);
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Executable
+188
@@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<nav class="d-flex justify-content-end">
|
||||
<itemadder :list="'/api/product/' + pid + '/available_metadata'" :target="'/api/product/' + pid + '/metadata'" method="PUT"></itemadder>
|
||||
<div ref="ddcreate_child" class="dropdown ms-1">
|
||||
<button class="btn btn-success dropdown-toggle" title="Add" data-bs-toggle="dropdown"><i class="fa fa-plus-circle"></i></button>
|
||||
<ul class="dropdown-menu" style="min-width: 300px;">
|
||||
<li class="p-2">
|
||||
<strong>Create new child product</strong>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">Name</span>
|
||||
<input @keyup.enter="create_child" ref="increate_child" class="form-control" name="title" v-model="newtitle">
|
||||
<button class="btn btn-primary" @click="create_child">Create</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div ref="ddrename" class="dropdown ms-1">
|
||||
<button class="btn btn-success dropdown-toggle" title="Rename" data-bs-toggle="dropdown" @click="set_edittitle"><i class="fa fa-pencil"></i></button>
|
||||
<ul class="dropdown-menu" style="min-width: 300px;">
|
||||
<li class="p-2">
|
||||
<strong>Rename product</strong>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">Name</span>
|
||||
<input ref="inrename" class="form-control" name="title" v-model="edittitle">
|
||||
<button class="btn btn-primary" @click="rename">Rename</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-success ms-1" @click="gemini_all"><i class="fa-solid fa-wand-magic"></i></button>
|
||||
<button class="btn btn-success ms-1" @click="gemini_all_move"><i class="fa-solid fa-wand-magic-sparkles"></i></button>
|
||||
|
||||
<button :class="'btn ms-1 ' + dp_class" @click="delete_product"><i class="fa fa-trash"></i></button>
|
||||
|
||||
<button @click="empty_trash" class="ms-1 btn btn-danger" v-if="prod.title=='Trash'"><i class="fa fa-recycle"></i></button>
|
||||
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue/dist/vue.esm-bundler';
|
||||
|
||||
export default {
|
||||
props: [
|
||||
"pid",
|
||||
],
|
||||
setup() {
|
||||
return {
|
||||
newtitle: ref(""),
|
||||
edittitle: ref(""),
|
||||
dp_class: ref("btn-danger"),
|
||||
dp_clicks: ref(0),
|
||||
prod: ref({}),
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
init() {
|
||||
$(this.$refs.ddcreate_child).on("show.bs.dropdown", this.add_child_show);
|
||||
$(this.$refs.ddcreate_child).on("shown.bs.dropdown", this.add_child_shown);
|
||||
|
||||
$(this.$refs.ddrename).on("show.bs.dropdown", this.rename_show);
|
||||
$(this.$refs.ddrename).on("shown.bs.dropdown", this.rename_shown);
|
||||
this.get_product();
|
||||
},
|
||||
|
||||
get_product() {
|
||||
$.ajax({
|
||||
url: "/api/product/" + this.pid,
|
||||
method: "GET"
|
||||
}).done(this.got_product);
|
||||
},
|
||||
|
||||
add_child_show(e) {
|
||||
this.newtitle = "";
|
||||
},
|
||||
|
||||
add_child_shown(e) {
|
||||
$(this.$refs.increate_child).focus();
|
||||
},
|
||||
|
||||
rename_show(e) {
|
||||
this.edittitle = this.prod.title;
|
||||
},
|
||||
|
||||
set_edittitle() {
|
||||
this.edittitle = this.prod.title;
|
||||
},
|
||||
|
||||
got_product(data) {
|
||||
this.prod = data[0];
|
||||
},
|
||||
|
||||
rename_shown(e) {
|
||||
},
|
||||
|
||||
create_child() {
|
||||
console.log("Adding child");
|
||||
$.ajax({
|
||||
url: "/api/product/" + this.pid,
|
||||
method: "PUT",
|
||||
data: {
|
||||
title: this.newtitle
|
||||
}
|
||||
}).done(this.created_child);
|
||||
},
|
||||
|
||||
rename() {
|
||||
$.ajax({
|
||||
url: "/api/product/" + this.pid,
|
||||
method: "POST",
|
||||
data: {
|
||||
title: this.edittitle
|
||||
}
|
||||
}).done(this.renamed);
|
||||
},
|
||||
|
||||
renamed() {
|
||||
window.location.reload();
|
||||
},
|
||||
|
||||
created_child(data) {
|
||||
window.location.reload();
|
||||
},
|
||||
|
||||
delete_product() {
|
||||
this.dp_clicks++;
|
||||
console.log("clicked " + this.dp_clicks);
|
||||
if (this.dp_clicks >= 2) {
|
||||
$.ajax({
|
||||
url: "/api/product/" + this.pid,
|
||||
method: "DELETE",
|
||||
}).done(this.done_delete);
|
||||
return;
|
||||
}
|
||||
this.dp_class = "btn-warning";
|
||||
setTimeout(this.undo_delete, 1000);
|
||||
},
|
||||
|
||||
undo_delete() {
|
||||
console.log("Undo delete");
|
||||
this.dp_class = "btn-danger";
|
||||
this.dp_clicks = 0;
|
||||
},
|
||||
|
||||
|
||||
done_delete(data) {
|
||||
document.location="/documents/" + data.id;
|
||||
},
|
||||
|
||||
empty_trash() {
|
||||
$.ajax({
|
||||
url: "/api/product/trash/" + this.prod.id,
|
||||
method: "DELETE"
|
||||
}).done(this.trash_emptied);
|
||||
},
|
||||
|
||||
trash_emptied() {
|
||||
window.location.reload();
|
||||
},
|
||||
|
||||
gemini_all() {
|
||||
$.ajax({
|
||||
url: "/api/product/gemini_all/" + this.prod.id + "/keep",
|
||||
method: "GET"
|
||||
}).done(this.gemini_all_queued);
|
||||
},
|
||||
|
||||
gemini_all_move() {
|
||||
$.ajax({
|
||||
url: "/api/product/gemini_all/" + this.prod.id + "/move",
|
||||
method: "GET"
|
||||
}).done(this.gemini_all_queued);
|
||||
},
|
||||
|
||||
gemini_all_queued(d) {
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user