Router Update + Bugfix
This commit is contained in:
		
							parent
							
								
									658157167e
								
							
						
					
					
						commit
						1fb875fb2c
					
				@ -12,3 +12,8 @@ RewriteOptions AllowNoSlash
 | 
			
		||||
RewriteRule ^((\.idea|\.git|src|test|core|docker|files)(/.*)?)$ /index.php?site=$1 [L,QSA]
 | 
			
		||||
 | 
			
		||||
FallbackResource /index.php
 | 
			
		||||
 | 
			
		||||
ErrorDocument 400 /index.php?error=400
 | 
			
		||||
ErrorDocument 403 /index.php?error=403
 | 
			
		||||
ErrorDocument 404 /index.php?error=404
 | 
			
		||||
ErrorDocument 500 /index.php?error=500
 | 
			
		||||
@ -1,4 +1,6 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="PhpProjectSharedConfiguration" php_language_level="7.4" />
 | 
			
		||||
  <component name="PhpProjectSharedConfiguration" php_language_level="8.0">
 | 
			
		||||
    <option name="suggestChangeDefaultLanguageLevel" value="false" />
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
@ -3,11 +3,6 @@ import {Link} from "react-router-dom";
 | 
			
		||||
import Alert from "../elements/alert";
 | 
			
		||||
import {Collapse} from "react-collapse/lib/Collapse";
 | 
			
		||||
import Icon from "../elements/icon";
 | 
			
		||||
import { EditorState, ContentState, convertToRaw } from 'draft-js'
 | 
			
		||||
import { Editor } from 'react-draft-wysiwyg'
 | 
			
		||||
import draftToHtml from 'draftjs-to-html';
 | 
			
		||||
import htmlToDraft from 'html-to-draftjs';
 | 
			
		||||
import sanitizeHtml from 'sanitize-html'
 | 
			
		||||
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
 | 
			
		||||
import ReactTooltip from "react-tooltip";
 | 
			
		||||
 | 
			
		||||
