User: Verify and improve Terms & Conditions and GDPR - refs #5121
parent
9b0e6fc6df
commit
bfa7400628
@ -0,0 +1,53 @@ |
|||||||
|
<template> |
||||||
|
<div class="base-editor"> |
||||||
|
<label v-if="title" :for="editorId">{{ title }}</label> |
||||||
|
<TinyEditor |
||||||
|
:id="editorId" |
||||||
|
:model-value="modelValue" |
||||||
|
:init="editorConfig" |
||||||
|
:required="required" |
||||||
|
@update:model-value="updateValue" |
||||||
|
@input="updateValue" |
||||||
|
/> |
||||||
|
<p v-if="helpText" class="help-text">{{ helpText }}</p> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref, computed, watch } from 'vue' |
||||||
|
const props = defineProps({ |
||||||
|
editorId: String, |
||||||
|
modelValue: String, |
||||||
|
required: Boolean, |
||||||
|
editorConfig: Object, |
||||||
|
title: String, |
||||||
|
helpText: String |
||||||
|
}) |
||||||
|
const emit = defineEmits(['update:modelValue']) |
||||||
|
const updateValue = (value) => { |
||||||
|
emit('update:modelValue', value) |
||||||
|
document.getElementById(props.editorId).value = value |
||||||
|
} |
||||||
|
const defaultEditorConfig = { |
||||||
|
skin_url: '/build/libs/tinymce/skins/ui/oxide', |
||||||
|
content_css: '/build/libs/tinymce/skins/content/default/content.css', |
||||||
|
branding: false, |
||||||
|
relative_urls: false, |
||||||
|
height: 280, |
||||||
|
toolbar_mode: 'sliding', |
||||||
|
file_picker_callback: browser, |
||||||
|
autosave_ask_before_unload: true, |
||||||
|
plugins: [ |
||||||
|
'fullpage advlist autolink lists link image charmap print preview anchor', |
||||||
|
'searchreplace visualblocks code fullscreen', |
||||||
|
'insertdatetime media table paste wordcount emoticons', |
||||||
|
], |
||||||
|
toolbar: 'undo redo | bold italic underline strikethrough | ...', |
||||||
|
} |
||||||
|
const editorConfig = computed(() => ({ |
||||||
|
...defaultEditorConfig, |
||||||
|
...props.editorConfig |
||||||
|
})) |
||||||
|
function browser(callback, value, meta) { |
||||||
|
} |
||||||
|
</script> |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
export default { |
||||||
|
path: '/resources/terms-conditions', |
||||||
|
meta: { requiresAuth: true }, |
||||||
|
name: 'TermsConditions', |
||||||
|
component: () => import('../views/terms/TermsLayout.vue'), |
||||||
|
children: [ |
||||||
|
{ |
||||||
|
name: 'TermsConditionsList', |
||||||
|
path: '', |
||||||
|
component: () => import('../views/terms/TermsList.vue') |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'TermsConditionsEdit', |
||||||
|
path: 'edit', |
||||||
|
component: () => import('../views/terms/TermsEdit.vue') |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
@ -0,0 +1,20 @@ |
|||||||
|
import makeService from './api' |
||||||
|
import { ENTRYPOINT } from "../config/entrypoint" |
||||||
|
|
||||||
|
const legalExtensions = { |
||||||
|
async findAllAvailable() { |
||||||
|
const url = new URL(`${ENTRYPOINT}languages`) |
||||||
|
url.searchParams.append("available", "true") |
||||||
|
try { |
||||||
|
const response = await fetch(url.toString()) |
||||||
|
if (!response.ok) { |
||||||
|
throw new Error('Network response was not ok') |
||||||
|
} |
||||||
|
return await response.json() |
||||||
|
} catch (error) { |
||||||
|
console.error('Error fetching available languages:', error) |
||||||
|
throw error |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
export default makeService('languages', legalExtensions) |
||||||
@ -0,0 +1,37 @@ |
|||||||
|
import makeService from './api'; |
||||||
|
import { ENTRYPOINT } from "../config/entrypoint" |
||||||
|
|
||||||
|
const legalExtensions = { |
||||||
|
async findAllByLanguage(languageId) { |
||||||
|
const params = new URLSearchParams({ |
||||||
|
languageId: languageId, |
||||||
|
'order[version]': 'desc' |
||||||
|
}); |
||||||
|
return fetch(`${ENTRYPOINT}legals?${params.toString()}`); |
||||||
|
}, |
||||||
|
async saveOrUpdateLegal(payload) { |
||||||
|
console.log('Saving or updating legal terms'); |
||||||
|
return fetch(`/legal/save`, { |
||||||
|
method: 'POST', |
||||||
|
headers: { |
||||||
|
'Content-Type': 'application/json' |
||||||
|
}, |
||||||
|
body: JSON.stringify(payload) |
||||||
|
}); |
||||||
|
}, |
||||||
|
async fetchExtraFields(termId = null) { |
||||||
|
try { |
||||||
|
const url = termId ? `/legal/extra-fields?termId=${termId}` : `/legal/extra-fields`; |
||||||
|
const response = await fetch(url); |
||||||
|
if (!response.ok) { |
||||||
|
throw new Error('Network response was not ok'); |
||||||
|
} |
||||||
|
return await response.json(); |
||||||
|
} catch (error) { |
||||||
|
console.error('Error loading extra fields:', error); |
||||||
|
throw error; |
||||||
|
} |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
export default makeService('legals', legalExtensions); |
||||||
@ -0,0 +1,196 @@ |
|||||||
|
<template> |
||||||
|
<div class="terms-edit-view mb-8"> |
||||||
|
|
||||||
|
<Message severity="info" icon="pi pi-send" :closable="false" class="mt-5"> |
||||||
|
{{ t('Display a Terms & Conditions statement on the registration page, require visitor to accept the T&C to register') }} |
||||||
|
</Message> |
||||||
|
|
||||||
|
<BaseToolbar showTopBorder> |
||||||
|
<div class="flex justify-between w-full items-center"> |
||||||
|
<BaseDropdown |
||||||
|
class="w-96 mb-0" |
||||||
|
:options="languages" |
||||||
|
v-model="selectedLanguage" |
||||||
|
optionLabel="name" |
||||||
|
placeholder="Select a language" |
||||||
|
inputId="language-dropdown" |
||||||
|
label="Language" |
||||||
|
name="language" |
||||||
|
/> |
||||||
|
<BaseButton :label="t('Load')" @click="loadTermsByLanguage" icon="search" type="button" class="ml-4"/> |
||||||
|
<BaseButton :label="t('All versions')" type="secondary" @click="backToList" icon="back" class="ml-4" /> |
||||||
|
</div> |
||||||
|
</BaseToolbar> |
||||||
|
|
||||||
|
<div v-if="termsLoaded"> |
||||||
|
<form @submit.prevent="saveTerms"> |
||||||
|
<BaseEditor |
||||||
|
:editorId="'item_content'" |
||||||
|
v-model="termData.content" |
||||||
|
:title="t('Personal Data Collection')" |
||||||
|
> |
||||||
|
<template #help-text> |
||||||
|
<p>{{ t('Why do we collect this data?') }}</p> |
||||||
|
</template> |
||||||
|
</BaseEditor> |
||||||
|
|
||||||
|
|
||||||
|
<BaseRadioButtons |
||||||
|
:options="typeOptions" |
||||||
|
v-model="termData.type" |
||||||
|
name="termsType" |
||||||
|
:title="t('Type of Terms')" |
||||||
|
/> |
||||||
|
|
||||||
|
<Dialog v-model:visible="dialogVisible" :style="{ width: '50vw' }" :header="t('Preview')" :modal="true"> |
||||||
|
<div v-html="previewContent" /> |
||||||
|
</Dialog> |
||||||
|
|
||||||
|
<!-- Extra fields --> |
||||||
|
<div v-for="field in extraFields" :key="field.id" class="extra-field"> |
||||||
|
<component :is="getFieldComponent(field.type)" v-bind="field.props" @update:modelValue="field.props.modelValue = $event"> |
||||||
|
<template v-if="field.type === 'editor'" #help-text> |
||||||
|
<p>{{ field.props.helpText }}</p> |
||||||
|
</template> |
||||||
|
</component> |
||||||
|
</div> |
||||||
|
|
||||||
|
<BaseTextArea |
||||||
|
id="changes" |
||||||
|
label="Explain changes" |
||||||
|
v-model="termData.changes" |
||||||
|
/> |
||||||
|
|
||||||
|
<div class="form-actions"> |
||||||
|
<BaseButton label="Back" type="secondary" @click="backToList" icon="back" class="mr-4" /> |
||||||
|
<BaseButton label="Preview" type="primary" @click="previewTerms" icon="search" class="mr-4" /> |
||||||
|
<BaseButton label="Save" type="success" isSubmit icon="save" class="mr-4" /> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { onMounted, ref, watch } from "vue" |
||||||
|
import { useRouter } from "vue-router" |
||||||
|
import BaseToolbar from "../../components/basecomponents/BaseToolbar.vue" |
||||||
|
import BaseButton from "../../components/basecomponents/BaseButton.vue" |
||||||
|
import BaseTextArea from "../../components/basecomponents/BaseTextArea.vue" |
||||||
|
import Message from "primevue/message" |
||||||
|
import BaseDropdown from "../../components/basecomponents/BaseDropdown.vue" |
||||||
|
import BaseRadioButtons from "../../components/basecomponents/BaseRadioButtons.vue" |
||||||
|
import BaseInputText from "../../components/basecomponents/BaseInputText.vue" |
||||||
|
import BaseEditor from "../../components/basecomponents/BaseEditor.vue" |
||||||
|
import { useI18n } from "vue-i18n" |
||||||
|
import languageService from "../../services/languageService" |
||||||
|
import legalService from "../../services/legalService" |
||||||
|
|
||||||
|
const { t } = useI18n() |
||||||
|
|
||||||
|
const router = useRouter() |
||||||
|
const languages = ref([]) |
||||||
|
const selectedLanguage = ref(null) |
||||||
|
const termsLoaded = ref(false) |
||||||
|
const termData = ref({ |
||||||
|
language: '', |
||||||
|
content: '', |
||||||
|
type: '0', |
||||||
|
changes: '', |
||||||
|
}) |
||||||
|
const dialogVisible = ref(false) |
||||||
|
const previewContent = ref('') |
||||||
|
const typeOptions = ref([ |
||||||
|
{ label: 'HTML', value: '0' }, |
||||||
|
{ label: 'Page Link', value: '1' } |
||||||
|
]) |
||||||
|
const loadTermsByLanguage = async () => { |
||||||
|
if (!selectedLanguage.value) return |
||||||
|
termsLoaded.value = false |
||||||
|
try { |
||||||
|
const response = await legalService.findAllByLanguage(selectedLanguage.value.id) |
||||||
|
if (response.ok) { |
||||||
|
const data = await response.json() |
||||||
|
const latestTerm = data['hydra:member'].length ? data['hydra:member'][0] : null |
||||||
|
termData.value = latestTerm ? { |
||||||
|
id: latestTerm.id, |
||||||
|
content: latestTerm.content, |
||||||
|
type: latestTerm.type.toString(), |
||||||
|
changes: latestTerm.changes, |
||||||
|
} : { |
||||||
|
content: '', |
||||||
|
type: '0', |
||||||
|
changes: '', |
||||||
|
} |
||||||
|
extraFields.value = await legalService.fetchExtraFields(latestTerm ? latestTerm.id : null) |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
console.error('Error loading terms:', error) |
||||||
|
} finally { |
||||||
|
termsLoaded.value = true |
||||||
|
} |
||||||
|
} |
||||||
|
const saveTerms = async () => { |
||||||
|
const payload = { |
||||||
|
lang: selectedLanguage.value.id, |
||||||
|
content: termData.value.content, |
||||||
|
type: termData.value.type.toString(), |
||||||
|
changes: termData.value.changes, |
||||||
|
extraFields: {}, |
||||||
|
} |
||||||
|
extraFields.value.forEach(field => { |
||||||
|
payload.extraFields[field.id] = field.props.modelValue |
||||||
|
}) |
||||||
|
try { |
||||||
|
const response = await legalService.saveOrUpdateLegal(payload) |
||||||
|
if (response.ok) { |
||||||
|
await router.push({ name: 'TermsConditionsList' }) |
||||||
|
} else { |
||||||
|
console.error('Error saving or updating legal terms:', response.statusText) |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
console.error('Error when making request:', error) |
||||||
|
} |
||||||
|
} |
||||||
|
const previewTerms = () => { |
||||||
|
previewContent.value = termData.value.content |
||||||
|
dialogVisible.value = true |
||||||
|
} |
||||||
|
const closePreview = () => { |
||||||
|
dialogVisible.value = false |
||||||
|
} |
||||||
|
function backToList() { |
||||||
|
router.push({ name: 'TermsConditionsList' }) |
||||||
|
} |
||||||
|
|
||||||
|
const extraFields = ref([]) |
||||||
|
|
||||||
|
function getFieldComponent(type) { |
||||||
|
const componentMap = { |
||||||
|
text: BaseInputText, |
||||||
|
select: BaseDropdown, |
||||||
|
editor: BaseEditor, |
||||||
|
// Add more mappings as needed |
||||||
|
} |
||||||
|
return componentMap[type] || 'div' |
||||||
|
} |
||||||
|
|
||||||
|
watch(selectedLanguage, () => { |
||||||
|
termsLoaded.value = false |
||||||
|
}) |
||||||
|
onMounted(async () => { |
||||||
|
try { |
||||||
|
const response = await languageService.findAll() |
||||||
|
if (!response.ok) { |
||||||
|
throw new Error('Network response was not ok') |
||||||
|
} |
||||||
|
const data = await response.json() |
||||||
|
languages.value = data['hydra:member'].map(lang => ({ |
||||||
|
name: lang.englishName, |
||||||
|
id: lang.id, |
||||||
|
})) |
||||||
|
} catch (error) { |
||||||
|
console.error('Error loading languages:', error) |
||||||
|
} |
||||||
|
}) |
||||||
|
</script> |
||||||
@ -0,0 +1,20 @@ |
|||||||
|
<template> |
||||||
|
<div class="terms-layout"> |
||||||
|
<header> |
||||||
|
<h1>{{ t('Terms and conditions') }}</h1> |
||||||
|
</header> |
||||||
|
|
||||||
|
<main> |
||||||
|
<router-view /> |
||||||
|
</main> |
||||||
|
|
||||||
|
<footer> |
||||||
|
</footer> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { useI18n } from "vue-i18n" |
||||||
|
const { t } = useI18n() |
||||||
|
|
||||||
|
</script> |
||||||
@ -0,0 +1,111 @@ |
|||||||
|
<template> |
||||||
|
<div class="terms-list-view mt-4"> |
||||||
|
<BaseToolbar> |
||||||
|
<BaseButton |
||||||
|
:label="t('Edit Terms and Conditions')" |
||||||
|
icon="edit" |
||||||
|
type="primary" |
||||||
|
@click="editTerms" |
||||||
|
/> |
||||||
|
</BaseToolbar> |
||||||
|
|
||||||
|
<Message severity="warn" :closable="false"> |
||||||
|
{{ t('You should create the Term and Conditions for all the available languages.') }} |
||||||
|
</Message> |
||||||
|
|
||||||
|
<DataTable :value="terms" :loading="isLoading"> |
||||||
|
<Column field="version" header="Version"></Column> |
||||||
|
<Column field="language" header="Language"></Column> |
||||||
|
|
||||||
|
<Column header="Content"> |
||||||
|
<template #body="slotProps"> |
||||||
|
<div v-html="slotProps.data.content"></div> |
||||||
|
</template> |
||||||
|
</Column> |
||||||
|
|
||||||
|
<Column field="changes" header="Changes"></Column> |
||||||
|
<Column field="typeLabel" header="Type"></Column> |
||||||
|
<Column field="date" header="Date"> |
||||||
|
<template #body="slotProps"> |
||||||
|
{{ formatDate(slotProps.data.date) }} |
||||||
|
</template> |
||||||
|
</Column> |
||||||
|
</DataTable> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { onMounted, ref } from "vue" |
||||||
|
import DataTable from "primevue/datatable" |
||||||
|
import Column from "primevue/column" |
||||||
|
import BaseToolbar from "../../components/basecomponents/BaseToolbar.vue" |
||||||
|
import BaseButton from "../../components/basecomponents/BaseButton.vue" |
||||||
|
import { useI18n } from "vue-i18n" |
||||||
|
import { useRouter } from "vue-router" |
||||||
|
import Message from "primevue/message" |
||||||
|
import languageService from "../../services/languageService" |
||||||
|
import legalService from "../../services/legalService" |
||||||
|
|
||||||
|
const { t } = useI18n() |
||||||
|
const router = useRouter() |
||||||
|
const terms = ref([]) |
||||||
|
const isLoading = ref(false) |
||||||
|
async function fetchLanguageName(languageId) { |
||||||
|
try { |
||||||
|
const response = await languageService.find("/api/languages/" + languageId) |
||||||
|
if (response.ok) { |
||||||
|
const languageData = await response.json() |
||||||
|
return languageData.originalName |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
console.error("Error loading language details:", error) |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
onMounted(async () => { |
||||||
|
isLoading.value = true |
||||||
|
try { |
||||||
|
const response = await legalService.findAll() |
||||||
|
if (response.ok) { |
||||||
|
const data = await response.json() |
||||||
|
terms.value = await Promise.all(data['hydra:member'].map(async (term) => { |
||||||
|
const languageName = await fetchLanguageName(term.languageId) |
||||||
|
return { |
||||||
|
...term, |
||||||
|
language: languageName, |
||||||
|
typeLabel: getTypeLabel(term.type), |
||||||
|
} |
||||||
|
})) |
||||||
|
} else { |
||||||
|
console.error("The request to the API was not successful:", response.statusText) |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
console.error("Error loading legal terms:", error) |
||||||
|
} finally { |
||||||
|
isLoading.value = false |
||||||
|
} |
||||||
|
}) |
||||||
|
function getTypeLabel(typeValue) { |
||||||
|
const typeMap = { |
||||||
|
'0': t('HTML'), |
||||||
|
'1': t('Page Link'), |
||||||
|
} |
||||||
|
return typeMap[typeValue] || 'Unknown' |
||||||
|
} |
||||||
|
|
||||||
|
function formatDate(timestamp) { |
||||||
|
const date = new Date(timestamp * 1000) |
||||||
|
const day = date.getDate().toString().padStart(2, '0') |
||||||
|
const month = (date.getMonth() + 1).toString().padStart(2, '0') |
||||||
|
const year = date.getFullYear() |
||||||
|
const hours = date.getHours().toString().padStart(2, '0') |
||||||
|
const minutes = date.getMinutes().toString().padStart(2, '0') |
||||||
|
const seconds = date.getSeconds().toString().padStart(2, '0') |
||||||
|
return `${day}/${month}/${year} ${hours}:${minutes}:${seconds}` |
||||||
|
} |
||||||
|
|
||||||
|
function editTerms() { |
||||||
|
router.push({ name: 'TermsConditionsEdit' }) |
||||||
|
} |
||||||
|
</script> |
||||||
@ -0,0 +1,192 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
/* For licensing terms, see /license.txt */ |
||||||
|
|
||||||
|
namespace Chamilo\CoreBundle\Controller; |
||||||
|
|
||||||
|
use Chamilo\CoreBundle\Entity\Legal; |
||||||
|
use Chamilo\CoreBundle\Repository\LegalRepository; |
||||||
|
use Doctrine\ORM\EntityManagerInterface; |
||||||
|
use ExtraField; |
||||||
|
use ExtraFieldValue; |
||||||
|
use LegalManager; |
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse; |
||||||
|
use Symfony\Component\HttpFoundation\Request; |
||||||
|
use Symfony\Component\HttpFoundation\Response; |
||||||
|
use Symfony\Component\Routing\Annotation\Route; |
||||||
|
|
||||||
|
#[Route('/legal')] |
||||||
|
class LegalController |
||||||
|
{ |
||||||
|
#[Route('/save', name: 'chamilo_core_legal_save', methods: ['POST'])] |
||||||
|
public function saveLegal( |
||||||
|
Request $request, |
||||||
|
EntityManagerInterface $entityManager, |
||||||
|
LegalRepository $legalRepository |
||||||
|
): Response { |
||||||
|
$data = json_decode($request->getContent(), true); |
||||||
|
|
||||||
|
$lang = $data['lang'] ?? null; |
||||||
|
$content = $data['content'] ?? null; |
||||||
|
$type = isset($data['type']) ? (int)$data['type'] : null; |
||||||
|
$changes = $data['changes'] ?? ''; |
||||||
|
$extraFields = $data['extraFields'] ?? []; |
||||||
|
|
||||||
|
$lastLegal = $legalRepository->findLastConditionByLanguage($lang); |
||||||
|
$extraFieldValue = new ExtraFieldValue('terms_and_condition'); |
||||||
|
|
||||||
|
$newVersionRequired = !$lastLegal || $lastLegal->getContent() !== $content || $this->hasExtraFieldsChanged($extraFieldValue, $lastLegal->getId(), $extraFields); |
||||||
|
$typeUpdateRequired = $lastLegal && $lastLegal->getType() !== $type; |
||||||
|
|
||||||
|
$legalToUpdate = $lastLegal; |
||||||
|
if ($newVersionRequired) { |
||||||
|
$legal = new Legal(); |
||||||
|
$legal->setLanguageId($lang); |
||||||
|
$legal->setContent($content); |
||||||
|
$legal->setType($type); |
||||||
|
$legal->setChanges($changes); |
||||||
|
$legal->setDate(time()); |
||||||
|
$version = $lastLegal ? $lastLegal->getVersion() + 1 : 1; |
||||||
|
$legal->setVersion($version); |
||||||
|
|
||||||
|
$entityManager->persist($legal); |
||||||
|
$legalToUpdate = $legal; |
||||||
|
} elseif ($typeUpdateRequired) { |
||||||
|
$lastLegal->setType($type); |
||||||
|
$lastLegal->setChanges($changes); |
||||||
|
} |
||||||
|
|
||||||
|
$entityManager->flush(); |
||||||
|
|
||||||
|
if ($newVersionRequired || $typeUpdateRequired) { |
||||||
|
$this->updateExtraFields($extraFieldValue, $legalToUpdate->getId(), $extraFields); |
||||||
|
} |
||||||
|
|
||||||
|
return new Response('Term and condition saved or updated successfully', Response::HTTP_OK); |
||||||
|
} |
||||||
|
|
||||||
|
#[Route('/extra-fields', name: 'chamilo_core_get_extra_fields')] |
||||||
|
public function getExtraFields(Request $request): JsonResponse |
||||||
|
{ |
||||||
|
|
||||||
|
$extraField = new ExtraField('terms_and_condition'); |
||||||
|
$types = LegalManager::getTreatmentTypeList(); |
||||||
|
|
||||||
|
foreach ($types as $variable => $name) { |
||||||
|
$label = 'PersonalData'.ucfirst($name).'Title'; |
||||||
|
$params = [ |
||||||
|
'variable' => $variable, |
||||||
|
'display_text' => $label, |
||||||
|
'value_type' => ExtraField::FIELD_TYPE_TEXTAREA, |
||||||
|
'default_value' => '', |
||||||
|
'visible' => true, |
||||||
|
'changeable' => true, |
||||||
|
'filter' => true, |
||||||
|
'visible_to_self' => true, |
||||||
|
'visible_to_others' => true, |
||||||
|
]; |
||||||
|
$extraField->save($params); |
||||||
|
} |
||||||
|
|
||||||
|
$termId = $request->query->get('termId'); |
||||||
|
$extraData = $extraField->get_handler_extra_data($termId ?? 0); |
||||||
|
$extraFieldsDefinition = $extraField->get_all(); |
||||||
|
$fieldsData = $extraField->getExtraFieldsData( |
||||||
|
$extraData, |
||||||
|
true, |
||||||
|
$extraFieldsDefinition |
||||||
|
); |
||||||
|
|
||||||
|
$prefix = 'extra_'; |
||||||
|
$extraFields = []; |
||||||
|
foreach ($fieldsData as $field) { |
||||||
|
$fieldType = $this->mapFieldType($field['type']); |
||||||
|
$extraField = [ |
||||||
|
'id' => $prefix.$field['variable'], |
||||||
|
'type' => $fieldType, |
||||||
|
'props' => [ |
||||||
|
'title' => $field['title'], |
||||||
|
'defaultValue' => $field['defaultValue'], |
||||||
|
], |
||||||
|
]; |
||||||
|
|
||||||
|
switch ($fieldType) { |
||||||
|
case 'editor': |
||||||
|
$extraField['props']['editorId'] = $prefix.$field['variable']; |
||||||
|
$extraField['props']['modelValue'] = $field['value'] ?? ''; |
||||||
|
$extraField['props']['helpText'] = 'Specific help text for ' . $field['title']; |
||||||
|
break; |
||||||
|
case 'text': |
||||||
|
$extraField['props']['label'] = $field['title']; |
||||||
|
$extraField['props']['modelValue'] = $field['value'] ?? ''; |
||||||
|
break; |
||||||
|
case 'select': |
||||||
|
$extraField['props']['label'] = $field['title']; |
||||||
|
$extraField['props']['options'] = []; |
||||||
|
$extraField['props']['modelValue'] = $field['value'] ?? ''; |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
$extraFields[] = $extraField; |
||||||
|
} |
||||||
|
|
||||||
|
return new JsonResponse($extraFields); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if the extra field values have changed. |
||||||
|
* |
||||||
|
* This function compares the new values for extra fields against the old ones to determine |
||||||
|
* if there have been any changes. It is useful for triggering events or updates only when |
||||||
|
* actual changes to data occur. |
||||||
|
*/ |
||||||
|
private function hasExtraFieldsChanged(ExtraFieldValue $extraFieldValue, int $legalId, array $newValues): bool |
||||||
|
{ |
||||||
|
$oldValues = $extraFieldValue->getAllValuesByItem($legalId); |
||||||
|
$oldValues = array_column($oldValues, 'value', 'variable'); |
||||||
|
|
||||||
|
foreach ($newValues as $key => $newValue) { |
||||||
|
if (isset($oldValues[$key]) && $newValue != $oldValues[$key]) { |
||||||
|
return true; |
||||||
|
} elseif (!isset($oldValues[$key])) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Updates the extra fields with new values for a specific item. |
||||||
|
*/ |
||||||
|
private function updateExtraFields(ExtraFieldValue $extraFieldValue, int $legalId, array $values): void |
||||||
|
{ |
||||||
|
$values['item_id'] = $legalId; |
||||||
|
$extraFieldValue->saveFieldValues($values); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Maps an integer representing a field type to its corresponding string value. |
||||||
|
*/ |
||||||
|
private function mapFieldType(int $type): string |
||||||
|
{ |
||||||
|
switch ($type) { |
||||||
|
case ExtraField::FIELD_TYPE_TEXT: |
||||||
|
return 'text'; |
||||||
|
case ExtraField::FIELD_TYPE_TEXTAREA: |
||||||
|
return 'editor'; |
||||||
|
case ExtraField::FIELD_TYPE_SELECT_MULTIPLE: |
||||||
|
case ExtraField::FIELD_TYPE_DATE: |
||||||
|
case ExtraField::FIELD_TYPE_DATETIME: |
||||||
|
case ExtraField::FIELD_TYPE_DOUBLE_SELECT: |
||||||
|
case ExtraField::FIELD_TYPE_RADIO: |
||||||
|
// Manage as needed |
||||||
|
break; |
||||||
|
case ExtraField::FIELD_TYPE_SELECT: |
||||||
|
return 'select'; |
||||||
|
} |
||||||
|
|
||||||
|
return 'text'; |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue