v1.0 Initial commit of project
This commit is contained in:
748
pub/api/classes/API.php
Normal file
748
pub/api/classes/API.php
Normal file
@@ -0,0 +1,748 @@
|
||||
<?php
|
||||
|
||||
namespace api\classes;
|
||||
class API
|
||||
{
|
||||
public $conn;
|
||||
|
||||
# The user uuid that requested the API
|
||||
protected $user_uuid;
|
||||
|
||||
# $user_type is either an API call (api) or an call from the frontend (frontend)
|
||||
protected $user_type;
|
||||
|
||||
# Either GET POST PUT or DELETE
|
||||
public $request_method;
|
||||
|
||||
protected $content_type;
|
||||
|
||||
# The original posted data to the API, this data is NOT sanitized and validated, never use this data for queries!
|
||||
public $postedData;
|
||||
|
||||
# The validated and sanitized data can be uses for the API actions
|
||||
public $data;
|
||||
|
||||
# The permission of the user to check if the action is allowed.
|
||||
public $permissions;
|
||||
|
||||
# The return url that the frontend request will forward to after the api call is done. if set to false it will only output
|
||||
# the json response with an http code. API calls always respond with json. $return_url can be set to supply the form with an input
|
||||
# with the name _return and value of the url to return to.
|
||||
public $return_url;
|
||||
|
||||
# Required fields & optional fields set by the API actions. This is an array like:
|
||||
# Example:
|
||||
# 'user_uuid' => ['type' => 'string', 'min' => 5, 'max' => 36],
|
||||
# 'user_enabled' => ['type' => 'int', 'min' => 0, 'max' => 99],
|
||||
# 'user_active' => ['type' => 'enum', 'values' => ['active', 'inactive', 'banned']],
|
||||
# 'user_email' => ['type' => 'string', 'format' => 'email'],
|
||||
private $requiredFields = [];
|
||||
private $optionalFields = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
# Setup Database connection
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/bin/php/db_connect.php';
|
||||
$this->conn = $GLOBALS['conn'];
|
||||
|
||||
if (!empty($_SESSION['user']['user_uuid'])) {
|
||||
$this->InitUserTypeFrontend();
|
||||
} else {
|
||||
$this->InitUserTypeAPI();
|
||||
}
|
||||
|
||||
$this->return_url = $this->setReturnUrl();
|
||||
|
||||
# user_uuid will be set if the user is authorized
|
||||
if (!$this->user_uuid) {
|
||||
$this->apiOutput(401, ['error' => 'Unauthorized']);
|
||||
}
|
||||
|
||||
# Only allow POST, GET, PUT and DELETE
|
||||
if (!$this->checkRequestMethod()) {
|
||||
$this->apiOutput(405, ['error' => 'Method not allowed']);
|
||||
}
|
||||
|
||||
|
||||
if (!$this->checkContentType()) {
|
||||
$this->apiOutput(400, ['error' => 'Unsupported Content-Type.']);
|
||||
}
|
||||
|
||||
if ($this->content_type === 'application/json') {
|
||||
if (!$this->checkJson()) {
|
||||
$this->apiOutput(400, ['error' => 'Invalid JSON format']);
|
||||
}
|
||||
}
|
||||
|
||||
// Disable builder input for non-GET requests to prevent potential SQL injection vulnerabilities.
|
||||
// Also disable the builder for users with the 'frontend' user type as an extra security measure.
|
||||
// The builder should only be active for API users making GET requests.
|
||||
// When building a frontend page, you can still programmatically construct a builder array
|
||||
// and set it via $_GET like so after the API class creation:
|
||||
// $_GET['builder'] = [1 => ['where' => [0 => 'permission_uuid', 1 => $permission_uuid]]];
|
||||
if ($this->request_method !== 'GET' || $this->user_type === 'frontend') {
|
||||
$this->disableBuilder();
|
||||
}
|
||||
|
||||
# This converts the posted data if needed to an PHP array
|
||||
$this->postedData = $this->processPostedData();
|
||||
|
||||
}
|
||||
|
||||
private function InitUserTypeFrontend()
|
||||
{
|
||||
$this->user_uuid = $_SESSION['user']['user_uuid'];
|
||||
$this->user_type = 'frontend';
|
||||
|
||||
# Load the locale for the user, this is used for the return message in the frontend and other globalFunctions.
|
||||
include_once $_SERVER['DOCUMENT_ROOT'] . '/bin/php/Functions/globalFunctions.php';
|
||||
$locale = getPreferredLocale();
|
||||
global $translations;
|
||||
$translations = require $_SERVER['DOCUMENT_ROOT'] . "/bin/locales/{$locale}.php";
|
||||
}
|
||||
|
||||
protected function RecursiveDeleteFolder($folderPath): bool
|
||||
{
|
||||
// Check if the folder exists
|
||||
if (!is_dir($folderPath)) {
|
||||
$this->apiOutput(500, ['error' => 'directory not found: ' . $folderPath]);
|
||||
}
|
||||
|
||||
// Get all files and folders in the directory
|
||||
$items = array_diff(scandir($folderPath), array('.', '..'));
|
||||
|
||||
// Loop through each item
|
||||
foreach ($items as $item) {
|
||||
|
||||
$itemPath = $folderPath . DIRECTORY_SEPARATOR . $item;
|
||||
|
||||
if (is_dir($itemPath)) {
|
||||
if (!$this->RecursiveDeleteFolder($itemPath)) {
|
||||
$this->apiOutput(500, ['error' => "Unable to remove directory: $itemPath"]);
|
||||
}
|
||||
} else {
|
||||
if (!unlink($itemPath)) {
|
||||
$this->apiOutput(500, ['error' => "Unable to delete file: $itemPath"]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Remove the main folder after all contents are gone
|
||||
if (!rmdir($folderPath)) {
|
||||
$this->apiOutput(500, ['error' => "Unable to remove directory: $folderPath"]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function InitUserTypeAPI()
|
||||
{
|
||||
$this->user_type = 'api';
|
||||
|
||||
$headers = getallheaders();
|
||||
$authHeader = $headers['Authorization'] ?? '';
|
||||
|
||||
if (!preg_match('/^Bearer\s+(.+)$/', $authHeader, $matches)) {
|
||||
$this->apiOutput(401, ['error' => 'Unauthorized, missing bearer token.']);
|
||||
}
|
||||
|
||||
$bearerToken = trim($matches[1]);
|
||||
|
||||
if (!preg_match('/^[a-f0-9\-]{36}\.[a-f0-9]{64}$/i', $bearerToken)) {
|
||||
$this->apiOutput(401, ['error' => 'Unauthorized, invalid token format.']);
|
||||
}
|
||||
|
||||
[$tokenId, $tokenSecret] = explode('.', $bearerToken, 2);
|
||||
|
||||
$this->user_uuid = $this->validateToken($tokenId, $tokenSecret);
|
||||
|
||||
if ($this->user_uuid === false) {
|
||||
$this->apiOutput(401, ['error' => 'Unauthorized, invalid or expired token.']);
|
||||
}
|
||||
|
||||
$api_token_last_used_timestamp = time();
|
||||
$stmt = $this->conn->prepare("UPDATE vc_api_tokens SET api_token_last_used_timestamp = ? WHERE api_token_uuid = ?");
|
||||
$stmt->bind_param("is", $api_token_last_used_timestamp, $tokenId);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
public function validateSingleData($value, $rules)
|
||||
{
|
||||
if (!$this->validateField($value, $rules)) {
|
||||
$this->apiOutput(400, ['error' => "Invalid value: $value"]);
|
||||
}
|
||||
|
||||
return $this->sanitizeData($value, $rules['type']);
|
||||
}
|
||||
|
||||
public function validateData($requiredFields, $optionalFields = [])
|
||||
{
|
||||
$inputData = $this->postedData;
|
||||
|
||||
$this->requiredFields = $requiredFields;
|
||||
$this->optionalFields = $optionalFields;
|
||||
$sanitizedData = [];
|
||||
|
||||
foreach ($this->requiredFields as $field => $rules) {
|
||||
|
||||
if (!array_key_exists($field, $inputData)) {
|
||||
$this->apiOutput(400, ['error' => "Missing required field: $field"]);
|
||||
}
|
||||
|
||||
$value = $inputData[$field];
|
||||
|
||||
if (!$this->validateField($value, $rules)) {
|
||||
$this->apiOutput(400, ['error' => "Invalid value for $field"]);
|
||||
}
|
||||
|
||||
$sanitizedData[$field] = $this->sanitizeData($value, $rules['type']);
|
||||
}
|
||||
|
||||
|
||||
// Check optional fields
|
||||
foreach ($this->optionalFields as $field => $rules) {
|
||||
if (isset($inputData[$field])) {
|
||||
$value = $inputData[$field];
|
||||
|
||||
if (!$this->validateField($value, $rules)) {
|
||||
$this->apiOutput(422, ['error' => "Invalid value for optional field: $field"]);
|
||||
}
|
||||
|
||||
$sanitizedData[$field] = $this->sanitizeData($value, $rules['type']);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_GET['builder']) && is_array($_GET['builder'])) {
|
||||
foreach ($_GET['builder'] as $builder) {
|
||||
if (!isset($builder['where']) || count($builder['where']) !== 2) {
|
||||
continue; // skip invalid builders
|
||||
}
|
||||
|
||||
$field = $builder['where'][0];
|
||||
$value = $builder['where'][1];
|
||||
|
||||
// Check if the field is allowed (in required or optional)
|
||||
$rules = $requiredFields[$field] ?? $optionalFields[$field] ?? null;
|
||||
if (!$rules) {
|
||||
$this->apiOutput(403, ['error' => "Field not allowed in query: $field"]);
|
||||
}
|
||||
|
||||
// Validate and sanitize
|
||||
if (!$this->validateField($value, $rules)) {
|
||||
$this->apiOutput(422, ['error' => "Invalid value for builder field: $field"]);
|
||||
}
|
||||
|
||||
$sanitizedData[$field] = $this->sanitizeData($value, $rules['type']);
|
||||
}
|
||||
}
|
||||
|
||||
$this->data = $sanitizedData;
|
||||
}
|
||||
|
||||
private function isValidLength($value, $rules)
|
||||
{
|
||||
$length = strlen($value);
|
||||
if (isset($rules['min']) && $length < $rules['min']) return false;
|
||||
if (isset($rules['max']) && $length > $rules['max']) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private function isValidNumberRange($value, $rules)
|
||||
{
|
||||
if (isset($rules['min']) && $value < $rules['min']) return false;
|
||||
if (isset($rules['max']) && $value > $rules['max']) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private function validateField($value, $rules)
|
||||
{
|
||||
switch ($rules['type']) {
|
||||
case 'string':
|
||||
if (!is_string($value)) return false;
|
||||
return $this->isValidLength($value, $rules);
|
||||
case 'slugify':
|
||||
if (!is_string($value) || !preg_match('/^[a-z0-9]+(-[a-z0-9]+)*$/', $value)) return false;
|
||||
return $this->isValidLength($value, $rules);
|
||||
|
||||
case 'boolean':
|
||||
if (is_bool($value)) return true;
|
||||
|
||||
if (is_string($value)) {
|
||||
$value = strtolower($value);
|
||||
return $value === 'true' || $value === 'false' || $value === '1' || $value === '0';
|
||||
}
|
||||
|
||||
if (is_int($value)) {
|
||||
return $value === 1 || $value === 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
case 'email':
|
||||
if (!is_string($value)) return false;
|
||||
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) return false;
|
||||
return $this->isValidLength($value, $rules);
|
||||
|
||||
case 'password':
|
||||
if (!is_string($value)) return false;
|
||||
return $this->isValidLength($value, $rules);
|
||||
|
||||
case 'html':
|
||||
if (!is_string($value)) return false;
|
||||
return $this->isValidLength($value, $rules);
|
||||
|
||||
case 'int':
|
||||
if (!is_int($value) && !ctype_digit($value)) return false;
|
||||
$value = (int)$value;
|
||||
return $this->isValidNumberRange($value, $rules);
|
||||
|
||||
case 'float':
|
||||
// Accept floats or numeric strings
|
||||
if (!is_float($value) && !is_numeric($value)) {
|
||||
return false;
|
||||
}
|
||||
$value = (float)$value;
|
||||
return $this->isValidNumberRange($value, $rules);
|
||||
|
||||
case 'timestamp':
|
||||
if (is_null($value)) return true;
|
||||
if (!is_int($value) && !ctype_digit($value)) return false;
|
||||
$value = (int)$value;
|
||||
if ($value < 0) return false;
|
||||
$min = $rules['min'] ?? 1;
|
||||
$max = $rules['max'] ?? 4102444800;
|
||||
return $value >= $min && $value <= $max;
|
||||
|
||||
case 'enum':
|
||||
if (!isset($rules['values']) || !in_array($value, $rules['values'], true)) return false;
|
||||
return true;
|
||||
|
||||
case 'uuid':
|
||||
if (!is_string($value)) return false;
|
||||
return preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $value);
|
||||
|
||||
case 'base64':
|
||||
if (!is_string($value)) return false;
|
||||
return base64_encode(base64_decode($value, true)) === $value;
|
||||
|
||||
case 'uuid':
|
||||
if (!is_string($value)) return false;
|
||||
return preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $value);
|
||||
|
||||
case 'json':
|
||||
if (!is_string($value)) return false;
|
||||
json_decode($value);
|
||||
return json_last_error() === JSON_ERROR_NONE;
|
||||
|
||||
case 'array':
|
||||
if (!is_array($value)) return false;
|
||||
return $value;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function sanitizeData($value, $type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'string':
|
||||
case 'enum':
|
||||
case 'uuid':
|
||||
// Remove HTML tags and encode special characters
|
||||
return htmlspecialchars(strip_tags($value), ENT_QUOTES, 'UTF-8');
|
||||
|
||||
case 'email':
|
||||
// Remove illegal characters from email address
|
||||
return filter_var($value, FILTER_SANITIZE_EMAIL);
|
||||
|
||||
case 'password':
|
||||
// Passwords may contain special characters; just trim spaces
|
||||
return trim($value);
|
||||
|
||||
case 'html':
|
||||
// Allow safe HTML, you can customize allowed tags
|
||||
return strip_tags($value, '<b><i><u><strong><em><p><br>');
|
||||
|
||||
case 'int':
|
||||
// Remove anything that's not a number
|
||||
return filter_var($value, FILTER_SANITIZE_NUMBER_INT);
|
||||
|
||||
case 'base64':
|
||||
// Only allow base64 valid characters
|
||||
return preg_replace('/[^a-zA-Z0-9\/\+=]/', '', $value);
|
||||
|
||||
case 'boolean':
|
||||
if (is_string($value)) {
|
||||
$value = strtolower(trim($value));
|
||||
return in_array($value, ['true', '1'], true) ? true : false;
|
||||
}
|
||||
|
||||
return (bool)$value;
|
||||
|
||||
default:
|
||||
// Return as-is if unknown type
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function checkPermissions($permission_name, $accessRightsRequired, $returnBoolean = false)
|
||||
{
|
||||
$accessLevels = [
|
||||
'NA' => 0, // No Access
|
||||
'RO' => 1, // Read Only
|
||||
'RW' => 2, // Read Write
|
||||
];
|
||||
$query = "SELECT
|
||||
vc_permissions.permission_name,
|
||||
vc_user_group_permissions_portal.permission_value
|
||||
FROM vc_user_group_permissions_portal
|
||||
INNER JOIN vc_permissions ON vc_user_group_permissions_portal.permission_uuid =vc_permissions.permission_uuid
|
||||
INNER JOIN vc_users ON vc_user_group_permissions_portal.user_group_uuid = vc_users.user_group_uuid
|
||||
WHERE user_uuid = ? AND permission_name = ?";
|
||||
|
||||
|
||||
$stmt = $this->conn->prepare($query);
|
||||
$stmt->bind_param("ss", $this->user_uuid, $permission_name);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result()->fetch_assoc();
|
||||
|
||||
if (!$result) {
|
||||
if ($returnBoolean) {
|
||||
return false;
|
||||
}
|
||||
$this->apiOutput(500, ['error' => 'Did not find permission required']);
|
||||
}
|
||||
|
||||
$userAccess = $result['permission_value'];
|
||||
|
||||
if (!isset($accessLevels[$userAccess]) || !isset($accessLevels[$accessRightsRequired])) {
|
||||
if ($returnBoolean) {
|
||||
return false;
|
||||
}
|
||||
$this->apiOutput(500, ['error' => 'Server error.']);
|
||||
}
|
||||
|
||||
// Compare user's access level with the required access level
|
||||
if ($accessLevels[$userAccess] < $accessLevels[$accessRightsRequired]) {
|
||||
if ($returnBoolean) {
|
||||
return false;
|
||||
}
|
||||
$this->apiOutput(403, ['error' => 'Permission denied. You do not have the required access level.']);
|
||||
}
|
||||
|
||||
if ($returnBoolean) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected function setReturnUrl()
|
||||
{
|
||||
if ($this->user_type !== 'frontend') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
if ($method === 'POST' && isset($_POST['_return'])) {
|
||||
return $_POST['_return'];
|
||||
}
|
||||
|
||||
if ($method === 'PUT') {
|
||||
parse_str(file_get_contents("php://input"), $putData);
|
||||
if (isset($putData['_return'])) {
|
||||
return $putData['_return'];
|
||||
}
|
||||
}
|
||||
if ($method === 'GET') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $_SERVER['HTTP_REFERER'];
|
||||
}
|
||||
|
||||
protected function checkRequestMethod()
|
||||
{
|
||||
$allowedMethods = ['GET', 'POST', 'PUT', 'DELETE'];
|
||||
$method = $_SERVER['REQUEST_METHOD'] ?? '';
|
||||
|
||||
if (!in_array($method, $allowedMethods)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
# Since browser doesnt allow DELETE or PUTs from the frontend forms (apart from some javascript/ajax fuckery)
|
||||
# we need to check the _method POST value.
|
||||
if ($this->user_type === 'frontend' && $method === 'POST' && isset($_POST['_method'])) {
|
||||
$overrideMethod = strtoupper($_POST['_method']);
|
||||
|
||||
if (in_array($overrideMethod, ['PUT', 'DELETE'])) {
|
||||
$this->request_method = $overrideMethod;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->request_method = $method;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function checkJson()
|
||||
{
|
||||
$rawInput = file_get_contents('php://input');
|
||||
if (empty($rawInput)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
json_decode($rawInput, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function processPostedData()
|
||||
{
|
||||
if ($this->user_type === 'api') {
|
||||
return json_decode(file_get_contents("php://input"), true);
|
||||
}
|
||||
|
||||
switch ($this->request_method) {
|
||||
case 'GET':
|
||||
return $_GET;
|
||||
case 'POST':
|
||||
return $_POST;
|
||||
case 'PUT':
|
||||
case 'DELETE':
|
||||
# When an image is uploaded from the front end the data needs to be specified its in $_POST and not $_FILES
|
||||
if ($this->content_type === 'multipart/form-data') {
|
||||
return $_POST;
|
||||
} else {
|
||||
parse_str(file_get_contents("php://input"), $data);
|
||||
return $data;
|
||||
}
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
protected function validateToken(string $tokenId, string $tokenSecret)
|
||||
{
|
||||
$stmt = $this->conn->prepare("SELECT user_uuid, api_token FROM vc_api_tokens WHERE api_token_uuid = ? AND api_token_expiration_timestamp > UNIX_TIMESTAMP()");
|
||||
$stmt->bind_param("s", $tokenId);
|
||||
$stmt->execute();
|
||||
|
||||
$row = $stmt->get_result()->fetch_assoc();
|
||||
|
||||
if (!$row) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!password_verify($tokenSecret, $row['api_token'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $row['user_uuid'];
|
||||
}
|
||||
|
||||
|
||||
protected function checkContentType()
|
||||
{
|
||||
# api will need to post with an application/json type Content type.
|
||||
# frontend will post with application/x-www-form-urlencoded content type but also is capable of application/json
|
||||
# frontend can also post multipart/form-data
|
||||
# GET requests dont have an content type
|
||||
|
||||
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
|
||||
|
||||
if ($this->request_method === 'GET') {
|
||||
$this->content_type = '';
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->user_type === 'api') {
|
||||
$this->content_type = 'application/json';
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strpos($contentType, 'application/json') !== false) {
|
||||
$this->content_type = 'application/json';
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strpos($contentType, 'application/x-www-form-urlencoded') !== false) {
|
||||
$this->content_type = 'application/x-www-form-urlencoded';
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strpos($contentType, 'multipart/form-data') !== false) {
|
||||
$this->content_type = 'multipart/form-data';
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getUserUuid()
|
||||
{
|
||||
return $this->user_uuid;
|
||||
}
|
||||
|
||||
public function apiOutput($code = 200, $data = [], $frontendMessage = false)
|
||||
{
|
||||
if ($this->user_type === 'api') {
|
||||
http_response_code($code);
|
||||
header('Content-Type: application/json');
|
||||
if ($code === 200) {
|
||||
echo json_encode(reset($data));
|
||||
} else {
|
||||
echo json_encode($data);
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
if ($this->user_type === 'frontend') {
|
||||
if (in_array($this->request_method, ['POST', 'PUT', 'DELETE'])) {
|
||||
http_response_code($code);
|
||||
|
||||
if ($this->return_url) { # sometimes the PUT doesnt need an return or response set (Think of js actions to api from frontend)
|
||||
$_SESSION['response'] = json_encode($data);
|
||||
|
||||
# When a request is successfull the api will recieve the data, the frontend needs a friendly message
|
||||
if ($frontendMessage) {
|
||||
$_SESSION['response'] = json_encode([key($data) => __($frontendMessage)]);
|
||||
}
|
||||
|
||||
header('Location: ' . $this->return_url);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($data);
|
||||
exit;
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
public function prepareStatement($query)
|
||||
{
|
||||
// Enable MySQLi to throw exceptions on errors
|
||||
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
|
||||
|
||||
try {
|
||||
$stmt = $this->conn->prepare($query);
|
||||
|
||||
} catch (mysqli_sql_exception $e) {
|
||||
// If an error occurs during prepare, catch it and return a proper response
|
||||
$this->apiOutput(500, ['error' => 'Database error: ' . $e->getMessage()]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $stmt;
|
||||
|
||||
}
|
||||
|
||||
public function executeStatement($stmt)
|
||||
{
|
||||
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
|
||||
|
||||
try {
|
||||
$stmt->execute();
|
||||
return true;
|
||||
} catch (mysqli_sql_exception $e) {
|
||||
if ($e->getCode() === 1451) {
|
||||
$this->apiOutput(409, ['error' => 'Cannot delete record: dependent data exists.']);
|
||||
} else {
|
||||
$this->apiOutput(500, ['error' => 'Database error: ' . $e->getMessage()]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function isSuperuser()
|
||||
{
|
||||
$query = "SELECT * FROM vc_users WHERE vc_users.user_uuid = ?";
|
||||
$stmt = $this->prepareStatement($query);
|
||||
$stmt->bind_param('s', $this->user_uuid);
|
||||
$this->executeStatement($stmt);
|
||||
$result = $stmt->get_result();
|
||||
$user_data = $result->fetch_assoc();
|
||||
if ($user_data['user_email'] == 'superuser') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected function buildDynamicQuery(string $tableName): array
|
||||
{
|
||||
$baseQuery = "SELECT * FROM " . $tableName;
|
||||
$whereClauses = [];
|
||||
$types = '';
|
||||
$values = [];
|
||||
|
||||
if (!isset($_GET['builder']) || !is_array($_GET['builder'])) {
|
||||
return [$baseQuery, $types, $values];
|
||||
}
|
||||
|
||||
foreach ($_GET['builder'] as $builder) {
|
||||
if (!isset($builder['where']) || !is_array($builder['where']) || count($builder['where']) !== 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$column = $builder['where'][0];
|
||||
$value = $builder['where'][1];
|
||||
|
||||
$whereClauses[] = "$column = ?";
|
||||
$types .= 's';
|
||||
$values[] = $value;
|
||||
}
|
||||
|
||||
if (!empty($whereClauses)) {
|
||||
$baseQuery .= " WHERE " . implode(" AND ", $whereClauses);
|
||||
}
|
||||
|
||||
return [$baseQuery, $types, $values];
|
||||
}
|
||||
|
||||
protected function generalGetFunction($query, $types, $params, $returnBoolean, $itemName)
|
||||
{
|
||||
$stmt = $this->prepareStatement($query);
|
||||
|
||||
if (!empty($params)) {
|
||||
$stmt->bind_param($types, ...$params);
|
||||
}
|
||||
|
||||
$this->executeStatement($stmt);
|
||||
$result = $stmt->get_result();
|
||||
|
||||
if ($result->num_rows === 0) {
|
||||
if (!$returnBoolean) {
|
||||
$this->apiOutput(404, ['error' => $itemName . ' not found.']);
|
||||
}
|
||||
}
|
||||
|
||||
$tokens = [];
|
||||
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$tokens[] = $row;
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
public function disableBuilder(): void
|
||||
{
|
||||
if (isset($_GET['builder'])) {
|
||||
unset($_GET['builder']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user