@ -36,15 +31,6 @@ export default class Settings extends React.Component {
 | 
			
		||||
                unsavedMailSettings: false,
 | 
			
		||||
                keys: ["mail_enabled", "mail_host", "mail_port", "mail_username", "mail_password", "mail_from"]
 | 
			
		||||
            },
 | 
			
		||||
            messages: {
 | 
			
		||||
                alerts: [],
 | 
			
		||||
                isOpen: true,
 | 
			
		||||
                isSaving: false,
 | 
			
		||||
                isResetting: false,
 | 
			
		||||
                editor: EditorState.createEmpty(),
 | 
			
		||||
                isEditing: null,
 | 
			
		||||
                keys: ["message_confirm_email", "message_accept_invite", "message_reset_password"]
 | 
			
		||||
            },
 | 
			
		||||
            recaptcha: {
 | 
			
		||||
                alerts: [],
 | 
			
		||||
                isOpen: true,
 | 
			
		||||
@ -77,7 +63,6 @@ export default class Settings extends React.Component {
 | 
			
		||||
        key = key.trim();
 | 
			
		||||
        return this.state.general.keys.includes(key)
 | 
			
		||||
            || this.state.mail.keys.includes(key)
 | 
			
		||||
            || this.state.messages.keys.includes(key)
 | 
			
		||||
            || this.state.recaptcha.keys.includes(key)
 | 
			
		||||
            || this.hiddenKeys.includes(key);
 | 
			
		||||
    }
 | 
			
		||||
@ -345,6 +330,7 @@ export default class Settings extends React.Component {
 | 
			
		||||
                        {this.state.mail.isSending ?
 | 
			
		||||
                            <span>Sending <Icon icon={"circle-notch"}/></span> : "Send Mail"}
 | 
			
		||||
                    </button>
 | 
			
		||||
 | 
			
		||||
                    <div className={"col-10"}>
 | 
			
		||||
                        {this.state.mail.unsavedMailSettings ?
 | 
			
		||||
                            <span className={"text-red"}>You need to save your mail settings first.</span> : null}
 | 
			
		||||
@ -354,63 +340,6 @@ export default class Settings extends React.Component {
 | 
			
		||||
        </>
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getMessagesForm() {
 | 
			
		||||
 | 
			
		||||
        const editor = <Editor
 | 
			
		||||
            editorState={this.state.messages.editor}
 | 
			
		||||
            onEditorStateChange={this.onEditorStateChange.bind(this)}
 | 
			
		||||
        />;
 | 
			
		||||
 | 
			
		||||
        let messageTemplates = {
 | 
			
		||||
            "message_confirm_email": "Confirm E-Mail Message",
 | 
			
		||||
            "message_accept_invite": "Accept Invitation Message",
 | 
			
		||||
            "message_reset_password": "Reset Password Message",
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let formGroups = [];
 | 
			
		||||
        for (let key in messageTemplates) {
 | 
			
		||||
            let title = messageTemplates[key];
 | 
			
		||||
            if (this.state.messages.isEditing === key) {
 | 
			
		||||
                formGroups.push(
 | 
			
		||||
                    <div className={"form-group"} key={"group-" + key}>
 | 
			
		||||
                        <label htmlFor={key}>
 | 
			
		||||
                            { title }
 | 
			
		||||
                            <ReactTooltip id={"tooltip-" + key} />
 | 
			
		||||
                            <Icon icon={"times"} className={"ml-2 text-danger"} style={{cursor: "pointer"}}
 | 
			
		||||
                                  onClick={() => this.closeEditor(false)} data-type={"error"}
 | 
			
		||||
                                  data-tip={"Discard Changes"} data-place={"top"} data-effect={"solid"}
 | 
			
		||||
                                  data-for={"tooltip-" + key}
 | 
			
		||||
                            />
 | 
			
		||||
                            <Icon icon={"check"} className={"ml-2 text-success"} style={{cursor: "pointer"}}
 | 
			
		||||
                                  onClick={() => this.closeEditor(true)} data-type={"success"}
 | 
			
		||||
                                  data-tip={"Save Changes"} data-place={"top"} data-effect={"solid"}
 | 
			
		||||
                                  data-for={"tooltip-" + key}
 | 
			
		||||
                            />
 | 
			
		||||
                        </label>
 | 
			
		||||
                        { editor }
 | 
			
		||||
                    </div>
 | 
			
		||||
                );
 | 
			
		||||
            } else {
 | 
			
		||||
                formGroups.push(
 | 
			
		||||
                    <div className={"form-group"} key={"group-" + key}>
 | 
			
		||||
                        <ReactTooltip id={"tooltip-" + key} />
 | 
			
		||||
                        <label htmlFor={key}>
 | 
			
		||||
                            { title }
 | 
			
		||||
                            <Icon icon={"pencil-alt"} className={"ml-2"} style={{cursor: "pointer"}}
 | 
			
		||||
                                  onClick={() => this.openEditor(key)} data-type={"info"}
 | 
			
		||||
                                  data-tip={"Edit Template"} data-place={"top"} data-effect={"solid"}
 | 
			
		||||
                                  data-for={"tooltip-" + key}
 | 
			
		||||
                            />
 | 
			
		||||
                        </label>
 | 
			
		||||
                        <div className={"p-2 text-black"} style={{backgroundColor: "#d2d6de"}} dangerouslySetInnerHTML={{ __html: sanitizeHtml(this.state.settings[key] ?? "") }} />
 | 
			
		||||
                    </div>
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return formGroups;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getRecaptchaForm() {
 | 
			
		||||
        return <>
 | 
			
		||||
            <div className={"form-group mt-2"}>
 | 
			
		||||
@ -520,7 +449,6 @@ export default class Settings extends React.Component {
 | 
			
		||||
        const categories = {
 | 
			
		||||
            "general": {color: "primary", icon: "cogs", title: "General Settings", content: this.createGeneralForm()},
 | 
			
		||||
            "mail": {color: "warning", icon: "envelope", title: "Mail Settings", content: this.createMailForm()},
 | 
			
		||||
            "messages": {color: "info", icon: "copy", title: "Message Templates", content: this.getMessagesForm()},
 | 
			
		||||
            "recaptcha": {color: "danger", icon: "google", title: "Google reCaptcha", content: this.getRecaptchaForm()},
 | 
			
		||||
            "uncategorised": {color: "secondary", icon: "stream", title: "Uncategorised", content: this.getUncategorizedForm()},
 | 
			
		||||
        };
 | 
			
		||||
@ -557,16 +485,6 @@ export default class Settings extends React.Component {
 | 
			
		||||
        </>
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    onEditorStateChange(editorState) {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            ...this.state,
 | 
			
		||||
            messages: {
 | 
			
		||||
                ...this.state.messages,
 | 
			
		||||
                editor: editorState
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    onChangeValue(event) {
 | 
			
		||||
        const target = event.target;
 | 
			
		||||
        const name = target.name;
 | 
			
		||||
@ -620,8 +538,6 @@ export default class Settings extends React.Component {
 | 
			
		||||
 | 
			
		||||
                    if (category === "mail") {
 | 
			
		||||
                        categoryUpdated.unsavedMailSettings = false;
 | 
			
		||||
                    } else if (category === "messages") {
 | 
			
		||||
                        categoryUpdated.isEditing = null;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -635,10 +551,6 @@ export default class Settings extends React.Component {
 | 
			
		||||
    onSave(category) {
 | 
			
		||||
        this.setState({...this.state, [category]: {...this.state[category], isSaving: true}});
 | 
			
		||||
 | 
			
		||||
        if (category === "messages" && this.state.messages.isEditing) {
 | 
			
		||||
            this.closeEditor(true, () => this.onSave(category));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let values = {};
 | 
			
		||||
        if (category === "uncategorised") {
 | 
			
		||||
            for (let prop of this.state.uncategorised.settings) {
 | 
			
		||||
@ -705,38 +617,4 @@ export default class Settings extends React.Component {
 | 
			
		||||
            this.setState({...this.state, mail: newState});
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    closeEditor(save, callback = null) {
 | 
			
		||||
        if (this.state.messages.isEditing) {
 | 
			
		||||
            const key = this.state.messages.isEditing;
 | 
			
		||||
            let newState = { ...this.state, messages: {...this.state.messages, isEditing: null }};
 | 
			
		||||
 | 
			
		||||
            if (save) {
 | 
			
		||||
                newState.settings = {
 | 
			
		||||
                    ...this.state.settings,
 | 
			
		||||
                    [key]: draftToHtml(convertToRaw(this.state.messages.editor.getCurrentContent())),
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            callback = callback || function () { };
 | 
			
		||||
            this.setState(newState, callback);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    openEditor(message) {
 | 
			
		||||
        this.closeEditor(true);
 | 
			
		||||
        const contentBlock = htmlToDraft(this.state.settings[message] ?? "");
 | 
			
		||||
        if (contentBlock) {
 | 
			
		||||
            const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);
 | 
			
		||||
            const editorState = EditorState.createWithContent(contentState);
 | 
			
		||||
            this.setState({
 | 
			
		||||
                ...this.state,
 | 
			
		||||
                messages: {
 | 
			
		||||
                    ...this.state.messages,
 | 
			
		||||
                    isEditing: message,
 | 
			
		||||
                    editor: editorState
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								cli.php
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										7
									
								
								cli.php
									
									
									
									
									
								
							@ -42,7 +42,7 @@ function getDatabaseConfig(): ConnectionData {
 | 
			
		||||
$config = new Configuration();
 | 
			
		||||
$database = $config->getDatabase();
 | 
			
		||||
if ($database !== null && $database->getProperty("isDocker", false) && !is_file("/.dockerenv")) {
 | 
			
		||||
  if (count($argv) < 2 || $argv[1] !== "db") {
 | 
			
		||||
  if (count($argv) < 3 || $argv[1] !== "db" || !in_array($argv[2], ["shell", "import", "export"])) {
 | 
			
		||||
    $command = array_merge(["docker", "exec", "-it", "php", "php"], $argv);
 | 
			
		||||
    $proc = proc_open($command, [1 => STDOUT, 2 => STDERR], $pipes, "/application");
 | 
			
		||||
    exit(proc_close($proc));
 | 
			
		||||
@ -464,11 +464,12 @@ function onRoutes(array $argv) {
 | 
			
		||||
      _exit("Error fetching routes: " . $req->getLastError());
 | 
			
		||||
    } else {
 | 
			
		||||
      $routes = $req->getResult()["routes"];
 | 
			
		||||
      $head = ["uid", "request", "action", "target", "extra", "active"];
 | 
			
		||||
      $head = ["uid", "request", "action", "target", "extra", "active", "exact"];
 | 
			
		||||
 | 
			
		||||
      // strict boolean
 | 
			
		||||
      foreach ($routes as &$route) {
 | 
			
		||||
        $route["active"] = $route["active"] ? "true" : "false";
 | 
			
		||||
        $route["exact"] = $route["exact"] ? "true" : "false";
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      printTable($head, $routes);
 | 
			
		||||
@ -482,7 +483,7 @@ function onRoutes(array $argv) {
 | 
			
		||||
      "request" => $argv[3],
 | 
			
		||||
      "action" => $argv[4],
 | 
			
		||||
      "target" => $argv[5],
 | 
			
		||||
      "extra" => $argv[6] ?? ""
 | 
			
		||||
      "extra" => $argv[7] ?? "",
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $req  = new Api\Routes\Add($user);
 | 
			
		||||
 | 
			
		||||
@ -94,7 +94,7 @@ namespace Api\Mail {
 | 
			
		||||
    public function _execute(): bool {
 | 
			
		||||
 | 
			
		||||
      $mailConfig = $this->getMailConfig();
 | 
			
		||||
      if (!$this->success) {
 | 
			
		||||
      if (!$this->success || $mailConfig === null) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -411,7 +411,7 @@ namespace Api\Mail {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $mailConfig = $this->getMailConfig();
 | 
			
		||||
      if (!$this->success) {
 | 
			
		||||
      if (!$this->success || $mailConfig === null) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -68,7 +68,10 @@ namespace Api\Routes {
 | 
			
		||||
  use Api\RoutesAPI;
 | 
			
		||||
  use Driver\SQL\Condition\Compare;
 | 
			
		||||
  use Driver\SQL\Condition\CondBool;
 | 
			
		||||
  use Objects\Router;
 | 
			
		||||
  use Objects\Router\DocumentRoute;
 | 
			
		||||
  use Objects\Router\RedirectRoute;
 | 
			
		||||
  use Objects\Router\Router;
 | 
			
		||||
  use Objects\Router\StaticFileRoute;
 | 
			
		||||
  use Objects\User;
 | 
			
		||||
 | 
			
		||||
  class Fetch extends RoutesAPI {
 | 
			
		||||
@ -367,17 +370,17 @@ namespace Api\Routes {
 | 
			
		||||
        $exact = $sql->parseBool($row["exact"]);
 | 
			
		||||
        switch ($row["action"]) {
 | 
			
		||||
          case "redirect_temporary":
 | 
			
		||||
            $this->router->addRoute(new Router\RedirectRoute($request, $exact, $target, 307));
 | 
			
		||||
            $this->router->addRoute(new RedirectRoute($request, $exact, $target, 307));
 | 
			
		||||
            break;
 | 
			
		||||
          case "redirect_permanently":
 | 
			
		||||
            $this->router->addRoute(new Router\RedirectRoute($request, $exact, $target, 308));
 | 
			
		||||
            $this->router->addRoute(new RedirectRoute($request, $exact, $target, 308));
 | 
			
		||||
            break;
 | 
			
		||||
          case "static":
 | 
			
		||||
            $this->router->addRoute(new Router\StaticFileRoute($request, $exact, $target));
 | 
			
		||||
            $this->router->addRoute(new StaticFileRoute($request, $exact, $target));
 | 
			
		||||
            break;
 | 
			
		||||
          case "dynamic":
 | 
			
		||||
            $extra = json_decode($row["extra"]) ?? [];
 | 
			
		||||
            $this->router->addRoute(new Router\DocumentRoute($request, $exact, $target, ...$extra));
 | 
			
		||||
            $this->router->addRoute(new DocumentRoute($request, $exact, $target, ...$extra));
 | 
			
		||||
            break;
 | 
			
		||||
          default:
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
@ -1076,6 +1076,10 @@ namespace Api\User {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $settings = $this->user->getConfiguration()->getSettings();
 | 
			
		||||
      if (!$settings->isMailEnabled()) {
 | 
			
		||||
        return $this->createError("The mail service is not enabled, please contact the server administration.");
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if ($settings->isRecaptchaEnabled()) {
 | 
			
		||||
        $captcha = $this->getParam("captcha");
 | 
			
		||||
        $req = new VerifyCaptcha($this->user);
 | 
			
		||||
 | 
			
		||||
@ -1,390 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Objects {
 | 
			
		||||
 | 
			
		||||
  use Driver\Logger\Logger;
 | 
			
		||||
  use Objects\Router\AbstractRoute;
 | 
			
		||||
 | 
			
		||||
  class Router {
 | 
			
		||||
 | 
			
		||||
    private User $user;
 | 
			
		||||
    private Logger $logger;
 | 
			
		||||
    protected array $routes;
 | 
			
		||||
    protected array $statusCodeRoutes;
 | 
			
		||||
 | 
			
		||||
    public function __construct(User $user) {
 | 
			
		||||
      $this->user = $user;
 | 
			
		||||
      $this->logger = new Logger("Router", $user->getSQL());
 | 
			
		||||
      $this->routes = [];
 | 
			
		||||
      $this->statusCodeRoutes = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function run(string $url): string {
 | 
			
		||||
 | 
			
		||||
      // TODO: do we want a global try cache and return status page 500 on any error?
 | 
			
		||||
      // or do we want to have a global status page function here?
 | 
			
		||||
 | 
			
		||||
      $url = strtok($url, "?");
 | 
			
		||||
      foreach ($this->routes as $route) {
 | 
			
		||||
        $pathParams = $route->match($url);
 | 
			
		||||
        if ($pathParams !== false) {
 | 
			
		||||
          return $route->call($this, $pathParams);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return $this->returnStatusCode(404);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function returnStatusCode(int $code, array $params = []): string {
 | 
			
		||||
      http_response_code($code);
 | 
			
		||||
      $params["status_code"] = $code;
 | 
			
		||||
      $params["status_description"] = HTTP_STATUS_DESCRIPTIONS[$code] ?? "Unknown Error";
 | 
			
		||||
      $route = $this->statusCodeRoutes[strval($code)] ?? null;
 | 
			
		||||
      if ($route) {
 | 
			
		||||
        return $route->call($this, $params);
 | 
			
		||||
      } else {
 | 
			
		||||
        $req = new \Api\Template\Render($this->user);
 | 
			
		||||
        $res = $req->execute(["file" => "error_document.twig", "parameters" => $params]);
 | 
			
		||||
        if ($res) {
 | 
			
		||||
          return $req->getResult()["html"];
 | 
			
		||||
        } else {
 | 
			
		||||
          var_dump($req->getLastError());
 | 
			
		||||
          $description = htmlspecialchars($params["status_description"]);
 | 
			
		||||
          return "<b>$code - $description</b>";
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function addRoute(AbstractRoute $route) {
 | 
			
		||||
      if (preg_match("/^\d+$/", $route->getPattern())) {
 | 
			
		||||
        $this->statusCodeRoutes[$route->getPattern()] = $route;
 | 
			
		||||
      } else {
 | 
			
		||||
        $this->routes[] = $route;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function writeCache(string $file): bool {
 | 
			
		||||
 | 
			
		||||
      $routes = "";
 | 
			
		||||
      foreach ($this->routes as $route) {
 | 
			
		||||
        $constructor = $route->generateCache();
 | 
			
		||||
        $routes .= "\n    \$this->addRoute($constructor);";
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $date = (new \DateTime())->format("Y/m/d H:i:s");
 | 
			
		||||
      $code = "<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * DO NOT EDIT! 
 | 
			
		||||
 * This file is automatically generated by the RoutesAPI on $date.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Cache;
 | 
			
		||||
use Objects\User;
 | 
			
		||||
use Objects\Router;
 | 
			
		||||
 | 
			
		||||
class RouterCache extends Router {
 | 
			
		||||
 | 
			
		||||
  public function __construct(User \$user) {
 | 
			
		||||
    parent::__construct(\$user);$routes
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
";
 | 
			
		||||
 | 
			
		||||
      if (@file_put_contents($file, $code) === false) {
 | 
			
		||||
        $this->logger->severe("Could not write Router cache file: $file");
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getUser(): User {
 | 
			
		||||
      return $this->user;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLogger(): Logger {
 | 
			
		||||
      return $this->logger;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function cleanURL(string $url, bool $cleanGET = true): string {
 | 
			
		||||
      // strip GET parameters
 | 
			
		||||
      if ($cleanGET) {
 | 
			
		||||
        if (($index = strpos($url, "?")) !== false) {
 | 
			
		||||
          $url = substr($url, 0, $index);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // strip document reference part
 | 
			
		||||
      if (($index = strpos($url, "#")) !== false) {
 | 
			
		||||
        $url = substr($url, 0, $index);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // strip leading slash
 | 
			
		||||
      return preg_replace("/^\/+/", "", $url);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace Objects\Router {
 | 
			
		||||
 | 
			
		||||
  use Api\Parameter\Parameter;
 | 
			
		||||
  use Elements\Document;
 | 
			
		||||
  use Objects\Router;
 | 
			
		||||
  use PHPUnit\TextUI\ReflectionException;
 | 
			
		||||
 | 
			
		||||
  abstract class AbstractRoute {
 | 
			
		||||
 | 
			
		||||
    private string $pattern;
 | 
			
		||||
    private bool $exact;
 | 
			
		||||
 | 
			
		||||
    public function __construct(string $pattern, bool $exact = true) {
 | 
			
		||||
      $this->pattern = $pattern;
 | 
			
		||||
      $this->exact = $exact;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static function parseParamType(?string $type): ?int {
 | 
			
		||||
      if ($type === null || trim($type) === "") {
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $type = strtolower(trim($type));
 | 
			
		||||
      if (in_array($type, ["int", "integer"])) {
 | 
			
		||||
        return Parameter::TYPE_INT;
 | 
			
		||||
      } else if (in_array($type, ["float", "double"])) {
 | 
			
		||||
        return Parameter::TYPE_FLOAT;
 | 
			
		||||
      } else if (in_array($type, ["bool", "boolean"])) {
 | 
			
		||||
        return Parameter::TYPE_BOOLEAN;
 | 
			
		||||
      } else {
 | 
			
		||||
        return Parameter::TYPE_STRING;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getPattern(): string {
 | 
			
		||||
      return $this->pattern;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public abstract function call(Router $router, array $params): string;
 | 
			
		||||
 | 
			
		||||
    protected function getArgs(): array {
 | 
			
		||||
      return [$this->pattern, $this->exact];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function generateCache(): string {
 | 
			
		||||
      $reflection = new \ReflectionClass($this);
 | 
			
		||||
      $className = $reflection->getName();
 | 
			
		||||
      $args = implode(", ", array_map(function ($arg) {
 | 
			
		||||
          return var_export($arg, true);
 | 
			
		||||
        }, $this->getArgs()));
 | 
			
		||||
      return "new \\$className($args)";
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function match(string $url) {
 | 
			
		||||
 | 
			
		||||
      # /test/{abc}/{param:?}/{xyz:int}/{aaa:int?}
 | 
			
		||||
      $patternParts = explode("/", Router::cleanURL($this->pattern, false));
 | 
			
		||||
      $countPattern = count($patternParts);
 | 
			
		||||
      $patternOffset = 0;
 | 
			
		||||
 | 
			
		||||
      # /test/param/optional/123
 | 
			
		||||
      $urlParts = explode("/", $url);
 | 
			
		||||
      $countUrl = count($urlParts);
 | 
			
		||||
      $urlOffset = 0;
 | 
			
		||||
 | 
			
		||||
      $params = [];
 | 
			
		||||
      for (; $patternOffset < $countPattern; $patternOffset++) {
 | 
			
		||||
 | 
			
		||||
        if (!preg_match("/^{.*}$/", $patternParts[$patternOffset])) {
 | 
			
		||||
 | 
			
		||||
          // not a parameter? check if it matches
 | 
			
		||||
          if ($urlOffset >= $countUrl || $urlParts[$urlOffset] !== $patternParts[$patternOffset]) {
 | 
			
		||||
            return false;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          $urlOffset++;
 | 
			
		||||
 | 
			
		||||
        } else {
 | 
			
		||||
 | 
			
		||||
          // we got a parameter here
 | 
			
		||||
          $paramDefinition = explode(":", substr($patternParts[$patternOffset], 1, -1));
 | 
			
		||||
          $paramName = array_shift($paramDefinition);
 | 
			
		||||
          $paramType = array_shift($paramDefinition);
 | 
			
		||||
          $paramOptional = endsWith($paramType, "?");
 | 
			
		||||
          if ($paramOptional) {
 | 
			
		||||
            $paramType = substr($paramType, 0, -1);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          $paramType = self::parseParamType($paramType);
 | 
			
		||||
          if ($urlOffset >= $countUrl || $urlParts[$urlOffset] === "") {
 | 
			
		||||
            if ($paramOptional) {
 | 
			
		||||
              $param = $urlParts[$urlOffset] ?? null;
 | 
			
		||||
              if ($param !== null && $paramType !== null && Parameter::parseType($param) !== $paramType) {
 | 
			
		||||
                return false;
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              $params[$paramName] = $param;
 | 
			
		||||
              if ($urlOffset < $countUrl) {
 | 
			
		||||
                $urlOffset++;
 | 
			
		||||
              }
 | 
			
		||||
            } else {
 | 
			
		||||
              return false;
 | 
			
		||||
            }
 | 
			
		||||
          } else {
 | 
			
		||||
            $param = $urlParts[$urlOffset];
 | 
			
		||||
            if ($paramType !== null && Parameter::parseType($param) !== $paramType) {
 | 
			
		||||
              return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $params[$paramName] = $param;
 | 
			
		||||
            $urlOffset++;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if ($urlOffset !== $countUrl && $this->exact) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return $params;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  class EmptyRoute extends AbstractRoute {
 | 
			
		||||
 | 
			
		||||
    public function __construct(string $pattern, bool $exact = true) {
 | 
			
		||||
      parent::__construct($pattern, $exact);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function call(Router $router, array $params): string {
 | 
			
		||||
      return "";
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  class StaticFileRoute extends AbstractRoute {
 | 
			
		||||
 | 
			
		||||
    private string $path;
 | 
			
		||||
    private int $code;
 | 
			
		||||
 | 
			
		||||
    public function __construct(string $pattern, bool $exact, string $path, int $code = 200) {
 | 
			
		||||
      parent::__construct($pattern, $exact);
 | 
			
		||||
      $this->path = $path;
 | 
			
		||||
      $this->code = $code;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function call(Router $router, array $params): string {
 | 
			
		||||
      http_response_code($this->code);
 | 
			
		||||
      return serveStatic(WEBROOT, $this->path);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function getArgs(): array {
 | 
			
		||||
      return array_merge(parent::getArgs(), [$this->path, $this->code]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  class StaticRoute extends AbstractRoute {
 | 
			
		||||
 | 
			
		||||
    private string $data;
 | 
			
		||||
    private int $code;
 | 
			
		||||
 | 
			
		||||
    public function __construct(string $pattern, bool $exact, string $data, int $code = 200) {
 | 
			
		||||
      parent::__construct($pattern, $exact);
 | 
			
		||||
      $this->data = $data;
 | 
			
		||||
      $this->code = $code;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function call(Router $router, array $params): string {
 | 
			
		||||
      http_response_code($this->code);
 | 
			
		||||
      return $this->data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function getArgs(): array {
 | 
			
		||||
      return array_merge(parent::getArgs(), [$this->data, $this->code]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  class RedirectRoute extends AbstractRoute {
 | 
			
		||||
 | 
			
		||||
    private string $destination;
 | 
			
		||||
    private int $code;
 | 
			
		||||
 | 
			
		||||
    public function __construct(string $pattern, bool $exact, string $destination, int $code = 307) {
 | 
			
		||||
      parent::__construct($pattern, $exact);
 | 
			
		||||
      $this->destination = $destination;
 | 
			
		||||
      $this->code = $code;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function call(Router $router, array $params): string {
 | 
			
		||||
      header("Location: $this->destination");
 | 
			
		||||
      http_response_code($this->code);
 | 
			
		||||
      return "";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function getArgs(): array {
 | 
			
		||||
      return array_merge(parent::getArgs(), [$this->destination, $this->code]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  class DocumentRoute extends AbstractRoute {
 | 
			
		||||
 | 
			
		||||
    private string $className;
 | 
			
		||||
    private array $args;
 | 
			
		||||
    private ?\ReflectionClass $reflectionClass;
 | 
			
		||||
 | 
			
		||||
    public function __construct(string $pattern, bool $exact, string $className, ...$args) {
 | 
			
		||||
      parent::__construct($pattern, $exact);
 | 
			
		||||
      $this->className = $className;
 | 
			
		||||
      $this->args = $args;
 | 
			
		||||
      $this->reflectionClass = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function loadClass(): bool {
 | 
			
		||||
 | 
			
		||||
      if ($this->reflectionClass === null) {
 | 
			
		||||
        try {
 | 
			
		||||
          $file = getClassPath($this->className);
 | 
			
		||||
          if (file_exists($file)) {
 | 
			
		||||
            $this->reflectionClass = new \ReflectionClass($this->className);
 | 
			
		||||
            if ($this->reflectionClass->isSubclassOf(Document::class)) {
 | 
			
		||||
              return true;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        } catch (ReflectionException $exception) {
 | 
			
		||||
          $this->reflectionClass = null;
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->reflectionClass = null;
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function match(string $url) {
 | 
			
		||||
      $match = parent::match($url);
 | 
			
		||||
      if ($match === false || !$this->loadClass()) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return $match;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function getArgs(): array {
 | 
			
		||||
      return array_merge(parent::getArgs(), [$this->className], $this->args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function call(Router $router, array $params): string {
 | 
			
		||||
      if (!$this->loadClass()) {
 | 
			
		||||
        return $router->returnStatusCode(500, [ "message" =>  "Error loading class: $this->className"]);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        $args = array_merge([$router->getUser()], $this->args);
 | 
			
		||||
        $document = $this->reflectionClass->newInstanceArgs($args);
 | 
			
		||||
        return $document->getCode($params);
 | 
			
		||||
      } catch (\ReflectionException $e) {
 | 
			
		||||
        return $router->returnStatusCode(500, [ "message" =>  "Error loading class $this->className: " . $e->getMessage()]);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										121
									
								
								core/Objects/Router/AbstractRoute.class.php
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										121
									
								
								core/Objects/Router/AbstractRoute.class.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,121 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Objects\Router;
 | 
			
		||||
 | 
			
		||||
use Api\Parameter\Parameter;
 | 
			
		||||
 | 
			
		||||
abstract class AbstractRoute {
 | 
			
		||||
 | 
			
		||||
  private string $pattern;
 | 
			
		||||
  private bool $exact;
 | 
			
		||||
 | 
			
		||||
  public function __construct(string $pattern, bool $exact = true) {
 | 
			
		||||
    $this->pattern = $pattern;
 | 
			
		||||
    $this->exact = $exact;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static function parseParamType(?string $type): ?int {
 | 
			
		||||
    if ($type === null || trim($type) === "") {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $type = strtolower(trim($type));
 | 
			
		||||
    if (in_array($type, ["int", "integer"])) {
 | 
			
		||||
      return Parameter::TYPE_INT;
 | 
			
		||||
    } else if (in_array($type, ["float", "double"])) {
 | 
			
		||||
      return Parameter::TYPE_FLOAT;
 | 
			
		||||
    } else if (in_array($type, ["bool", "boolean"])) {
 | 
			
		||||
      return Parameter::TYPE_BOOLEAN;
 | 
			
		||||
    } else {
 | 
			
		||||
      return Parameter::TYPE_STRING;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function getPattern(): string {
 | 
			
		||||
    return $this->pattern;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public abstract function call(Router $router, array $params): string;
 | 
			
		||||
 | 
			
		||||
  protected function getArgs(): array {
 | 
			
		||||
    return [$this->pattern, $this->exact];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function generateCache(): string {
 | 
			
		||||
    $reflection = new \ReflectionClass($this);
 | 
			
		||||
    $className = $reflection->getName();
 | 
			
		||||
    $args = implode(", ", array_map(function ($arg) {
 | 
			
		||||
      return var_export($arg, true);
 | 
			
		||||
    }, $this->getArgs()));
 | 
			
		||||
    return "new \\$className($args)";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function match(string $url) {
 | 
			
		||||
 | 
			
		||||
    # /test/{abc}/{param:?}/{xyz:int}/{aaa:int?}
 | 
			
		||||
    $patternParts = explode("/", Router::cleanURL($this->pattern, false));
 | 
			
		||||
    $countPattern = count($patternParts);
 | 
			
		||||
    $patternOffset = 0;
 | 
			
		||||
 | 
			
		||||
    # /test/param/optional/123
 | 
			
		||||
    $urlParts = explode("/", $url);
 | 
			
		||||
    $countUrl = count($urlParts);
 | 
			
		||||
    $urlOffset = 0;
 | 
			
		||||
 | 
			
		||||
    $params = [];
 | 
			
		||||
    for (; $patternOffset < $countPattern; $patternOffset++) {
 | 
			
		||||
 | 
			
		||||
      if (!preg_match("/^{.*}$/", $patternParts[$patternOffset])) {
 | 
			
		||||
 | 
			
		||||
        // not a parameter? check if it matches
 | 
			
		||||
        if ($urlOffset >= $countUrl || $urlParts[$urlOffset] !== $patternParts[$patternOffset]) {
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $urlOffset++;
 | 
			
		||||
 | 
			
		||||
      } else {
 | 
			
		||||
 | 
			
		||||
        // we got a parameter here
 | 
			
		||||
        $paramDefinition = explode(":", substr($patternParts[$patternOffset], 1, -1));
 | 
			
		||||
        $paramName = array_shift($paramDefinition);
 | 
			
		||||
        $paramType = array_shift($paramDefinition);
 | 
			
		||||
        $paramOptional = endsWith($paramType, "?");
 | 
			
		||||
        if ($paramOptional) {
 | 
			
		||||
          $paramType = substr($paramType, 0, -1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $paramType = self::parseParamType($paramType);
 | 
			
		||||
        if ($urlOffset >= $countUrl || $urlParts[$urlOffset] === "") {
 | 
			
		||||
          if ($paramOptional) {
 | 
			
		||||
            $param = $urlParts[$urlOffset] ?? null;
 | 
			
		||||
            if ($param !== null && $paramType !== null && Parameter::parseType($param) !== $paramType) {
 | 
			
		||||
              return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $params[$paramName] = $param;
 | 
			
		||||
            if ($urlOffset < $countUrl) {
 | 
			
		||||
              $urlOffset++;
 | 
			
		||||
            }
 | 
			
		||||
          } else {
 | 
			
		||||
            return false;
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          $param = $urlParts[$urlOffset];
 | 
			
		||||
          if ($paramType !== null && Parameter::parseType($param) !== $paramType) {
 | 
			
		||||
            return false;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          $params[$paramName] = $param;
 | 
			
		||||
          $urlOffset++;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($urlOffset !== $countUrl && $this->exact) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $params;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								core/Objects/Router/ApiRoute.class.php
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										60
									
								
								core/Objects/Router/ApiRoute.class.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Objects\Router;
 | 
			
		||||
 | 
			
		||||
use Api\Request;
 | 
			
		||||
use ReflectionClass;
 | 
			
		||||
use ReflectionException;
 | 
			
		||||
 | 
			
		||||
class ApiRoute extends AbstractRoute {
 | 
			
		||||
 | 
			
		||||
  public function __construct() {
 | 
			
		||||
    parent::__construct("/api/{endpoint:?}/{method:?}", false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function call(Router $router, array $params): string {
 | 
			
		||||
    $user = $router->getUser();
 | 
			
		||||
    if (empty($params["endpoint"])) {
 | 
			
		||||
      header("Content-Type: text/html");
 | 
			
		||||
      $document = new \Elements\TemplateDocument($user, "swagger.twig");
 | 
			
		||||
      return $document->getCode();
 | 
			
		||||
    } else if(!preg_match("/[a-zA-Z]+(\/[a-zA-Z]+)*/", $params["endpoint"])) {
 | 
			
		||||
      http_response_code(400);
 | 
			
		||||
      $response = createError("Invalid Method");
 | 
			
		||||
    } else {
 | 
			
		||||
      $apiEndpoint = ucfirst($params["endpoint"]);
 | 
			
		||||
      if (!empty($params["method"])) {
 | 
			
		||||
        $apiMethod = ucfirst($params["method"]);
 | 
			
		||||
        $parentClass = "\\Api\\${apiEndpoint}API";
 | 
			
		||||
        $apiClass = "\\Api\\${apiEndpoint}\\${apiMethod}";
 | 
			
		||||
      } else {
 | 
			
		||||
        $apiClass = "\\Api\\${apiEndpoint}";
 | 
			
		||||
        $parentClass = $apiClass;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        $file = getClassPath($parentClass);
 | 
			
		||||
        if (!file_exists($file) || !class_exists($parentClass) || !class_exists($apiClass)) {
 | 
			
		||||
          http_response_code(404);
 | 
			
		||||
          $response = createError("Not found");
 | 
			
		||||
        } else {
 | 
			
		||||
          $apiClass = new ReflectionClass($apiClass);
 | 
			
		||||
          if(!$apiClass->isSubclassOf(Request::class) || !$apiClass->isInstantiable()) {
 | 
			
		||||
            http_response_code(400);
 | 
			
		||||
            $response = createError("Invalid Method");
 | 
			
		||||
          } else {
 | 
			
		||||
            $request = $apiClass->newInstanceArgs(array($user, true));
 | 
			
		||||
            $request->execute();
 | 
			
		||||
            $response = $request->getJsonResult();
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      } catch (ReflectionException $e) {
 | 
			
		||||
        http_response_code(500);
 | 
			
		||||
        $response = createError("Error instantiating class: $e");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    header("Content-Type: application/json");
 | 
			
		||||
    return $response;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										70
									
								
								core/Objects/Router/DocumentRoute.class.php
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										70
									
								
								core/Objects/Router/DocumentRoute.class.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,70 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Objects\Router;
 | 
			
		||||
 | 
			
		||||
use Elements\Document;
 | 
			
		||||
use ReflectionException;
 | 
			
		||||
 | 
			
		||||
class DocumentRoute extends AbstractRoute {
 | 
			
		||||
 | 
			
		||||
  private string $className;
 | 
			
		||||
  private array $args;
 | 
			
		||||
  private ?\ReflectionClass $reflectionClass;
 | 
			
		||||
 | 
			
		||||
  public function __construct(string $pattern, bool $exact, string $className, ...$args) {
 | 
			
		||||
    parent::__construct($pattern, $exact);
 | 
			
		||||
    $this->className = $className;
 | 
			
		||||
    $this->args = $args;
 | 
			
		||||
    $this->reflectionClass = null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private function loadClass(): bool {
 | 
			
		||||
 | 
			
		||||
    if ($this->reflectionClass === null) {
 | 
			
		||||
      try {
 | 
			
		||||
        $file = getClassPath($this->className);
 | 
			
		||||
        if (file_exists($file)) {
 | 
			
		||||
          $this->reflectionClass = new \ReflectionClass($this->className);
 | 
			
		||||
          if ($this->reflectionClass->isSubclassOf(Document::class)) {
 | 
			
		||||
            return true;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      } catch (ReflectionException $exception) {
 | 
			
		||||
        $this->reflectionClass = null;
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $this->reflectionClass = null;
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function match(string $url) {
 | 
			
		||||
    $match = parent::match($url);
 | 
			
		||||
    if ($match === false || !$this->loadClass()) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $match;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function getArgs(): array {
 | 
			
		||||
    return array_merge(parent::getArgs(), [$this->className], $this->args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function call(Router $router, array $params): string {
 | 
			
		||||
    if (!$this->loadClass()) {
 | 
			
		||||
      return $router->returnStatusCode(500, [ "message" =>  "Error loading class: $this->className"]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      $args = array_merge([$router->getUser()], $this->args);
 | 
			
		||||
      $document = $this->reflectionClass->newInstanceArgs($args);
 | 
			
		||||
      return $document->getCode($params);
 | 
			
		||||
    } catch (\ReflectionException $e) {
 | 
			
		||||
      return $router->returnStatusCode(500, [ "message" =>  "Error loading class $this->className: " . $e->getMessage()]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								core/Objects/Router/EmptyRoute.class.php
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										14
									
								
								core/Objects/Router/EmptyRoute.class.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Objects\Router;
 | 
			
		||||
 | 
			
		||||
class EmptyRoute extends AbstractRoute {
 | 
			
		||||
 | 
			
		||||
  public function __construct(string $pattern, bool $exact = true) {
 | 
			
		||||
    parent::__construct($pattern, $exact);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function call(Router $router, array $params): string {
 | 
			
		||||
    return "";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								core/Objects/Router/RedirectRoute.class.php
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										25
									
								
								core/Objects/Router/RedirectRoute.class.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Objects\Router;
 | 
			
		||||
 | 
			
		||||
class RedirectRoute extends AbstractRoute {
 | 
			
		||||
 | 
			
		||||
  private string $destination;
 | 
			
		||||
  private int $code;
 | 
			
		||||
 | 
			
		||||
  public function __construct(string $pattern, bool $exact, string $destination, int $code = 307) {
 | 
			
		||||
    parent::__construct($pattern, $exact);
 | 
			
		||||
    $this->destination = $destination;
 | 
			
		||||
    $this->code = $code;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function call(Router $router, array $params): string {
 | 
			
		||||
    header("Location: $this->destination");
 | 
			
		||||
    http_response_code($this->code);
 | 
			
		||||
    return "";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function getArgs(): array {
 | 
			
		||||
    return array_merge(parent::getArgs(), [$this->destination, $this->code]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										128
									
								
								core/Objects/Router/Router.class.php
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										128
									
								
								core/Objects/Router/Router.class.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,128 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Objects\Router;
 | 
			
		||||
 | 
			
		||||
use Driver\Logger\Logger;
 | 
			
		||||
use Objects\User;
 | 
			
		||||
 | 
			
		||||
class Router {
 | 
			
		||||
 | 
			
		||||
  private User $user;
 | 
			
		||||
  private Logger $logger;
 | 
			
		||||
  protected array $routes;
 | 
			
		||||
  protected array $statusCodeRoutes;
 | 
			
		||||
 | 
			
		||||
  public function __construct(User $user) {
 | 
			
		||||
    $this->user = $user;
 | 
			
		||||
    $this->logger = new Logger("Router", $user->getSQL());
 | 
			
		||||
    $this->routes = [];
 | 
			
		||||
    $this->statusCodeRoutes = [];
 | 
			
		||||
 | 
			
		||||
    $this->addRoute(new ApiRoute());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function run(string $url): string {
 | 
			
		||||
 | 
			
		||||
    // TODO: do we want a global try cache and return status page 500 on any error?
 | 
			
		||||
    // or do we want to have a global status page function here?
 | 
			
		||||
 | 
			
		||||
    $url = strtok($url, "?");
 | 
			
		||||
    foreach ($this->routes as $route) {
 | 
			
		||||
      $pathParams = $route->match($url);
 | 
			
		||||
      if ($pathParams !== false) {
 | 
			
		||||
        return $route->call($this, $pathParams);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $this->returnStatusCode(404);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function returnStatusCode(int $code, array $params = []): string {
 | 
			
		||||
    http_response_code($code);
 | 
			
		||||
    $params["status_code"] = $code;
 | 
			
		||||
    $params["status_description"] = HTTP_STATUS_DESCRIPTIONS[$code] ?? "Unknown Error";
 | 
			
		||||
    $route = $this->statusCodeRoutes[strval($code)] ?? null;
 | 
			
		||||
    if ($route) {
 | 
			
		||||
      return $route->call($this, $params);
 | 
			
		||||
    } else {
 | 
			
		||||
      $req = new \Api\Template\Render($this->user);
 | 
			
		||||
      $res = $req->execute(["file" => "error_document.twig", "parameters" => $params]);
 | 
			
		||||
      if ($res) {
 | 
			
		||||
        return $req->getResult()["html"];
 | 
			
		||||
      } else {
 | 
			
		||||
        var_dump($req->getLastError());
 | 
			
		||||
        $description = htmlspecialchars($params["status_description"]);
 | 
			
		||||
        return "<b>$code - $description</b>";
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function addRoute(AbstractRoute $route) {
 | 
			
		||||
    if (preg_match("/^\d+$/", $route->getPattern())) {
 | 
			
		||||
      $this->statusCodeRoutes[$route->getPattern()] = $route;
 | 
			
		||||
    } else {
 | 
			
		||||
      $this->routes[] = $route;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function writeCache(string $file): bool {
 | 
			
		||||
 | 
			
		||||
    $routes = "";
 | 
			
		||||
    foreach ($this->routes as $route) {
 | 
			
		||||
      $constructor = $route->generateCache();
 | 
			
		||||
      $routes .= "\n    \$this->addRoute($constructor);";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $date = (new \DateTime())->format("Y/m/d H:i:s");
 | 
			
		||||
    $code = "<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * DO NOT EDIT! 
 | 
			
		||||
 * This file is automatically generated by the RoutesAPI on $date.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Cache;
 | 
			
		||||
use Objects\User;
 | 
			
		||||
use Objects\Router\Router;
 | 
			
		||||
 | 
			
		||||
class RouterCache extends Router {
 | 
			
		||||
 | 
			
		||||
  public function __construct(User \$user) {
 | 
			
		||||
    parent::__construct(\$user);$routes
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
";
 | 
			
		||||
 | 
			
		||||
    if (@file_put_contents($file, $code) === false) {
 | 
			
		||||
      $this->logger->severe("Could not write Router cache file: $file");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function getUser(): User {
 | 
			
		||||
    return $this->user;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function getLogger(): Logger {
 | 
			
		||||
    return $this->logger;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static function cleanURL(string $url, bool $cleanGET = true): string {
 | 
			
		||||
    // strip GET parameters
 | 
			
		||||
    if ($cleanGET) {
 | 
			
		||||
      if (($index = strpos($url, "?")) !== false) {
 | 
			
		||||
        $url = substr($url, 0, $index);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // strip document reference part
 | 
			
		||||
    if (($index = strpos($url, "#")) !== false) {
 | 
			
		||||
      $url = substr($url, 0, $index);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // strip leading slash
 | 
			
		||||
    return preg_replace("/^\/+/", "", $url);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								core/Objects/Router/StaticFileRoute.class.php
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										24
									
								
								core/Objects/Router/StaticFileRoute.class.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Objects\Router;
 | 
			
		||||
 | 
			
		||||
class StaticFileRoute extends AbstractRoute {
 | 
			
		||||
 | 
			
		||||
  private string $path;
 | 
			
		||||
  private int $code;
 | 
			
		||||
 | 
			
		||||
  public function __construct(string $pattern, bool $exact, string $path, int $code = 200) {
 | 
			
		||||
    parent::__construct($pattern, $exact);
 | 
			
		||||
    $this->path = $path;
 | 
			
		||||
    $this->code = $code;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function call(Router $router, array $params): string {
 | 
			
		||||
    http_response_code($this->code);
 | 
			
		||||
    return serveStatic(WEBROOT, $this->path);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function getArgs(): array {
 | 
			
		||||
    return array_merge(parent::getArgs(), [$this->path, $this->code]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								core/Objects/Router/StaticRoute.class.php
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										24
									
								
								core/Objects/Router/StaticRoute.class.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Objects\Router;
 | 
			
		||||
 | 
			
		||||
class StaticRoute extends AbstractRoute {
 | 
			
		||||
 | 
			
		||||
  private string $data;
 | 
			
		||||
  private int $code;
 | 
			
		||||
 | 
			
		||||
  public function __construct(string $pattern, bool $exact, string $data, int $code = 200) {
 | 
			
		||||
    parent::__construct($pattern, $exact);
 | 
			
		||||
    $this->data = $data;
 | 
			
		||||
    $this->code = $code;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function call(Router $router, array $params): string {
 | 
			
		||||
    http_response_code($this->code);
 | 
			
		||||
    return $this->data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function getArgs(): array {
 | 
			
		||||
    return array_merge(parent::getArgs(), [$this->data, $this->code]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -20,7 +20,10 @@
 | 
			
		||||
            </div>
 | 
			
		||||
            <input type="password" autocomplete='password' name='password' id='password' class="form-control" placeholder="Password">
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="input-group mt-5 mb-4">
 | 
			
		||||
        <div class="ml-2" style="line-height: 38px;">
 | 
			
		||||
            <a href="/resetPassword">Forgot Password?</a>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="input-group mt-3 mb-4">
 | 
			
		||||
            <button type="button" class="btn btn-primary" id='btnLogin'>Sign In</button>
 | 
			
		||||
            {% if site.registrationEnabled %}
 | 
			
		||||
                <div class="ml-2" style="line-height: 38px;">Don't have an account yet? <a href="/register">Click here</a> to register.</div>
 | 
			
		||||
 | 
			
		||||
@ -25,6 +25,7 @@
 | 
			
		||||
                        You can either <a href="javascript:history.back()">Go Back to previous page</a>
 | 
			
		||||
                        or try to <a href="javascript:document.location.reload()">reload the page</a>.
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <p>{{ message }}</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,12 @@ server {
 | 
			
		||||
	access_log /var/log/nginx/access.log;
 | 
			
		||||
	root /application;
 | 
			
		||||
 | 
			
		||||
    # rewrite error codes
 | 
			
		||||
    error_page   400          /index.php?error=400;
 | 
			
		||||
    error_page   403          /index.php?error=403;
 | 
			
		||||
    error_page   404          /index.php?error=404;
 | 
			
		||||
    error_page   500          /index.php?error=500;
 | 
			
		||||
 | 
			
		||||
	# rewrite api
 | 
			
		||||
	rewrite ^/api(/.*)$ /index.php?api=$1;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1 +0,0 @@
 | 
			
		||||
php_flag engine on
 | 
			
		||||
							
								
								
									
										71
									
								
								index.php
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										71
									
								
								index.php
									
									
									
									
									
								
							@ -12,13 +12,12 @@ if (is_file("MAINTENANCE") && !in_array($_SERVER['REMOTE_ADDR'], ['127.0.0.1', '
 | 
			
		||||
  die();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
use Api\Request;
 | 
			
		||||
use Configuration\Configuration;
 | 
			
		||||
use Objects\Router;
 | 
			
		||||
use Objects\Router\Router;
 | 
			
		||||
 | 
			
		||||
if (!is_readable(getClassPath(Configuration::class))) {
 | 
			
		||||
  header("Content-Type: application/json");
 | 
			
		||||
  die(json_encode(array( "success" => false, "msg" => "Configuration class is not readable, check permissions before proceeding." )));
 | 
			
		||||
  die(json_encode([ "success" => false, "msg" => "Configuration class is not readable, check permissions before proceeding." ]));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$config = new Configuration();
 | 
			
		||||
@ -27,64 +26,7 @@ $sql    = $user->getSQL();
 | 
			
		||||
$settings = $config->getSettings();
 | 
			
		||||
$installation = !$sql || ($sql->isConnected() && !$settings->isInstalled());
 | 
			
		||||
 | 
			
		||||
// API routes, prefix: /api/
 | 
			
		||||
// TODO: move this to Router?
 | 
			
		||||
if (isset($_GET["api"]) && is_string($_GET["api"])) {
 | 
			
		||||
  $isApiResponse = true;
 | 
			
		||||
  if ($installation) {
 | 
			
		||||
    $response = createError("Not installed");
 | 
			
		||||
  } else {
 | 
			
		||||
    $apiFunction = $_GET["api"];
 | 
			
		||||
    if (empty($apiFunction) || $apiFunction === "/") {
 | 
			
		||||
      $document = new \Elements\TemplateDocument($user, "swagger.twig");
 | 
			
		||||
      $response = $document->getCode();
 | 
			
		||||
      $isApiResponse = false;
 | 
			
		||||
    } else if(!preg_match("/[a-zA-Z]+(\/[a-zA-Z]+)*/", $apiFunction)) {
 | 
			
		||||
      http_response_code(400);
 | 
			
		||||
      $response = createError("Invalid Method");
 | 
			
		||||
    } else {
 | 
			
		||||
      $apiFunction = array_filter(array_map('ucfirst', explode("/", $apiFunction)));
 | 
			
		||||
      if (count($apiFunction) > 1) {
 | 
			
		||||
        $parentClass = "\\Api\\" . reset($apiFunction) . "API";
 | 
			
		||||
        $apiClass = "\\Api\\" . implode("\\", $apiFunction);
 | 
			
		||||
      } else {
 | 
			
		||||
        $apiClass = "\\Api\\" . implode("\\", $apiFunction);
 | 
			
		||||
        $parentClass = $apiClass;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        $file = getClassPath($parentClass);
 | 
			
		||||
        if(!file_exists($file) || !class_exists($parentClass) || !class_exists($apiClass)) {
 | 
			
		||||
          http_response_code(404);
 | 
			
		||||
          $response = createError("Not found");
 | 
			
		||||
        } else {
 | 
			
		||||
          $parentClass = new ReflectionClass($parentClass);
 | 
			
		||||
          $apiClass = new ReflectionClass($apiClass);
 | 
			
		||||
          if(!$apiClass->isSubclassOf(Request::class) || !$apiClass->isInstantiable()) {
 | 
			
		||||
            http_response_code(400);
 | 
			
		||||
            $response = createError("Invalid Method");
 | 
			
		||||
          } else {
 | 
			
		||||
            $request = $apiClass->newInstanceArgs(array($user, true));
 | 
			
		||||
            $success = $request->execute();
 | 
			
		||||
            $msg = $request->getLastError();
 | 
			
		||||
            $response = $request->getJsonResult();
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      } catch (ReflectionException $e) {
 | 
			
		||||
        $response = createError("Error instantiating class: $e");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($isApiResponse) {
 | 
			
		||||
      header("Content-Type: application/json");
 | 
			
		||||
    } else {
 | 
			
		||||
      header("Content-Type: text/html");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
} else {
 | 
			
		||||
 | 
			
		||||
  // all other routes
 | 
			
		||||
  $requestedUri = $_GET["site"] ?? $_SERVER["REQUEST_URI"];
 | 
			
		||||
$requestedUri = $_GET["site"] ?? $_GET["api"] ?? $_SERVER["REQUEST_URI"];
 | 
			
		||||
$requestedUri = Router::cleanURL($requestedUri);
 | 
			
		||||
 | 
			
		||||
if ($installation) {
 | 
			
		||||
@ -98,7 +40,6 @@ if (isset($_GET["api"]) && is_string($_GET["api"])) {
 | 
			
		||||
} else {
 | 
			
		||||
 | 
			
		||||
  $router = null;
 | 
			
		||||
 | 
			
		||||
  $routerCacheClass = '\Cache\RouterCache';
 | 
			
		||||
  $routerCachePath = getClassPath($routerCacheClass);
 | 
			
		||||
  if (is_file($routerCachePath)) {
 | 
			
		||||
@ -119,12 +60,16 @@ if (isset($_GET["api"]) && is_string($_GET["api"])) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if ($router !== null) {
 | 
			
		||||
    if (!isset($_GET["site"]) && isset($_GET["error"]) &&
 | 
			
		||||
      is_string($_GET["error"]) && preg_match("^\d+$", $_GET["error"])) {
 | 
			
		||||
      $response = $router->returnStatusCode(intval($_GET["error"]));
 | 
			
		||||
    } else {
 | 
			
		||||
      $response = $router->run($requestedUri);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $user->processVisit();
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$user->sendCookies();
 | 
			
		||||
die($response);
 | 
			
		||||
							
								
								
									
										113
									
								
								js/admin.min.js
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										113
									
								
								js/admin.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user