errorHandler = $errorHandler; } /** * Validate album creation input * @param array $input Input data * @return array|null Returns error response if validation fails, null if valid */ public function validateAlbumCreation($input) { // Check required fields $error = $this->errorHandler->validateRequiredFields($input, ['title']); if ($error) { return $error; } // Validate title $title = trim($input['title']); $error = $this->errorHandler->validateStringLength($title, 'title', 1, 255); if ($error) { return $error; } // Validate description if provided if (isset($input['description'])) { $description = trim($input['description']); $error = $this->errorHandler->validateStringLength($description, 'description', 0, 1000); if ($error) { return $error; } } return null; } /** * Validate album update input * @param array $input Input data * @return array|null Returns error response if validation fails, null if valid */ public function validateAlbumUpdate($input) { // At least one field must be provided $allowedFields = ['title', 'description', 'is_rss_enabled']; $hasValidField = false; foreach ($allowedFields as $field) { if (isset($input[$field])) { $hasValidField = true; break; } } if (!$hasValidField) { return $this->errorHandler->createErrorResponse( 'At least one field must be provided for update: '.implode(', ', $allowedFields), 'INVALID_INPUT', ['allowed_fields' => $allowedFields] ); } // Validate title if provided if (isset($input['title'])) { $title = trim($input['title']); if (empty($title)) { return $this->errorHandler->createErrorResponse( 'Album title cannot be empty', 'VALIDATION_FAILED', ['field' => 'title'] ); } $error = $this->errorHandler->validateStringLength($title, 'title', 1, 255); if ($error) { return $error; } } // Validate description if provided if (isset($input['description'])) { $description = trim($input['description']); $error = $this->errorHandler->validateStringLength($description, 'description', 0, 1000); if ($error) { return $error; } } // Validate is_rss_enabled if provided if (isset($input['is_rss_enabled'])) { if (!is_bool($input['is_rss_enabled']) && !in_array($input['is_rss_enabled'], [0, 1, '0', '1', 'true', 'false'], true)) { return $this->errorHandler->createErrorResponse( 'is_rss_enabled must be a boolean value', 'VALIDATION_FAILED', ['field' => 'is_rss_enabled', 'value' => $input['is_rss_enabled']] ); } } return null; } /** * Validate tag creation input * @param array $input Input data * @return array|null Returns error response if validation fails, null if valid */ public function validateTagCreation($input) { // Check required fields $error = $this->errorHandler->validateRequiredFields($input, ['name']); if ($error) { return $error; } // Validate name $name = trim($input['name']); $error = $this->errorHandler->validateStringLength($name, 'name', 1, 100); if ($error) { return $error; } // Validate color if provided if (isset($input['color'])) { $color = trim($input['color']); if (!preg_match('/^#[0-9A-Fa-f]{6}$/', $color)) { return $this->errorHandler->createErrorResponse( 'Color must be a valid hex color code (e.g., #3498db)', 'VALIDATION_FAILED', ['field' => 'color', 'value' => $color, 'format' => '#RRGGBB'] ); } } return null; } /** * Validate album tags update input * @param array $input Input data * @return array|null Returns error response if validation fails, null if valid */ public function validateAlbumTagsUpdate($input) { // Check required fields $error = $this->errorHandler->validateRequiredFields($input, ['tagIds']); if ($error) { return $error; } // Validate tagIds is an array if (!is_array($input['tagIds'])) { return $this->errorHandler->createErrorResponse( 'tagIds must be an array', 'VALIDATION_FAILED', ['field' => 'tagIds', 'type' => gettype($input['tagIds'])] ); } // Validate each tag ID is numeric foreach ($input['tagIds'] as $index => $tagId) { if (!is_numeric($tagId)) { return $this->errorHandler->createErrorResponse( "Tag ID at index {$index} must be numeric", 'VALIDATION_FAILED', ['field' => "tagIds[{$index}]", 'value' => $tagId] ); } } return null; } /** * Validate tag ID parameter * @param string $albumId Tag ID * @return array|null Returns error response if validation fails, null if valid */ public function validateTagId($tagId) { if (empty($tagId)) { return $this->errorHandler->createErrorResponse( 'Tag ID is required', 'MISSING_REQUIRED_FIELD', ['field' => 'tagId'] ); } if (!is_numeric($tagId)) { return $this->errorHandler->createErrorResponse( 'Tag ID must be numeric', 'VALIDATION_FAILED', ['field' => 'tagId', 'value' => $tagId] ); } return null; } /** * Validate album ID parameter * @param string $albumId Album ID * @return array|null Returns error response if validation fails, null if valid */ public function validateAlbumId($albumId) { if (empty($albumId)) { return $this->errorHandler->createErrorResponse( 'Album ID is required', 'MISSING_REQUIRED_FIELD', ['field' => 'albumId'] ); } if (!is_numeric($albumId)) { return $this->errorHandler->createErrorResponse( 'Album ID must be numeric', 'VALIDATION_FAILED', ['field' => 'albumId', 'value' => $albumId] ); } return null; } /** * Validate pagination parameters * @param array $params Parameters containing page, limit, etc. * @return array|null Returns error response if validation fails, null if valid */ public function validatePaginationParams($params) { // Validate page if (isset($params['page'])) { $error = $this->errorHandler->validateNumeric($params['page'], 'page', 1); if ($error) { return $error; } } // Validate limit if (isset($params['limit'])) { $error = $this->errorHandler->validateNumeric($params['limit'], 'limit', 1, 100); if ($error) { return $error; } } // Validate sortBy if (isset($params['sortBy'])) { $allowedSortFields = ['uploaded_at', 'filename', 'file_size', 'width', 'height', 'created_at', 'updated_at']; $error = $this->errorHandler->validateAllowedValues($params['sortBy'], 'sortBy', $allowedSortFields); if ($error) { return $error; } } // Validate sortOrder if (isset($params['sortOrder'])) { $allowedSortOrders = ['asc', 'desc']; $error = $this->errorHandler->validateAllowedValues($params['sortOrder'], 'sortOrder', $allowedSortOrders); if ($error) { return $error; } } return null; } /** * Validate file upload parameters * @param array $files $_FILES array * @param string $albumId Album ID * @return array|null Returns error response if validation fails, null if valid */ public function validateFileUpload($files, $albumId) { // Validate album ID $error = $this->validateAlbumId($albumId); if ($error) { return $error; } // Check if files were uploaded if (empty($files) || !isset($files['photos'])) { // Get current PHP upload limits for better error message $uploadMaxFilesize = ini_get('upload_max_filesize'); $postMaxSize = ini_get('post_max_size'); $maxFileUploads = ini_get('max_file_uploads'); return $this->errorHandler->createErrorResponse( "No files uploaded. Please select valid image files. Current server limits: Max file size: {$uploadMaxFilesize}, Max files: {$maxFileUploads}, Max total size: {$postMaxSize}", 'INVALID_INPUT', [ 'field' => 'photos', 'hint' => 'Files may be too large or in unsupported format', 'limits' => [ 'max_file_size' => $uploadMaxFilesize, 'max_files' => $maxFileUploads, 'max_total_size' => $postMaxSize ] ] ); } $photos = $files['photos']; // Handle single file upload (convert to array format) if (!is_array($photos['name'])) { $photos = [ 'name' => [$photos['name']], 'type' => [$photos['type']], 'tmp_name' => [$photos['tmp_name']], 'error' => [$photos['error']], 'size' => [$photos['size']] ]; } // Validate each uploaded file for ($i = 0; $i < count($photos['name']); $i++) { // Check for upload errors if ($photos['error'][$i] !== UPLOAD_ERR_OK) { $errorMessage = $this->getUploadErrorMessage($photos['error'][$i]); return $this->errorHandler->createErrorResponse( "Upload error for file '{$photos['name'][$i]}': {$errorMessage}", 'INVALID_INPUT', [ 'file' => $photos['name'][$i], 'upload_error_code' => $photos['error'][$i], 'upload_error_message' => $errorMessage ] ); } // Validate file name if (empty($photos['name'][$i])) { return $this->errorHandler->createErrorResponse( 'File name cannot be empty', 'INVALID_INPUT', ['file_index' => $i] ); } // Validate file size if ($photos['size'][$i] <= 0) { return $this->errorHandler->createErrorResponse( "File '{$photos['name'][$i]}' is empty or invalid", 'INVALID_INPUT', ['file' => $photos['name'][$i], 'size' => $photos['size'][$i]] ); } } return null; } /** * Validate RSS settings update input * @param array $input Input data * @return array|null Returns error response if validation fails, null if valid */ public function validateRSSSettingsUpdate($input) { $allowedFields = ['title', 'description', 'max_items', 'base_url']; $hasValidField = false; foreach ($allowedFields as $field) { if (isset($input[$field])) { $hasValidField = true; break; } } if (!$hasValidField) { return $this->errorHandler->createErrorResponse( 'At least one field must be provided for RSS settings update: '.implode(', ', $allowedFields), 'INVALID_INPUT', ['allowed_fields' => $allowedFields] ); } // Validate title if provided if (isset($input['title'])) { $error = $this->errorHandler->validateStringLength($input['title'], 'title', 1, 255); if ($error) { return $error; } } // Validate description if provided if (isset($input['description'])) { $error = $this->errorHandler->validateStringLength($input['description'], 'description', 0, 1000); if ($error) { return $error; } } // Validate max_items if provided if (isset($input['max_items'])) { $error = $this->errorHandler->validateNumeric($input['max_items'], 'max_items', 1, 1000); if ($error) { return $error; } } // Validate base_url if provided if (isset($input['base_url'])) { $baseUrl = trim($input['base_url']); if (!empty($baseUrl) && !filter_var($baseUrl, FILTER_VALIDATE_URL)) { return $this->errorHandler->createErrorResponse( 'base_url must be a valid URL', 'VALIDATION_FAILED', ['field' => 'base_url', 'value' => $baseUrl] ); } } return null; } /** * Validate configuration update input * @param array $input Input data * @return array|null Returns error response if validation fails, null if valid */ public function validateConfigUpdate($input) { if (empty($input) || !is_array($input)) { return $this->errorHandler->createErrorResponse( 'Configuration data must be provided as an object', 'INVALID_INPUT' ); } // Define allowed configuration keys and their validation rules $configRules = [ 'upload_max_file_size' => ['type' => 'numeric', 'min' => 1024, 'max' => 104857600], // 1KB to 100MB 'image_webp_quality' => ['type' => 'numeric', 'min' => 1, 'max' => 100], 'image_max_width' => ['type' => 'numeric', 'min' => 100, 'max' => 10000], 'image_max_height' => ['type' => 'numeric', 'min' => 100, 'max' => 10000], 'image_thumbnail_size' => ['type' => 'numeric', 'min' => 50, 'max' => 1000], 'rss_title' => ['type' => 'string', 'min_length' => 1, 'max_length' => 255], 'rss_description' => ['type' => 'string', 'min_length' => 0, 'max_length' => 1000], 'rss_max_items' => ['type' => 'numeric', 'min' => 1, 'max' => 1000], 'rss_base_url' => ['type' => 'url'], 'storage_max_total_size' => ['type' => 'numeric', 'min' => 1048576], // Minimum 1MB 'storage_cleanup_enabled' => ['type' => 'boolean'] ]; // Validate each provided configuration key foreach ($input as $key => $value) { if (!isset($configRules[$key])) { return $this->errorHandler->createErrorResponse( "Unknown configuration key: {$key}", 'VALIDATION_FAILED', ['field' => $key, 'allowed_keys' => array_keys($configRules)] ); } $rule = $configRules[$key]; // Validate based on type switch ($rule['type']) { case 'numeric': $error = $this->errorHandler->validateNumeric( $value, $key, $rule['min'] ?? null, $rule['max'] ?? null ); if ($error) { return $error; } break; case 'string': if (!is_string($value)) { return $this->errorHandler->createErrorResponse( "{$key} must be a string", 'VALIDATION_FAILED', ['field' => $key, 'type' => gettype($value)] ); } $error = $this->errorHandler->validateStringLength( $value, $key, $rule['min_length'] ?? 0, $rule['max_length'] ?? null ); if ($error) { return $error; } break; case 'boolean': if (!is_bool($value) && !in_array($value, [0, 1, '0', '1', 'true', 'false'], true)) { return $this->errorHandler->createErrorResponse( "{$key} must be a boolean value", 'VALIDATION_FAILED', ['field' => $key, 'value' => $value] ); } break; case 'url': if (!empty($value) && !filter_var($value, FILTER_VALIDATE_URL)) { return $this->errorHandler->createErrorResponse( "{$key} must be a valid URL", 'VALIDATION_FAILED', ['field' => $key, 'value' => $value] ); } break; } } return null; } /** * Get human-readable upload error message * @param int $errorCode PHP upload error code * @return string Error message */ private function getUploadErrorMessage($errorCode) { switch ($errorCode) { case UPLOAD_ERR_INI_SIZE: return 'File exceeds the upload_max_filesize directive in php.ini'; case UPLOAD_ERR_FORM_SIZE: return 'File exceeds the MAX_FILE_SIZE directive in the HTML form'; case UPLOAD_ERR_PARTIAL: return 'File was only partially uploaded'; case UPLOAD_ERR_NO_FILE: return 'No file was uploaded'; case UPLOAD_ERR_NO_TMP_DIR: return 'Missing temporary folder'; case UPLOAD_ERR_CANT_WRITE: return 'Failed to write file to disk'; case UPLOAD_ERR_EXTENSION: return 'File upload stopped by extension'; default: return 'Unknown upload error'; } } }