2023-02-01 12:50:00 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace Shimmie2;
|
|
|
|
|
2024-01-11 00:55:05 +00:00
|
|
|
use Symfony\Component\Console\Command\Command;
|
|
|
|
use Symfony\Component\Console\Input\{InputInterface,InputArgument};
|
|
|
|
use Symfony\Component\Console\Output\OutputInterface;
|
2023-02-01 12:50:00 +00:00
|
|
|
use GraphQL\GraphQL as GQL;
|
|
|
|
use GraphQL\Server\StandardServer;
|
|
|
|
use GraphQL\Error\DebugFlag;
|
2023-02-04 18:24:34 +00:00
|
|
|
use GraphQL\Type\Schema;
|
2023-02-01 12:50:00 +00:00
|
|
|
use GraphQL\Utils\SchemaPrinter;
|
|
|
|
|
2023-03-08 22:27:00 +00:00
|
|
|
#[\GQLA\InputObjectType]
|
|
|
|
class MetadataInput
|
|
|
|
{
|
|
|
|
public function __construct(
|
|
|
|
#[\GQLA\Field]
|
2024-02-25 19:33:34 +00:00
|
|
|
public string $key,
|
2023-03-08 22:27:00 +00:00
|
|
|
#[\GQLA\Field]
|
2024-02-25 19:33:34 +00:00
|
|
|
public string $value,
|
2023-03-08 22:27:00 +00:00
|
|
|
) {
|
|
|
|
}
|
|
|
|
|
2024-02-25 19:33:34 +00:00
|
|
|
/**
|
|
|
|
* @param array<MetadataInput> $metadata
|
|
|
|
*/
|
|
|
|
#[\GQLA\Mutation(args: ["post_id" => "Int!", "metadata" => "[MetadataInput!]!"])]
|
|
|
|
public static function update_post_metadata(int $post_id, array $metadata): Image
|
2023-03-08 22:27:00 +00:00
|
|
|
{
|
2024-02-20 00:22:25 +00:00
|
|
|
$image = Image::by_id_ex($post_id);
|
2024-02-25 19:33:34 +00:00
|
|
|
$pairs = [];
|
|
|
|
foreach ($metadata as $m) {
|
|
|
|
$pairs[$m->key] = $m->value;
|
2023-03-08 22:27:00 +00:00
|
|
|
}
|
2024-02-25 19:33:34 +00:00
|
|
|
send_event(new ImageInfoSetEvent($image, 0, $pairs));
|
2024-02-20 00:22:25 +00:00
|
|
|
return Image::by_id_ex($post_id);
|
2023-03-08 22:27:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-25 19:29:33 +00:00
|
|
|
function shmFieldResolver(
|
|
|
|
mixed $objectValue,
|
|
|
|
mixed $args,
|
|
|
|
mixed $context,
|
|
|
|
\GraphQL\Type\Definition\ResolveInfo $info
|
|
|
|
): mixed {
|
|
|
|
$fieldName = $info->fieldName;
|
|
|
|
$property = null;
|
|
|
|
|
|
|
|
if (is_array($objectValue)) {
|
|
|
|
if (isset($objectValue[$fieldName])) {
|
|
|
|
$property = $objectValue[$fieldName];
|
|
|
|
}
|
|
|
|
} elseif ($objectValue instanceof \ArrayAccess) {
|
|
|
|
if (isset($objectValue->{$fieldName})) {
|
|
|
|
$property = $objectValue->{$fieldName};
|
|
|
|
} elseif (isset($objectValue[$fieldName])) {
|
|
|
|
$property = $objectValue[$fieldName];
|
|
|
|
}
|
|
|
|
|
|
|
|
} elseif (is_object($objectValue)) {
|
|
|
|
if (isset($objectValue->{$fieldName})) {
|
|
|
|
$property = $objectValue->{$fieldName};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $property instanceof \Closure
|
|
|
|
? $property($objectValue, $args, $context, $info)
|
|
|
|
: $property;
|
|
|
|
}
|
|
|
|
|
2023-02-01 12:50:00 +00:00
|
|
|
class GraphQL extends Extension
|
|
|
|
{
|
2023-02-04 18:24:34 +00:00
|
|
|
public static function get_schema(): Schema
|
|
|
|
{
|
|
|
|
global $_tracer;
|
|
|
|
$_tracer->begin("Create Schema");
|
2023-02-05 15:56:06 +00:00
|
|
|
$schema = new \GQLA\Schema();
|
2023-02-04 18:24:34 +00:00
|
|
|
$_tracer->end(null);
|
|
|
|
return $schema;
|
|
|
|
}
|
|
|
|
|
2023-02-07 14:18:21 +00:00
|
|
|
private function cors(): void
|
|
|
|
{
|
|
|
|
global $config;
|
|
|
|
$pat = $config->get_string("graphql_cors_pattern");
|
|
|
|
|
|
|
|
if ($pat && isset($_SERVER['HTTP_ORIGIN'])) {
|
|
|
|
if (preg_match("#$pat#", $_SERVER['HTTP_ORIGIN'])) {
|
|
|
|
header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
|
|
|
|
header('Access-Control-Allow-Credentials: true');
|
|
|
|
header('Access-Control-Max-Age: 86400');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
|
|
|
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
|
|
|
|
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
|
|
|
|
}
|
|
|
|
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
|
|
|
|
header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
|
|
|
|
}
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-15 11:52:35 +00:00
|
|
|
public function onInitExt(InitExtEvent $event): void
|
2023-02-07 14:18:21 +00:00
|
|
|
{
|
|
|
|
global $config;
|
|
|
|
$config->set_default_string('graphql_cors_pattern', "");
|
|
|
|
$config->set_default_bool('graphql_debug', false);
|
|
|
|
}
|
|
|
|
|
2024-01-15 11:52:35 +00:00
|
|
|
public function onPageRequest(PageRequestEvent $event): void
|
2023-02-01 12:50:00 +00:00
|
|
|
{
|
2023-02-07 14:18:21 +00:00
|
|
|
global $config, $page;
|
2023-02-02 16:50:09 +00:00
|
|
|
if ($event->page_matches("graphql")) {
|
2023-02-07 14:18:21 +00:00
|
|
|
$this->cors();
|
2023-02-01 12:50:00 +00:00
|
|
|
$t1 = ftime();
|
|
|
|
$server = new StandardServer([
|
2023-02-04 18:24:34 +00:00
|
|
|
'schema' => $this->get_schema(),
|
2024-02-25 19:29:33 +00:00
|
|
|
'fieldResolver' => "\Shimmie2\shmFieldResolver",
|
2023-02-01 12:50:00 +00:00
|
|
|
]);
|
|
|
|
$t2 = ftime();
|
|
|
|
$resp = $server->executeRequest();
|
2024-01-20 20:48:47 +00:00
|
|
|
assert(!is_array($resp));
|
|
|
|
assert(is_a($resp, \GraphQL\Executor\ExecutionResult::class));
|
2023-02-07 14:18:21 +00:00
|
|
|
if ($config->get_bool("graphql_debug")) {
|
|
|
|
$debug = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::RETHROW_INTERNAL_EXCEPTIONS;
|
|
|
|
$body = $resp->toArray($debug);
|
|
|
|
} else {
|
|
|
|
$body = $resp->toArray();
|
|
|
|
}
|
2023-02-01 12:50:00 +00:00
|
|
|
$t3 = ftime();
|
|
|
|
$body['stats'] = get_debug_info_arr();
|
|
|
|
$body['stats']['graphql_schema_time'] = round($t2 - $t1, 2);
|
|
|
|
$body['stats']['graphql_execute_time'] = round($t3 - $t2, 2);
|
2023-03-08 22:27:00 +00:00
|
|
|
// sleep(1);
|
2023-02-01 12:50:00 +00:00
|
|
|
$page->set_mode(PageMode::DATA);
|
|
|
|
$page->set_mime("application/json");
|
2024-02-20 00:22:25 +00:00
|
|
|
$page->set_data(\Safe\json_encode($body, JSON_UNESCAPED_UNICODE));
|
2023-02-01 12:50:00 +00:00
|
|
|
}
|
2023-02-24 05:32:52 +00:00
|
|
|
if ($event->page_matches("graphql_upload")) {
|
|
|
|
$this->cors();
|
|
|
|
$page->set_mode(PageMode::DATA);
|
|
|
|
$page->set_mime("application/json");
|
2024-02-20 00:22:25 +00:00
|
|
|
$page->set_data(\Safe\json_encode(self::handle_uploads()));
|
2023-02-24 05:32:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-20 14:10:59 +00:00
|
|
|
/**
|
|
|
|
* @return array{error?:string,results?:array<array{error?:string,image_ids?:int[]}>}
|
|
|
|
*/
|
2023-02-24 05:32:52 +00:00
|
|
|
private static function handle_uploads(): array
|
|
|
|
{
|
|
|
|
global $user;
|
|
|
|
|
|
|
|
if (!$user->can(Permissions::CREATE_IMAGE)) {
|
|
|
|
return ["error" => "User cannot create posts"];
|
|
|
|
}
|
|
|
|
|
2024-02-25 19:33:34 +00:00
|
|
|
$metadata = only_strings($_POST);
|
2023-02-24 05:32:52 +00:00
|
|
|
|
|
|
|
$results = [];
|
2023-11-11 21:49:12 +00:00
|
|
|
for ($n = 0; $n < 100; $n++) {
|
2023-02-24 05:32:52 +00:00
|
|
|
if (empty($_POST["url$n"]) && empty($_FILES["data$n"])) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (isset($_FILES["data$n"]) && ($_FILES["data$n"]["size"] == 0 || $_FILES["data$n"]["error"] == UPLOAD_ERR_NO_FILE)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
try {
|
2024-02-25 19:33:34 +00:00
|
|
|
$results[] = ["image_ids" => self::handle_upload($n, $metadata)];
|
2024-08-31 16:05:18 +00:00
|
|
|
} catch (\Exception $e) {
|
2023-02-24 05:32:52 +00:00
|
|
|
$results[] = ["error" => $e->getMessage()];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ["results" => $results];
|
|
|
|
}
|
|
|
|
|
2024-01-20 14:10:59 +00:00
|
|
|
/**
|
2024-02-25 19:33:34 +00:00
|
|
|
* @param array<string, string> $metadata
|
2024-01-20 14:10:59 +00:00
|
|
|
* @return int[]
|
|
|
|
*/
|
2024-02-25 19:33:34 +00:00
|
|
|
private static function handle_upload(int $n, array $metadata): array
|
2023-02-24 05:32:52 +00:00
|
|
|
{
|
2024-01-20 14:10:59 +00:00
|
|
|
global $database;
|
2023-02-24 05:32:52 +00:00
|
|
|
if (!empty($_POST["url$n"])) {
|
2024-01-20 14:10:59 +00:00
|
|
|
throw new UploadException("URLs not handled yet");
|
2023-02-24 05:32:52 +00:00
|
|
|
} else {
|
|
|
|
$ec = $_FILES["data$n"]["error"];
|
2024-08-31 16:05:18 +00:00
|
|
|
switch ($ec) {
|
2023-02-24 05:32:52 +00:00
|
|
|
case UPLOAD_ERR_OK:
|
|
|
|
$tmpname = $_FILES["data$n"]["tmp_name"];
|
|
|
|
$filename = $_FILES["data$n"]["name"];
|
|
|
|
break;
|
|
|
|
case UPLOAD_ERR_INI_SIZE:
|
2024-01-20 14:10:59 +00:00
|
|
|
throw new UploadException("File larger than PHP can handle");
|
2023-02-24 05:32:52 +00:00
|
|
|
default:
|
2024-02-25 19:33:34 +00:00
|
|
|
throw new UploadException("Mystery error: ".var_export($ec, true));
|
2023-02-24 05:32:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-25 19:33:34 +00:00
|
|
|
$event = $database->with_savepoint(function () use ($tmpname, $filename, $n, $metadata) {
|
|
|
|
return send_event(new DataUploadEvent($tmpname, $filename, $n, $metadata));
|
2024-01-20 14:10:59 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return array_map(fn ($im) => $im->id, $event->images);
|
2023-02-01 12:50:00 +00:00
|
|
|
}
|
|
|
|
|
2024-01-15 11:52:35 +00:00
|
|
|
public function onCliGen(CliGenEvent $event): void
|
2023-02-01 12:50:00 +00:00
|
|
|
{
|
2024-01-11 00:55:05 +00:00
|
|
|
$event->app->register('graphql:query')
|
|
|
|
->addArgument('query', InputArgument::REQUIRED)
|
|
|
|
->setDescription('Run a GraphQL query')
|
|
|
|
->setCode(function (InputInterface $input, OutputInterface $output): int {
|
|
|
|
$query = $input->getArgument('query');
|
|
|
|
$t1 = ftime();
|
|
|
|
$schema = $this->get_schema();
|
|
|
|
$t2 = ftime();
|
|
|
|
$debug = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::RETHROW_INTERNAL_EXCEPTIONS;
|
2024-02-25 19:29:33 +00:00
|
|
|
$body = GQL::executeQuery($schema, $query, fieldResolver: "\Shimmie2\shmFieldResolver")->toArray($debug);
|
2024-01-11 00:55:05 +00:00
|
|
|
$t3 = ftime();
|
|
|
|
$body['stats'] = get_debug_info_arr();
|
|
|
|
$body['stats']['graphql_schema_time'] = round($t2 - $t1, 2);
|
|
|
|
$body['stats']['graphql_execute_time'] = round($t3 - $t2, 2);
|
2024-02-20 00:22:25 +00:00
|
|
|
echo \Safe\json_encode($body, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
2024-01-11 00:55:05 +00:00
|
|
|
return Command::SUCCESS;
|
|
|
|
});
|
|
|
|
$event->app->register('graphql:schema')
|
|
|
|
->setDescription('Print out the GraphQL schema')
|
|
|
|
->setCode(function (InputInterface $input, OutputInterface $output): int {
|
|
|
|
$schema = $this->get_schema();
|
|
|
|
echo(SchemaPrinter::doPrint($schema));
|
|
|
|
return Command::SUCCESS;
|
|
|
|
});
|
2023-02-01 12:50:00 +00:00
|
|
|
}
|
|
|
|
}
|