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