Skip to content

Commit 8931bf1

Browse files
committed
refactor: simplified the category code
1 parent 5a8695d commit 8931bf1

File tree

15 files changed

+444
-137
lines changed

15 files changed

+444
-137
lines changed

phpmyfaq/src/phpMyFAQ/Administration/Category.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public function loadCategories(): array
7676
// Centralize permission WHERE clause
7777
$perm = new CategoryPermissionService();
7878
// In admin, include inactive categories (no active filter)
79-
$where = $perm->buildWhereClause($this->groups, $this->user, true) . ' ' . $languageCheck;
79+
$where = $perm->buildWhereClauseWithInactive($this->groups, $this->user) . ' ' . $languageCheck;
8080

8181
$prefix = Database::getTablePrefix();
8282
$query = "SELECT

phpmyfaq/src/phpMyFAQ/Category/CategoryRepository.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ public function findOrderedCategories(
6666

6767
if ($withPermission) {
6868
$perm = new CategoryPermissionService();
69-
$where = $perm->buildWhereClause($groups, $userId, $withInactive);
69+
$where = $withInactive
70+
? $perm->buildWhereClauseWithInactive($groups, $userId)
71+
: $perm->buildWhereClause($groups, $userId);
7072
}
7173

7274
if ($language !== null && preg_match('/^[a-z\-]{2,}$/', $language)) {

phpmyfaq/src/phpMyFAQ/Category/Internal/CategoryServices.php

Lines changed: 0 additions & 1 deletion
This file was deleted.

phpmyfaq/src/phpMyFAQ/Category/Internal/CategoryState.php

Lines changed: 0 additions & 23 deletions
This file was deleted.

phpmyfaq/src/phpMyFAQ/Category/Language/CategoryLanguageService.php

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
<?php
22

3+
/**
4+
* Category language service class
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public License,
7+
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
8+
* obtain one at https://mozilla.org/MPL/2.0/.
9+
*
10+
* @package phpMyFAQ
11+
* @author Thorsten Rinne <[email protected]>
12+
* @copyright 2025 phpMyFAQ Team
13+
* @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14+
* @link https://www.phpmyfaq.de
15+
* @since 2026-10-18
16+
*/
17+
318
declare(strict_types=1);
419

520
namespace phpMyFAQ\Category\Language;
@@ -18,13 +33,13 @@ final class CategoryLanguageService
1833
*/
1934
public function getLanguagesToTranslate(Configuration $configuration, int $categoryId): array
2035
{
21-
$existing = $configuration->getLanguage()->isLanguageAvailable($categoryId, 'faqcategories');
36+
$existing = $configuration->getLanguage()->isLanguageAvailable($categoryId, table: 'faqcategories');
2237
$existingLower = array_map(static fn($l) => strtolower((string) $l), $existing);
2338

2439
$result = [];
2540
foreach (LanguageHelper::getAvailableLanguages() as $lang => $name) {
2641
$langLower = strtolower((string) $lang);
27-
if (!in_array($langLower, $existingLower, true)) {
42+
if (!in_array($langLower, $existingLower, strict: true)) {
2843
$result[$langLower] = $name;
2944
}
3045
}
@@ -40,13 +55,12 @@ public function getLanguagesToTranslate(Configuration $configuration, int $categ
4055
*/
4156
public function getExistingTranslations(Configuration $configuration, int $categoryId): array
4257
{
43-
$existing = $configuration->getLanguage()->isLanguageAvailable($categoryId, 'faqcategories');
58+
$existing = $configuration->getLanguage()->isLanguageAvailable($categoryId, table: 'faqcategories');
4459
$available = LanguageHelper::getAvailableLanguages();
4560

4661
$result = [];
4762
foreach ($existing as $code) {
48-
$codeLower = strtolower((string) $code);
49-
// Prefer LanguageHelper names, fallback to LanguageCodes
63+
$codeLower = strtolower($code);
5064
$result[$codeLower] = $available[$codeLower] ?? LanguageCodes::get($codeLower) ?? $codeLower;
5165
}
5266

@@ -62,10 +76,13 @@ public function getExistingTranslations(Configuration $configuration, int $categ
6276
*/
6377
public function getLanguagesInUse(Configuration $configuration): array
6478
{
65-
$all = $configuration->getLanguage()->isLanguageAvailable(0, 'faqcategories');
79+
$all = $configuration->getLanguage()->isLanguageAvailable(
80+
identifier: 0,
81+
table: 'faqcategories',
82+
);
6683
$result = [];
6784
foreach ($all as $code) {
68-
$codeLower = strtolower((string) $code);
85+
$codeLower = strtolower($code);
6986
$result[$codeLower] = LanguageCodes::get($codeLower) ?? $codeLower;
7087
}
7188
asort($result);

phpmyfaq/src/phpMyFAQ/Category/Navigation/BreadcrumbsBuilder.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
<?php
22

3+
/**
4+
* Category breadcrumb builder class
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public License,
7+
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
8+
* obtain one at https://mozilla.org/MPL/2.0/.
9+
*
10+
* @package phpMyFAQ
11+
* @author Thorsten Rinne <[email protected]>
12+
* @copyright 2025 phpMyFAQ Team
13+
* @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14+
* @link https://www.phpmyfaq.de
15+
* @since 2026-10-18
16+
*/
17+
318
declare(strict_types=1);
419

520
namespace phpMyFAQ\Category\Navigation;

phpmyfaq/src/phpMyFAQ/Category/Navigation/BreadcrumbsHtmlRenderer.php

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
<?php
22

3+
/**
4+
* Category breadcrumb HTML renderer class
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public License,
7+
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
8+
* obtain one at https://mozilla.org/MPL/2.0/.
9+
*
10+
* @package phpMyFAQ
11+
* @author Thorsten Rinne <[email protected]>
12+
* @copyright 2025 phpMyFAQ Team
13+
* @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14+
* @link https://www.phpmyfaq.de
15+
* @since 2026-10-18
16+
*/
17+
318
declare(strict_types=1);
419

520
namespace phpMyFAQ\Category\Navigation;
@@ -20,17 +35,35 @@ public function render(Configuration $configuration, array $segments, string $us
2035
{
2136
$items = [];
2237
foreach ($segments as $index => $segment) {
23-
$url = sprintf('%sindex.php?action=show&cat=%d', $configuration->getDefaultUrl(), (int) $segment['id']);
38+
$url = strtr(
39+
string: '{base}index.php?action=show&cat={id}',
40+
replace_pairs: [
41+
'{base}' => $configuration->getDefaultUrl(),
42+
'{id}' => (string) $segment['id'],
43+
],
44+
);
2445
$oLink = new Link($url, $configuration);
2546
$oLink->text = Strings::htmlentities($segment['name']);
2647
$oLink->itemTitle = Strings::htmlentities($segment['name']);
2748
$oLink->tooltip = Strings::htmlentities($segment['description'] ?? '');
2849
if (0 === $index) {
29-
$oLink->setRelation('index');
50+
$oLink->setRelation(rel: 'index');
3051
}
31-
$items[] = sprintf('<li class="breadcrumb-item">%s</li>', $oLink->toHtmlAnchor());
52+
$items[] = sprintf(
53+
format: '<li class="breadcrumb-item">%s</li>',
54+
values: $oLink->toHtmlAnchor(),
55+
);
3256
}
3357

34-
return sprintf('<ul class="%s">%s</ul>', $useCssClass, implode('', $items));
58+
return strtr(
59+
string: '<ul class="{class}">{items}</ul>',
60+
replace_pairs: [
61+
'{class}' => $useCssClass,
62+
'{items}' => implode(
63+
separator: '',
64+
array: $items,
65+
),
66+
],
67+
);
3568
}
3669
}
Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,66 @@
11
<?php
22

3+
/**
4+
* Category permission service class
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public License,
7+
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
8+
* obtain one at https://mozilla.org/MPL/2.0/.
9+
*
10+
* @package phpMyFAQ
11+
* @author Thorsten Rinne <[email protected]>
12+
* @copyright 2025 phpMyFAQ Team
13+
* @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14+
* @link https://www.phpmyfaq.de
15+
* @since 2026-10-18
16+
*/
17+
318
declare(strict_types=1);
419

520
namespace phpMyFAQ\Category\Permission;
621

722
final class CategoryPermissionService
823
{
924
/**
10-
* Builds the permission-related WHERE clause for category listing.
25+
* Builds the permission-related WHERE clause for category listing (active categories only).
26+
*
27+
* @param int[] $groups
28+
*/
29+
public function buildWhereClause(array $groups, int $userId): string
30+
{
31+
return $this->buildPermissionWhereClause($groups, $userId, activeClause: 'AND fc.active = 1');
32+
}
33+
34+
/**
35+
* Builds the permission-related WHERE clause for category listing (including inactive categories).
36+
*
37+
* @param int[] $groups
38+
*/
39+
public function buildWhereClauseWithInactive(array $groups, int $userId): string
40+
{
41+
return $this->buildPermissionWhereClause($groups, $userId, activeClause: '');
42+
}
43+
44+
/**
45+
* Internal helper to build the WHERE clause with permission logic.
1146
*
1247
* @param int[] $groups
1348
*/
14-
public function buildWhereClause(array $groups, int $userId, bool $withInactive): string
49+
private function buildPermissionWhereClause(array $groups, int $userId, string $activeClause): string
1550
{
16-
$groupsList = $groups === [] ? '-1' : implode(', ', array_map('intval', $groups));
17-
$activeClause = $withInactive ? '' : 'AND fc.active = 1';
18-
return sprintf(
19-
'WHERE ( fg.group_id IN (%s) OR (fu.user_id = %d AND fg.group_id IN (%s))) %s',
20-
$groupsList,
21-
$userId,
22-
$groupsList,
23-
$activeClause,
24-
);
51+
$groupsList = $groups === []
52+
? '-1'
53+
: implode(
54+
separator: ', ',
55+
array: array_map(
56+
callback: 'intval',
57+
array: $groups,
58+
),
59+
);
60+
return strtr('WHERE ( fg.group_id IN ({groups}) OR (fu.user_id = {userId} AND fg.group_id IN ({groups}))) {active}', [
61+
'{groups}' => $groupsList,
62+
'{userId}' => (string) $userId,
63+
'{active}' => $activeClause,
64+
]);
2565
}
2666
}

phpmyfaq/src/phpMyFAQ/Category/Presentation/AdminCategoryTreePresenter.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
<?php
22

3+
/**
4+
* Category tree presenter class
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public License,
7+
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
8+
* obtain one at https://mozilla.org/MPL/2.0/.
9+
*
10+
* @package phpMyFAQ
11+
* @author Thorsten Rinne <[email protected]>
12+
* @copyright 2025 phpMyFAQ Team
13+
* @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14+
* @link https://www.phpmyfaq.de
15+
* @since 2026-10-18
16+
*/
17+
318
declare(strict_types=1);
419

520
namespace phpMyFAQ\Category\Presentation;
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
/**
4+
* Validates category data structures.
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public License,
7+
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
8+
* obtain one at https://mozilla.org/MPL/2.0/.
9+
*
10+
* @package phpMyFAQ
11+
* @author Thorsten Rinne <[email protected]>
12+
* @copyright 2025 phpMyFAQ Team
13+
* @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14+
* @link https://www.phpmyfaq.de
15+
* @since 2025-10-19
16+
*/
17+
18+
declare(strict_types=1);
19+
20+
namespace phpMyFAQ\Category\Tree;
21+
22+
final class CategoryValidator
23+
{
24+
/**
25+
* Validates if a category array has required fields.
26+
*
27+
* @param mixed $category
28+
* @return bool
29+
*/
30+
public function isValidCategory(mixed $category): bool
31+
{
32+
if (!is_array($category)) {
33+
return false;
34+
}
35+
return (
36+
array_key_exists(
37+
key: 'parent_id',
38+
array: $category,
39+
)
40+
&& array_key_exists(
41+
key: 'id',
42+
array: $category,
43+
)
44+
);
45+
}
46+
47+
/**
48+
* Checks if a category is a direct child of a parent.
49+
*
50+
* @param array<string, mixed> $category
51+
*/
52+
public function isDirectChild(array $category, mixed $categoryId, int $parentId): bool
53+
{
54+
if (!isset($category['parent_id'])) {
55+
return false;
56+
}
57+
if ((int) $category['parent_id'] !== $parentId) {
58+
return false;
59+
}
60+
return is_int($categoryId) && $categoryId > 0;
61+
}
62+
63+
/**
64+
* Collects direct children IDs for a parent.
65+
*
66+
* @param array<int, array<string, mixed>> $categories
67+
* @return array<int>
68+
*/
69+
public function collectDirectChildren(array $categories, int $parentId): array
70+
{
71+
$children = [];
72+
foreach ($categories as $categoryId => $category) {
73+
if ($this->isDirectChild($category, $categoryId, $parentId)) {
74+
$children[] = $categoryId;
75+
}
76+
}
77+
return $children;
78+
}
79+
}

0 commit comments

Comments
 (0)