Skip to content

Commit 916632d

Browse files
committed
refactor: simplified code
1 parent 293d69b commit 916632d

File tree

4 files changed

+217
-61
lines changed

4 files changed

+217
-61
lines changed

phpmyfaq/src/phpMyFAQ/Filter.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,18 +103,30 @@ public static function getFilteredQueryString(): string
103103
parse_str($queryString, $urlData);
104104

105105
foreach ($urlData as $key => $urlPart) {
106-
$cleanUrlData[strip_tags($key)] = strip_tags($urlPart);
106+
$cleanKey = strip_tags((string) $key);
107+
if (is_array($urlPart)) {
108+
// sanitize one level deep; http_build_query will handle arrays
109+
$cleanUrlData[$cleanKey] = array_map(static fn($v) => strip_tags((string) $v), $urlPart);
110+
continue;
111+
}
112+
113+
$cleanUrlData[$cleanKey] = strip_tags((string) $urlPart);
107114
}
108115

109-
return http_build_query($cleanUrlData);
116+
return http_build_query($cleanUrlData, arg_separator: '&', encoding_type: PHP_QUERY_RFC3986);
110117
}
111118

112119
/**
113120
* This method is a polyfill for FILTER_SANITIZE_STRING, deprecated since PHP 8.1.
114121
*/
115122
public function filterSanitizeString(string $string): string
116123
{
117-
$string = htmlspecialchars($string);
124+
$string = htmlspecialchars(
125+
string: $string,
126+
flags: ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401,
127+
encoding: 'UTF-8',
128+
double_encode: true,
129+
);
118130
$string = preg_replace(
119131
pattern: '/\x00|<[^>]*>?/',
120132
replacement: '',
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
/**
4+
* The forms helper class to reduce complexity in Forms.
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+
* @author Jan Harms <[email protected]>
13+
* @copyright 2025 phpMyFAQ Team
14+
* @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
15+
* @link https://www.phpmyfaq.de
16+
* @since 2025-11-07
17+
*/
18+
19+
declare(strict_types=1);
20+
21+
namespace phpMyFAQ\Form;
22+
23+
use phpMyFAQ\Translation;
24+
25+
final class FormsHelper
26+
{
27+
public function filterAndSortFormData(array $formData, Translation $translation): array
28+
{
29+
foreach ($formData as $input) {
30+
if ($input->input_lang === 'default') {
31+
$input->input_label = Translation::get($input->input_label);
32+
}
33+
}
34+
35+
$filteredEntries = [];
36+
$fallbackCandidates = [];
37+
$seenIds = [];
38+
39+
foreach ($formData as $entry) {
40+
if ($entry->input_lang === $translation->getCurrentLanguage()) {
41+
$filteredEntries[] = $entry;
42+
$seenIds[] = $entry->input_id;
43+
continue;
44+
}
45+
$fallbackCandidates[] = $entry;
46+
}
47+
48+
foreach ($fallbackCandidates as $candidate) {
49+
if ($candidate->input_lang === 'default' && !in_array($candidate->input_id, $seenIds, strict: true)) {
50+
$filteredEntries[] = $candidate;
51+
}
52+
}
53+
54+
usort($filteredEntries, static fn(object $a, object $b) => $a->input_id <=> $b->input_id);
55+
return $filteredEntries;
56+
}
57+
}

phpmyfaq/src/phpMyFAQ/Forms.php

Lines changed: 29 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020

2121
namespace phpMyFAQ;
2222

23+
use phpMyFAQ\Form\FormsHelper;
2324
use phpMyFAQ\Form\FormsRepository;
2425
use phpMyFAQ\Form\FormsRepositoryInterface;
2526
use phpMyFAQ\Language\LanguageCodes;
2627

2728
readonly class Forms
2829
{
30+
/* @mago-expect[too-many-methods]: Backward-compatible wrappers retained until the next major release. */
2931
private Translation $translation;
3032
private FormsRepositoryInterface $repository;
3133

@@ -45,44 +47,7 @@ public function __construct(
4547
public function getFormData(int $formId): array
4648
{
4749
$formData = $this->repository->fetchFormDataByFormId($formId);
48-
49-
foreach ($formData as $input) {
50-
if ($input->input_lang === 'default') {
51-
$input->input_label = Translation::get($input->input_label);
52-
}
53-
}
54-
55-
$filteredEntries = [];
56-
57-
$formDataNoMatchingLanguage = [];
58-
$idsAlreadyFiltered = [];
59-
60-
foreach ($formData as $entry) {
61-
if ($entry->input_lang === $this->translation->getCurrentLanguage()) {
62-
$filteredEntries[] = $entry;
63-
$idsAlreadyFiltered[] = $entry->input_id;
64-
} else {
65-
$formDataNoMatchingLanguage[] = $entry;
66-
}
67-
}
68-
69-
foreach ($formDataNoMatchingLanguage as $item) {
70-
if ($item->input_lang === 'default' && !in_array($item->input_id, $idsAlreadyFiltered, strict: true)) {
71-
$filteredEntries[] = $item;
72-
}
73-
}
74-
75-
usort($filteredEntries, [$this, 'sortByInputId']);
76-
77-
return $filteredEntries;
78-
}
79-
80-
/**
81-
* Sort function for usort | Sorting form data by input-id
82-
*/
83-
private function sortByInputId(object $first, object $second): int
84-
{
85-
return $first->input_id - $second->input_id;
50+
return (new FormsHelper())->filterAndSortFormData($formData, $this->translation);
8651
}
8752

8853
/**
@@ -104,28 +69,18 @@ public function getTranslatedLanguages(int $formId, int $inputId): array
10469
}
10570

10671
/**
107-
* Save the (de-)activation of a given input
108-
*
109-
* @param int $formId Form ID
110-
* @param int $inputId Input ID
111-
* @param int $activated Activation status
72+
* Update activation and required flags of a given input.
11273
*/
113-
public function saveActivateInputStatus(int $formId, int $inputId, int $activated): bool
74+
public function updateInputFlags(int $formId, int $inputId, ?int $activated = null, ?int $required = null): bool
11475
{
115-
return $this->repository->updateInputActive($formId, $inputId, $activated);
116-
}
117-
118-
/**
119-
* Save the requirement status of a given input
120-
*
121-
* @param int $formId Form ID
122-
* @param int $inputId Input ID
123-
* @param int $required
124-
* @return bool
125-
*/
126-
public function saveRequiredInputStatus(int $formId, int $inputId, int $required): bool
127-
{
128-
return $this->repository->updateInputRequired($formId, $inputId, $required);
76+
$ok = true;
77+
if ($activated !== null) {
78+
$ok = $ok && $this->repository->updateInputActive($formId, $inputId, $activated);
79+
}
80+
if ($required !== null) {
81+
$ok = $ok && $this->repository->updateInputRequired($formId, $inputId, $required);
82+
}
83+
return $ok;
12984
}
13085

13186
/**
@@ -209,4 +164,20 @@ public function getInsertQueries(array $input): string
209164
{
210165
return $this->repository->buildInsertQuery($input);
211166
}
167+
168+
/**
169+
* Save the (de-)activation of a given input (legacy wrapper).
170+
*/
171+
public function saveActivateInputStatus(int $formId, int $inputId, int $activated): bool
172+
{
173+
return $this->updateInputFlags($formId, $inputId, activated: $activated);
174+
}
175+
176+
/**
177+
* Save the requirement status of a given input (legacy wrapper).
178+
*/
179+
public function saveRequiredInputStatus(int $formId, int $inputId, int $required): bool
180+
{
181+
return $this->updateInputFlags($formId, $inputId, required: $required);
182+
}
212183
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpMyFAQ\Form; // Test namespace für direkte Klassenreferenz
6+
7+
use phpMyFAQ\Translation;
8+
use PHPUnit\Framework\TestCase;
9+
10+
class FormsHelperTest extends TestCase
11+
{
12+
private FormsHelper $helper;
13+
private Translation $translation;
14+
15+
protected function setUp(): void
16+
{
17+
parent::setUp();
18+
$this->helper = new FormsHelper();
19+
Translation::create()
20+
->setLanguagesDir(PMF_TRANSLATION_DIR)
21+
->setDefaultLanguage('en')
22+
->setCurrentLanguage('en')
23+
->setMultiByteLanguage();
24+
$this->translation = new Translation();
25+
}
26+
27+
public function testEmptyDataReturnsEmptyArray(): void
28+
{
29+
$result = $this->helper->filterAndSortFormData([], $this->translation);
30+
$this->assertSame([], $result);
31+
}
32+
33+
public function testOnlyDefaultLanguageFallsBack(): void
34+
{
35+
$data = [(object) [
36+
'form_id' => 1,
37+
'input_id' => 2,
38+
'input_type' => 'text',
39+
'input_label' => 'msgNewContentName',
40+
'input_active' => 1,
41+
'input_required' => 1,
42+
'input_lang' => 'default',
43+
]];
44+
$result = $this->helper->filterAndSortFormData($data, $this->translation);
45+
$this->assertCount(1, $result);
46+
$this->assertEquals(2, $result[0]->input_id);
47+
$this->assertIsString($result[0]->input_label); // wurde via Translation::get() ersetzt
48+
}
49+
50+
public function testCurrentLanguagePreferredOverDefault(): void
51+
{
52+
$data = [
53+
(object) [
54+
'form_id' => 1,
55+
'input_id' => 5,
56+
'input_type' => 'text',
57+
'input_label' => 'Custom EN',
58+
'input_active' => 1,
59+
'input_required' => 0,
60+
'input_lang' => 'en',
61+
],
62+
(object) [
63+
'form_id' => 1,
64+
'input_id' => 5,
65+
'input_type' => 'text',
66+
'input_label' => 'msgNewContentName',
67+
'input_active' => 1,
68+
'input_required' => 0,
69+
'input_lang' => 'default',
70+
],
71+
];
72+
$result = $this->helper->filterAndSortFormData($data, $this->translation);
73+
$this->assertCount(1, $result);
74+
$this->assertSame('en', $result[0]->input_lang);
75+
$this->assertSame('Custom EN', $result[0]->input_label);
76+
}
77+
78+
public function testDefaultIncludedIfCurrentMissing(): void
79+
{
80+
$data = [
81+
(object) [
82+
'form_id' => 1,
83+
'input_id' => 10,
84+
'input_type' => 'text',
85+
'input_label' => 'msgNewContentName',
86+
'input_active' => 1,
87+
'input_required' => 0,
88+
'input_lang' => 'default',
89+
],
90+
(object) [
91+
'form_id' => 1,
92+
'input_id' => 11,
93+
'input_type' => 'text',
94+
'input_label' => 'ad_sess_pageviews',
95+
'input_active' => 1,
96+
'input_required' => 0,
97+
'input_lang' => 'default',
98+
],
99+
];
100+
$result = $this->helper->filterAndSortFormData($data, $this->translation);
101+
$this->assertCount(2, $result);
102+
$this->assertSame(10, $result[0]->input_id);
103+
$this->assertSame(11, $result[1]->input_id);
104+
}
105+
106+
public function testSortingAscending(): void
107+
{
108+
$data = [
109+
(object) ['form_id' => 1, 'input_id' => 3, 'input_type' => 'text', 'input_label' => 'X', 'input_active' => 1, 'input_required' => 0, 'input_lang' => 'en'],
110+
(object) ['form_id' => 1, 'input_id' => 1, 'input_type' => 'text', 'input_label' => 'A', 'input_active' => 1, 'input_required' => 0, 'input_lang' => 'en'],
111+
(object) ['form_id' => 1, 'input_id' => 2, 'input_type' => 'text', 'input_label' => 'B', 'input_active' => 1, 'input_required' => 0, 'input_lang' => 'en'],
112+
];
113+
$result = $this->helper->filterAndSortFormData($data, $this->translation);
114+
$this->assertSame([1,2,3], array_map(static fn($o) => $o->input_id, $result));
115+
}
116+
}

0 commit comments

Comments
 (0)