30 changed files with 2311 additions and 0 deletions
			
			
		@ -0,0 +1,6 @@ | 
				
			|||
.idea/ | 
				
			|||
 | 
				
			|||
vendor/ | 
				
			|||
composer.phar | 
				
			|||
 | 
				
			|||
Configuration/Secrets.yaml | 
				
			|||
@ -0,0 +1,44 @@ | 
				
			|||
<?php | 
				
			|||
 | 
				
			|||
namespace App; | 
				
			|||
 | 
				
			|||
use App\Common\UUID; | 
				
			|||
use App\Enumerators\Parameter; | 
				
			|||
use App\Enumerators\SessionElement; | 
				
			|||
use App\Wrappers\APIWrapper; | 
				
			|||
use SimpleXMLElement; | 
				
			|||
use Symfony\Component\Yaml\Yaml; | 
				
			|||
 | 
				
			|||
class Configuration | 
				
			|||
{ | 
				
			|||
    public static string $SecretDenominator = ";"; | 
				
			|||
 | 
				
			|||
    public static function GetConfig(string ...$nav) : string|array | 
				
			|||
    { | 
				
			|||
        $config = Yaml::parseFile(__DIR__ . "/../Configuration/Configuration.yaml"); | 
				
			|||
 | 
				
			|||
        $temp = $config; | 
				
			|||
        foreach ($nav as $n) $temp = $temp[$n]; | 
				
			|||
 | 
				
			|||
        if(is_array($temp)) | 
				
			|||
        { | 
				
			|||
            return $temp; | 
				
			|||
        } | 
				
			|||
        else | 
				
			|||
        { | 
				
			|||
            if(str_starts_with(haystack: $temp, needle: Configuration::$SecretDenominator)) | 
				
			|||
            { | 
				
			|||
                $secrets = Yaml::parseFile(__DIR__ . "/../Configuration/Secrets.yaml"); | 
				
			|||
                return $secrets[substr($temp, 1)]; | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
 | 
				
			|||
        return $temp; | 
				
			|||
    } | 
				
			|||
    public static function GetDictionary() : array | 
				
			|||
    { | 
				
			|||
        return Yaml::parseFile(__DIR__ . "/../Localisation/en-GB.yaml"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
} | 
				
			|||
@ -0,0 +1,97 @@ | 
				
			|||
<?php | 
				
			|||
 | 
				
			|||
namespace App\TwigExtensions; | 
				
			|||
 | 
				
			|||
use App\Configuration; | 
				
			|||
use DateTime; | 
				
			|||
use DateTimeZone; | 
				
			|||
use Twig\Extension\AbstractExtension; | 
				
			|||
use Twig\TwigFilter; | 
				
			|||
 | 
				
			|||
require_once dirname($_SERVER["DOCUMENT_ROOT"]) . "/vendor/autoload.php"; | 
				
			|||
 | 
				
			|||
class FiltersExtension extends AbstractExtension | 
				
			|||
{ | 
				
			|||
    public function getFilters() | 
				
			|||
    { | 
				
			|||
        return [ | 
				
			|||
            new TwigFilter('translate', [$this, 'Translate']), | 
				
			|||
            new TwigFilter('base64_encode', [$this, 'Base64Encode']), | 
				
			|||
            new TwigFilter('base64_decode', [$this, 'Base64Decode']), | 
				
			|||
            new TwigFilter('get_type', [$this, 'GetType']), | 
				
			|||
            new TwigFilter('pretty_date', [$this, 'PrettyDate']), | 
				
			|||
            new TwigFilter('pretty_time', [$this, 'PrettyTime']), | 
				
			|||
            new TwigFilter('pretty_date_time', [$this, 'PrettyDateTime']), | 
				
			|||
 | 
				
			|||
            new TwigFilter('json_encode', [$this, 'JSONEncode']), | 
				
			|||
            new TwigFilter('json_decode', [$this, 'JSONDecode']), | 
				
			|||
        ]; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public function JSONEncode(mixed $target): bool|string | 
				
			|||
    { | 
				
			|||
        return json_encode(value: $target); | 
				
			|||
    } | 
				
			|||
    public function JSONDecode(string $target): ?array | 
				
			|||
    { | 
				
			|||
        return json_decode($target, true); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public function Translate(?string $target): string | 
				
			|||
    { | 
				
			|||
        if($target == null) return "!!$target!!"; | 
				
			|||
 | 
				
			|||
        if(key_exists(key: $target, array: Configuration::GetDictionary())) | 
				
			|||
            return Configuration::GetDictionary()[$target]; | 
				
			|||
        /* | 
				
			|||
        $temp = Configuration::GetDictionary()[$target]; | 
				
			|||
        if($temp == null) return "!!$target!!"; | 
				
			|||
        return $temp; | 
				
			|||
        */ | 
				
			|||
        return "!!$target!!"; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
 | 
				
			|||
    public function Base64Encode($target) : string | 
				
			|||
    { | 
				
			|||
        return base64_encode($target) . "=="; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public function Base64Decode($target) | 
				
			|||
    { | 
				
			|||
        return base64_decode($target); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
 | 
				
			|||
    public function GetType($target): string | 
				
			|||
    { | 
				
			|||
        return gettype($target); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
 | 
				
			|||
    public function PrettyDate(?string $target): string | 
				
			|||
    { | 
				
			|||
        if($target == "" || $target == null) return "meow :3"; | 
				
			|||
 | 
				
			|||
        $temp = new DateTime( | 
				
			|||
            datetime: $target, | 
				
			|||
        ); | 
				
			|||
 | 
				
			|||
        return $temp->format("l d F Y"); | 
				
			|||
    } | 
				
			|||
    public function PrettyTime(?string $target): string | 
				
			|||
    { | 
				
			|||
        if($target == [] || $target == "" || $target == null) return "meow :3"; | 
				
			|||
 | 
				
			|||
        $temp = new DateTime( | 
				
			|||
            datetime: $target, | 
				
			|||
        ); | 
				
			|||
 | 
				
			|||
        return $temp->format("h:i:s A"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public function PrettyDateTime(?string $target): string | 
				
			|||
    { | 
				
			|||
        return $this->PrettyDate(target: $target) . " at " . $this->PrettyTime(target: $target); | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
@ -0,0 +1,130 @@ | 
				
			|||
<?php | 
				
			|||
 | 
				
			|||
namespace App\TwigExtensions; | 
				
			|||
 | 
				
			|||
use App\Configuration; | 
				
			|||
use App\Wrappers\APIWrapper; | 
				
			|||
use App\Wrappers\JWTWrapper; | 
				
			|||
use App\Wrappers\LFSWrapper; | 
				
			|||
use Ramsey\Uuid\Uuid; | 
				
			|||
use Twig\Environment; | 
				
			|||
use Twig\Error\LoaderError; | 
				
			|||
use Twig\Extension\AbstractExtension; | 
				
			|||
use Twig\TwigFunction; | 
				
			|||
 | 
				
			|||
class FunctionExtensions extends AbstractExtension | 
				
			|||
{ | 
				
			|||
    private $twig; | 
				
			|||
 | 
				
			|||
    public function __construct(Environment $twig) | 
				
			|||
    { | 
				
			|||
        $this->twig = $twig; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public function getFunctions() | 
				
			|||
    { | 
				
			|||
        return [ | 
				
			|||
            new TwigFunction('RandomUUID',              [$this, 'RandomUUID'],              ['is_safe' => ['html']]), | 
				
			|||
 | 
				
			|||
            new TwigFunction('BuildForms',              [$this, 'BuildForms'],              ['is_safe' => ['html']]), | 
				
			|||
            new TwigFunction('BuildModifiableField',    [$this, 'BuildModifiableField'],    ['is_safe' => ['html']]), | 
				
			|||
            new TwigFunction('BuildEnumField',          [$this, 'BuildEnumField'],          ['is_safe' => ['html']]), | 
				
			|||
 | 
				
			|||
            new TwigFunction('GetLFSFileURL',           [$this, 'GetLFSFileURL'],           ['is_safe' => ['html']]), | 
				
			|||
            new TwigFunction('GetConfigurationItem',    [$this, 'GetConfigurationItem'],    ['is_safe' => ['html']]), | 
				
			|||
 | 
				
			|||
            new TwigFunction('ParseForUserToken',       [$this, 'ParseForUserToken'],       ['is_safe' => ['html']]), | 
				
			|||
        ]; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public function RandomUUID(): string | 
				
			|||
    { | 
				
			|||
        return Uuid::uuid4()->toString(); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public function BuildForms(...$formDefinitions): string | 
				
			|||
    { | 
				
			|||
        $output = '<div>'; | 
				
			|||
        foreach ($formDefinitions as $formDefinition) | 
				
			|||
        { | 
				
			|||
            $output .= twig_include($this->twig, [], '/Partials/Form.html.twig', ['FormDefinition' => $formDefinition]); | 
				
			|||
        } | 
				
			|||
        return "$output</div>"; | 
				
			|||
    } | 
				
			|||
    public function BuildModifiableField(string $apiBase, string $id, string $value): string | 
				
			|||
    { | 
				
			|||
        $output = twig_include($this->twig, [], '/Partials/ModifiableField.html.twig', ['FieldData' => [ | 
				
			|||
            "APIBase"=>$apiBase, | 
				
			|||
            "ID"=>$id, | 
				
			|||
            "Value"=>$value, | 
				
			|||
        ]]); | 
				
			|||
        return "$output"; | 
				
			|||
    } | 
				
			|||
    public function BuildEnumField(string $apiBase, string $id, string $value, string $enumName): string | 
				
			|||
    { | 
				
			|||
        $enumData = APIWrapper::Get("/Enumerators/$enumName")->Payload["Cases"]; | 
				
			|||
        foreach($enumData as $case) | 
				
			|||
        { | 
				
			|||
            if($case["Value"] == $value) | 
				
			|||
            { | 
				
			|||
                $currentEnum = $case; | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        $output = twig_include($this->twig, [], '/Partials/EnumField.html.twig', ['FieldData' => [ | 
				
			|||
            "APIBase"=>$apiBase, | 
				
			|||
            "ID"=>$id, | 
				
			|||
            "Value"=>$value, | 
				
			|||
            "EnumName"=>$enumName, | 
				
			|||
            "CurrentEnum"=>$currentEnum, | 
				
			|||
            "EnumData"=>$enumData, | 
				
			|||
        ]]); | 
				
			|||
        return "$output"; | 
				
			|||
    } | 
				
			|||
    public function GetConfigurationItem(string...$nav): string | 
				
			|||
    { | 
				
			|||
        return Configuration::GetConfig(...$nav); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public function GetLFSFileURL(string $fileID): string | 
				
			|||
    { | 
				
			|||
        return LFSWrapper::GetFileURLByID($fileID); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
 | 
				
			|||
    public function ParseForUserToken(string $input) : string | 
				
			|||
    { | 
				
			|||
        $modes = implode(separator: "|", array:[ | 
				
			|||
            "USER", | 
				
			|||
            "VEHICLE", | 
				
			|||
        ]); | 
				
			|||
        $uuidRegex = "[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}"; | 
				
			|||
        $fullRegex = "/@({$modes}).{$uuidRegex}.{$uuidRegex}/"; | 
				
			|||
        preg_match_all($fullRegex, $input, $matches); | 
				
			|||
 | 
				
			|||
        foreach($matches[0] as $match) | 
				
			|||
        { | 
				
			|||
            $parts = explode(".", $match); | 
				
			|||
 | 
				
			|||
            $mode = substr(string: $parts[0], offset: 1); | 
				
			|||
            $tenantID = $parts[1]; | 
				
			|||
            $objectID = $parts[2]; | 
				
			|||
 | 
				
			|||
            switch ($mode) | 
				
			|||
            { | 
				
			|||
                case "USER": | 
				
			|||
                    $temp = twig_include($this->twig, [], '/Partials/UserButton.html.twig', [ | 
				
			|||
                        '_tenantID' => $tenantID, | 
				
			|||
                        '_userID' => $objectID, | 
				
			|||
                    ]); | 
				
			|||
                    break; | 
				
			|||
                case "VEHICLE": | 
				
			|||
                    break; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            $input = str_replace($match, $temp, $input); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        return $input; | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
@ -0,0 +1,45 @@ | 
				
			|||
<?php | 
				
			|||
 | 
				
			|||
namespace App\TwigExtensions; | 
				
			|||
 | 
				
			|||
use Twig\Extension\AbstractExtension; | 
				
			|||
use Twig\TwigFunction; | 
				
			|||
 | 
				
			|||
require_once dirname($_SERVER["DOCUMENT_ROOT"]) . "/vendor/autoload.php"; | 
				
			|||
 | 
				
			|||
class NavigationExtension extends AbstractExtension | 
				
			|||
{ | 
				
			|||
    public function getFunctions() | 
				
			|||
    { | 
				
			|||
        return [ | 
				
			|||
            new TwigFunction('RenderServiceLinks', [$this, 'RenderServiceLinks']), | 
				
			|||
            new TwigFunction('RenderBackButton', [$this, 'RenderBackButton']), | 
				
			|||
        ]; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public function RenderServiceLinks(): string | 
				
			|||
    { | 
				
			|||
        $serviceLinkHTML = ""; | 
				
			|||
 | 
				
			|||
        return "yrdy"; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public function RenderBackButton(string $title) : string | 
				
			|||
    { | 
				
			|||
        $html = ' | 
				
			|||
        <div style="display: flex; align-items: center;"> | 
				
			|||
            <div style="cursor: pointer; display: flex; align-items: center;" onclick="window.history.back();"> | 
				
			|||
                <i class="material-icons">arrow_back</i> | 
				
			|||
                <span>Back</span> | 
				
			|||
            </div> | 
				
			|||
            <h1 style="margin-left: 1em;">{{TITLE}}</h1> | 
				
			|||
            <div style="display: table; clear: both"></div> | 
				
			|||
        </div> | 
				
			|||
        '; | 
				
			|||
        return str_replace( | 
				
			|||
            search: "{{TITLE}}", | 
				
			|||
            replace: $title, | 
				
			|||
            subject: $html | 
				
			|||
        ); | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
@ -0,0 +1,36 @@ | 
				
			|||
<?php | 
				
			|||
 | 
				
			|||
namespace App\TwigExtensions; | 
				
			|||
 | 
				
			|||
use Twig\Extension\AbstractExtension; | 
				
			|||
use Twig\TwigFunction; | 
				
			|||
 | 
				
			|||
class StringManipulationExtension extends AbstractExtension | 
				
			|||
{ | 
				
			|||
    public function getFunctions() | 
				
			|||
    { | 
				
			|||
        return [ | 
				
			|||
            new TwigFunction('turn_mot_code_to_url', [$this, 'TurnMOTCodeToURL']), | 
				
			|||
        ]; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
 | 
				
			|||
    public function TurnMOTCodeToURL(string $motCode) : string | 
				
			|||
    { | 
				
			|||
        $groups = [ | 
				
			|||
            7=>"7-other-equipment" | 
				
			|||
        ]; | 
				
			|||
 | 
				
			|||
        $motCodeElements = explode(separator: '.', string: $motCode); | 
				
			|||
 | 
				
			|||
        $builder = "https://www.gov.uk/guidance/mot-inspection-manual-for-private-passenger-and-light-commercial-vehicles/"; | 
				
			|||
        $builder .= $groups[intval(value: $motCodeElements[0])]; | 
				
			|||
        $builder .= "#section-"; | 
				
			|||
        $builder .= "-"; | 
				
			|||
        $builder .= $motCodeElements[1]; | 
				
			|||
        $builder .= "-"; | 
				
			|||
        $builder .= $motCodeElements[2]; | 
				
			|||
 | 
				
			|||
        return $builder; | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
@ -0,0 +1,33 @@ | 
				
			|||
<?php | 
				
			|||
// src/Twig/AppExtension.php | 
				
			|||
namespace App\TwigTests; | 
				
			|||
 | 
				
			|||
use Twig\Extension\AbstractExtension; | 
				
			|||
use Twig\TwigTest; | 
				
			|||
 | 
				
			|||
class TwigTests extends AbstractExtension | 
				
			|||
{ | 
				
			|||
    public function getTests() | 
				
			|||
    { | 
				
			|||
        return array( | 
				
			|||
            new TwigTest('object', [$this, 'isObject']), | 
				
			|||
            new TwigTest('array', [$this, 'isArray']), | 
				
			|||
            new TwigTest('numeric', [$this, 'isNumeric']), | 
				
			|||
        ); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public function isObject($object) | 
				
			|||
    { | 
				
			|||
        return is_object($object); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public function isArray($value) | 
				
			|||
    { | 
				
			|||
        return is_array($value); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public function isNumeric($value) | 
				
			|||
    { | 
				
			|||
        return is_numeric($value); | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
@ -0,0 +1,18 @@ | 
				
			|||
<?php | 
				
			|||
 | 
				
			|||
namespace App\Wrappers; | 
				
			|||
 | 
				
			|||
use Algolia\AlgoliaSearch\Api\SearchClient; | 
				
			|||
use App\Configuration; | 
				
			|||
 | 
				
			|||
class AlgoliaInteractions | 
				
			|||
{ | 
				
			|||
    private SearchClient $SearchClient; | 
				
			|||
    public function __construct() | 
				
			|||
    { | 
				
			|||
        $this->SearchClient = SearchClient::create( | 
				
			|||
            appId: Configuration::GetConfig("Algolia", "AppID"), | 
				
			|||
            apiKey: Configuration::GetConfig("Algolia", "Keys", "SearchOnly"), | 
				
			|||
        ); | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
@ -0,0 +1,77 @@ | 
				
			|||
<?php | 
				
			|||
 | 
				
			|||
namespace App\Wrappers; | 
				
			|||
 | 
				
			|||
use App\API\Models\_DatabaseRecordPermissionSet; | 
				
			|||
use App\API\Models\EnumeratorDetails; | 
				
			|||
use App\API\Models\UserDetails; | 
				
			|||
use App\Configuration; | 
				
			|||
use Aura\SqlQuery\Common\DeleteInterface; | 
				
			|||
use Aura\SqlQuery\Common\InsertInterface; | 
				
			|||
use Aura\SqlQuery\Common\SelectInterface; | 
				
			|||
use Aura\SqlQuery\Common\UpdateInterface; | 
				
			|||
use Aura\SqlQuery\QueryFactory; | 
				
			|||
use PDO; | 
				
			|||
 | 
				
			|||
class DatabaseInteractions | 
				
			|||
{ | 
				
			|||
    private PDO $pdo; | 
				
			|||
 | 
				
			|||
    public function __construct() | 
				
			|||
    { | 
				
			|||
        $servername = Configuration::GetConfig('MariaDB', 'Host'); | 
				
			|||
        $database = Configuration::GetConfig('MariaDB', 'Database'); | 
				
			|||
        $username = Configuration::GetConfig('MariaDB', 'Username'); | 
				
			|||
        $password = Configuration::GetConfig('MariaDB', 'Password'); | 
				
			|||
 | 
				
			|||
        $this->pdo = new PDO( | 
				
			|||
            "mysql:host=$servername;dbname=$database", $username, $password | 
				
			|||
        ); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public function ScheduleRowForDeletion(string $tableName, string $id) | 
				
			|||
    { | 
				
			|||
 | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
    public function RunSelect(SelectInterface $queryBuilder): array | 
				
			|||
    { | 
				
			|||
        $sth = $this->pdo->prepare($queryBuilder->getStatement()); | 
				
			|||
        $sth->execute($queryBuilder->getBindValues()); | 
				
			|||
        $result = $sth->fetchAll(PDO::FETCH_ASSOC); | 
				
			|||
        return $result; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public function RunOneSelect(SelectInterface $queryBuilder): array | 
				
			|||
    { | 
				
			|||
        $sth = $this->pdo->prepare($queryBuilder->getStatement()); | 
				
			|||
        $sth->execute($queryBuilder->getBindValues()); | 
				
			|||
        $result = $sth->fetchAll(PDO::FETCH_ASSOC); | 
				
			|||
        if(sizeof($result) == 1) return $result[0]; | 
				
			|||
 | 
				
			|||
        echo "invalid single row db response"; | 
				
			|||
        die(); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public function UpdateSingleField(UpdateInterface $queryBuilder): void | 
				
			|||
    { | 
				
			|||
        $sth = $this->pdo->prepare($queryBuilder->getStatement()); | 
				
			|||
        $sth->execute($queryBuilder->getBindValues()); | 
				
			|||
        $result = $sth->fetchAll(PDO::FETCH_ASSOC); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public function RunInsert(InsertInterface $queryBuilder): bool | 
				
			|||
    { | 
				
			|||
        $sth = $this->pdo->prepare($queryBuilder->getStatement()); | 
				
			|||
        return $sth->execute($queryBuilder->getBindValues()); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public function RunDelete(DeleteInterface $queryBuilder): bool | 
				
			|||
    { | 
				
			|||
        $sth = $this->pdo->prepare($queryBuilder->getStatement()); | 
				
			|||
        return $sth->execute($queryBuilder->getBindValues()); | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
@ -0,0 +1,41 @@ | 
				
			|||
<?php | 
				
			|||
 | 
				
			|||
namespace App\Wrappers; | 
				
			|||
 | 
				
			|||
use App\Common\DateTimeHandling; | 
				
			|||
use App\Common\ErrorPayloadRendering; | 
				
			|||
use Aura\SqlQuery\Common\DeleteInterface; | 
				
			|||
use Aura\SqlQuery\QueryFactory; | 
				
			|||
use DateTime; | 
				
			|||
 | 
				
			|||
class SQLQueryBuilderWrapper | 
				
			|||
{ | 
				
			|||
    public static function SELECT(string $table) | 
				
			|||
    { | 
				
			|||
        $query_factory = new QueryFactory(db: 'mysql'); | 
				
			|||
        $query = $query_factory->newSelect() | 
				
			|||
            ->from("$table AS T") | 
				
			|||
            ; | 
				
			|||
        return $query | 
				
			|||
            ->cols([ | 
				
			|||
                "T.*", | 
				
			|||
            ]); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public static function SELECT_ONE(string $table, string $id) | 
				
			|||
    { | 
				
			|||
        return self::SELECT(table: $table) | 
				
			|||
            ->where(cond: "ID=:__id__") | 
				
			|||
            ->bindValue(name: "__id__", value: $id) | 
				
			|||
            ->limit(limit: 1) | 
				
			|||
            ; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    public static function SELECT_WITH_PARENT_ID(string $table, string $parentIDName, string $parentIDValue) | 
				
			|||
    { | 
				
			|||
        return self::SELECT(table: $table) | 
				
			|||
            ->where(cond: "$parentIDName=:__parent_id__") | 
				
			|||
            ->bindValue(name: "__parent_id__", value: $parentIDValue) | 
				
			|||
            ; | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
@ -0,0 +1,15 @@ | 
				
			|||
 | 
				
			|||
Algolia: | 
				
			|||
  AppID: ;ALGOLIA__APP_ID | 
				
			|||
  IndexName: FolkTunes_LIVE | 
				
			|||
  APIKeys: | 
				
			|||
    SearchOnly: ;ALGOLIA__API_SEARCH_KEY | 
				
			|||
    Write: ;ALGOLIA__API_WRITE_KEY | 
				
			|||
    Admin: ;ALGOLIA__API_ADMIN_KEY | 
				
			|||
 | 
				
			|||
MariaDB: | 
				
			|||
  Host: vps-gen-eng-001.infra.scorpia.network | 
				
			|||
  Port: 3306 | 
				
			|||
  Username: ;MARIADB__USERNAME | 
				
			|||
  Password: ;MARIADB__PASSWORD | 
				
			|||
  Database: FolkTuneFinder_Live | 
				
			|||
@ -0,0 +1,160 @@ | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# A | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# B | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# C | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# D | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# E | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# F | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# G | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# H | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# I | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# J | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# K | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# L | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# M | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# N | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# O | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# P | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# Q | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# R | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# S | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# T | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# U | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# V | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# W | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# X | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# Y | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# Z | 
				
			|||
################################################## | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
################################################## | 
				
			|||
# _ | 
				
			|||
################################################## | 
				
			|||
@ -0,0 +1,8 @@ | 
				
			|||
<?php | 
				
			|||
 | 
				
			|||
use App\Wrappers\TwigWrapper; | 
				
			|||
 | 
				
			|||
require_once __DIR__ . "/../vendor/autoload.php"; | 
				
			|||
 | 
				
			|||
TwigWrapper::AutoRenderTwig([ | 
				
			|||
]); | 
				
			|||
@ -0,0 +1,22 @@ | 
				
			|||
<?php | 
				
			|||
 | 
				
			|||
use App\Wrappers\DatabaseInteractions; | 
				
			|||
use App\Wrappers\SQLQueryBuilderWrapper; | 
				
			|||
use App\Wrappers\TwigWrapper; | 
				
			|||
 | 
				
			|||
$db = new DatabaseInteractions(); | 
				
			|||
 | 
				
			|||
$tuneDetails = $db->RunOneSelect( | 
				
			|||
    queryBuilder: SQLQueryBuilderWrapper::SELECT_ONE( | 
				
			|||
        table: 'Tunes', | 
				
			|||
        id: $_GET['tune-id'] | 
				
			|||
    ), | 
				
			|||
); | 
				
			|||
 | 
				
			|||
TwigWrapper::RenderTwig( | 
				
			|||
    target: "Pages/tune/uuid.html.twig", | 
				
			|||
    arguments: [ | 
				
			|||
        "TuneDetails"=>$tuneDetails, | 
				
			|||
    ] | 
				
			|||
); | 
				
			|||
 | 
				
			|||
@ -0,0 +1,37 @@ | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
.InnerContent { | 
				
			|||
    margin: 0 auto; | 
				
			|||
    text-align: center; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
input[type="text"] { | 
				
			|||
    width: 80%; | 
				
			|||
    padding: 10px; | 
				
			|||
    margin-bottom: 10px; | 
				
			|||
    border: 1px solid #ccc; | 
				
			|||
    border-radius: 4px; | 
				
			|||
} | 
				
			|||
#AlgoliaResults { | 
				
			|||
    margin-top: 20px; | 
				
			|||
    text-align: left; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.AlgoliaTuneHit { | 
				
			|||
    padding: 10px; | 
				
			|||
    border: 1px solid #ccc; | 
				
			|||
    border-radius: 4px; | 
				
			|||
    background-color: #fff; | 
				
			|||
    margin-bottom: 10px; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.AlgoliaTuneHit h2 { | 
				
			|||
 | 
				
			|||
} | 
				
			|||
.AlgoliaTuneHit .TuneTimeSignature { | 
				
			|||
 | 
				
			|||
} | 
				
			|||
.AlgoliaTuneHit .TuneKeySignature { | 
				
			|||
 | 
				
			|||
} | 
				
			|||
@ -0,0 +1,21 @@ | 
				
			|||
 | 
				
			|||
* { | 
				
			|||
    margin: 0; | 
				
			|||
    padding: 0; | 
				
			|||
} | 
				
			|||
body{ | 
				
			|||
    font-family: 'Atkinson Hyperlegible', sans-serif; | 
				
			|||
    font-style: normal; | 
				
			|||
    font-weight: 300; | 
				
			|||
    font-smoothing: antialiased; | 
				
			|||
 | 
				
			|||
    -webkit-font-smoothing: antialiased; | 
				
			|||
    -moz-osx-font-smoothing: grayscale; | 
				
			|||
 | 
				
			|||
    font-size: 1rem; | 
				
			|||
 | 
				
			|||
} | 
				
			|||
main { | 
				
			|||
    background: var(--colour--main--background); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
@ -0,0 +1,11 @@ | 
				
			|||
 | 
				
			|||
/* FONTS */ | 
				
			|||
@import url('https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible&display=swap'); | 
				
			|||
 | 
				
			|||
/* ICONS */ | 
				
			|||
@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css'); | 
				
			|||
 | 
				
			|||
/* ELEMENTS */ | 
				
			|||
@import "/Static/CSS/Elements/_General.css"; | 
				
			|||
@import "/Static/CSS/Elements/HomePage.css"; | 
				
			|||
 | 
				
			|||
@ -0,0 +1,2 @@ | 
				
			|||
:root { | 
				
			|||
} | 
				
			|||
@ -0,0 +1,30 @@ | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
function searchTunes(query) { | 
				
			|||
    const resultsDiv = document.getElementById("AlgoliaResults"); | 
				
			|||
 | 
				
			|||
    resultsDiv.innerHTML = ""; | 
				
			|||
 | 
				
			|||
    index.search(query).then(({ hits }) => { | 
				
			|||
        if (hits.length > 0) { | 
				
			|||
            hits.forEach(hit => { | 
				
			|||
                const tuneDiv = document.createElement("div"); | 
				
			|||
                tuneDiv.className = "AlgoliaTuneHit"; | 
				
			|||
                tuneDiv.innerHTML = ` | 
				
			|||
<h2><a href="/tune/${hit.objectID}">${hit.title}</a></h2> | 
				
			|||
<div class="TuneTimeSignature">time signature: ${hit.time_sig}</div> | 
				
			|||
<div class="TuneKeySignature">key signature: ${hit.key_sig}</div> | 
				
			|||
`;
 | 
				
			|||
                resultsDiv.appendChild(tuneDiv); | 
				
			|||
            }); | 
				
			|||
        } else { | 
				
			|||
            resultsDiv.innerHTML = "<p>No tunes found. Try a different search!</p>"; | 
				
			|||
        } | 
				
			|||
    }).catch(err => { | 
				
			|||
        console.error('Error searching Algolia:', err); | 
				
			|||
        resultsDiv.innerHTML = "<p>An error occurred. Please try again later.</p>"; | 
				
			|||
    }); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
 | 
				
			|||
| 
		 After Width: | Height: | Size: 1.2 KiB  | 
@ -0,0 +1,29 @@ | 
				
			|||
<?php | 
				
			|||
 | 
				
			|||
use App\Configuration; | 
				
			|||
use App\Enumerators\SessionElement; | 
				
			|||
use App\Wrappers\TwigWrapper; | 
				
			|||
 | 
				
			|||
require_once __DIR__ . "/../vendor/autoload.php"; | 
				
			|||
 | 
				
			|||
session_start(); | 
				
			|||
 | 
				
			|||
// Get the request URI and break it into segments | 
				
			|||
$requestUri = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH); | 
				
			|||
$requestElements = explode("/", trim($requestUri, "/")); | 
				
			|||
 | 
				
			|||
switch($requestElements[0]) | 
				
			|||
{ | 
				
			|||
    case "Static": | 
				
			|||
    case "favicon.ico": | 
				
			|||
        return false; | 
				
			|||
    case "": | 
				
			|||
        require_once __DIR__ . '/../Pages/index.php'; | 
				
			|||
        return true; | 
				
			|||
    case "tune": | 
				
			|||
        $_GET['tune-id'] = $requestElements[1]; | 
				
			|||
        require_once __DIR__ . '/../Pages/tune/uuid.php'; | 
				
			|||
        return true; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
return false; | 
				
			|||
@ -0,0 +1,26 @@ | 
				
			|||
<!DOCTYPE html> | 
				
			|||
<html lang="en"> | 
				
			|||
 | 
				
			|||
<head> | 
				
			|||
    <meta charset="UTF-8"> | 
				
			|||
    <link rel="shortcut icon" type="image/png" href="/favicon.png?type=png" /> | 
				
			|||
    <title>{{ "Folk Tunes"|translate }} | {% block PageTitle %}{% endblock %}</title> | 
				
			|||
    <link rel="stylesheet" href="/Static/CSS/Mapper.css"> | 
				
			|||
    <link rel="stylesheet" href="/Static/CSS/Themes/Light.css"> | 
				
			|||
</head> | 
				
			|||
 | 
				
			|||
<body> | 
				
			|||
 | 
				
			|||
<main> | 
				
			|||
 | 
				
			|||
    <div class="ErrorPageContainer"> | 
				
			|||
        <h1>{% block error_code %}{% endblock %}</h1> | 
				
			|||
        <h2>{% block error_message %}{% endblock %}</h2> | 
				
			|||
 | 
				
			|||
        <a class="Button" href="/">Back to Home</a> | 
				
			|||
    </div> | 
				
			|||
 | 
				
			|||
</main> | 
				
			|||
 | 
				
			|||
</body> | 
				
			|||
</html> | 
				
			|||
@ -0,0 +1,33 @@ | 
				
			|||
<!DOCTYPE html> | 
				
			|||
<html lang="en"> | 
				
			|||
 | 
				
			|||
<head> | 
				
			|||
    <meta charset="UTF-8"> | 
				
			|||
    <link rel="shortcut icon" type="image/png" href="/favicon.png?type=png" /> | 
				
			|||
    <title>{{ "Folk Tunes"|translate }} | {% block PageTitle %}{% endblock %}</title> | 
				
			|||
    <link rel="stylesheet" href="/Static/CSS/Mapper.css"> | 
				
			|||
    <link rel="stylesheet" href="/Static/CSS/Themes/Light.css"> | 
				
			|||
 | 
				
			|||
 | 
				
			|||
    <script src="https://cdn.jsdelivr.net/npm/algoliasearch/dist/algoliasearch-lite.umd.js"></script> | 
				
			|||
    <script> | 
				
			|||
        const client = algoliasearch('{{ _ALGOLIA_APP_ID_ }}', '{{ _ALGOLIA_SEARCH_ONLY_API_KEY_ }}'); | 
				
			|||
        const index = client.initIndex('{{ _ALGOLIA_INDEX_NAME_ }}'); | 
				
			|||
    </script> | 
				
			|||
    <script src="/Static/JS/AlgoliaInteractions.js"></script> | 
				
			|||
</head> | 
				
			|||
 | 
				
			|||
<body> | 
				
			|||
 | 
				
			|||
<div class="wrapper"> | 
				
			|||
    <div class="main_container"> | 
				
			|||
        <main> | 
				
			|||
            {% block content %}{% endblock %} | 
				
			|||
        </main> | 
				
			|||
    </div> | 
				
			|||
 | 
				
			|||
</div> | 
				
			|||
 | 
				
			|||
</body> | 
				
			|||
 | 
				
			|||
</html> | 
				
			|||
@ -0,0 +1,4 @@ | 
				
			|||
{% extends "/Bases/ErrorPage.html.twig" %} | 
				
			|||
 | 
				
			|||
{% block error_code %}404{% endblock %} | 
				
			|||
{% block error_message %}{{ "Page Not Found"|translate }}{% endblock %} | 
				
			|||
@ -0,0 +1,21 @@ | 
				
			|||
{% extends "/Bases/StandardWebPage.html.twig" %} | 
				
			|||
 | 
				
			|||
{% block content %} | 
				
			|||
    <div class="InnerContent"> | 
				
			|||
        <h1>Folk Tune Search</h1> | 
				
			|||
        <input type="text" id="SearchInput" placeholder="Search for a tune..."> | 
				
			|||
        <div id="AlgoliaResults"></div> | 
				
			|||
    </div> | 
				
			|||
 | 
				
			|||
    <script> | 
				
			|||
        // Event listener for real-time search | 
				
			|||
        document.getElementById("SearchInput").addEventListener("input", (event) => { | 
				
			|||
            const query = event.target.value; | 
				
			|||
            if (query.trim().length > 0) { | 
				
			|||
                searchTunes(query); | 
				
			|||
            } else { | 
				
			|||
                document.getElementById("AlgoliaResults").innerHTML = ""; | 
				
			|||
            } | 
				
			|||
        }); | 
				
			|||
    </script> | 
				
			|||
{% endblock %} | 
				
			|||
@ -0,0 +1,7 @@ | 
				
			|||
{% extends "/Bases/StandardWebPage.html.twig" %} | 
				
			|||
 | 
				
			|||
{% block content %} | 
				
			|||
    <div class="InnerContent"> | 
				
			|||
        <h1>{{ TuneDetails.Title }}</h1> | 
				
			|||
    </div> | 
				
			|||
{% endblock %} | 
				
			|||
@ -0,0 +1,32 @@ | 
				
			|||
{ | 
				
			|||
  "name": "darksparrow/folktunes", | 
				
			|||
  "description": "Folk Tune Search Engine", | 
				
			|||
  "type": "project", | 
				
			|||
  "license": "proprietary", | 
				
			|||
  "authors": [ | 
				
			|||
    { | 
				
			|||
      "name": "Cerys Lewis", | 
				
			|||
      "email": "cerys@darksparrow.uk", | 
				
			|||
      "role": "Lead Developer" | 
				
			|||
    } | 
				
			|||
  ], | 
				
			|||
  "support": { | 
				
			|||
    "email": "systems@darksparrow.uk" | 
				
			|||
  }, | 
				
			|||
  "autoload": { | 
				
			|||
    "psr-4": { | 
				
			|||
      "App\\": "App/" | 
				
			|||
    } | 
				
			|||
  }, | 
				
			|||
  "require": { | 
				
			|||
    "php": ">=8.2", | 
				
			|||
 | 
				
			|||
    "zircote/swagger-php": "~4.7.9", | 
				
			|||
 | 
				
			|||
    "twig/twig": "3.14.1.0", | 
				
			|||
 | 
				
			|||
    "algolia/algoliasearch-client-php": "*", | 
				
			|||
    "ext-pdo": "*", | 
				
			|||
    "aura/sqlquery": "2.8.1" | 
				
			|||
  } | 
				
			|||
} | 
				
			|||
								
									
										File diff suppressed because it is too large
									
								
							
						
					
					Loading…
					
					
				
		Reference in new issue