Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .docker/frankenphp/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,14 @@
php_server
}

# Setup and update pages
# Setup pages
@setup path /setup*
rewrite @setup /setup/index.php

# Update page - route directly to index.php (handled by Symfony router)
@update path /update*
rewrite @update /index.php

# Administration API
@admin_api path /admin/api/*
rewrite @admin_api /admin/api/index.php
Expand Down Expand Up @@ -88,12 +92,13 @@
php_server
}

# Setup and update pages
# Setup pages
@setup path /setup*
rewrite @setup /setup/index.php

# Update page - route directly to index.php (handled by Symfony router)
@update path /update*
rewrite @update /update/index.php
rewrite @update /index.php

# Administration API
@admin_api path /admin/api/*
Expand Down
8 changes: 8 additions & 0 deletions .docker/nginx/default.conf
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ server {
# Setup pages
rewrite ^/setup/ /setup/index.php last;

# Update page - route directly to index.php (handled by Symfony router)
rewrite ^/update$ /index.php last;
rewrite ^/update/ /index.php last;

# Front controller: route all other requests to index.php (Symfony Router)
rewrite ^ /index.php last;
}
Expand Down Expand Up @@ -207,6 +211,10 @@ server {
# Setup pages
rewrite ^/setup/ /setup/index.php last;

# Update page - route directly to index.php (handled by Symfony router)
rewrite ^/update$ /index.php last;
rewrite ^/update/ /index.php last;

# Front controller: route all other requests to index.php (Symfony Router)
rewrite ^ /index.php last;
}
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ This is a log of major user-visible changes in each phpMyFAQ release.
- improved API errors with formatted RFC 7807 Problem Details JSON responses (Thorsten)
- improved support for PDO (Thorsten)
- improved sticky FAQs administration (Thorsten)
- improved update process (Thorsten)
- migrated codebase using PHP 8.4 language features (Thorsten)
- migrated routes using PHP 8+ #[Route] attributes (Thorsten)

Expand Down
4 changes: 4 additions & 0 deletions nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ server {
# Setup pages
rewrite ^/setup/ /setup/index.php last;

# Update page - route directly to index.php (handled by Symfony router)
rewrite ^/update$ /index.php last;
rewrite ^/update/ /index.php last;

# Front controller: route all other requests to index.php (Symfony Router)
rewrite ^ /index.php last;
}
Expand Down
4 changes: 3 additions & 1 deletion phpmyfaq/.htaccess
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ Header set Access-Control-Allow-Headers "Content-Type, Authorization"
RewriteRule ^api/ api/index.php [L,QSA]
# Setup pages
RewriteRule ^setup/ setup/index.php [L,QSA]
RewriteRule ^update$ update/ [R=301,L]
# Update page - route directly to index.php (handled by Symfony router)
RewriteRule ^update$ index.php [L,QSA]
RewriteRule ^update/ index.php [L,QSA]
# Front controller: route all other requests to index.php (Symfony Router)
# Skip if the file or directory exists
RewriteCond %{REQUEST_FILENAME} !-f
Expand Down
2 changes: 1 addition & 1 deletion phpmyfaq/admin/api/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
$app = new Application($container);
$app->setAdminContext(true);
$app->setApiContext(true);
$app->setRoutingContext('admin-api');
$app->routingContext = 'admin-api';
try {
// Autoload routes from attributes (falls back to api-routes.php during migration)
$app->run();
Expand Down
2 changes: 1 addition & 1 deletion phpmyfaq/admin/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
}

$app = new Application($container);
$app->setRoutingContext('admin');
$app->routingContext = 'admin';
try {
// Auto-loads routes from attributes (falls back to admin-routes.php during migration)
$app->run();
Expand Down
2 changes: 1 addition & 1 deletion phpmyfaq/api/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@

$app = new Application($container);
$app->setApiContext(true);
$app->setRoutingContext('api');
$app->routingContext = 'api';
try {
// Autoload routes from attributes (falls back to api-routes.php during migration)
$app->run();
Expand Down
3 changes: 2 additions & 1 deletion phpmyfaq/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
}

$app = new Application($container);
$app->setRoutingContext('public');
$app->routingContext = 'public';

try {
// Auto-loads routes from attributes (falls back to public-routes.php during migration)
$app->run();
Expand Down
5 changes: 2 additions & 3 deletions phpmyfaq/src/Bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage;

//
// Fix the PHP include path if PMF is running under a "strange" PHP configuration
Expand Down Expand Up @@ -95,7 +93,8 @@
// Skip redirect if we're already in setup or API setup context
//
$requestUri = $_SERVER['REQUEST_URI'] ?? '';
$isSetupContext = str_contains($requestUri, '/setup/') || str_contains($requestUri, '/api/setup/');
$isSetupContext = str_contains($requestUri, '/setup/') || str_contains($requestUri, '/api/setup/') ||
str_contains($requestUri, '/update') || str_contains($requestUri, '/update/');

if (!file_exists(PMF_CONFIG_DIR . '/database.php') && !file_exists(PMF_LEGACY_CONFIG_DIR . '/database.php')) {
if (!$isSetupContext) {
Expand Down
4 changes: 2 additions & 2 deletions phpmyfaq/src/phpMyFAQ/Api/Pagination/PaginationRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,15 @@ public static function fromRequest(Request $request, int $defaultPerPage = 25, i
// Offset-based pagination
$isOffsetBased = true;
$offset = max(0, $offset); // Ensure non-negative
$limit = $limit ?? $perPage ?? $defaultPerPage;
$limit ??= $perPage ?? $defaultPerPage;
$limit = self::validateLimit($limit, $maxPerPage);
$perPage = $limit;
// Calculate page from offset
$page = (int) floor($offset / $limit) + 1;
} else {
// Page-based pagination
$isPageBased = true;
$perPage = $perPage ?? $limit ?? $defaultPerPage;
$perPage ??= $limit ?? $defaultPerPage;
$perPage = self::validateLimit($perPage, $maxPerPage);
$limit = $perPage;
// Calculate offset from page
Expand Down
35 changes: 16 additions & 19 deletions phpmyfaq/src/phpMyFAQ/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,27 @@

class Application
{
private UrlMatcher $urlMatcher;
public UrlMatcher $urlMatcher {
set(UrlMatcher $value) {
$this->urlMatcher = $value;
}
}

private ControllerResolver $controllerResolver;
public ControllerResolver $controllerResolver {
set(ControllerResolver $value) {
$this->controllerResolver = $value;
}
}

private bool $isApiContext = false;

private bool $isAdminContext = false;

private string $routingContext = 'public';
public string $routingContext = 'public' {
set {
$this->routingContext = $value;
}
}

public function __construct(
private readonly ?ContainerInterface $container = null,
Expand All @@ -76,16 +88,6 @@ public function run(?RouteCollection $routeCollection = null, ?Request $request
$this->handleRequest($routeCollection, $request, $requestContext);
}

public function setUrlMatcher(UrlMatcher $urlMatcher): void
{
$this->urlMatcher = $urlMatcher;
}

public function setControllerResolver(ControllerResolver $controllerResolver): void
{
$this->controllerResolver = $controllerResolver;
}

public function setApiContext(bool $isApiContext): void
{
$this->isApiContext = $isApiContext;
Expand All @@ -96,11 +98,6 @@ public function setAdminContext(bool $isAdminContext): void
$this->isAdminContext = $isAdminContext;
}

public function setRoutingContext(string $context): void
{
$this->routingContext = $context;
}

private function setLanguage(): string
{
if (!is_null($this->container)) {
Expand Down Expand Up @@ -294,7 +291,7 @@ private function loadRoutes(): RouteCollection
// Load routes with caching if enabled (disabled automatically in debug mode)
if ($cacheEnabled && !Environment::isDebugMode()) {
$cacheManager = new RouteCacheManager($cacheDir, Environment::isDebugMode());
return $cacheManager->getRoutes($context, function () use ($configuration, $context) {
return $cacheManager->getRoutes($context, static function () use ($configuration, $context) {
$builder = new RouteCollectionBuilder($configuration);
return $builder->build($context);
});
Expand Down
1 change: 0 additions & 1 deletion phpmyfaq/src/phpMyFAQ/Category/CategoryService.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

namespace phpMyFAQ\Category;

use phpMyFAQ\Category\CategoryRepositoryInterface;
use phpMyFAQ\Entity\CategoryEntity;

/**
Expand Down
6 changes: 3 additions & 3 deletions phpmyfaq/src/phpMyFAQ/Chat.php
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ public function getConversationList(int $userId): array
}

// Sort by last message time descending
usort($conversations, fn($a, $b) => strcmp($b['lastMessageTime'], $a['lastMessageTime']));
usort($conversations, static fn($a, $b) => strcmp($b['lastMessageTime'], $a['lastMessageTime']));

return $conversations;
}
Expand Down Expand Up @@ -341,12 +341,12 @@ public function messagesToArray(array $messages): array
}

// Collect unique sender IDs
$senderIds = array_unique(array_map(fn(ChatMessage $m) => $m->getSenderId(), $messages));
$senderIds = array_unique(array_map(static fn(ChatMessage $m) => $m->getSenderId(), $messages));

// Batch fetch user info
$userInfo = $this->getBatchUserInfo($senderIds);

return array_map(fn(ChatMessage $message) => [
return array_map(static fn(ChatMessage $message) => [
'id' => $message->getId(),
'senderId' => $message->getSenderId(),
'senderName' => $userInfo[$message->getSenderId()] ?? 'Unknown User',
Expand Down
2 changes: 1 addition & 1 deletion phpmyfaq/src/phpMyFAQ/Controller/AbstractController.php
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ protected function userHasPermission(PermissionType $permissionType): void
* verification may fail due to domain/path mismatches.
* @throws \Exception
*/
protected function verifySessionCsrfToken(string $page, string $requestToken): bool
protected function verifySessionCsrfToken(string $page, #[\SensitiveParameter] string $requestToken): bool
{
if (empty($requestToken)) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,10 @@ public function save(Request $request): JsonResponse
$this->configuration->update($newConfigValues);

// Filter out non-scalar values from old config before comparison
$oldConfigComparable = array_filter($oldConfigurationData, fn($value) => is_scalar($value) || $value === null);
$oldConfigComparable = array_filter(
$oldConfigurationData,
static fn($value) => is_scalar($value) || $value === null,
);

$changedKeys = array_keys(array_diff_assoc($newConfigValues, $oldConfigComparable));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public function upload(Request $request): JsonResponse
'messages' => ['Files uploaded successfully'],
'files' => $fileUrls, // For Jodit uploader
'isImages' => array_map(
fn($file) => !in_array(pathinfo($file, PATHINFO_EXTENSION), ['mov', 'mp4', 'webm']),
static fn($file) => !in_array(pathinfo($file, PATHINFO_EXTENSION), ['mov', 'mp4', 'webm']),
$uploadedFiles,
),
'sources' => [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,11 @@ public function create(Request $request): JsonResponse
// Validate required fields
$requiredFields = ['pageTitle', 'slug', 'authorName', 'authorEmail', 'lang'];
foreach ($requiredFields as $field) {
if (!isset($data->$field) || $data->$field === '') {
return $this->json(['error' => "Missing required field: $field"], Response::HTTP_BAD_REQUEST);
if (!(!isset($data->$field) || $data->$field === '')) {
continue;
}

return $this->json(['error' => "Missing required field: $field"], Response::HTTP_BAD_REQUEST);
}

$pageTitle = Filter::filterVar($data->pageTitle, FILTER_SANITIZE_SPECIAL_CHARS);
Expand Down Expand Up @@ -321,9 +323,11 @@ public function update(Request $request): JsonResponse
// Validate required fields
$requiredFields = ['id', 'pageTitle', 'slug', 'authorName', 'authorEmail', 'lang'];
foreach ($requiredFields as $field) {
if (!isset($data->$field) || $data->$field === '') {
return $this->json(['error' => "Missing required field: $field"], Response::HTTP_BAD_REQUEST);
if (!(!isset($data->$field) || $data->$field === '')) {
continue;
}

return $this->json(['error' => "Missing required field: $field"], Response::HTTP_BAD_REQUEST);
}

$pageId = Filter::filterVar($data->id, FILTER_VALIDATE_INT);
Expand Down
2 changes: 1 addition & 1 deletion phpmyfaq/src/phpMyFAQ/Controller/Api/FaqController.php
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ public function list(): JsonResponse

// Apply sorting if needed (basic client-side sorting)
if ($sort->getField() && $sort->getField() !== 'id') {
usort($allFaqs, function ($a, $b) use ($sort) {
usort($allFaqs, static function ($a, $b) use ($sort) {
$field = $sort->getField();
$aVal = $a[$field] ?? '';
$bVal = $b[$field] ?? '';
Expand Down
3 changes: 1 addition & 2 deletions phpmyfaq/src/phpMyFAQ/Controller/Api/GlossaryController.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
use OpenApi\Attributes as OA;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

final class GlossaryController extends AbstractApiController
Expand Down Expand Up @@ -137,7 +136,7 @@ public function list(Request $request): JsonResponse

// Apply sorting if needed
if ($sort->getField()) {
usort($allItems, function ($a, $b) use ($sort) {
usort($allItems, static function ($a, $b) use ($sort) {
$field = $sort->getField();
$aVal = $a[$field] ?? '';
$bVal = $b[$field] ?? '';
Expand Down
3 changes: 1 addition & 2 deletions phpmyfaq/src/phpMyFAQ/Controller/Api/GroupController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
use OpenApi\Attributes as OA;
use phpMyFAQ\Permission\MediumPermission;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;

final class GroupController extends AbstractApiController
{
Expand Down Expand Up @@ -124,7 +123,7 @@ public function list(): JsonResponse

// Apply sorting if needed
if ($sort->getOrderSql() === 'DESC') {
usort($allGroups, function ($a, $b) {
usort($allGroups, static function ($a, $b) {
return ($b['group-id'] ?? 0) <=> ($a['group-id'] ?? 0);
});
}
Expand Down
1 change: 0 additions & 1 deletion phpmyfaq/src/phpMyFAQ/Controller/Api/NewsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
use OpenApi\Attributes as OA;
use phpMyFAQ\News;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;

final class NewsController extends AbstractApiController
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
use OpenApi\Attributes as OA;
use phpMyFAQ\Question;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

final class OpenQuestionController extends AbstractApiController
Expand Down Expand Up @@ -140,7 +139,7 @@ public function list(): JsonResponse

// Apply sorting if needed
if ($sort->getField()) {
usort($allQuestions, function ($a, $b) use ($sort) {
usort($allQuestions, static function ($a, $b) use ($sort) {
$field = $sort->getField();
$aVal = $a[$field] ?? '';
$bVal = $b[$field] ?? '';
Expand Down
2 changes: 1 addition & 1 deletion phpmyfaq/src/phpMyFAQ/Controller/Api/SearchController.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ public function search(Request $request): JsonResponse

// Apply sorting if needed
if ($sort->getField()) {
usort($allResults, function ($a, $b) use ($sort) {
usort($allResults, static function ($a, $b) use ($sort) {
$field = $sort->getField();
$aVal = $a->{$field} ?? '';
$bVal = $b->{$field} ?? '';
Expand Down
Loading