Skip to content

Commit 013b779

Browse files
committed
feat: added permission to translate FAQs, closes #3499
1 parent 548e696 commit 013b779

File tree

10 files changed

+75
-67
lines changed

10 files changed

+75
-67
lines changed

phpmyfaq/admin/assets/src/api/faqs.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
*/
1515

1616
import { Response } from '../interfaces';
17-
import { FaqResponse } from '../interfaces';
17+
import { FaqList } from '../interfaces';
1818

1919
export const fetchAllFaqsByCategory = async (
2020
categoryId: string,
2121
language: string,
2222
onlyInactive?: boolean,
2323
onlyNew?: boolean
24-
): Promise<FaqResponse> => {
24+
): Promise<FaqList> => {
2525
try {
2626
let currentUrl: string = window.location.protocol + '//' + window.location.host;
2727
let pathname: string = window.location.pathname;

phpmyfaq/admin/assets/src/content/faqs.overview.ts

Lines changed: 44 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,7 @@
1616
import { Modal } from 'bootstrap';
1717
import { deleteFaq, fetchAllFaqsByCategory, fetchCategoryTranslations } from '../api';
1818
import { addElement, pushErrorNotification, pushNotification } from '../../../../assets/src/utils';
19-
import { CategoryTranslations, FaqResponse, Response } from '../interfaces';
20-
21-
interface Faq {
22-
id: string;
23-
language: string;
24-
solution_id: string;
25-
question: string;
26-
created: string;
27-
category_id: string;
28-
sticky: string;
29-
active: string;
30-
}
19+
import { CategoryTranslations, Faq, FaqList, Response } from '../interfaces';
3120

3221
export const handleFaqOverview = async (): Promise<void> => {
3322
const collapsedCategories: NodeListOf<Element> = document.querySelectorAll('.accordion-collapse');
@@ -47,8 +36,8 @@ export const handleFaqOverview = async (): Promise<void> => {
4736
const onlyInactive: boolean = getInactiveCheckboxState();
4837
const onlyNew: boolean = getNewCheckboxState();
4938

50-
const faqs = (await fetchAllFaqsByCategory(categoryId, language, onlyInactive, onlyNew)) as FaqResponse;
51-
await populateCategoryTable(categoryId, faqs.faqs);
39+
const faqs = (await fetchAllFaqsByCategory(categoryId, language, onlyInactive, onlyNew)) as FaqList;
40+
await populateCategoryTable(categoryId, faqs.faqs, faqs.isAllowedToTranslate);
5241
const toggleStickyFaq: NodeListOf<HTMLInputElement> = document.querySelectorAll('.pmf-admin-sticky-faq');
5342
const toggleActiveFaq: NodeListOf<HTMLInputElement> = document.querySelectorAll('.pmf-admin-active-faq');
5443
const translationDropdown: NodeListOf<HTMLElement> = document.querySelectorAll('#dropdownAddNewTranslation');
@@ -211,20 +200,20 @@ const saveStatus = async (
211200
}
212201
};
213202

214-
const populateCategoryTable = async (categoryId: string, faqs: Faq[]): Promise<void> => {
203+
const populateCategoryTable = async (categoryId: string, faqs: Faq[], isAllowedToTranslate: boolean): Promise<void> => {
215204
const tableBody = document.getElementById(`tbody-category-id-${categoryId}`) as HTMLElement;
216205
const csrfToken = tableBody.getAttribute('data-pmf-csrf') as string;
217206

218207
faqs.forEach((faq: Faq): void => {
219208
const row: HTMLTableRowElement = document.createElement('tr');
220-
row.setAttribute('id', `faq_${faq.id}_${faq.language}`);
209+
row.setAttribute('id', `faq_${faq.id.toString()}_${faq.language}`);
221210

222211
row.append(
223212
addElement('td', { classList: 'align-middle text-center' }, [
224213
addElement('a', {
225214
classList: 'text-decoration-none',
226-
href: `./faq/edit/${faq.id}/${faq.language}`,
227-
innerText: faq.id,
215+
href: `./faq/edit/${faq.id.toString()}/${faq.language}`,
216+
innerText: faq.id.toString(),
228217
}),
229218
])
230219
);
@@ -233,16 +222,16 @@ const populateCategoryTable = async (categoryId: string, faqs: Faq[]): Promise<v
233222
addElement('td', { classList: 'align-middle text-center' }, [
234223
addElement('a', {
235224
classList: 'text-decoration-none',
236-
href: `./faq/edit/${faq.id}/${faq.language}`,
237-
innerText: faq.solution_id,
225+
href: `./faq/edit/${faq.id.toString()}/${faq.language}`,
226+
innerText: faq.solution_id.toString(),
238227
}),
239228
])
240229
);
241230
row.append(
242231
addElement('td', {}, [
243232
addElement('a', {
244233
classList: 'text-decoration-none',
245-
href: `./faq/edit/${faq.id}/${faq.language}`,
234+
href: `./faq/edit/${faq.id.toString()}/${faq.language}`,
246235
innerText: faq.question,
247236
}),
248237
])
@@ -253,11 +242,11 @@ const populateCategoryTable = async (categoryId: string, faqs: Faq[]): Promise<v
253242
addElement('input', {
254243
classList: 'form-check-input pmf-admin-sticky-faq',
255244
type: 'checkbox',
256-
'data-pmfCategoryIdSticky': faq.category_id,
257-
'data-pmfFaqId': faq.id,
245+
'data-pmfCategoryIdSticky': faq.category_id.toString(),
246+
'data-pmfFaqId': faq.id.toString(),
258247
'data-pmfCsrf': csrfToken,
259248
lang: faq.language,
260-
id: `sticky_record_${faq.category_id}_${faq.id}`,
249+
id: `sticky_record_${faq.category_id}_${faq.id.toString()}`,
261250
checked: faq.sticky === 'yes',
262251
}),
263252
])
@@ -267,68 +256,70 @@ const populateCategoryTable = async (categoryId: string, faqs: Faq[]): Promise<v
267256
addElement('input', {
268257
classList: 'form-check-input pmf-admin-active-faq',
269258
type: 'checkbox',
270-
'data-pmfCategoryIdActive': faq.category_id,
271-
'data-pmfFaqId': faq.id,
259+
'data-pmfCategoryIdActive': faq.category_id.toString(),
260+
'data-pmfFaqId': faq.id.toString(),
272261
'data-pmfCsrf': csrfToken,
273262
lang: faq.language,
274-
id: `active_record_${faq.category_id}_${faq.id}`,
263+
id: `active_record_${faq.category_id.toString()}_${faq.id.toString()}`,
275264
checked: faq.active === 'yes',
276265
}),
277266
])
278267
);
279268
row.append(
280269
addElement('td', { classList: 'align-middle text-center' }, [
281-
addElement('a', { classList: 'btn btn-primary', href: `./faq/edit/${faq.id}/${faq.language}` }, [
270+
addElement('a', { classList: 'btn btn-primary', href: `./faq/edit/${faq.id.toString()}/${faq.language}` }, [
282271
addElement('i', { classList: 'bi bi-pencil', 'aria-hidden': 'true' }),
283272
]),
284273
])
285274
);
286275
row.append(
287276
addElement('td', { classList: 'align-middle text-center' }, [
288-
addElement('a', { classList: 'btn btn-info', href: `./faq/copy/${faq.id}/${faq.language}` }, [
277+
addElement('a', { classList: 'btn btn-info', href: `./faq/copy/${faq.id.toString()}/${faq.language}` }, [
289278
addElement('i', { classList: 'bi bi-copy', 'aria-hidden': 'true' }),
290279
]),
291280
])
292281
);
293-
row.append(
294-
addElement('td', { classList: 'align-middle text-center' }, [
295-
addElement('div', { classList: 'checkbox' }, [
296-
addElement(
297-
'a',
298-
{
299-
classList: 'btn btn-secondary dropdown-toggle',
300-
href: '#',
301-
role: 'button',
302-
id: 'dropdownAddNewTranslation',
303-
'data-bsToggle': 'dropdown',
304-
'aria-haspopup': 'true',
305-
'aria-expanded': 'false',
306-
'data-pmfFaqId': faq.id,
307-
},
308-
[addElement('i', { classList: 'bi bi-globe', 'aria-hidden': 'true' })]
309-
),
310-
addElement('div', { classList: 'dropdown-menu', 'aria-labelledby': 'dropdownAddNewTranslation' }, [
311-
addElement('a', { classList: 'dropdown-item', id: 'dropdownTranslation', innerText: 'n/a' }),
282+
if (isAllowedToTranslate) {
283+
row.append(
284+
addElement('td', { classList: 'align-middle text-center' }, [
285+
addElement('div', { classList: 'checkbox' }, [
286+
addElement(
287+
'a',
288+
{
289+
classList: 'btn btn-secondary dropdown-toggle',
290+
href: '#',
291+
role: 'button',
292+
id: 'dropdownAddNewTranslation',
293+
'data-bsToggle': 'dropdown',
294+
'aria-haspopup': 'true',
295+
'aria-expanded': 'false',
296+
'data-pmfFaqId': faq.id.toString(),
297+
},
298+
[addElement('i', { classList: 'bi bi-globe', 'aria-hidden': 'true' })]
299+
),
300+
addElement('div', { classList: 'dropdown-menu', 'aria-labelledby': 'dropdownAddNewTranslation' }, [
301+
addElement('a', { classList: 'dropdown-item', id: 'dropdownTranslation', innerText: 'n/a' }),
302+
]),
312303
]),
313-
]),
314-
])
315-
);
304+
])
305+
);
306+
}
316307
row.append(
317308
addElement('td', { classList: 'text-center' }, [
318309
addElement(
319310
'button',
320311
{
321312
classList: 'btn btn-danger pmf-button-delete-faq',
322313
type: 'button',
323-
'data-pmfId': faq.id,
314+
'data-pmfId': faq.id.toString(),
324315
'data-pmfLanguage': faq.language,
325316
'data-pmfToken': csrfToken,
326317
},
327318
[
328319
addElement('i', {
329320
classList: 'bi bi-trash',
330321
'aria-hidden': 'true',
331-
'data-pmfId': faq.id,
322+
'data-pmfId': faq.id.toString(),
332323
'data-pmfLanguage': faq.language,
333324
'data-pmfToken': csrfToken,
334325
}),

phpmyfaq/admin/assets/src/interfaces/faqResponse.ts renamed to phpmyfaq/admin/assets/src/interfaces/FaqList.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
interface Faq {
1+
export interface Faq {
22
id: number;
33
language: string;
44
solution_id: number;
@@ -11,6 +11,7 @@ interface Faq {
1111
created: string; // Format: YYYY-MM-DD HH:MM:SS
1212
}
1313

14-
export interface FaqResponse {
14+
export interface FaqList {
1515
faqs: Faq[];
16+
isAllowedToTranslate: boolean;
1617
}

phpmyfaq/admin/assets/src/interfaces/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export * from './attachment';
22
export * from './categoryTranslations';
33
export * from './elasticsearch';
4-
export * from './faqResponse';
4+
export * from './faqList';
55
export * from './Group';
66
export * from './instance';
77
export * from './mediaBrowserApiResponse';

phpmyfaq/assets/src/utils/helper.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,17 @@ export const insertAfter = (referenceNode: Node, newNode: Node): void => {
2626

2727
/**
2828
* Creates a new element with the given tag name.
29-
* @param htmlTag
29+
* @param HTMLElement
3030
* @param properties
3131
* @param children
3232
* @returns {*}
3333
*/
3434
export const addElement = (
35-
htmlTag: string,
35+
HTMLElement: string,
3636
properties: Record<string, any> = {},
3737
children: Node[] = []
3838
): HTMLElement => {
39-
const element = Object.assign(document.createElement(htmlTag), properties);
39+
const element = Object.assign(document.createElement(HTMLElement), properties);
4040

4141
Object.keys(properties).forEach((key) => {
4242
if (key.startsWith('data-')) {

phpmyfaq/assets/templates/admin/content/faq.editor.twig

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
{% if faqData['notes'] != '' %}
2929
<div class="alert alert-info alert-dismissible fade show" role="alert">
30-
<h4 class="alert-heading">{{ 'ad_admin_notes' | translate }}</h4>
30+
<h6 class="alert-heading">{{ 'ad_admin_notes' | translate }}</h6>
3131
<p>{{ 'msgPrivateNotesOfEditor' | translate }} {{ faqData['notes'] }}</p>
3232
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
3333
</div>
@@ -189,6 +189,7 @@
189189
</div>
190190

191191
<!-- Language -->
192+
{% if hasPermissionForTranslateFaqs %}
192193
<div class="row mb-2">
193194
<label class="col-lg-2 col-form-label" for="lang">
194195
{{ 'msgLanguage' | translate }}
@@ -197,6 +198,9 @@
197198
{{ languageOptions | raw }}
198199
</div>
199200
</div>
201+
{% else %}
202+
<input type="hidden" name="lang" id="lang" value="{{ faqData['lang'] }}">
203+
{% endif %}
200204

201205
<!-- Attachments -->
202206
{% if hasPermissionForAddAttachments %}
@@ -223,7 +227,7 @@
223227
{% endfor %}
224228
</ul>
225229

226-
<button type="button" class="btn btn-primary mt-2" data-bs-toggle="modal"
230+
<button type="button" class="btn btn-primary" data-bs-toggle="modal"
227231
data-bs-target="#attachmentModal">
228232
{{ 'msgAddAttachment' | translate }}
229233
</button>

phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/FaqController.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
use DateTime;
2020
use Exception;
21+
use phpMyFAQ\Administration\Faq as FaqAdministration;
2122
use phpMyFAQ\Administration\Revision;
2223
use phpMyFAQ\Attachment\AttachmentException;
2324
use phpMyFAQ\Attachment\Filesystem\File\FileException;
@@ -520,12 +521,16 @@ public function listByCategory(Request $request): JsonResponse
520521
$onlyInactive = Filter::filterVar($request->query->get('only-inactive'), FILTER_VALIDATE_BOOLEAN, false);
521522
$onlyNew = Filter::filterVar($request->query->get('only-new'), FILTER_VALIDATE_BOOLEAN, false);
522523

523-
$faq = new \phpMyFAQ\Administration\Faq($this->configuration);
524+
$faq = new FaqAdministration($this->configuration);
524525
$faq->setLanguage($language);
525526

526527
return $this->json(
527528
[
528529
'faqs' => $faq->getAllFaqsByCategory($categoryId, $onlyInactive, $onlyNew),
530+
'isAllowedToTranslate' => $this->currentUser->perm->hasPermission(
531+
$this->currentUser->getUserId(),
532+
PermissionType::FAQ_TRANSLATE->value
533+
),
529534
],
530535
Response::HTTP_OK
531536
);
@@ -550,7 +555,7 @@ public function activate(Request $request): JsonResponse
550555
}
551556

552557
if (!($faqIds === false || $faqIds === [] || $faqIds === null)) {
553-
$faq = new \phpMyFAQ\Administration\Faq($this->configuration);
558+
$faq = new FaqAdministration($this->configuration);
554559
$success = false;
555560

556561
foreach ($faqIds as $faqId) {
@@ -588,7 +593,7 @@ public function sticky(Request $request): JsonResponse
588593
}
589594

590595
if (!($faqIds === false || $faqIds === [] || $faqIds === null)) {
591-
$faq = new \phpMyFAQ\Administration\Faq($this->configuration);
596+
$faq = new FaqAdministration($this->configuration);
592597
$success = false;
593598

594599
foreach ($faqIds as $faqId) {

phpmyfaq/src/phpMyFAQ/Controller/Administration/FaqController.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,11 @@ private function getBaseTemplateVars(): array
555555
PermissionType::ATTACHMENT_DELETE->value
556556
);
557557

558+
$canTranslateFaqs = $this->currentUser->perm->hasPermission(
559+
$this->currentUser->getUserId(),
560+
PermissionType::FAQ_TRANSLATE->value
561+
);
562+
558563
return [
559564
'csrfToken' => $token->getTokenString('edit-faq'),
560565
'csrfTokenDeleteAttachment' => $token->getTokenString('delete-attachment'),
@@ -567,6 +572,7 @@ private function getBaseTemplateVars(): array
567572
'maxAttachmentSize' => $this->configuration->get('records.maxAttachmentSize'),
568573
'hasPermissionForAddAttachments' => $canAddAttachments,
569574
'hasPermissionForDeleteAttachments' => $canDeleteAttachments,
575+
'hasPermissionForTranslateFaqs' => $canTranslateFaqs,
570576
'ad_entry_restricted_groups' => Translation::get('ad_entry_restricted_groups'),
571577
'ad_entry_userpermission' => Translation::get('ad_entry_userpermission'),
572578
'ad_entry_restricted_users' => Translation::get('ad_entry_restricted_users'),

phpmyfaq/translations/language_de.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1487,5 +1487,5 @@
14871487
$PMF_LANG['msgAdminOpenSearchCreateIndex_success'] = 'Der OpenSearch Suchindex erfolgreich erstellt.';
14881488
$PMF_LANG['ad_os_create_import_success'] = 'Der OpenSearch Import war erfolgreich.';
14891489
$PMF_LANG['msgAdminOpenSearchDropIndex_success'] = 'Der OpenSearch Suchindex erfolgreich gelöscht.';
1490-
1490+
$PMF_LANG['permission::translate_faq'] = 'FAQs übersetzen';
14911491
return $PMF_LANG;

phpmyfaq/translations/language_en.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,5 +1506,6 @@
15061506
$PMF_LANG['msgAdminOpenSearchCreateIndex_success'] = 'The OpenSearch search index was successfully created.';
15071507
$PMF_LANG['ad_os_create_import_success'] = 'The OpenSearch import was successful.';
15081508
$PMF_LANG['msgAdminOpenSearchDropIndex_success'] = 'The OpenSearch search index was successfully deleted.';
1509+
$PMF_LANG['permission::translate_faq'] = 'Translate FAQs';
15091510

15101511
return $PMF_LANG;

0 commit comments

Comments
 (0)