diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b465e8fcd3..9e305e91b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,6 +92,9 @@ jobs: - name: Create test database run: bin/console test:database:create --env=test + - name: Warmup cache + run: bin/console cache:clear --env=test + - name: Setup Yarn uses: threeal/setup-yarn-action@v2.0.0 with: diff --git a/assets/js/accommodation_widget.js b/assets/js/accommodation_widget.js new file mode 100644 index 0000000000..426c5cf327 --- /dev/null +++ b/assets/js/accommodation_widget.js @@ -0,0 +1,67 @@ +import {default as rangeSlider} from 'rangeslider-pure'; +import {trans} from './translator' + +export { initializeAccommodationWidget } + +const accommodationRadiobuttons = document.querySelectorAll(".js-accommodation"); +const hostingInterest = document.getElementById('hosting_interest'); +const radioHandler = (event) => { + if (event.target.type === 'radio') { + if (event.target.value === 'no') { + hostingInterest.classList.remove('u:block'); + hostingInterest.classList.add('u:hidden'); + } else { + hostingInterest.classList.remove('u:hidden'); + hostingInterest.classList.add('u:block'); + } + accommodationRadiobuttons.forEach( (radio) => { + radio.parentElement.classList.remove('u:bg-gray-400') + }) + event.target.parentElement.classList.add('u:bg-gray-400'); + } +} + +const markers = [ + trans('hosting_interest.select'), + trans('hosting_interest.very_low'), + trans('hosting_interest.low'), + trans('hosting_interest.lower'), + trans('hosting_interest.low_to_medium'), + trans('hosting_interest.medium'), + trans('hosting_interest.medium_to_high'), + trans('hosting_interest.high'), + trans('hosting_interest.higher'), + trans('hosting_interest.very_high'), + trans('hosting_interest.cant_wait') +] + +const slider = document.querySelectorAll('input[type="range"]'); + +function updateValueOutput(value) { + const valueOutput = document.getElementsByClassName('rangeSlider__value-output'); + if (valueOutput.length) { + valueOutput[0].innerHTML = markers[value]; + } +} + +const initializeHostingInterestSlider = () => { + return rangeSlider.create(slider, { + onInit: function () { + updateValueOutput(0); + }, + onSlide: function (value, percent, position) { + updateValueOutput(value); + } + }); +}; + +const initializeAccommodationRadioButtons = () => { + accommodationRadiobuttons.forEach( (radio) => { + radio.addEventListener("click", radioHandler) + }) +} + +const initializeAccommodationWidget = () => { + initializeAccommodationRadioButtons() + initializeHostingInterestSlider() +} diff --git a/assets/js/calendar.js b/assets/js/calendar.js new file mode 100644 index 0000000000..2d6cbe56ec --- /dev/null +++ b/assets/js/calendar.js @@ -0,0 +1,38 @@ +import { Calendar } from 'vanilla-calendar-pro'; +import 'vanilla-calendar-pro/styles/index.css'; +import * as dayjs from 'dayjs' + +export { initializeCalendar } + +const htmlTag = document.getElementsByTagName('html')[0]; +const lang = htmlTag.attributes['lang'].value; + +const minimumAge = dayjs().subtract(18, 'year'); +const maximumAge = minimumAge.subtract(122, 'year'); + +const options = { + inputMode: true, + type: 'default', + onChangeToInput(self) { + if (!self.context.inputElement) return; + if (self.context.selectedDates[0]) { + self.context.inputElement.value = self.context.selectedDates[0]; + self.hide(); + } else { + self.context.inputElement.value = ''; + } + }, + lang: lang, + dateMin: maximumAge.format('YYYY-MM-DD'), + dateMax: minimumAge.format('YYYY-MM-DD'), + positionToInput: 'auto', + selectedTheme: 'light', + disabledDates: [], + dateToday: minimumAge.toDate(), +}; + +const initializeCalendar = (id) => { + const calendarAnchor = document.getElementById(id); + const calendar = new Calendar(calendarAnchor, options); + calendar.init(); +} \ No newline at end of file diff --git a/assets/js/profile/account.js b/assets/js/profile/account.js new file mode 100644 index 0000000000..1e7886fc33 --- /dev/null +++ b/assets/js/profile/account.js @@ -0,0 +1,3 @@ +import { initializeCalendar} from "../calendar"; + +initializeCalendar('account_edit_form_birthdate') diff --git a/assets/js/profile/edit_accommodation.js b/assets/js/profile/edit_accommodation.js index dfe7f2e577..29c22b02d7 100644 --- a/assets/js/profile/edit_accommodation.js +++ b/assets/js/profile/edit_accommodation.js @@ -1,41 +1,78 @@ -import { default as rangeSlider } from 'rangeslider-pure'; +import { initializeAccommodationWidget } from "../accommodation_widget"; -const slider = document.querySelectorAll('input[type="range"]'); +const hostingInterest = document.getElementById('accommodation_form_hosting_interest') -function updateValueOutput(value) { - const valueOutput = document.getElementsByClassName('rangeSlider__value-output'); - if (valueOutput.length) { - valueOutput[0].innerHTML = markers[value]; +initializeAccommodationWidget() + +// now register on change handler for the radio buttons to change state when the user clicks + +const updateAccommodation = async (e) => { + const accommodation = document.querySelector('input[name="accommodation_form[accommodation]"]:checked').value; + const newAccommodationValue = { + accommodation: accommodation, + hostingInterest: +hostingInterest.value } -} -const initializeSlider = () => { - return rangeSlider.create(slider, { - onInit: function() { - updateValueOutput(0); + await fetch('/members/update/accommodation', { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' }, - onSlide: function(value, percent, position) { - updateValueOutput(value); - } - }); -}; - -initializeSlider(); - -const accommodationRadiobuttons = document.querySelectorAll(".btn-light"); -const hostingInterest = document.getElementById('hosting_interest'); -const radioHandler = (event) => { - if (event.target.type === 'radio') { - if (event.target.value === 'no') { - hostingInterest.classList.remove('u:block'); - hostingInterest.classList.add('u:hidden'); - } else { - hostingInterest.classList.remove('u:hidden'); - hostingInterest.classList.add('u:block'); - } + body: JSON.stringify(newAccommodationValue) + }).then((response) => { + }) +} + +const updateOffers = async (e) => { + const newOffers = { + dinner: offers[0].checked, + tour: offers[1].checked, + accessible: offers[2].checked } + + await fetch('/members/update/offers', { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify(newOffers) + }).then((response) => { + }) } -for (let radio of accommodationRadiobuttons) { - radio.addEventListener("click", radioHandler) +const updateRestrictions = async (e) => { + const newRestrictions = { + noAlcohol: restrictions[0].checked, + noSmoking: restrictions[1].checked, + noDrugs: restrictions[2].checked + } + + await fetch('/members/update/restrictions', { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify(newRestrictions) + }).then((response) => { + }) } + +const accommodationRadiobuttons = document.querySelectorAll(('.js-accommodation')) +accommodationRadiobuttons.forEach( (radio) => { + radio.addEventListener("change", updateAccommodation) +}) + +hostingInterest.addEventListener("change", updateAccommodation) + +const offers = document.querySelectorAll('[data-offer]') +offers.forEach( (offer) => { + offer.addEventListener("change", updateOffers) +}) + +const restrictions = document.querySelectorAll('[data-restrictions]') +restrictions.forEach( (restriction) => { + restriction.addEventListener("change", updateRestrictions) +}) diff --git a/assets/js/roxeditor.js b/assets/js/roxeditor.js index ae580c3848..36c99d61b8 100644 --- a/assets/js/roxeditor.js +++ b/assets/js/roxeditor.js @@ -118,8 +118,9 @@ const config = { ] }, autosave: { + waitingTime: 2000, save( editor ) { - return saveData( editor ); + return saveData( editor, false ); } }, ckfinder: { @@ -164,6 +165,24 @@ sourceElements.forEach( (element) => { editor .then( editor => { + editor.ui.focusTracker.on( 'change:isFocused', ( evt, data, isFocused ) => { + console.log(editor.sourceElement.id + " - " + isFocused) + + const host = editor.sourceElement; + const editorType = host.dataset.editorType; + + if (editorType === 'inline') { + const progress = document.getElementById(host.dataset.progress) + + if (isFocused) { + progress.classList.remove('u:hidden') + progress.classList.add('u:bg-bewelcome') + } else { + saveData(editor, true) + } + } + } ); + editors.set(element.id, editor) const form = editor.sourceElement.closest('form') @@ -194,27 +213,49 @@ sourceElements.forEach( (element) => { function registerSubmitHandler( form ) { form.addEventListener('submit', function( form ) { - // get all editors and set data on hidden input for inline and decoupled editor - // then remove data from localeStorage - for (let [id, editor] of editors.entries()) { + // Remove data from localeStorage. + for (let [editor] of editors.entries()) { const element = editor.sourceElement - if (element.dataset.editorType === 'inline') { - const input = document.getElementById(element.dataset.input) - input.value = editor.getData() - } - window.localStorage.removeItem(element.id); } }) } -function saveData( editor ) { +async function saveData( editor, lostFocus ) { + const element = document.getElementById(editor.sourceElement.id) + const lastChange = new Date(); + const language = element.dataset.language; + const storageKey = editor.sourceElement.id + "-" + (language ? language : ''); - window.localStorage.setItem(editor.sourceElement.id, JSON.stringify({ + window.localStorage.setItem(storageKey, JSON.stringify({ lastChange: lastChange, editorData: editor.getData() })); -} + if (lostFocus) { + // Only triggered for inline editor: messages, forum posts, trips description stay keep the data till form submit + const progress = document.getElementById(element.dataset.progress); + + if (progress) { + progress.classList.add('u:bg-bewelcome', 'u:animate-pulse') + } + + // Post data to the server + const form = new FormData(); + form.append('field', element.dataset.field); + form.append('language', element.dataset.language); + form.append('username', element.dataset.username); + form.append('content', editor.getData()); + + await fetch("/members/update/field", { method: 'POST', body: form }) + .then(() => { + if (progress) { + progress.classList.remove('u:animate-pulse', 'u:bg-bewelcome') + progress.classList.add('u:hidden') + } + window.localStorage.removeItem(storageKey); + }) + } +} diff --git a/assets/js/signup/finalize.js b/assets/js/signup/finalize.js index a9dbc0ed3f..ed574cc974 100644 --- a/assets/js/signup/finalize.js +++ b/assets/js/signup/finalize.js @@ -1,103 +1,26 @@ -import { Calendar } from 'vanilla-calendar-pro'; -import 'vanilla-calendar-pro/styles/index.css'; -import * as dayjs from 'dayjs' +import { initializeSingleAutoComplete } from '../suggest/locations'; +import { initializeAccommodationWidget } from "../accommodation_widget"; +import { initializeCalendar } from "../calendar"; -import 'leaflet'; -import 'leaflet/dist/leaflet.css'; -import {initializeSingleAutoComplete} from '../suggest/locations'; -import 'leaflet.fullscreen'; -import 'leaflet.fullscreen/Control.FullScreen.css'; -import { default as rangeSlider } from 'rangeslider-pure'; - -const htmlTag = document.getElementsByTagName('html')[0]; -const lang = htmlTag.attributes['lang'].value; - -const minimumAge = dayjs().subtract(18, 'year'); -const maximumAge = minimumAge.subtract(122, 'year'); - -const options = { - inputMode: true, - type: 'default', - onChangeToInput(self) { - if (!self.context.inputElement) return; - if (self.context.selectedDates[0]) { - self.context.inputElement.value = self.context.selectedDates[0]; - self.hide(); - } else { - self.context.inputElement.value = ''; - } - }, - lang: lang, - dateMin: maximumAge.format('YYYY-MM-DD'), - dateMax: minimumAge.format('YYYY-MM-DD'), - positionToInput: 'auto', - selectedTheme: 'light', - disabledDates: [], - dateToday: minimumAge.toDate(), -}; - -const birthDate = document.getElementById('signup_form_finalize_birthdate'); -const calendar = new Calendar(birthDate, options); -calendar.init(); - -const labelText = 'marker-label'; // document.getElementById('marker_label_text').value; -const locationGeonameId = document.getElementById('signup_form_finalize_location_geoname_id'); -const locationLatitude = document.getElementById('signup_form_finalize_location_latitude'); -const locationLongitude = document.getElementById('signup_form_finalize_location_longitude'); +const locationGeonameId = document.getElementById('signup_form_finalize_location_geoname_id') +const locationLatitude = document.getElementById('signup_form_finalize_location_latitude') +const locationLongitude = document.getElementById('signup_form_finalize_location_longitude') const myIcon = L.icon({ iconUrl: 'images/icons/marker_drop.png', iconSize: [29, 24], iconAnchor: [9, 21], popupAnchor: [0, -14] -}); +}) // callback when a selection is done from the list of possible results const storeLocation = function(element, result) { - locationGeonameId.value = result.id; - locationLatitude.value = result.latitude; - locationLongitude.value = result.longitude; -}; - -initializeSingleAutoComplete("/suggest/locations/places/exact", 'js-location-picker', storeLocation); - -const slider = document.querySelectorAll('input[type="range"]'); - -function updateValueOutput(value) { - const valueOutput = document.getElementsByClassName('rangeSlider__value-output'); - if (valueOutput.length) { - valueOutput[0].innerHTML = markers[value]; - } + locationGeonameId.value = result.id + locationLatitude.value = result.latitude + locationLongitude.value = result.longitude } -const initializeSlider = () => { - return rangeSlider.create(slider, { - onInit: function() { - updateValueOutput(0); - }, - onSlide: function(value, percent, position) { - updateValueOutput(value); - } - }); -}; - -initializeSlider(); +initializeSingleAutoComplete("/suggest/locations/places/exact", 'js-location-picker', storeLocation) +initializeAccommodationWidget() +initializeCalendar('signup_form_finalize_birthdate') -const accommodationRadiobuttons = document.querySelectorAll(".btn-light"); -const hostingInterest = document.getElementById('hosting_interest'); -const radioHandler = (event) => { - if (event.target.type === 'radio') { - console.log("Clicked: ", event.target.type, event.target.checked, event.target.value); - if (event.target.value === 'no') { - hostingInterest.classList.remove('u:block'); - hostingInterest.classList.add('u:hidden'); - } else { - hostingInterest.classList.remove('u:hidden'); - hostingInterest.classList.add('u:block'); - } - } -} - -for (let radio of accommodationRadiobuttons) { - radio.addEventListener("click", radioHandler) -} diff --git a/assets/js/translator.js b/assets/js/translator.js new file mode 100644 index 0000000000..4e9943f084 --- /dev/null +++ b/assets/js/translator.js @@ -0,0 +1,18 @@ +import { createTranslator } from '@symfony/ux-translator'; +import { messages, localeFallbacks } from '../../var/translations'; + +/* + * This file is part of the Symfony UX Translator package. + * + * If folder "../var/translations" does not exist, or some translations are missing, + * you must warmup your Symfony cache to refresh JavaScript translations. + * + * If you use TypeScript, you can rename this file to "translator.ts" to take advantage of types checking. + */ + +const translator = createTranslator({ + messages, + localeFallbacks, +}); + +export const { trans } = translator; diff --git a/build/forums/forums.model.php b/build/forums/forums.model.php index eabcec8951..3ca4e6d7df 100644 --- a/build/forums/forums.model.php +++ b/build/forums/forums.model.php @@ -1,3062 +1,3062 @@ - -* @copyright Copyright (c) 2005-2006, myTravelbook Team -* @license http://www.gnu.org/licenses/gpl.html GNU General Public License (GPL) -* @version $Id: forums.model.php 32 2007-04-03 10:22:22Z marco_p $ -*/ - -// Utility function to sort the languages -function cmpForumLang($a, $b) -{ - if ($a == $b) { - return 0; - } - return (strtolower((string) $a->Name) < strToLower((string) $b->Name)) ? -1 : 1; -} - -class Forums extends RoxModelBase { - - const CV_THREADS_PER_PAGE = 15; - const CV_POSTS_PER_PAGE = 100; - const CV_TOPMODE_CATEGORY = 1; // Says that the forum topmode is for categories - const CV_TOPMODE_LASTPOSTS = 2; // Says that the forum topmode is for lastposts - const CV_TOPMODE_LANDING = 3; // Says that we use the forums landing page for topmode - const CV_TOPMODE_FORUM = 4; // Says that we use the forums main page for topmode - const CV_TOPMODE_GROUPS = 5; // Says that we use the group forums overview page for topmode - - - const NUMBER_LAST_POSTS_PREVIEW = 10; // Number of Posts shown as a help on the "reply" page - - public $THREADS_PER_PAGE ; //Variable because it can change wether the user is logged or no - public $POSTS_PER_PAGE ; //Variable because it can change wether the user is logged or no - public $words ; // a shortcut to words module - public $ForumOrderList ; // The order of list in forum ascencding or desc this is a preference - public $BW_Right; - - - -/** -* GetLanguageChoosen function -* -* This return the language choosen by the user -* this function is supposed to be called after a new post, and editpost or a reply -* it return the language choosen if any -*/ -function GetLanguageChoosen() { - $DefLanguage=0 ; - if ($this->session->has( 'IdLanguage' )) { - $DefLanguage=$this->session->get('IdLanguage') ; - } - if (isset($_POST['IdLanguage'])) { // This will allow to consider a Language specified in the form - $DefLanguage=$_POST['IdLanguage'] ; - } - return($DefLanguage) ; -} // end of GetLanguageChoosen - - - /** - * InsertInfTrad function - * - * This InsertInFTrad create a new translatable text in forum_trads - * @$ss is for the content of the text - * @$TableColumn refers to the table and coilumn the trad is associated to - * @$IdRecord is the num of the record in this table - * @$_IdMember ; is the id of the member who own the record - * @$_IdLanguage - * @$IdTrad is probably useless (I don't remmber why I defined it) - * - * - * Warning : as default language this function will use by priority : - * 1) the content of $_IdLanguage if it is set to something else than -1 - * 2) the content of an optional $_POST[IdLanguage] if it is set - * 3) the content of the current $this->session->get('IdLanguage') of the current membr if it set - * 4) The default language (0) - * - * returns the id of the created trad - * @param string $ss - * @param string $TableColumn - * @param int $IdRecord - * @param int $_IdMember - * @param int $_IdLanguage - * @param int $IdTrad - * @return int - * @throws PException - */ -private function InsertInFTrad($ss, $TableColumn, $IdRecord, $_IdMember = 0, $_IdLanguage = -1, $IdTrad = -1) { - $this->words = new MOD_words(); - return ($this->words->InsertInFTrad($ss,$TableColumn,$IdRecord, $_IdMember, $_IdLanguage, $IdTrad)) ; -} // end of InsertInFTrad - - /** - * ReplaceInFTrad function - * - * This ReplaceInFTrad replace or create translatable text in forum_trads - * @$ss is for the content of the text - * @$TableColumn refers to the table and column the trad is associated to - * @$IdRecord is the num of the record in this table - * $IdTrad is the record in forum_trads to replace (unique for each IdLanguage) - * @$Owner ; is the id of the member who own the record - * - * Warning : as default language this function will use by priority : - * 1) the content of $_IdLanguage if it is set to something else than -1 - * 2) the content of an optional $_POST[IdLanguage] if it is set - * 3) the content of the current $this->session->get('IdLanguage') of the current membr if it set - * 4) The default language (0) - * @param string $ss - * @param string $TableColumn - * @param int $IdRecord - * @param int $IdTrad - * @param int $IdOwner - * @return int - * @throws PException - */ -private function ReplaceInFTrad($ss, $TableColumn, $IdRecord, $IdTrad = 0, $IdOwner = 0) { - $this->words = new MOD_words(); - return ($this->words->ReplaceInFTrad($ss,$TableColumn,$IdRecord, $IdTrad, $IdOwner )) ; -} // end of ReplaceInFTrad - -/** -* FindAppropriatedLanguage function will retrieve the appropriated default language -* for a member who want to reply to a thread (started with the#@IdPost post) -* this retriewal is made according to the language of the post, the current language of the user -*/ -function FindAppropriatedLanguage($IdPost=0) { - $ss="select `IdContent` FROM `forums_posts` WHERE `id`=".$IdPost ; - $q = $this->dao->query($ss); - $row= $q->fetch(PDB::FETCH_OBJ); - -// $q = $this->_dao->query($ss); -// $row = $q->fetch(PDB::FETCH_OBJ); - if (!isset($row->IdContent)) { - return (0) ; - } - else { - $IdTrad=$row->IdContent ; - } - - // Try IdTrad with current language of the member - $query ="SELECT IdLanguage FROM `forum_trads` WHERE `IdTrad`=".$IdTrad." and `IdLanguage`=".$this->session->get("IdLanguage") ; - $q = $this->dao->query($query); - $row = $q->fetch(PDB::FETCH_OBJ); - if (isset ($row->IdLanguage)) { - return($row->IdLanguage) ; - } - - // Try with the original language used for this post - $query ="SELECT `IdLanguage` FROM `forum_trads` WHERE `IdTrad`=".$IdTrad." order by id asc limit 1" ; - $q = $this->dao->query($query); - $row = $q->fetch(PDB::FETCH_OBJ); - - return($row->IdLanguage ?? 0) ; // By default we will return english - -} // end of FindAppropriatedLanguage - - - /* - * Constructor of forum model, prepare manything - * and things relative to Visibility - * - */ - public function __construct() { - parent::__construct(); - $this->THREADS_PER_PAGE=Forums::CV_THREADS_PER_PAGE ; //Variable because it can change wether the user is logged or no - $this->POSTS_PER_PAGE=Forums::CV_POSTS_PER_PAGE ; //Variable because it can change wether the user is logged or no - - if ($this->session->has('IdMember')) { - $member = $this->getLoggedInMember(); - - match ($member->getPreference("PreferenceForumFirstPage")) { - "Pref_ForumFirstPageLastPost" => $this->setTopMode(Forums::CV_TOPMODE_FORUM), - "Pref_ForumFirstPageCategory" => $this->setTopMode(Forums::CV_TOPMODE_CATEGORY), - default => $this->setTopMode(Forums::CV_TOPMODE_LANDING), - }; - $layoutbits = new MOD_layoutbits(); - $this->ForumOrderList = $layoutbits->GetPreference("PreferenceForumOrderListAsc", $member->id); - } else { - $this->setTopMode(Forums::CV_TOPMODE_FORUM); - } - - if (!$this->session->has( 'IdMember' )) { - $this->THREADS_PER_PAGE = 100; // Variable because it can change wether the user is logged or no - $this->POSTS_PER_PAGE = self::CV_POSTS_PER_PAGE; // Variable because it can change wether the user is logged or no - } - - $MyGroups = []; - - - $this->words = new MOD_words(); - $this->BW_Right = MOD_right::get(); - $this->IdGroup = false; // By default no group - $this->ByCategories = false; // toggle or not toglle the main view is TopCategories or TopLevel - - // Decide if it is an active LoggeMember or not - if (!$this->session->has("IdMember") || - !$this->session->has("MemberStatus") || - $this->session->get("MemberStatus") == 'Pending' || - $this->session->get("MemberStatus") == 'NeedMore' ) { - $this->PublicThreadVisibility=" (ThreadVisibility = 'NoRestriction') AND (ThreadDeleted != 'Deleted')"; - $this->PublicPostVisibility = " (PostVisibility = 'NoRestriction') AND (PostDeleted != 'Deleted')"; - $this->ThreadGroupsRestriction = " (IdGroup IS NULL OR ThreadVisibility = 'NoRestriction')"; - $this->PostGroupsRestriction = " (IdGroup IS NULL OR PostVisibility = 'NoRestriction')" ; - } - else { - $this->PublicThreadVisibility = "(ThreadVisibility != 'ModeratorOnly') AND (ThreadDeleted != 'Deleted')" ; - $this->PublicPostVisibility = "(PostVisibility != 'ModeratorOnly') AND (PostDeleted !='Deleted')" ; - $this->PostGroupsRestriction = " PostVisibility IN ('MembersOnly','NoRestriction') OR (PostVisibility='GroupOnly' AND (IdGroup IS NULL OR IdGroup in (0" ; - $this->ThreadGroupsRestriction = " ThreadVisibility IN ('MembersOnly','NoRestriction') OR (ThreadVisibility = 'GroupOnly' and (IdGroup IS NULL OR IdGroup in (0" ; - $qry = $this->dao->query("SELECT IdGroup FROM membersgroups WHERE IdMember = " . $this->session->get("IdMember") . " AND Status = 'In'"); - if (!$qry) { - throw new PException('Failed to retrieve groups for member id =#'.$this->session->get("IdMember").' !'); - } - while ($rr = $qry->fetch(PDB::FETCH_OBJ)) { - $this->PostGroupsRestriction = $this->PostGroupsRestriction . "," . $rr->IdGroup; - $this->ThreadGroupsRestriction = $this->ThreadGroupsRestriction . "," . $rr->IdGroup; - array_push($MyGroups,$rr->IdGroup) ; // Save the group list - } - $this->PostGroupsRestriction = $this->PostGroupsRestriction . ")))"; - $this->ThreadGroupsRestriction = $this->ThreadGroupsRestriction . ")))"; - } - - // Prepares additional visibility options for moderator - if ($this->BW_Right->HasRight("ForumModerator")) { - $this->PublicPostVisibility = " PostVisibility IN ('NoRestriction', 'MembersOnly','GroupOnly','ModeratorOnly')"; - $this->PublicThreadVisibility = " ThreadVisibility IN ('NoRestriction', 'MembersOnly','GroupOnly','ModeratorOnly')"; - if ($this->BW_Right->HasRight("ForumModerator","AllGroups") or $this->BW_Right->HasRight("ForumModerator","All")) { - $this->PostGroupsRestriction = " (1=1)"; - $this->ThreadGroupsRestriction = " (1=1)"; - } - } - $this->MyGroups = $MyGroups; - } // __construct - - // This switch the preference ForumOrderList - public function switchForumOrderList() { - $ss="SELECT id FROM preferences WHERE codename ='PreferenceForumOrderListAsc'"; - $qq = $this->dao->query($ss); - if (!$qq) { - // We couldn't get a connection to the database... - return; - } - if ($this->ForumOrderList=="Yes") { - $this->ForumOrderList="No" ; - } - else { - $this->ForumOrderList="Yes" ; - } - $row = $qq->fetch(PDB::FETCH_OBJ); - if (null === $row) - { - // if the preference doesn't exist something is very, very wrong. return at your own risk... - return; - } - $idPreference = $row->id; - $idMember = $this->session->get('IdMember'); - - $ss="SELECT mp.value, mp.id FROM memberspreferences mp WHERE mp.IdMember = " . $idMember . " AND mp.IdPreference = " . $idPreference; - $result = $this->dao->query($ss); - if (!$result) { - // We couldn't get a connection to the database... - return; - } - - $row = $result->fetch(PDB::FETCH_OBJ) ; - if (null === $row) { - $query = " - INSERT INTO - memberspreferences (`created`, `updated`, `IdPreference`, `IdMember`, `Value`) - VALUES (NOW(), `created`, " .$idPreference . ", " . $idMember . ", '" . $this->dao->escape($this->ForumOrderList) . "' ) - "; - } - else { - $query = " - UPDATE - memberspreferences - SET Value='" . $this->dao->escape($this->ForumOrderList) . "' WHERE id= " . $row->id - ; - } - $result = $this->dao->query($query); - } // end of switchForumOrderList - - public function adjustThreadsCountToShow($step = 1) { - $MAX_THREADS = 1000; //An upper limit just in case - if (!$member = $this->getLoggedInMember()) { - return false; - } - $vars =& PPostHandler::getVars(); - if (!isset($vars['agoragroupsthreadscountmoreless'])) { - return false; - } - $command = $vars['agoragroupsthreadscountmoreless']; - $layoutbits = new MOD_layoutbits(); - $forumthreads = intval($layoutbits->GetPreference("ForumThreadsOnLandingPage", $member->id)); - $groupsthreads = intval($layoutbits->GetPreference("GroupsThreadsOnLandingPage", $member->id)); - $membersmodel = new MembersModel(); - - $query = " - SELECT - id - FROM - preferences - WHERE - CodeName = 'ForumThreadsOnLandingPage' - LIMIT 1 - "; - $row = $this->dao->query($query); - $forumpref = $row->fetch(PDB::FETCH_OBJ); - if ($forumpref === false) { - throw new Exception('Database error: "ForumThreadsOnLandingPage"' - . ' preference not found in "preferences" table'); - } - - $query = " - SELECT - id - FROM - preferences - WHERE - CodeName = 'GroupsThreadsOnLandingPage' - LIMIT 1 - "; - $row = $this->dao->query($query); - $groupspref = $row->fetch(PDB::FETCH_OBJ); - if ($groupspref === false) { - throw new Exception('Database error: "GroupsThreadsOnLandingPage"' - . ' preference not found in "preferences" table'); - } - - match ($command) { - "moreagora" => $membersmodel->set_preference($member->id, $forumpref->id, min($forumthreads + $step, $MAX_THREADS)), - "lessagora" => $membersmodel->set_preference($member->id, $forumpref->id, max($forumthreads - $step, 1)), - "moregroups" => $membersmodel->set_preference($member->id, $groupspref->id, min($groupsthreads + $step, $MAX_THREADS)), - "lessgroups" => $membersmodel->set_preference($member->id, $groupspref->id, max($groupsthreads - $step, 1)), - default => false, - }; - - return false; - } - - - - // This switch the preference switchShowMyGroupsTopicsOnly - public function switchShowMyGroupsTopicsOnly() { - if (!$member = $this->getLoggedInMember()) { - return false; - } - $owngroupsonly = $member->getPreference("ShowMyGroupsTopicsOnly", $default = "No"); - $this->ShowMyGroupsTopicsOnly = $owngroupsonly; - if ($this->ShowMyGroupsTopicsOnly == "Yes") { - $this->ShowMyGroupsTopicsOnly = "No" ; - } - else { - $this->ShowMyGroupsTopicsOnly = "Yes" ; - } - - // Fetch preference object - $query = " - SELECT - id - FROM - preferences - WHERE - CodeName = 'ShowMyGroupsTopicsOnly' - LIMIT 1 - "; - $row = $this->dao->query($query); - $preference = $row->fetch(PDB::FETCH_OBJ); - if ($preference === false) { - throw new Exception('Database error: "ShowMyGroupsTopicsOnly"' - . ' preference not found in "preferences" table'); - } - - $ss = " -SELECT - m.Value AS Value, - m.id AS id, - p.id AS IdPreferences -FROM - memberspreferences AS m, - preferences AS p -WHERE - p.id = m.IdPreference - AND m.IdMember = " . $this->session->get('IdMember') . " - AND p.CodeName = 'ShowMyGroupsTopicsOnly'"; - - $qq = $this->dao->query($ss); - $rr = $qq->fetch(PDB::FETCH_OBJ) ; - if (empty($rr->Value)) { - $ss = " -INSERT INTO - memberspreferences( - created, - IdPreference, - IdMember, - Value - ) -VALUES( - now(), - " . $preference->id . "," . - $this->session->get('IdMember') . ", - '" . $this->ShowMyGroupsTopicsOnly . "' -)" ; - } - else { - $ss = " -UPDATE - memberspreferences -SET - Value='" . $this->ShowMyGroupsTopicsOnly . "' -WHERE - id=" . $rr->id ; - } - - $qq = $this->dao->query($ss); - if (!$qq) { - throw new PException('switchShowMyGroupsTopicsOnly ' . $ss . ' !'); - } - return false; - } // end of switchShowMyGroupsTopicsOnly - - - public function checkGroupMembership($group_id) { - if (in_array($group_id,$this->MyGroups)) { - return true; - } - return false; - } // end of checkGroupMembership - - private function boardTopLevelForum($showsticky = true) { - $this->board = new Board($this->dao, 'Forum', '.', $this->getSession(), false, null, false); - $this->board->initThreads($this->getPage(), $showsticky); - - } // end of boardTopLevelForum - - private function boardTopLevelGroups($showsticky = true) { - $this->board = new Board($this->dao, 'Groups', '.', $this->getSession(), false, false, true); - $this->board->initThreads($this->getPage(), $showsticky); - - } // end of boardTopLevelGroups - - private function boardTopLevelLanding($showsticky = true) { - $User = $this->getLoggedInMember(); - if (!$User) { - // Show informal message that the forums are limited to members only - return false; - } - - $MAX_THREADS = 1000; //An upper limit of threads th show just in case the preference goes silly - $layoutbits = new MOD_layoutbits(); - - $idMember = $this->getSession()->get('IdMember'); - $forumthreads = intval($layoutbits->getPreference("ForumThreadsOnLandingPage", $idMember)); - $groupsthreads = intval($layoutbits->getPreference("GroupsThreadsOnLandingPage", $idMember)); - - $page_array = $this->getPageArray(); - - if (isset($page_array[0]) && isset($page_array[1])) { - $forumpage = $page_array[0]; - $groupspage = $page_array[1]; - } else { - $forumpage = 1; - $groupspage = 1; - } - - - $this->board = new Board($this->dao, 'Forums and Groups', '.', $this->getSession()); - - $forum = new Board($this->dao, 'Forum', '.', $this->getSession(), false, null, false); - $forum->THREADS_PER_PAGE = max(1, min($forumthreads, $MAX_THREADS)); - $forum->initThreads($forumpage, $showsticky); - $forumMaxPage = max(ceil($forum->getNumberOfThreads() / $forum->THREADS_PER_PAGE), 1); - if ($forumpage > $forumMaxPage) { - $forum->initThreads($forumMaxPage, $showsticky); - } - - $groups = new Board($this->dao, 'Groups', '.', $this->getSession(), false, false, true); - $groups->THREADS_PER_PAGE = max(1, min($groupsthreads, $MAX_THREADS)); - $groups->initThreads($groupspage, false); - - $this->board->add($forum); - $this->board->add($groups); - return true; - } // end of boardTopLevelLanding - - private function boardTopLevelLastPosts($showsticky = true) { - $this->board = new Board($this->dao, 'Forums', '.', $this->getSession()); - $this->board->initThreads($this->getPage(), $showsticky); - } // end of boardTopLevelLastPosts - - // This build the board for the $this->IdGroup - private function boardGroup($showsticky = true) { - - $query = "SELECT `Name` FROM `groups` WHERE `id` = " . (int)$this->IdGroup; - $gr = $this->dao->query($query); - if (!$gr) { - throw new PException('No such IdGroup=#'.$this->IdGroup); - } - $group = $gr->fetch(PDB::FETCH_OBJ); - - $subboards = []; - $gtitle= $this->words->getSilent("ForumGroupTitle", $this->getGroupName($group->Name)) ; - $this->board = new Board($this->dao, "", ".", $this->getSession(), $subboards, $this->IdGroup); - $this->board->initThreads($this->getPage(), $showsticky); - } // end of boardGroup - - /* - @ $Name name of the group (direct from groups.Name - */ - public function getGroupName($Name) { - return($Name) ; - - } - - /** - * Fetch all required data for the view to display a forum - * this data are stored in $this->board - */ - public function prepareForum($showsticky = true) { - if (!$this->IdGroup) { - if ($this->TopMode==Forums::CV_TOPMODE_LASTPOSTS) { - $this->boardTopLevelLastPosts($showsticky); - } - elseif ($this->TopMode==Forums::CV_TOPMODE_LANDING) { - $this->boardTopLevelLanding($showsticky); - } - elseif ($this->TopMode==Forums::CV_TOPMODE_FORUM) { - $this->boardTopLevelForum($showsticky); - } - elseif ($this->TopMode==Forums::CV_TOPMODE_GROUPS) { - $this->boardTopLevelGroups($showsticky); - } - else { - $this->boardTopLevelLanding($showsticky); - } - } elseif ($this->IdGroup) { - $this->boardGroup($showsticky); - } elseif (PVars::get()->debug) { - throw new PException('Invalid Request'); - } else { - PRequest::home(); - } - } // end of prepareForum - - private $board; - public function getBoard() { - return $this->board; - } - - public function createProcess() { - if (!($User = $this->getLoggedInMember())) { - return false; - } - - $vars =& PPostHandler::getVars(); - - $vars_ok = $this->checkVarsTopic($vars); - if ($vars_ok) { - $topicid = $this->newTopic($vars); - PPostHandler::clearVars(); - return PVars::getObj('env')->baseuri.$this->forums_uri.'s'.$topicid; - } else { - return false; - } - - } - - /* - * Fill the Vars in order to prepare edit a post - * this fetch the data which are then going to be display and then change - * by the user - */ - public function getEditData($callbackId) { - $query = - " -SELECT - `forums_posts`.`id` AS `postid`, - `IdWriter`, - `PostDeleted`, - `PostVisibility`, - `ThreadVisibility`, - `ThreadDeleted`, - `forums_posts`.`threadid` as `threadid`, - `message` AS `topic_text`, - `OwnerCanStillEdit`, - `IdContent`, - `title` AS `topic_title`, `first_postid`, `last_postid`, `IdTitle`, - `ThreadVisibility`, - `ThreadDeleted`, - `forums_threads`.`IdGroup` -FROM `forums_posts` -LEFT JOIN `forums_threads` ON (`forums_posts`.`threadid` = `forums_threads`.`id`) -WHERE `forums_posts`.`id` = $this->messageId -and ($this->PublicPostVisibility) - "; - - $s = $this->dao->query($query); - if (!$s) { - throw new PException('getEditData :: Could not retrieve Postinfo!'); - } - $vars =& PPostHandler::getVars($callbackId); - $vars = $s->fetch(PDB::FETCH_ASSOC); - $this->IdGroup = $vars['IdGroup']; - } // end of get getEditData - - /* - * Write in the database the changed data - * when a post is edited, this also write a log and - * this call editPost and may be editTopic which does the update in the database - * by the user - */ - public function editProcess() { - if (!($User = $this->getLoggedInMember())) { - return false; - } - - $vars =& PPostHandler::getVars(); - - $query = - " -SELECT - `forums_posts`.`id` AS `postid`, - `IdWriter`, - `forums_posts`.`threadid`, - `first_postid`, - `OwnerCanStillEdit`, - `PostDeleted`, - `PostVisibility`, - `ThreadVisibility`, - `ThreadDeleted`, - `forums_threads`.`IdGroup`, - `last_postid` -FROM `forums_posts` -LEFT JOIN `forums_threads` ON (`forums_posts`.`threadid` = `forums_threads`.`id`) -WHERE `forums_posts`.`id` = $this->messageId - " - ; - $s = $this->dao->query($query); - if (!$s) { - throw new PException('Could not retrieve Postinfo!'); - } - $postinfo = $s->fetch(PDB::FETCH_OBJ); - -// if ($this->BW_Right->HasRight("ForumModerator","Edit") || ($User->hasRight('edit_own@forums') && $postinfo->authorid == $User->getId())) { - if ($this->BW_Right->HasRight("ForumModerator","Edit") || ($postinfo->IdWriter == $this->session->get("IdMember") and $postinfo->OwnerCanStillEdit=="Yes")) { - $is_topic = ($postinfo->postid == $postinfo->first_postid); - - if ($is_topic) { - $vars_ok = $this->checkVarsTopic($vars); - } else { - $vars_ok = $this->checkVarsReply($vars); - } - if ($vars_ok) { - $this->dao->query("START TRANSACTION"); - - if ($is_topic) { - if (!isset($vars['ThreadVisibility'])) { - $vars['ThreadVisibility'] = 'MembersOnly'; - } - $vars['PostVisibility'] = $vars['ThreadVisibility']; - } - - if (!isset($vars['PostVisibility'])) { - $vars['PostVisibility'] = 'MembersOnly'; - } - - $this->editPost($vars, $User->getId()); - if ($is_topic) { - $this->editTopic($vars, $postinfo->threadid); - } - - $this->dao->query("COMMIT"); - - PPostHandler::clearVars(); - return PVars::getObj('env')->baseuri.$this->forums_uri.'s'.$postinfo->threadid; - } else { - return false; - } - } else { - return false; - } - } // end of editProcess - -/** -* the function DofTradUpdate() update a forum translation -* @IdForumTrads is the primary key of the parameter to update -*/ - public function DofTradUpdate($IdForumTrads,$P_Sentence,$P_IdLanguage=0) { - $id=(int)$IdForumTrads ; - $s=$this->dao->query("select * from forum_trads where id=".$id); - $rBefore=$s->fetch(PDB::FETCH_OBJ) ; - -// Save the previous version - $this->words->MakeRevision($id, "forum_trads",$this->session->get("IdMember"), $DoneBy = "DoneByModerator") ; - $IdLanguage=(int)$P_IdLanguage ; - $Sentence= $this->dao->escape($P_Sentence); - - MOD_log::get()->write("Updating data for IdForumTrads=#".$id." Before [".addslashes((string) $rBefore->Sentence)."] IdLanguage=".$rBefore->IdLanguage."
\nAfter [".$Sentence."] IdLanguage=".$IdLanguage, "ForumModerator"); - $sUpdate="update forum_trads set Sentence='".$Sentence."',IdLanguage=".$IdLanguage.",IdTranslator=".$this->session->get("IdMember").", updated=NOW() where id=".$id ; - $s=$this->dao->query($sUpdate); - if (!$s) { - throw new PException('Failed for Update forum_trads.id=#'.$id); - } - - } // end of DofTradUpdate - - -/** -* editPost write the data in of change post in the database -* warning : dont start any transaction in it sinc ethere is already one -* started by the caller -* this also write a log -*/ - private function editPost($vars, $editorid) { - $query = "SELECT message,forums_posts.threadid, - `PostVisibility`, - OwnerCanStillEdit,IdWriter,forums_posts.IdFirstLanguageUsed as post_IdFirstLanguageUsed,forums_threads.IdFirstLanguageUsed as thread_IdFirstLanguageUsed,forums_posts.id,IdWriter,IdContent,forums_threads.IdTitle,forums_threads.first_postid from `forums_posts`,`forums_threads` WHERE forums_posts.threadid=forums_threads.id and forums_posts.id = ".$this->messageId ; - $s=$this->dao->query($query); - $rBefore=$s->fetch(PDB::FETCH_OBJ) ; - - $query = sprintf("UPDATE `forums_posts` SET `message` = '%s', `last_edittime` = NOW(), `last_editorid` = '%d', `edit_count` = `edit_count` + 1 WHERE `id` = '%d'", - $this->dao->escape($this->cleanupText($vars['topic_text'])), $editorid, $this->messageId); - $this->dao->query($query); - $this->ReplaceInFTrad($this->dao->escape($this->cleanupText($vars['topic_text'])), "forums_posts.IdContent", $rBefore->id, $rBefore->IdContent, $rBefore->IdWriter) ; - $this->updatePostInManticoreIndex($vars); - // case the update concerns the reference language of the posts - if ($rBefore->post_IdFirstLanguageUsed==$this->GetLanguageChoosen()) { - $query="update forums_posts set message='".$this->dao->escape($this->cleanupText($vars['topic_text']))."' where id=".$this->messageId ; - $s=$this->dao->query($query); - } - - // case the visibility has changed - if ($rBefore->PostVisibility!=$vars['PostVisibility']) { - $query="update forums_posts set PostVisibility='".$vars['PostVisibility']."' where id=".$this->messageId ; - $s=$this->dao->query($query); - MOD_log::get()->write("Changing Post Visibility from ".$rBefore->PostVisibility." to ".$vars['PostVisibility']."", "Forum"); - } - - - - // If this is the first post, may be we can update the title - if ($rBefore->first_postid==$rBefore->id) { - $this->ReplaceInFTrad($this->dao->escape($this->cleanupText($vars['topic_title'])), "forums_threads.IdTitle", $rBefore->threadid, $rBefore->IdTitle, $rBefore->IdWriter) ; - // case the update concerns the reference language of the threads - if ($rBefore->thread_IdFirstLanguageUsed==$this->GetLanguageChoosen()) { - $groupId = $vars['IdGroup'] ?? 'NULL'; - $query="update forums_threads set IdGroup=". $groupId.",title='".$this->dao->escape($this->cleanupText($vars['topic_title']))."' where forums_threads.id=".$rBefore->threadid ; - $s=$this->dao->query($query); - } - } - - // subscription if any, could be done out of transaction, this is not so important - if ((isset($vars['NotifyMe'])) and ($vars['NotifyMe']=="on")) { - if (!$this->IsThreadSubscribed($rBefore->threadid,$this->session->get("IdMember"))) { - $this->SubscribeThread($rBefore->threadid,$this->session->get("IdMember")) ; - } - } - else { - $vars['NotifyMe']="Not Asked" ; - if ($this->IsThreadSubscribed($rBefore->threadid,$this->session->get("IdMember"))) { - $this->UnsubscribeThreadDirect($rBefore->threadid,$this->session->get("IdMember")) ; - } - } - - $this->prepare_notification($this->messageId,"useredit") ; // Prepare a notification - MOD_log::get()->write("Editing Post=#".$this->messageId." Text Before=".addslashes((string) $rBefore->message)."
NotifyMe=[".$vars['NotifyMe']."]", "Forum"); - } // editPost - - /** - * editTopic write the data in of change thread in the database - * warning : dont start any transaction in it since there is already one - * started by the caller - * this also write a log - */ - - private function editTopic($vars, $threadid) { - $query = sprintf(" -UPDATE `forums_threads` -SET `title` = '%s' -WHERE `id` = '%d' ", - $this->dao->escape(strip_tags((string) $vars['topic_title'])), - $threadid - ); - - $this->dao->query($query); - - $s=$this->dao->query("select IdWriter,forums_threads.id as IdThread,forums_threads.IdTitle,forums_threads.IdFirstLanguageUsed as thread_IdFirstLanguageUsed - from forums_threads,forums_posts - where forums_threads.first_postid=forums_posts.id and forums_threads.id=".$threadid); - if (!$s) { - throw new PException('editTopic:: previous info for first post in the thread!'); - } - $rBefore = $s->fetch(PDB::FETCH_OBJ); - - $this->ReplaceInFTrad($this->dao->escape(strip_tags((string) $vars['topic_title'])), "forums_threads.IdTitle", $rBefore->IdThread, $rBefore->IdTitle, $rBefore->IdWriter) ; - - // case the update concerns the reference language of the posts - if ($rBefore->thread_IdFirstLanguageUsed==$this->GetLanguageChoosen()) { - $query="update forums_threads set title='".$this->dao->escape($this->cleanupText($vars['topic_title']))."' where forums_threads.id=".$rBefore->IdThread ; - $s=$this->dao->query($query); - } - - // Set ThreadVisibility - $query = 'UPDATE forums_threads SET ThreadVisibility = "' . $vars['ThreadVisibility'] . '" WHERE forums_threads.id=' . $rBefore->IdThread; - $s =$this->dao->query($query); - - MOD_log::get()->write("Editing Topic Thread=#".$threadid, "Forum"); - } // end of editTopic - - public function replyProcess($suggestion = false) { - if (!($User = $this->getLoggedInMember())) { - return false; - } - - $vars =& PPostHandler::getVars(); - $this->checkVarsReply($vars); - $this->replyTopic($vars); - - PPostHandler::clearVars(); - return PVars::getObj('env')->baseuri.$this->forums_uri.'s'.$this->threadid; - } // end of replyProcess - - public function reportpostProcess() { - if (!($User = $this->getLoggedInMember())) { - return false; - } - - $vars =& PPostHandler::getVars(); - $IdPost=$vars['IdPost'] ; - - $ss = "select threadid from forums_posts where id=".$IdPost ; - $s = $this->dao->query($ss); - $rr = $s->fetch(PDB::FETCH_OBJ) ; - - $this->threadid=$rr->threadid ; - - $PostComment=$vars['PostComment'] ; - $Status=$vars['Status'] ; - if (isset($vars['Type'])) { - $Type=$vars['Type'] ; - } else { - $Type = 'SeeText'; - } - if (!empty($vars['IdReporter'])) { - $IdReporter=$vars['IdReporter'] ; - } - else { - $IdReporter=$this->session->get("IdMember") ; - } - - $ss = "select reports_to_moderators.* from reports_to_moderators where IdPost=".$IdPost." and IdReporter=".$IdReporter ; - $s = $this->dao->query($ss); - $OldReport = $s->fetch(PDB::FETCH_OBJ) ; - - - $UsernameAddTime='On '.date("d-m-Y").' '.date("H:i"). ' (server time) '.$this->session->get("Username").' wrote:
' ; - if (($this->BW_Right->HasRight("ForumModerator")) and (isset($OldReport->IdReporter))) { - $PostComment=$UsernameAddTime.$this->cleanupText($vars['PostComment']) ; - if (isset($OldReport->PostComment)) $PostComment=$PostComment."
\n".$OldReport->PostComment ; - $ss="update reports_to_moderators set LastWhoSpoke='Moderator',PostComment='".$this->dao->escape($PostComment)."',IdModerator=".$this->session->get("IdMember").",Status='".$this->dao->escape($Status)."',Type='".$this->dao->escape($Type)."',IdModerator=".$this->session->get('IdMember')." where IdPost=".$IdPost." and IdReporter=".$IdReporter ; - $this->dao->query($ss); - } - else { - if ($IdReporter!=$this->session->get("IdMember")) { - MOD_log::get()->write("Trying to trick report to moderator for post #".$IdPost,"Forum") ; - die("Failed to report to moderator") ; - } - if (isset($OldReport->IdReporter)) { - $PostComment=$UsernameAddTime.$this->cleanupText($vars['PostComment'])."
\n".$OldReport->PostComment ; - $ss="update reports_to_moderators set LastWhoSpoke='Member',PostComment='".$this->dao->escape($PostComment)."',Status='".$this->dao->escape($Status)."'"." where IdPost=".$IdPost." and IdReporter=".$IdReporter ; - $this->dao->query($ss); - } - else { - $PostComment=$UsernameAddTime.$this->cleanupText($vars['PostComment']) ; - $ss=" - insert into reports_to_moderators(PostComment,created,IdPost,IdReporter,Status) - values('".$this->dao->escape($PostComment)."',now(),".$IdPost.",".$this->session->get("IdMember") .",'".$Status."')" ; - $this->dao->query($ss); - } - } - MOD_log::get()->write("Adding to report for post #".$IdPost."
".$PostComment."
Status=".$Status,"Forum") ; - PPostHandler::clearVars(); - return PVars::getObj('env')->baseuri.$this->forums_uri.'s'.$this->threadid.'/#'.$IdPost; - } // end of reportpostProcess - - /** - * This will return the list of reports for a given post - * @IdPost : Id of the post to process with their status - * @IdReporter : OPtional id of teh member for a specific report, in this case a record is returned7 - * in other case an array of reports is returned - */ - public function GetReports($IdPost,$IdReporter=0) { - $tt=[] ; - if (empty($IdReporter)) { - $ss = "select reports_to_moderators.*,Username from reports_to_moderators,member where IdPost=".$IdPost." and member.id=IdReporter" ; - $s = $this->dao->query($ss); - while ($rr = $s->fetch(PDB::FETCH_OBJ)) { - array_push($tt,$rr) ; - } - return($tt) ; - } - else { - $ss = "select IdReporter from reports_to_moderators where IdPost=".$IdPost." and IdReporter=".$IdReporter ; - $s = $this->dao->query($ss); - array_push($tt,$s->fetch(PDB::FETCH_OBJ)) ; - return($tt) ; - } - } // end of GetReports - /** - * This will prepare the additional data to process a report - * @IdPost : Id of the post to process - * @IdWriter is the id of the writer - */ - public function prepareReportPost($IdPost,$IdWriter) { - $query = "select * from reports_to_moderators where IdPost=".$IdPost." and IdReporter=".$IdWriter ; - $s = $this->dao->query($query); - $Report = $s->fetch(PDB::FETCH_OBJ) ; - return($Report) ; - } // end of prepareReportPost - - /** - This function loads the list of links to reports - @IdMember : if to 0 it means for all moderators, if not 0 it mean for a given moderator - @StatusList : is the list of reports status to consider, if empty it means all status - - returns an array - */ - public function prepareReportList($IdMember,$StatusList) { // This retrieve all the reports for the a member or all members - - $ss = "select reports_to_moderators.*,Username from reports_to_moderators,member where member.id=IdReporter " ; - if (!empty($StatusList)) { - $ss=$ss." and reports_to_moderators.Status in ".$StatusList ; - } - - $tt=[] ; - if (!empty($IdMember)) { - $ss=$ss." and reports_to_moderators.IdModerator=".$IdMember ; - } - $ss=$ss." order by reports_to_moderators.updated" ; - $s = $this->dao->query($ss); - while ($rr = $s->fetch(PDB::FETCH_OBJ)) { - array_push($tt,$rr) ; - } - return($tt) ; - } // end of prepareReportList - - /** - This function count the list of links to reports - @IdMember : if to 0 it means for all moderators, if not 0 it mean for a given moderator - @StatusList : is the list of reports status to consider, if empty it means all status - returns and integer - */ - public function countReportList($IdMember,$StatusList) { // This count all the reports for and optional members or all members according to their styatus - $ss = "select count(*) as cnt from reports_to_moderators,member where member.id=IdReporter " ; - if (!empty($StatusList)) { - $ss=$ss." and reports_to_moderators.Status in ".$StatusList ; - } - $s = $this->dao->query($ss); - if ($rr = $s->fetch(PDB::FETCH_OBJ)) { - return($rr->cnt) ; - } - return(0) ; - } // end of countReportList - - - - /** - * This will prepare a post for a full edit moderator action - * @IdPost : Id of the post to process - */ - public function prepareModeratorEditPost($IdPost, $moderator = false) { - $DataPost = new StdClass(); - $DataPost->IdPost=$IdPost ; - $DataPost->Error="" ; // This will receive the error sentence if any - - $query = "select forums_posts.*,member.Status as memberstatus,member.UserName as UserNamePoster from forums_posts,member where forums_posts.id=".$IdPost." and IdWriter=member.id" ; - $s = $this->dao->query($query); - $DataPost->Post = $s->fetch(PDB::FETCH_OBJ) ; - - if (!isset($DataPost->Post)) { - $DataPost->Error="No Post for Post=#".$IdPost ; - return($DataPost) ; - } - - if (!$moderator) { - if ($DataPost->Post->PostVisibility == 'GroupOnly') { - // first check if post was made in a group and if the current member is a member of that group - $query = "SELECT IdGroup FROM forums_posts fp, forums_threads ft WHERE fp.id = " . $this->dao->escape($IdPost) . " AND fp.threadId = ft.id"; - $s = $this->dao->query($query); - $row = $s->fetch(PDB::FETCH_OBJ); - $IdGroup = $row->IdGroup; - if ($IdGroup <> 0) { - $group = $this->createEntity('Group')->findByid($IdGroup); - $member = $this->getLoggedInMember(); - // Can't use $group->isMember() for some reason - $query = " SELECT * FROM membersgroups WHERE IdMember = " . $member->id . " AND IdGroup = " . $IdGroup; - $s = $this->dao->query($query); - $row = $s->fetch(PDB::FETCH_OBJ); - if (!$row) { - $DataPost->Error="NoGroupMember"; - return($DataPost) ; - } - } - } - if ($DataPost->Post->PostVisibility == 'ModeratorOnly') { - $DataPost->Error = "NoModerator"; - return $DataPost; - } - } - - // retrieve all trads for content - $query = "select forum_trads.*,Name,ShortCode,forum_trads.id as IdForumTrads from forum_trads,languages where IdLanguage=languages.id and IdTrad=".$DataPost->Post->IdContent." order by forum_trads.created asc" ; - $s = $this->dao->query($query); - $DataPost->Post->Content=[] ; - while ($row=$s->fetch(PDB::FETCH_OBJ)) { - $DataPost->Post->Content[]=$row ; - } - - - $query = "select * from forums_threads where id=".$DataPost->Post->threadid ; - $s = $this->dao->query($query); - if (!isset($DataPost->Post)) { - $DataPost->Error="No Thread=#".$DataPost->Post->threadid ; - return($DataPost) ; - } - $DataPost->Thread = $s->fetch(PDB::FETCH_OBJ) ; - - -// retrieve all trads for Title - $query = "select forum_trads.*,Name,ShortCode,forum_trads.id as IdForumTrads from forum_trads,languages where IdLanguage=languages.id and IdTrad=".$DataPost->Thread->IdTitle." order by forum_trads.created asc" ; - $s = $this->dao->query($query); - $DataPost->Thread->Title=[] ; - while ($row=$s->fetch(PDB::FETCH_OBJ)) { - array_push($DataPost->Thread->Title,$row) ; - } - - $DataPost->PossibleGroups=$this->ModeratorGroupChoice() ; - return ($DataPost) ; - } // end of prepareModeratorEditPost - - -// This is what is called by the Full Moderator edit -// ---> ($vars["submit"]=="update thread")) means the Stick Value or the expire date of the thread have been updated and also the Group -// ---> ($vars["submit"]=="add translated title")) means that a new translated title is made available -// ---> ($vars["submit"]=="add translated post")) means that a new translated post is made available -// ---> ($vars["submit"]=="update post")) means the CanOwnerEdit has been updated -// ---> isset($vars["IdForumTrads"]) means that on of the trad of the forum has been changed (title of one of the post) -// ---> ($vars["submit"]=="delete Tag")) means that the Tag IdTag is to be deleted -// ---> ($vars["submit"]=="Add Tag")) means that the Tag IdTag is to be added - public function ModeratorEditPostProcess() { - if (!($User = $this->getLoggedInMember())) { - return false; - } - - $vars =& PPostHandler::getVars(); - if (isset($vars["submit"]) and ($vars["submit"]=="update thread")) { // if an effective update was chosen for a forum trads - $IdThread=(int)$vars["IdThread"] ; - $IdGroup=(int)$vars["IdGroup"] ; - $ThreadVisibility=$vars["ThreadVisibility"] ; - $ThreadDeleted=$vars["ThreadDeleted"] ; - $WhoCanReply=$vars["WhoCanReply"] ; - $expiredate="'".$vars["expiredate"]."'" ; - $stickyvalue=(int)$vars["stickyvalue"]; - if (empty($expiredate)) { - $expiredate="NULL" ; - } - MOD_log::get()->write("Updating Thread=#".$IdThread." IdGroup=#".$IdGroup." Setting expiredate=[".$expiredate."] stickyvalue=".$stickyvalue." ThreadDeleted=".$ThreadDeleted." ThreadVisibility=".$ThreadVisibility." WhoCanReply=".$WhoCanReply,"ForumModerator"); - $sql="update forums_threads set "; - if ($IdGroup === 0) { - $sql .= "IdGroup = NULL"; - } else { - $sql .= "IdGroup=" . $IdGroup; - } - $sql .= ",stickyvalue=".$stickyvalue.",expiredate=".$expiredate.",ThreadVisibility='".$ThreadVisibility."',ThreadDeleted='".$ThreadDeleted."',WhoCanReply='".$WhoCanReply."' where id=".$IdThread; - -// die ($sql) ; - $this->dao->query($sql); - } - - if (isset($vars["submit"]) and ($vars["submit"]=="add translated title")) { // if a new translation is to be added for a title - $IdThread=(int)$vars["IdThread"] ; - $qry=$this->dao->query("select * from forum_trads where IdTrad=".$vars["IdTrad"]." and IdLanguage=".$vars["IdLanguage"]); - $rr=$qry->fetch(PDB::FETCH_OBJ) ; - if (empty($rr->id)) { // Only proceed if no such a title exists - $ss=$vars["NewTranslatedTitle"] ; - $this->InsertInFTrad($ss,"forums_threads.IdTitle",$IdThread, $this->session->get("IdMember"), $vars["IdLanguage"],$vars["IdTrad"]) ; - MOD_log::get()->write("Updating Thread=#".$IdThread." Adding translation for title in language=[".$vars["IdLanguage"]."]","ForumModerator"); - } - } - - $IdPost=(int)$vars['IdPost'] ; - - if (isset($vars["submit"]) and ($vars["submit"]=="update post")) { // if an effective update was chosen for a forum trads - $OwnerCanStillEdit="'".$vars["OwnerCanStillEdit"]."'" ; - - MOD_log::get()->write("Updating Post=#".$IdPost." Setting OwnerCanStillEdit=[".$OwnerCanStillEdit."]","ForumModerator"); - $this->dao->query("update forums_posts set OwnerCanStillEdit=".$OwnerCanStillEdit." where id=".$IdPost); - } - - if (isset($vars["submit"]) and ($vars["submit"]=="add translated post")) { // if a new translation is to be added for a title - $IdPost=(int)$vars["IdPost"] ; - $qry=$this->dao->query("select * from forum_trads where IdTrad=".$vars["IdTrad"]." and IdLanguage=".$vars["IdLanguage"]); - $rr=$qry->fetch(PDB::FETCH_OBJ) ; - if (empty($rr->id)) { // Only proceed if no such a post exists - $ss=$this->dao->escape($vars["NewTranslatedPost"]) ; - $this->InsertInFTrad($ss,"forums_posts.IdContent",$IdPost, $this->session->get("IdMember"), $vars["IdLanguage"],$vars["IdTrad"]) ; - MOD_log::get()->write("Updating Post=#".$IdPost." Adding translation for title in language=[".$vars["IdLanguage"]."]","ForumModerator"); - $this->updatePostInManticoreIndex($vars); - } - } - - $IdPost=(int)$vars['IdPost'] ; - - if (isset($vars["submit"]) and ($vars["submit"]=="update post")) { // if an effective update was chosen for a forum trads - $IdThread=(int)$vars["IdThread"] ; - $OwnerCanStillEdit=$vars["OwnerCanStillEdit"] ; - $PostVisibility=$vars["PostVisibility"] ; - $PostDeleted=$vars["PostDeleted"] ; - - MOD_log::get()->write("Updating Post=#".$IdPost." Setting OwnerCanStillEdit=[".$OwnerCanStillEdit."] PostVisibility=[".$PostVisibility."] PostDeleted=[".$PostDeleted."] ","ForumModerator"); - $this->dao->query("update forums_posts set OwnerCanStillEdit='".$OwnerCanStillEdit."',PostVisibility='".$PostVisibility."',PostDeleted='".$PostDeleted."' where id=".$IdPost); - // Update last post id - $query = " - SELECT - id - FROM - forums_posts - WHERE - threadid = " . $IdThread . " - AND PostDeleted = 'NotDeleted' - ORDER BY create_time DESC - LIMIT 1"; - $s = $this->dao->query($query); - $row = $s->fetch(PDB::FETCH_OBJ); - if (null === $row) { - // last post of thread was deleted - $this->dao->query("update forums_threads set ThreadDeleted='Deleted' where id=".$IdThread); - } else { - $id = $row->id; - $update = " - UPDATE - forums_threads - SET - last_postid = " . $id . " - WHERE - id = " . $IdThread; - $this->dao->query($update); - } - } - - if (isset($vars["IdForumTrads"])) { // if an effective update was chosen for a forum trads - $this->DofTradUpdate($vars["IdForumTrads"],$vars["Sentence"],$vars["IdLanguage"]) ; // update the corresponding translations - $this->updatePostInManticoreIndex($vars); - } - - PPostHandler::clearVars(); - - return PVars::getObj('env')->baseuri.$this->forums_uri.'modfulleditpost/'.$IdPost; - } // end of ModeratorEditPostProcess - - public function delProcess() { - if (!($User = $this->getLoggedInMember())) { - return false; - } - - if ($this->BW_Right->HasRight("ForumModerator","Delete")) { - $this->dao->query("START TRANSACTION"); - $query = sprintf( - " -SELECT - `forums_posts`.`threadid`, - `forums_threads`.`first_postid`, - `forums_threads`.`last_postid`, - `forums_threads`.`expiredate`, - `forums_threads`.`stickyvalue` -FROM `forums_posts` -LEFT JOIN `forums_threads` ON (`forums_posts`.`threadid` = `forums_threads`.`id`) -WHERE `forums_posts`.`id` = '%d' - ", - $this->messageId - ); - $s = $this->dao->query($query); - if (!$s) { - throw new PException('Could not retrieve Threadinfo!'); - } - $topicinfo = $s->fetch(PDB::FETCH_OBJ); - - if ($topicinfo->first_postid == $this->messageId) { // Delete the complete topic - - $query = - " -UPDATE `forums_threads` -SET `first_postid` = NULL, `last_postid` = NULL -WHERE `id` = '$topicinfo->threadid' - " - ; - $this->dao->query($query); - - $query = - " -DELETE FROM `forums_posts` -WHERE `threadid` = '$topicinfo->threadid' - " - ; - $this->dao->query($query); - MOD_log::get()->write("deleting posts where Thread=#". $topicinfo->threadid, "Forum"); - - // Prepare a notification (before the delete !) - $this->prepare_notification($this->messageId,"deletethread") ; - - $query = - " -DELETE FROM `forums_threads` -WHERE `id` = '$topicinfo->threadid' - " - ; - $this->dao->query($query); - - $redir = 'forums'; - } else { // Delete a single post - /* - * Check if we are deleting the very last post of a topic - * if so, we have to update the `last_postid` field of the `forums_threads` table - */ - if ($topicinfo->last_postid == $this->messageId) { - $query = - " -UPDATE `forums_threads` -SET `last_postid` = NULL -WHERE `id` = '$topicinfo->threadid' - " - ; - $this->dao->query($query); - } - MOD_log::get()->write("deleting single post where Post=#". $this->messageId, "Forum"); - - $this->prepare_notification($this->messageId,"deletepost") ; // Prepare a notification (before the delete !) - - $query = - " -DELETE FROM `forums_posts` -WHERE `id` = '$this->messageId' - " - ; - $this->dao->query($query); - - if ($topicinfo->last_postid == $this->messageId) { - $query = - " -SELECT `forums_posts`.`id` AS `postid` -FROM `forums_posts` -WHERE `threadid` = '$topicinfo->threadid' -ORDER BY `create_time` DESC LIMIT 1 - " - ; - $s = $this->dao->query($query); - if (!$s) { - throw new PException('Could not retrieve Postinfo!'); - } - $lastpost = $s->fetch(PDB::FETCH_OBJ); - - $lastpostupdate = sprintf(", `last_postid` = '%d'", $lastpost->postid); - } else { - $lastpostupdate = ''; - } - - $query = - " -UPDATE `forums_threads` -SET `replies` = (`replies` - 1) $lastpostupdate -WHERE `id` = '$topicinfo->threadid' - " - ; - $this->dao->query($query); - - $redir = $this->forums_uri.'s'.$topicinfo->threadid; - } - - $this->dao->query("COMMIT"); - } - - - header('Location: '.PVars::getObj('env')->baseuri.$redir); - PPHP::PExit(); - } - - - private function checkVarsReply(&$vars) { - $errors = []; - - if (!isset($vars['topic_text']) || empty($vars['topic_text'])) { - $errors[] = 'text'; - } - if ($errors) { - $vars['errors'] = $errors; - return false; - } - - return true; - } - - public function checkVarsTopic(&$vars) { - $errors = []; - - if (!isset($vars['topic_title']) || empty($vars['topic_title'])) { - $errors[] = 'title'; - } - if (!isset($vars['topic_text']) || empty($vars['topic_text'])) { - $errors[] = 'text'; - } - - if ($errors) { - $vars['errors'] = $errors; - return false; - } - - return true; - } - - private function replyTopic(&$vars) { - if (!($User = $this->getLoggedInMember())) { - throw new PException('User gone missing...'); - } - $IdGroup = 0; - if (isset($vars['IdGroup'])) { - $IdGroup = $vars['IdGroup']; - } - if (isset($vars['PostVisibility'])) { - $postVisibility = $vars['PostVisibility']; - } else { - // Someone unchecked the box for group only posts - $vars['ThreadVisibility'] = $vars['PostVisibility'] = $postVisibility = 'MembersOnly'; - } - - $this->dao->query("START TRANSACTION"); - $query = sprintf( - " -INSERT INTO `forums_posts` ( `threadid`, `create_time`, `message`,`IdWriter`,`IdFirstLanguageUsed`,`PostVisibility`, `IdContent`,`edit_count`) -VALUES ('%d', NOW(), '%s','%d',%d,'%s',0, 0) - ", - $this->threadid, - $this->dao->escape($this->cleanupText($vars['topic_text'])), - $this->session->get("IdMember"), $this->GetLanguageChoosen(), $postVisibility - ); - - $result = $this->dao->query($query); - - - $postId = $result->insertId(); - - // Now create the text in forum_trads - $this->InsertInFTrad($this->dao->escape($this->cleanupText($vars['topic_text'])),"forums_posts.IdContent",$postId) ; - - $query = - " -UPDATE `forums_threads` -SET `last_postid` = '$postId', `replies` = `replies` + 1 -WHERE `id` = '$this->threadid' - " - ; - $this->dao->query($query); - - $this->dao->query("COMMIT"); - - - // subscription if any is out of transaction, this is not so important - if ((isset($vars['NotifyMe'])) and ($vars['NotifyMe']=="on")) { - if (!$this->IsThreadSubscribed($this->threadid,$this->session->get("IdMember"))) { - $this->SubscribeThread($this->threadid,$this->session->get("IdMember")) ; - } - } - else { - $vars['NotifyMe']="Not Asked" ; - if ($this->IsThreadSubscribed($this->threadid,$this->session->get("IdMember"))) { - $this->UnsubscribeThreadDirect($this->threadid,$this->session->get("IdMember")) ; - } - } - - $this->addPostToManticoreIndex($vars, $postId, $this->threadid, $this->session->get("IdMember"), $this->GetLanguageChoosen()); - - MOD_log::get()->write("Replying new Post=#". $postId." in Thread=#".$this->threadid." NotifyMe=[".$vars['NotifyMe']."]", "Forum"); - $this->prepare_notification($postId,"reply") ; // Prepare a notification - - return $postId; - } // end of replyTopic - - /** - * Create a new Topic (with initial first post) - * @return int topicid Id of the newly created topic - */ - public function newTopic(&$vars) { - if (!($User = $this->getLoggedInMember())) { - throw new PException('User gone missing...'); - } - $IdGroup = null; - if (isset($vars['IdGroup']) && $vars['IdGroup'] !== 0) { - $IdGroup = $vars['IdGroup']; - } - if (isset($vars['ThreadVisibility'])) { - $ThreadVisibility = $vars['ThreadVisibility']; - } else { - // Someone unchecked the box for group only posts - if (isset($vars['groupOnly']) && ($vars['groupOnly'] === '1')) - { - $ThreadVisibility = 'GroupOnly'; - } else { - $ThreadVisibility = 'MembersOnly'; - } - $vars['ThreadVisibility'] = $ThreadVisibility; - } - - /** @var PDBStatement_mysqli $statement */ - $statement = $this->dao->prepare(" - INSERT INTO `forums_posts` - ( - `create_time`, - `message`, - `IdWriter`, - `IdFirstLanguageUsed`, - `PostVisibility`, - `PostDeleted`, - `IdContent`, - `edit_count` - ) - VALUES (NOW(), ?, ?, ?, ?, 'NotDeleted', 0, 0) - "); - $text = $this->cleanupText($vars['topic_text']); - $memberId = $this->session->get("IdMember"); - $language = $this->GetLanguageChoosen(); - $statement->bindParam(1, $text); - $statement->bindParam(2, $memberId); - $statement->bindParam(3, $language); - $statement->bindParam(4,$ThreadVisibility); - - $result = $statement->execute(); - $postId = $statement->insertId(); - - $this->InsertInFTrad( - $this->dao->escape($this->cleanupText($vars['topic_text'])), - "forums_posts.IdContent", - $postId, - $memberId, - $language - ); - - $statement = $this->dao->prepare(" - INSERT INTO `forums_threads` ( - `title`, `first_postid`, `last_postid`, - `IdFirstLanguageUsed`,`IdGroup`,`ThreadVisibility`, `IdTitle`, `replies`, `views`, `stickyvalue`) - VALUES (?, ?, ?, ?, ?, ?, 0, 0, 0, 0) - "); - - $title = strip_tags((string) $vars['topic_title']); - $statement->bindParam(1, $title); - $statement->bindParam(2, $postId); - $statement->bindParam(3, $postId); - $statement->bindParam(8, $language); - $statement->bindParam(9, $IdGroup); - $statement->bindParam(10, $ThreadVisibility); - - $result = $statement->execute(); - $threadId = $statement->insertId(); - - $ss=$this->dao->escape(strip_tags(((string) $vars['topic_title']))) ; - $this->InsertInFTrad($ss,"forums_threads.IdTitle",$threadId) ; - - $statement = $this->dao->prepare("UPDATE `forums_posts` SET `threadId` = ? WHERE `Id` = ?"); - $statement->bindParam(1, $threadId); - $statement->bindParam(2, $postId); - - $result = $statement->execute(); - - // subscription if any is out of transaction, this is not so important - if ((isset($vars['NotifyMe'])) and ($vars['NotifyMe']=="on")) { - $this->SubscribeThread($threadId,$this->session->get("IdMember")) ; - } - else { - $vars['NotifyMe']="Not Asked" ; - } - - $this->addPostToManticoreIndex($vars, $postId, $threadId, $memberId, $language); - - $this->prepare_notification($postId,"newthread") ; // Prepare a notification - MOD_log::get()->write("New Thread new Tread=#".$threadId." Post=#". $postId." IdGroup=#".$IdGroup." NotifyMe=[".$vars['NotifyMe']."] initial Visibility=".$ThreadVisibility, "Forum"); - - return $threadId; - } // end of NewTopic - - private $topic; - - /** - * function prepareTopic prepares the detail of a topic for display according to threadid - * if @$WithDetail is set to true, additional details (available languages and original author are displayed) - */ - public function prepareTopic($WithDetail=false) { - $this->topic = new Topic(); - $this->topic->WithDetail = $WithDetail; - - // Topic Data - $query = " - SELECT - `forums_threads`.`title`, - `forums_threads`.`IdTitle`, - `forums_threads`.`replies`, - `forums_threads`.`id` as IdThread, - `forums_threads`.`views`, - `forums_threads`.`ThreadVisibility`, - `forums_threads`.`ThreadDeleted`, - `forums_threads`.`WhoCanReply`, - `forums_threads`.`first_postid`, - `forums_threads`.`expiredate`, - `forums_threads`.`stickyvalue`, - `forums_threads`.`IdGroup`, - `groups`.`Name` AS `GroupName` - FROM - `forums_threads` - LEFT JOIN `groups` ON (`forums_threads`.`IdGroup` = `groups`.`id`) - WHERE - `forums_threads`.`id` = '$this->threadid' - AND ($this->PublicThreadVisibility) - AND ($this->ThreadGroupsRestriction) - "; - - $s = $this->dao->query($query); - if (!$s) { - throw new PException('Could not retrieve Thread=#".$this->threadid." !'); - } - - $topicinfo = $s->fetch(PDB::FETCH_OBJ); - // Check if any result was found - if (!$topicinfo) { - $topicinfo = new stdClass; - } - if (isset($topicinfo->WhoCanReply)) { - if ($topicinfo->WhoCanReply=="MembersOnly") { - $topicinfo->CanReply=$this->getLoggedInMember()->Status !== \App\Doctrine\MemberStatusType::ACCOUNT_ACTIVATED; - } - else if ($topicinfo->WhoCanReply=="GroupsMembersOnly") { - if ($topicinfo->IdGroup==0) { - $topicinfo->CanReply=$this->getLoggedInMember()->Status !== \App\Doctrine\MemberStatusType::ACCOUNT_ACTIVATED ; - } - else { - $topicinfo->CanReply=in_array($topicinfo->IdGroup,$this->MyGroups) ; // Set to true only if current member is member of the group - } - } - else if ($topicinfo->WhoCanReply=="ModeratorOnly") { - if ($this->BW_Right->HasRight("ForumModerator")) { - $topicinfo->CanReply=true ; - } - else { - $topicinfo->CanReply=false ; - } - } - else { - $topicinfo->CanReply=false ; - } - } - - $this->topic->topicinfo = $topicinfo; - $this->topic->IdThread=$this->threadid ; - - $from = $this->POSTS_PER_PAGE * ($this->getPage() - 1); - $order = ($this->ForumOrderList == "Yes") ? "ASC" : "DESC"; - - $query = sprintf(" - SELECT - `forums_posts`.`id` AS `postid`, - forums_posts.id as IdPost, - UNIX_TIMESTAMP(create_time) AS posttime, - message, - IdContent, - IdWriter, - geo__names.name AS city, - geonamescountries.name AS country, - forums_posts.threadid, - OwnerCanStillEdit, - member.Username as OwnerUsername, - PostVisibility, - PostDeleted, - forums_threads.IdGroup - FROM - forums_threads, - forums_posts - LEFT - JOIN member ON forums_posts.IdWriter = member.id - LEFT JOIN - address AS a ON a.member_id = member.id AND a.active = 1 - LEFT JOIN - geo__names ON a.location = geo__names.geonameId - LEFT JOIN - geonamescountries ON geo__names.country = geonamescountries.country - WHERE - forums_posts.threadid = '%d' - AND forums_posts.threadid=forums_threads.id - AND ({$this->PublicPostVisibility}) - AND ({$this->ThreadGroupsRestriction}) - ORDER BY - posttime {$order} - LIMIT %d, %d", - $this->threadid,$from,$this->POSTS_PER_PAGE - ); - - $s = $this->dao->query($query); - if (!$s) { - throw new PException('Could not retrieve Posts)!'); - } - while ($row = $s->fetch(PDB::FETCH_OBJ)) { - if ($WithDetail) { // if details are required retrieve all the translated text for posts (sentence, owner, modification time and translator name) of this thread - $sw = $this->dao->query("select forum_trads.IdLanguage,UNIX_TIMESTAMP(forum_trads.created) as trad_created, UNIX_TIMESTAMP(forum_trads.updated) as trad_updated, forum_trads.Sentence,IdOwner,IdTranslator,languages.ShortCode,languages.Name,mTranslator.Username as TranslatorUsername ,mOwner.Username as OwnerUsername - from forum_trads,languages,member as mOwner, member as mTranslator - where languages.id=forum_trads.IdLanguage and forum_trads.IdTrad=".$row->IdContent." and mOwner.id=IdOwner and mTranslator.id=IdTranslator order by forum_trads.id asc"); - while ($roww = $sw->fetch(PDB::FETCH_OBJ)) { - $row->Trad[]=$roww ; - } - } - $this->topic->posts[] = $row; - } // end // Now retrieve all the Posts of this thread - - // Check if the current user has subscribe to this thread or not (to display the proper option, subscribe or unsubscribe) - if ($this->session->has( "IdMember" )) { - $memberId = $this->session->get("IdMember"); - $query = " -SELECT - `members_threads_subscribed`.`id` AS IdSubscribe, - `members_threads_subscribed`.`UnSubscribeKey` AS IdKey, - `members_threads_subscribed`.`notificationsEnabled` AS notificationsEnabled -FROM members_threads_subscribed -WHERE IdThread = {$this->threadid} -AND IdSubscriber = {$memberId}"; - $s = $this->dao->query($query); - if (!$s) { - throw new PException('Could if has subscribed to Thread=#".$this->threadid." !'); - } - $row = $s->fetch(PDB::FETCH_OBJ) ; - if (isset($row->IdSubscribe)) { - $this->topic->notificationsEnabled = $row->notificationsEnabled; - $this->topic->IdSubscribe= $row->IdSubscribe ; - $this->topic->IdKey= $row->IdKey ; - } - if (isset($this->topic->topicinfo->IdGroup)) { - // Check if member has enabled group mails - $group = $this->createEntity('Group', $this->topic->topicinfo->IdGroup); - $member = $this->createEntity('Member', $this->session->get("IdMember")); - $membership = $this->createEntity('GroupMembership')->getMembership($group, $member); - if ($membership) { - $this->topic->isGroupSubscribed = ($membership->IacceptMassMailFromThisGroup == 'yes'); - if (!isset($row->IdSubscribe)) { - $this->topic->notificationsEnabled = ($membership->notificationsEnabled); - } - } - } - } - - // Increase the number of views - $query = " -UPDATE `forums_threads` -SET `views` = (`views` + 1) -WHERE `id` = '$this->threadid' LIMIT 1 - " ; - $this->dao->query($query); - - } // end of prepareTopic - - public function initLastPosts() { - $query = sprintf(" -SELECT - `forums_posts`.`id` AS `postid`, - UNIX_TIMESTAMP(`create_time`) AS `posttime`, - `message`, - `IdContent`, - `member`.`username` AS `OwnerUsername`, - `IdWriter`, - forums_threads.`id` as `threadid`, - `PostVisibility`, - `PostDeleted`, - `ThreadDeleted`, - `OwnerCanStillEdit`, - `geo__names`.`name` as `city`, - `geonamescountries`.`name` as `country`, - `IdGroup` -FROM forums_posts, forums_threads, member, address -LEFT JOIN `geo__names` ON (address.location = `geo__names`.`geonameId`) -LEFT JOIN `geonamescountries` ON (geo__names.country = `geonamescountries`.`country`) -WHERE `forums_posts`.`threadid` = '%d' AND `forums_posts`.`IdWriter` = `member`.`id` -AND address.member_id = member.id AND address.active = 1 - AND `forums_posts`.`threadid`=`forums_threads`.`id` - and ({$this->PublicPostVisibility}) - and ({$this->PublicThreadVisibility}) - and ({$this->PostGroupsRestriction}) - and ThreadDeleted <> 'Deleted' - And PostDeleted <> 'Deleted' -ORDER BY `posttime` DESC -LIMIT %d - ", - $this->threadid, - Forums::NUMBER_LAST_POSTS_PREVIEW - ); - $s = $this->dao->query($query); - if (!$s) { - throw new PException('Could not retrieve Posts!'); - } - $this->topic->posts = []; - while ($row = $s->fetch(PDB::FETCH_OBJ)) { - $sw = $this->dao->query("select forum_trads.IdLanguage,UNIX_TIMESTAMP(forum_trads.created) as trad_created, UNIX_TIMESTAMP(forum_trads.updated) as trad_updated, forum_trads.Sentence,IdOwner,IdTranslator,languages.ShortCode,languages.Name,mTranslator.Username as TranslatorUsername ,mOwner.Username as OwnerUsername from forum_trads,languages,member as mOwner, member as mTranslator - where languages.id=forum_trads.IdLanguage and forum_trads.IdTrad=".$row->IdContent." and mOwner.id=IdOwner and mTranslator.id=IdTranslator order by forum_trads.id asc"); - while ($roww = $sw->fetch(PDB::FETCH_OBJ)) { - $row->Trad[]=$roww ; - } - $this->topic->posts[] = $row; - } - } // end of initLastPosts - - private function updateSubscriptions($memberId, $enable) { - // update subscription (keep old assignments through negating if disabling) - // members_threads_subscribed - $query =" - UPDATE - members_threads_subscribed - SET - notificationsEnabled = '" . ($enable ? 1 : 0) . "' - WHERE - IdSubscriber = " . $memberId; - $this->dao->query($query); - $this->updateGroupNotifications($memberId, $enable); - $this->dao->query($query); - } - - private function updateGroupNotifications($memberId, $enable) { - $query = " - UPDATE - membersgroups - SET - notificationsEnabled = '" . ($enable ? 1 : 0) . "' - WHERE - IdMember = " . $memberId . " - AND IacceptMassMailFromThisGroup = 'Yes' - "; - $this->dao->query($query); - } - - private function updateGroupNotification($groupId, $memberId, $enable) { - $query = " - UPDATE - membersgroups - SET - notificationsEnabled = '" . ($enable ? 1 : 0) . "' - WHERE - IdGroup = " . $groupId . " - AND IdMember = " . $memberId . " - AND IacceptMassMailFromThisGroup = 'Yes' - "; - $this->dao->query($query); - } - - public function disableSubscriptions() { - $member = $this->getLoggedInMember(); - if ($member) { - $this->updateSubscriptions($member->id, false); - } - } - - public function enableSubscriptions() { - $member = $this->getLoggedInMember(); - if ($member) { - $this->updateSubscriptions($member->id, true); - } - } - - public function disableGroup($IdGroup) { - $member = $this->getLoggedInMember(); - if ($member) { - $this->updateGroupNotification($IdGroup, $member->id, false); - } - } - - public function enableGroup($IdGroup) { - $member = $this->getLoggedInMember(); - if ($member) { - $this->updateGroupNotification($IdGroup, $member->id, true); - } - } - - public function subscribeGroup($IdGroup) { - $member = $this->getLoggedInMember(); - if ($member) { - $group = $this->createEntity('Group', $IdGroup); - if (!($membership = $this->createEntity('GroupMembership')->getMembership($group, $member))) - { - return false; - } - - $membership->updateMembership('yes', $membership->Comment); - } - } - - public function unsubscribeGroup($IdGroup) { - $member = $this->getLoggedInMember(); - if ($member) { - $group = $this->createEntity('Group', $IdGroup); - if (!($membership = $this->createEntity('GroupMembership')->getMembership($group, $member))) - { - return false; - } - - $membership->updateMembership('no', $membership->Comment); - } - } - - /** - * This function retrieve the subscriptions for the member $cid and/or the the thread IdThread and/or theIdTag - * @$cid : either the IdMember or the username of the member we are searching the subscription - * this $cid and $IdThread and $IdTag parameters are only used if the current member has moderator rights - * It returns a $TResults structure - * Very important : member who are not moderators cannot see other people subscriptions - * @param bool $IdThread - * @param bool $IdTag - * @return StdClass - * @throws PException - */ - public function searchSubscriptions() { - $member= $this->getLoggedInMember(); - if (!$member) { - return []; - } - $TResults = new StdClass(); - $query = " -SELECT - `members_threads_subscribed`.`id` as IdSubscribe, - `members_threads_subscribed`.`created` AS `subscribedtime`, - `ThreadVisibility`, - `ThreadDeleted`, - `forums_threads`.`id` as IdThread, - `forums_threads`.`title`, - `forums_threads`.`IdTitle`, - `members_threads_subscribed`.`ActionToWatch`, - `members_threads_subscribed`.`UnSubscribeKey`, - `members_threads_subscribed`.`notificationsEnabled` -FROM `forums_threads`,`members_threads_subscribed` -WHERE `forums_threads`.`id` = `members_threads_subscribed`.`IdThread` -AND `members_threads_subscribed`.`IdSubscriber`= {$member->id} -ORDER BY `subscribedtime` DESC - "; - $s = $this->dao->query($query); - if (!$s) { - throw new PException('Could not retrieve members_threads_subscribed sts via searchSubscription !'); - } - - $TResults->TData = []; - while ($row = $s->fetch(PDB::FETCH_OBJ)) { - $TResults->TData[] = $row; - } - - $query = " - SELECT - Name, IdGroup, IdMember, IacceptMassMailFromThisGroup As AcceptMails, notificationsEnabled - FROM - `membersgroups` mg, - `groups` g - WHERE - g.id = mg.IdGroup - AND IdMember = '{$member->id}' - AND Status = 'In' - ORDER BY - Name"; - $s = $this->dao->query($query); - if (!$s) { - throw new PException('Could load group memberships'); - } - $TResults->Groups = []; - while ($row = $s->fetch(PDB::FETCH_OBJ)) { - $TResults->Groups[] = $row; - } - return $TResults; - } // end of searchSubscriptions - - - /** - * This function remove the subscription marked by IdSubscribe - * @IdSubscribe is the primary key of the members_threads_subscribed area to remove - * @Key is the key to check to be sure it is not an abuse of url - * It returns a $res=1 if ok - */ - public function UnsubscribeThread($IdSubscribe=0,$Key="") { - $query = sprintf( - " -SELECT - members_threads_subscribed.id AS IdSubscribe, - IdThread, - IdSubscriber, - Username from member, - members_threads_subscribed -WHERE member.id=members_threads_subscribed.IdSubscriber -AND members_threads_subscribed.id=%d -AND UnSubscribeKey='%s' - ", - $IdSubscribe,$this->dao->escape($Key) - ); - $s = $this->dao->query($query); - if (!$s) { - throw new PException('Forum->UnsubscribeThread Could not retrieve the subscription !'); - } - $row = $s->fetch(PDB::FETCH_OBJ) ; - if (!isset($row->IdSubscribe)) { - MOD_log::get()->write("No entry found while Trying to unsubscribe thread IdSubscribe=#".$IdSubscribe." IdKey=".$Key, "Forum"); - return(false) ; - } - $query = sprintf( - " -DELETE -FROM members_threads_subscribed -WHERE id=%d -AND UnSubscribeKey='%s' - ", - $IdSubscribe, - $this->dao->escape($Key) - ); - $s = $this->dao->query($query); - if (!$s) { - throw new PException('Forum->UnsubscribeThread delete failed !'); - } - if ($this->session->has( "IdMember" )) { - MOD_log::get()->write("Unsubscribing member ".$row->Username." from Thread=#".$row->IdThread, "Forum"); - if ($this->session->get("IdMember")!=$row->IdSubscriber) { // If it is not the member himself, log a forum action in addition - MOD_log::get()->write("Unsubscribing member ".$row->Username." from Thread=#".$row->IdThread, "ForumModerator"); - } - } - else { - MOD_log::get()->write("Unsubscribing member ".$row->Username." from Thread=#".$row->IdThread." without beeing logged", "Forum"); - } - return(true) ; - } // end of UnsubscribeThread - - /** - * This function remove the subscription without checking the key - * - * @param unknown_type $IdThread the id of the thread to unsubscribe to - * @param unknown_type $ParamIdMember the member to unsubscribe, if 0, the current member will eb used - * @return unknown - */ - public function UnsubscribeThreadDirect($IdThread=0,$ParamIdMember=0) { - $IdMember=$ParamIdMember ; - if ($this->session->has( "IdMember" ) and $IdMember==0) { - $IdMember=$this->session->get("IdMember") ; - } - - if ($IdMember==0) { // No need to do something if no member is logged - return ; - } - - $query = sprintf( - " -DELETE -FROM members_threads_subscribed -WHERE IdSubscriber=%d -AND IdThread=%d - ", - $IdMember, - $IdThread - ); - $s = $this->dao->query($query); - if (!$s) { - throw new PException('Forum->UnsubscribeThreadDirect failed to delete !'); - } - MOD_log::get()->write("Unsubscribing direct (By NotifyMe) member=#".$IdMember." from Thread=#".$IdThread, "Forum"); - return(true) ; - } // end of UnsubscribeThreadDirect - - - /** - * This function allow to subscribe to a thread - * - * @$IdThread : The thread we want the user to subscribe to - * @$ParamIdMember optional IdMember, by default set to 0 in this case current logged member will be used - * It also check that member is not yet subscribing to thread - */ - public function SubscribeThread($IdThread,$ParamIdMember=0) { - $IdMember=$ParamIdMember ; - if ($this->session->has( "IdMember" ) and $IdMember==0) { - $IdMember=$this->session->get("IdMember") ; - } - - // Check if there is a previous Subscription - if ($this->IsThreadSubscribed($IdThread,$this->session->get("IdMember"))) { - MOD_log::get()->write("Allready subscribed to Thread=#".$IdThread, "Forum"); - return(false) ; - } - $key=MD5(random_int(100000,900000)) ; - $query = "INSERT INTO - members_threads_subscribed(IdThread,IdSubscriber,UnSubscribeKey,notificationsEnabled,created) - VALUES(".$IdThread.",".$this->session->get("IdMember").",'".$this->dao->escape($key)."', 1,NOW())" ; - $s = $this->dao->query($query); - if (!$s) { - throw new PException('Forum->SubscribeThread failed !'); - } - $IdSubscribe= $s->insertId() ; - MOD_log::get()->write("Subscribing to Thread=#".$IdThread." IdSubscribe=#".$IdSubscribe, "Forum"); - } // end of UnsubscribeThread - - /** - * This function allows to enable a thread that has been disabled - * - * @$IdThread : The thread we want the user to subscribe to - * @$ParamIdMember optional IdMember, by default set to 0 in this case current logged member will be used - * It also check that member is not yet subscribing to thread - */ - public function EnableThread($IdThread) { - $member = $this->getLoggedInMember(); - if (!$member) { - return; - } - - // Check if there is a previous Subscription - if ($this->IsThreadSubscribed($IdThread,$member->id)) { - $query = " - UPDATE - members_threads_subscribed - SET - notificationsEnabled = '1' - WHERE - IdThread = " . $IdThread . " - AND IdSubscriber = " . $member->id; - $this->dao->query($query); - } - } // end of EnableThread - - /** - * This function allows to disable notifications for a thread if the group has been subscribed - * - * @$IdThread : The thread we want the user to disable - */ - public function DisableThread($IdThread) { - $member = $this->getLoggedInMember(); - if (!$member) { - return; - } - - // Make sure there is something to disable - if (!$this->IsThreadSubscribed($IdThread,$member->id)) { - $this->SubscribeThread($IdThread); - } - - // if there was already a disable notification this won't change it. - $query = " - UPDATE - members_threads_subscribed - SET - notificationsEnabled = '0' - WHERE - IdThread = " . $IdThread . " - AND IdSubscriber = " . $member->id; - $this->dao->query($query); - } // end of DisableThread - - // This function retrieve search post of the member $cid - //@$cid : either the IdMember or the username of the member we are searching the post - public function searchUserposts($cid=0) { - $IdMember=0 ; - if (is_numeric($cid)) { - $IdMember=$cid ; - } - else - { - if (!($member = $this->createEntity('Member')->findByUsername($cid))) - { - throw new PException('Could not retrieve members id via username !'); - } - $IdMember = $member->id; - } - - $query = sprintf( - "SELECT `forums_posts`.`id` AS `postid`,`forums_posts`.`id` as IdPost, UNIX_TIMESTAMP(`create_time`) AS `posttime`, `message`, - `OwnerCanStillEdit`,`IdContent`, `forums_threads`.`id` AS `threadid`, `forums_threads`.`title`, - `ThreadVisibility`, - `ThreadDeleted`, - `PostVisibility`, - `PostDeleted`, - `ThreadDeleted`, - `forums_threads`.`IdTitle`,`forums_threads`.`IdGroup`, `IdWriter`, `member`.`Username` AS `OwnerUsername`, `groups`.`Name` AS `GroupName`, `geo__names`.`country` - FROM (forums_posts, member, forums_threads, address) -LEFT JOIN `groups` ON (`forums_threads`.`IdGroup` = `groups`.`id`) -LEFT JOIN `geo__names` ON (address.location = geo__names.geonameId) -WHERE `forums_posts`.`IdWriter` = %d AND `forums_posts`.`IdWriter` = `member`.`id` -AND `forums_posts`.`threadid` = `forums_threads`.`id` -AND address.member_id = member.id AND address.active = 1 -AND ($this->PublicThreadVisibility) -AND ($this->PublicPostVisibility) -AND ($this->PostGroupsRestriction) -ORDER BY `posttime` DESC", $IdMember ); - $s = $this->dao->query($query); - if (!$s) { - throw new PException('Could not retrieve Posts via searchUserposts !'); - } - $posts = []; - while ($row = $s->fetch(PDB::FETCH_OBJ)) { - $sw = $this->dao->query("select forum_trads.IdLanguage,UNIX_TIMESTAMP(forum_trads.created) as trad_created, UNIX_TIMESTAMP(forum_trads.updated) as trad_updated, forum_trads.Sentence,IdOwner,IdTranslator,languages.ShortCode,languages.Name,mTranslator.Username as TranslatorUsername ,mOwner.Username as OwnerUsername from forum_trads,languages,member as mOwner,member as mTranslator where languages.id=forum_trads.IdLanguage and forum_trads.IdTrad=".$row->IdContent." and mTranslator.id=IdTranslator and mOwner.id=IdOwner order by forum_trads.id asc"); - while ($roww = $sw->fetch(PDB::FETCH_OBJ)) { - $row->Trad[]=$roww ; - } - $posts[] = $row; - - } // - - return $posts; - } // end of searchUserposts - - public function getTopic() { - return $this->topic; - } - - /** - * Check if it's a topic or a forum - * @return bool true on topic - * @return bool false on forum - */ - public function isTopic() { - return (bool) $this->threadid; - } - - private $threadid = 0; - private $page = 1; - private $page_array = []; - private $messageId = 0; - private $TopMode=Forums::CV_TOPMODE_LANDING; // define that we use the landing page for top mode - - public function setTopMode($Mode) { - $this->TopMode = $Mode ; - } - public function getTopMode() { - return $this->TopMode; - } - public function setGroupId($IdGroup) { - $this->IdGroup = (int) $IdGroup; - } - public function setThreadId($threadid) { - $this->threadid = (int) $threadid; - } - public function getThreadId() { - return $this->threadid; - } - public function getIdGroup() { - return $this->IdGroup; - } - public function getPage() { - return $this->page; - } - public function setPage($page) { - $this->page = (int) $page; - } - public function getPageArray() { - return $this->page_array; - } - public function setPageArray($page_array) { - $this->page_array = $page_array; - } - public function pushToPageArray($page) { - $this->page_array[] = $page; - } - public function setMessageId($messageid) { - $this->messageId = (int) $messageid; - } - public function getMessageId() { - return $this->messageId; - } - - public function getIdContent() { // Return the IdContent (IdTrad for the id of the post, according to currently set $this->messageId - $IdContent=-1 ; - $query = "select `IdContent` from `forums_posts` where `id`=".$this->messageId ; - $s = $this->dao->query($query); - if (!$s) { - throw new PException('Forum-> getIdContent failed for this->messageId='.$this->messageId); - } - $row = $s->fetch(PDB::FETCH_OBJ) ; - if (isset ($row->IdContent)) { - $IdContent=$row->IdContent ; - } - return $IdContent; - } - - /* - * cleanupText - * - * @param string $txt - * @access private - * @return string - */ - private function cleanupText($txt) - { - $purifier = MOD_htmlpure::get()->getForumsHtmlPurifier(); - - return $purifier->purify($txt); - } // end of cleanupText - - function GetLanguageName($IdLanguage) { - $query="select id as IdLanguage,Name,Name,ShortCode from languages where id=".($IdLanguage) - . " AND IsWrittenLanguage = 1"; - $s = $this->dao->query($query); - if (!$s) { - throw new PException('Could not retrieve IdLanguage in GetLanguageName entries'); - } else { - $row = $s->fetch(PDB::FETCH_OBJ) ; - $row->Name = $this->getWords()->getSilent('lang' . $row->ShortCode) . " (" . $row->Name . ")"; - return($row) ; - } - return("not Found") ; - } // end of GetLanguageName - - - // This fonction will prepare a list of language to choose - // @DefIdLanguage : an optional language to use - // return an array of object with LanguageName and IdLanguage - public function LanguageChoices($DefIdLanguage=-1) { - - - $tt=[] ; - $allreadyin=[] ; - $ii=0 ; - -// First proposed will deflanguage -// if (!empty($DefIdLanguage) and ($DefIdLanguage>=0)) { - if (($DefIdLanguage>=0)) { - $row=$this->GetLanguageName($DefIdLanguage) ; - array_push($allreadyin,$row->IdLanguage) ; - array_push($tt, "CurrentLanguage"); - array_push($tt,$row) ; - } - // Then next will be english (if not allready in the list) - if (!in_array(0,$allreadyin)) { - $row=$this->GetLanguageName(0) ; - array_push($allreadyin,$row->IdLanguage) ; - array_push($tt, "DefaultLanguage"); - array_push($tt,$row) ; - } - // Then next will the current user language - if (($this->session->has( "IdLanguage" ) and (!in_array($this->session->get("IdLanguage"),$allreadyin)))) { - $row=$this->GetLanguageName($this->session->get("IdLanguage")) ; - array_push($allreadyin,$row->IdLanguage) ; - array_push($tt, "UILanguage"); - array_push($tt,$row); - } - - array_push($tt, "AllLanguages"); - // then now all available languages - $query="select id as IdLanguage,Name,Name,ShortCode from languages where id>0" - . " AND IsWrittenLanguage = 1"; - $s = $this->dao->query($query); - $langarr = []; - while ($row = $s->fetch(PDB::FETCH_OBJ)) { - if (!in_array($row->IdLanguage,$allreadyin)) { - array_push($allreadyin,$row->IdLanguage) ; - $row->Name = $this->getWords()->getSilent('lang_' . $row->ShortCode) . " (" . trim((string) $row->Name) . ")"; - $langarr[] = $row; - } - } - // now sort langarr and append to the $tt array - usort($langarr, "cmpForumLang"); - return($tt + $langarr) ; // returs the array of structures - } // end of LanguageChoices - - // This fonction will prepare a list of group in an array that the moderator can use - public function ModeratorGroupChoice() { - $tt=[] ; - - $query="select groups.id as IdGroup,Name,count(*) as cnt from groups,membersgroups - WHERE membersgroups.IdGroup=groups.id group by groups.id order by Name "; - $s = $this->dao->query($query); - while ($row = $s->fetch(PDB::FETCH_OBJ)) { - $row->GroupName=$row->Name=$this->getGroupName($row->Name); - array_push($tt,$row) ; - } - return($tt) ; // returs the array of structures - - } // end of ModeratorGroupChoices - - // This fonction will prepare a list of group in an array that the user can use - // (according to his member ship) - public function GroupChoice() { - $tt=[] ; - - $query="select groups.id as IdGroup,groups.Name,count(*) as cnt from groups,membersgroups,member - WHERE membersgroups.IdGroup=groups.id and member.id=membersgroups.IdMember and - member.Status in ('Active','ActiveHidden') and member.id=".$this->session->get('IdMember')." and membersgroups.Status='In' group by groups.id order by groups.id "; - $s = $this->dao->query($query); - while ($row = $s->fetch(PDB::FETCH_OBJ)) { - $row->GroupName=$this->getGroupName($row->Name); - array_push($tt,$row) ; - } - return($tt) ; // returs the array of structures - - } // end of GroupChoices - - /** - Return true if the Member is not allowed to see the given post because of groups restrictions - @$IdMember : Id of the member - @$rPost data about the post (Thread and Post visibility, id of the group and delettion status) - return true or false - !! Becareful, in case the member has moderator right, his moderator rights will not be considerated - */ -public function NotAllowedForGroup($IdMember, $rPost) { - if ($rPost->ThreadDeleted=='Deleted') return (true) ; //Deleted thread: no notifications - if ($rPost->PostDeleted=='Deleted') return (true) ; //Deleted post: no notifications - if ($rPost->IdGroup==0) return(false) ; // No group defined, noification can be allowed - - // In case there is a restriction to moderator, check if the member is a moderator - if (($rPost->PostVisibility=='ModeratorOnly') or ($rPost->ThreadVisibility=='ModeratorOnly')) - if ($this->BW_Right->HasRight("ForumModerator")) { - return (false) ; // Moderator are allowed - } - else { - return(true) ; - } - - if (($rPost->PostVisibility=='GroupOnly') or ($rPost->ThreadVisibility=='GroupOnly')) { // If there is a group restriction, we need to check the membership of the member - $query = "select IdGroup from membersgroups where IdMember=".$IdMember." and IdGroup=".$rPost->IdGroup." and Status='In'"; - $qry = $this->dao->query("select IdGroup from membersgroups where IdMember=".$IdMember." and IdGroup=".$rPost->IdGroup." and Status='In'"); - if (!$qry) { - throw new PException('Failed to retrieve groupsmembership for member id =#'.$IdMember.' !'); - } - $rr=$qry->fetch(PDB::FETCH_OBJ) ; - if (isset($rr->IdGroup)) { // If the guy is member of the group - return(false) ; // He is allowed to see - } - else { - return(true) ; - } - } - - // Other cases no reason to restrict because of some group restriction - return (false) ; - } // end of NotAllowedForGroup - - - /** - * Handle forum notifications - */ - private function prepare_notification($postId, $type) { - // Get post details - $query = " - SELECT - p.threadid as threadId, - t.IdGroup as groupId, - p.PostVisibility, - p.PostDeleted, - t.ThreadVisibility, - t.ThreadDeleted - FROM - forums_posts p, - forums_threads t - WHERE - p.threadid = t.id - AND p.id = '" . $this->dao->escape($postId) ."'"; - - $res = $this->dao->query($query); - if (!$res) { - // just don't set notifications - return; - } - $post = $res->fetch(PDB::FETCH_OBJ); - - // Some checks before we take the long way - if (($post->PostDeleted == 'deleted') || ($post->ThreadDeleted == 'deleted')) { - return; - } - - $members = []; // collects all members that get a notification (to avoid several notifications for the same post) - - // first we get all open (ToSend) post notifications from the database and build - // a list of members that don't need another reminder - $query = " - SELECT - DISTINCT IdMember - FROM - posts_notificationqueue p - WHERE - p.IdPost = $postId - AND Status = 'ToSend' - ORDER BY - IdMember - "; - $res = $this->dao->query($query); - if ($res) { - while ($row = $res->fetch(PDB::FETCH_OBJ)) { - $members[] = $row->IdMember; - } - } - - // get group members in case of a group post to limit subscriptions to tags and threads - $group = false; - $groupMembers = []; - if ($post->groupId != 0) { - $group = $this->createEntity('Group')->findById($post->groupId); - $memberEntities = $group->getMembers(); - foreach($memberEntities as $groupMember) { - $groupMembers[] = $groupMember->getPKValue(); - } - } - - if ($group != false) { - // We reuse the $group entity from above - $subscriberEntities = $group->getEmailAcceptingMembers(); - - $membersTemp = []; - foreach($subscriberEntities as $subscriber) { - $memberId = $subscriber->getPKValue(); - if ($memberId == 0) continue; - if (array_search($memberId, $members) === false) { - $membersTemp[] = $memberId; - $members[] = $memberId; - } - } - if (!empty($membersTemp)) { - $count = 0; - $query = " - INSERT INTO - posts_notificationqueue ( - `IdMember`, - `IdPost`, - `Status`, - `created`, - `Type`, - `TableSubscription`, - `IdSubscription` - ) - VALUES "; - foreach($membersTemp as $member) { - $query .= "(" . $member . ", " . $postId . ", '" . NotificationStatusType::SCHEDULED . "', NOW(), '" . $type . "', 'membersgroups', 0), "; - $count++; - } - if ($count > 0) { - $query = substr($query, 0, -2); - $this->dao->query($query); - } - } - } - - // Set notifications for subscribed threads - $query = " - SELECT - IdSubscriber as subscriber, - notificationsEnabled, - members_threads_subscribed.id as subscriptionId - FROM - members_threads_subscribed - WHERE IdThread = '" . $this->dao->escape($post->threadId) . "'"; - $res = $this->dao->query($query); - if (!$res) { - // just don't write notifications - return; - } - - $membersTemp = []; - while ($row = $res->fetch(PDB::FETCH_OBJ)) { - if ($row->subscriber > 0) { - // did member disable notifications for this thread? - if ($row->notificationsEnabled) { - // only add gets notification don't add one - if (array_search($row->subscriber, $members) === false) { - $membersTemp[$row->subscriber] = $row->subscriptionId; - } - } else { - if (array_search($row->subscriber, $members) !== false) { - unset($membersTemp[$row->subscriber]); - } - } - } - } - - if (!empty($membersTemp)) { - $count = 0; - $query = " - INSERT INTO - posts_notificationqueue ( - `IdMember`, - `IdPost`, - `Status`, - `created`, - `Type`, - `TableSubscription`, - `IdSubscription` - ) - VALUES "; - foreach($membersTemp as $member => $subscriptionId) { - // current member doesn't get a notification yet - if (array_search($member, $members) === false) { - // Thread notifications might need to be limited to group members only - if (($post->groupId == 0) || ($post->PostVisibility != 'GroupOnly' && $post->ThreadVisibility != 'GroupOnly') - || (array_search($member, $groupMembers) !== false)) { - $query .= "(" . $member . ", " . $postId . ", '" . NotificationStatusType::SCHEDULED . "', NOW(), '" . $type . "', 'members_threads_subscribed', '" . $this->dao->escape($subscriptionId) . "'), "; - $members[] = $member; - $count++; - } - } - } - if ($count > 0) { - $query = substr($query, 0, -2); - $this->dao->query($query); - } - } - } - - // This function IsGroupSubscribed return true of the member is subscribing to the IdGroup - // @$IdGroup : The thread we want to know if the user is subscribing too - // @$ParamIdMember optional IdMember, by default set to 0 in this case current logged membver will be used - public function IsGroupSubscribed($IdGroup=0,$ParamIdMember=0) { - $IdMember=$ParamIdMember ; - if ($this->session->has( "IdMember" ) and $IdMember==0) { - $IdMember=$this->session->get("IdMember") ; - } - - // Check if there is a previous Subscription - $query = sprintf("select members_groups_subscribed.id as IdSubscribe,IdThread,IdSubscriber from members_groups_subscribed where IdGroup=%d and IdSubscriber=%d",$IdGroup,$IdMember); - $s = $this->dao->query($query); - if (!$s) { - throw new PException('IsGroupSubscribed Could not check previous subscription !'); - } - $row = $s->fetch(PDB::FETCH_OBJ) ; - return (isset($row->IdSubscribe)) ; - } // end of IsGroupSubscribed - - public function IsThreadSubscribed($IdThread=0,$ParamIdMember=0) { - $IdMember=$ParamIdMember ; - if ($this->session->has( "IdMember" ) and $IdMember==0) { - $IdMember=$this->session->get("IdMember") ; - } - - // Check if there is a previous Subscription - $query = sprintf("select members_threads_subscribed.id as IdSubscribe,IdThread,IdSubscriber from members_threads_subscribed where IdThread=%d and IdSubscriber=%d",$IdThread,$IdMember); - $s = $this->dao->query($query); - if (!$s) { - throw new PException('IsThreadSubscribed Could not check previous subscription !'); - } - $row = $s->fetch(PDB::FETCH_OBJ) ; - return (isset($row->IdSubscribe)) ; - } // end of IsThreadSubscribed - - // This function isMembersForumPostsPagePublic return true if the members allows other members to see his forum posts page: /forums/member/username - // @$userId : The user we want to know if his forum page is public - public function isMembersForumPostsPagePublic($userId = 0) { - $member = $this->createEntity("Member", $userId); - $usersForumPostsPagePublic = $member->getPreference("MyForumPostsPagePublic", $default = "No"); - if ($usersForumPostsPagePublic == "Yes") { - return true; - } - return false; - } // end of isMembersForumPostsPagePublic - - - - public function GetThreadVisibility($IdThread) { - $query = "SELECT ThreadVisibility FROM forums_threads WHERE id = " . intval($IdThread); - $s = $this->dao->query($query); - if (!$s) { - // Couldn't fetch the result from the DB assume 'MembersOnly' - return "MembersOnly"; - } - $row = $s->fetch(PDB::FETCH_OBJ) ; - return ($row->ThreadVisibility); - } - - public function GetPostVisibility($IdPost) { - $query = "SELECT PostVisibility FROM forums_posts WHERE id = " . intval($IdPost); - $s = $this->dao->query($query); - if (!$s) { - // Couldn't fetch the result from the DB assume 'MembersOnly' - return "MembersOnly"; - } - $row = $s->fetch(PDB::FETCH_OBJ) ; - return ($row->PostVisibility); - } - - public function GetGroupEntity($IdGroup) { - return $this->createEntity('Group')->findById($IdGroup); - } - - public function getTinyMCEPreference() { - $member = $this->getLoggedInMember(); - return $member->getPreference("PreferenceDisableTinyMCE", $default = "No"); - } - - /** - * @param $keywords Keywords to search for in the Sphinx index - * @return array - */ - public function searchForums($keywords, $currentPage, $items = 30) { - $hasForumModeratorRights = $this->BW_Right->HasRight("ForumModerator"); - - $results = ['count' => 0]; - $member = $this->getLoggedInMember(); - $groupEntities = $member->getGroups(); - $groups = [0]; - foreach ($groupEntities as $group) { - $groups[] = (int) $group->id; - } - - $config = ['host' => '127.0.0.1','port' => 9412]; - $client = new Client($config); - $query = new Search($client); - $query - ->setTable('forum_rt') - ->limit(1000) - ->sort(['post_id' => 'DESC']) - ; - - $matchQuery = new MatchPhrase($keywords, 'content'); - - $boolQuery = new BoolQuery(); - $boolQuery->must($matchQuery); - - if (!$hasForumModeratorRights) { - $groupPosts = new BoolQuery(); - $groupPosts - ->must(new In('group', $groups)) - ->must(new Equals('post_visibility', 'GroupOnly')) - ; - - $visibility = new BoolQuery(); - $visibility - ->should(new Equals('post_visibility', 'NoRestriction')) - ->should(new Equals('post_visibility', 'MembersOnly')) - ->should($groupPosts) - ; - - $publiclyVisiblePosts = new BoolQuery(); - $publiclyVisiblePosts - ->mustNot(new Equals('post_deleted', 'Deleted')) - ->mustNot(new Equals('thread_deleted', 'Deleted')) - ->must($visibility) - ; - - $limitQuery = new BoolQuery(); - $limitQuery - ->must($publiclyVisiblePosts) - ; - - $boolQuery->must($limitQuery); - } - - $query->search($boolQuery); - - $queryResult = $query->get(); - $queryResult->rewind(); - - $manticoreResult = []; - while ($queryResult->valid()) { - $hit = $queryResult->current(); - $data = $hit->getData(); - if ($hit->getScore() > 0 && !isset($manticoreResult[$data['post_id']])) { - $manticoreResult[$data['post_id']] = $data; - $manticoreResult[$data['post_id']]['score'] = $hit->getScore(); - } - $queryResult->next(); - } - - if (!empty($manticoreResult)) { - $languageId = $this->session->get('IdLanguage', 0); - $postIds = []; - foreach ($manticoreResult as $match) { - $postIds[] = $match['post_id']; - } - $separatedPostIds = implode(',', $postIds); - $separatedGroupIds = implode(',', $groups); - $offset = ($currentPage - 1) * $items; - $query = " - SELECT SQL_CALC_FOUND_ROWS - `forums_posts`.`id`, - `member`.`Username`, - `forums_posts`.`message`, - `forum_trads`.`Sentence`, - `forums_threads`.`id` AS `IdThread`, - `forums_threads`.`title`, - `forums_threads`.`IdGroup`, - `groups`.`Name` AS `GroupName`, - `forums_posts`.`PostVisibility`, - `forums_threads`.`ThreadVisibility`, - `forums_threads`.`ThreadDeleted`, - UNIX_TIMESTAMP(`forums_posts`.`create_time`) AS `created`, - geo__names.name AS city, - geonamescountries.name AS country - FROM - `forums_posts` - LEFT JOIN - `forums_threads` ON (`forums_posts`.`threadid` = `forums_threads`.`id`) - LEFT JOIN - `groups` ON (`groups`.`id` = `forums_threads`.`IdGroup`) - LEFT JOIN - `forum_trads` ON (`forum_trads`.`IdTrad` = `forums_posts`.`IdContent` AND `forum_trads`.`IdLanguage` = {$languageId}) - LEFT JOIN - `member` ON (`forums_posts`.`IdWriter` = `member`.`id`) - LEFT JOIN - `address` ON `member`.`id` = `address`.`member_id` and `address`.`active` = 1 - LEFT JOIN - `geo__names` ON `address`.location = `geo__names`.`geonameId` - LEFT JOIN - `geonamescountries` ON `geo__names`.`country` = `geonamescountries`.`country` - WHERE - `forums_posts`.`id` IN ({$separatedPostIds}) - AND (`forums_threads`.`IdGroup` IN ({$separatedGroupIds}) OR `forums_threads`.`IdGroup` IS NULL) - ORDER BY `created` DESC - LIMIT {$items} OFFSET {$offset} - "; - $posts = $this->bulkLookup($query); - $results['posts'] = $posts; - - $query = " - SELECT - count(`forums_posts`.`id`) AS `count` - FROM - `forums_posts` - LEFT JOIN `forums_threads` on `forums_posts`.`threadId` = `forums_threads`.`id` - WHERE - `forums_posts`.`id` IN ({$separatedPostIds}) - AND (`forums_threads`.`IdGroup` IN ({$separatedGroupIds}) OR `forums_threads`.`IdGroup` IS NULL) - "; - $count = $this->singleLookup($query); - $results['count'] = $count->count; - } else { - $results['errors'][] = 'ForumSearchNoResults'; - } - - return $results; - } - - /** - * Checks for correctness of search box vars (not empty) - * - * @param $vars - * @return bool - */ - private function _checkVarsSearch($vars) { - return true; - } - - /** - * Fetches matching threads/posts from the Sphinx index - * - * @return mixed Either false if there was a problem with the search box content or a list of matches. - */ - public function searchProcess() { - $User = $this->getLoggedInMember(); - if (!$User) { - return false; - } - - $vars =& PPostHandler::getVars(); - - $vars_ok = $this->_checkVarsSearch($vars); - if ($vars_ok) { - $keyword = htmlspecialchars((string) $vars['fs-keyword']); - PPostHandler::clearVars(); - return PVars::getObj('env')->baseuri.$this->forums_uri.'search/'. $keyword; - } - return false; - } - - private function addPostToManticoreIndex($vars, $postId, $threadId, $memberId, $languageId) - { - $language = $this->createEntity('Language')->findByid($languageId); - $host = PVars::getObj('env')->manticore_host; - $port = PVars::getObj('env')->manticore_port; - $config = ['host' => $host, 'port' => $port]; - $client = new Client($config); - $index = $client->table('forum_rt'); - $index->addDocument([ - 'post_id' => $postId, - 'post_deleted' => 'NotDeleted', - 'post_visibility' => $vars['PostVisibility'] ?? $vars['ThreadVisibility'], - 'thread_id' => $threadId, - 'thread_deleted' => 'NotDeleted', - 'thread_visibility' => $vars['ThreadVisibility'], - 'content' => $vars['topic_text'], - 'group' => $vars['group'] ?? 0, - 'author' => $memberId, - 'locale' => $language->getShortCode(), - ]); - } - - private function updatePostInManticoreIndex($vars) - { - $postId = (int)$vars['IdPost']; - $threadId = (int)$vars['IdThread']; - $config = ['host' => '127.0.0.1','port' => 9412]; - $vars['Sentence'] = $vars['Sentence'] ?? $vars['topic_text']; - - $client = new Client($config); - // Find document in index - $query = new Search($client); - $query - ->setTable('forum_rt') - ->filter('post_id', 'equals', $postId) - ->filter('thread_id', 'equals', $threadId) - ; - - $results = $query->get(); - $results->rewind(); - $hit = $results->current(); - $data = $hit->getData(); - $data['content'] = $vars['Sentence']; - - // Then replace with new content - $index = $client->table('forum_rt'); - $index->replaceDocument($data, $hit->getId()); - } -} // end of class Forums - - -class Topic { - public $topicinfo; - public $posts = []; -} - -class Board implements Iterator { - public $THREADS_PER_PAGE ; //Variable because it can change wether the user is logged or no - public $POSTS_PER_PAGE ; //Variable because it can change wether the user is logged or no - - public function __construct(&$dao, private $boardname, private $link, $session, private $board_description=false, $IdGroup=false, $no_forumsgroup=false) { - $this->THREADS_PER_PAGE=Forums::CV_THREADS_PER_PAGE ; //Variable because it can change wether the user is logged or no - $this->POSTS_PER_PAGE=Forums::CV_POSTS_PER_PAGE ; //Variable because it can change wether the user is logged or no - - $this->BW_Right = MOD_right::get(); - $this->session = $session; - - if (!$this->session->has( 'IdMember' )) { - $this->THREADS_PER_PAGE=100 ; // Variable because it can change wether the user is logged or no - $this->POSTS_PER_PAGE=self::CV_POSTS_PER_PAGE ; // Variable because it can change wether the user is logged or no - } - - $this->dao =& $dao; - $this->IdGroup = $IdGroup; - - $this->PublicThreadVisibility = "(ThreadVisibility!='ModeratorOnly') and (ThreadDeleted!='Deleted')"; - $this->PublicPostVisibility = " (PostDeleted!='Deleted')"; - - //if the member prefers to see only posts to his/her groups - $roxmodel = new RoxModelBase(); - $member = $roxmodel->getLoggedInMember(); - $owngroupsonly = $member->getPreference("ShowMyGroupsTopicsOnly", $default = "No"); - $this->owngroupsonly = $owngroupsonly; - if ($this->IdGroup === null) { - $this->PostGroupsRestriction = " ((PostVisibility IN ('MembersOnly','NoRestriction') or (PostVisibility = 'GroupOnly')) AND (IdGroup IS NULL))"; - $this->ThreadGroupsRestriction = " ((ThreadVisibility IN ('MembersOnly','NoRestriction') OR (ThreadVisibility = 'GroupOnly')) AND (IdGroup IS NULL))"; - } elseif ($this->IdGroup > 0) { - $this->PostGroupsRestriction = " (IdGroup= " . (int)$this->IdGroup . ") "; - $this->ThreadGroupsRestriction = " (IdGroup= " . (int)$this->IdGroup . ") "; - } else { - if ($owngroupsonly == "Yes" && ($this->IdGroup === false || !isset($this->IdGroup))) { - // 0 is the group id for topics without an explicit group, we don't want them in this case. Lazy hack to avoid changing more than necessary: replace 0 with -1 - $this->PostGroupsRestriction = " ((((IdGroup IN (-1"; - $this->ThreadGroupsRestriction = " ((((IdGroup IN (-1"; - } else { - $this->PostGroupsRestriction = " ((PostVisibility IN ('MembersOnly','NoRestriction') or (PostVisibility = 'GroupOnly' AND (IdGroup IS NULL OR IdGroup IN (0"; - $this->ThreadGroupsRestriction = " ((ThreadVisibility IN ('MembersOnly','NoRestriction') OR (ThreadVisibility = 'GroupOnly' AND (IdGroup IS NULL OR IdGroup IN (0"; - } - $query = "SELECT IdGroup FROM membersgroups WHERE IdMember=" . $this->session->get("IdMember") . " AND Status = 'In'"; - $qry = $this->dao->query($query); - if (!$qry) { - throw new PException('Failed to retrieve groups for member id =#' . $this->session->get("IdMember") . ' !'); - } - while ($rr = $qry->fetch(PDB::FETCH_OBJ)) { - $this->PostGroupsRestriction = $this->PostGroupsRestriction . "," . $rr->IdGroup; - $this->ThreadGroupsRestriction = $this->ThreadGroupsRestriction . "," . $rr->IdGroup; - } - - if ($no_forumsgroup) { - $this->PostGroupsRestriction = $this->PostGroupsRestriction . ")))) AND (NOT IdGroup IS NULL))"; - $this->ThreadGroupsRestriction = $this->ThreadGroupsRestriction . ")))) AND (NOT IdGroup IS NULL))"; - } else { - $this->PostGroupsRestriction = $this->PostGroupsRestriction . ")))) AND (IDGroup IS NULL))"; - $this->ThreadGroupsRestriction = $this->ThreadGroupsRestriction . ")))) AND (IDGroup IS NULL))"; - } - } - - // Prepares additional visibility options for moderator - if ($this->BW_Right->HasRight("ForumModerator")) { - $this->PublicPostVisibility = " PostVisibility IN ('NoRestriction', 'MembersOnly','GroupOnly','ModeratorOnly')"; - $this->PublicThreadVisibility = " ThreadVisibility IN ('NoRestriction', 'MembersOnly','GroupOnly','ModeratorOnly')"; - if ($this->BW_Right->HasRight("ForumModerator","AllGroups") or $this->BW_Right->HasRight("ForumModerator","All")) { - if ($IdGroup === null) { - $this->PostGroupsRestriction = " (IdGroup IS NULL)"; - $this->ThreadGroupsRestriction = " (IdGroup IS NULL)"; - } elseif ($no_forumsgroup) { - $this->PostGroupsRestriction = " (IdGroup != 0)"; - $this->ThreadGroupsRestriction = " (IdGroup != 0)"; - } - else { - $this->PostGroupsRestriction = " (1=1)"; - $this->ThreadGroupsRestriction = " (1=1)"; - } - } - } - } - - private $dao; - private $numberOfThreads; - private $totalThreads; - - /** - this filtres the list of thread results according to the presence of : - $this->IdGroup ; - */ - public function FilterThreadListResultsWithIdCriteria($ids = []) { - $wherethread="" ; - - if (count($ids) <> 0) { - $wherethread = " AND `forums_threads`.`id` in ('" . implode("', '", $ids) . "') "; - return $wherethread; - } - - if (isset($this->IdGroup)) { - if (is_numeric($this->IdGroup)) { - $wherethread .= sprintf("AND `forums_threads`.`IdGroup` = '%d' ", $this->IdGroup); - } elseif ($this->IdGroup === null) { - $wherethread .= "AND `forums_threads`.`IdGroup` IS NULL "; - } - } - $wherethread=$wherethread." AND (".$this->PublicThreadVisibility.")" ; - $wherethread=$wherethread." AND (".$this->ThreadGroupsRestriction.")" ; - return($wherethread) ; - } // end of FilterThreadListResultsWithIdCriteria - - /** - * Initializes the thread storages. - * - * If ids is set only threads with the given ids will be loaded - * - * @param int $page - * @param bool $showsticky - * @param array $ids - * @throws PException - */ - public function initThreads($page = 1, $showsticky = true, $ids = []) { - - $this->threads = []; - $wherethread=$this->FilterThreadListResultsWithIdCriteria($ids) ; - - if ($showsticky) { - $orderby = " ORDER BY `stickyvalue` ASC,`last_create_time` DESC"; - } else { - $orderby = " ORDER BY `last_create_time` DESC"; - } - - $query = "SELECT COUNT(*) AS `number` FROM `forums_threads` WHERE 1 ".$wherethread; - $s = $this->dao->query($query); - if (!$s) { - throw new PException('Could not retrieve Threads!'); - } - $row = $s->fetch(PDB::FETCH_OBJ); - $this->numberOfThreads = $row->number; - - if ($page == 0) { - $from = 0; - } else { - $from = $this->THREADS_PER_PAGE * ($page - 1); - } - - $query = "SELECT SQL_CALC_FOUND_ROWS `forums_threads`.`id`, - `forums_threads`.`id` as IdThread, `forums_threads`.`title`, - `forums_threads`.`IdTitle`, - `forums_threads`.`IdGroup`, - `forums_threads`.`replies`, - `forums_threads`.`stickyvalue`, - `groups`.`Name` as `GroupName`, - `ThreadVisibility`, - `ThreadDeleted`, - `forums_threads`.`views`, - `first`.`id` AS `first_postid`, - `first`.`idWriter` AS `first_authorid`, - UNIX_TIMESTAMP(`first`.`create_time`) AS `first_create_time`, - UNIX_TIMESTAMP(`last`.`create_time`) AS `last_create_time`, - `last`.`id` AS `last_postid`, - `last`.`idWriter` AS `last_authorid`, - UNIX_TIMESTAMP(`last`.`create_time`) AS `last_create_time`," ; - $query .= "`first_member`.`Username` AS `first_author`,`last_member`.`Username` AS `last_author`" ; - $query .= "FROM `forums_threads` LEFT JOIN `forums_posts` AS `first` ON (`forums_threads`.`first_postid` = `first`.`id`)" ; - $query .= "LEFT JOIN `groups` ON (`groups`.`id` = `forums_threads`.`IdGroup`)" ; - $query .= "LEFT JOIN `forums_posts` AS `last` ON last.id = - ( SELECT MAX(id) - FROM forums_posts fp - WHERE fp.threadid = `forums_threads`.`id` - AND fp.PostDeleted = 'NotDeleted' - ) "; - $query .= "LEFT JOIN `member` AS `first_member` ON (`first`.`IdWriter` = `first_member`.`id`)" ; - $query .= "LEFT JOIN `member` AS `last_member` ON (`last`.`IdWriter` = `last_member`.`id`)" ; - $query .= " WHERE 1 ".$wherethread . $orderby . " LIMIT ".$from.", ".$this->THREADS_PER_PAGE ; - - - $s = $this->dao->query($query); - if (!$s) { - throw new PException('Could not retrieve Threads!'); - } - while ($row = $s->fetch(PDB::FETCH_OBJ)) { - $this->threads[] = $row; - } - - $sFounRow = $this->dao->query("SELECT FOUND_ROWS() AS `found_rows`"); - if (!$sFounRow) { - throw new PException('Could not retrieve number of rows!'); - } - $rowFounRow = $sFounRow->fetch(PDB::FETCH_OBJ); - $this->totalThreads = $rowFounRow->found_rows; - - } // end of initThreads - - private $threads = []; - public function getThreads() { - return $this->threads; - } - public function getBoardName() { - return $this->boardname; - } - - public function getNumberOfThreads() { - return $this->numberOfThreads; - } - - public function getTotalThreads() { - return $this->totalThreads; - } - - private $subboards = []; - - // Add a subboard - public function add(Board $board) { - $this->subboards[] = $board; - } - - public function hasSubBoards() { - return (bool)(count($this->subboards) > 0); - } - - public function rewind() { - reset($this->subboards); - } - - public function current() { - $var = current($this->subboards); - return $var; - } - - public function key() { - $var = key($this->subboards); - return $var; - } - - public function next() { - $var = next($this->subboards); - return $var; - } - - public function valid() { - $var = $this->current() !== false; - return $var; - } -} + +* @copyright Copyright (c) 2005-2006, myTravelbook Team +* @license http://www.gnu.org/licenses/gpl.html GNU General Public License (GPL) +* @version $Id: forums.model.php 32 2007-04-03 10:22:22Z marco_p $ +*/ + +// Utility function to sort the languages +function cmpForumLang($a, $b) +{ + if ($a == $b) { + return 0; + } + return (strtolower((string) $a->Name) < strToLower((string) $b->Name)) ? -1 : 1; +} + +class Forums extends RoxModelBase { + + const CV_THREADS_PER_PAGE = 15; + const CV_POSTS_PER_PAGE = 100; + const CV_TOPMODE_CATEGORY = 1; // Says that the forum topmode is for categories + const CV_TOPMODE_LASTPOSTS = 2; // Says that the forum topmode is for lastposts + const CV_TOPMODE_LANDING = 3; // Says that we use the forums landing page for topmode + const CV_TOPMODE_FORUM = 4; // Says that we use the forums main page for topmode + const CV_TOPMODE_GROUPS = 5; // Says that we use the group forums overview page for topmode + + + const NUMBER_LAST_POSTS_PREVIEW = 10; // Number of Posts shown as a help on the "reply" page + + public $THREADS_PER_PAGE ; //Variable because it can change wether the user is logged or no + public $POSTS_PER_PAGE ; //Variable because it can change wether the user is logged or no + public $words ; // a shortcut to words module + public $ForumOrderList ; // The order of list in forum ascencding or desc this is a preference + public $BW_Right; + + + +/** +* GetLanguageChoosen function +* +* This return the language choosen by the user +* this function is supposed to be called after a new post, and editpost or a reply +* it return the language choosen if any +*/ +function GetLanguageChoosen() { + $DefLanguage=0 ; + if ($this->session->has( 'IdLanguage' )) { + $DefLanguage=$this->session->get('IdLanguage') ; + } + if (isset($_POST['IdLanguage'])) { // This will allow to consider a Language specified in the form + $DefLanguage=$_POST['IdLanguage'] ; + } + return($DefLanguage) ; +} // end of GetLanguageChoosen + + + /** + * InsertInfTrad function + * + * This InsertInFTrad create a new translatable text in forum_trads + * @$ss is for the content of the text + * @$TableColumn refers to the table and coilumn the trad is associated to + * @$IdRecord is the num of the record in this table + * @$_IdMember ; is the id of the member who own the record + * @$_IdLanguage + * @$IdTrad is probably useless (I don't remmber why I defined it) + * + * + * Warning : as default language this function will use by priority : + * 1) the content of $_IdLanguage if it is set to something else than -1 + * 2) the content of an optional $_POST[IdLanguage] if it is set + * 3) the content of the current $this->session->get('IdLanguage') of the current membr if it set + * 4) The default language (0) + * + * returns the id of the created trad + * @param string $ss + * @param string $TableColumn + * @param int $IdRecord + * @param int $_IdMember + * @param int $_IdLanguage + * @param int $IdTrad + * @return int + * @throws PException + */ +private function InsertInFTrad($ss, $TableColumn, $IdRecord, $_IdMember = 0, $_IdLanguage = -1, $IdTrad = -1) { + $this->words = new MOD_words(); + return ($this->words->InsertInFTrad($ss,$TableColumn,$IdRecord, $_IdMember, $_IdLanguage, $IdTrad)) ; +} // end of InsertInFTrad + + /** + * ReplaceInFTrad function + * + * This ReplaceInFTrad replace or create translatable text in forum_trads + * @$ss is for the content of the text + * @$TableColumn refers to the table and column the trad is associated to + * @$IdRecord is the num of the record in this table + * $IdTrad is the record in forum_trads to replace (unique for each IdLanguage) + * @$Owner ; is the id of the member who own the record + * + * Warning : as default language this function will use by priority : + * 1) the content of $_IdLanguage if it is set to something else than -1 + * 2) the content of an optional $_POST[IdLanguage] if it is set + * 3) the content of the current $this->session->get('IdLanguage') of the current membr if it set + * 4) The default language (0) + * @param string $ss + * @param string $TableColumn + * @param int $IdRecord + * @param int $IdTrad + * @param int $IdOwner + * @return int + * @throws PException + */ +private function ReplaceInFTrad($ss, $TableColumn, $IdRecord, $IdTrad = 0, $IdOwner = 0) { + $this->words = new MOD_words(); + return ($this->words->ReplaceInFTrad($ss,$TableColumn,$IdRecord, $IdTrad, $IdOwner )) ; +} // end of ReplaceInFTrad + +/** +* FindAppropriatedLanguage function will retrieve the appropriated default language +* for a member who want to reply to a thread (started with the#@IdPost post) +* this retriewal is made according to the language of the post, the current language of the user +*/ +function FindAppropriatedLanguage($IdPost=0) { + $ss="select `IdContent` FROM `forums_posts` WHERE `id`=".$IdPost ; + $q = $this->dao->query($ss); + $row= $q->fetch(PDB::FETCH_OBJ); + +// $q = $this->_dao->query($ss); +// $row = $q->fetch(PDB::FETCH_OBJ); + if (!isset($row->IdContent)) { + return (0) ; + } + else { + $IdTrad=$row->IdContent ; + } + + // Try IdTrad with current language of the member + $query ="SELECT IdLanguage FROM `forum_trads` WHERE `IdTrad`=".$IdTrad." and `IdLanguage`=".$this->session->get("IdLanguage") ; + $q = $this->dao->query($query); + $row = $q->fetch(PDB::FETCH_OBJ); + if (isset ($row->IdLanguage)) { + return($row->IdLanguage) ; + } + + // Try with the original language used for this post + $query ="SELECT `IdLanguage` FROM `forum_trads` WHERE `IdTrad`=".$IdTrad." order by id asc limit 1" ; + $q = $this->dao->query($query); + $row = $q->fetch(PDB::FETCH_OBJ); + + return($row->IdLanguage ?? 0) ; // By default we will return english + +} // end of FindAppropriatedLanguage + + + /* + * Constructor of forum model, prepare manything + * and things relative to Visibility + * + */ + public function __construct() { + parent::__construct(); + $this->THREADS_PER_PAGE=Forums::CV_THREADS_PER_PAGE ; //Variable because it can change wether the user is logged or no + $this->POSTS_PER_PAGE=Forums::CV_POSTS_PER_PAGE ; //Variable because it can change wether the user is logged or no + + if ($this->session->has('IdMember')) { + $member = $this->getLoggedInMember(); + + match ($member->getPreference("PreferenceForumFirstPage")) { + "Pref_ForumFirstPageLastPost" => $this->setTopMode(Forums::CV_TOPMODE_FORUM), + "Pref_ForumFirstPageCategory" => $this->setTopMode(Forums::CV_TOPMODE_CATEGORY), + default => $this->setTopMode(Forums::CV_TOPMODE_LANDING), + }; + $layoutbits = new MOD_layoutbits(); + $this->ForumOrderList = $layoutbits->GetPreference("PreferenceForumOrderListAsc", $member->id); + } else { + $this->setTopMode(Forums::CV_TOPMODE_FORUM); + } + + if (!$this->session->has( 'IdMember' )) { + $this->THREADS_PER_PAGE = 100; // Variable because it can change wether the user is logged or no + $this->POSTS_PER_PAGE = self::CV_POSTS_PER_PAGE; // Variable because it can change wether the user is logged or no + } + + $MyGroups = []; + + + $this->words = new MOD_words(); + $this->BW_Right = MOD_right::get(); + $this->IdGroup = false; // By default no group + $this->ByCategories = false; // toggle or not toglle the main view is TopCategories or TopLevel + + // Decide if it is an active LoggeMember or not + if (!$this->session->has("IdMember") || + !$this->session->has("MemberStatus") || + $this->session->get("MemberStatus") == 'Pending' || + $this->session->get("MemberStatus") == 'NeedMore' ) { + $this->PublicThreadVisibility=" (ThreadVisibility = 'NoRestriction') AND (ThreadDeleted != 'Deleted')"; + $this->PublicPostVisibility = " (PostVisibility = 'NoRestriction') AND (PostDeleted != 'Deleted')"; + $this->ThreadGroupsRestriction = " (IdGroup IS NULL OR ThreadVisibility = 'NoRestriction')"; + $this->PostGroupsRestriction = " (IdGroup IS NULL OR PostVisibility = 'NoRestriction')" ; + } + else { + $this->PublicThreadVisibility = "(ThreadVisibility != 'ModeratorOnly') AND (ThreadDeleted != 'Deleted')" ; + $this->PublicPostVisibility = "(PostVisibility != 'ModeratorOnly') AND (PostDeleted !='Deleted')" ; + $this->PostGroupsRestriction = " PostVisibility IN ('MembersOnly','NoRestriction') OR (PostVisibility='GroupOnly' AND (IdGroup IS NULL OR IdGroup in (0" ; + $this->ThreadGroupsRestriction = " ThreadVisibility IN ('MembersOnly','NoRestriction') OR (ThreadVisibility = 'GroupOnly' and (IdGroup IS NULL OR IdGroup in (0" ; + $qry = $this->dao->query("SELECT IdGroup FROM membersgroups WHERE IdMember = " . $this->session->get("IdMember") . " AND Status = 'In'"); + if (!$qry) { + throw new PException('Failed to retrieve groups for member id =#'.$this->session->get("IdMember").' !'); + } + while ($rr = $qry->fetch(PDB::FETCH_OBJ)) { + $this->PostGroupsRestriction = $this->PostGroupsRestriction . "," . $rr->IdGroup; + $this->ThreadGroupsRestriction = $this->ThreadGroupsRestriction . "," . $rr->IdGroup; + array_push($MyGroups,$rr->IdGroup) ; // Save the group list + } + $this->PostGroupsRestriction = $this->PostGroupsRestriction . ")))"; + $this->ThreadGroupsRestriction = $this->ThreadGroupsRestriction . ")))"; + } + + // Prepares additional visibility options for moderator + if ($this->BW_Right->HasRight("ForumModerator")) { + $this->PublicPostVisibility = " PostVisibility IN ('NoRestriction', 'MembersOnly','GroupOnly','ModeratorOnly')"; + $this->PublicThreadVisibility = " ThreadVisibility IN ('NoRestriction', 'MembersOnly','GroupOnly','ModeratorOnly')"; + if ($this->BW_Right->HasRight("ForumModerator","AllGroups") or $this->BW_Right->HasRight("ForumModerator","All")) { + $this->PostGroupsRestriction = " (1=1)"; + $this->ThreadGroupsRestriction = " (1=1)"; + } + } + $this->MyGroups = $MyGroups; + } // __construct + + // This switch the preference ForumOrderList + public function switchForumOrderList() { + $ss="SELECT id FROM preferences WHERE codename ='PreferenceForumOrderListAsc'"; + $qq = $this->dao->query($ss); + if (!$qq) { + // We couldn't get a connection to the database... + return; + } + if ($this->ForumOrderList=="Yes") { + $this->ForumOrderList="No" ; + } + else { + $this->ForumOrderList="Yes" ; + } + $row = $qq->fetch(PDB::FETCH_OBJ); + if (null === $row) + { + // if the preference doesn't exist something is very, very wrong. return at your own risk... + return; + } + $idPreference = $row->id; + $idMember = $this->session->get('IdMember'); + + $ss="SELECT mp.value, mp.id FROM memberspreferences mp WHERE mp.IdMember = " . $idMember . " AND mp.IdPreference = " . $idPreference; + $result = $this->dao->query($ss); + if (!$result) { + // We couldn't get a connection to the database... + return; + } + + $row = $result->fetch(PDB::FETCH_OBJ) ; + if (null === $row) { + $query = " + INSERT INTO + memberspreferences (`created`, `updated`, `IdPreference`, `IdMember`, `Value`) + VALUES (NOW(), `created`, " .$idPreference . ", " . $idMember . ", '" . $this->dao->escape($this->ForumOrderList) . "' ) + "; + } + else { + $query = " + UPDATE + memberspreferences + SET Value='" . $this->dao->escape($this->ForumOrderList) . "' WHERE id= " . $row->id + ; + } + $result = $this->dao->query($query); + } // end of switchForumOrderList + + public function adjustThreadsCountToShow($step = 1) { + $MAX_THREADS = 1000; //An upper limit just in case + if (!$member = $this->getLoggedInMember()) { + return false; + } + $vars =& PPostHandler::getVars(); + if (!isset($vars['agoragroupsthreadscountmoreless'])) { + return false; + } + $command = $vars['agoragroupsthreadscountmoreless']; + $layoutbits = new MOD_layoutbits(); + $forumthreads = intval($layoutbits->GetPreference("ForumThreadsOnLandingPage", $member->id)); + $groupsthreads = intval($layoutbits->GetPreference("GroupsThreadsOnLandingPage", $member->id)); + $membersmodel = new MembersModel(); + + $query = " + SELECT + id + FROM + preferences + WHERE + CodeName = 'ForumThreadsOnLandingPage' + LIMIT 1 + "; + $row = $this->dao->query($query); + $forumpref = $row->fetch(PDB::FETCH_OBJ); + if ($forumpref === false) { + throw new Exception('Database error: "ForumThreadsOnLandingPage"' + . ' preference not found in "preferences" table'); + } + + $query = " + SELECT + id + FROM + preferences + WHERE + CodeName = 'GroupsThreadsOnLandingPage' + LIMIT 1 + "; + $row = $this->dao->query($query); + $groupspref = $row->fetch(PDB::FETCH_OBJ); + if ($groupspref === false) { + throw new Exception('Database error: "GroupsThreadsOnLandingPage"' + . ' preference not found in "preferences" table'); + } + + match ($command) { + "moreagora" => $membersmodel->set_preference($member->id, $forumpref->id, min($forumthreads + $step, $MAX_THREADS)), + "lessagora" => $membersmodel->set_preference($member->id, $forumpref->id, max($forumthreads - $step, 1)), + "moregroups" => $membersmodel->set_preference($member->id, $groupspref->id, min($groupsthreads + $step, $MAX_THREADS)), + "lessgroups" => $membersmodel->set_preference($member->id, $groupspref->id, max($groupsthreads - $step, 1)), + default => false, + }; + + return false; + } + + + + // This switch the preference switchShowMyGroupsTopicsOnly + public function switchShowMyGroupsTopicsOnly() { + if (!$member = $this->getLoggedInMember()) { + return false; + } + $owngroupsonly = $member->getPreference("ShowMyGroupsTopicsOnly", $default = "No"); + $this->ShowMyGroupsTopicsOnly = $owngroupsonly; + if ($this->ShowMyGroupsTopicsOnly == "Yes") { + $this->ShowMyGroupsTopicsOnly = "No" ; + } + else { + $this->ShowMyGroupsTopicsOnly = "Yes" ; + } + + // Fetch preference object + $query = " + SELECT + id + FROM + preferences + WHERE + CodeName = 'ShowMyGroupsTopicsOnly' + LIMIT 1 + "; + $row = $this->dao->query($query); + $preference = $row->fetch(PDB::FETCH_OBJ); + if ($preference === false) { + throw new Exception('Database error: "ShowMyGroupsTopicsOnly"' + . ' preference not found in "preferences" table'); + } + + $ss = " +SELECT + m.Value AS Value, + m.id AS id, + p.id AS IdPreferences +FROM + memberspreferences AS m, + preferences AS p +WHERE + p.id = m.IdPreference + AND m.IdMember = " . $this->session->get('IdMember') . " + AND p.CodeName = 'ShowMyGroupsTopicsOnly'"; + + $qq = $this->dao->query($ss); + $rr = $qq->fetch(PDB::FETCH_OBJ) ; + if (empty($rr->Value)) { + $ss = " +INSERT INTO + memberspreferences( + created, + IdPreference, + IdMember, + Value + ) +VALUES( + now(), + " . $preference->id . "," . + $this->session->get('IdMember') . ", + '" . $this->ShowMyGroupsTopicsOnly . "' +)" ; + } + else { + $ss = " +UPDATE + memberspreferences +SET + Value='" . $this->ShowMyGroupsTopicsOnly . "' +WHERE + id=" . $rr->id ; + } + + $qq = $this->dao->query($ss); + if (!$qq) { + throw new PException('switchShowMyGroupsTopicsOnly ' . $ss . ' !'); + } + return false; + } // end of switchShowMyGroupsTopicsOnly + + + public function checkGroupMembership($group_id) { + if (in_array($group_id,$this->MyGroups)) { + return true; + } + return false; + } // end of checkGroupMembership + + private function boardTopLevelForum($showsticky = true) { + $this->board = new Board($this->dao, 'Forum', '.', $this->getSession(), false, null, false); + $this->board->initThreads($this->getPage(), $showsticky); + + } // end of boardTopLevelForum + + private function boardTopLevelGroups($showsticky = true) { + $this->board = new Board($this->dao, 'Groups', '.', $this->getSession(), false, false, true); + $this->board->initThreads($this->getPage(), $showsticky); + + } // end of boardTopLevelGroups + + private function boardTopLevelLanding($showsticky = true) { + $User = $this->getLoggedInMember(); + if (!$User) { + // Show informal message that the forums are limited to members only + return false; + } + + $MAX_THREADS = 1000; //An upper limit of threads th show just in case the preference goes silly + $layoutbits = new MOD_layoutbits(); + + $idMember = $this->getSession()->get('IdMember'); + $forumthreads = intval($layoutbits->getPreference("ForumThreadsOnLandingPage", $idMember)); + $groupsthreads = intval($layoutbits->getPreference("GroupsThreadsOnLandingPage", $idMember)); + + $page_array = $this->getPageArray(); + + if (isset($page_array[0]) && isset($page_array[1])) { + $forumpage = $page_array[0]; + $groupspage = $page_array[1]; + } else { + $forumpage = 1; + $groupspage = 1; + } + + + $this->board = new Board($this->dao, 'Forums and Groups', '.', $this->getSession()); + + $forum = new Board($this->dao, 'Forum', '.', $this->getSession(), false, null, false); + $forum->THREADS_PER_PAGE = max(1, min($forumthreads, $MAX_THREADS)); + $forum->initThreads($forumpage, $showsticky); + $forumMaxPage = max(ceil($forum->getNumberOfThreads() / $forum->THREADS_PER_PAGE), 1); + if ($forumpage > $forumMaxPage) { + $forum->initThreads($forumMaxPage, $showsticky); + } + + $groups = new Board($this->dao, 'Groups', '.', $this->getSession(), false, false, true); + $groups->THREADS_PER_PAGE = max(1, min($groupsthreads, $MAX_THREADS)); + $groups->initThreads($groupspage, false); + + $this->board->add($forum); + $this->board->add($groups); + return true; + } // end of boardTopLevelLanding + + private function boardTopLevelLastPosts($showsticky = true) { + $this->board = new Board($this->dao, 'Forums', '.', $this->getSession()); + $this->board->initThreads($this->getPage(), $showsticky); + } // end of boardTopLevelLastPosts + + // This build the board for the $this->IdGroup + private function boardGroup($showsticky = true) { + + $query = "SELECT `Name` FROM `groups` WHERE `id` = " . (int)$this->IdGroup; + $gr = $this->dao->query($query); + if (!$gr) { + throw new PException('No such IdGroup=#'.$this->IdGroup); + } + $group = $gr->fetch(PDB::FETCH_OBJ); + + $subboards = []; + $gtitle= $this->words->getSilent("ForumGroupTitle", $this->getGroupName($group->Name)) ; + $this->board = new Board($this->dao, "", ".", $this->getSession(), $subboards, $this->IdGroup); + $this->board->initThreads($this->getPage(), $showsticky); + } // end of boardGroup + + /* + @ $Name name of the group (direct from groups.Name + */ + public function getGroupName($Name) { + return($Name) ; + + } + + /** + * Fetch all required data for the view to display a forum + * this data are stored in $this->board + */ + public function prepareForum($showsticky = true) { + if (!$this->IdGroup) { + if ($this->TopMode==Forums::CV_TOPMODE_LASTPOSTS) { + $this->boardTopLevelLastPosts($showsticky); + } + elseif ($this->TopMode==Forums::CV_TOPMODE_LANDING) { + $this->boardTopLevelLanding($showsticky); + } + elseif ($this->TopMode==Forums::CV_TOPMODE_FORUM) { + $this->boardTopLevelForum($showsticky); + } + elseif ($this->TopMode==Forums::CV_TOPMODE_GROUPS) { + $this->boardTopLevelGroups($showsticky); + } + else { + $this->boardTopLevelLanding($showsticky); + } + } elseif ($this->IdGroup) { + $this->boardGroup($showsticky); + } elseif (PVars::get()->debug) { + throw new PException('Invalid Request'); + } else { + PRequest::home(); + } + } // end of prepareForum + + private $board; + public function getBoard() { + return $this->board; + } + + public function createProcess() { + if (!($User = $this->getLoggedInMember())) { + return false; + } + + $vars =& PPostHandler::getVars(); + + $vars_ok = $this->checkVarsTopic($vars); + if ($vars_ok) { + $topicid = $this->newTopic($vars); + PPostHandler::clearVars(); + return PVars::getObj('env')->baseuri.$this->forums_uri.'s'.$topicid; + } else { + return false; + } + + } + + /* + * Fill the Vars in order to prepare edit a post + * this fetch the data which are then going to be display and then change + * by the user + */ + public function getEditData($callbackId) { + $query = + " +SELECT + `forums_posts`.`id` AS `postid`, + `IdWriter`, + `PostDeleted`, + `PostVisibility`, + `ThreadVisibility`, + `ThreadDeleted`, + `forums_posts`.`threadid` as `threadid`, + `message` AS `topic_text`, + `OwnerCanStillEdit`, + `IdContent`, + `title` AS `topic_title`, `first_postid`, `last_postid`, `IdTitle`, + `ThreadVisibility`, + `ThreadDeleted`, + `forums_threads`.`IdGroup` +FROM `forums_posts` +LEFT JOIN `forums_threads` ON (`forums_posts`.`threadid` = `forums_threads`.`id`) +WHERE `forums_posts`.`id` = $this->messageId +and ($this->PublicPostVisibility) + "; + + $s = $this->dao->query($query); + if (!$s) { + throw new PException('getEditData :: Could not retrieve Postinfo!'); + } + $vars =& PPostHandler::getVars($callbackId); + $vars = $s->fetch(PDB::FETCH_ASSOC); + $this->IdGroup = $vars['IdGroup']; + } // end of get getEditData + + /* + * Write in the database the changed data + * when a post is edited, this also write a log and + * this call editPost and may be editTopic which does the update in the database + * by the user + */ + public function editProcess() { + if (!($User = $this->getLoggedInMember())) { + return false; + } + + $vars =& PPostHandler::getVars(); + + $query = + " +SELECT + `forums_posts`.`id` AS `postid`, + `IdWriter`, + `forums_posts`.`threadid`, + `first_postid`, + `OwnerCanStillEdit`, + `PostDeleted`, + `PostVisibility`, + `ThreadVisibility`, + `ThreadDeleted`, + `forums_threads`.`IdGroup`, + `last_postid` +FROM `forums_posts` +LEFT JOIN `forums_threads` ON (`forums_posts`.`threadid` = `forums_threads`.`id`) +WHERE `forums_posts`.`id` = $this->messageId + " + ; + $s = $this->dao->query($query); + if (!$s) { + throw new PException('Could not retrieve Postinfo!'); + } + $postinfo = $s->fetch(PDB::FETCH_OBJ); + +// if ($this->BW_Right->HasRight("ForumModerator","Edit") || ($User->hasRight('edit_own@forums') && $postinfo->authorid == $User->getId())) { + if ($this->BW_Right->HasRight("ForumModerator","Edit") || ($postinfo->IdWriter == $this->session->get("IdMember") and $postinfo->OwnerCanStillEdit=="Yes")) { + $is_topic = ($postinfo->postid == $postinfo->first_postid); + + if ($is_topic) { + $vars_ok = $this->checkVarsTopic($vars); + } else { + $vars_ok = $this->checkVarsReply($vars); + } + if ($vars_ok) { + $this->dao->query("START TRANSACTION"); + + if ($is_topic) { + if (!isset($vars['ThreadVisibility'])) { + $vars['ThreadVisibility'] = 'MembersOnly'; + } + $vars['PostVisibility'] = $vars['ThreadVisibility']; + } + + if (!isset($vars['PostVisibility'])) { + $vars['PostVisibility'] = 'MembersOnly'; + } + + $this->editPost($vars, $User->getId()); + if ($is_topic) { + $this->editTopic($vars, $postinfo->threadid); + } + + $this->dao->query("COMMIT"); + + PPostHandler::clearVars(); + return PVars::getObj('env')->baseuri.$this->forums_uri.'s'.$postinfo->threadid; + } else { + return false; + } + } else { + return false; + } + } // end of editProcess + +/** +* the function DofTradUpdate() update a forum translation +* @IdForumTrads is the primary key of the parameter to update +*/ + public function DofTradUpdate($IdForumTrads,$P_Sentence,$P_IdLanguage=0) { + $id=(int)$IdForumTrads ; + $s=$this->dao->query("select * from forum_trads where id=".$id); + $rBefore=$s->fetch(PDB::FETCH_OBJ) ; + +// Save the previous version + $this->words->MakeRevision($id, "forum_trads",$this->session->get("IdMember"), $DoneBy = "DoneByModerator") ; + $IdLanguage=(int)$P_IdLanguage ; + $Sentence= $this->dao->escape($P_Sentence); + + MOD_log::get()->write("Updating data for IdForumTrads=#".$id." Before [".addslashes((string) $rBefore->Sentence)."] IdLanguage=".$rBefore->IdLanguage."
\nAfter [".$Sentence."] IdLanguage=".$IdLanguage, "ForumModerator"); + $sUpdate="update forum_trads set Sentence='".$Sentence."',IdLanguage=".$IdLanguage.",IdTranslator=".$this->session->get("IdMember").", updated=NOW() where id=".$id ; + $s=$this->dao->query($sUpdate); + if (!$s) { + throw new PException('Failed for Update forum_trads.id=#'.$id); + } + + } // end of DofTradUpdate + + +/** +* editPost write the data in of change post in the database +* warning : dont start any transaction in it sinc ethere is already one +* started by the caller +* this also write a log +*/ + private function editPost($vars, $editorid) { + $query = "SELECT message,forums_posts.threadid, + `PostVisibility`, + OwnerCanStillEdit,IdWriter,forums_posts.IdFirstLanguageUsed as post_IdFirstLanguageUsed,forums_threads.IdFirstLanguageUsed as thread_IdFirstLanguageUsed,forums_posts.id,IdWriter,IdContent,forums_threads.IdTitle,forums_threads.first_postid from `forums_posts`,`forums_threads` WHERE forums_posts.threadid=forums_threads.id and forums_posts.id = ".$this->messageId ; + $s=$this->dao->query($query); + $rBefore=$s->fetch(PDB::FETCH_OBJ) ; + + $query = sprintf("UPDATE `forums_posts` SET `message` = '%s', `last_edittime` = NOW(), `last_editorid` = '%d', `edit_count` = `edit_count` + 1 WHERE `id` = '%d'", + $this->dao->escape($this->cleanupText($vars['topic_text'])), $editorid, $this->messageId); + $this->dao->query($query); + $this->ReplaceInFTrad($this->dao->escape($this->cleanupText($vars['topic_text'])), "forums_posts.IdContent", $rBefore->id, $rBefore->IdContent, $rBefore->IdWriter) ; + $this->updatePostInManticoreIndex($vars); + // case the update concerns the reference language of the posts + if ($rBefore->post_IdFirstLanguageUsed==$this->GetLanguageChoosen()) { + $query="update forums_posts set message='".$this->dao->escape($this->cleanupText($vars['topic_text']))."' where id=".$this->messageId ; + $s=$this->dao->query($query); + } + + // case the visibility has changed + if ($rBefore->PostVisibility!=$vars['PostVisibility']) { + $query="update forums_posts set PostVisibility='".$vars['PostVisibility']."' where id=".$this->messageId ; + $s=$this->dao->query($query); + MOD_log::get()->write("Changing Post Visibility from ".$rBefore->PostVisibility." to ".$vars['PostVisibility']."", "Forum"); + } + + + + // If this is the first post, may be we can update the title + if ($rBefore->first_postid==$rBefore->id) { + $this->ReplaceInFTrad($this->dao->escape($this->cleanupText($vars['topic_title'])), "forums_threads.IdTitle", $rBefore->threadid, $rBefore->IdTitle, $rBefore->IdWriter) ; + // case the update concerns the reference language of the threads + if ($rBefore->thread_IdFirstLanguageUsed==$this->GetLanguageChoosen()) { + $groupId = $vars['IdGroup'] ?? 'NULL'; + $query="update forums_threads set IdGroup=". $groupId.",title='".$this->dao->escape($this->cleanupText($vars['topic_title']))."' where forums_threads.id=".$rBefore->threadid ; + $s=$this->dao->query($query); + } + } + + // subscription if any, could be done out of transaction, this is not so important + if ((isset($vars['NotifyMe'])) and ($vars['NotifyMe']=="on")) { + if (!$this->IsThreadSubscribed($rBefore->threadid,$this->session->get("IdMember"))) { + $this->SubscribeThread($rBefore->threadid,$this->session->get("IdMember")) ; + } + } + else { + $vars['NotifyMe']="Not Asked" ; + if ($this->IsThreadSubscribed($rBefore->threadid,$this->session->get("IdMember"))) { + $this->UnsubscribeThreadDirect($rBefore->threadid,$this->session->get("IdMember")) ; + } + } + + $this->prepare_notification($this->messageId,"useredit") ; // Prepare a notification + MOD_log::get()->write("Editing Post=#".$this->messageId." Text Before=".addslashes((string) $rBefore->message)."
NotifyMe=[".$vars['NotifyMe']."]", "Forum"); + } // editPost + + /** + * editTopic write the data in of change thread in the database + * warning : dont start any transaction in it since there is already one + * started by the caller + * this also write a log + */ + + private function editTopic($vars, $threadid) { + $query = sprintf(" +UPDATE `forums_threads` +SET `title` = '%s' +WHERE `id` = '%d' ", + $this->dao->escape(strip_tags((string) $vars['topic_title'])), + $threadid + ); + + $this->dao->query($query); + + $s=$this->dao->query("select IdWriter,forums_threads.id as IdThread,forums_threads.IdTitle,forums_threads.IdFirstLanguageUsed as thread_IdFirstLanguageUsed + from forums_threads,forums_posts + where forums_threads.first_postid=forums_posts.id and forums_threads.id=".$threadid); + if (!$s) { + throw new PException('editTopic:: previous info for first post in the thread!'); + } + $rBefore = $s->fetch(PDB::FETCH_OBJ); + + $this->ReplaceInFTrad($this->dao->escape(strip_tags((string) $vars['topic_title'])), "forums_threads.IdTitle", $rBefore->IdThread, $rBefore->IdTitle, $rBefore->IdWriter) ; + + // case the update concerns the reference language of the posts + if ($rBefore->thread_IdFirstLanguageUsed==$this->GetLanguageChoosen()) { + $query="update forums_threads set title='".$this->dao->escape($this->cleanupText($vars['topic_title']))."' where forums_threads.id=".$rBefore->IdThread ; + $s=$this->dao->query($query); + } + + // Set ThreadVisibility + $query = 'UPDATE forums_threads SET ThreadVisibility = "' . $vars['ThreadVisibility'] . '" WHERE forums_threads.id=' . $rBefore->IdThread; + $s =$this->dao->query($query); + + MOD_log::get()->write("Editing Topic Thread=#".$threadid, "Forum"); + } // end of editTopic + + public function replyProcess($suggestion = false) { + if (!($User = $this->getLoggedInMember())) { + return false; + } + + $vars =& PPostHandler::getVars(); + $this->checkVarsReply($vars); + $this->replyTopic($vars); + + PPostHandler::clearVars(); + return PVars::getObj('env')->baseuri.$this->forums_uri.'s'.$this->threadid; + } // end of replyProcess + + public function reportpostProcess() { + if (!($User = $this->getLoggedInMember())) { + return false; + } + + $vars =& PPostHandler::getVars(); + $IdPost=$vars['IdPost'] ; + + $ss = "select threadid from forums_posts where id=".$IdPost ; + $s = $this->dao->query($ss); + $rr = $s->fetch(PDB::FETCH_OBJ) ; + + $this->threadid=$rr->threadid ; + + $PostComment=$vars['PostComment'] ; + $Status=$vars['Status'] ; + if (isset($vars['Type'])) { + $Type=$vars['Type'] ; + } else { + $Type = 'SeeText'; + } + if (!empty($vars['IdReporter'])) { + $IdReporter=$vars['IdReporter'] ; + } + else { + $IdReporter=$this->session->get("IdMember") ; + } + + $ss = "select reports_to_moderators.* from reports_to_moderators where IdPost=".$IdPost." and IdReporter=".$IdReporter ; + $s = $this->dao->query($ss); + $OldReport = $s->fetch(PDB::FETCH_OBJ) ; + + + $UsernameAddTime='On '.date("d-m-Y").' '.date("H:i"). ' (server time) '.$this->session->get("Username").' wrote:
' ; + if (($this->BW_Right->HasRight("ForumModerator")) and (isset($OldReport->IdReporter))) { + $PostComment=$UsernameAddTime.$this->cleanupText($vars['PostComment']) ; + if (isset($OldReport->PostComment)) $PostComment=$PostComment."
\n".$OldReport->PostComment ; + $ss="update reports_to_moderators set LastWhoSpoke='Moderator',PostComment='".$this->dao->escape($PostComment)."',IdModerator=".$this->session->get("IdMember").",Status='".$this->dao->escape($Status)."',Type='".$this->dao->escape($Type)."',IdModerator=".$this->session->get('IdMember')." where IdPost=".$IdPost." and IdReporter=".$IdReporter ; + $this->dao->query($ss); + } + else { + if ($IdReporter!=$this->session->get("IdMember")) { + MOD_log::get()->write("Trying to trick report to moderator for post #".$IdPost,"Forum") ; + die("Failed to report to moderator") ; + } + if (isset($OldReport->IdReporter)) { + $PostComment=$UsernameAddTime.$this->cleanupText($vars['PostComment'])."
\n".$OldReport->PostComment ; + $ss="update reports_to_moderators set LastWhoSpoke='Member',PostComment='".$this->dao->escape($PostComment)."',Status='".$this->dao->escape($Status)."'"." where IdPost=".$IdPost." and IdReporter=".$IdReporter ; + $this->dao->query($ss); + } + else { + $PostComment=$UsernameAddTime.$this->cleanupText($vars['PostComment']) ; + $ss=" + insert into reports_to_moderators(PostComment,created,IdPost,IdReporter,Status) + values('".$this->dao->escape($PostComment)."',now(),".$IdPost.",".$this->session->get("IdMember") .",'".$Status."')" ; + $this->dao->query($ss); + } + } + MOD_log::get()->write("Adding to report for post #".$IdPost."
".$PostComment."
Status=".$Status,"Forum") ; + PPostHandler::clearVars(); + return PVars::getObj('env')->baseuri.$this->forums_uri.'s'.$this->threadid.'/#'.$IdPost; + } // end of reportpostProcess + + /** + * This will return the list of reports for a given post + * @IdPost : Id of the post to process with their status + * @IdReporter : OPtional id of teh member for a specific report, in this case a record is returned7 + * in other case an array of reports is returned + */ + public function GetReports($IdPost,$IdReporter=0) { + $tt=[] ; + if (empty($IdReporter)) { + $ss = "select reports_to_moderators.*,Username from reports_to_moderators,member where IdPost=".$IdPost." and member.id=IdReporter" ; + $s = $this->dao->query($ss); + while ($rr = $s->fetch(PDB::FETCH_OBJ)) { + array_push($tt,$rr) ; + } + return($tt) ; + } + else { + $ss = "select IdReporter from reports_to_moderators where IdPost=".$IdPost." and IdReporter=".$IdReporter ; + $s = $this->dao->query($ss); + array_push($tt,$s->fetch(PDB::FETCH_OBJ)) ; + return($tt) ; + } + } // end of GetReports + /** + * This will prepare the additional data to process a report + * @IdPost : Id of the post to process + * @IdWriter is the id of the writer + */ + public function prepareReportPost($IdPost,$IdWriter) { + $query = "select * from reports_to_moderators where IdPost=".$IdPost." and IdReporter=".$IdWriter ; + $s = $this->dao->query($query); + $Report = $s->fetch(PDB::FETCH_OBJ) ; + return($Report) ; + } // end of prepareReportPost + + /** + This function loads the list of links to reports + @IdMember : if to 0 it means for all moderators, if not 0 it mean for a given moderator + @StatusList : is the list of reports status to consider, if empty it means all status + + returns an array + */ + public function prepareReportList($IdMember,$StatusList) { // This retrieve all the reports for the a member or all members + + $ss = "select reports_to_moderators.*,Username from reports_to_moderators,member where member.id=IdReporter " ; + if (!empty($StatusList)) { + $ss=$ss." and reports_to_moderators.Status in ".$StatusList ; + } + + $tt=[] ; + if (!empty($IdMember)) { + $ss=$ss." and reports_to_moderators.IdModerator=".$IdMember ; + } + $ss=$ss." order by reports_to_moderators.updated" ; + $s = $this->dao->query($ss); + while ($rr = $s->fetch(PDB::FETCH_OBJ)) { + array_push($tt,$rr) ; + } + return($tt) ; + } // end of prepareReportList + + /** + This function count the list of links to reports + @IdMember : if to 0 it means for all moderators, if not 0 it mean for a given moderator + @StatusList : is the list of reports status to consider, if empty it means all status + returns and integer + */ + public function countReportList($IdMember,$StatusList) { // This count all the reports for and optional members or all members according to their styatus + $ss = "select count(*) as cnt from reports_to_moderators,member where member.id=IdReporter " ; + if (!empty($StatusList)) { + $ss=$ss." and reports_to_moderators.Status in ".$StatusList ; + } + $s = $this->dao->query($ss); + if ($rr = $s->fetch(PDB::FETCH_OBJ)) { + return($rr->cnt) ; + } + return(0) ; + } // end of countReportList + + + + /** + * This will prepare a post for a full edit moderator action + * @IdPost : Id of the post to process + */ + public function prepareModeratorEditPost($IdPost, $moderator = false) { + $DataPost = new StdClass(); + $DataPost->IdPost=$IdPost ; + $DataPost->Error="" ; // This will receive the error sentence if any + + $query = "select forums_posts.*,member.Status as memberstatus,member.UserName as UserNamePoster from forums_posts,member where forums_posts.id=".$IdPost." and IdWriter=member.id" ; + $s = $this->dao->query($query); + $DataPost->Post = $s->fetch(PDB::FETCH_OBJ) ; + + if (!isset($DataPost->Post)) { + $DataPost->Error="No Post for Post=#".$IdPost ; + return($DataPost) ; + } + + if (!$moderator) { + if ($DataPost->Post->PostVisibility == 'GroupOnly') { + // first check if post was made in a group and if the current member is a member of that group + $query = "SELECT IdGroup FROM forums_posts fp, forums_threads ft WHERE fp.id = " . $this->dao->escape($IdPost) . " AND fp.threadId = ft.id"; + $s = $this->dao->query($query); + $row = $s->fetch(PDB::FETCH_OBJ); + $IdGroup = $row->IdGroup; + if ($IdGroup <> 0) { + $group = $this->createEntity('Group')->findByid($IdGroup); + $member = $this->getLoggedInMember(); + // Can't use $group->isMember() for some reason + $query = " SELECT * FROM membersgroups WHERE IdMember = " . $member->id . " AND IdGroup = " . $IdGroup; + $s = $this->dao->query($query); + $row = $s->fetch(PDB::FETCH_OBJ); + if (!$row) { + $DataPost->Error="NoGroupMember"; + return($DataPost) ; + } + } + } + if ($DataPost->Post->PostVisibility == 'ModeratorOnly') { + $DataPost->Error = "NoModerator"; + return $DataPost; + } + } + + // retrieve all trads for content + $query = "select forum_trads.*,Name,ShortCode,forum_trads.id as IdForumTrads from forum_trads,language where ShortCode=language.ShortCode and IdTrad=".$DataPost->Post->IdContent." order by forum_trads.created asc" ; + $s = $this->dao->query($query); + $DataPost->Post->Content=[] ; + while ($row=$s->fetch(PDB::FETCH_OBJ)) { + $DataPost->Post->Content[]=$row ; + } + + + $query = "select * from forums_threads where id=".$DataPost->Post->threadid ; + $s = $this->dao->query($query); + if (!isset($DataPost->Post)) { + $DataPost->Error="No Thread=#".$DataPost->Post->threadid ; + return($DataPost) ; + } + $DataPost->Thread = $s->fetch(PDB::FETCH_OBJ) ; + + +// retrieve all trads for Title + $query = "select forum_trads.*,Name,ShortCode,forum_trads.id as IdForumTrads from forum_trads,languages where IdLanguage=languages.id and IdTrad=".$DataPost->Thread->IdTitle." order by forum_trads.created asc" ; + $s = $this->dao->query($query); + $DataPost->Thread->Title=[] ; + while ($row=$s->fetch(PDB::FETCH_OBJ)) { + array_push($DataPost->Thread->Title,$row) ; + } + + $DataPost->PossibleGroups=$this->ModeratorGroupChoice() ; + return ($DataPost) ; + } // end of prepareModeratorEditPost + + +// This is what is called by the Full Moderator edit +// ---> ($vars["submit"]=="update thread")) means the Stick Value or the expire date of the thread have been updated and also the Group +// ---> ($vars["submit"]=="add translated title")) means that a new translated title is made available +// ---> ($vars["submit"]=="add translated post")) means that a new translated post is made available +// ---> ($vars["submit"]=="update post")) means the CanOwnerEdit has been updated +// ---> isset($vars["IdForumTrads"]) means that on of the trad of the forum has been changed (title of one of the post) +// ---> ($vars["submit"]=="delete Tag")) means that the Tag IdTag is to be deleted +// ---> ($vars["submit"]=="Add Tag")) means that the Tag IdTag is to be added + public function ModeratorEditPostProcess() { + if (!($User = $this->getLoggedInMember())) { + return false; + } + + $vars =& PPostHandler::getVars(); + if (isset($vars["submit"]) and ($vars["submit"]=="update thread")) { // if an effective update was chosen for a forum trads + $IdThread=(int)$vars["IdThread"] ; + $IdGroup=(int)$vars["IdGroup"] ; + $ThreadVisibility=$vars["ThreadVisibility"] ; + $ThreadDeleted=$vars["ThreadDeleted"] ; + $WhoCanReply=$vars["WhoCanReply"] ; + $expiredate="'".$vars["expiredate"]."'" ; + $stickyvalue=(int)$vars["stickyvalue"]; + if (empty($expiredate)) { + $expiredate="NULL" ; + } + MOD_log::get()->write("Updating Thread=#".$IdThread." IdGroup=#".$IdGroup." Setting expiredate=[".$expiredate."] stickyvalue=".$stickyvalue." ThreadDeleted=".$ThreadDeleted." ThreadVisibility=".$ThreadVisibility." WhoCanReply=".$WhoCanReply,"ForumModerator"); + $sql="update forums_threads set "; + if ($IdGroup === 0) { + $sql .= "IdGroup = NULL"; + } else { + $sql .= "IdGroup=" . $IdGroup; + } + $sql .= ",stickyvalue=".$stickyvalue.",expiredate=".$expiredate.",ThreadVisibility='".$ThreadVisibility."',ThreadDeleted='".$ThreadDeleted."',WhoCanReply='".$WhoCanReply."' where id=".$IdThread; + +// die ($sql) ; + $this->dao->query($sql); + } + + if (isset($vars["submit"]) and ($vars["submit"]=="add translated title")) { // if a new translation is to be added for a title + $IdThread=(int)$vars["IdThread"] ; + $qry=$this->dao->query("select * from forum_trads where IdTrad=".$vars["IdTrad"]." and IdLanguage=".$vars["IdLanguage"]); + $rr=$qry->fetch(PDB::FETCH_OBJ) ; + if (empty($rr->id)) { // Only proceed if no such a title exists + $ss=$vars["NewTranslatedTitle"] ; + $this->InsertInFTrad($ss,"forums_threads.IdTitle",$IdThread, $this->session->get("IdMember"), $vars["IdLanguage"],$vars["IdTrad"]) ; + MOD_log::get()->write("Updating Thread=#".$IdThread." Adding translation for title in language=[".$vars["IdLanguage"]."]","ForumModerator"); + } + } + + $IdPost=(int)$vars['IdPost'] ; + + if (isset($vars["submit"]) and ($vars["submit"]=="update post")) { // if an effective update was chosen for a forum trads + $OwnerCanStillEdit="'".$vars["OwnerCanStillEdit"]."'" ; + + MOD_log::get()->write("Updating Post=#".$IdPost." Setting OwnerCanStillEdit=[".$OwnerCanStillEdit."]","ForumModerator"); + $this->dao->query("update forums_posts set OwnerCanStillEdit=".$OwnerCanStillEdit." where id=".$IdPost); + } + + if (isset($vars["submit"]) and ($vars["submit"]=="add translated post")) { // if a new translation is to be added for a title + $IdPost=(int)$vars["IdPost"] ; + $qry=$this->dao->query("select * from forum_trads where IdTrad=".$vars["IdTrad"]." and IdLanguage=".$vars["IdLanguage"]); + $rr=$qry->fetch(PDB::FETCH_OBJ) ; + if (empty($rr->id)) { // Only proceed if no such a post exists + $ss=$this->dao->escape($vars["NewTranslatedPost"]) ; + $this->InsertInFTrad($ss,"forums_posts.IdContent",$IdPost, $this->session->get("IdMember"), $vars["IdLanguage"],$vars["IdTrad"]) ; + MOD_log::get()->write("Updating Post=#".$IdPost." Adding translation for title in language=[".$vars["IdLanguage"]."]","ForumModerator"); + $this->updatePostInManticoreIndex($vars); + } + } + + $IdPost=(int)$vars['IdPost'] ; + + if (isset($vars["submit"]) and ($vars["submit"]=="update post")) { // if an effective update was chosen for a forum trads + $IdThread=(int)$vars["IdThread"] ; + $OwnerCanStillEdit=$vars["OwnerCanStillEdit"] ; + $PostVisibility=$vars["PostVisibility"] ; + $PostDeleted=$vars["PostDeleted"] ; + + MOD_log::get()->write("Updating Post=#".$IdPost." Setting OwnerCanStillEdit=[".$OwnerCanStillEdit."] PostVisibility=[".$PostVisibility."] PostDeleted=[".$PostDeleted."] ","ForumModerator"); + $this->dao->query("update forums_posts set OwnerCanStillEdit='".$OwnerCanStillEdit."',PostVisibility='".$PostVisibility."',PostDeleted='".$PostDeleted."' where id=".$IdPost); + // Update last post id + $query = " + SELECT + id + FROM + forums_posts + WHERE + threadid = " . $IdThread . " + AND PostDeleted = 'NotDeleted' + ORDER BY create_time DESC + LIMIT 1"; + $s = $this->dao->query($query); + $row = $s->fetch(PDB::FETCH_OBJ); + if (null === $row) { + // last post of thread was deleted + $this->dao->query("update forums_threads set ThreadDeleted='Deleted' where id=".$IdThread); + } else { + $id = $row->id; + $update = " + UPDATE + forums_threads + SET + last_postid = " . $id . " + WHERE + id = " . $IdThread; + $this->dao->query($update); + } + } + + if (isset($vars["IdForumTrads"])) { // if an effective update was chosen for a forum trads + $this->DofTradUpdate($vars["IdForumTrads"],$vars["Sentence"],$vars["IdLanguage"]) ; // update the corresponding translations + $this->updatePostInManticoreIndex($vars); + } + + PPostHandler::clearVars(); + + return PVars::getObj('env')->baseuri.$this->forums_uri.'modfulleditpost/'.$IdPost; + } // end of ModeratorEditPostProcess + + public function delProcess() { + if (!($User = $this->getLoggedInMember())) { + return false; + } + + if ($this->BW_Right->HasRight("ForumModerator","Delete")) { + $this->dao->query("START TRANSACTION"); + $query = sprintf( + " +SELECT + `forums_posts`.`threadid`, + `forums_threads`.`first_postid`, + `forums_threads`.`last_postid`, + `forums_threads`.`expiredate`, + `forums_threads`.`stickyvalue` +FROM `forums_posts` +LEFT JOIN `forums_threads` ON (`forums_posts`.`threadid` = `forums_threads`.`id`) +WHERE `forums_posts`.`id` = '%d' + ", + $this->messageId + ); + $s = $this->dao->query($query); + if (!$s) { + throw new PException('Could not retrieve Threadinfo!'); + } + $topicinfo = $s->fetch(PDB::FETCH_OBJ); + + if ($topicinfo->first_postid == $this->messageId) { // Delete the complete topic + + $query = + " +UPDATE `forums_threads` +SET `first_postid` = NULL, `last_postid` = NULL +WHERE `id` = '$topicinfo->threadid' + " + ; + $this->dao->query($query); + + $query = + " +DELETE FROM `forums_posts` +WHERE `threadid` = '$topicinfo->threadid' + " + ; + $this->dao->query($query); + MOD_log::get()->write("deleting posts where Thread=#". $topicinfo->threadid, "Forum"); + + // Prepare a notification (before the delete !) + $this->prepare_notification($this->messageId,"deletethread") ; + + $query = + " +DELETE FROM `forums_threads` +WHERE `id` = '$topicinfo->threadid' + " + ; + $this->dao->query($query); + + $redir = 'forums'; + } else { // Delete a single post + /* + * Check if we are deleting the very last post of a topic + * if so, we have to update the `last_postid` field of the `forums_threads` table + */ + if ($topicinfo->last_postid == $this->messageId) { + $query = + " +UPDATE `forums_threads` +SET `last_postid` = NULL +WHERE `id` = '$topicinfo->threadid' + " + ; + $this->dao->query($query); + } + MOD_log::get()->write("deleting single post where Post=#". $this->messageId, "Forum"); + + $this->prepare_notification($this->messageId,"deletepost") ; // Prepare a notification (before the delete !) + + $query = + " +DELETE FROM `forums_posts` +WHERE `id` = '$this->messageId' + " + ; + $this->dao->query($query); + + if ($topicinfo->last_postid == $this->messageId) { + $query = + " +SELECT `forums_posts`.`id` AS `postid` +FROM `forums_posts` +WHERE `threadid` = '$topicinfo->threadid' +ORDER BY `create_time` DESC LIMIT 1 + " + ; + $s = $this->dao->query($query); + if (!$s) { + throw new PException('Could not retrieve Postinfo!'); + } + $lastpost = $s->fetch(PDB::FETCH_OBJ); + + $lastpostupdate = sprintf(", `last_postid` = '%d'", $lastpost->postid); + } else { + $lastpostupdate = ''; + } + + $query = + " +UPDATE `forums_threads` +SET `replies` = (`replies` - 1) $lastpostupdate +WHERE `id` = '$topicinfo->threadid' + " + ; + $this->dao->query($query); + + $redir = $this->forums_uri.'s'.$topicinfo->threadid; + } + + $this->dao->query("COMMIT"); + } + + + header('Location: '.PVars::getObj('env')->baseuri.$redir); + PPHP::PExit(); + } + + + private function checkVarsReply(&$vars) { + $errors = []; + + if (!isset($vars['topic_text']) || empty($vars['topic_text'])) { + $errors[] = 'text'; + } + if ($errors) { + $vars['errors'] = $errors; + return false; + } + + return true; + } + + public function checkVarsTopic(&$vars) { + $errors = []; + + if (!isset($vars['topic_title']) || empty($vars['topic_title'])) { + $errors[] = 'title'; + } + if (!isset($vars['topic_text']) || empty($vars['topic_text'])) { + $errors[] = 'text'; + } + + if ($errors) { + $vars['errors'] = $errors; + return false; + } + + return true; + } + + private function replyTopic(&$vars) { + if (!($User = $this->getLoggedInMember())) { + throw new PException('User gone missing...'); + } + $IdGroup = 0; + if (isset($vars['IdGroup'])) { + $IdGroup = $vars['IdGroup']; + } + if (isset($vars['PostVisibility'])) { + $postVisibility = $vars['PostVisibility']; + } else { + // Someone unchecked the box for group only posts + $vars['ThreadVisibility'] = $vars['PostVisibility'] = $postVisibility = 'MembersOnly'; + } + + $this->dao->query("START TRANSACTION"); + $query = sprintf( + " +INSERT INTO `forums_posts` ( `threadid`, `create_time`, `message`,`IdWriter`,`IdFirstLanguageUsed`,`PostVisibility`, `IdContent`,`edit_count`) +VALUES ('%d', NOW(), '%s','%d',%d,'%s',0, 0) + ", + $this->threadid, + $this->dao->escape($this->cleanupText($vars['topic_text'])), + $this->session->get("IdMember"), $this->GetLanguageChoosen(), $postVisibility + ); + + $result = $this->dao->query($query); + + + $postId = $result->insertId(); + + // Now create the text in forum_trads + $this->InsertInFTrad($this->dao->escape($this->cleanupText($vars['topic_text'])),"forums_posts.IdContent",$postId) ; + + $query = + " +UPDATE `forums_threads` +SET `last_postid` = '$postId', `replies` = `replies` + 1 +WHERE `id` = '$this->threadid' + " + ; + $this->dao->query($query); + + $this->dao->query("COMMIT"); + + + // subscription if any is out of transaction, this is not so important + if ((isset($vars['NotifyMe'])) and ($vars['NotifyMe']=="on")) { + if (!$this->IsThreadSubscribed($this->threadid,$this->session->get("IdMember"))) { + $this->SubscribeThread($this->threadid,$this->session->get("IdMember")) ; + } + } + else { + $vars['NotifyMe']="Not Asked" ; + if ($this->IsThreadSubscribed($this->threadid,$this->session->get("IdMember"))) { + $this->UnsubscribeThreadDirect($this->threadid,$this->session->get("IdMember")) ; + } + } + + $this->addPostToManticoreIndex($vars, $postId, $this->threadid, $this->session->get("IdMember"), $this->GetLanguageChoosen()); + + MOD_log::get()->write("Replying new Post=#". $postId." in Thread=#".$this->threadid." NotifyMe=[".$vars['NotifyMe']."]", "Forum"); + $this->prepare_notification($postId,"reply") ; // Prepare a notification + + return $postId; + } // end of replyTopic + + /** + * Create a new Topic (with initial first post) + * @return int topicid Id of the newly created topic + */ + public function newTopic(&$vars) { + if (!($User = $this->getLoggedInMember())) { + throw new PException('User gone missing...'); + } + $IdGroup = null; + if (isset($vars['IdGroup']) && $vars['IdGroup'] !== 0) { + $IdGroup = $vars['IdGroup']; + } + if (isset($vars['ThreadVisibility'])) { + $ThreadVisibility = $vars['ThreadVisibility']; + } else { + // Someone unchecked the box for group only posts + if (isset($vars['groupOnly']) && ($vars['groupOnly'] === '1')) + { + $ThreadVisibility = 'GroupOnly'; + } else { + $ThreadVisibility = 'MembersOnly'; + } + $vars['ThreadVisibility'] = $ThreadVisibility; + } + + /** @var PDBStatement_mysqli $statement */ + $statement = $this->dao->prepare(" + INSERT INTO `forums_posts` + ( + `create_time`, + `message`, + `IdWriter`, + `IdFirstLanguageUsed`, + `PostVisibility`, + `PostDeleted`, + `IdContent`, + `edit_count` + ) + VALUES (NOW(), ?, ?, ?, ?, 'NotDeleted', 0, 0) + "); + $text = $this->cleanupText($vars['topic_text']); + $memberId = $this->session->get("IdMember"); + $language = $this->GetLanguageChoosen(); + $statement->bindParam(1, $text); + $statement->bindParam(2, $memberId); + $statement->bindParam(3, $language); + $statement->bindParam(4,$ThreadVisibility); + + $result = $statement->execute(); + $postId = $statement->insertId(); + + $this->InsertInFTrad( + $this->dao->escape($this->cleanupText($vars['topic_text'])), + "forums_posts.IdContent", + $postId, + $memberId, + $language + ); + + $statement = $this->dao->prepare(" + INSERT INTO `forums_threads` ( + `title`, `first_postid`, `last_postid`, + `IdFirstLanguageUsed`,`IdGroup`,`ThreadVisibility`, `IdTitle`, `replies`, `views`, `stickyvalue`) + VALUES (?, ?, ?, ?, ?, ?, 0, 0, 0, 0) + "); + + $title = strip_tags((string) $vars['topic_title']); + $statement->bindParam(1, $title); + $statement->bindParam(2, $postId); + $statement->bindParam(3, $postId); + $statement->bindParam(8, $language); + $statement->bindParam(9, $IdGroup); + $statement->bindParam(10, $ThreadVisibility); + + $result = $statement->execute(); + $threadId = $statement->insertId(); + + $ss=$this->dao->escape(strip_tags(((string) $vars['topic_title']))) ; + $this->InsertInFTrad($ss,"forums_threads.IdTitle",$threadId) ; + + $statement = $this->dao->prepare("UPDATE `forums_posts` SET `threadId` = ? WHERE `Id` = ?"); + $statement->bindParam(1, $threadId); + $statement->bindParam(2, $postId); + + $result = $statement->execute(); + + // subscription if any is out of transaction, this is not so important + if ((isset($vars['NotifyMe'])) and ($vars['NotifyMe']=="on")) { + $this->SubscribeThread($threadId,$this->session->get("IdMember")) ; + } + else { + $vars['NotifyMe']="Not Asked" ; + } + + $this->addPostToManticoreIndex($vars, $postId, $threadId, $memberId, $language); + + $this->prepare_notification($postId,"newthread") ; // Prepare a notification + MOD_log::get()->write("New Thread new Tread=#".$threadId." Post=#". $postId." IdGroup=#".$IdGroup." NotifyMe=[".$vars['NotifyMe']."] initial Visibility=".$ThreadVisibility, "Forum"); + + return $threadId; + } // end of NewTopic + + private $topic; + + /** + * function prepareTopic prepares the detail of a topic for display according to threadid + * if @$WithDetail is set to true, additional details (available languages and original author are displayed) + */ + public function prepareTopic($WithDetail=false) { + $this->topic = new Topic(); + $this->topic->WithDetail = $WithDetail; + + // Topic Data + $query = " + SELECT + `forums_threads`.`title`, + `forums_threads`.`IdTitle`, + `forums_threads`.`replies`, + `forums_threads`.`id` as IdThread, + `forums_threads`.`views`, + `forums_threads`.`ThreadVisibility`, + `forums_threads`.`ThreadDeleted`, + `forums_threads`.`WhoCanReply`, + `forums_threads`.`first_postid`, + `forums_threads`.`expiredate`, + `forums_threads`.`stickyvalue`, + `forums_threads`.`IdGroup`, + `groups`.`Name` AS `GroupName` + FROM + `forums_threads` + LEFT JOIN `groups` ON (`forums_threads`.`IdGroup` = `groups`.`id`) + WHERE + `forums_threads`.`id` = '$this->threadid' + AND ($this->PublicThreadVisibility) + AND ($this->ThreadGroupsRestriction) + "; + + $s = $this->dao->query($query); + if (!$s) { + throw new PException('Could not retrieve Thread=#".$this->threadid." !'); + } + + $topicinfo = $s->fetch(PDB::FETCH_OBJ); + // Check if any result was found + if (!$topicinfo) { + $topicinfo = new stdClass; + } + if (isset($topicinfo->WhoCanReply)) { + if ($topicinfo->WhoCanReply=="MembersOnly") { + $topicinfo->CanReply=$this->getLoggedInMember()->Status !== \App\Doctrine\MemberStatusType::ACCOUNT_ACTIVATED; + } + else if ($topicinfo->WhoCanReply=="GroupsMembersOnly") { + if ($topicinfo->IdGroup==0) { + $topicinfo->CanReply=$this->getLoggedInMember()->Status !== \App\Doctrine\MemberStatusType::ACCOUNT_ACTIVATED ; + } + else { + $topicinfo->CanReply=in_array($topicinfo->IdGroup,$this->MyGroups) ; // Set to true only if current member is member of the group + } + } + else if ($topicinfo->WhoCanReply=="ModeratorOnly") { + if ($this->BW_Right->HasRight("ForumModerator")) { + $topicinfo->CanReply=true ; + } + else { + $topicinfo->CanReply=false ; + } + } + else { + $topicinfo->CanReply=false ; + } + } + + $this->topic->topicinfo = $topicinfo; + $this->topic->IdThread=$this->threadid ; + + $from = $this->POSTS_PER_PAGE * ($this->getPage() - 1); + $order = ($this->ForumOrderList == "Yes") ? "ASC" : "DESC"; + + $query = sprintf(" + SELECT + `forums_posts`.`id` AS `postid`, + forums_posts.id as IdPost, + UNIX_TIMESTAMP(create_time) AS posttime, + message, + IdContent, + IdWriter, + geo__names.name AS city, + geonamescountries.name AS country, + forums_posts.threadid, + OwnerCanStillEdit, + member.Username as OwnerUsername, + PostVisibility, + PostDeleted, + forums_threads.IdGroup + FROM + forums_threads, + forums_posts + LEFT + JOIN member ON forums_posts.IdWriter = member.id + LEFT JOIN + address AS a ON a.member_id = member.id AND a.active = 1 + LEFT JOIN + geo__names ON a.location = geo__names.geonameId + LEFT JOIN + geonamescountries ON geo__names.country = geonamescountries.country + WHERE + forums_posts.threadid = '%d' + AND forums_posts.threadid=forums_threads.id + AND ({$this->PublicPostVisibility}) + AND ({$this->ThreadGroupsRestriction}) + ORDER BY + posttime {$order} + LIMIT %d, %d", + $this->threadid,$from,$this->POSTS_PER_PAGE + ); + + $s = $this->dao->query($query); + if (!$s) { + throw new PException('Could not retrieve Posts)!'); + } + while ($row = $s->fetch(PDB::FETCH_OBJ)) { + if ($WithDetail) { // if details are required retrieve all the translated text for posts (sentence, owner, modification time and translator name) of this thread + $sw = $this->dao->query("select forum_trads.IdLanguage,UNIX_TIMESTAMP(forum_trads.created) as trad_created, UNIX_TIMESTAMP(forum_trads.updated) as trad_updated, forum_trads.Sentence,IdOwner,IdTranslator,languages.ShortCode,languages.Name,mTranslator.Username as TranslatorUsername ,mOwner.Username as OwnerUsername + from forum_trads,languages,member as mOwner, member as mTranslator + where languages.id=forum_trads.IdLanguage and forum_trads.IdTrad=".$row->IdContent." and mOwner.id=IdOwner and mTranslator.id=IdTranslator order by forum_trads.id asc"); + while ($roww = $sw->fetch(PDB::FETCH_OBJ)) { + $row->Trad[]=$roww ; + } + } + $this->topic->posts[] = $row; + } // end // Now retrieve all the Posts of this thread + + // Check if the current user has subscribe to this thread or not (to display the proper option, subscribe or unsubscribe) + if ($this->session->has( "IdMember" )) { + $memberId = $this->session->get("IdMember"); + $query = " +SELECT + `members_threads_subscribed`.`id` AS IdSubscribe, + `members_threads_subscribed`.`UnSubscribeKey` AS IdKey, + `members_threads_subscribed`.`notificationsEnabled` AS notificationsEnabled +FROM members_threads_subscribed +WHERE IdThread = {$this->threadid} +AND IdSubscriber = {$memberId}"; + $s = $this->dao->query($query); + if (!$s) { + throw new PException('Could if has subscribed to Thread=#".$this->threadid." !'); + } + $row = $s->fetch(PDB::FETCH_OBJ) ; + if (isset($row->IdSubscribe)) { + $this->topic->notificationsEnabled = $row->notificationsEnabled; + $this->topic->IdSubscribe= $row->IdSubscribe ; + $this->topic->IdKey= $row->IdKey ; + } + if (isset($this->topic->topicinfo->IdGroup)) { + // Check if member has enabled group mails + $group = $this->createEntity('Group', $this->topic->topicinfo->IdGroup); + $member = $this->createEntity('Member', $this->session->get("IdMember")); + $membership = $this->createEntity('GroupMembership')->getMembership($group, $member); + if ($membership) { + $this->topic->isGroupSubscribed = ($membership->IacceptMassMailFromThisGroup == 'yes'); + if (!isset($row->IdSubscribe)) { + $this->topic->notificationsEnabled = ($membership->notificationsEnabled); + } + } + } + } + + // Increase the number of views + $query = " +UPDATE `forums_threads` +SET `views` = (`views` + 1) +WHERE `id` = '$this->threadid' LIMIT 1 + " ; + $this->dao->query($query); + + } // end of prepareTopic + + public function initLastPosts() { + $query = sprintf(" +SELECT + `forums_posts`.`id` AS `postid`, + UNIX_TIMESTAMP(`create_time`) AS `posttime`, + `message`, + `IdContent`, + `member`.`username` AS `OwnerUsername`, + `IdWriter`, + forums_threads.`id` as `threadid`, + `PostVisibility`, + `PostDeleted`, + `ThreadDeleted`, + `OwnerCanStillEdit`, + `geo__names`.`name` as `city`, + `geonamescountries`.`name` as `country`, + `IdGroup` +FROM forums_posts, forums_threads, member, address +LEFT JOIN `geo__names` ON (address.location = `geo__names`.`geonameId`) +LEFT JOIN `geonamescountries` ON (geo__names.country = `geonamescountries`.`country`) +WHERE `forums_posts`.`threadid` = '%d' AND `forums_posts`.`IdWriter` = `member`.`id` +AND address.member_id = member.id AND address.active = 1 + AND `forums_posts`.`threadid`=`forums_threads`.`id` + and ({$this->PublicPostVisibility}) + and ({$this->PublicThreadVisibility}) + and ({$this->PostGroupsRestriction}) + and ThreadDeleted <> 'Deleted' + And PostDeleted <> 'Deleted' +ORDER BY `posttime` DESC +LIMIT %d + ", + $this->threadid, + Forums::NUMBER_LAST_POSTS_PREVIEW + ); + $s = $this->dao->query($query); + if (!$s) { + throw new PException('Could not retrieve Posts!'); + } + $this->topic->posts = []; + while ($row = $s->fetch(PDB::FETCH_OBJ)) { + $sw = $this->dao->query("select forum_trads.IdLanguage,UNIX_TIMESTAMP(forum_trads.created) as trad_created, UNIX_TIMESTAMP(forum_trads.updated) as trad_updated, forum_trads.Sentence,IdOwner,IdTranslator,languages.ShortCode,languages.Name,mTranslator.Username as TranslatorUsername ,mOwner.Username as OwnerUsername from forum_trads,languages,member as mOwner, member as mTranslator + where languages.id=forum_trads.IdLanguage and forum_trads.IdTrad=".$row->IdContent." and mOwner.id=IdOwner and mTranslator.id=IdTranslator order by forum_trads.id asc"); + while ($roww = $sw->fetch(PDB::FETCH_OBJ)) { + $row->Trad[]=$roww ; + } + $this->topic->posts[] = $row; + } + } // end of initLastPosts + + private function updateSubscriptions($memberId, $enable) { + // update subscription (keep old assignments through negating if disabling) + // members_threads_subscribed + $query =" + UPDATE + members_threads_subscribed + SET + notificationsEnabled = '" . ($enable ? 1 : 0) . "' + WHERE + IdSubscriber = " . $memberId; + $this->dao->query($query); + $this->updateGroupNotifications($memberId, $enable); + $this->dao->query($query); + } + + private function updateGroupNotifications($memberId, $enable) { + $query = " + UPDATE + membersgroups + SET + notificationsEnabled = '" . ($enable ? 1 : 0) . "' + WHERE + IdMember = " . $memberId . " + AND IacceptMassMailFromThisGroup = 'Yes' + "; + $this->dao->query($query); + } + + private function updateGroupNotification($groupId, $memberId, $enable) { + $query = " + UPDATE + membersgroups + SET + notificationsEnabled = '" . ($enable ? 1 : 0) . "' + WHERE + IdGroup = " . $groupId . " + AND IdMember = " . $memberId . " + AND IacceptMassMailFromThisGroup = 'Yes' + "; + $this->dao->query($query); + } + + public function disableSubscriptions() { + $member = $this->getLoggedInMember(); + if ($member) { + $this->updateSubscriptions($member->id, false); + } + } + + public function enableSubscriptions() { + $member = $this->getLoggedInMember(); + if ($member) { + $this->updateSubscriptions($member->id, true); + } + } + + public function disableGroup($IdGroup) { + $member = $this->getLoggedInMember(); + if ($member) { + $this->updateGroupNotification($IdGroup, $member->id, false); + } + } + + public function enableGroup($IdGroup) { + $member = $this->getLoggedInMember(); + if ($member) { + $this->updateGroupNotification($IdGroup, $member->id, true); + } + } + + public function subscribeGroup($IdGroup) { + $member = $this->getLoggedInMember(); + if ($member) { + $group = $this->createEntity('Group', $IdGroup); + if (!($membership = $this->createEntity('GroupMembership')->getMembership($group, $member))) + { + return false; + } + + $membership->updateMembership('yes', $membership->Comment); + } + } + + public function unsubscribeGroup($IdGroup) { + $member = $this->getLoggedInMember(); + if ($member) { + $group = $this->createEntity('Group', $IdGroup); + if (!($membership = $this->createEntity('GroupMembership')->getMembership($group, $member))) + { + return false; + } + + $membership->updateMembership('no', $membership->Comment); + } + } + + /** + * This function retrieve the subscriptions for the member $cid and/or the the thread IdThread and/or theIdTag + * @$cid : either the IdMember or the username of the member we are searching the subscription + * this $cid and $IdThread and $IdTag parameters are only used if the current member has moderator rights + * It returns a $TResults structure + * Very important : member who are not moderators cannot see other people subscriptions + * @param bool $IdThread + * @param bool $IdTag + * @return StdClass + * @throws PException + */ + public function searchSubscriptions() { + $member= $this->getLoggedInMember(); + if (!$member) { + return []; + } + $TResults = new StdClass(); + $query = " +SELECT + `members_threads_subscribed`.`id` as IdSubscribe, + `members_threads_subscribed`.`created` AS `subscribedtime`, + `ThreadVisibility`, + `ThreadDeleted`, + `forums_threads`.`id` as IdThread, + `forums_threads`.`title`, + `forums_threads`.`IdTitle`, + `members_threads_subscribed`.`ActionToWatch`, + `members_threads_subscribed`.`UnSubscribeKey`, + `members_threads_subscribed`.`notificationsEnabled` +FROM `forums_threads`,`members_threads_subscribed` +WHERE `forums_threads`.`id` = `members_threads_subscribed`.`IdThread` +AND `members_threads_subscribed`.`IdSubscriber`= {$member->id} +ORDER BY `subscribedtime` DESC + "; + $s = $this->dao->query($query); + if (!$s) { + throw new PException('Could not retrieve members_threads_subscribed sts via searchSubscription !'); + } + + $TResults->TData = []; + while ($row = $s->fetch(PDB::FETCH_OBJ)) { + $TResults->TData[] = $row; + } + + $query = " + SELECT + Name, IdGroup, IdMember, IacceptMassMailFromThisGroup As AcceptMails, notificationsEnabled + FROM + `membersgroups` mg, + `groups` g + WHERE + g.id = mg.IdGroup + AND IdMember = '{$member->id}' + AND Status = 'In' + ORDER BY + Name"; + $s = $this->dao->query($query); + if (!$s) { + throw new PException('Could load group memberships'); + } + $TResults->Groups = []; + while ($row = $s->fetch(PDB::FETCH_OBJ)) { + $TResults->Groups[] = $row; + } + return $TResults; + } // end of searchSubscriptions + + + /** + * This function remove the subscription marked by IdSubscribe + * @IdSubscribe is the primary key of the members_threads_subscribed area to remove + * @Key is the key to check to be sure it is not an abuse of url + * It returns a $res=1 if ok + */ + public function UnsubscribeThread($IdSubscribe=0,$Key="") { + $query = sprintf( + " +SELECT + members_threads_subscribed.id AS IdSubscribe, + IdThread, + IdSubscriber, + Username from member, + members_threads_subscribed +WHERE member.id=members_threads_subscribed.IdSubscriber +AND members_threads_subscribed.id=%d +AND UnSubscribeKey='%s' + ", + $IdSubscribe,$this->dao->escape($Key) + ); + $s = $this->dao->query($query); + if (!$s) { + throw new PException('Forum->UnsubscribeThread Could not retrieve the subscription !'); + } + $row = $s->fetch(PDB::FETCH_OBJ) ; + if (!isset($row->IdSubscribe)) { + MOD_log::get()->write("No entry found while Trying to unsubscribe thread IdSubscribe=#".$IdSubscribe." IdKey=".$Key, "Forum"); + return(false) ; + } + $query = sprintf( + " +DELETE +FROM members_threads_subscribed +WHERE id=%d +AND UnSubscribeKey='%s' + ", + $IdSubscribe, + $this->dao->escape($Key) + ); + $s = $this->dao->query($query); + if (!$s) { + throw new PException('Forum->UnsubscribeThread delete failed !'); + } + if ($this->session->has( "IdMember" )) { + MOD_log::get()->write("Unsubscribing member ".$row->Username." from Thread=#".$row->IdThread, "Forum"); + if ($this->session->get("IdMember")!=$row->IdSubscriber) { // If it is not the member himself, log a forum action in addition + MOD_log::get()->write("Unsubscribing member ".$row->Username." from Thread=#".$row->IdThread, "ForumModerator"); + } + } + else { + MOD_log::get()->write("Unsubscribing member ".$row->Username." from Thread=#".$row->IdThread." without beeing logged", "Forum"); + } + return(true) ; + } // end of UnsubscribeThread + + /** + * This function remove the subscription without checking the key + * + * @param unknown_type $IdThread the id of the thread to unsubscribe to + * @param unknown_type $ParamIdMember the member to unsubscribe, if 0, the current member will eb used + * @return unknown + */ + public function UnsubscribeThreadDirect($IdThread=0,$ParamIdMember=0) { + $IdMember=$ParamIdMember ; + if ($this->session->has( "IdMember" ) and $IdMember==0) { + $IdMember=$this->session->get("IdMember") ; + } + + if ($IdMember==0) { // No need to do something if no member is logged + return ; + } + + $query = sprintf( + " +DELETE +FROM members_threads_subscribed +WHERE IdSubscriber=%d +AND IdThread=%d + ", + $IdMember, + $IdThread + ); + $s = $this->dao->query($query); + if (!$s) { + throw new PException('Forum->UnsubscribeThreadDirect failed to delete !'); + } + MOD_log::get()->write("Unsubscribing direct (By NotifyMe) member=#".$IdMember." from Thread=#".$IdThread, "Forum"); + return(true) ; + } // end of UnsubscribeThreadDirect + + + /** + * This function allow to subscribe to a thread + * + * @$IdThread : The thread we want the user to subscribe to + * @$ParamIdMember optional IdMember, by default set to 0 in this case current logged member will be used + * It also check that member is not yet subscribing to thread + */ + public function SubscribeThread($IdThread,$ParamIdMember=0) { + $IdMember=$ParamIdMember ; + if ($this->session->has( "IdMember" ) and $IdMember==0) { + $IdMember=$this->session->get("IdMember") ; + } + + // Check if there is a previous Subscription + if ($this->IsThreadSubscribed($IdThread,$this->session->get("IdMember"))) { + MOD_log::get()->write("Allready subscribed to Thread=#".$IdThread, "Forum"); + return(false) ; + } + $key=MD5(random_int(100000,900000)) ; + $query = "INSERT INTO + members_threads_subscribed(IdThread,IdSubscriber,UnSubscribeKey,notificationsEnabled,created) + VALUES(".$IdThread.",".$this->session->get("IdMember").",'".$this->dao->escape($key)."', 1,NOW())" ; + $s = $this->dao->query($query); + if (!$s) { + throw new PException('Forum->SubscribeThread failed !'); + } + $IdSubscribe= $s->insertId() ; + MOD_log::get()->write("Subscribing to Thread=#".$IdThread." IdSubscribe=#".$IdSubscribe, "Forum"); + } // end of UnsubscribeThread + + /** + * This function allows to enable a thread that has been disabled + * + * @$IdThread : The thread we want the user to subscribe to + * @$ParamIdMember optional IdMember, by default set to 0 in this case current logged member will be used + * It also check that member is not yet subscribing to thread + */ + public function EnableThread($IdThread) { + $member = $this->getLoggedInMember(); + if (!$member) { + return; + } + + // Check if there is a previous Subscription + if ($this->IsThreadSubscribed($IdThread,$member->id)) { + $query = " + UPDATE + members_threads_subscribed + SET + notificationsEnabled = '1' + WHERE + IdThread = " . $IdThread . " + AND IdSubscriber = " . $member->id; + $this->dao->query($query); + } + } // end of EnableThread + + /** + * This function allows to disable notifications for a thread if the group has been subscribed + * + * @$IdThread : The thread we want the user to disable + */ + public function DisableThread($IdThread) { + $member = $this->getLoggedInMember(); + if (!$member) { + return; + } + + // Make sure there is something to disable + if (!$this->IsThreadSubscribed($IdThread,$member->id)) { + $this->SubscribeThread($IdThread); + } + + // if there was already a disable notification this won't change it. + $query = " + UPDATE + members_threads_subscribed + SET + notificationsEnabled = '0' + WHERE + IdThread = " . $IdThread . " + AND IdSubscriber = " . $member->id; + $this->dao->query($query); + } // end of DisableThread + + // This function retrieve search post of the member $cid + //@$cid : either the IdMember or the username of the member we are searching the post + public function searchUserposts($cid=0) { + $IdMember=0 ; + if (is_numeric($cid)) { + $IdMember=$cid ; + } + else + { + if (!($member = $this->createEntity('Member')->findByUsername($cid))) + { + throw new PException('Could not retrieve members id via username !'); + } + $IdMember = $member->id; + } + + $query = sprintf( + "SELECT `forums_posts`.`id` AS `postid`,`forums_posts`.`id` as IdPost, UNIX_TIMESTAMP(`create_time`) AS `posttime`, `message`, + `OwnerCanStillEdit`,`IdContent`, `forums_threads`.`id` AS `threadid`, `forums_threads`.`title`, + `ThreadVisibility`, + `ThreadDeleted`, + `PostVisibility`, + `PostDeleted`, + `ThreadDeleted`, + `forums_threads`.`IdTitle`,`forums_threads`.`IdGroup`, `IdWriter`, `member`.`Username` AS `OwnerUsername`, `groups`.`Name` AS `GroupName`, `geo__names`.`country` + FROM (forums_posts, member, forums_threads, address) +LEFT JOIN `groups` ON (`forums_threads`.`IdGroup` = `groups`.`id`) +LEFT JOIN `geo__names` ON (address.location = geo__names.geonameId) +WHERE `forums_posts`.`IdWriter` = %d AND `forums_posts`.`IdWriter` = `member`.`id` +AND `forums_posts`.`threadid` = `forums_threads`.`id` +AND address.member_id = member.id AND address.active = 1 +AND ($this->PublicThreadVisibility) +AND ($this->PublicPostVisibility) +AND ($this->PostGroupsRestriction) +ORDER BY `posttime` DESC", $IdMember ); + $s = $this->dao->query($query); + if (!$s) { + throw new PException('Could not retrieve Posts via searchUserposts !'); + } + $posts = []; + while ($row = $s->fetch(PDB::FETCH_OBJ)) { + $sw = $this->dao->query("select forum_trads.IdLanguage,UNIX_TIMESTAMP(forum_trads.created) as trad_created, UNIX_TIMESTAMP(forum_trads.updated) as trad_updated, forum_trads.Sentence,IdOwner,IdTranslator,languages.ShortCode,languages.Name,mTranslator.Username as TranslatorUsername ,mOwner.Username as OwnerUsername from forum_trads,languages,member as mOwner,member as mTranslator where languages.id=forum_trads.IdLanguage and forum_trads.IdTrad=".$row->IdContent." and mTranslator.id=IdTranslator and mOwner.id=IdOwner order by forum_trads.id asc"); + while ($roww = $sw->fetch(PDB::FETCH_OBJ)) { + $row->Trad[]=$roww ; + } + $posts[] = $row; + + } // + + return $posts; + } // end of searchUserposts + + public function getTopic() { + return $this->topic; + } + + /** + * Check if it's a topic or a forum + * @return bool true on topic + * @return bool false on forum + */ + public function isTopic() { + return (bool) $this->threadid; + } + + private $threadid = 0; + private $page = 1; + private $page_array = []; + private $messageId = 0; + private $TopMode=Forums::CV_TOPMODE_LANDING; // define that we use the landing page for top mode + + public function setTopMode($Mode) { + $this->TopMode = $Mode ; + } + public function getTopMode() { + return $this->TopMode; + } + public function setGroupId($IdGroup) { + $this->IdGroup = (int) $IdGroup; + } + public function setThreadId($threadid) { + $this->threadid = (int) $threadid; + } + public function getThreadId() { + return $this->threadid; + } + public function getIdGroup() { + return $this->IdGroup; + } + public function getPage() { + return $this->page; + } + public function setPage($page) { + $this->page = (int) $page; + } + public function getPageArray() { + return $this->page_array; + } + public function setPageArray($page_array) { + $this->page_array = $page_array; + } + public function pushToPageArray($page) { + $this->page_array[] = $page; + } + public function setMessageId($messageid) { + $this->messageId = (int) $messageid; + } + public function getMessageId() { + return $this->messageId; + } + + public function getIdContent() { // Return the IdContent (IdTrad for the id of the post, according to currently set $this->messageId + $IdContent=-1 ; + $query = "select `IdContent` from `forums_posts` where `id`=".$this->messageId ; + $s = $this->dao->query($query); + if (!$s) { + throw new PException('Forum-> getIdContent failed for this->messageId='.$this->messageId); + } + $row = $s->fetch(PDB::FETCH_OBJ) ; + if (isset ($row->IdContent)) { + $IdContent=$row->IdContent ; + } + return $IdContent; + } + + /* + * cleanupText + * + * @param string $txt + * @access private + * @return string + */ + private function cleanupText($txt) + { + $purifier = MOD_htmlpure::get()->getForumsHtmlPurifier(); + + return $purifier->purify($txt); + } // end of cleanupText + + function GetLanguageName($IdLanguage) { + $query="select id as IdLanguage,Name,Name,ShortCode from languages where id=".($IdLanguage) + . " AND IsWrittenLanguage = 1"; + $s = $this->dao->query($query); + if (!$s) { + throw new PException('Could not retrieve IdLanguage in GetLanguageName entries'); + } else { + $row = $s->fetch(PDB::FETCH_OBJ) ; + $row->Name = $this->getWords()->getSilent('lang' . $row->ShortCode) . " (" . $row->Name . ")"; + return($row) ; + } + return("not Found") ; + } // end of GetLanguageName + + + // This fonction will prepare a list of language to choose + // @DefIdLanguage : an optional language to use + // return an array of object with LanguageName and IdLanguage + public function LanguageChoices($DefIdLanguage=-1) { + + + $tt=[] ; + $allreadyin=[] ; + $ii=0 ; + +// First proposed will deflanguage +// if (!empty($DefIdLanguage) and ($DefIdLanguage>=0)) { + if (($DefIdLanguage>=0)) { + $row=$this->GetLanguageName($DefIdLanguage) ; + array_push($allreadyin,$row->IdLanguage) ; + array_push($tt, "CurrentLanguage"); + array_push($tt,$row) ; + } + // Then next will be english (if not allready in the list) + if (!in_array(0,$allreadyin)) { + $row=$this->GetLanguageName(0) ; + array_push($allreadyin,$row->IdLanguage) ; + array_push($tt, "DefaultLanguage"); + array_push($tt,$row) ; + } + // Then next will the current user language + if (($this->session->has( "IdLanguage" ) and (!in_array($this->session->get("IdLanguage"),$allreadyin)))) { + $row=$this->GetLanguageName($this->session->get("IdLanguage")) ; + array_push($allreadyin,$row->IdLanguage) ; + array_push($tt, "UILanguage"); + array_push($tt,$row); + } + + array_push($tt, "AllLanguages"); + // then now all available languages + $query="select id as IdLanguage,Name,Name,ShortCode from languages where id>0" + . " AND IsWrittenLanguage = 1"; + $s = $this->dao->query($query); + $langarr = []; + while ($row = $s->fetch(PDB::FETCH_OBJ)) { + if (!in_array($row->IdLanguage,$allreadyin)) { + array_push($allreadyin,$row->IdLanguage) ; + $row->Name = $this->getWords()->getSilent('lang_' . $row->ShortCode) . " (" . trim((string) $row->Name) . ")"; + $langarr[] = $row; + } + } + // now sort langarr and append to the $tt array + usort($langarr, "cmpForumLang"); + return($tt + $langarr) ; // returs the array of structures + } // end of LanguageChoices + + // This fonction will prepare a list of group in an array that the moderator can use + public function ModeratorGroupChoice() { + $tt=[] ; + + $query="select groups.id as IdGroup,Name,count(*) as cnt from groups,membersgroups + WHERE membersgroups.IdGroup=groups.id group by groups.id order by Name "; + $s = $this->dao->query($query); + while ($row = $s->fetch(PDB::FETCH_OBJ)) { + $row->GroupName=$row->Name=$this->getGroupName($row->Name); + array_push($tt,$row) ; + } + return($tt) ; // returs the array of structures + + } // end of ModeratorGroupChoices + + // This fonction will prepare a list of group in an array that the user can use + // (according to his member ship) + public function GroupChoice() { + $tt=[] ; + + $query="select groups.id as IdGroup,groups.Name,count(*) as cnt from groups,membersgroups,member + WHERE membersgroups.IdGroup=groups.id and member.id=membersgroups.IdMember and + member.Status in ('Active','ActiveHidden') and member.id=".$this->session->get('IdMember')." and membersgroups.Status='In' group by groups.id order by groups.id "; + $s = $this->dao->query($query); + while ($row = $s->fetch(PDB::FETCH_OBJ)) { + $row->GroupName=$this->getGroupName($row->Name); + array_push($tt,$row) ; + } + return($tt) ; // returs the array of structures + + } // end of GroupChoices + + /** + Return true if the Member is not allowed to see the given post because of groups restrictions + @$IdMember : Id of the member + @$rPost data about the post (Thread and Post visibility, id of the group and delettion status) + return true or false + !! Becareful, in case the member has moderator right, his moderator rights will not be considerated + */ +public function NotAllowedForGroup($IdMember, $rPost) { + if ($rPost->ThreadDeleted=='Deleted') return (true) ; //Deleted thread: no notifications + if ($rPost->PostDeleted=='Deleted') return (true) ; //Deleted post: no notifications + if ($rPost->IdGroup==0) return(false) ; // No group defined, noification can be allowed + + // In case there is a restriction to moderator, check if the member is a moderator + if (($rPost->PostVisibility=='ModeratorOnly') or ($rPost->ThreadVisibility=='ModeratorOnly')) + if ($this->BW_Right->HasRight("ForumModerator")) { + return (false) ; // Moderator are allowed + } + else { + return(true) ; + } + + if (($rPost->PostVisibility=='GroupOnly') or ($rPost->ThreadVisibility=='GroupOnly')) { // If there is a group restriction, we need to check the membership of the member + $query = "select IdGroup from membersgroups where IdMember=".$IdMember." and IdGroup=".$rPost->IdGroup." and Status='In'"; + $qry = $this->dao->query("select IdGroup from membersgroups where IdMember=".$IdMember." and IdGroup=".$rPost->IdGroup." and Status='In'"); + if (!$qry) { + throw new PException('Failed to retrieve groupsmembership for member id =#'.$IdMember.' !'); + } + $rr=$qry->fetch(PDB::FETCH_OBJ) ; + if (isset($rr->IdGroup)) { // If the guy is member of the group + return(false) ; // He is allowed to see + } + else { + return(true) ; + } + } + + // Other cases no reason to restrict because of some group restriction + return (false) ; + } // end of NotAllowedForGroup + + + /** + * Handle forum notifications + */ + private function prepare_notification($postId, $type) { + // Get post details + $query = " + SELECT + p.threadid as threadId, + t.IdGroup as groupId, + p.PostVisibility, + p.PostDeleted, + t.ThreadVisibility, + t.ThreadDeleted + FROM + forums_posts p, + forums_threads t + WHERE + p.threadid = t.id + AND p.id = '" . $this->dao->escape($postId) ."'"; + + $res = $this->dao->query($query); + if (!$res) { + // just don't set notifications + return; + } + $post = $res->fetch(PDB::FETCH_OBJ); + + // Some checks before we take the long way + if (($post->PostDeleted == 'deleted') || ($post->ThreadDeleted == 'deleted')) { + return; + } + + $members = []; // collects all members that get a notification (to avoid several notifications for the same post) + + // first we get all open (ToSend) post notifications from the database and build + // a list of members that don't need another reminder + $query = " + SELECT + DISTINCT IdMember + FROM + posts_notificationqueue p + WHERE + p.IdPost = $postId + AND Status = 'ToSend' + ORDER BY + IdMember + "; + $res = $this->dao->query($query); + if ($res) { + while ($row = $res->fetch(PDB::FETCH_OBJ)) { + $members[] = $row->IdMember; + } + } + + // get group members in case of a group post to limit subscriptions to tags and threads + $group = false; + $groupMembers = []; + if ($post->groupId != 0) { + $group = $this->createEntity('Group')->findById($post->groupId); + $memberEntities = $group->getMembers(); + foreach($memberEntities as $groupMember) { + $groupMembers[] = $groupMember->getPKValue(); + } + } + + if ($group != false) { + // We reuse the $group entity from above + $subscriberEntities = $group->getEmailAcceptingMembers(); + + $membersTemp = []; + foreach($subscriberEntities as $subscriber) { + $memberId = $subscriber->getPKValue(); + if ($memberId == 0) continue; + if (array_search($memberId, $members) === false) { + $membersTemp[] = $memberId; + $members[] = $memberId; + } + } + if (!empty($membersTemp)) { + $count = 0; + $query = " + INSERT INTO + posts_notificationqueue ( + `IdMember`, + `IdPost`, + `Status`, + `created`, + `Type`, + `TableSubscription`, + `IdSubscription` + ) + VALUES "; + foreach($membersTemp as $member) { + $query .= "(" . $member . ", " . $postId . ", '" . NotificationStatusType::SCHEDULED . "', NOW(), '" . $type . "', 'membersgroups', 0), "; + $count++; + } + if ($count > 0) { + $query = substr($query, 0, -2); + $this->dao->query($query); + } + } + } + + // Set notifications for subscribed threads + $query = " + SELECT + IdSubscriber as subscriber, + notificationsEnabled, + members_threads_subscribed.id as subscriptionId + FROM + members_threads_subscribed + WHERE IdThread = '" . $this->dao->escape($post->threadId) . "'"; + $res = $this->dao->query($query); + if (!$res) { + // just don't write notifications + return; + } + + $membersTemp = []; + while ($row = $res->fetch(PDB::FETCH_OBJ)) { + if ($row->subscriber > 0) { + // did member disable notifications for this thread? + if ($row->notificationsEnabled) { + // only add gets notification don't add one + if (array_search($row->subscriber, $members) === false) { + $membersTemp[$row->subscriber] = $row->subscriptionId; + } + } else { + if (array_search($row->subscriber, $members) !== false) { + unset($membersTemp[$row->subscriber]); + } + } + } + } + + if (!empty($membersTemp)) { + $count = 0; + $query = " + INSERT INTO + posts_notificationqueue ( + `IdMember`, + `IdPost`, + `Status`, + `created`, + `Type`, + `TableSubscription`, + `IdSubscription` + ) + VALUES "; + foreach($membersTemp as $member => $subscriptionId) { + // current member doesn't get a notification yet + if (array_search($member, $members) === false) { + // Thread notifications might need to be limited to group members only + if (($post->groupId == 0) || ($post->PostVisibility != 'GroupOnly' && $post->ThreadVisibility != 'GroupOnly') + || (array_search($member, $groupMembers) !== false)) { + $query .= "(" . $member . ", " . $postId . ", '" . NotificationStatusType::SCHEDULED . "', NOW(), '" . $type . "', 'members_threads_subscribed', '" . $this->dao->escape($subscriptionId) . "'), "; + $members[] = $member; + $count++; + } + } + } + if ($count > 0) { + $query = substr($query, 0, -2); + $this->dao->query($query); + } + } + } + + // This function IsGroupSubscribed return true of the member is subscribing to the IdGroup + // @$IdGroup : The thread we want to know if the user is subscribing too + // @$ParamIdMember optional IdMember, by default set to 0 in this case current logged membver will be used + public function IsGroupSubscribed($IdGroup=0,$ParamIdMember=0) { + $IdMember=$ParamIdMember ; + if ($this->session->has( "IdMember" ) and $IdMember==0) { + $IdMember=$this->session->get("IdMember") ; + } + + // Check if there is a previous Subscription + $query = sprintf("select members_groups_subscribed.id as IdSubscribe,IdThread,IdSubscriber from members_groups_subscribed where IdGroup=%d and IdSubscriber=%d",$IdGroup,$IdMember); + $s = $this->dao->query($query); + if (!$s) { + throw new PException('IsGroupSubscribed Could not check previous subscription !'); + } + $row = $s->fetch(PDB::FETCH_OBJ) ; + return (isset($row->IdSubscribe)) ; + } // end of IsGroupSubscribed + + public function IsThreadSubscribed($IdThread=0,$ParamIdMember=0) { + $IdMember=$ParamIdMember ; + if ($this->session->has( "IdMember" ) and $IdMember==0) { + $IdMember=$this->session->get("IdMember") ; + } + + // Check if there is a previous Subscription + $query = sprintf("select members_threads_subscribed.id as IdSubscribe,IdThread,IdSubscriber from members_threads_subscribed where IdThread=%d and IdSubscriber=%d",$IdThread,$IdMember); + $s = $this->dao->query($query); + if (!$s) { + throw new PException('IsThreadSubscribed Could not check previous subscription !'); + } + $row = $s->fetch(PDB::FETCH_OBJ) ; + return (isset($row->IdSubscribe)) ; + } // end of IsThreadSubscribed + + // This function isMembersForumPostsPagePublic return true if the members allows other members to see his forum posts page: /forums/member/username + // @$userId : The user we want to know if his forum page is public + public function isMembersForumPostsPagePublic($userId = 0) { + $member = $this->createEntity("Member", $userId); + $usersForumPostsPagePublic = $member->getPreference("MyForumPostsPagePublic", $default = "No"); + if ($usersForumPostsPagePublic == "Yes") { + return true; + } + return false; + } // end of isMembersForumPostsPagePublic + + + + public function GetThreadVisibility($IdThread) { + $query = "SELECT ThreadVisibility FROM forums_threads WHERE id = " . intval($IdThread); + $s = $this->dao->query($query); + if (!$s) { + // Couldn't fetch the result from the DB assume 'MembersOnly' + return "MembersOnly"; + } + $row = $s->fetch(PDB::FETCH_OBJ) ; + return ($row->ThreadVisibility); + } + + public function GetPostVisibility($IdPost) { + $query = "SELECT PostVisibility FROM forums_posts WHERE id = " . intval($IdPost); + $s = $this->dao->query($query); + if (!$s) { + // Couldn't fetch the result from the DB assume 'MembersOnly' + return "MembersOnly"; + } + $row = $s->fetch(PDB::FETCH_OBJ) ; + return ($row->PostVisibility); + } + + public function GetGroupEntity($IdGroup) { + return $this->createEntity('Group')->findById($IdGroup); + } + + public function getTinyMCEPreference() { + $member = $this->getLoggedInMember(); + return $member->getPreference("PreferenceDisableTinyMCE", $default = "No"); + } + + /** + * @param $keywords Keywords to search for in the Sphinx index + * @return array + */ + public function searchForums($keywords, $currentPage, $items = 30) { + $hasForumModeratorRights = $this->BW_Right->HasRight("ForumModerator"); + + $results = ['count' => 0]; + $member = $this->getLoggedInMember(); + $groupEntities = $member->getGroups(); + $groups = [0]; + foreach ($groupEntities as $group) { + $groups[] = (int) $group->id; + } + + $config = ['host' => '127.0.0.1','port' => 9412]; + $client = new Client($config); + $query = new Search($client); + $query + ->setTable('forum_rt') + ->limit(1000) + ->sort(['post_id' => 'DESC']) + ; + + $matchQuery = new MatchPhrase($keywords, 'content'); + + $boolQuery = new BoolQuery(); + $boolQuery->must($matchQuery); + + if (!$hasForumModeratorRights) { + $groupPosts = new BoolQuery(); + $groupPosts + ->must(new In('group', $groups)) + ->must(new Equals('post_visibility', 'GroupOnly')) + ; + + $visibility = new BoolQuery(); + $visibility + ->should(new Equals('post_visibility', 'NoRestriction')) + ->should(new Equals('post_visibility', 'MembersOnly')) + ->should($groupPosts) + ; + + $publiclyVisiblePosts = new BoolQuery(); + $publiclyVisiblePosts + ->mustNot(new Equals('post_deleted', 'Deleted')) + ->mustNot(new Equals('thread_deleted', 'Deleted')) + ->must($visibility) + ; + + $limitQuery = new BoolQuery(); + $limitQuery + ->must($publiclyVisiblePosts) + ; + + $boolQuery->must($limitQuery); + } + + $query->search($boolQuery); + + $queryResult = $query->get(); + $queryResult->rewind(); + + $manticoreResult = []; + while ($queryResult->valid()) { + $hit = $queryResult->current(); + $data = $hit->getData(); + if ($hit->getScore() > 0 && !isset($manticoreResult[$data['post_id']])) { + $manticoreResult[$data['post_id']] = $data; + $manticoreResult[$data['post_id']]['score'] = $hit->getScore(); + } + $queryResult->next(); + } + + if (!empty($manticoreResult)) { + $languageId = $this->session->get('IdLanguage', 0); + $postIds = []; + foreach ($manticoreResult as $match) { + $postIds[] = $match['post_id']; + } + $separatedPostIds = implode(',', $postIds); + $separatedGroupIds = implode(',', $groups); + $offset = ($currentPage - 1) * $items; + $query = " + SELECT SQL_CALC_FOUND_ROWS + `forums_posts`.`id`, + `member`.`Username`, + `forums_posts`.`message`, + `forum_trads`.`Sentence`, + `forums_threads`.`id` AS `IdThread`, + `forums_threads`.`title`, + `forums_threads`.`IdGroup`, + `groups`.`Name` AS `GroupName`, + `forums_posts`.`PostVisibility`, + `forums_threads`.`ThreadVisibility`, + `forums_threads`.`ThreadDeleted`, + UNIX_TIMESTAMP(`forums_posts`.`create_time`) AS `created`, + geo__names.name AS city, + geonamescountries.name AS country + FROM + `forums_posts` + LEFT JOIN + `forums_threads` ON (`forums_posts`.`threadid` = `forums_threads`.`id`) + LEFT JOIN + `groups` ON (`groups`.`id` = `forums_threads`.`IdGroup`) + LEFT JOIN + `forum_trads` ON (`forum_trads`.`IdTrad` = `forums_posts`.`IdContent` AND `forum_trads`.`IdLanguage` = {$languageId}) + LEFT JOIN + `member` ON (`forums_posts`.`IdWriter` = `member`.`id`) + LEFT JOIN + `address` ON `member`.`id` = `address`.`member_id` and `address`.`active` = 1 + LEFT JOIN + `geo__names` ON `address`.location = `geo__names`.`geonameId` + LEFT JOIN + `geonamescountries` ON `geo__names`.`country` = `geonamescountries`.`country` + WHERE + `forums_posts`.`id` IN ({$separatedPostIds}) + AND (`forums_threads`.`IdGroup` IN ({$separatedGroupIds}) OR `forums_threads`.`IdGroup` IS NULL) + ORDER BY `created` DESC + LIMIT {$items} OFFSET {$offset} + "; + $posts = $this->bulkLookup($query); + $results['posts'] = $posts; + + $query = " + SELECT + count(`forums_posts`.`id`) AS `count` + FROM + `forums_posts` + LEFT JOIN `forums_threads` on `forums_posts`.`threadId` = `forums_threads`.`id` + WHERE + `forums_posts`.`id` IN ({$separatedPostIds}) + AND (`forums_threads`.`IdGroup` IN ({$separatedGroupIds}) OR `forums_threads`.`IdGroup` IS NULL) + "; + $count = $this->singleLookup($query); + $results['count'] = $count->count; + } else { + $results['errors'][] = 'ForumSearchNoResults'; + } + + return $results; + } + + /** + * Checks for correctness of search box vars (not empty) + * + * @param $vars + * @return bool + */ + private function _checkVarsSearch($vars) { + return true; + } + + /** + * Fetches matching threads/posts from the Sphinx index + * + * @return mixed Either false if there was a problem with the search box content or a list of matches. + */ + public function searchProcess() { + $User = $this->getLoggedInMember(); + if (!$User) { + return false; + } + + $vars =& PPostHandler::getVars(); + + $vars_ok = $this->_checkVarsSearch($vars); + if ($vars_ok) { + $keyword = htmlspecialchars((string) $vars['fs-keyword']); + PPostHandler::clearVars(); + return PVars::getObj('env')->baseuri.$this->forums_uri.'search/'. $keyword; + } + return false; + } + + private function addPostToManticoreIndex($vars, $postId, $threadId, $memberId, $languageId) + { + $language = $this->createEntity('Language')->findByid($languageId); + $host = PVars::getObj('env')->manticore_host; + $port = PVars::getObj('env')->manticore_port; + $config = ['host' => $host, 'port' => $port]; + $client = new Client($config); + $index = $client->table('forum_rt'); + $index->addDocument([ + 'post_id' => $postId, + 'post_deleted' => 'NotDeleted', + 'post_visibility' => $vars['PostVisibility'] ?? $vars['ThreadVisibility'], + 'thread_id' => $threadId, + 'thread_deleted' => 'NotDeleted', + 'thread_visibility' => $vars['ThreadVisibility'], + 'content' => $vars['topic_text'], + 'group' => $vars['group'] ?? 0, + 'author' => $memberId, + 'locale' => $language->getShortCode(), + ]); + } + + private function updatePostInManticoreIndex($vars) + { + $postId = (int)$vars['IdPost']; + $threadId = (int)$vars['IdThread']; + $config = ['host' => '127.0.0.1','port' => 9412]; + $vars['Sentence'] = $vars['Sentence'] ?? $vars['topic_text']; + + $client = new Client($config); + // Find document in index + $query = new Search($client); + $query + ->setTable('forum_rt') + ->filter('post_id', 'equals', $postId) + ->filter('thread_id', 'equals', $threadId) + ; + + $results = $query->get(); + $results->rewind(); + $hit = $results->current(); + $data = $hit->getData(); + $data['content'] = $vars['Sentence']; + + // Then replace with new content + $index = $client->table('forum_rt'); + $index->replaceDocument($data, $hit->getId()); + } +} // end of class Forums + + +class Topic { + public $topicinfo; + public $posts = []; +} + +class Board implements Iterator { + public $THREADS_PER_PAGE ; //Variable because it can change wether the user is logged or no + public $POSTS_PER_PAGE ; //Variable because it can change wether the user is logged or no + + public function __construct(&$dao, private $boardname, private $link, $session, private $board_description=false, $IdGroup=false, $no_forumsgroup=false) { + $this->THREADS_PER_PAGE=Forums::CV_THREADS_PER_PAGE ; //Variable because it can change wether the user is logged or no + $this->POSTS_PER_PAGE=Forums::CV_POSTS_PER_PAGE ; //Variable because it can change wether the user is logged or no + + $this->BW_Right = MOD_right::get(); + $this->session = $session; + + if (!$this->session->has( 'IdMember' )) { + $this->THREADS_PER_PAGE=100 ; // Variable because it can change wether the user is logged or no + $this->POSTS_PER_PAGE=self::CV_POSTS_PER_PAGE ; // Variable because it can change wether the user is logged or no + } + + $this->dao =& $dao; + $this->IdGroup = $IdGroup; + + $this->PublicThreadVisibility = "(ThreadVisibility!='ModeratorOnly') and (ThreadDeleted!='Deleted')"; + $this->PublicPostVisibility = " (PostDeleted!='Deleted')"; + + //if the member prefers to see only posts to his/her groups + $roxmodel = new RoxModelBase(); + $member = $roxmodel->getLoggedInMember(); + $owngroupsonly = $member->getPreference("ShowMyGroupsTopicsOnly", $default = "No"); + $this->owngroupsonly = $owngroupsonly; + if ($this->IdGroup === null) { + $this->PostGroupsRestriction = " ((PostVisibility IN ('MembersOnly','NoRestriction') or (PostVisibility = 'GroupOnly')) AND (IdGroup IS NULL))"; + $this->ThreadGroupsRestriction = " ((ThreadVisibility IN ('MembersOnly','NoRestriction') OR (ThreadVisibility = 'GroupOnly')) AND (IdGroup IS NULL))"; + } elseif ($this->IdGroup > 0) { + $this->PostGroupsRestriction = " (IdGroup= " . (int)$this->IdGroup . ") "; + $this->ThreadGroupsRestriction = " (IdGroup= " . (int)$this->IdGroup . ") "; + } else { + if ($owngroupsonly == "Yes" && ($this->IdGroup === false || !isset($this->IdGroup))) { + // 0 is the group id for topics without an explicit group, we don't want them in this case. Lazy hack to avoid changing more than necessary: replace 0 with -1 + $this->PostGroupsRestriction = " ((((IdGroup IN (-1"; + $this->ThreadGroupsRestriction = " ((((IdGroup IN (-1"; + } else { + $this->PostGroupsRestriction = " ((PostVisibility IN ('MembersOnly','NoRestriction') or (PostVisibility = 'GroupOnly' AND (IdGroup IS NULL OR IdGroup IN (0"; + $this->ThreadGroupsRestriction = " ((ThreadVisibility IN ('MembersOnly','NoRestriction') OR (ThreadVisibility = 'GroupOnly' AND (IdGroup IS NULL OR IdGroup IN (0"; + } + $query = "SELECT IdGroup FROM membersgroups WHERE IdMember=" . $this->session->get("IdMember") . " AND Status = 'In'"; + $qry = $this->dao->query($query); + if (!$qry) { + throw new PException('Failed to retrieve groups for member id =#' . $this->session->get("IdMember") . ' !'); + } + while ($rr = $qry->fetch(PDB::FETCH_OBJ)) { + $this->PostGroupsRestriction = $this->PostGroupsRestriction . "," . $rr->IdGroup; + $this->ThreadGroupsRestriction = $this->ThreadGroupsRestriction . "," . $rr->IdGroup; + } + + if ($no_forumsgroup) { + $this->PostGroupsRestriction = $this->PostGroupsRestriction . ")))) AND (NOT IdGroup IS NULL))"; + $this->ThreadGroupsRestriction = $this->ThreadGroupsRestriction . ")))) AND (NOT IdGroup IS NULL))"; + } else { + $this->PostGroupsRestriction = $this->PostGroupsRestriction . ")))) AND (IDGroup IS NULL))"; + $this->ThreadGroupsRestriction = $this->ThreadGroupsRestriction . ")))) AND (IDGroup IS NULL))"; + } + } + + // Prepares additional visibility options for moderator + if ($this->BW_Right->HasRight("ForumModerator")) { + $this->PublicPostVisibility = " PostVisibility IN ('NoRestriction', 'MembersOnly','GroupOnly','ModeratorOnly')"; + $this->PublicThreadVisibility = " ThreadVisibility IN ('NoRestriction', 'MembersOnly','GroupOnly','ModeratorOnly')"; + if ($this->BW_Right->HasRight("ForumModerator","AllGroups") or $this->BW_Right->HasRight("ForumModerator","All")) { + if ($IdGroup === null) { + $this->PostGroupsRestriction = " (IdGroup IS NULL)"; + $this->ThreadGroupsRestriction = " (IdGroup IS NULL)"; + } elseif ($no_forumsgroup) { + $this->PostGroupsRestriction = " (IdGroup != 0)"; + $this->ThreadGroupsRestriction = " (IdGroup != 0)"; + } + else { + $this->PostGroupsRestriction = " (1=1)"; + $this->ThreadGroupsRestriction = " (1=1)"; + } + } + } + } + + private $dao; + private $numberOfThreads; + private $totalThreads; + + /** + this filtres the list of thread results according to the presence of : + $this->IdGroup ; + */ + public function FilterThreadListResultsWithIdCriteria($ids = []) { + $wherethread="" ; + + if (count($ids) <> 0) { + $wherethread = " AND `forums_threads`.`id` in ('" . implode("', '", $ids) . "') "; + return $wherethread; + } + + if (isset($this->IdGroup)) { + if (is_numeric($this->IdGroup)) { + $wherethread .= sprintf("AND `forums_threads`.`IdGroup` = '%d' ", $this->IdGroup); + } elseif ($this->IdGroup === null) { + $wherethread .= "AND `forums_threads`.`IdGroup` IS NULL "; + } + } + $wherethread=$wherethread." AND (".$this->PublicThreadVisibility.")" ; + $wherethread=$wherethread." AND (".$this->ThreadGroupsRestriction.")" ; + return($wherethread) ; + } // end of FilterThreadListResultsWithIdCriteria + + /** + * Initializes the thread storages. + * + * If ids is set only threads with the given ids will be loaded + * + * @param int $page + * @param bool $showsticky + * @param array $ids + * @throws PException + */ + public function initThreads($page = 1, $showsticky = true, $ids = []) { + + $this->threads = []; + $wherethread=$this->FilterThreadListResultsWithIdCriteria($ids) ; + + if ($showsticky) { + $orderby = " ORDER BY `stickyvalue` ASC,`last_create_time` DESC"; + } else { + $orderby = " ORDER BY `last_create_time` DESC"; + } + + $query = "SELECT COUNT(*) AS `number` FROM `forums_threads` WHERE 1 ".$wherethread; + $s = $this->dao->query($query); + if (!$s) { + throw new PException('Could not retrieve Threads!'); + } + $row = $s->fetch(PDB::FETCH_OBJ); + $this->numberOfThreads = $row->number; + + if ($page == 0) { + $from = 0; + } else { + $from = $this->THREADS_PER_PAGE * ($page - 1); + } + + $query = "SELECT SQL_CALC_FOUND_ROWS `forums_threads`.`id`, + `forums_threads`.`id` as IdThread, `forums_threads`.`title`, + `forums_threads`.`IdTitle`, + `forums_threads`.`IdGroup`, + `forums_threads`.`replies`, + `forums_threads`.`stickyvalue`, + `groups`.`Name` as `GroupName`, + `ThreadVisibility`, + `ThreadDeleted`, + `forums_threads`.`views`, + `first`.`id` AS `first_postid`, + `first`.`idWriter` AS `first_authorid`, + UNIX_TIMESTAMP(`first`.`create_time`) AS `first_create_time`, + UNIX_TIMESTAMP(`last`.`create_time`) AS `last_create_time`, + `last`.`id` AS `last_postid`, + `last`.`idWriter` AS `last_authorid`, + UNIX_TIMESTAMP(`last`.`create_time`) AS `last_create_time`," ; + $query .= "`first_member`.`Username` AS `first_author`,`last_member`.`Username` AS `last_author`" ; + $query .= "FROM `forums_threads` LEFT JOIN `forums_posts` AS `first` ON (`forums_threads`.`first_postid` = `first`.`id`)" ; + $query .= "LEFT JOIN `groups` ON (`groups`.`id` = `forums_threads`.`IdGroup`)" ; + $query .= "LEFT JOIN `forums_posts` AS `last` ON last.id = + ( SELECT MAX(id) + FROM forums_posts fp + WHERE fp.threadid = `forums_threads`.`id` + AND fp.PostDeleted = 'NotDeleted' + ) "; + $query .= "LEFT JOIN `member` AS `first_member` ON (`first`.`IdWriter` = `first_member`.`id`)" ; + $query .= "LEFT JOIN `member` AS `last_member` ON (`last`.`IdWriter` = `last_member`.`id`)" ; + $query .= " WHERE 1 ".$wherethread . $orderby . " LIMIT ".$from.", ".$this->THREADS_PER_PAGE ; + + + $s = $this->dao->query($query); + if (!$s) { + throw new PException('Could not retrieve Threads!'); + } + while ($row = $s->fetch(PDB::FETCH_OBJ)) { + $this->threads[] = $row; + } + + $sFounRow = $this->dao->query("SELECT FOUND_ROWS() AS `found_rows`"); + if (!$sFounRow) { + throw new PException('Could not retrieve number of rows!'); + } + $rowFounRow = $sFounRow->fetch(PDB::FETCH_OBJ); + $this->totalThreads = $rowFounRow->found_rows; + + } // end of initThreads + + private $threads = []; + public function getThreads() { + return $this->threads; + } + public function getBoardName() { + return $this->boardname; + } + + public function getNumberOfThreads() { + return $this->numberOfThreads; + } + + public function getTotalThreads() { + return $this->totalThreads; + } + + private $subboards = []; + + // Add a subboard + public function add(Board $board) { + $this->subboards[] = $board; + } + + public function hasSubBoards() { + return (bool)(count($this->subboards) > 0); + } + + public function rewind() { + reset($this->subboards); + } + + public function current() { + $var = current($this->subboards); + return $var; + } + + public function key() { + $var = key($this->subboards); + return $var; + } + + public function next() { + $var = next($this->subboards); + return $var; + } + + public function valid() { + $var = $this->current() !== false; + return $var; + } +} diff --git a/composer.json b/composer.json index 39eca88f5b..b154704fe2 100644 --- a/composer.json +++ b/composer.json @@ -47,6 +47,8 @@ "nelmio/security-bundle": "3.*", "nesbot/carbon": "3.*", "pagerfanta/pagerfanta": "^v4.4.0", + "phpdocumentor/reflection-docblock": "^6.0", + "phpstan/phpdoc-parser": "^2.3", "ramsey/uuid-doctrine": "2.*", "stof/doctrine-extensions-bundle": "^1.7", "symfony/apache-pack": "^1.0", @@ -62,11 +64,15 @@ "symfony/mailer": "8.0.*", "symfony/monolog-bundle": "^4.0", "symfony/password-hasher": "8.0.*", + "symfony/property-access": "8.0.*", + "symfony/property-info": "8.0.*", "symfony/runtime": "8.0.*", "symfony/security-bundle": "8.0.*", + "symfony/serializer": "8.0.*", "symfony/stopwatch": "8.0.*", "symfony/translation": "8.0.*", "symfony/twig-bundle": "8.0.*", + "symfony/ux-translator": "*", "symfony/validator": "8.0.*", "symfony/var-dumper": "8.0.*", "symfony/web-link": "8.0.*", diff --git a/composer.lock b/composer.lock index 5e52d374d4..53f6fa15e2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e16fe984ba2609e153b8129766d157e4", + "content-hash": "d64459008fa158b9840d59a5ab816e53", "packages": [ { "name": "amphp/amp", @@ -1523,16 +1523,16 @@ }, { "name": "doctrine/collections", - "version": "2.5.0", + "version": "2.6.0", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "6108e0cd57d7ef125fb84696346a68860403a25d" + "reference": "7713da39d8e237f28411d6a616a3dce5e20d5de2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/6108e0cd57d7ef125fb84696346a68860403a25d", - "reference": "6108e0cd57d7ef125fb84696346a68860403a25d", + "url": "https://api.github.com/repos/doctrine/collections/zipball/7713da39d8e237f28411d6a616a3dce5e20d5de2", + "reference": "7713da39d8e237f28411d6a616a3dce5e20d5de2", "shasum": "" }, "require": { @@ -1589,7 +1589,7 @@ ], "support": { "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/2.5.0" + "source": "https://github.com/doctrine/collections/tree/2.6.0" }, "funding": [ { @@ -1605,7 +1605,7 @@ "type": "tidelift" } ], - "time": "2026-01-07T17:26:56+00:00" + "time": "2026-01-15T10:01:58+00:00" }, { "name": "doctrine/dbal", @@ -1963,16 +1963,16 @@ }, { "name": "doctrine/event-manager", - "version": "2.0.1", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e" + "reference": "c07799fcf5ad362050960a0fd068dded40b1e312" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e", - "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/c07799fcf5ad362050960a0fd068dded40b1e312", + "reference": "c07799fcf5ad362050960a0fd068dded40b1e312", "shasum": "" }, "require": { @@ -1982,10 +1982,10 @@ "doctrine/common": "<2.9" }, "require-dev": { - "doctrine/coding-standard": "^12", - "phpstan/phpstan": "^1.8.8", - "phpunit/phpunit": "^10.5", - "vimeo/psalm": "^5.24" + "doctrine/coding-standard": "^14", + "phpdocumentor/guides-cli": "^1.4", + "phpstan/phpstan": "^2.1.32", + "phpunit/phpunit": "^10.5.58" }, "type": "library", "autoload": { @@ -2034,7 +2034,7 @@ ], "support": { "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/2.0.1" + "source": "https://github.com/doctrine/event-manager/tree/2.1.0" }, "funding": [ { @@ -2050,7 +2050,7 @@ "type": "tidelift" } ], - "time": "2024-05-22T20:47:39+00:00" + "time": "2026-01-17T22:40:21+00:00" }, { "name": "doctrine/inflector", @@ -4252,16 +4252,16 @@ }, { "name": "laminas/laminas-validator", - "version": "3.12.0", + "version": "3.13.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-validator.git", - "reference": "eb972023dfdad6ba87482ccaa3c9e937686ea759" + "reference": "dac5010a16447c69f38687aff09d1bc0e2a3445f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/eb972023dfdad6ba87482ccaa3c9e937686ea759", - "reference": "eb972023dfdad6ba87482ccaa3c9e937686ea759", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/dac5010a16447c69f38687aff09d1bc0e2a3445f", + "reference": "dac5010a16447c69f38687aff09d1bc0e2a3445f", "shasum": "" }, "require": { @@ -4325,7 +4325,7 @@ "type": "community_bridge" } ], - "time": "2025-12-15T10:21:07+00:00" + "time": "2026-01-20T11:13:00+00:00" }, { "name": "league/html-to-markdown", @@ -4415,20 +4415,20 @@ }, { "name": "league/uri", - "version": "7.7.0", + "version": "7.8.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807" + "reference": "4436c6ec8d458e4244448b069cc572d088230b76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/8d587cddee53490f9b82bf203d3a9aa7ea4f9807", - "reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/4436c6ec8d458e4244448b069cc572d088230b76", + "reference": "4436c6ec8d458e4244448b069cc572d088230b76", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.7", + "league/uri-interfaces": "^7.8", "php": "^8.1", "psr/http-factory": "^1" }, @@ -4442,11 +4442,11 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "ext-uri": "to use the PHP native URI class", - "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", - "league/uri-components": "Needed to easily manipulate URI objects components", - "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", + "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain", + "league/uri-components": "to provide additional tools to manipulate URI objects components", + "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP", "php-64bit": "to improve IPV4 host parsing", - "rowbot/url": "to handle WHATWG URL", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -4501,7 +4501,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.7.0" + "source": "https://github.com/thephpleague/uri/tree/7.8.0" }, "funding": [ { @@ -4509,37 +4509,36 @@ "type": "github" } ], - "time": "2025-12-07T16:02:06+00:00" + "time": "2026-01-14T17:24:56+00:00" }, { "name": "league/uri-components", - "version": "7.7.0", + "version": "7.8.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-components.git", - "reference": "005f8693ce8c1f16f80e88a05cbf08da04c1c374" + "reference": "8b5ffcebcc0842b76eb80964795bd56a8333b2ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-components/zipball/005f8693ce8c1f16f80e88a05cbf08da04c1c374", - "reference": "005f8693ce8c1f16f80e88a05cbf08da04c1c374", + "url": "https://api.github.com/repos/thephpleague/uri-components/zipball/8b5ffcebcc0842b76eb80964795bd56a8333b2ba", + "reference": "8b5ffcebcc0842b76eb80964795bd56a8333b2ba", "shasum": "" }, "require": { - "league/uri": "^7.7", + "league/uri": "^7.8", "php": "^8.1" }, "suggest": { - "bakame/aide-uri": "A polyfill for PHP8.1 until PHP8.4 to add support to PHP Native URI parser", "ext-bcmath": "to improve IPV4 host parsing", "ext-fileinfo": "to create Data URI from file contennts", "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "ext-mbstring": "to use the sorting algorithm of URLSearchParams", - "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", - "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", + "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain", + "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP", "php-64bit": "to improve IPV4 host parsing", - "rowbot/url": "to handle WHATWG URL", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -4586,7 +4585,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-components/tree/7.7.0" + "source": "https://github.com/thephpleague/uri-components/tree/7.8.0" }, "funding": [ { @@ -4594,20 +4593,20 @@ "type": "github" } ], - "time": "2025-12-07T16:02:56+00:00" + "time": "2026-01-14T17:24:56+00:00" }, { "name": "league/uri-interfaces", - "version": "7.7.0", + "version": "7.8.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c" + "reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/62ccc1a0435e1c54e10ee6022df28d6c04c2946c", - "reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/c5c5cd056110fc8afaba29fa6b72a43ced42acd4", + "reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4", "shasum": "" }, "require": { @@ -4620,7 +4619,7 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "php-64bit": "to improve IPV4 host parsing", - "rowbot/url": "to handle WHATWG URL", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -4670,7 +4669,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.7.0" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.0" }, "funding": [ { @@ -4678,7 +4677,7 @@ "type": "github" } ], - "time": "2025-12-07T16:03:21+00:00" + "time": "2026-01-15T06:54:53+00:00" }, { "name": "lorenzo/pinky", @@ -5130,16 +5129,16 @@ }, { "name": "nelmio/cors-bundle", - "version": "2.6.0", + "version": "2.6.1", "source": { "type": "git", "url": "https://github.com/nelmio/NelmioCorsBundle.git", - "reference": "530217472204881cacd3671909f634b960c7b948" + "reference": "3d80dbcd5d1eb5f8b20ed5199e1778d44c2e4d1c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/530217472204881cacd3671909f634b960c7b948", - "reference": "530217472204881cacd3671909f634b960c7b948", + "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/3d80dbcd5d1eb5f8b20ed5199e1778d44c2e4d1c", + "reference": "3d80dbcd5d1eb5f8b20ed5199e1778d44c2e4d1c", "shasum": "" }, "require": { @@ -5189,22 +5188,22 @@ ], "support": { "issues": "https://github.com/nelmio/NelmioCorsBundle/issues", - "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.6.0" + "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.6.1" }, - "time": "2025-10-23T06:57:22+00:00" + "time": "2026-01-12T15:59:08+00:00" }, { "name": "nelmio/security-bundle", - "version": "v3.7.0", + "version": "v3.8.0", "source": { "type": "git", "url": "https://github.com/nelmio/NelmioSecurityBundle.git", - "reference": "9389ec28cd219d621d3d91c840a3df6f04c9f651" + "reference": "2fafee1cdda1d5952554c44eef4c3c8566d56f40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nelmio/NelmioSecurityBundle/zipball/9389ec28cd219d621d3d91c840a3df6f04c9f651", - "reference": "9389ec28cd219d621d3d91c840a3df6f04c9f651", + "url": "https://api.github.com/repos/nelmio/NelmioSecurityBundle/zipball/2fafee1cdda1d5952554c44eef4c3c8566d56f40", + "reference": "2fafee1cdda1d5952554c44eef4c3c8566d56f40", "shasum": "" }, "require": { @@ -5263,9 +5262,9 @@ ], "support": { "issues": "https://github.com/nelmio/NelmioSecurityBundle/issues", - "source": "https://github.com/nelmio/NelmioSecurityBundle/tree/v3.7.0" + "source": "https://github.com/nelmio/NelmioSecurityBundle/tree/v3.8.0" }, - "time": "2025-12-30T14:05:13+00:00" + "time": "2026-01-14T19:38:55+00:00" }, { "name": "nesbot/carbon", @@ -5660,6 +5659,229 @@ }, "time": "2024-10-02T11:20:13+00:00" }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "2f5cbed597cb261d1ea458f3da3a9ad32e670b1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/2f5cbed597cb261d1ea458f3da3a9ad32e670b1e", + "reference": "2f5cbed597cb261d1ea458f3da3a9ad32e670b1e", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^2.0", + "phpstan/phpdoc-parser": "^2.0", + "webmozart/assert": "^1.9.1 || ^2" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26", + "shipmonk/dead-code-detector": "^0.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/6.0.1" + }, + "time": "2026-01-20T15:30:42+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/327a05bbee54120d4786a0dc67aad30226ad4cf9", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/2.0.0" + }, + "time": "2026-01-06T21:53:42+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "16dbf9937da8d4528ceb2145c9c7c0bd29e26374" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/16dbf9937da8d4528ceb2145c9c7c0bd29e26374", + "reference": "16dbf9937da8d4528ceb2145c9c7c0bd29e26374", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.1" + }, + "time": "2026-01-12T11:33:04+00:00" + }, { "name": "psr/cache", "version": "3.0.0", @@ -10836,6 +11058,101 @@ ], "time": "2025-12-21T10:59:45+00:00" }, + { + "name": "symfony/serializer", + "version": "v8.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/serializer.git", + "reference": "66a9ab0146fb6aa6ac7abcc3b09b1a0c2799303a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/serializer/zipball/66a9ab0146fb6aa6ac7abcc3b09b1a0c2799303a", + "reference": "66a9ab0146fb6aa6ac7abcc3b09b1a0c2799303a", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "seld/jsonlint": "^1.10", + "symfony/cache": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/filesystem": "^7.4|^8.0", + "symfony/form": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/property-info": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/type-info": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0", + "symfony/var-exporter": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Serializer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/serializer/tree/v8.0.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-23T14:52:06+00:00" + }, { "name": "symfony/service-contracts", "version": "v3.6.1", @@ -11526,6 +11843,87 @@ ], "time": "2025-12-05T14:08:45+00:00" }, + { + "name": "symfony/ux-translator", + "version": "v2.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/ux-translator.git", + "reference": "fde719a87903d9bc6fe60abf7581c1143532c918" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ux-translator/zipball/fde719a87903d9bc6fe60abf7581c1143532c918", + "reference": "fde719a87903d9bc6fe60abf7581c1143532c918", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/console": "^5.4|^6.0|^7.0|^8.0", + "symfony/filesystem": "^5.4|^6.0|^7.0|^8.0", + "symfony/string": "^5.4|^6.0|^7.0|^8.0", + "symfony/translation": "^5.4|^6.0|^7.0|^8.0" + }, + "require-dev": { + "symfony/framework-bundle": "^5.4|^6.0|^7.0|^8.0", + "symfony/phpunit-bridge": "^5.2|^6.0|^7.0|^8.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0|^8.0", + "symfony/yaml": "^5.4|^6.0|^7.0|^8.0" + }, + "type": "symfony-bundle", + "extra": { + "thanks": { + "url": "https://github.com/symfony/ux", + "name": "symfony/ux" + } + }, + "autoload": { + "psr-4": { + "Symfony\\UX\\Translator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Hugo Alliaume", + "email": "hugo@alliau.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Exposes Symfony Translations directly to JavaScript.", + "homepage": "https://symfony.com", + "keywords": [ + "symfony-ux" + ], + "support": { + "source": "https://github.com/symfony/ux-translator/tree/v2.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-26T17:37:51+00:00" + }, { "name": "symfony/validator", "version": "v8.0.3", @@ -12754,16 +13152,16 @@ }, { "name": "webmozart/assert", - "version": "2.1.1", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "bdbabc199a7ba9965484e4725d66170e5711323b" + "reference": "ce6a2f100c404b2d32a1dd1270f9b59ad4f57649" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/bdbabc199a7ba9965484e4725d66170e5711323b", - "reference": "bdbabc199a7ba9965484e4725d66170e5711323b", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/ce6a2f100c404b2d32a1dd1270f9b59ad4f57649", + "reference": "ce6a2f100c404b2d32a1dd1270f9b59ad4f57649", "shasum": "" }, "require": { @@ -12810,9 +13208,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/2.1.1" + "source": "https://github.com/webmozarts/assert/tree/2.1.2" }, - "time": "2026-01-08T11:28:40+00:00" + "time": "2026-01-13T14:02:24+00:00" }, { "name": "willdurand/negotiation", @@ -13368,34 +13766,34 @@ }, { "name": "dama/doctrine-test-bundle", - "version": "v8.4.1", + "version": "v8.5.0", "source": { "type": "git", "url": "https://github.com/dmaicher/doctrine-test-bundle.git", - "reference": "d9f4fb01a43da2e279ca190fa25ab9e26f15a0c8" + "reference": "490cdc0df04e2b9f3c4dab93c8302521b1019f6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/d9f4fb01a43da2e279ca190fa25ab9e26f15a0c8", - "reference": "d9f4fb01a43da2e279ca190fa25ab9e26f15a0c8", + "url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/490cdc0df04e2b9f3c4dab93c8302521b1019f6f", + "reference": "490cdc0df04e2b9f3c4dab93c8302521b1019f6f", "shasum": "" }, "require": { "doctrine/dbal": "^3.3 || ^4.0", "doctrine/doctrine-bundle": "^2.11.0 || ^3.0", - "php": ">= 8.1", + "php": ">= 8.2", "psr/cache": "^2.0 || ^3.0", "symfony/cache": "^6.4 || ^7.3 || ^8.0", "symfony/framework-bundle": "^6.4 || ^7.3 || ^8.0" }, "conflict": { - "phpunit/phpunit": "<10.0" + "phpunit/phpunit": "<11.0" }, "require-dev": { "behat/behat": "^3.0", "friendsofphp/php-cs-fixer": "^3.27", "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^10.5.57 || ^11.5.41|| ^12.3.14", + "phpunit/phpunit": "^11.5.41|| ^12.3.14", "symfony/dotenv": "^6.4 || ^7.3 || ^8.0", "symfony/process": "^6.4 || ^7.3 || ^8.0" }, @@ -13431,9 +13829,9 @@ ], "support": { "issues": "https://github.com/dmaicher/doctrine-test-bundle/issues", - "source": "https://github.com/dmaicher/doctrine-test-bundle/tree/v8.4.1" + "source": "https://github.com/dmaicher/doctrine-test-bundle/tree/v8.5.0" }, - "time": "2025-12-07T21:48:15+00:00" + "time": "2026-01-18T12:00:00+00:00" }, { "name": "doctrine/data-fixtures", @@ -14035,16 +14433,16 @@ }, { "name": "infection/infection", - "version": "0.32.2", + "version": "0.32.3", "source": { "type": "git", "url": "https://github.com/infection/infection.git", - "reference": "df90353784ab0505f07502770f59f8fabef24d8c" + "reference": "3654db483619b63b9bcb04c24caeb03677c6d057" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/infection/infection/zipball/df90353784ab0505f07502770f59f8fabef24d8c", - "reference": "df90353784ab0505f07502770f59f8fabef24d8c", + "url": "https://api.github.com/repos/infection/infection/zipball/3654db483619b63b9bcb04c24caeb03677c6d057", + "reference": "3654db483619b63b9bcb04c24caeb03677c6d057", "shasum": "" }, "require": { @@ -14155,7 +14553,7 @@ ], "support": { "issues": "https://github.com/infection/infection/issues", - "source": "https://github.com/infection/infection/tree/0.32.2" + "source": "https://github.com/infection/infection/tree/0.32.3" }, "funding": [ { @@ -14167,7 +14565,7 @@ "type": "open_collective" } ], - "time": "2026-01-07T13:28:58+00:00" + "time": "2026-01-13T14:23:38+00:00" }, { "name": "infection/mutator", @@ -14699,11 +15097,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.33", + "version": "2.1.34", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f", - "reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/070ba754a949fcade788e16e8dc5a5935b7cf2ee", + "reference": "070ba754a949fcade788e16e8dc5a5935b7cf2ee", "shasum": "" }, "require": { @@ -14748,25 +15146,25 @@ "type": "github" } ], - "time": "2025-12-05T10:24:31+00:00" + "time": "2026-01-19T19:52:16+00:00" }, { "name": "phpstan/phpstan-doctrine", - "version": "2.0.12", + "version": "2.0.13", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-doctrine.git", - "reference": "d20ee0373d22735271f1eb4d631856b5f847d399" + "reference": "2d2ad04a0ac14ac52e21ad47ec67a54a14355c1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/d20ee0373d22735271f1eb4d631856b5f847d399", - "reference": "d20ee0373d22735271f1eb4d631856b5f847d399", + "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/2d2ad04a0ac14ac52e21ad47ec67a54a14355c1f", + "reference": "2d2ad04a0ac14ac52e21ad47ec67a54a14355c1f", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.1.13" + "phpstan/phpstan": "^2.1.34" }, "conflict": { "doctrine/collections": "<1.0", @@ -14819,9 +15217,9 @@ "description": "Doctrine extensions for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-doctrine/issues", - "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.12" + "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.13" }, - "time": "2025-12-01T11:34:02+00:00" + "time": "2026-01-18T16:15:40+00:00" }, { "name": "phpstan/phpstan-mockery", @@ -14874,16 +15272,16 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "2.0.9", + "version": "2.0.10", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "24d8c157aa483141b0579d705ef0aac9e1b95436" + "reference": "5a7ab5319a0b0d856ddbe08f67a21b00b386107f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/24d8c157aa483141b0579d705ef0aac9e1b95436", - "reference": "24d8c157aa483141b0579d705ef0aac9e1b95436", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/5a7ab5319a0b0d856ddbe08f67a21b00b386107f", + "reference": "5a7ab5319a0b0d856ddbe08f67a21b00b386107f", "shasum": "" }, "require": { @@ -14939,9 +15337,9 @@ "description": "Symfony Framework extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-symfony/issues", - "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.9" + "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.10" }, - "time": "2025-11-29T11:17:28+00:00" + "time": "2026-01-20T16:40:28+00:00" }, { "name": "phpunit/php-code-coverage", @@ -15279,16 +15677,16 @@ }, { "name": "phpunit/phpunit", - "version": "12.5.4", + "version": "12.5.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "4ba0e923f9d3fc655de22f9547c01d15a41fc93a" + "reference": "ab8e4374264bc65523d1458d14bf80261577e01f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4ba0e923f9d3fc655de22f9547c01d15a41fc93a", - "reference": "4ba0e923f9d3fc655de22f9547c01d15a41fc93a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ab8e4374264bc65523d1458d14bf80261577e01f", + "reference": "ab8e4374264bc65523d1458d14bf80261577e01f", "shasum": "" }, "require": { @@ -15302,7 +15700,7 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.3", - "phpunit/php-code-coverage": "^12.5.1", + "phpunit/php-code-coverage": "^12.5.2", "phpunit/php-file-iterator": "^6.0.0", "phpunit/php-invoker": "^6.0.0", "phpunit/php-text-template": "^5.0.0", @@ -15356,7 +15754,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.4" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.6" }, "funding": [ { @@ -15380,7 +15778,7 @@ "type": "tidelift" } ], - "time": "2025-12-15T06:05:34+00:00" + "time": "2026-01-16T16:28:10+00:00" }, { "name": "react/cache", @@ -15910,21 +16308,21 @@ }, { "name": "rector/rector", - "version": "2.3.0", + "version": "2.3.2", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "f7166355dcf47482f27be59169b0825995f51c7d" + "reference": "07cbbe28bd60251b96b18d42e514779b0e2faa83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/f7166355dcf47482f27be59169b0825995f51c7d", - "reference": "f7166355dcf47482f27be59169b0825995f51c7d", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/07cbbe28bd60251b96b18d42e514779b0e2faa83", + "reference": "07cbbe28bd60251b96b18d42e514779b0e2faa83", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.1.33" + "phpstan/phpstan": "^2.1.34" }, "conflict": { "rector/rector-doctrine": "*", @@ -15958,7 +16356,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.3.0" + "source": "https://github.com/rectorphp/rector/tree/2.3.2" }, "funding": [ { @@ -15966,20 +16364,20 @@ "type": "github" } ], - "time": "2025-12-25T22:00:18+00:00" + "time": "2026-01-20T01:11:51+00:00" }, { "name": "sanmai/di-container", - "version": "0.1.9", + "version": "0.1.11", "source": { "type": "git", "url": "https://github.com/sanmai/di-container.git", - "reference": "bde5001bec7942179b4a3816cf09534edf66c0fc" + "reference": "4723e235e04589f88c0a114a4e438ff57a7cbb8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sanmai/di-container/zipball/bde5001bec7942179b4a3816cf09534edf66c0fc", - "reference": "bde5001bec7942179b4a3816cf09534edf66c0fc", + "url": "https://api.github.com/repos/sanmai/di-container/zipball/4723e235e04589f88c0a114a4e438ff57a7cbb8a", + "reference": "4723e235e04589f88c0a114a4e438ff57a7cbb8a", "shasum": "" }, "require": { @@ -15990,14 +16388,18 @@ "require-dev": { "ergebnis/composer-normalize": "^2.8", "friendsofphp/php-cs-fixer": "^3.17", - "infection/infection": ">=0.29", + "infection/infection": ">=0.31", "php-coveralls/php-coveralls": "^2.4.1", + "phpbench/phpbench": "^1.4", "phpstan/extension-installer": "^1.4", "phpunit/phpunit": "^11.5.25", "sanmai/phpstan-rules": "^0.3.10" }, "type": "library", "extra": { + "branch-alias": { + "dev-main": "0.1.x-dev" + }, "preferred-install": "dist" }, "autoload": { @@ -16033,7 +16435,7 @@ ], "support": { "issues": "https://github.com/sanmai/di-container/issues", - "source": "https://github.com/sanmai/di-container/tree/0.1.9" + "source": "https://github.com/sanmai/di-container/tree/0.1.11" }, "funding": [ { @@ -16041,7 +16443,7 @@ "type": "github" } ], - "time": "2026-01-12T12:09:00+00:00" + "time": "2026-01-13T07:38:17+00:00" }, { "name": "sanmai/duoclock", @@ -16172,16 +16574,16 @@ }, { "name": "sanmai/pipeline", - "version": "7.7", + "version": "7.9", "source": { "type": "git", "url": "https://github.com/sanmai/pipeline.git", - "reference": "9341ac243258ff16259b0edc6654af5583fa8743" + "reference": "d7046ecce91ae57fca403be694888371a21250eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sanmai/pipeline/zipball/9341ac243258ff16259b0edc6654af5583fa8743", - "reference": "9341ac243258ff16259b0edc6654af5583fa8743", + "url": "https://api.github.com/repos/sanmai/pipeline/zipball/d7046ecce91ae57fca403be694888371a21250eb", + "reference": "d7046ecce91ae57fca403be694888371a21250eb", "shasum": "" }, "require": { @@ -16191,7 +16593,7 @@ "ergebnis/composer-normalize": "^2.8", "esi/phpunit-coverage-check": ">2", "friendsofphp/php-cs-fixer": "^3.17", - "infection/infection": ">=0.30.3", + "infection/infection": ">=0.32.3", "league/pipeline": "^0.3 || ^1.0", "php-coveralls/php-coveralls": "^2.4.1", "phpstan/extension-installer": "^1.4", @@ -16228,7 +16630,7 @@ "description": "General-purpose collections pipeline", "support": { "issues": "https://github.com/sanmai/pipeline/issues", - "source": "https://github.com/sanmai/pipeline/tree/7.7" + "source": "https://github.com/sanmai/pipeline/tree/7.9" }, "funding": [ { @@ -16236,7 +16638,7 @@ "type": "github" } ], - "time": "2026-01-09T10:16:17+00:00" + "time": "2026-01-16T11:54:05+00:00" }, { "name": "sebastian/cli-parser", @@ -17704,5 +18106,5 @@ "platform-overrides": { "php": "8.4" }, - "plugin-api-version": "2.9.0" + "plugin-api-version": "2.6.0" } diff --git a/config/bundles.php b/config/bundles.php index 7a67129fd8..300a4e1441 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -23,4 +23,5 @@ Nelmio\SecurityBundle\NelmioSecurityBundle::class => ['all' => true], BabDev\PagerfantaBundle\BabDevPagerfantaBundle::class => ['all' => true], Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true], + Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true], ]; diff --git a/config/packages/ux_translator.yaml b/config/packages/ux_translator.yaml new file mode 100644 index 0000000000..66091c7b27 --- /dev/null +++ b/config/packages/ux_translator.yaml @@ -0,0 +1,11 @@ +ux_translator: + # The directory where the JavaScript translations are dumped + dump_directory: '%kernel.project_dir%/var/translations' + keys_patterns: + - hosting_interest.* + +when@prod: + ux_translator: + # Control whether TypeScript types are dumped alongside translations. + # Disable this if you do not use TypeScript (e.g. in production when using AssetMapper), to speed up cache warmup. + dump_typescript: false diff --git a/package.json b/package.json index ba29e3609c..6a5574cf3a 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@fontsource/signika": "5.*", "@fortawesome/fontawesome-free": "6.*", "@popperjs/core": "^2.11.8", + "@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets", "@symfony/webpack-encore": "5.*", "@tailwindcss/forms": "^0.5", "@tomickigrzegorz/autocomplete": "^3", @@ -37,6 +38,7 @@ "gsap": "^3.7.1", "imagesloaded": "^4.1.4", "improved-yarn-audit": "^2.3.3", + "intl-messageformat": "^10.5.11", "jarallax": "^1.12.4", "jquery": "^3.6.0", "jquery-mousewheel": "^3.1.13", @@ -97,6 +99,7 @@ "scroll-lock": "^2.1.5", "show-more-read": "tomik23/show-more", "tom-select": "2.*", + "ux-translator": "symfony/ux-translator", "vanilla-calendar-pro": "^3.0", "webpack": "5.*" } diff --git a/roxlauncher/roxfrontroutermodel.php b/roxlauncher/roxfrontroutermodel.php index 12c9d3cfb8..3489ebb6f1 100644 --- a/roxlauncher/roxfrontroutermodel.php +++ b/roxlauncher/roxfrontroutermodel.php @@ -17,26 +17,14 @@ function getLanguage($langcode = false) { if (!$langcode) { return false; - } else { - if (is_numeric($langcode)) { - return $this->singleLookup(" - SELECT - languages.id AS id, - languages.ShortCode AS ShortCode - FROM - languages - WHERE - languages.id = '" . $this->dao->escape($langcode) . "'"); - } else { - return $this->singleLookup(" - SELECT - languages.id AS id, - languages.ShortCode AS ShortCode - FROM - languages - WHERE - languages.ShortCode = '" . $this->dao->escape($langcode) . "'"); - } } + + return $this->singleLookup(" + SELECT + language.ShortCode AS ShortCode + FROM + language + WHERE + language.ShortCode = '" . $this->dao->escape($langcode) . "'"); } } diff --git a/src/Command/MigrateMembersCommand.php b/src/Command/MigrateMembersCommand.php index 7a222798dc..78267b8e82 100644 --- a/src/Command/MigrateMembersCommand.php +++ b/src/Command/MigrateMembersCommand.php @@ -6,6 +6,7 @@ use App\Doctrine\StandardOffersType; use App\Doctrine\TypicalOfferType; use App\Entity\Language; +use App\Entity\Languages; use App\Entity\Location; use App\Entity\Member; use DateTime; @@ -20,11 +21,15 @@ use Symfony\Component\Console\Style\SymfonyStyle; #[AsCommand( - name: 'migrate:members', - description: 'Migrates the old profile (members and memberstrads table) to the new profile tables (member and member_translations)', + name: 'migrate:database', + description: 'Migrates the existing production database to the new table layout', )] class MigrateMembersCommand extends Command { + private const int MEMBER_FIRSTNAME_HIDDEN = 1; + private const int MEMBER_SECONDNAME_HIDDEN = 2; + private const int MEMBER_LASTNAME_HIDDEN = 4; + private const array TRANSLATED_FIELDS = [ 'Occupation', 'ILiveWith', @@ -61,6 +66,7 @@ class MigrateMembersCommand extends Command private const string INSERT = <<<'SQL' INSERT INTO member ( id, + Locale, Username, Password, Name, @@ -68,67 +74,76 @@ class MigrateMembersCommand extends Command Status, Email, Gender, - Accommodation, - MaxGuests, HideAttribute, bewelcomed, BirthDate, - GenderOfGuests, - HostingInterest, - Reminders, - LastActivity, + LastActive, LastSwitchToActive, + Reminders, created, updated, RegistrationKey, + Accommodation, + MaxGuests, + HostingInterest, StandardOffers, - Restrictions, + /* Translated fields are set to null */ Occupation, + PleaseBring, + OfferGuests, + OfferHosts, + GettingThere, ILiveWith, MaxLengthOfStay, - Organizations, - AdditionalAccommodationInfo, + Restrictions, HouseRules, - ProfileLanguage, - AboutMe, Hobbies, Books, - Music, Movies, - PleaseBring, - OfferGuests, - OfferHosts, - GettingThere, + Music, + Organizations, PastTrips, - PlannedTrips + PlannedTrips ) VALUES ( - :id, - :Username, - :Password, - :Name, - :ShortName, - :Status, - :Email, - :Gender, - :Accommodation, - :MaxGuests, - :HideAttribute, - :bewelcomed, + :id, + :Locale, + :Username, + :Password, + :Name, + :ShortName, + :Status, + :Email, + :Gender, + :HideAttribute, + :bewelcomed, :BirthDate, - :GenderOfGuests, - :HostingInterest, - :Reminders, - :LastLogin, + :LastActive, :LastSwitchToActive, + :Reminders, :created, :updated, :RegistrationKey, + :Accommodation, + :MaxGuests, + :HostingInterest, :StandardOffers, - :Restrictions, - null, null, null, null, null, - null, null, null, null, null, - null, null, null, null, null, - null, null, null + /* Translated fields are set to null */ + null /* :Occupation*/, + null /* :PleaseBring*/, + null /* :OfferGuests*/, + null /* :OfferHosts*/, + null /* :GettingThere*/, + null /* :ILiveWith*/, + null /* :MaxLengthOfStay*/, + null /* :Restrictions*/, + null /* :HouseRules*/, + null /* :Hobbies*/, + null /* :Books*/, + null /* :Movies*/, + null /* :Music*/, + null /* :Organizations*/, + null /* :PastTrips*/, + null /* :PlannedTrips */ ) SQL; @@ -158,8 +173,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int // load members in chunks of 100 $locationRepository = $this->entityManager->getRepository(Location::class); - $languageRepository = $this->entityManager->getRepository(Language::class); - $rawLanguages = $languageRepository->findAll(); + $languagesRepository = $this->entityManager->getRepository(Languages::class); + $rawLanguages = $languagesRepository->findAll(); $languages = []; foreach ($rawLanguages as $language) { $languages[$language->getId()] = $language; @@ -179,202 +194,296 @@ protected function execute(InputInterface $input, OutputInterface $output): int // Make sure the tables member and member_translations are empty - $this->connection->executeStatement('SET FOREIGN_KEY_CHECKS=0; TRUNCATE `member`; TRUNCATE `member_translations`; SET FOREIGN_KEY_CHECKS=1;'); + try { + $this->connection->executeStatement('SET FOREIGN_KEY_CHECKS=0;'); + $this->connection->executeStatement('TRUNCATE `address`; TRUNCATE `member`; TRUNCATE `member_translations`; TRUNCATE `member_language_level`; TRUNCATE `relation`;'); + gc_enable(); - for ($i = 0; $i < $countOfMembers; $i += $batchSize) { - $query = $this->connection->executeQuery("SELECT * FROM members m WHERE m.status IN ('" . implode("', '", self::MIGRATED_STATUSES) . "') ORDER BY ID LIMIT " . $i . ",$batchSize"); - $members = $query->fetchAllAssociative(); + for ($i = 0; $i < $countOfMembers; $i += $batchSize) { + $query = $this->connection->executeQuery("SELECT * FROM members m WHERE m.status IN ('" . implode("', '", self::MIGRATED_STATUSES) . "') ORDER BY ID LIMIT " . $i . ",$batchSize"); + $members = $query->fetchAllAssociative(); - foreach ($members as $member) { - // First non translated elements - $memberId = $member['id']; + gc_collect_cycles(); + foreach ($members as $member) { + // First non translated elements + $memberId = $member['id']; - $locale = $this->connection->executeQuery('SELECT mp.Value FROM memberspreferences mp WHERE mp.IdMember = :memberId AND mp.IdPreference = 1', ['memberId' => $memberId])->fetchOne(); - if (is_numeric($locale)) { - $locale = $this->connection->executeQuery('SELECT l.ShortCode FROM memberspreferences mp, languages l WHERE mp.IdMember = :memberId AND mp.IdPreference = 1 and mp.Value = l.id', ['memberId' => $memberId])->fetchOne(); - } - $statement = $this->connection->prepare(self::INSERT); - $statement->bindValue('id', $memberId); - $statement->bindValue('Locale', false === $locale ? 'en' : $locale); - $statement->bindValue('Username', $member['Username']); - $statement->bindValue('Password', $member['PassWord']); - $statement->bindValue('Name', $this->getFullname($member)); - $statement->bindValue('ShortName', $this->isFirstnameShown($member) ? $member['FirstName'] : null); - $statement->bindValue('Status', $member['Status']); - $statement->bindValue('Email', $member['Email']); - $statement->bindValue('Gender', $member['Gender']); - $statement->bindValue('Accommodation', $member['Accomodation']); - $statement->bindValue('MaxGuests', $member['MaxGuest']); - $statement->bindValue('HideAttribute', $this->migrateHiddenFields($member)); - $statement->bindValue('bewelcomed', $member['bewelcomed']); - if ('0000-00-00' !== $member['BirthDate']) { - $statement->bindValue('BirthDate', new DateTime($member['BirthDate']), 'datetime'); - } else { - $statement->bindValue('BirthDate', new DateTime('1970-01-01 00:00:00'), 'datetime'); - } - $statement->bindValue('GenderOfGuests', $member['GenderOfGuest']); - $statement->bindValue('HostingInterest', $member['hosting_interest']); - $statement->bindValue('Reminders', $member['NbRemindWithoutLogingIn']); - if (null !== $member['LastLogin'] && '0000-00-00 00:00:00' !== $member['LastLogin']) { - $statement->bindValue('LastLogin', new DateTime($member['LastLogin']), 'datetime'); - } else { - $statement->bindValue('LastLogin', null); - } - if (null !== $member['LastSwitchToActive']) { - $statement->bindValue('LastSwitchToActive', new DateTime($member['LastSwitchToActive']), 'datetime'); - } else { - $statement->bindValue('LastSwitchToActive', null); - } + $locale = $this->connection->executeQuery('SELECT mp.Value FROM memberspreferences mp WHERE mp.IdMember = :memberId AND mp.IdPreference = 1', ['memberId' => $memberId])->fetchOne(); + if (is_numeric($locale)) { + $locale = $this->connection->executeQuery('SELECT l.ShortCode FROM memberspreferences mp, languages l WHERE mp.IdMember = :memberId AND mp.IdPreference = 1 and mp.Value = l.id', ['memberId' => $memberId])->fetchOne(); + } + $statement = $this->connection->prepare(self::INSERT); + $statement->bindValue('id', $memberId); + $statement->bindValue('Locale', false === $locale ? 'en' : $locale); + $statement->bindValue('Username', $member['Username']); + $statement->bindValue('Password', $member['PassWord']); + $statement->bindValue('Name', $this->getFullname($member)); + $statement->bindValue('ShortName', $this->isFirstnameShown($member) ? $member['FirstName'] : null); + $statement->bindValue('Status', $member['Status']); + $statement->bindValue('Email', $member['Email']); + $statement->bindValue('Gender', $this->migrateGender($member['Gender'])); + $statement->bindValue('HideAttribute', $this->migrateHiddenFields($member)); + $statement->bindValue('bewelcomed', $member['bewelcomed']); + if ('0000-00-00' !== $member['BirthDate']) { + $statement->bindValue('BirthDate', new DateTime($member['BirthDate']), 'datetime'); + } else { + $statement->bindValue('BirthDate', new DateTime('1970-01-01 00:00:00'), 'datetime'); + } + if (null !== $member['LastLogin'] && '0000-00-00 00:00:00' !== $member['LastLogin']) { + $statement->bindValue('LastActive', new DateTime($member['LastLogin']), 'datetime'); + } else { + $statement->bindValue('LastActive', null); + } + if (null !== $member['LastSwitchToActive']) { + $statement->bindValue('LastSwitchToActive', new DateTime($member['LastSwitchToActive']), 'datetime'); + } else { + $statement->bindValue('LastSwitchToActive', null); + } + $statement->bindValue('Reminders', $member['NbRemindWithoutLogingIn']); - if ('0000-00-00 00:00:00' !== $member['created']) { - $statement->bindValue('created', new DateTime($member['created']), 'datetime'); - } else { - $statement->bindValue('created', new DateTime('1970-01-01 00:00:00'), 'datetime'); - } + if ('0000-00-00 00:00:00' !== $member['created']) { + $statement->bindValue('created', new DateTime($member['created']), 'datetime'); + } else { + $statement->bindValue('created', new DateTime('1970-01-01 00:00:00'), 'datetime'); + } - if ('0000-00-00 00:00:00' !== $member['updated']) { - if (null === $member['updated']) { - $statement->bindValue('updated', null, 'datetime'); + if ('0000-00-00 00:00:00' !== $member['updated']) { + if (null === $member['updated']) { + $statement->bindValue('updated', null, 'datetime'); + } else { + $statement->bindValue('updated', new DateTime($member['updated']), 'datetime'); + } } else { - $statement->bindValue('updated', new DateTime($member['updated']), 'datetime'); + $statement->bindValue('updated', new DateTime('1970-01-01 00:00:00'), 'datetime'); } - } else { - $statement->bindValue('updated', new DateTime('1970-01-01 00:00:00'), 'datetime'); - } - $statement->bindValue('RegistrationKey', $member['registration_key']); - $statement->bindValue('StandardOffers', $this->migrateTypicalOffer($member['TypicOffer'])); - $statement->bindValue('Restrictions', $this->migrateRestrictions($member['Restrictions'])); + $statement->bindValue('RegistrationKey', $member['registration_key']); - try { - $statement->executeStatement(); - } catch (Exception $e) { - $progressBar->setMessage($member['Username'], 'error'); - $this->addErrorMemberSql($member, $e->getMessage()); - } + $statement->bindValue('Accommodation', $this->migrateAccommodation($member['Accomodation'])); + $statement->bindValue('MaxGuests', $member['MaxGuest']); + $statement->bindValue('HostingInterest', $member['hosting_interest']); + $statement->bindValue('StandardOffers', $this->migrateTypicalOffer($member['TypicOffer'])); - // Migrate address - $city = $locationRepository->findOneBy(['geonameId' => $member['IdCity']]); - if (null === $city) { - if ('AskToLeave' !== $member['Status'] && 'TakenOut' !== $member['Status']) { - $this->addErrorCity($member); + try { + $statement->executeStatement(); + } catch (Exception $e) { $progressBar->setMessage($member['Username'], 'error'); + $this->addErrorMemberSql($member, $e->getMessage()); } - $statement->bindValue('City', null); - } else { - $statement->bindValue('City', $city->getGeonameid()); - } - // Now handle translated fields - $memberLocales = []; + $addressSQL = 'INSERT INTO address (member_id, active, location, latitude, longitude, wheelChairAccessible) ' . + 'VALUES (:member, 1, :city, :latitude, :longitude, :wheelchairAccessible)'; - // Determine currently used translation ids - $translationIds = []; - foreach (self::TRANSLATED_FIELDS as $translatedField) { - if (0 !== $member[$translatedField] && null !== $member[$translatedField]) { - $translationIds[] = $member[$translatedField]; + // Migrate address + $statement = $this->connection->prepare($addressSQL); + $city = $locationRepository->findOneBy(['geonameId' => $member['IdCity']]); + $statement->bindValue('member', $memberId); + if (null === $city) { + if ('AskToLeave' !== $member['Status'] && 'TakenOut' !== $member['Status']) { + $this->addErrorCity($member); + $progressBar->setMessage($member['Username'], 'error'); + } + $statement->bindValue('city', null); + $statement->bindValue('latitude', null); + $statement->bindValue('longitude', null); + $statement->bindValue('wheelchairAccessible', null); + } else { + $statement->bindValue('city', $city->getGeonameid()); + $statement->bindValue('latitude', $member['Latitude']); + $statement->bindValue('longitude', $member['Longitude']); + $statement->bindValue('wheelchairAccessible', $this->isWheelchairAccessible($member['TypicOffer']) ? 1 : 0); } - } - if (empty($translationIds)) { - if ('Active' === $member['Status'] || 'OutOfRemind' === $member['Status']) { - // Check where this happens - $this->addErrorNoTranslations($member); + try { + $statement->executeStatement(); + } catch (Exception $e) { + $progressBar->setMessage($member['Username'], 'error'); + $this->addErrorAddress($member, $e->getMessage()); + } + + // Now handle translated fields + $memberLocales = []; + + // Determine currently used translation ids + $translationIds = []; + foreach (self::TRANSLATED_FIELDS as $translatedField) { + if (0 !== $member[$translatedField] && null !== $member[$translatedField]) { + $translationIds[] = $member[$translatedField]; + } } - } else { - $processedTranslations = []; - $processedTranslations['en']['ProfileLanguage'] = true; - $queryString = $this->addTranslation($memberId, 'en', 'ProfileLanguage', 'en'); - $memberTranslations = $this->connection->executeQuery('SELECT * FROM memberstrads m WHERE IdTrad IN (' . implode(',', $translationIds) . ') ORDER BY IdTrad, id desc, IdLanguage')->fetchAllAssociative(); - foreach ($memberTranslations as $memberTranslation) { - $language = $languages[$memberTranslation['IdLanguage']] ?? null; - if (null === $language) { - $this->addErrorLanguage($member, $memberTranslation['IdLanguage']); - $progressBar->setMessage($member['Username'], 'error'); - } else { - $locale = $language->getShortCode(); - $field = $this->mapField($memberTranslation['TableColumn']); - if (!isset($processedTranslations[$locale][$field])) { - if (!isset($processedTranslations[$locale]['ProfileLanguage'])) { - $queryString .= $this->addTranslation($memberId, $locale, 'ProfileLanguage', $locale); - $processedTranslations[$locale]['ProfileLanguage'] = true; - } - $content = $memberTranslation['Sentence']; - if (!empty($content)) { - $queryString .= $this->addTranslation($memberId, $locale, $field, $content); + if (empty($translationIds)) { + if ('Active' === $member['Status'] || 'OutOfRemind' === $member['Status']) { + // Check where this happens + $this->addErrorNoTranslations($member); + } + } else { + $processedTranslations = []; + $processedTranslations['en']['ProfileLanguage'] = true; + $queryString = $this->addTranslation($memberId, 'en', 'ProfileLanguage', 'en'); + $memberTranslations = $this->connection->executeQuery('SELECT * FROM memberstrads m WHERE IdTrad IN (' . implode(',', $translationIds) . ') ORDER BY IdTrad, id desc, IdLanguage')->fetchAllAssociative(); + foreach ($memberTranslations as $memberTranslation) { + $language = $languages[$memberTranslation['IdLanguage']] ?? null; + if (null === $language) { + $this->addErrorLanguage($member, $memberTranslation['IdLanguage']); + $progressBar->setMessage($member['Username'], 'error'); + } else { + $locale = $language->getShortCode(); + $field = $this->mapField($memberTranslation['TableColumn']); + if (!isset($processedTranslations[$locale][$field])) { + if (!isset($processedTranslations[$locale]['ProfileLanguage'])) { + $queryString .= $this->addTranslation($memberId, $locale, 'ProfileLanguage', $locale); + $processedTranslations[$locale]['ProfileLanguage'] = true; + } + + $content = $memberTranslation['Sentence']; + if (!empty($content)) { + $queryString .= $this->addTranslation($memberId, $locale, $field, $content); + } + $processedTranslations[$locale][$field] = true; } - $processedTranslations[$locale][$field] = true; } } + + $sql = 'INSERT INTO member_translations (object_id, Locale, Field, Content) VALUES ' . substr($queryString, 2); + + try { + $this->connection->executeStatement($sql); + } catch (Exception $e) { + $progressBar->setMessage($member['Username'], 'error'); + $this->addErrorTranslationSql($member, $e->getMessage()); + } } - $sql = 'INSERT INTO member_translations (object_id, Locale, Field, Content) VALUES ' . substr($queryString, 2); + unset($translationIds); - try { - $this->connection->executeStatement($sql); - } catch (Exception $e) { - $progressBar->setMessage($member['Username'], 'error'); - $this->addErrorTranslationSql($member, $e->getMessage()); + // migrate language levels + $languageLevels = $this->connection->executeQuery('select * from memberslanguageslevel where Idmember = ' . $memberId)->fetchAllAssociative(); + foreach ($languageLevels as $languageLevel) { + $statement = $this->connection->prepare( + 'INSERT INTO member_language_level (member_id, language, level) ' . + 'VALUES (:member_id, :language, :level)' + ); + + $language = $languages[$languageLevel['IdLanguage']] ?? null; + if (null === $language) { + $progressBar->setMessage($member['Username'], 'error'); + $this->addErrorLanguage($member, $languageLevel['IdLanguage']); + } else { + $statement->bindValue('member_id', $memberId); + $statement->bindValue('language', $languages[$languageLevel['IdLanguage']]->getShortCode()); + $statement->bindValue('level', $languageLevel['Level']); + try { + $statement->executeStatement(); + } catch (Exception $e) { + $progressBar->setMessage($member['Username'], 'error'); + $this->addErrorLanguage($member, $languageLevel['IdLanguage']); + } + } + } + unset($languageLevels); + + // migrate special relations (family and friends) + $statement = $this->connection->prepare(" + select + mt.Sentence as comment, sr.* + from + specialrelations sr, memberstrads mt + where + sr.IdOwner = :memberId + and sr.IdOwner= mt.IdOwner + and mt.IdLanguage = 0 + and sr.Comment = mt.idtrad + and mt.TableColumn = 'specialrelations.comment' + "); + $statement->bindValue('memberId', $memberId); + + $specialRelations = $statement->executeQuery()->fetchAllAssociative(); + + foreach ($specialRelations as $specialRelation) { + $statement = $this->connection->prepare( + 'INSERT INTO relation (`comment`, created, updated, confirmed, owner_id, relation_id) ' . + 'VALUES (:comment, :created, :updated, :confirmed, :owner_id, :relation_id)' + ); + + $statement->bindValue('comment', $specialRelation['comment']); + $statement->bindValue('created', $specialRelation['created']); + $statement->bindValue('updated', $specialRelation['updated']); + $statement->bindValue('confirmed', $languageLevel['confirmed'] ?? 0); + $statement->bindValue('owner_id', $memberId); + $statement->bindValue('relation_id', $specialRelation['IdRelation']); + try { + $statement->executeStatement(); + } catch (Exception $e) { + $progressBar->setMessage($member['Username'], 'error'); + $this->addErrorMemberSql($member, $e->getMessage()); + } } + unset($specialRelations); + $progressBar->advance(); + unset($member); } - $progressBar->advance(); + unset($members); } - unset($members); - } - if (!empty($this->errorMembers)) { - $file = fopen('migrateMembers.errors.txt', 'w'); - $countOfErrorMembers = 0; - foreach ($this->errorMembers as $errors) { - $countOfErrorMembers += \count($errors); - } - $this->io->error("Error migrating {$countOfErrorMembers} members."); - fwrite($file, 'Members with errors: ' . $countOfErrorMembers . "\n"); - foreach ($this->errorMembers as $status => $membersWithErrors) { - $countOfErrorMembers = \count($membersWithErrors); - fwrite($file, "{$status}: {$countOfErrorMembers}" . \PHP_EOL); - $errorsByCategory = []; - foreach ($membersWithErrors as $errors) { - foreach ($errors as $category => $error) { - if (!isset($errorsByCategory[$category])) { - $errorsByCategory[$category] = 0; + if (!empty($this->errorMembers)) { + $file = fopen('migrateMembers.errors.txt', 'w'); + $countOfErrorMembers = 0; + foreach ($this->errorMembers as $errors) { + $countOfErrorMembers += \count($errors); + } + $this->io->error("Error migrating {$countOfErrorMembers} members."); + fwrite($file, 'Members with errors: ' . $countOfErrorMembers . "\n"); + foreach ($this->errorMembers as $status => $membersWithErrors) { + $countOfErrorMembers = \count($membersWithErrors); + fwrite($file, "{$status}: {$countOfErrorMembers}" . \PHP_EOL); + $errorsByCategory = []; + foreach ($membersWithErrors as $errors) { + foreach ($errors as $category => $error) { + if (!isset($errorsByCategory[$category])) { + $errorsByCategory[$category] = 0; + } + ++$errorsByCategory[$category]; } - ++$errorsByCategory[$category]; + } + foreach (array_keys($errorsByCategory) as $category) { + fwrite($file, " {$category}: {$errorsByCategory[$category]}" . \PHP_EOL); } } - foreach (array_keys($errorsByCategory) as $category) { - fwrite($file, " {$category}: {$errorsByCategory[$category]}" . \PHP_EOL); - } - } - fwrite($file, \PHP_EOL); + fwrite($file, \PHP_EOL); - foreach ($this->errorMembers as $status => $membersWithErrors) { - foreach ($membersWithErrors as $username => $errors) { - $errorText = []; - $errorText[] = "Error migrating member: {$status} - {$username}"; + foreach ($this->errorMembers as $status => $membersWithErrors) { + foreach ($membersWithErrors as $username => $errors) { + $errorText = []; + $errorText[] = "Error migrating member: {$status} - {$username}"; - if (\array_key_exists('city', $errors)) { - $errorText[] = "City not found: {$errors['city']}"; - } - if (\array_key_exists('language', $errors)) { - $errorText[] = "Language not found: {$errors['language']}"; - } - if (\array_key_exists('no_translations', $errors)) { - $errorText[] = 'No translations in database!'; - } - if (\array_key_exists('member_sql', $errors)) { - $errorText[] = $errors['member_sql']; - } - if (\array_key_exists('translation_sql', $errors)) { - $errorText[] = $errors['translation_sql']; + if (\array_key_exists('city', $errors)) { + $errorText[] = "City not found: {$errors['city']}"; + } + if (\array_key_exists('address', $errors)) { + $errorText[] = "Address: {$errors['address']}"; + } + if (\array_key_exists('language', $errors)) { + $errorText[] = "Language not found: {$errors['language']}"; + } + if (\array_key_exists('no_translations', $errors)) { + $errorText[] = 'No translations in database!'; + } + if (\array_key_exists('member_sql', $errors)) { + $errorText[] = $errors['member_sql']; + } + if (\array_key_exists('translation_sql', $errors)) { + $errorText[] = $errors['translation_sql']; + } + fwrite($file, implode(\PHP_EOL, $errorText)); + fwrite($file, \PHP_EOL); } - fwrite($file, implode(\PHP_EOL, $errorText)); - fwrite($file, \PHP_EOL); } - } - fclose($file); + fclose($file); + } + } finally { + $this->connection->executeStatement('SET FOREIGN_KEY_CHECKS=1;'); } $this->io->success('Done migrating old profiles.'); @@ -382,11 +491,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::SUCCESS; } - private function migrateAccommodation(string $accommodation): string + private function migrateAccommodation(?string $accommodation): string { return match ($accommodation) { - 'neverask' => 'no', 'dependonrequest', 'anytime' => 'yes', + default => 'no', }; } @@ -400,13 +509,14 @@ private function migrateTypicalOffer(string $typicalOffer): string $standardOffer .= ',' . StandardOffersType::GUIDED_TOUR; } - if (str_contains(TypicalOfferType::WHEELCHAIR_ACCESSIBLE, $typicalOffer)) { - $standardOffer .= ',' . StandardOffersType::WHEELCHAIR_ACCESSIBLE; - } - return substr($standardOffer, 1); } + private function isWheelchairAccessible(string $typicalOffer): bool + { + return str_contains(TypicalOfferType::WHEELCHAIR_ACCESSIBLE, $typicalOffer); + } + private function getFullname(array $member): string { $name = $member['FirstName'] . ' ' . $member['SecondName'] . ' ' . $member['LastName']; @@ -416,7 +526,7 @@ private function getFullname(array $member): string private function isFirstnameShown(array $member): bool { - return ($member['HideAttribute'] & Member::MEMBER_FIRSTNAME_HIDDEN) !== Member::MEMBER_FIRSTNAME_HIDDEN; + return ($member['HideAttribute'] & self::MEMBER_FIRSTNAME_HIDDEN) !== self::MEMBER_FIRSTNAME_HIDDEN; } private function migrateRestrictions(mixed $restrictions): string @@ -472,6 +582,13 @@ private function addErrorCity(array $member): void $this->errorMembers[$member['Status']][$member['Username']]['city'] = $member['IdCity']; } + private function addErrorAddress(array $member, string $message): void + { + $this->addErrorMember($member); + + $this->errorMembers[$member['Status']][$member['Username']]['address'] = $message; + } + private function addErrorMemberSql(array $member, string $message): void { $this->addErrorMember($member); @@ -481,13 +598,13 @@ private function addErrorMemberSql(array $member, string $message): void private function addErrorTranslationSql(array $member, string $message): void { $this->addErrorMember($member); - $this->errorMembers[$member['Status']][$member['Username']]['translation_sql'] = $message(); + $this->errorMembers[$member['Status']][$member['Username']]['translation_sql'] = $message; } private function addErrorNoTranslations(array $member): void { - $this->addErrorMember($member); - $this->errorMembers[$member['Status']][$member['Username']]['no_translations'] = 'No Translations'; + // $this->addErrorMember($member); + // $this->errorMembers[$member['Status']][$member['Username']]['no_translations'] = 'No Translations'; } private function addErrorLanguage(array $member, mixed $language): void @@ -500,11 +617,11 @@ private function migrateHiddenFields(array $member): int { $hideAttribute = 0; if ( - ($member['HideAttribute'] && Member::MEMBER_FIRSTNAME_HIDDEN) - || ($member['HideAttribute'] && Member::MEMBER_SECONDNAME_HIDDEN) - || ($member['HideAttribute'] && Member::MEMBER_LASTNAME_HIDDEN) + ($member['HideAttribute'] && self::MEMBER_FIRSTNAME_HIDDEN) + || ($member['HideAttribute'] && self::MEMBER_SECONDNAME_HIDDEN) + || ($member['HideAttribute'] && self::MEMBER_LASTNAME_HIDDEN) ) { - $hideAttribute = Member::NAME_HIDDEN; + $hideAttribute |= Member::NAME_HIDDEN; } if ('Yes' === $member['HideBirthDate']) { @@ -521,4 +638,12 @@ private function migrateHiddenFields(array $member): int return $hideAttribute; } + + private function migrateGender(mixed $gender): string + { + return match ($gender) { + 'IDontTell' => 'other', + default => $gender, + }; + } } diff --git a/src/Controller/AccountController.php b/src/Controller/AccountController.php index 9a077829b0..b0698ef993 100644 --- a/src/Controller/AccountController.php +++ b/src/Controller/AccountController.php @@ -6,16 +6,32 @@ use App\Entity\Member; use App\Form\AccountEditFormType; +use App\Form\ConfirmEmailAddressFormType; use App\Model\ProfileModel; +use App\Model\SignupModel; use App\Utilities\ChangeProfilePictureGlobals; use App\Utilities\ProfileSubmenu; +use App\Utilities\TranslatedFlashTrait; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Form\FormError; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Contracts\Translation\TranslatorInterface; class AccountController extends AbstractController { + use TranslatedFlashTrait; + + public function __construct( + private readonly EntityManagerInterface $entityManager, + private readonly ProfileModel $profileModel, + private readonly SignupModel $signupModel, + private readonly TranslatorInterface $translator, + ) { + } + #[Route(path: '/members/{username:member}/account', name: 'account_edit')] public function editAccountInfo( Request $request, @@ -42,7 +58,33 @@ public function editAccountInfo( $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - return $this->redirectToRoute('members_profile', ['username' => $member->getUsername()]); + $data = $form->getData(); + // Check if email was changed, and the new one is unique + $email = $data['email']; + + if ($email !== $member->getEmail()) { + $unknownEmail = $this->signupModel->checkEmailAddress($email); + if (!$unknownEmail) { + $form->get('email')->addError(new FormError($this->translator->trans('account.new.email.taken'))); + } + } + + if (0 === $form->getErrors(true)->count()) { + $member->setName($data['name']); + $member->setShortName($data['short_name']); + $member->setGender($data['gender']); + $member->setBirthdate($data['birthdate']); + + if ($data['email'] !== $member->getEmail()) { + $member->setNewEmail($data['email']); + $this->profileModel->sendEmailConfirmationEmail($member, $data['email']); + } + + $this->entityManager->persist($member); + $this->entityManager->flush(); + + return $this->redirectToRoute('members_profile', ['username' => $member->getUsername()]); + } } return $this->render('profile/account.edit.html.twig', [ @@ -53,4 +95,53 @@ public function editAccountInfo( 'submenu' => $profileSubmenu->getSubmenu($loggedInMember, $member), ]); } + + /** + * Email change is triggered through editing your account therefore handling the process here. + */ + #[Route( + path: '/members/{username:member}/change/email/{registrationKey}', + name: 'change_email', + defaults: ['registrationKey' => null], + priority: 20 + )] + public function changeEmailAddress(Request $request, Member $member, ?string $registrationKey): Response + { + $loggedInMember = $this->getUser(); + if ($member !== $loggedInMember) { + $this->addTranslatedFlash('error', 'flash.signup.wrong.user'); + + return $this->redirectToRoute('members_profile', ['username' => $member->getUsername()]); + } + + if (null === $member->getRegistrationKey()) { + $this->addTranslatedFlash('error', 'flash.profile.mail.already.confirmed'); + + return $this->redirectToRoute('members_profile', ['username' => $member->getUsername()]); + } + + $changeEmailAddressForm = $this->createForm(ConfirmEmailAddressFormType::class, [ + 'registration_key' => $registrationKey, + ]); + $changeEmailAddressForm->handleRequest($request); + if ($changeEmailAddressForm->isSubmitted() && $changeEmailAddressForm->isValid()) { + $registrationKey = $changeEmailAddressForm['registration_key']->getData(); + if ($registrationKey === $member->getRegistrationKey()) { + $member->setEmail($member->getNewEmail()); + $member->setNewEmail(null); + $member->setRegistrationKey(null); + + $this->entityManager->persist($member); + $this->entityManager->flush(); + } + + return $this->redirectToRoute('members_profile', ['username' => $member->getUsername()]); + } + + return $this->render('signup/confirm.email.html.twig', [ + 'hide_confirm_email' => true, + 'member' => $member, + 'confirm_email' => $changeEmailAddressForm, + ]); + } } diff --git a/src/Controller/LandingController.php b/src/Controller/LandingController.php index 4e1d611f8f..14d51d2085 100644 --- a/src/Controller/LandingController.php +++ b/src/Controller/LandingController.php @@ -165,11 +165,8 @@ public function getActivities(Request $request) return $content; } - /** - * @return Response - */ #[Route(path: '/widget/accommodation', name: '/widget/accommodation')] - public function setAccommodationAction(Request $request, Environment $twig) + public function setAccommodation(Request $request, Environment $twig): JsonResponse { /** @var Member $member */ $member = $this->getUser(); diff --git a/src/Controller/ProfileController.php b/src/Controller/ProfileController.php index 481b8b309b..3c53e0218e 100644 --- a/src/Controller/ProfileController.php +++ b/src/Controller/ProfileController.php @@ -2,6 +2,12 @@ namespace App\Controller; +use App\Doctrine\AccommodationType; +use App\Doctrine\HostRestrictionsType; +use App\Doctrine\StandardOffersType; +use App\Dto\AccommodationDto; +use App\Dto\OffersDto; +use App\Dto\RestrictionsDto; use App\Entity\Comment; use App\Entity\GalleryImage; use App\Entity\Location; @@ -29,11 +35,13 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Attribute\MapRequestPayload; use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; @@ -465,6 +473,136 @@ public function editProfileInLanguage(Request $request, Member $member, string $ ]); } + #[Route(path: '/members/update/field', name: 'profile_update_field', methods: ['POST'], priority: 20)] + public function updateField(Request $request): Response + { + $form = $this->createFormBuilder(options: ['csrf_protection' => false]) + ->add('language', TextType::class) + ->add('field', TextType::class) + ->add('content', TextType::class) + ->add('username', TextType::class) + ->getForm(); + + $form->submit($request->request->all()); + if ($form->isSubmitted() && $form->isValid()) { + $loggedInMember = $this->getUser(); + $data = $form->getData(); + $memberRepository = $this->entityManager->getRepository(Member::class); + $member = $memberRepository->findOneBy(['username' => $data['username']]); + + // Check if user exists and if logged in member is either same or privileged + if ($member === $loggedInMember) { + $this->profileModel->handleField($member, $data['language'], $data['field'], $data['content']); + } + } + + return new Response(); + } + + #[Route( + path: '/members/update/accommodation', + name: 'profile_update_accommodation', + methods: ['POST'], + priority: 10, + )] + public function setAccommodation(#[MapRequestPayload] AccommodationDto $payload): Response + { + // Result of call will not be communicated to the requestor + $response = new Response(); + + /** @var Member $member */ + $member = $this->getUser(); + if (null === $member) { + return $response; + } + + $accommodation = $payload->accommodation; + $valid = (AccommodationType::YES === $accommodation) || (AccommodationType::NO === $accommodation); + if ($valid) { + $member->setAccommodation($accommodation); + } + + $hostingInterest = $payload->hostingInterest; + if (null !== $hostingInterest && $hostingInterest > 0) { + $member->setHostingInterest($hostingInterest); + } + + $this->entityManager->persist($member); + $this->entityManager->flush(); + + return $response; + } + + #[Route(path: '/members/update/offers', name: 'profile_update_offers', methods: ['POST'], priority: 10)] + public function setOffers(#[MapRequestPayload] OffersDto $payload): Response + { + // Result of call will not be communicated to the requestor + $response = new Response(); + + /** @var Member $member */ + $member = $this->getUser(); + if (null === $member) { + return $response; + } + + $dinner = $payload->dinner; + $tour = $payload->tour; + + $offers = []; + if (true === $dinner) { + $offers[] = StandardOffersType::DINNER; + } + + if (true === $tour) { + $offers[] = StandardOffersType::GUIDED_TOUR; + } + + $member->setStandardOffers($offers); + $address = $member->getActiveAddress(); + $address->setIsWheelchairAccessible($payload->accessible); + + $this->entityManager->persist($address); + $this->entityManager->persist($member); + $this->entityManager->flush(); + + return $response; + } + + #[Route(path: '/members/update/restrictions', name: 'profile_update_restrictions', methods: ['POST'], priority: 10)] + public function setRestrictions(#[MapRequestPayload] RestrictionsDto $payload): Response + { + // Result of call will not be communicated to the requestor + $response = new Response(); + + /** @var Member $member */ + $member = $this->getUser(); + if (null === $member) { + return $response; + } + + $noAlcohol = $payload->noAlcohol; + $noSmoking = $payload->noSmoking; + $noDrugs = $payload->noDrugs; + + $restrictions = []; + if (true === $noAlcohol) { + $restrictions[] = HostRestrictionsType::NO_ALCOHOL; + } + if (true === $noSmoking) { + $restrictions[] = HostRestrictionsType::NO_SMOKING; + } + if (true === $noDrugs) { + $restrictions[] = HostRestrictionsType::NO_DRUGS; + } + + $member->setRestrictions($restrictions); + + $this->entityManager->persist($member); + $this->entityManager->flush(); + + return $response; + } + private function renderProfile(Member $member, Member $loggedInMember, ?string $language, bool $editMode): Response { /** @var CommentRepository $commentRepository */ @@ -474,7 +612,7 @@ private function renderProfile(Member $member, Member $loggedInMember, ?string $ /** @var RelationRepository $relationsRepository */ $relationsRepository = $this->entityManager->getRepository(Relation::class); - $relations = $relationsRepository->findBy(['receiver' => $member, 'confirmed' => 'Yes']); + $relations = $relationsRepository->findRelationsFor($member); /** @var GalleryImageRepository $galleryRepository */ $galleryRepository = $this->entityManager->getRepository(GalleryImage::class); diff --git a/src/Controller/SignupController.php b/src/Controller/SignupController.php index 71e9a76eca..d0bb88fdb2 100644 --- a/src/Controller/SignupController.php +++ b/src/Controller/SignupController.php @@ -176,7 +176,8 @@ public function resendConfirmationEmail( #[Route( path: '/members/{username:member}/confirm/{registrationKey}', name: 'confirm_email', - defaults: ['registrationKey' => null] + defaults: ['registrationKey' => null], + priority: 20 )] public function confirmEmailAddress(Request $request, Member $member, ?string $registrationKey): Response { @@ -193,7 +194,9 @@ public function confirmEmailAddress(Request $request, Member $member, ?string $r return $this->redirectToRoute('members_profile', ['username' => $member->getUsername()]); } - $confirmEmailAddressForm = $this->createForm(ConfirmEmailAddressFormType::class, ['registration_key' => $registrationKey]); + $confirmEmailAddressForm = $this->createForm(ConfirmEmailAddressFormType::class, [ + 'registration_key' => $registrationKey, + ]); $confirmEmailAddressForm->handleRequest($request); if ($confirmEmailAddressForm->isSubmitted() && $confirmEmailAddressForm->isValid()) { $registrationKey = $confirmEmailAddressForm['registration_key']->getData(); diff --git a/src/Dto/AccommodationDto.php b/src/Dto/AccommodationDto.php new file mode 100644 index 0000000000..4ce9773449 --- /dev/null +++ b/src/Dto/AccommodationDto.php @@ -0,0 +1,12 @@ +active; } - public function getCreated(): Carbon - { - return Carbon::instance($this->created); - } - - public function getUpdated(): Carbon - { - return Carbon::instance($this->updated); - } - public function setIsWheelChairAccessible(?bool $isWheelChairAccessible): self { $this->isWheelChairAccessible = $isWheelChairAccessible; @@ -205,22 +186,4 @@ public function addTranslation(MemberTranslation $translation): void $translation->setObject($this); } } - - /** - * Triggered on insert. - */ - #[ORM\PrePersist] - public function onPrePersist() - { - $this->created = new DateTime('now'); - } - - /** - * Triggered on update. - */ - #[ORM\PreUpdate] - public function onPreUpdate() - { - $this->updated = new DateTime('now'); - } } diff --git a/src/Entity/Languages.php b/src/Entity/Languages.php index f82ff4115e..4d723191e7 100644 --- a/src/Entity/Languages.php +++ b/src/Entity/Languages.php @@ -17,6 +17,7 @@ */ #[ORM\Table(name: 'languages')] #[ORM\UniqueConstraint(name: 'ShortCode', columns: ['ShortCode'])] +#[ORM\Entity] class Languages { #[ORM\Column(name: 'Name', type: 'text', length: 255, nullable: false)] diff --git a/src/Entity/Member.php b/src/Entity/Member.php index 1ea3ff1d4f..5ea608d2e8 100644 --- a/src/Entity/Member.php +++ b/src/Entity/Member.php @@ -79,7 +79,7 @@ class Member implements Stringable, Serializable, UserInterface, PasswordHasherA protected string $email; #[ORM\Column(name: 'NewEmail', type: 'string', nullable: true)] - protected string $newEmail; + protected ?string $newEmail; #[ORM\Column(name: 'Locale', type: 'string', length: 8, nullable: false)] protected string $locale = 'en'; @@ -159,9 +159,6 @@ class Member implements Stringable, Serializable, UserInterface, PasswordHasherA #[ORM\Column(name: 'Gender', type: 'gender_type', nullable: false)] private string $gender = 'other'; - #[ORM\Column(name: 'GenderOfGuests', type: 'string', nullable: false)] - private string $genderOfGuests = 'any'; - #[ORM\Column(name: 'BirthDate', type: 'date', nullable: true)] private ?DateTime $birthdate = null; @@ -326,7 +323,7 @@ public function setNewEmail(?string $email): self return $this; } - public function getNewEmail(): string + public function getNewEmail(): ?string { return $this->newEmail; } @@ -492,7 +489,7 @@ public function setRestrictions(array $restrictions): self public function getRestrictions(): array { - if ('' === $this->restrictions) { + if (empty($this->restrictions)) { return []; } @@ -1288,16 +1285,6 @@ public function setRegistrationKey(?string $registrationKey): self return $this; } - public function getRegion(): ?Location - { - return $this->city->getAdmin1(); - } - - public function getCountry(): ?Location - { - return $this->city->getCountry(); - } - public function getAge(): int { if (null === $this->birthdate) { diff --git a/src/Entity/MemberLanguageLevel.php b/src/Entity/MemberLanguageLevel.php index f22d48f287..32ac72378a 100644 --- a/src/Entity/MemberLanguageLevel.php +++ b/src/Entity/MemberLanguageLevel.php @@ -9,8 +9,6 @@ namespace App\Entity; use App\Doctrine\LanguageLevelType; -use Carbon\Carbon; -use DateTime; use Doctrine\ORM\Mapping as ORM; /** @@ -20,19 +18,12 @@ */ #[ORM\Table(name: 'member_language_level')] #[ORM\Entity] -#[ORM\HasLifecycleCallbacks] class MemberLanguageLevel { #[ORM\ManyToOne(targetEntity: Member::class, inversedBy: 'languageLevels')] #[ORM\Id] protected ?Member $member = null; - #[ORM\Column(name: 'updated', type: 'datetime', nullable: true)] - private ?DateTime $updated = null; - - #[ORM\Column(name: 'created', type: 'datetime', nullable: false)] - private DateTime $created; - #[ORM\JoinColumn(name: 'language', referencedColumnName: 'ShortCode', nullable: false)] #[ORM\Id] #[ORM\ManyToOne(targetEntity: Language::class)] @@ -42,20 +33,6 @@ class MemberLanguageLevel #[ORM\Id] private ?string $level = LanguageLevelType::BEGINNER; - public function getUpdated(): ?Carbon - { - if (null === $this->updated) { - return null; - } - - return Carbon::instance($this->updated); - } - - public function getCreated(): Carbon - { - return Carbon::instance($this->created); - } - public function setMember(?Member $member): self { $this->member = $member; @@ -91,16 +68,4 @@ public function getLevel(): string { return $this->level; } - - #[ORM\PrePersist] - public function onPrePersist(): void - { - $this->created = Carbon::now(); - } - - #[ORM\PreUpdate] - public function onPreUpdate(): void - { - $this->updated = Carbon::now(); - } } diff --git a/src/Entity/Relation.php b/src/Entity/Relation.php index 10f4f632a5..3f35b63730 100644 --- a/src/Entity/Relation.php +++ b/src/Entity/Relation.php @@ -2,30 +2,26 @@ namespace App\Entity; +use App\Repository\RelationRepository; use Carbon\Carbon; use DateTime; -use Doctrine\ORM\Event\PostLoadEventArgs; -use Doctrine\ORM\Event\PostUpdateEventArgs; use Doctrine\ORM\Event\PrePersistEventArgs; use Doctrine\ORM\Mapping as ORM; -use Doctrine\Persistence\ObjectManager; /** * Do not check entities with PHPMD. * * @SuppressWarnings("PHPMD") */ -#[ORM\Table(name: 'specialrelations')] -#[ORM\Index(name: 'IdOwner', columns: ['IdOwner'])] -#[ORM\UniqueConstraint(name: 'UniqueRelation', columns: ['IdOwner', 'IdRelation'])] +#[ORM\Table(name: 'relation')] +#[ORM\Index(name: 'owner', columns: ['owner_id'])] +#[ORM\UniqueConstraint(name: 'UniqueRelation', columns: ['owner_id', 'relation_id'])] #[ORM\HasLifecycleCallbacks] -#[ORM\Entity(repositoryClass: \App\Repository\RelationRepository::class)] +#[ORM\Entity(repositoryClass: RelationRepository::class)] class Relation { - #[ORM\Column(name: 'Comment', type: 'integer', nullable: false)] - private int $comment = 0; - - private ?string $commentText = ''; + #[ORM\Column(name: 'comment', type: 'text', nullable: true)] + private ?string $comment; #[ORM\Column(name: 'created', type: 'datetime', nullable: false)] private DateTime $created; @@ -33,15 +29,15 @@ class Relation #[ORM\Column(name: 'updated', type: 'datetime', nullable: false)] private DateTime $updated; - #[ORM\JoinColumn(name: 'IdOwner', referencedColumnName: 'id')] + #[ORM\JoinColumn(name: 'owner_id', referencedColumnName: 'id')] #[ORM\ManyToOne(targetEntity: Member::class)] private Member $owner; - #[ORM\JoinColumn(name: 'IdRelation', referencedColumnName: 'id')] + #[ORM\JoinColumn(name: 'relation_id', referencedColumnName: 'id')] #[ORM\ManyToOne(targetEntity: Member::class, inversedBy: 'relations')] private Member $receiver; - #[ORM\Column(name: 'Confirmed', type: 'string', nullable: false)] + #[ORM\Column(name: 'confirmed', type: 'string', nullable: false)] private string $confirmed = 'No'; #[ORM\Column(name: 'id', type: 'integer')] @@ -49,14 +45,14 @@ class Relation #[ORM\GeneratedValue(strategy: 'IDENTITY')] private int $id; - public function setComment(int $comment): self + public function setComment(?string $comment): self { $this->comment = $comment; return $this; } - public function getComment(): int + public function getComment(): ?string { return $this->comment; } @@ -85,9 +81,6 @@ public function setOwner(Member $owner): self return $this; } - /** - * Get owner. - */ public function getOwner(): Member { return $this->owner; @@ -122,94 +115,10 @@ public function getId(): int return $this->id; } - /** - * Triggered after load from database. - */ - #[ORM\PostLoad] - public function onPostLoad(PostLoadEventArgs $args): void - { - $objectManager = $args->getObjectManager(); - /* $memberTranslationRepository = $objectManager->getRepository(MemberTranslation::class); - $translatedComment = $memberTranslationRepository->findOneBy([ - 'translation' => $this->comment, - 'owner' => $this->owner, - ]); - - if (null !== $translatedComment) { - $this->commentText = $translatedComment->getSentence(); - } - */ - $this->commentText = 'Damit unser frisch eingerichteter Raspberry auch zukünftig unsere ganzen Hauskomponenten steuern kann benötigen wir noch eine Automatisierungssoftware. Dafür kommt bei mir FHEM (offizielle Seite) zum Einsatz. FHEM ist eine in Perl geschriebene Serveranwendung. Diese bietet für den Benutzer eine grafische Oberfläche, mit der er neue Komponenten hinzufügen, bearbeiten und übersichtlich anordnen kann. Wie du FHEM einrichtest, will ich dir in diesem Artikel einmal zeigen…'; - } - - /** - * Triggered on insert. - */ #[ORM\PrePersist] public function onPrePersist(PrePersistEventArgs $args) { $this->created = new DateTime('now'); $this->updated = $this->created; - - if (null !== $this->commentText) { - $this->createRelationComment($args->getObjectManager()); - } - } - - /** - * Triggered on update. - */ - #[ORM\PostUpdate] - public function onPostUpdate(PostUpdateEventArgs $args) - { - if (0 !== $this->comment) { - $objectManager = $args->getObjectManager(); - - $memberTranslationRepository = $objectManager->getRepository(MemberTranslation::class); - $translatedComment = $memberTranslationRepository->findOneBy([ - 'translation' => $this->comment, - 'owner' => $this->getOwner(), - ]); - - $translatedComment->setSentence($this->commentText ?? ''); - $objectManager->persist($translatedComment); - $objectManager->flush(); - } else { - $translatedComment = $this->createRelationComment($args->getObjectManager()); - $this->comment = $translatedComment->getId(); - } - } - - public function getCommentText(): ?string - { - return $this->commentText; - } - - public function setCommentText(?string $commentText): self - { - $this->commentText = $commentText; - - return $this; - } - - private function createRelationComment(ObjectManager $objectManager): MemberTranslation - { - $languageRepository = $objectManager->getRepository(Language::class); - $language = $languageRepository->findOneBy(['shortCode' => 'en']); - - $translatedComment = new MemberTranslation(); - $translatedComment->setSentence($this->commentText); - $translatedComment->setOwner($this->getOwner()); - $translatedComment->setTranslator($this->getOwner()); - $translatedComment->setLanguage($language); - $translatedComment->setTableColumn('specialrelations.comment'); - $objectManager->persist($translatedComment); - $objectManager->flush(); - - $translatedComment->setTranslation($translatedComment->getId()); - $objectManager->persist($translatedComment); - $objectManager->flush(); - - return $translatedComment; } } diff --git a/src/Entity/SpecialRelation.php b/src/Entity/SpecialRelation.php new file mode 100644 index 0000000000..e141043935 --- /dev/null +++ b/src/Entity/SpecialRelation.php @@ -0,0 +1,214 @@ +comment = $comment; + + return $this; + } + + public function getComment(): int + { + return $this->comment; + } + + public function getCreated(): Carbon + { + return Carbon::instance($this->created); + } + + public function getUpdated(): Carbon + { + return Carbon::instance($this->updated); + } + + public function setUpdated(DateTime $updated): self + { + $this->updated = $updated; + + return $this; + } + + public function setOwner(Member $owner): self + { + $this->owner = $owner; + + return $this; + } + + /** + * Get owner. + */ + public function getOwner(): Member + { + return $this->owner; + } + + public function setReceiver(Member $receiver): self + { + $this->receiver = $receiver; + + return $this; + } + + public function getReceiver(): Member + { + return $this->receiver; + } + + public function setConfirmed(string $confirmed): self + { + $this->confirmed = $confirmed; + + return $this; + } + + public function getConfirmed(): string + { + return $this->confirmed; + } + + public function getId(): int + { + return $this->id; + } + + /** + * Triggered after load from database. + */ + #[ORM\PostLoad] + public function onPostLoad(PostLoadEventArgs $args): void + { + $objectManager = $args->getObjectManager(); + /* $memberTranslationRepository = $objectManager->getRepository(MemberTranslation::class); + $translatedComment = $memberTranslationRepository->findOneBy([ + 'translation' => $this->comment, + 'owner' => $this->owner, + ]); + + if (null !== $translatedComment) { + $this->commentText = $translatedComment->getSentence(); + } + */ + $this->commentText = 'Damit unser frisch eingerichteter Raspberry auch zukünftig unsere ganzen Hauskomponenten steuern kann benötigen wir noch eine Automatisierungssoftware. Dafür kommt bei mir FHEM (offizielle Seite) zum Einsatz. FHEM ist eine in Perl geschriebene Serveranwendung. Diese bietet für den Benutzer eine grafische Oberfläche, mit der er neue Komponenten hinzufügen, bearbeiten und übersichtlich anordnen kann. Wie du FHEM einrichtest, will ich dir in diesem Artikel einmal zeigen…'; + } + + /** + * Triggered on insert. + */ + #[ORM\PrePersist] + public function onPrePersist(PrePersistEventArgs $args) + { + $this->created = new DateTime('now'); + $this->updated = $this->created; + + if (null !== $this->commentText) { + $this->createRelationComment($args->getObjectManager()); + } + } + + /** + * Triggered on update. + */ + #[ORM\PostUpdate] + public function onPostUpdate(PostUpdateEventArgs $args) + { + if (0 !== $this->comment) { + $objectManager = $args->getObjectManager(); + + $memberTranslationRepository = $objectManager->getRepository(MemberTranslation::class); + $translatedComment = $memberTranslationRepository->findOneBy([ + 'translation' => $this->comment, + 'owner' => $this->getOwner(), + ]); + + $translatedComment->setSentence($this->commentText ?? ''); + $objectManager->persist($translatedComment); + $objectManager->flush(); + } else { + $translatedComment = $this->createRelationComment($args->getObjectManager()); + $this->comment = $translatedComment->getId(); + } + } + + public function getCommentText(): ?string + { + return $this->commentText; + } + + public function setCommentText(?string $commentText): self + { + $this->commentText = $commentText; + + return $this; + } + + private function createRelationComment(ObjectManager $objectManager): MemberTranslation + { + $languageRepository = $objectManager->getRepository(Language::class); + $language = $languageRepository->findOneBy(['shortCode' => 'en']); + + $translatedComment = new MemberTranslation(); + $translatedComment->setSentence($this->commentText); + $translatedComment->setOwner($this->getOwner()); + $translatedComment->setTranslator($this->getOwner()); + $translatedComment->setLanguage($language); + $translatedComment->setTableColumn('specialrelations.comment'); + $objectManager->persist($translatedComment); + $objectManager->flush(); + + $translatedComment->setTranslation($translatedComment->getId()); + $objectManager->persist($translatedComment); + $objectManager->flush(); + + return $translatedComment; + } +} diff --git a/src/Form/AccommodationFormType.php b/src/Form/AccommodationFormType.php index 18f3399c02..7509ec9f5c 100644 --- a/src/Form/AccommodationFormType.php +++ b/src/Form/AccommodationFormType.php @@ -50,7 +50,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'min' => 0, 'max' => 10, ], - 'data' => 0, ]) ->add('restrictions', ChoiceType::class, [ 'label' => 'label.restrictions', @@ -74,9 +73,9 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ], ]) ->add('wheelchair_accessible', CheckboxType::class, [ + 'required' => false, 'label' => 'label.profile.wheelchair_accessible', 'help' => 'help.profile.wheelchair_accessible', - 'required' => false, ]) ->add('length_of_stay', CkEditorType::class, [ 'label' => 'label.profile.length_of_stay', diff --git a/src/Form/AccountEditFormType.php b/src/Form/AccountEditFormType.php index cbba291483..44c20e4b30 100644 --- a/src/Form/AccountEditFormType.php +++ b/src/Form/AccountEditFormType.php @@ -83,25 +83,9 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'help' => 'help.gender.show', 'required' => false, ]) - ->add('address', TextType::class, [ - 'label' => 'label.address', - 'help' => 'help.address', - 'required' => false, - 'constraints' => [ - new NotNull(message: 'error.address'), - ], - ]) - ->add('show_address', CheckboxType::class, [ - 'label' => 'label.address.show', - 'help' => 'help.addresss.show', - 'required' => false, - ]) ->add('email', EmailType::class, [ - 'label' => 'label.email', - 'attr' => [ - 'class' => 'js-email-address', - ], - 'help' => 'help.email', + 'label' => 'label.email.change', + 'help' => 'help.email.change', 'required' => false, 'constraints' => [ new NotBlank(message: 'signup.error.email.blank'), diff --git a/src/Model/ProfileModel.php b/src/Model/ProfileModel.php index 88b111f808..5930f1fc82 100644 --- a/src/Model/ProfileModel.php +++ b/src/Model/ProfileModel.php @@ -15,6 +15,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use Hidehalo\Nanoid\Client; use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormInterface; @@ -185,6 +186,47 @@ public function handleProfileEdit(?string $section, Member $member, array $data) return $errors; } + public function handleField(Member $member, string $language, string $field, ?string $fieldContent): void + { + $fieldTranslation = $this->memberTranslationRepository->findOneBy([ + 'object' => $member, + 'locale' => $language, + 'field' => $field, + ]); + + if (empty($fieldContent)) { + if (null !== $fieldTranslation) { + $member->getRawTranslations()->removeElement($fieldTranslation); + $this->entityManager->remove($fieldTranslation); + } + } else { + if (null === $fieldTranslation) { + $fieldTranslation = new MemberTranslation($language, $field, $fieldContent); + } + $fieldTranslation->setContent($fieldContent); + + $member->addTranslation($fieldTranslation); + } + + $this->entityManager->persist($member); + $this->entityManager->flush(); + } + + public function sendEmailConfirmationEmail(Member $member, string $email): void + { + $nanoClient = new Client(); + $member->setRegistrationKey($nanoClient->generateId(16, Client::MODE_DYNAMIC)); + + $parameters = [ + 'subject' => 'signup.confirm.email', + 'username' => $member->getUsername(), + 'email_address' => $email, + 'key' => $member->getRegistrationKey(), + ]; + + $this->mailer->sendSignupEmail($member, 'newemail', $parameters); + } + private function handleAboutMe(Member $member, array $data): array { $language = $data['language']; @@ -261,27 +303,4 @@ private function handleAccommodation(Member $member, array $data): array return []; } - - private function handleField(Member $member, string $language, string $field, ?string $fieldContent): void - { - $fieldTranslation = $this->memberTranslationRepository->findOneBy([ - 'object' => $member, - 'locale' => $language, - 'field' => $field, - ]); - - if (empty($fieldContent)) { - if (null !== $fieldTranslation) { - $member->getRawTranslations()->removeElement($fieldTranslation); - $this->entityManager->remove($fieldTranslation); - } - } else { - if (null === $fieldTranslation) { - $fieldTranslation = new MemberTranslation($language, $field, $fieldContent); - } - $fieldTranslation->setContent($fieldContent); - - $member->addTranslation($fieldTranslation); - } - } } diff --git a/src/Model/SignupModel.php b/src/Model/SignupModel.php index 93a332ad26..30d82139fb 100644 --- a/src/Model/SignupModel.php +++ b/src/Model/SignupModel.php @@ -191,6 +191,9 @@ public function checkUsername(string $username): bool return true; } + /** + * Email is either taken directly or is currently changed and awaiting confirmation. + */ public function checkEmailAddress(string $email): bool { $memberRepository = $this->entityManager->getRepository(Member::class); @@ -200,6 +203,11 @@ public function checkEmailAddress(string $email): bool return false; } + $member = $memberRepository->findOneBy(['newEmail' => $email]); + if (null !== $member) { + return false; + } + return true; } } diff --git a/symfony.lock b/symfony.lock index c932adca35..37e6e4b613 100644 --- a/symfony.lock +++ b/symfony.lock @@ -766,6 +766,20 @@ "templates/base.html.twig" ] }, + "symfony/ux-translator": { + "version": "2.32", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "2.32", + "ref": "20e2abac415da4c3a9a6bafa059a6419beb74593" + }, + "files": [ + "./assets/translator.js", + "./config/packages/ux_translator.yaml", + "./var/translations/index.js" + ] + }, "symfony/validator": { "version": "7.3", "recipe": { diff --git a/templates/base.html.twig b/templates/base.html.twig index c61dfccc00..aa50491c21 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -1,134 +1,149 @@ {% import 'macros.twig' as macros %} - - - - - - - - - + + + + + + + + + - {%- if block('title') is defined %}{{ block('title') | trim }} | {% endif -%} BeWelcome + {%- if block('title') is defined %}{{ block('title') | trim }} | {% endif -%} BeWelcome - - - - {% block redirect %}{% endblock %} + + + + {% block redirect %}{% endblock %} - {{ encore_entry_link_tags('bewelcome') }} - {{ encore_entry_link_tags('tailwind') }} + {{ encore_entry_link_tags('bewelcome') }} + {{ encore_entry_link_tags('tailwind') }} - {% block stylesheets %}{% endblock %} + {% block stylesheets %}{% endblock %} - {% block matomo %}{% endblock %} - + {% block matomo %}{% endblock %} + - - - - - {% set sidebar = (submenu is defined) %} - {% include 'menu.html.twig' %} - {% block main %} -
-
-
- {% set contentBlock %} - {% set loggedInMember = app.user %} - {% if not loggedInMember is null and not hide_finish_setup is defined %} - {% if loggedInMember.status == constant('App\\Doctrine\\MemberStatusType::MAIL_CONFIRMED') or - loggedInMember.status == constant('App\\Doctrine\\MemberStatusType::AWAITING_MAIL_CONFIRMATION') %} -
-
{% set deletionDate = app.user.created|date_modify("+7 days")|format_date('long') %} - {{ 'profile.finish.setup'|trans({deletion_date: deletionDate})|raw }}
-
{{ 'profile.finish.setup.button'|trans }}
-
- {% endif %} - {% if loggedInMember.status == constant('App\\Doctrine\\MemberStatusType::ACCOUNT_ACTIVATED') %} -
-
{{ 'member.mail.not.confirmed'|trans|raw }}
-
{{ 'profile.confirm.email.button'|trans }}
-
+ + + + +{% set sidebar = (submenu is defined) %} +{% include 'menu.html.twig' %} +{% block main %} +
+
+
+ {% set contentBlock %} + {% set loggedInMember = app.user %} + {% if not loggedInMember is null %} + {% if not hide_finish_setup is defined %} + {% if loggedInMember.status == constant('App\\Doctrine\\MemberStatusType::MAIL_CONFIRMED') or + loggedInMember.status == constant('App\\Doctrine\\MemberStatusType::AWAITING_MAIL_CONFIRMATION') %} +
+
{% set deletionDate = app.user.created|date_modify("+7 days")|format_date('long') %} + {{ 'profile.finish.setup'|trans({deletion_date: deletionDate})|raw }}
+
{{ 'profile.finish.setup.button'|trans }} +
+
+ {% endif %} + {% if loggedInMember.status == constant('App\\Doctrine\\MemberStatusType::ACCOUNT_ACTIVATED') %} +
+
{{ 'member.mail.not.confirmed'|trans|raw }}
+
{{ 'profile.confirm.email.button'|trans }} +
+
+ {% endif %} + {% if not loggedInMember.newEmail is null %} +
+
{{ 'change.email.not.confirmed'|trans|raw }}
+
{{ 'profile.confirm.email.button'|trans }} +
+
{% endif %} {% endif %} - {% for label, messages in app.flashes %} -
- {% for message in messages %} -
-
- {{ message | raw }} -
+ {% endif %} + {% for label, messages in app.flashes %} +
+ {% for message in messages %} +
+
+ {{ message | raw }}
- {% endfor %} +
+ {% endfor %} +
+ {% endfor %} + {% block content %}{% endblock %} + {% endset %} +
+ {% if sidebar %} +
+
+

+ +

+ {{ contentBlock | raw }}
- {% endfor %} - {% block content %}{% endblock %} - {% endset %} -
- {% if sidebar %} -
-
-

- -

- {{ contentBlock | raw }} + - {% else %} - {{ contentBlock | raw }} - {% endif %} -
-
- {% endblock main %} - {% include 'footer.html.twig' %} - {{ encore_entry_script_tags('bewelcome') }} - {{ encore_entry_script_tags('updatecounters') }} - {% if sidebar %} - {{ encore_entry_script_tags('offcanvas') }} - {% endif %} - {% block javascripts %}{% endblock javascripts %} - + + {% else %} + {{ contentBlock | raw }} + {% endif %} + +
+{% endblock main %} +{% include 'footer.html.twig' %} +{{ encore_entry_script_tags('bewelcome') }} +{{ encore_entry_script_tags('updatecounters') }} +{% if sidebar %} + {{ encore_entry_script_tags('offcanvas') }} +{% endif %} +{% block javascripts %}{% endblock javascripts %} + diff --git a/templates/ckeditor/ckeditor.html.twig b/templates/ckeditor/ckeditor.html.twig index 5ef317aeec..ec96ba167f 100644 --- a/templates/ckeditor/ckeditor.html.twig +++ b/templates/ckeditor/ckeditor.html.twig @@ -7,14 +7,14 @@ {% if editor_type == constant('App\\Form\\CkEditorType::EDITOR_TYPE_DECOUPLED') %}
{% endif %} -
+
{{ value|raw }}
{% if editor_type == constant('App\\Form\\CkEditorType::EDITOR_TYPE_INLINE') %}
{{ 'profile.edit'|trans() }}
{% endif %} +
- {% else %} {% set attr = attr|merge({'data-editor-type': editor_type}) %} {% set attr = attr|merge({'data-image-upload': image_upload ? 'yes' : 'no'}) %} diff --git a/templates/emails/newemail.html.twig b/templates/emails/newemail.html.twig new file mode 100644 index 0000000000..ff44ba3d59 --- /dev/null +++ b/templates/emails/newemail.html.twig @@ -0,0 +1,9 @@ +{% extends 'emails/email.html.twig' %} + +{% block content%} +

{{ 'email.change'|trans }}

+

{{ 'email.change.confirm.link'|trans({'email_address': email_address}) }}

+

{{ 'email.change.confirm.email.address'|trans }}

+

{{ 'email.change.confirm.key'|trans }}

+

+{% endblock content %} diff --git a/templates/landing/widget/accommodation.html.twig b/templates/landing/widget/accommodation.html.twig index 83f9477f4c..caeec5d04d 100644 --- a/templates/landing/widget/accommodation.html.twig +++ b/templates/landing/widget/accommodation.html.twig @@ -1,10 +1,10 @@
-
diff --git a/templates/landing/widget/profilepicturewithaccommodation.html.twig b/templates/landing/widget/profilepicturewithaccommodation.html.twig index 14b5a3381c..25fb1c0aed 100644 --- a/templates/landing/widget/profilepicturewithaccommodation.html.twig +++ b/templates/landing/widget/profilepicturewithaccommodation.html.twig @@ -3,12 +3,12 @@
{{ macros.roundedavatarstack( member.Username, 120, false, true) }} {% if not member.Accommodation is null %} -
- {% if member.Accommodation == 'anytime' %} +
+ {% if member.Accommodation == constant('App\\Doctrine\\AccommodationType::YES') %} - {% elseif member.Accommodation == 'neverask' %} + {% elseif member.Accommodation == constant('App\\Doctrine\\AccommodationType::NO') %} diff --git a/templates/macros.twig b/templates/macros.twig index 677f9959f8..bd6d60a586 100644 --- a/templates/macros.twig +++ b/templates/macros.twig @@ -208,12 +208,15 @@ {% endif %} {% endmacro %} -{% macro field_translation(form, language, field, translations) %} -{% import _self as macros %} +{% macro field_translation(form, language, field, username, translations) %} + {% import _self as macros %}
{{ form.vars.label|trans({}, 'messages', language) }}
- {{ form_widget(form) }} + {% set attr = form.vars.attr|merge({'data-language': language}) %} + {% set attr = attr|merge({'data-field': field}) %} + {% set attr = attr|merge({'data-username': username}) %} + {{ form_widget(form, {attr: attr}) }} {{ _self.field_english(language, field, translations) }}
diff --git a/templates/profile/accommodation_widget.html.twig b/templates/profile/accommodation_widget.html.twig new file mode 100644 index 0000000000..157293523c --- /dev/null +++ b/templates/profile/accommodation_widget.html.twig @@ -0,0 +1,40 @@ +
+ Accommodation + {{ form_errors(form.accommodation) }} +
+ + +
+ {{ form_help(form.accommodation) }} +
+{% do form.accommodation.setRendered %} + +
+ {{ form_label(form.hosting_interest) }} + {{ form_widget(form.hosting_interest) }} +
+

{{ 'hosting.interest.select'|trans }}

+
+ {{ form_help(form.hosting_interest) }} +
diff --git a/templates/profile/account.edit.html.twig b/templates/profile/account.edit.html.twig index 6fad4ce7b9..8c6e46793c 100644 --- a/templates/profile/account.edit.html.twig +++ b/templates/profile/account.edit.html.twig @@ -1,36 +1,68 @@ {% extends 'profile/profile.html.twig' %} +{% block javascripts %} + {{ parent() }} + {{ encore_entry_script_tags('profile/account') }} +{% endblock %} + +{% block stylesheets %} + {{ parent() }} + {{ encore_entry_link_tags('profile/account') }} +{% endblock %} + {% block page_content %} - {{ form_start(form) }} - {{ form_row(form.name) }} - {{ form_row(form.show_name) }} - {{ form_row(form.short_name) }} - - -
- {% for child in form.gender %} -
- -
- {% do child.setRendered %} - {% endfor %} -
- {{ form_row(form.show_gender) }} - {{ form_row(form.birthdate) }} - {{ form_row(form.show_age) }} +
+
+
+ {{ 'account.edit'|trans }} +
+
+
- {{ form_row(form.address) }} - {{ form_row(form.show_address) }} + {{ form_start(form) }} + {{ form_errors(form) }} - {{ form_row(form.email) }} + {{ form_label(form.name) }} + {{ form_widget(form.name) }} + {{ form_errors(form.name) }} + {{ form_help(form.name) }} -
- -
+ {{ form_row(form.show_name) }} + {{ form_row(form.short_name) }} + + +
+ {% for child in form.gender %} +
+ +
+ {% do child.setRendered %} + {% endfor %} +
+ {{ form_row(form.show_gender) }} - {{ form_end(form) }} + {{ form_label(form.birthdate) }} + {{ form_widget(form.birthdate) }} + {{ form_errors(form.birthdate) }} + {{ form_help(form.birthdate) }} + + {{ form_row(form.show_age) }} + + {{ form_label(form.email) }} + {{ form_widget(form.email) }} + {{ form_errors(form.email) }} + {{ form_help(form.email) }} + +
+ +
+ {{ form_end(form) }} +
+
+
+
{% endblock page_content %} diff --git a/templates/profile/content.html.twig b/templates/profile/content.html.twig index 2b86d0536a..720290723e 100644 --- a/templates/profile/content.html.twig +++ b/templates/profile/content.html.twig @@ -436,7 +436,7 @@ {% set houseRules = profileTranslation['HouseRules'] ?? '' %} {% if restrictions|length > 0 or houseRules|length > 0 or edit %}
{{ 'profilehouserules'|trans({}, "messages", profile_language)|raw }}
+ class="h5 u:mt-8">{{ 'profilehouserules'|trans({}, "messages", profile_language)|raw }}
{% for restriction in restrictions %} {{ ('restriction_' ~ restriction)|lower|trans({}, "messages", profile_language) }} @@ -448,7 +448,7 @@
{% endif %} - {% if member.accommodation == constant('App\\Doctrine\\AccommodationType::YES') %} + {% if not edit and member.accommodation == constant('App\\Doctrine\\AccommodationType::YES') %}
{{ form_start(form) }} - {{ macros.field_translation(form.about_me, language, 'AboutMe', member.translations) }} - {{ macros.field_translation(form.occupation, language, 'Occupation', member.translations) }} - {{ macros.field_translation(form.offer_hosts, language, 'OfferHosts', member.translations) }} - -{# {{ form_widget(form.about_me) }} - {{ macros.field_english(language, 'AboutMe', member.translations) }} - - {{ form_label(form.occupation) }} - {{ form_widget(form.occupation) }} - {{ macros.field_english(language, 'Occupation', member.translations) }} - - {{ form_label(form.offer_hosts) }} - {{ form_widget(form.offer_hosts) }} - {{ macros.field_english(language, 'OfferHosts', member.translations) }} -#} + {% set username = member.username %} + {% set translations = member.translations %} + {{ macros.field_translation(form.about_me, language, 'AboutMe', username, translations) }} + {{ macros.field_translation(form.occupation, language, 'Occupation', username, translations) }} + {{ macros.field_translation(form.offer_hosts, language, 'OfferHosts', username, translations) }} {{ form_end(form) }}
diff --git a/templates/profile/edit/accommodation.html.twig b/templates/profile/edit/accommodation.html.twig index e0b95f8f84..1813dc5de3 100644 --- a/templates/profile/edit/accommodation.html.twig +++ b/templates/profile/edit/accommodation.html.twig @@ -5,22 +5,6 @@ {{ parent() }} {{ encore_entry_script_tags('roxeditor') }} {{ encore_entry_script_tags('profile/edit_accommodation') }} - {% endblock %} {% block stylesheets %} @@ -39,96 +23,64 @@ {{ form_start(form) }} {{ form_errors(form) }} -
- Accommodation - {{ form_errors(form.accommodation) }} -
- - -
- {{ form_help(form.accommodation) }} -
- {% do form.accommodation.setRendered %} - -
- {{ form_label(form.hosting_interest) }} - {{ form_widget(form.hosting_interest) }} -
-

{{ 'hosting.interest.set'|trans }}

-
- {{ form_help(form.hosting_interest) }} -
+ {% include 'profile/accommodation_widget.html.twig' %} {{ form_row(form.max_guests) }} - {{ macros.field_translation(form.length_of_stay, language, 'MaxLengthOfStay', member.translations) }} - {{ macros.field_translation(form.i_live_with, language, 'ILiveWith', member.translations) }} - {{ macros.field_translation(form.please_bring, language, 'PleaseBring', member.translations) }} + {% set username = member.username %} + {% set translations = member.translations %} + {{ macros.field_translation(form.length_of_stay, language, 'MaxLengthOfStay', username, translations) }} + {{ macros.field_translation(form.i_live_with, language, 'ILiveWith', username, translations) }} + {{ macros.field_translation(form.please_bring, language, 'PleaseBring', username, translations) }} - + {{ form.offers.vars.label|trans }}
{% for child in form.offers.children %}
-
+
{% do child.setRendered %} {% endfor %}
- +
{% do form.wheelchair_accessible.setRendered %}
- {{ macros.field_translation(form.offer_guests, language, 'OfferGuests', member.translations) }} - {{ macros.field_translation(form.where_you_sleep, language, 'WhereYouSleep', member.translations) }} - {{ macros.field_translation(form.additional_info, language, 'AdditionalInfo', member.translations) }} + {{ macros.field_translation(form.offer_guests, language, 'OfferGuests', username, translations) }} + {{ macros.field_translation(form.where_you_sleep, language, 'WhereYouSleep', username, translations) }} + {{ macros.field_translation(form.additional_info, language, 'AdditionalAccommodationInfo', username, translations) }} - + {{ form.restrictions.vars.label|trans }}
{% for child in form.restrictions.children %}
+ >
{% do child.setRendered %} {% endfor %}
- {{ macros.field_translation(form.house_rules, language, 'OtherRestrictions', member.translations) }} - {{ macros.field_translation(form.getting_there, language, 'PublicTransport', member.translations) }} + {{ macros.field_translation(form.house_rules, language, 'HouseRules', username, translations) }} + {{ macros.field_translation(form.getting_there, language, 'GettingThere', username, translations) }}
- + {{ 'profile.back'|trans({}, "messages", language) }} - {{ ('lang_' ~ language)|trans({}, "messages", language) }}
{{ form_end(form) }} diff --git a/templates/profile/edit/languages.html.twig b/templates/profile/edit/languages.html.twig index e6ea39e786..eb6ef82a28 100644 --- a/templates/profile/edit/languages.html.twig +++ b/templates/profile/edit/languages.html.twig @@ -68,7 +68,6 @@ {{ form_errors(language_level.level) }} -
{% endfor %}
diff --git a/templates/profile/edit/my_interests.html.twig b/templates/profile/edit/my_interests.html.twig index a7050a871f..baf7fac074 100644 --- a/templates/profile/edit/my_interests.html.twig +++ b/templates/profile/edit/my_interests.html.twig @@ -21,14 +21,16 @@
{{ form_start(form) }} - {{ macros.field_translation(form.hobbies, language, 'Hobbies', member.translations) }} - {{ macros.field_translation(form.books, language, 'Books', member.translations) }} - {{ macros.field_translation(form.music, language, 'Music', member.translations) }} - {{ macros.field_translation(form.movies, language, 'Movies', member.translations) }} - {{ macros.field_translation(form.organizations, language, 'Organizations', member.translations) }} + {% set username = member.username %} + {% set translations = member.translations %} + {{ macros.field_translation(form.hobbies, language, 'Hobbies', username, translations) }} + {{ macros.field_translation(form.books, language, 'Books', username, translations) }} + {{ macros.field_translation(form.music, language, 'Music', username, translations) }} + {{ macros.field_translation(form.movies, language, 'Movies', username, translations) }} + {{ macros.field_translation(form.organizations, language, 'Organizations', username, translations) }} {{ form_end(form) }}
diff --git a/templates/profile/edit/travel_experiences.html.twig b/templates/profile/edit/travel_experiences.html.twig index bdfd7aaf58..b07b86ea7b 100644 --- a/templates/profile/edit/travel_experiences.html.twig +++ b/templates/profile/edit/travel_experiences.html.twig @@ -21,11 +21,13 @@
{{ form_start(form) }} - {{ macros.field_translation(form.past, language, 'PastTrips', member.translations) }} - {{ macros.field_translation(form.planned, language, 'PlannedTrips', member.translations) }} + {% set username = member.username %} + {% set translations = member.translations %} + {{ macros.field_translation(form.past, language, 'PastTrips', username, translations) }} + {{ macros.field_translation(form.planned, language, 'PlannedTrips', username, translations) }} {{ form_end(form) }}
diff --git a/templates/signup/confirm.email.html.twig b/templates/signup/confirm.email.html.twig index 2670c24814..2584f228f5 100644 --- a/templates/signup/confirm.email.html.twig +++ b/templates/signup/confirm.email.html.twig @@ -1,4 +1,4 @@ -{% extends 'base.html.twig' %} +{% extends 'profile/profile.html.twig' %} {% block stylesheets %} {{ encore_entry_link_tags('signup/finalize') }} diff --git a/templates/signup/finalize.html.twig b/templates/signup/finalize.html.twig index 0a9e1ee098..67a38926ad 100644 --- a/templates/signup/finalize.html.twig +++ b/templates/signup/finalize.html.twig @@ -2,22 +2,6 @@ {% block javascripts %} {{ encore_entry_script_tags('signup/finalize') }} - {% endblock %} {% block stylesheets %} @@ -83,44 +67,8 @@ {{ form_widget(finalize.location.latitude) }} {{ form_widget(finalize.location.longitude) }} -
- Accommodation - {{ form_errors(finalize.accommodation) }} -
- - -
- {{ form_help(finalize.accommodation) }} -
- {% do finalize.accommodation.setRendered %} + {% include 'profile/accommodation_widget.html.twig' with {form: finalize} %} -
- {{ form_label(finalize.hosting_interest) }} - {{ form_widget(finalize.hosting_interest) }} -
-

{{ 'hosting.interest.set'|trans }}

-
- {{ form_help(finalize.hosting_interest) }} -

{{ 'signup.optional'|trans }}

{{ form_row(finalize.newsletters) }} diff --git a/tools/routing/roxfrontrouter.class.php b/tools/routing/roxfrontrouter.class.php index 265e6b3f51..09cce496e9 100644 --- a/tools/routing/roxfrontrouter.class.php +++ b/tools/routing/roxfrontrouter.class.php @@ -116,7 +116,7 @@ public function setLanguage() // Set id of language $Model = new RoxFrontRouterModel(); $lang = $Model->getLanguage($this->session->get('_locale')); - $this->session->set('IdLanguage', $lang->id); + $this->session->set('IdLanguage', $lang->ShortCode); } if (!($this->session->has('lang')) ) { $Model = new RoxFrontRouterModel; diff --git a/translations/missing/languages.yaml b/translations/missing/languages.yaml index 48b348b6b7..c86dbe4274 100644 --- a/translations/missing/languages.yaml +++ b/translations/missing/languages.yaml @@ -19,5 +19,5 @@ language.delete.yes: - 'Yes' - Button label to confirm that the profile language shall be deleted language.deleted.flash: - - Deleted {{language}} content of the profile. - - Flash message shown if a profile translation gets deleted. Make sure to keep the {{ language }} tag. + - Deleted {language} content of the profile. + - Flash message shown if a profile translation gets deleted. Make sure to keep the { language } tag. diff --git a/translations/missing/profile.yaml b/translations/missing/profile.yaml index a8a9948538..0a4b18486f 100644 --- a/translations/missing/profile.yaml +++ b/translations/missing/profile.yaml @@ -59,7 +59,7 @@ profile.no.about.me: - 'Shown if a member didn''t provide any information about themselves in about me.' profile.joined: - 'Joined:' - - Used with the date a member created their profile. Like ''Joined: 10 years ago.'' + - 'Used with the date a member created their profile. Like ''Joined: 10 years ago.''' profile.last.activity: - 'Last activity:' - Shows the last time a user accessed the site (not the last time they logged in which might be remembered). diff --git a/webpack.config.js b/webpack.config.js index cd60a5e6d6..071b0e3162 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -43,6 +43,7 @@ Encore .addEntry('profile/profile', './assets/js/profile/profile.js') .addEntry('profile/show', './assets/js/profile/show.js') .addEntry('profile/edit', './assets/js/profile/edit.js') + .addEntry('profile/account', './assets/js/profile/account.js') .addEntry('profile/edit_languages', './assets/js/profile/edit_languages.js') .addEntry('profile/edit_accommodation', './assets/js/profile/edit_accommodation.js') .addEntry('profile/setlocation', './assets/js/profile/setlocation.js') diff --git a/yarn.lock b/yarn.lock index a0c566b382..523ac2ce5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16,34 +16,34 @@ jsonpointer "^5.0.0" leven "^3.1.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" - integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.28.6.tgz#72499312ec58b1e2245ba4a4f550c132be4982f7" + integrity sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q== dependencies: - "@babel/helper-validator-identifier" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" js-tokens "^4.0.0" picocolors "^1.1.1" -"@babel/compat-data@^7.27.2", "@babel/compat-data@^7.27.7", "@babel/compat-data@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.5.tgz#a8a4962e1567121ac0b3b487f52107443b455c7f" - integrity sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA== +"@babel/compat-data@^7.27.7", "@babel/compat-data@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.6.tgz#103f466803fa0f059e82ccac271475470570d74c" + integrity sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg== "@babel/core@^7.11.1", "@babel/core@^7.14.2": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.5.tgz#4c81b35e51e1b734f510c99b07dfbc7bbbb48f7e" - integrity sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.28.5" - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-module-transforms" "^7.28.3" - "@babel/helpers" "^7.28.4" - "@babel/parser" "^7.28.5" - "@babel/template" "^7.27.2" - "@babel/traverse" "^7.28.5" - "@babel/types" "^7.28.5" + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.6.tgz#531bf883a1126e53501ba46eb3bb414047af507f" + integrity sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/generator" "^7.28.6" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helpers" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" "@jridgewell/remapping" "^2.3.5" convert-source-map "^2.0.0" debug "^4.1.0" @@ -51,13 +51,13 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.5.tgz#712722d5e50f44d07bc7ac9fe84438742dd61298" - integrity sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ== +"@babel/generator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.6.tgz#48dcc65d98fcc8626a48f72b62e263d25fc3c3f1" + integrity sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw== dependencies: - "@babel/parser" "^7.28.5" - "@babel/types" "^7.28.5" + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" "@jridgewell/gen-mapping" "^0.3.12" "@jridgewell/trace-mapping" "^0.3.28" jsesc "^3.0.2" @@ -69,31 +69,31 @@ dependencies: "@babel/types" "^7.27.3" -"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" - integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== +"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2", "@babel/helper-compilation-targets@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25" + integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== dependencies: - "@babel/compat-data" "^7.27.2" + "@babel/compat-data" "^7.28.6" "@babel/helper-validator-option" "^7.27.1" browserslist "^4.24.0" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.27.1", "@babel/helper-create-class-features-plugin@^7.28.3": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz#472d0c28028850968979ad89f173594a6995da46" - integrity sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ== +"@babel/helper-create-class-features-plugin@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz#611ff5482da9ef0db6291bcd24303400bca170fb" + integrity sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow== dependencies: "@babel/helper-annotate-as-pure" "^7.27.3" "@babel/helper-member-expression-to-functions" "^7.28.5" "@babel/helper-optimise-call-expression" "^7.27.1" - "@babel/helper-replace-supers" "^7.27.1" + "@babel/helper-replace-supers" "^7.28.6" "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" - "@babel/traverse" "^7.28.5" + "@babel/traverse" "^7.28.6" semver "^6.3.1" -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.27.1": +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.27.1", "@babel/helper-create-regexp-features-plugin@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz#7c1ddd64b2065c7f78034b25b43346a7e19ed997" integrity sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw== @@ -118,7 +118,7 @@ resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== -"@babel/helper-member-expression-to-functions@^7.27.1", "@babel/helper-member-expression-to-functions@^7.28.5": +"@babel/helper-member-expression-to-functions@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz#f3e07a10be37ed7a63461c63e6929575945a6150" integrity sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg== @@ -126,22 +126,22 @@ "@babel/traverse" "^7.28.5" "@babel/types" "^7.28.5" -"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" - integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== +"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.27.1", "@babel/helper-module-imports@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c" + integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== dependencies: - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" -"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" - integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== +"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.3", "@babel/helper-module-transforms@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e" + integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@babel/traverse" "^7.28.3" + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.6" "@babel/helper-optimise-call-expression@^7.27.1": version "7.27.1" @@ -150,10 +150,10 @@ dependencies: "@babel/types" "^7.27.1" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" - integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz#6f13ea251b68c8532e985fd532f28741a8af9ac8" + integrity sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug== "@babel/helper-remap-async-to-generator@^7.27.1": version "7.27.1" @@ -164,14 +164,14 @@ "@babel/helper-wrap-function" "^7.27.1" "@babel/traverse" "^7.27.1" -"@babel/helper-replace-supers@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz#b1ed2d634ce3bdb730e4b52de30f8cccfd692bc0" - integrity sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA== +"@babel/helper-replace-supers@^7.27.1", "@babel/helper-replace-supers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz#94aa9a1d7423a00aead3f204f78834ce7d53fe44" + integrity sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg== dependencies: - "@babel/helper-member-expression-to-functions" "^7.27.1" + "@babel/helper-member-expression-to-functions" "^7.28.5" "@babel/helper-optimise-call-expression" "^7.27.1" - "@babel/traverse" "^7.27.1" + "@babel/traverse" "^7.28.6" "@babel/helper-skip-transparent-expression-wrappers@^7.27.1": version "7.27.1" @@ -186,7 +186,7 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== -"@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.28.5": +"@babel/helper-validator-identifier@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== @@ -197,28 +197,28 @@ integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== "@babel/helper-wrap-function@^7.27.1": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz#fe4872092bc1438ffd0ce579e6f699609f9d0a7a" - integrity sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz#4e349ff9222dab69a93a019cc296cdd8442e279a" + integrity sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ== dependencies: - "@babel/template" "^7.27.2" - "@babel/traverse" "^7.28.3" - "@babel/types" "^7.28.2" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" -"@babel/helpers@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" - integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== +"@babel/helpers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.6.tgz#fca903a313ae675617936e8998b814c415cbf5d7" + integrity sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw== dependencies: - "@babel/template" "^7.27.2" - "@babel/types" "^7.28.4" + "@babel/template" "^7.28.6" + "@babel/types" "^7.28.6" -"@babel/parser@^7.27.2", "@babel/parser@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.5.tgz#0b0225ee90362f030efd644e8034c99468893b08" - integrity sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ== +"@babel/parser@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.6.tgz#f01a8885b7fa1e56dd8a155130226cd698ef13fd" + integrity sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ== dependencies: - "@babel/types" "^7.28.5" + "@babel/types" "^7.28.6" "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.28.5": version "7.28.5" @@ -251,39 +251,39 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" "@babel/plugin-transform-optional-chaining" "^7.27.1" -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz#373f6e2de0016f73caf8f27004f61d167743742a" - integrity sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw== +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz#0e8289cec28baaf05d54fd08d81ae3676065f69f" + integrity sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/traverse" "^7.28.3" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/traverse" "^7.28.6" "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": version "7.21.0-placeholder-for-preset-env.2" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== -"@babel/plugin-syntax-import-assertions@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz#88894aefd2b03b5ee6ad1562a7c8e1587496aecd" - integrity sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg== +"@babel/plugin-syntax-import-assertions@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz#ae9bc1923a6ba527b70104dd2191b0cd872c8507" + integrity sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-syntax-import-attributes@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" - integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== +"@babel/plugin-syntax-import-attributes@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz#b71d5914665f60124e133696f17cd7669062c503" + integrity sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-syntax-jsx@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" - integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== +"@babel/plugin-syntax-jsx@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz#f8ca28bbd84883b5fea0e447c635b81ba73997ee" + integrity sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" @@ -300,22 +300,22 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-async-generator-functions@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz#1276e6c7285ab2cd1eccb0bc7356b7a69ff842c2" - integrity sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q== +"@babel/plugin-transform-async-generator-functions@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.6.tgz#80cb86d3eaa2102e18ae90dd05ab87bdcad3877d" + integrity sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-remap-async-to-generator" "^7.27.1" - "@babel/traverse" "^7.28.0" + "@babel/traverse" "^7.28.6" -"@babel/plugin-transform-async-to-generator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz#9a93893b9379b39466c74474f55af03de78c66e7" - integrity sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA== +"@babel/plugin-transform-async-to-generator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz#bd97b42237b2d1bc90d74bcb486c39be5b4d7e77" + integrity sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g== dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-remap-async-to-generator" "^7.27.1" "@babel/plugin-transform-block-scoped-functions@^7.27.1": @@ -325,50 +325,50 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-block-scoping@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz#e0d3af63bd8c80de2e567e690a54e84d85eb16f6" - integrity sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g== +"@babel/plugin-transform-block-scoping@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz#e1ef5633448c24e76346125c2534eeb359699a99" + integrity sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-class-properties@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz#dd40a6a370dfd49d32362ae206ddaf2bb082a925" - integrity sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA== +"@babel/plugin-transform-class-properties@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz#d274a4478b6e782d9ea987fda09bdb6d28d66b72" + integrity sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-class-static-block@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz#d1b8e69b54c9993bc558203e1f49bfc979bfd852" - integrity sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg== +"@babel/plugin-transform-class-static-block@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz#1257491e8259c6d125ac4d9a6f39f9d2bf3dba70" + integrity sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ== dependencies: - "@babel/helper-create-class-features-plugin" "^7.28.3" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-classes@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz#75d66175486788c56728a73424d67cbc7473495c" - integrity sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA== +"@babel/plugin-transform-classes@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz#8f6fb79ba3703978e701ce2a97e373aae7dda4b7" + integrity sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q== dependencies: "@babel/helper-annotate-as-pure" "^7.27.3" - "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-compilation-targets" "^7.28.6" "@babel/helper-globals" "^7.28.0" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-replace-supers" "^7.27.1" - "@babel/traverse" "^7.28.4" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-replace-supers" "^7.28.6" + "@babel/traverse" "^7.28.6" -"@babel/plugin-transform-computed-properties@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz#81662e78bf5e734a97982c2b7f0a793288ef3caa" - integrity sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw== +"@babel/plugin-transform-computed-properties@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz#936824fc71c26cb5c433485776d79c8e7b0202d2" + integrity sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/template" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/template" "^7.28.6" -"@babel/plugin-transform-destructuring@^7.28.0", "@babel/plugin-transform-destructuring@^7.28.5": +"@babel/plugin-transform-destructuring@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz#b8402764df96179a2070bb7b501a1586cf8ad7a7" integrity sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw== @@ -376,13 +376,13 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/traverse" "^7.28.5" -"@babel/plugin-transform-dotall-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz#aa6821de864c528b1fecf286f0a174e38e826f4d" - integrity sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw== +"@babel/plugin-transform-dotall-regex@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz#def31ed84e0fb6e25c71e53c124e7b76a4ab8e61" + integrity sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-duplicate-keys@^7.27.1": version "7.27.1" @@ -391,13 +391,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz#5043854ca620a94149372e69030ff8cb6a9eb0ec" - integrity sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ== +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.28.6.tgz#e0c59ba54f1655dd682f2edf5f101b5910a8f6f3" + integrity sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-dynamic-import@^7.27.1": version "7.27.1" @@ -406,20 +406,20 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-explicit-resource-management@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz#45be6211b778dbf4b9d54c4e8a2b42fa72e09a1a" - integrity sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ== +"@babel/plugin-transform-explicit-resource-management@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz#dd6788f982c8b77e86779d1d029591e39d9d8be7" + integrity sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-transform-destructuring" "^7.28.0" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-transform-destructuring" "^7.28.5" -"@babel/plugin-transform-exponentiation-operator@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz#7cc90a8170e83532676cfa505278e147056e94fe" - integrity sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw== +"@babel/plugin-transform-exponentiation-operator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz#5e477eb7eafaf2ab5537a04aaafcf37e2d7f1091" + integrity sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-export-namespace-from@^7.27.1": version "7.27.1" @@ -445,12 +445,12 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/traverse" "^7.27.1" -"@babel/plugin-transform-json-strings@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz#a2e0ce6ef256376bd527f290da023983527a4f4c" - integrity sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q== +"@babel/plugin-transform-json-strings@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz#4c8c15b2dc49e285d110a4cf3dac52fd2dfc3038" + integrity sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-literals@^7.27.1": version "7.27.1" @@ -459,12 +459,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-logical-assignment-operators@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz#d028fd6db8c081dee4abebc812c2325e24a85b0e" - integrity sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA== +"@babel/plugin-transform-logical-assignment-operators@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz#53028a3d77e33c50ef30a8fce5ca17065936e605" + integrity sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-member-expression-literals@^7.27.1": version "7.27.1" @@ -481,13 +481,13 @@ "@babel/helper-module-transforms" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-modules-commonjs@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz#8e44ed37c2787ecc23bdc367f49977476614e832" - integrity sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw== +"@babel/plugin-transform-modules-commonjs@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz#c0232e0dfe66a734cc4ad0d5e75fc3321b6fdef1" + integrity sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA== dependencies: - "@babel/helper-module-transforms" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-modules-systemjs@^7.28.5": version "7.28.5" @@ -522,30 +522,30 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-nullish-coalescing-operator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz#4f9d3153bf6782d73dd42785a9d22d03197bc91d" - integrity sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA== +"@babel/plugin-transform-nullish-coalescing-operator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz#9bc62096e90ab7a887f3ca9c469f6adec5679757" + integrity sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-numeric-separator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz#614e0b15cc800e5997dadd9bd6ea524ed6c819c6" - integrity sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw== +"@babel/plugin-transform-numeric-separator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz#1310b0292762e7a4a335df5f580c3320ee7d9e9f" + integrity sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-object-rest-spread@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz#9ee1ceca80b3e6c4bac9247b2149e36958f7f98d" - integrity sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew== +"@babel/plugin-transform-object-rest-spread@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz#fdd4bc2d72480db6ca42aed5c051f148d7b067f7" + integrity sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA== dependencies: - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-transform-destructuring" "^7.28.0" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-transform-destructuring" "^7.28.5" "@babel/plugin-transform-parameters" "^7.27.7" - "@babel/traverse" "^7.28.4" + "@babel/traverse" "^7.28.6" "@babel/plugin-transform-object-super@^7.27.1": version "7.27.1" @@ -555,19 +555,19 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/helper-replace-supers" "^7.27.1" -"@babel/plugin-transform-optional-catch-binding@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz#84c7341ebde35ccd36b137e9e45866825072a30c" - integrity sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q== +"@babel/plugin-transform-optional-catch-binding@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz#75107be14c78385978201a49c86414a150a20b4c" + integrity sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-optional-chaining@^7.27.1", "@babel/plugin-transform-optional-chaining@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz#8238c785f9d5c1c515a90bf196efb50d075a4b26" - integrity sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ== +"@babel/plugin-transform-optional-chaining@^7.27.1", "@babel/plugin-transform-optional-chaining@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz#926cf150bd421fc8362753e911b4a1b1ce4356cd" + integrity sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" "@babel/plugin-transform-parameters@^7.27.7": @@ -577,22 +577,22 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-private-methods@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz#fdacbab1c5ed81ec70dfdbb8b213d65da148b6af" - integrity sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA== +"@babel/plugin-transform-private-methods@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz#c76fbfef3b86c775db7f7c106fff544610bdb411" + integrity sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg== dependencies: - "@babel/helper-create-class-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-private-property-in-object@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz#4dbbef283b5b2f01a21e81e299f76e35f900fb11" - integrity sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ== +"@babel/plugin-transform-private-property-in-object@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz#4fafef1e13129d79f1d75ac180c52aafefdb2811" + integrity sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA== dependencies: - "@babel/helper-annotate-as-pure" "^7.27.1" - "@babel/helper-create-class-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-property-literals@^7.27.1": version "7.27.1" @@ -616,15 +616,15 @@ "@babel/plugin-transform-react-jsx" "^7.27.1" "@babel/plugin-transform-react-jsx@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz#1023bc94b78b0a2d68c82b5e96aed573bcfb9db0" - integrity sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz#f51cb70a90b9529fbb71ee1f75ea27b7078eed62" + integrity sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow== dependencies: - "@babel/helper-annotate-as-pure" "^7.27.1" - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-syntax-jsx" "^7.27.1" - "@babel/types" "^7.27.1" + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-syntax-jsx" "^7.28.6" + "@babel/types" "^7.28.6" "@babel/plugin-transform-react-pure-annotations@^7.27.1": version "7.27.1" @@ -634,20 +634,20 @@ "@babel/helper-annotate-as-pure" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-regenerator@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz#9d3fa3bebb48ddd0091ce5729139cd99c67cea51" - integrity sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA== +"@babel/plugin-transform-regenerator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.6.tgz#6ca2ed5b76cff87980f96eaacfc2ce833e8e7a1b" + integrity sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/plugin-transform-regexp-modifiers@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz#df9ba5577c974e3f1449888b70b76169998a6d09" - integrity sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA== +"@babel/plugin-transform-regexp-modifiers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz#7ef0163bd8b4a610481b2509c58cf217f065290b" + integrity sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-reserved-words@^7.27.1": version "7.27.1" @@ -675,12 +675,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-spread@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz#1a264d5fc12750918f50e3fe3e24e437178abb08" - integrity sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q== +"@babel/plugin-transform-spread@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz#40a2b423f6db7b70f043ad027a58bcb44a9757b6" + integrity sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA== dependencies: - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" "@babel/plugin-transform-sticky-regex@^7.27.1": @@ -711,13 +711,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-unicode-property-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz#bdfe2d3170c78c5691a3c3be934c8c0087525956" - integrity sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q== +"@babel/plugin-transform-unicode-property-regex@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz#63a7a6c21a0e75dae9b1861454111ea5caa22821" + integrity sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-unicode-regex@^7.27.1": version "7.27.1" @@ -727,83 +727,83 @@ "@babel/helper-create-regexp-features-plugin" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-unicode-sets-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz#6ab706d10f801b5c72da8bb2548561fa04193cd1" - integrity sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw== +"@babel/plugin-transform-unicode-sets-regex@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz#924912914e5df9fe615ec472f88ff4788ce04d4e" + integrity sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/preset-env@^7.11.0", "@babel/preset-env@^7.26.0": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.28.5.tgz#82dd159d1563f219a1ce94324b3071eb89e280b0" - integrity sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.28.6.tgz#b4586bb59d8c61be6c58997f4912e7ea6bd17178" + integrity sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw== dependencies: - "@babel/compat-data" "^7.28.5" - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/compat-data" "^7.28.6" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-validator-option" "^7.27.1" "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.28.5" "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.27.1" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.27.1" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.27.1" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.28.3" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.28.6" "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-import-assertions" "^7.27.1" - "@babel/plugin-syntax-import-attributes" "^7.27.1" + "@babel/plugin-syntax-import-assertions" "^7.28.6" + "@babel/plugin-syntax-import-attributes" "^7.28.6" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" "@babel/plugin-transform-arrow-functions" "^7.27.1" - "@babel/plugin-transform-async-generator-functions" "^7.28.0" - "@babel/plugin-transform-async-to-generator" "^7.27.1" + "@babel/plugin-transform-async-generator-functions" "^7.28.6" + "@babel/plugin-transform-async-to-generator" "^7.28.6" "@babel/plugin-transform-block-scoped-functions" "^7.27.1" - "@babel/plugin-transform-block-scoping" "^7.28.5" - "@babel/plugin-transform-class-properties" "^7.27.1" - "@babel/plugin-transform-class-static-block" "^7.28.3" - "@babel/plugin-transform-classes" "^7.28.4" - "@babel/plugin-transform-computed-properties" "^7.27.1" + "@babel/plugin-transform-block-scoping" "^7.28.6" + "@babel/plugin-transform-class-properties" "^7.28.6" + "@babel/plugin-transform-class-static-block" "^7.28.6" + "@babel/plugin-transform-classes" "^7.28.6" + "@babel/plugin-transform-computed-properties" "^7.28.6" "@babel/plugin-transform-destructuring" "^7.28.5" - "@babel/plugin-transform-dotall-regex" "^7.27.1" + "@babel/plugin-transform-dotall-regex" "^7.28.6" "@babel/plugin-transform-duplicate-keys" "^7.27.1" - "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.27.1" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.28.6" "@babel/plugin-transform-dynamic-import" "^7.27.1" - "@babel/plugin-transform-explicit-resource-management" "^7.28.0" - "@babel/plugin-transform-exponentiation-operator" "^7.28.5" + "@babel/plugin-transform-explicit-resource-management" "^7.28.6" + "@babel/plugin-transform-exponentiation-operator" "^7.28.6" "@babel/plugin-transform-export-namespace-from" "^7.27.1" "@babel/plugin-transform-for-of" "^7.27.1" "@babel/plugin-transform-function-name" "^7.27.1" - "@babel/plugin-transform-json-strings" "^7.27.1" + "@babel/plugin-transform-json-strings" "^7.28.6" "@babel/plugin-transform-literals" "^7.27.1" - "@babel/plugin-transform-logical-assignment-operators" "^7.28.5" + "@babel/plugin-transform-logical-assignment-operators" "^7.28.6" "@babel/plugin-transform-member-expression-literals" "^7.27.1" "@babel/plugin-transform-modules-amd" "^7.27.1" - "@babel/plugin-transform-modules-commonjs" "^7.27.1" + "@babel/plugin-transform-modules-commonjs" "^7.28.6" "@babel/plugin-transform-modules-systemjs" "^7.28.5" "@babel/plugin-transform-modules-umd" "^7.27.1" "@babel/plugin-transform-named-capturing-groups-regex" "^7.27.1" "@babel/plugin-transform-new-target" "^7.27.1" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.27.1" - "@babel/plugin-transform-numeric-separator" "^7.27.1" - "@babel/plugin-transform-object-rest-spread" "^7.28.4" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.28.6" + "@babel/plugin-transform-numeric-separator" "^7.28.6" + "@babel/plugin-transform-object-rest-spread" "^7.28.6" "@babel/plugin-transform-object-super" "^7.27.1" - "@babel/plugin-transform-optional-catch-binding" "^7.27.1" - "@babel/plugin-transform-optional-chaining" "^7.28.5" + "@babel/plugin-transform-optional-catch-binding" "^7.28.6" + "@babel/plugin-transform-optional-chaining" "^7.28.6" "@babel/plugin-transform-parameters" "^7.27.7" - "@babel/plugin-transform-private-methods" "^7.27.1" - "@babel/plugin-transform-private-property-in-object" "^7.27.1" + "@babel/plugin-transform-private-methods" "^7.28.6" + "@babel/plugin-transform-private-property-in-object" "^7.28.6" "@babel/plugin-transform-property-literals" "^7.27.1" - "@babel/plugin-transform-regenerator" "^7.28.4" - "@babel/plugin-transform-regexp-modifiers" "^7.27.1" + "@babel/plugin-transform-regenerator" "^7.28.6" + "@babel/plugin-transform-regexp-modifiers" "^7.28.6" "@babel/plugin-transform-reserved-words" "^7.27.1" "@babel/plugin-transform-shorthand-properties" "^7.27.1" - "@babel/plugin-transform-spread" "^7.27.1" + "@babel/plugin-transform-spread" "^7.28.6" "@babel/plugin-transform-sticky-regex" "^7.27.1" "@babel/plugin-transform-template-literals" "^7.27.1" "@babel/plugin-transform-typeof-symbol" "^7.27.1" "@babel/plugin-transform-unicode-escapes" "^7.27.1" - "@babel/plugin-transform-unicode-property-regex" "^7.27.1" + "@babel/plugin-transform-unicode-property-regex" "^7.28.6" "@babel/plugin-transform-unicode-regex" "^7.27.1" - "@babel/plugin-transform-unicode-sets-regex" "^7.27.1" + "@babel/plugin-transform-unicode-sets-regex" "^7.28.6" "@babel/preset-modules" "0.1.6-no-external-plugins" babel-plugin-polyfill-corejs2 "^0.4.14" babel-plugin-polyfill-corejs3 "^0.13.0" @@ -833,505 +833,505 @@ "@babel/plugin-transform-react-pure-annotations" "^7.27.1" "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326" - integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ== - -"@babel/template@^7.27.1", "@babel/template@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" - integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/parser" "^7.27.2" - "@babel/types" "^7.27.1" - -"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4", "@babel/traverse@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.5.tgz#450cab9135d21a7a2ca9d2d35aa05c20e68c360b" - integrity sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.28.5" + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.6.tgz#d267a43cb1836dc4d182cce93ae75ba954ef6d2b" + integrity sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA== + +"@babel/template@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57" + integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.5", "@babel/traverse@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.6.tgz#871ddc79a80599a5030c53b1cc48cbe3a5583c2e" + integrity sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/generator" "^7.28.6" "@babel/helper-globals" "^7.28.0" - "@babel/parser" "^7.28.5" - "@babel/template" "^7.27.2" - "@babel/types" "^7.28.5" + "@babel/parser" "^7.28.6" + "@babel/template" "^7.28.6" + "@babel/types" "^7.28.6" debug "^4.3.1" -"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4", "@babel/types@^7.28.5", "@babel/types@^7.4.4": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.5.tgz#10fc405f60897c35f07e85493c932c7b5ca0592b" - integrity sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA== +"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.5", "@babel/types@^7.28.6", "@babel/types@^7.4.4": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.6.tgz#c3e9377f1b155005bcc4c46020e7e394e13089df" + integrity sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg== dependencies: "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.28.5" -"@ckeditor/ckeditor5-adapter-ckfinder@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-adapter-ckfinder/-/ckeditor5-adapter-ckfinder-47.3.0.tgz#bd60873b87e6c3e3d62cf2ddc3dda572fc2b3771" - integrity sha512-I0oE2wuyGSwCirHRj5i+IvBRKUrlmGCP7HMGv7fzXcHS1MW43LV0t9L8PQ/aKQX3gNmiqlfj631y/S7s5nqR8A== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-upload" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-alignment@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-alignment/-/ckeditor5-alignment-47.3.0.tgz#f5ea4fe5e3adce297732543658c0eb025d0c4d36" - integrity sha512-T01xV7UsS4D1VbyRdWxc68Wl4NN/Ov/4+2EsbjYF7O0UA0pJs8dWZJOZ+yGFJ6p8Aask991eu91vy3r/nq3d+g== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-autoformat@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-autoformat/-/ckeditor5-autoformat-47.3.0.tgz#b5096c419298ec5aa76f5508c435bc078826c690" - integrity sha512-1Np63YOsNMddrVHtsAPZUQvVuhMyvmwPwnPO3EHudPPDg8c5p+fbSb7DSUSPCUmkIKS8RJ8tv/3eDpS7y+EEXg== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-heading" "47.3.0" - "@ckeditor/ckeditor5-typing" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-autosave@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-autosave/-/ckeditor5-autosave-47.3.0.tgz#cdb6cfa38dcc6751e5e277165c4e9b47611b5698" - integrity sha512-ctYdlBcJ/CPUUcpRzCbCp3oG2HWn8gy7GZUL95C1BIZTH08cLKZgkX0TySSUHygMvVymgvWq3LrmwByWri9AvQ== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" +"@ckeditor/ckeditor5-adapter-ckfinder@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-adapter-ckfinder/-/ckeditor5-adapter-ckfinder-47.4.0.tgz#7ff01dc465a8cd71f7a1acd0e9f943649ffce9df" + integrity sha512-g90RXXOoyBL0hsUMo6/IsCKF6qlKtxYlwzeTch+XboZOxkvJmozETKY4mnkR+XI1xZeO1bqqzLe8sKiFRvG7Hg== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-upload" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-alignment@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-alignment/-/ckeditor5-alignment-47.4.0.tgz#a0d4fc432e1a8bcc15255cd383fbaf9ca2c37642" + integrity sha512-MI4PrumF62HZ5kG824WOhqtntDS6oPhmlFwg2vOd8L8fW1Gn4SgigvhqxARLi/OIf0ExnNcXFunS30B6lz1Ciw== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-autoformat@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-autoformat/-/ckeditor5-autoformat-47.4.0.tgz#1e14143970abd433ebfcc5b4b6ffcc65d86069fc" + integrity sha512-dYjPpSaIt8z8d7em+I54+S6Y0m/4fXX27DF6gXMHG+79TIzZxakHK096RJBxj3cIjpzSjHI+v9FQ1Y+nO/M79Q== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-heading" "47.4.0" + "@ckeditor/ckeditor5-typing" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-autosave@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-autosave/-/ckeditor5-autosave-47.4.0.tgz#e3d85027040d48eb46306c3fca0c0066b00e34a0" + integrity sha512-1DpjdGn+xXfYoeDd6SIcQbkUiOeHQbjN7qmjQWrd6JvowQ6loPtDPGL9OHmL4OFubrVn5GM4dS3E1+cU29SVHg== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-basic-styles@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-basic-styles/-/ckeditor5-basic-styles-47.3.0.tgz#d70613743e711ef01c498b06bdf37dafede8a36f" - integrity sha512-KGDZLyhVc+sF9o8XTiupNRdroALhLpfOssWQv8zzyu7Ak2LFYXCrrr3abscbIX2whL/X92sted11ktLaLmgL0w== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-typing" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-block-quote@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-block-quote/-/ckeditor5-block-quote-47.3.0.tgz#04a77edcfa099c80f8a3f19748873b2b4e6d74f7" - integrity sha512-Ik3buFYNpEYVkI5LnimDbHTOgHAYtkZ2qTwGT47wAvyScgQ9Jx0fcUBA6EjX2EuGr6w/snZfXkI4WsZqrMYp+g== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-enter" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-typing" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-bookmark@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-bookmark/-/ckeditor5-bookmark-47.3.0.tgz#e29a0cf99af562e07828802f72b293dc93f42505" - integrity sha512-Cn+O/Ayr9zcKk/v9dyP1SXbpFslLGCiinS6Nb8jQOS+pmxb1s32W/ycZBtAg0EYmTMskoVEkpwz6ugogNAzmaw== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-link" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - "@ckeditor/ckeditor5-widget" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-ckbox@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-ckbox/-/ckeditor5-ckbox-47.3.0.tgz#5a1da5d697745d7a41566840a68ab400f6c5921b" - integrity sha512-SVF3CGH7/DBSrsV/vMFIzyvSPAoD1Qg12A5dS+ySnG46XC8ou9uQXXAfIGzAvwajC8GF3LJf9nG4+vJx3tIE/A== - dependencies: - "@ckeditor/ckeditor5-cloud-services" "47.3.0" - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-image" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-upload" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" +"@ckeditor/ckeditor5-basic-styles@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-basic-styles/-/ckeditor5-basic-styles-47.4.0.tgz#c277f33868f80e071a9761e187a2c95c3b444bf8" + integrity sha512-nCVP7W5ryshBG7UfXuFRv58qb/HmSS9Gjb2UUM84ODLOjYPFxvzWgQ5bV5t+x1bYAT8z/Xqfv9Ycs9ywEwOA9A== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-typing" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-block-quote@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-block-quote/-/ckeditor5-block-quote-47.4.0.tgz#2288acbe34cb8e489e57b67192a0edc90510ab76" + integrity sha512-B1iX0p5ByU/y7AVREgevr0Kfobt9uT1n9rtXToXbA9W4u4yZIVJULpceTgDw+/OJNU8lyKbq/S/6trjYFsyf0Q== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-enter" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-typing" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-bookmark@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-bookmark/-/ckeditor5-bookmark-47.4.0.tgz#7cd5d324ad48992c8c4a5ad2b4a09bd07cb3b23e" + integrity sha512-XBAOfYpy0TdVqAXsBgKSKCD46S7kR/oohqP9UKTGUGrNjojW6FS1k1IxvcpRVATn0xPHjZld58wkwizIdeJveg== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-link" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + "@ckeditor/ckeditor5-widget" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-ckbox@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-ckbox/-/ckeditor5-ckbox-47.4.0.tgz#e9cdb256c318bfbb0263a23bfb25637f24923e81" + integrity sha512-Utk9nYwzVRLQXYVVR+oi3x4xN7C0lzt+ZUyPjBRf3k60ijP/OpA8lsJJWzonuEEsdELsLzaBNSivTa9hjLZLDA== + dependencies: + "@ckeditor/ckeditor5-cloud-services" "47.4.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-image" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-upload" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" blurhash "2.0.5" - ckeditor5 "47.3.0" + ckeditor5 "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-ckfinder@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-ckfinder/-/ckeditor5-ckfinder-47.3.0.tgz#2a41fc097c77d16bbf967c9e60d7010a5fd30192" - integrity sha512-OIDpmoHsw+ZRbhso3EvnSDEKkXZBgZTq7TQT7+TAg264SWuGB7y6UCKMMoA5OWpuqDJh/Wp8wBubTWqA3OwYmw== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-image" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-clipboard@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-clipboard/-/ckeditor5-clipboard-47.3.0.tgz#f19789ebe4729585f7fce11d44f302018b402399" - integrity sha512-fVBBWyWIaLTTUZglvOz+ld0QfQR8yr9TVwgk0XFN90S3UxFiYYkxgDAeef/o51qBhBGotgw8hGYYbY4k4G10mA== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - "@ckeditor/ckeditor5-widget" "47.3.0" +"@ckeditor/ckeditor5-ckfinder@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-ckfinder/-/ckeditor5-ckfinder-47.4.0.tgz#bde9e251ca17984fc8095bc1abdf8002d7b57e84" + integrity sha512-jXWwDfzFOn2S/oK84Io6cB7I0W9I7CwMyBfg5YbCEhYtv5aeNQBpRqwik/5cfmMrBMBXrPu1QRs60NIwegk/Eg== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-image" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-clipboard@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-clipboard/-/ckeditor5-clipboard-47.4.0.tgz#72558f987e559d91ddee17ce6594dbc96ade9fd0" + integrity sha512-LUR5yTXjHxLn8YLKrJj4/DBtqk6zdPg5SAVXkpNSz5UxU63aaj/L7jKCInr36Uy23Ov5TgT6FkgXPaBtakAqDA== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + "@ckeditor/ckeditor5-widget" "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-cloud-services@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-cloud-services/-/ckeditor5-cloud-services-47.3.0.tgz#5f4a647ee6709153e7062b1de268ed84bf86bc8f" - integrity sha512-oFHz/Aavs6IDU6XwQD9NUgssJs3hSv4Vu2Np5rkZIyhabKRJcNma7fwM+gmmvQJupltv0uG/0ldMigjfEqHAQA== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-code-block@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-code-block/-/ckeditor5-code-block-47.3.0.tgz#ea5552fe3f4cd1f2d3d6c382ba7ff2dea062a2b3" - integrity sha512-zgzlCFqqJxWRTvuIGl9jJ0KYGZIjsCOYHjj1s3+asXjuskRoSip6yzcPK/LPaQXkUYf9zTGJHQ9tqmiNbRQBiA== - dependencies: - "@ckeditor/ckeditor5-clipboard" "47.3.0" - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-enter" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-core@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-core/-/ckeditor5-core-47.3.0.tgz#7aa71f6174e05aa94102d24b412740c4851580e7" - integrity sha512-jLawN3a8yL5lbwG8gZeJihcVKkDgq+rAFeXc+Rd+nw+c5uGCdkc5F7PCRjhw+JOGruXUhNsbiF/4iNv3hUcO/A== - dependencies: - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - "@ckeditor/ckeditor5-watchdog" "47.3.0" +"@ckeditor/ckeditor5-cloud-services@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-cloud-services/-/ckeditor5-cloud-services-47.4.0.tgz#d3978b92528fe4600f8bacc5e8239a5ac7c4fbe5" + integrity sha512-6xUiyoMkcW8F/8OJrEGeKrMixRGLeQYHxij7tYyrXUqugdCJmZ5WNfvsoyVBwk7g3XQDSKnfKG28gSVBPirwBQ== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-code-block@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-code-block/-/ckeditor5-code-block-47.4.0.tgz#0898ebb555689eda97c50345eb2094e1e208dc9b" + integrity sha512-lfZd1Zu6FvHbOEXa1yJnuRDK0jYXZR0OaV9ek6A2ZQ6Z169Brc+aH1sTakw7r6S8m1clTz+vRH3UuVk7ETsQGA== + dependencies: + "@ckeditor/ckeditor5-clipboard" "47.4.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-enter" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-core@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-core/-/ckeditor5-core-47.4.0.tgz#16413d2fe25e456ddd97b67d3e2b4bdf1ff1b4c1" + integrity sha512-upV/3x9fhgFWxVVtwR47zCOAvZKgP8a8N7UQOFwfs3Tr52+oE1gULWKTiS9079MBaXaIqtM/EbelNdvBh4gOGg== + dependencies: + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + "@ckeditor/ckeditor5-watchdog" "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-easy-image@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-easy-image/-/ckeditor5-easy-image-47.3.0.tgz#f08994188ff8c5de6b56c0f0834f2b12828e313c" - integrity sha512-pdQHtLBdkDuY59FzzgyTjS6XM5aJlSUW33sOSfN0I/iROl6LpSr1kHjf6ybJAAWEhSD6B+o6hv4+K+tx184UpA== - dependencies: - "@ckeditor/ckeditor5-cloud-services" "47.3.0" - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-upload" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-editor-balloon@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-balloon/-/ckeditor5-editor-balloon-47.3.0.tgz#100bd48cdf643cdf854501ecff3b284d77ba0f30" - integrity sha512-8EzuV48gTuqrNd5rt58rp7eWf8B5q1PeRUS2f5fAF6RwDS6HsBDeqmEic8JPxOh+30pvAcR6UiylSYe6S+H9bg== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" +"@ckeditor/ckeditor5-easy-image@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-easy-image/-/ckeditor5-easy-image-47.4.0.tgz#fcbb1d470e1e4e80c0198a92ca8d6bc077b55dcf" + integrity sha512-YMxvD3Gh6kVux1OKdtdubvjtUHu4TIN7YgCThqsfnuumpnx94Dhq3+wy8o/dO73dRcq/iVvb/9LmkivT4+8uXg== + dependencies: + "@ckeditor/ckeditor5-cloud-services" "47.4.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-upload" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-editor-balloon@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-balloon/-/ckeditor5-editor-balloon-47.4.0.tgz#489dd513869036b673d5d9f4d7408dd39693bb58" + integrity sha512-FZuHy5EhzssTQZTuXQF7aVRJyvY0QaIOr6yj8fttRoWQgIDMzJNm+rVW9C9FRa1+j1i9tlrE21+GYIhCgEGyOg== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-editor-classic@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-classic/-/ckeditor5-editor-classic-47.3.0.tgz#03412d376358395071ccb0eff83329801e2c8a83" - integrity sha512-uCA8cr23LSJf8POkg2c403zS+xWjbE8Ucu521PQPcxrTMyTI6rYfjnuZ6vT/qzqAwZrLpiNZucJIQxRDFhLWGQ== +"@ckeditor/ckeditor5-editor-classic@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-classic/-/ckeditor5-editor-classic-47.4.0.tgz#c41243dfe3e2029d432db43c537ff73775c2f483" + integrity sha512-b698aEHRJSC4jMP0fYD78tdqMw5oQHtCpUL6lU8LFsysCe5M0cqgab4V0hEjeIsg4Ft/UmkgFd1aAleRCDftJg== dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-editor-decoupled@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-decoupled/-/ckeditor5-editor-decoupled-47.3.0.tgz#f5c430d40b17fb98e9dbe2e23452ca73cd3b6d79" - integrity sha512-G4szgSWluqNG/wv+JQxiZv1lzwUzTxdPZWO/mL8pi3sc69vp30QYT+I4TOTLpfBdISBPkWajn/hfEXJPS1hCXw== +"@ckeditor/ckeditor5-editor-decoupled@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-decoupled/-/ckeditor5-editor-decoupled-47.4.0.tgz#df4bcd16c03addcedb2c5b56887850eba592558c" + integrity sha512-4Nk/fe5Sob9aUf8gf4K7GQjqI0XftDThGRjX1eKOSDs+OGXRyB4Fxtu+tHLCyCt8cITac/PAMWaO7dwqbAK8bA== dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-editor-inline@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-inline/-/ckeditor5-editor-inline-47.3.0.tgz#1990a71af5ae8b60d22140d60a8adbea4c3d0cef" - integrity sha512-ie66wno1gbxNuoqGJ7iSDIz4gydPxJtSE5F9kb3NjfwecQxjj/0yBS+HsbZhqbFFNdJ01KZOtbAxNXQ0r1OW2g== +"@ckeditor/ckeditor5-editor-inline@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-inline/-/ckeditor5-editor-inline-47.4.0.tgz#05576fe4bd2a6c41ab4845ca4584981c52ae08bf" + integrity sha512-/xKtAwq0Pg3Zq7q9QcmrUnqc8XScrUlixWnl58gOxsdmflaSaK4qLtnId0FmSrax0tqVp1qihsUfvE5uUNnyGg== dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-editor-multi-root@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-multi-root/-/ckeditor5-editor-multi-root-47.3.0.tgz#4cb0016a55458545cc426fb7b71c74e3e500fb9a" - integrity sha512-PRxVNpoo7YiECugo9rPsUmuTL3f2xUwvHSUKh6FvPneQS4oFIdMNJrg/Hhn02sEOe6+ScFIi4X06ryK+/N6zOA== +"@ckeditor/ckeditor5-editor-multi-root@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-multi-root/-/ckeditor5-editor-multi-root-47.4.0.tgz#bb41b4c5a076c23f8dcb51660ecfbef300db84b5" + integrity sha512-gKYQeg2QI+9JM2gujYVBaLVlh7Dw4XfkX1g4jYMEqq4YG5E17Hpbc1A/IqUb0LLpAd1TG64AR4s/vxK0JrnY1g== dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-emoji@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-emoji/-/ckeditor5-emoji-47.3.0.tgz#f6d5f2d3e84844f63c93b9601b90a1d61a18425a" - integrity sha512-3xmB9VKShWmK2x1qZ7BecfqaxAGP6Ys1/UEPhBhoFyRK34UvtA9KrK0G+KWF4kwA5OgkoqnQRmVkMEO6mKXMnw== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-mention" "47.3.0" - "@ckeditor/ckeditor5-typing" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" +"@ckeditor/ckeditor5-emoji@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-emoji/-/ckeditor5-emoji-47.4.0.tgz#b3214f8668f6e8c1621153f3535154015d356030" + integrity sha512-PbTqvbBzMfvKaxTzAt72VskT8ifGoKRNKzskEmm74RCLu6a60rUaqL/4ChkTsF1FKPvB07VDbyDQx4XkvUOBIA== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-mention" "47.4.0" + "@ckeditor/ckeditor5-typing" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" es-toolkit "1.39.5" fuzzysort "3.1.0" -"@ckeditor/ckeditor5-engine@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-engine/-/ckeditor5-engine-47.3.0.tgz#bf4409390be95a85309019e47351558f1f029153" - integrity sha512-op/9TsJgFtWctfUd/QY41HYyFZd5hfSK6hBTJh0Xpz2XAfvpWsVim27FyWX0yIhyMLmtwDETDq8iBaH5kEZ15g== +"@ckeditor/ckeditor5-engine@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-engine/-/ckeditor5-engine-47.4.0.tgz#7711c1e3bfc7912f56ba22310d8299691e18b02c" + integrity sha512-U3Zq3qZ86Si6L4BslJIXotK9oVXu59zAuDVWlx3prAUS5Mrz7MfVlWdz9HeWu9W1i2FmUGVksX+uoO/ng2CZUA== dependencies: - "@ckeditor/ckeditor5-utils" "47.3.0" + "@ckeditor/ckeditor5-utils" "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-enter@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-enter/-/ckeditor5-enter-47.3.0.tgz#ea3d162cf19fd42594380ece10cdadb26bd9e8b3" - integrity sha512-gBsT2ffLKUQclJpWkjn8mggtmoa3AfYH6vjsfMefN3yov1FoGY65kQDXl9KOfdG71E/tRtOZkMUPXqZUlYrBlA== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - -"@ckeditor/ckeditor5-essentials@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-essentials/-/ckeditor5-essentials-47.3.0.tgz#51681029ae2af39f003235bfb545105014b59635" - integrity sha512-tLqgNXfdZJiBR56CHBNkrHWd7WCSPTIRxfqB9xoDvFD3AQngv1J3tIj3ye0WtTr8V23CCcXzz3v3NFZwtuDPBw== - dependencies: - "@ckeditor/ckeditor5-clipboard" "47.3.0" - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-enter" "47.3.0" - "@ckeditor/ckeditor5-select-all" "47.3.0" - "@ckeditor/ckeditor5-typing" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-undo" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-find-and-replace@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-find-and-replace/-/ckeditor5-find-and-replace-47.3.0.tgz#6e27bf44fddfec25d804fbc2d5f55095150958fa" - integrity sha512-jSbc4ss36ynQvyNYKNR4UXceoS8r2JE9fjedHZbMPpFRPlypCC2oc21WhWa/Fo+PcfAIV7q2izNDclcFtEFB/A== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" +"@ckeditor/ckeditor5-enter@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-enter/-/ckeditor5-enter-47.4.0.tgz#9d6b5038a43c926cb0245f6bd19c52d80151e99a" + integrity sha512-BQjJ7CjXENoF8Inv8ydRl+luRMKQvw1ohkiYsTEruHjGKkAFyDTGrorzkoGp2IU98n5SVGJE+XwVxpKgjsKAVQ== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + +"@ckeditor/ckeditor5-essentials@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-essentials/-/ckeditor5-essentials-47.4.0.tgz#f0100ebe4ec1dedf427648848571d722d076faa8" + integrity sha512-M+8xGJF+PKEcTjTeqofNe6cjcTnsy6EomqwGrbHDHhyAXC4d8k/vRrptymjonW7H9IsuOcQ5t2eZj3d+yl03gg== + dependencies: + "@ckeditor/ckeditor5-clipboard" "47.4.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-enter" "47.4.0" + "@ckeditor/ckeditor5-select-all" "47.4.0" + "@ckeditor/ckeditor5-typing" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-undo" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-find-and-replace@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-find-and-replace/-/ckeditor5-find-and-replace-47.4.0.tgz#7ae9606af2def3a71f75883d6d69e126034e3911" + integrity sha512-CZAX1XxrJcnOAwENfw4x4DiLyZ6uOHUHJqFXyyJdQC9qfEizvFYTXn3zO6fbViyDd/k4ugAoLBjpaZh6p9FyOQ== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-font@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-font/-/ckeditor5-font-47.3.0.tgz#68e1c772aa0ccde4fc266cbf421eed05f223805f" - integrity sha512-J1QhW0Z6LfU0Mc3cITw21vPTIv1sGtlyO7JSFU9rQUkF1p2PCMQ/SvEja3bdz8LipidoDUh+QCeT2z9TSt1VDA== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-fullscreen@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-fullscreen/-/ckeditor5-fullscreen-47.3.0.tgz#552253beeede6673ab34b2840cbf617537c58c91" - integrity sha512-lSge/Lw30GYkztAWifZYOpc5Q9tjuT73gq0Hcs1tDpiIIt63CM7AfIS/sjiTUus0ZSG8fjLdd3ivSf4TiE/kOg== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-editor-classic" "47.3.0" - "@ckeditor/ckeditor5-editor-decoupled" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-heading@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-heading/-/ckeditor5-heading-47.3.0.tgz#e45707f8bcc03a143e6665de93c1a86e81e59dbe" - integrity sha512-sCBpuGTY+RGnE45r1cgFfe29cW6hmVQo+4HGppyErj7Sac5f1PCG84/DSTP1n+6LPiA51Yh2Z/VtQdYKMRNnmQ== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-paragraph" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-highlight@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-highlight/-/ckeditor5-highlight-47.3.0.tgz#9f364b193587f4601bc80f670830e7b3894c3da4" - integrity sha512-wlT7R+7LVp0LmCyKIRN+U6+3FJqw6NpmfHhidSZnTRd9qzGnZ2EMxdEIkfOyCZd2CYH/gxtf/QFGik+DTjV/ow== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-horizontal-line@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-horizontal-line/-/ckeditor5-horizontal-line-47.3.0.tgz#5d2e4b8e1ec31c57333f1b6975df68fc2bba6eaf" - integrity sha512-F0QlRncwX/wvUN/LtZjpdsld9qT3jDxrniv4a/nz4LIotTVAsw2tMy9y8Sw2TNjIrOY5cCytxG91kzc+WNwUlA== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - "@ckeditor/ckeditor5-widget" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-html-embed@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-html-embed/-/ckeditor5-html-embed-47.3.0.tgz#2a7285ec8af6781e672a3182dcbe0968bb2852ea" - integrity sha512-B8xgh/4fUoccNhTKajBFlWWgz03G0QS41iXGtEoDY74Z1Ewx8zKccw4kPcyowIsrM7iq8w8tmo7uHJQaB5rhlA== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - "@ckeditor/ckeditor5-widget" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-html-support@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-html-support/-/ckeditor5-html-support-47.3.0.tgz#e521de883323c83dc571550d73d05eedbdf73388" - integrity sha512-sdqB2NPlCy4UC6Wgi1RzW/kzeWd9zIgf8s/bx4KzGbWekAvfnJWUVAYkkziM+7N6NhXTKDx8Wu2Zh/66pIo1XA== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-enter" "47.3.0" - "@ckeditor/ckeditor5-heading" "47.3.0" - "@ckeditor/ckeditor5-image" "47.3.0" - "@ckeditor/ckeditor5-list" "47.3.0" - "@ckeditor/ckeditor5-remove-format" "47.3.0" - "@ckeditor/ckeditor5-table" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - "@ckeditor/ckeditor5-widget" "47.3.0" - ckeditor5 "47.3.0" +"@ckeditor/ckeditor5-font@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-font/-/ckeditor5-font-47.4.0.tgz#c93d96c7c96a584f708d98380658e20e6781e7a9" + integrity sha512-QRIThyZg0kT1R4LTotD6cV9gm0NX3Z0Cq/IOQtuwTbRb3wa+kWXhVfKZPV9Qru5HifvrCrcWXMjkBRRIdfqp+Q== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-fullscreen@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-fullscreen/-/ckeditor5-fullscreen-47.4.0.tgz#a1eaae21a4f3de061bf86fa34c1ff37f18cc9491" + integrity sha512-DdroZD1cgNU3up74ZQq84vXyCDknQJJyyxQIXS5CKJy7qNR9YmixpVmyXpYJmZzdSVvp/p8Ej87VlOXfju3ilQ== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-editor-classic" "47.4.0" + "@ckeditor/ckeditor5-editor-decoupled" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-heading@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-heading/-/ckeditor5-heading-47.4.0.tgz#632bbc6eb40fcccce90fd2177e53e588cceadc0d" + integrity sha512-VWBxQ2ngrT0x50Tb1klZyIOykgNPby8sw5rBq/nv/UXBb2Ql/crp50miC8pBCOvkbTP16qzVbl5HoiltJQkH/g== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-paragraph" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-highlight@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-highlight/-/ckeditor5-highlight-47.4.0.tgz#3ff7a0314c72649d3754b578699f4ae88d538aba" + integrity sha512-SHBkoMVu/uTkvE0/1zaehlvCpEqYuh/u1Rh7SHNysrD05Nacs1t5jw+l2lTFoyJnhTy+RA9IONYSDF+5tK3dqQ== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-horizontal-line@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-horizontal-line/-/ckeditor5-horizontal-line-47.4.0.tgz#02e27c27fa0f928ad41bafb9b8cfd961e5049396" + integrity sha512-UvL0x55QxRGiem8EPO9n/WQk6218TDNatKSCRueZkAYUrFC1bmtVs9g6GqvSl59RoRGcTxVcz0fXbsxrhZY6HA== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + "@ckeditor/ckeditor5-widget" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-html-embed@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-html-embed/-/ckeditor5-html-embed-47.4.0.tgz#6cbcdd19341f18513c1c8349c68004bf53a7dd54" + integrity sha512-SnidyadvuC0ohT2kZ0crsnFy8adQwhHcRaGUNXx5qAHRK7K1wGp3nxdnyOW5GdK2CIe8DTo+H3v8nXfvt7VgnQ== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + "@ckeditor/ckeditor5-widget" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-html-support@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-html-support/-/ckeditor5-html-support-47.4.0.tgz#69111ef5781ee732876beb77c40c1e347b2bc4d3" + integrity sha512-SGd6wvPB9VGNqEWvoEdK1kQJ3lpvrTNfsA5Pg02V/Zr3gIxnAqajYEArWDYtsz3ajaUDs06i1tFdpCbFB7JRMg== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-enter" "47.4.0" + "@ckeditor/ckeditor5-heading" "47.4.0" + "@ckeditor/ckeditor5-image" "47.4.0" + "@ckeditor/ckeditor5-list" "47.4.0" + "@ckeditor/ckeditor5-remove-format" "47.4.0" + "@ckeditor/ckeditor5-table" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + "@ckeditor/ckeditor5-widget" "47.4.0" + ckeditor5 "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-icons@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-icons/-/ckeditor5-icons-47.3.0.tgz#e8adb3991e523ba6feca4f7d30116bf45548c345" - integrity sha512-erpbkXiPtA3Bu8a8ZLQjPYpX4W0WoT3OFZElHZgXOmVl8xQAefp2q+lFYKgzsqB757/zZO7i/B6U9czNv6lPmw== - -"@ckeditor/ckeditor5-image@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-image/-/ckeditor5-image-47.3.0.tgz#3f90466a565a3c0d053da27c4c98010f586675a7" - integrity sha512-KnsQUv1itQdKJIAlj3GSTETuaiyFq7ggMsK7UVJFTk0yCiIi+oSEkrIn5r+p1e98QYEYjArS2SwOIxDsxDM2sQ== - dependencies: - "@ckeditor/ckeditor5-clipboard" "47.3.0" - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-typing" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-undo" "47.3.0" - "@ckeditor/ckeditor5-upload" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - "@ckeditor/ckeditor5-widget" "47.3.0" - ckeditor5 "47.3.0" +"@ckeditor/ckeditor5-icons@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-icons/-/ckeditor5-icons-47.4.0.tgz#73a1fbd70f14cb859ee71118978690489cdb2b9c" + integrity sha512-2THOymXou/dBR+Jk69+/DzE3lK3QVk8+9eSKdWQ4+kvYom9MXT9RwKJNe3BlvqUNxBymI8eVBjdaQjfv3AOT0Q== + +"@ckeditor/ckeditor5-image@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-image/-/ckeditor5-image-47.4.0.tgz#6222e3ae17fe6d94b609afabbd7e0d605e1ffcb0" + integrity sha512-Z0q+cANAvzvW/3lIMg0rpvVHx4nlWbUsfPw78gM7/DmB4qpdbKsX07iTut84ZnWvOP+WU3XIrhinMXTvl6IqEw== + dependencies: + "@ckeditor/ckeditor5-clipboard" "47.4.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-typing" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-undo" "47.4.0" + "@ckeditor/ckeditor5-upload" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + "@ckeditor/ckeditor5-widget" "47.4.0" + ckeditor5 "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-indent@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-indent/-/ckeditor5-indent-47.3.0.tgz#5934ac5ed590ac3aed241ad8d1834705eb52086d" - integrity sha512-kIpuMrTrtf7YhOBYre2Ny7NnL/x6sqMzdaxy4LN+4Sa9+Cw+KR2QJij2d0VkwDzV+z2B8GZ1mNZvCzpEwWDUUA== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-heading" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-list" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-language@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-language/-/ckeditor5-language-47.3.0.tgz#7a5268f78675dceec2d68d8d34eb7865f62a0a73" - integrity sha512-sPAgbKYT3NpofS2FWphkgiPzD2YqbTpxpLyzHymDJo7s2LQWj5FUGacZiiddGPOdzicSasZ6qHvcHIMHCmLBpg== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-link@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-link/-/ckeditor5-link-47.3.0.tgz#eaf1b67a1d5c100912904ee55b4abc35d54f11bf" - integrity sha512-YbxZQHi36EF/O7deiDlrM8Xnw/J18x4dQgxaiHKTSHu7/4sZuVfJFAzF6afdt1uQ+8yeX3+q60jkJr2mm1zOEw== - dependencies: - "@ckeditor/ckeditor5-clipboard" "47.3.0" - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-image" "47.3.0" - "@ckeditor/ckeditor5-typing" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - "@ckeditor/ckeditor5-widget" "47.3.0" - ckeditor5 "47.3.0" +"@ckeditor/ckeditor5-indent@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-indent/-/ckeditor5-indent-47.4.0.tgz#ddbd56d04ab80c4a5bf2039197e778ca4e0487b1" + integrity sha512-lFPYPUSuByK6GHiTnkHeLkWHD5/SbXCQ5TJVzRJ3uaWvbqo0b0Hvoz92vtKueOwi1QsgXD38aYhMljs0h8eP5g== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-heading" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-list" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-language@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-language/-/ckeditor5-language-47.4.0.tgz#30ea15cde33cc28d7e1582bd7f44578becf67534" + integrity sha512-3FEoS59ZOTm6m0m0O5qEpsf4tGX/r+r0LjkDrRjhIcaGJh0W4Ao2J6cSrXv7hikDpgBjbHIkEy0V6KkIWWAZpg== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-link@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-link/-/ckeditor5-link-47.4.0.tgz#f6aad2f7f03d2688db0171632c5f06c775725f80" + integrity sha512-AF7TVV64iOqia4x4psHakYYznPoS3I5j1Gijoa7jiTLGJZSaAL7xAc1qAajgWQ66o7DWuVGL7QkZwKIo1jlTPg== + dependencies: + "@ckeditor/ckeditor5-clipboard" "47.4.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-image" "47.4.0" + "@ckeditor/ckeditor5-typing" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + "@ckeditor/ckeditor5-widget" "47.4.0" + ckeditor5 "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-list@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-list/-/ckeditor5-list-47.3.0.tgz#8c71e9f4c40eab840bdf2edbcf6112118218c8ae" - integrity sha512-iOJ4prpoqf1UamKztQ0If/k638+NGSPsFaGGjOqhGPcIJxTtscs4c34uNUH6yCXDNF1ZaET2FxFckAQvrb0DFQ== - dependencies: - "@ckeditor/ckeditor5-clipboard" "47.3.0" - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-enter" "47.3.0" - "@ckeditor/ckeditor5-font" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-typing" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-markdown-gfm@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-markdown-gfm/-/ckeditor5-markdown-gfm-47.3.0.tgz#22e4df0d5ccca82cea77f034371a7262343106ae" - integrity sha512-PyRXnwnUmwW7pxe8DaV1sle/g45fp/e+1vzXgFIvLYWJO5i2Sych1yDbAU1RGbJr5R05eFS7Fov3bowzRE2ICA== - dependencies: - "@ckeditor/ckeditor5-clipboard" "47.3.0" - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" +"@ckeditor/ckeditor5-list@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-list/-/ckeditor5-list-47.4.0.tgz#daecd93432dd43a0d8eba9b58923c131a4fa8a46" + integrity sha512-OGvAgS+NB1dzrqhN1xEVfN8PTM73pjMnmDvQeQurwIfjQdJaO07jGPRqujQzNostckWvNPtQysXkbnp+QiCPOw== + dependencies: + "@ckeditor/ckeditor5-clipboard" "47.4.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-enter" "47.4.0" + "@ckeditor/ckeditor5-font" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-typing" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-markdown-gfm@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-markdown-gfm/-/ckeditor5-markdown-gfm-47.4.0.tgz#7ec446c002d000d53b0c5ee1a756add477718019" + integrity sha512-2W1dBzxPIdEsE0CiU19K4xQfBS2jSBruJh5XV924eyuJPh76CdXKDGPBwuVd6i1oK7x+ji0Griu9Y+R2F0jRIw== + dependencies: + "@ckeditor/ckeditor5-clipboard" "47.4.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" "@types/hast" "3.0.4" - ckeditor5 "47.3.0" + ckeditor5 "47.4.0" hast-util-from-dom "5.0.1" hast-util-to-html "9.0.5" hast-util-to-mdast "10.1.2" @@ -1347,271 +1347,271 @@ unified "11.0.5" unist-util-visit "5.0.0" -"@ckeditor/ckeditor5-media-embed@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-media-embed/-/ckeditor5-media-embed-47.3.0.tgz#e88ad49979d804976486b6a2e8f114be6bd0f1d8" - integrity sha512-c0wP3VZp6409VMMRYL4z2ZiqCsP2p4RyHcfH8TZSy3g25pZnUbYpdMepHCxT0U5wLVdqhGMn7cW+k5Fq6Mp/hA== - dependencies: - "@ckeditor/ckeditor5-clipboard" "47.3.0" - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-typing" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-undo" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - "@ckeditor/ckeditor5-widget" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-mention@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-mention/-/ckeditor5-mention-47.3.0.tgz#7262434b8c5df7d76c8e7e450b8d1eab20febff2" - integrity sha512-yIRbRSd0b66kUlur80kiskVMyymHvtg96endZ8FuGDjKgdLApFnkonNmpCNLAxGuwJDMfDyvyEikZy1i0bgWlg== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-typing" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" +"@ckeditor/ckeditor5-media-embed@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-media-embed/-/ckeditor5-media-embed-47.4.0.tgz#611ea3ccc49c4a529da966bd792cd210a3ef6515" + integrity sha512-oL/In6Q3dtgj23FyyKbtYa704sl1eEx8JeO4ODRL3scCNI2/7qx9nGMexydiJi+Saulvs/3g7A8PbXiI+iArog== + dependencies: + "@ckeditor/ckeditor5-clipboard" "47.4.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-typing" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-undo" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + "@ckeditor/ckeditor5-widget" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-mention@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-mention/-/ckeditor5-mention-47.4.0.tgz#362e1e63898215f8df4c2abbcd908bb7408a6056" + integrity sha512-1niRMaI5HxYbSTosxjU/6F5Uo+2hCEa3s18emwIBMTG1zOu0OViubuj+P8wCOqmSmpzvfkNybl4kk74MahGk0w== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-typing" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-minimap@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-minimap/-/ckeditor5-minimap-47.3.0.tgz#a131d2ce6a479fa52beb1f4aae7fb8cdff1daaba" - integrity sha512-8JrmRwEMdIVoSp5Xms8sWHxlXcBPwhf7HjY35ptbS2sMQQ4RC9o5tbyLe8V2kGt8Qgmvx3F2H2VT9VFpQCUmFg== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-page-break@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-page-break/-/ckeditor5-page-break-47.3.0.tgz#3bd6cbac2cea9040be054f6de926220cb242dc27" - integrity sha512-ZvLfObeXnhYKs8+kcVBbjpAWQnGelVopnEIC0Ljds2cxyeUJ25pnLAZGKMcEOFvdm8Hh1OKnlfPWj3VRZMkrVw== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - "@ckeditor/ckeditor5-widget" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-paragraph@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-paragraph/-/ckeditor5-paragraph-47.3.0.tgz#dd32ac01641738128fd68802f7d9c1e1f6e996e2" - integrity sha512-CCnCd57ySxYrb6XCocAzj49PH6jOc+YbsgVVQ4+4sMyOQR/d5VdgJAkQKO7m288nwvE+Ai9gMAl5rqiph+PcMg== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - -"@ckeditor/ckeditor5-paste-from-office@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-paste-from-office/-/ckeditor5-paste-from-office-47.3.0.tgz#6a35af1321d57ff9062a93de52e38fd023e23129" - integrity sha512-8M7pKMAI0cwviVx/QWYQRDfy9GLUUBVKrqBFuOu/lcxfsncL7BUJYVVvaOC+iN0I9Mi513XHz78FLi4PbRoC0A== - dependencies: - "@ckeditor/ckeditor5-clipboard" "47.3.0" - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-remove-format@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-remove-format/-/ckeditor5-remove-format-47.3.0.tgz#4c1455b791f1312e8b6060b9246695c7d6c11bf6" - integrity sha512-tGBSxVKu2fUO7oH2U4QyAb6+/47YFkEVwRPGvpwg4QUQn670qAJJenJBWqXEYFHK6V5mLDfD5xmKdTk79OXgTw== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-restricted-editing@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-restricted-editing/-/ckeditor5-restricted-editing-47.3.0.tgz#11011b9e1928ea04c667c1a483352ae1b3b50eaa" - integrity sha512-DoJFgX7RXapubLnulcW6aFuTQD25jSPWMJA25EXHTHMq9ZQP69ey2kJgp2iioas0zpsKhnVzioUyIiGe28ufng== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-select-all@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-select-all/-/ckeditor5-select-all-47.3.0.tgz#a35a2b4edc36d2e0912f262fb607e0f5217e88e6" - integrity sha512-pMWVdKDlLowiwnVGycJd0mW2jQ3HdlzzstfIhawhU2jspSY4Byk8XiPZ9Dyq6aAwEtdJOShLLau1dcVnB2OltA== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - -"@ckeditor/ckeditor5-show-blocks@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-show-blocks/-/ckeditor5-show-blocks-47.3.0.tgz#8820630061f44895a39b727eaafd88693ed0ae51" - integrity sha512-vgmH/FqCHproRvVqXYLQrDeDgc5D+2iEK/MB7sRH75w+ZjP495XUYRtoZWud59yQ8P3kCgywycR74iyenxntlw== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-source-editing@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-source-editing/-/ckeditor5-source-editing-47.3.0.tgz#650515f93506cf8c1ea1c5cf90b06edb0891752e" - integrity sha512-a2hFkyUzDJBpPh5jF3+LUO356PeQ84/Amqp9Y8oqzk6nKXlfr5IdPU1kQTkwDxee7F85EUNd2/wRZm4tLKL02A== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-theme-lark" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-special-characters@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-special-characters/-/ckeditor5-special-characters-47.3.0.tgz#40e8e4e0e2677ac8bf2819c61513427202df3d01" - integrity sha512-kh9gONY8HqP1hQ5AImLzYyiecyVRHmyGE9xc1koyOV5HvZ3X+ogTWuAFqG5e3zjLaVCeKQKXkbuBS6/+Gi2NxQ== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-typing" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" - -"@ckeditor/ckeditor5-style@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-style/-/ckeditor5-style-47.3.0.tgz#450756719867dc6781bcb25a4e50afb592202e1d" - integrity sha512-EsQ3ZZccrsniKadcfjBI7HJgsNbZAl6NomQBKauvTQzmOoL90Ouffp6yIQTIQkIgm/xzIh2zVhGTcw84VoioJw== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-html-support" "47.3.0" - "@ckeditor/ckeditor5-list" "47.3.0" - "@ckeditor/ckeditor5-table" "47.3.0" - "@ckeditor/ckeditor5-typing" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" +"@ckeditor/ckeditor5-minimap@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-minimap/-/ckeditor5-minimap-47.4.0.tgz#0aeffe10bc25f850bb57656d183c5c80faad3b42" + integrity sha512-j0bOrjhEB5U6wCrz8CgW8ueFgHJJORtgqkOiRfQd++SBHGULSRr/WJwvaObcrhhNrY4Mlme8Nws6s5YJxzlFhA== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-page-break@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-page-break/-/ckeditor5-page-break-47.4.0.tgz#bf25609dd31c6e184570522ed54f60855056167e" + integrity sha512-v4VR4OhLqj5Rp/Dwb9BSb9lSNAkGVF9n5ThvC0dFeHMikC4ENcqH8NpcbVnaua4tsM9tX0jZLHbcX+jMune4IQ== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + "@ckeditor/ckeditor5-widget" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-paragraph@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-paragraph/-/ckeditor5-paragraph-47.4.0.tgz#dfd2f314bcb3b0557a16e9c8ae7be5e5d5201a95" + integrity sha512-epw82iXcK6togOeE/rolQBkyxCfz8m30VoH0bdq0YKkg8+HJ5uzB2FweFDH+l/cyoubdB2f1370G2dAMp6huBg== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + +"@ckeditor/ckeditor5-paste-from-office@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-paste-from-office/-/ckeditor5-paste-from-office-47.4.0.tgz#b1b37e743ca71548e6b3e7d258be34ce4e42a9df" + integrity sha512-yKOk+CDV0dAy+XeqUcP5Drur1u69h6UCdLwDUEbS/egSv/+o+tJwCGrTCRzPqBeUxIahUGBMk0obID7v6xT9IQ== + dependencies: + "@ckeditor/ckeditor5-clipboard" "47.4.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-remove-format@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-remove-format/-/ckeditor5-remove-format-47.4.0.tgz#1461db9343d708a79c24c0af07c70e54a783d999" + integrity sha512-XD6LY76m3bZr/twRGTjNRnU4z0VU1akDC7evVMhRPaDruR71km00VT1YNPRChCDmdssEVeWEynHhLQ/kRjy+0w== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-restricted-editing@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-restricted-editing/-/ckeditor5-restricted-editing-47.4.0.tgz#29df0b5763d973d5e1713fea194abe6e72d32c85" + integrity sha512-roywT2jKCs0NVd6TVhYlmrnP0oI4499M5L1mV8Vqq4wc9puVeEPSIKoZNdIF5YWXsHjpCUCMejpuigLTIbf9MQ== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-select-all@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-select-all/-/ckeditor5-select-all-47.4.0.tgz#6759a9474ecac0df1d75b2361b0521a7f5e8e7eb" + integrity sha512-9fVsmNFmSj53kJKPKUmCkgpXUev2OeMJ5cFVKXvzEvsm6jFTO8/9iHRTbN/j/ZzWuK5MoO/I3gVn4wGOIX//zw== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + +"@ckeditor/ckeditor5-show-blocks@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-show-blocks/-/ckeditor5-show-blocks-47.4.0.tgz#0ae9ebbc8f13a48f54cb969dfa09145fc552ecee" + integrity sha512-uIFHsH2HMPYRWmK+heZoiXRVqbxFJZwYZY1WmNKjE5g7OM8y+PVowe0ZYICjauV2/Z2rwCWtodDKb1bnVnl+mQ== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-source-editing@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-source-editing/-/ckeditor5-source-editing-47.4.0.tgz#59635fd5d85988f84885bc455da8839bd79ca26d" + integrity sha512-AtamOK+Dya6abkuo9XYME05FYFigBRic5gr3/KzhyFfHh7qiFlZFLCDH0S/JEQ0AduFjfgUx4h0ST22RIhiYoA== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-theme-lark" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-special-characters@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-special-characters/-/ckeditor5-special-characters-47.4.0.tgz#e220194e45a2cf0563cc30d2e81ed87a3a2853d6" + integrity sha512-eYP23WZY8ayA0q8LNVCUcP85yf9J2gSpVE9E6LNIku4rbzox6mCf0sZF0ZhzvqHyXyj9Mn+S21IZpLOTuTUW0g== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-typing" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" + +"@ckeditor/ckeditor5-style@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-style/-/ckeditor5-style-47.4.0.tgz#e6077f4875309733e7d56bccd02f284b57ae08c3" + integrity sha512-R6kt9jX9FOnYRXKn7kX0ZdIdW5A3S7ZZBfcdwzG9O/t7r5IIkp+yhC1y6/uBAc2twvvqMhG7Gu5KH2o/TVVjSg== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-html-support" "47.4.0" + "@ckeditor/ckeditor5-list" "47.4.0" + "@ckeditor/ckeditor5-table" "47.4.0" + "@ckeditor/ckeditor5-typing" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-table@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-table/-/ckeditor5-table-47.3.0.tgz#cabe9f43f10edda8faa83af0e7a3e2ea125ff3fe" - integrity sha512-YVupV2lEvE8tJi2tSnrthT1GCdzA0+zv4x0AQR5fBKfu82fux7vxKb222UnHkHhazrR3dGY5MSBRjIaDerY3TA== - dependencies: - "@ckeditor/ckeditor5-clipboard" "47.3.0" - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - "@ckeditor/ckeditor5-widget" "47.3.0" - ckeditor5 "47.3.0" +"@ckeditor/ckeditor5-table@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-table/-/ckeditor5-table-47.4.0.tgz#079fca2e5d4739966b59c6b5e0d6df41c9d97e25" + integrity sha512-gWraeB14YnpR+ELySu3xgSFlfur07ZBPN76rQuiIobrecKwhh1Az8rk7Qo4c1K/q/f4pHmqh87nhSprn7Mo7+w== + dependencies: + "@ckeditor/ckeditor5-clipboard" "47.4.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + "@ckeditor/ckeditor5-widget" "47.4.0" + ckeditor5 "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-theme-lark@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-theme-lark/-/ckeditor5-theme-lark-47.3.0.tgz#35749a9d94ddb7ad4d59fb11a31a7111a0560b88" - integrity sha512-ovaRKQAVTqlmYlpo3y9q1iu+5SKmmdjBTFDRGRgZ9nXNuD2vmikJA4pG5A4aNKLl/d3/LIkPfbn2g2w9VIlb7Q== +"@ckeditor/ckeditor5-theme-lark@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-theme-lark/-/ckeditor5-theme-lark-47.4.0.tgz#1731c864b4fc46d7440b7c5bc6e136527f1c1da2" + integrity sha512-kdtwV5HJ+8/oNcsGM8sdpULhXr2TfM7gEKlH/EAdycLDa6topcJuTl7iVSEu4hZzwVo2agiEMmdUIf3dvWweow== dependencies: - "@ckeditor/ckeditor5-ui" "47.3.0" + "@ckeditor/ckeditor5-ui" "47.4.0" -"@ckeditor/ckeditor5-typing@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-typing/-/ckeditor5-typing-47.3.0.tgz#737e7b8298008b49d1df4dbd5e9fc906e3d62002" - integrity sha512-hxwdd4hcCXLMFehS9/DLlcl+Bx+TlF+gG8f1DqNmpmqRbbVtfMFfMlHuqKC7+0c3TLJz7f0F5ih681s2H4t9RA== +"@ckeditor/ckeditor5-typing@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-typing/-/ckeditor5-typing-47.4.0.tgz#4135bb117f0e7c38d63d1ee7c98e02283ff00d5d" + integrity sha512-+YmCUTLVAryK5h68TgQ0qxDngs1MTCLKPDXxHzNqs0oXHai9YkJv/zg4zeb0/RQRIps7jh3bPapZoi2hP2iN3A== dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-ui@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-ui/-/ckeditor5-ui-47.3.0.tgz#8f9d8e1e88eb3ad93bc40174d2040641c0cee73c" - integrity sha512-dDHvfIxNfo3z00KwDO6nHCx9ZC2vVEQ+lMmpjbMD8P3FzGRPRd7NqzRbPoieDKlgAiG6Sa2CLThqA+71C+RMfw== +"@ckeditor/ckeditor5-ui@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-ui/-/ckeditor5-ui-47.4.0.tgz#3a2c653dbf890abd04dfb42edeb5ef833e30a541" + integrity sha512-sL67wp2DX+P3zxeJLo2I7yLhBlX6Zhd0xfUAB6vX6SkjhMeC0L2gLOIr3kKq/OMKEuS+0iZ+qVvEN1j+2Flzlg== dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-editor-multi-root" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-editor-multi-root" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" "@types/color-convert" "2.0.4" color-convert "3.1.0" color-parse "2.0.2" es-toolkit "1.39.5" vanilla-colorful "0.7.2" -"@ckeditor/ckeditor5-undo@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-undo/-/ckeditor5-undo-47.3.0.tgz#36653b37e34aa3a482f1b0888ecb85c146e4b3e1" - integrity sha512-vO0WCOQBC1Cj7hCxh3+VhQNrANiBjj+8561XkLGhDpQt/lpzuEqXn11Rx4BXjSzpuDZvNnMNO9duzXfEfVjAzw== +"@ckeditor/ckeditor5-undo@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-undo/-/ckeditor5-undo-47.4.0.tgz#8d92e91efb8929206a83374119a6692490fee036" + integrity sha512-OnxpJb9glDwuSTl59Yb4+6bjWW5h4BA+94YhesKZXeIaXjyzwFmNGxM07nRyaX4KXwGVP5y5JZC2xv5bCOXKSQ== dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" -"@ckeditor/ckeditor5-upload@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-upload/-/ckeditor5-upload-47.3.0.tgz#732e721ea1f741d824b5e94afe0bc0be14107e07" - integrity sha512-j4GngBlxg/tjztS/B67RD/OUrTYQhrwDYSpAjXV6shabwEbtEadsKLYgpXPR12ENB30mmrYKIRC/pgT5/wXc6Q== +"@ckeditor/ckeditor5-upload@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-upload/-/ckeditor5-upload-47.4.0.tgz#b0b6c232945ffea4f21d136b499393b837e566e5" + integrity sha512-9gMfYltVNi5aYNs8IixTXww9kyU0+oEeY9pN8W6YLrhToVJdnN14pW3yNkQJKJPK7HS2RgM6L1Y+u50qu/IL2g== dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" -"@ckeditor/ckeditor5-utils@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-utils/-/ckeditor5-utils-47.3.0.tgz#04ad3fb365bfe2c61d6137dd30805ec4ef49e931" - integrity sha512-RF5iAkI7NpVYZW1Fo+BhIQmPNLqA6iRVNoqo43P7vE8QfvG0fYB1Ff3jsEeM4UVV/G6pABBhE+9UMpwJcBuxWw== +"@ckeditor/ckeditor5-utils@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-utils/-/ckeditor5-utils-47.4.0.tgz#27477eed00a0b6059c29727783a77f26d2cecad9" + integrity sha512-+5v1k3+8Yr0VUnO+3GfP7MsDCqt5KD9f9Z5wUVRig/J61hPTv8cUQp0859K87IuOLdAP/rZ1iQpdi1psanQeIQ== dependencies: - "@ckeditor/ckeditor5-ui" "47.3.0" + "@ckeditor/ckeditor5-ui" "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-watchdog@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-watchdog/-/ckeditor5-watchdog-47.3.0.tgz#3f0f200e1f5b61c56fb1a83a50d67e6f46ed8298" - integrity sha512-gurXEgfiIvnmmd7u68PdffdAaYFuNuAE8fJoWeJFMzrrFGuG7TvGmulXG/Wom2D4D+eW7wQE93Sisx9wIfAcPQ== +"@ckeditor/ckeditor5-watchdog@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-watchdog/-/ckeditor5-watchdog-47.4.0.tgz#8ce4b3836032cf4635960dd6162e6bba46b5597c" + integrity sha512-MEfHIVYV4SILXi++G00y3wREm/1gT5dO+pTGpQY+NNYw8wgi32rg1q8hO2P/upsVaPzbeD3WLURyqeIxKwY20Q== dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-editor-multi-root" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-editor-multi-root" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-widget@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-widget/-/ckeditor5-widget-47.3.0.tgz#c15f87b47200bef75bcb501e117d0d811440097a" - integrity sha512-8IagE3JdKLM04KB3XR2SCDJTIlmtGOhkfWZBn9kwy7g8SIjI2bJARA/0wgXMGlzUV2AMbbxb0HdkMEK6Xxg/nQ== - dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-enter" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-typing" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" +"@ckeditor/ckeditor5-widget@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-widget/-/ckeditor5-widget-47.4.0.tgz#2d63a8faa2df59f1c12d0c31b772a097e1bda048" + integrity sha512-wffwrMQ6h+Hdu9IMG0H0QAf0YWWn+AGeJwPs69cRjRwB5pNOCUmMyM4h8MtNp15UEvGGARlhOjFf1TniMUkKrw== + dependencies: + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-enter" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-typing" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-word-count@47.3.0": - version "47.3.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-word-count/-/ckeditor5-word-count-47.3.0.tgz#7765599663fe9f0a3fac5b74e2535f96a5f3b2ee" - integrity sha512-VluTjPWaJnYS6uoJfi8XJZIBPzfrARH4RBEHOBto4SM1jNdSV0gltz6jfNSteGXm4Bl+VdBgltzRAXqsugi2Vg== +"@ckeditor/ckeditor5-word-count@47.4.0": + version "47.4.0" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-word-count/-/ckeditor5-word-count-47.4.0.tgz#4fb9a5a23c347bbf56c9baccab6951d7e3d1b95c" + integrity sha512-JeiwHJyBdlUCdzfW3K2KoGO/QhDe1qOKNPXiVXzExIyZpww+hm5HjV/zi5gX4xAvWg9ew0UaQRco5Dy7mBBfRQ== dependencies: - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - ckeditor5 "47.3.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + ckeditor5 "47.4.0" es-toolkit "1.39.5" "@csstools/selector-specificity@^2.0.0": @@ -1656,6 +1656,47 @@ resolved "https://registry.yarnpkg.com/@fontsource/signika/-/signika-5.2.8.tgz#87f0e439ec656c71cbf04363343e02a53cd81ee0" integrity sha512-q7OZfzEsRnyKzx5HQ2cUN8Q05HYE+mqa0HTKhlgcaRF9fysB5uzST+cEZ0cJEkgXyvt0uPNCwHhy2v6kRc8fpA== +"@formatjs/ecma402-abstract@2.3.6": + version "2.3.6" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz#d6ca9d3579054fe1e1a0a0b5e872e0d64922e4e1" + integrity sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw== + dependencies: + "@formatjs/fast-memoize" "2.2.7" + "@formatjs/intl-localematcher" "0.6.2" + decimal.js "^10.4.3" + tslib "^2.8.0" + +"@formatjs/fast-memoize@2.2.7": + version "2.2.7" + resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz#707f9ddaeb522a32f6715bb7950b0831f4cc7b15" + integrity sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ== + dependencies: + tslib "^2.8.0" + +"@formatjs/icu-messageformat-parser@2.11.4": + version "2.11.4" + resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz#63bd2cd82d08ae2bef55adeeb86486df68826f32" + integrity sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw== + dependencies: + "@formatjs/ecma402-abstract" "2.3.6" + "@formatjs/icu-skeleton-parser" "1.8.16" + tslib "^2.8.0" + +"@formatjs/icu-skeleton-parser@1.8.16": + version "1.8.16" + resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz#13f81f6845c7cf6599623006aacaf7d6b4ad2970" + integrity sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ== + dependencies: + "@formatjs/ecma402-abstract" "2.3.6" + tslib "^2.8.0" + +"@formatjs/intl-localematcher@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz#e9ebe0b4082d7d48e5b2d753579fb7ece4eaefea" + integrity sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA== + dependencies: + tslib "^2.8.0" + "@fortawesome/fontawesome-free@6.*": version "6.7.2" resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz#8249de9b7e22fcb3ceb5e66090c30a1d5492b81a" @@ -1762,94 +1803,94 @@ resolved "https://registry.yarnpkg.com/@orchidjs/unicode-variants/-/unicode-variants-1.1.2.tgz#1fd71791a67fdd1591ebe0dcaadd3964537a824e" integrity sha512-5DobW1CHgnBROOEpFlEXytED5OosEWESFvg/VYmH0143oXcijYTprRYJTs+55HzGM4IqxiLFSuqEzu9mPNwVsA== -"@parcel/watcher-android-arm64@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz#507f836d7e2042f798c7d07ad19c3546f9848ac1" - integrity sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA== - -"@parcel/watcher-darwin-arm64@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz#3d26dce38de6590ef79c47ec2c55793c06ad4f67" - integrity sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw== - -"@parcel/watcher-darwin-x64@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz#99f3af3869069ccf774e4ddfccf7e64fd2311ef8" - integrity sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg== - -"@parcel/watcher-freebsd-x64@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz#14d6857741a9f51dfe51d5b08b7c8afdbc73ad9b" - integrity sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ== - -"@parcel/watcher-linux-arm-glibc@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz#43c3246d6892381db473bb4f663229ad20b609a1" - integrity sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA== - -"@parcel/watcher-linux-arm-musl@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz#663750f7090bb6278d2210de643eb8a3f780d08e" - integrity sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q== - -"@parcel/watcher-linux-arm64-glibc@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz#ba60e1f56977f7e47cd7e31ad65d15fdcbd07e30" - integrity sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w== - -"@parcel/watcher-linux-arm64-musl@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz#f7fbcdff2f04c526f96eac01f97419a6a99855d2" - integrity sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg== - -"@parcel/watcher-linux-x64-glibc@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz#4d2ea0f633eb1917d83d483392ce6181b6a92e4e" - integrity sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A== - -"@parcel/watcher-linux-x64-musl@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz#277b346b05db54f55657301dd77bdf99d63606ee" - integrity sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg== - -"@parcel/watcher-win32-arm64@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz#7e9e02a26784d47503de1d10e8eab6cceb524243" - integrity sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw== - -"@parcel/watcher-win32-ia32@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz#2d0f94fa59a873cdc584bf7f6b1dc628ddf976e6" - integrity sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ== - -"@parcel/watcher-win32-x64@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz#ae52693259664ba6f2228fa61d7ee44b64ea0947" - integrity sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA== +"@parcel/watcher-android-arm64@2.5.4": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.4.tgz#88c67bde2c3efa997a0b1fea540080c6ade0322c" + integrity sha512-hoh0vx4v+b3BNI7Cjoy2/B0ARqcwVNrzN/n7DLq9ZB4I3lrsvhrkCViJyfTj/Qi5xM9YFiH4AmHGK6pgH1ss7g== + +"@parcel/watcher-darwin-arm64@2.5.4": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.4.tgz#d9dc037cff8a4ab7839a79c5287a6e6660f7ab27" + integrity sha512-kphKy377pZiWpAOyTgQYPE5/XEKVMaj6VUjKT5VkNyUJlr2qZAn8gIc7CPzx+kbhvqHDT9d7EqdOqRXT6vk0zw== + +"@parcel/watcher-darwin-x64@2.5.4": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.4.tgz#da0e13e16ee6d378242e2cfb469d72667624383a" + integrity sha512-UKaQFhCtNJW1A9YyVz3Ju7ydf6QgrpNQfRZ35wNKUhTQ3dxJ/3MULXN5JN/0Z80V/KUBDGa3RZaKq1EQT2a2gg== + +"@parcel/watcher-freebsd-x64@2.5.4": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.4.tgz#feb7cc9ec680bae3e91dddcdb4fe1c399ed52cc1" + integrity sha512-Dib0Wv3Ow/m2/ttvLdeI2DBXloO7t3Z0oCp4bAb2aqyqOjKPPGrg10pMJJAQ7tt8P4V2rwYwywkDhUia/FgS+Q== + +"@parcel/watcher-linux-arm-glibc@2.5.4": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.4.tgz#fa4e9cf8228c8c433e2f035e8b16aa299d892a78" + integrity sha512-I5Vb769pdf7Q7Sf4KNy8Pogl/URRCKu9ImMmnVKYayhynuyGYMzuI4UOWnegQNa2sGpsPSbzDsqbHNMyeyPCgw== + +"@parcel/watcher-linux-arm-musl@2.5.4": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.4.tgz#9ee6792e2d8810af9871ee5bbc2aa04e0b079d62" + integrity sha512-kGO8RPvVrcAotV4QcWh8kZuHr9bXi9a3bSZw7kFarYR0+fGliU7hd/zevhjw8fnvIKG3J9EO5G6sXNGCSNMYPQ== + +"@parcel/watcher-linux-arm64-glibc@2.5.4": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.4.tgz#624c6d874d99afa79305720f96a0c233d4ad7fde" + integrity sha512-KU75aooXhqGFY2W5/p8DYYHt4hrjHZod8AhcGAmhzPn/etTa+lYCDB2b1sJy3sWJ8ahFVTdy+EbqSBvMx3iFlw== + +"@parcel/watcher-linux-arm64-musl@2.5.4": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.4.tgz#5341e88b9e645d31c015ed40f384e60e49bd74d2" + integrity sha512-Qx8uNiIekVutnzbVdrgSanM+cbpDD3boB1f8vMtnuG5Zau4/bdDbXyKwIn0ToqFhIuob73bcxV9NwRm04/hzHQ== + +"@parcel/watcher-linux-x64-glibc@2.5.4": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.4.tgz#be5bcc49d3f6d21cc81bb531970a05d3721e385c" + integrity sha512-UYBQvhYmgAv61LNUn24qGQdjtycFBKSK3EXr72DbJqX9aaLbtCOO8+1SkKhD/GNiJ97ExgcHBrukcYhVjrnogA== + +"@parcel/watcher-linux-x64-musl@2.5.4": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.4.tgz#bffd3895b1f0cc8fd1436e409fd65d0a901281c0" + integrity sha512-YoRWCVgxv8akZrMhdyVi6/TyoeeMkQ0PGGOf2E4omODrvd1wxniXP+DBynKoHryStks7l+fDAMUBRzqNHrVOpg== + +"@parcel/watcher-win32-arm64@2.5.4": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.4.tgz#7fb8aedea5b34ba97a01e1555929d01f4eb72fe4" + integrity sha512-iby+D/YNXWkiQNYcIhg8P5hSjzXEHaQrk2SLrWOUD7VeC4Ohu0WQvmV+HDJokZVJ2UjJ4AGXW3bx7Lls9Ln4TQ== + +"@parcel/watcher-win32-ia32@2.5.4": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.4.tgz#f7f94ebdb21dedf37b12e030a82d4211798a1c26" + integrity sha512-vQN+KIReG0a2ZDpVv8cgddlf67J8hk1WfZMMP7sMeZmJRSmEax5xNDNWKdgqSe2brOKTQQAs3aCCUal2qBHAyg== + +"@parcel/watcher-win32-x64@2.5.4": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.4.tgz#8d895c9723f7fffdf4b360fd1becf1b6bcb571df" + integrity sha512-3A6efb6BOKwyw7yk9ro2vus2YTt2nvcd56AuzxdMiVOxL9umDyN5PKkKfZ/gZ9row41SjVmTVQNWQhaRRGpOKw== "@parcel/watcher@^2.4.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.5.1.tgz#342507a9cfaaf172479a882309def1e991fb1200" - integrity sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg== + version "2.5.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.5.4.tgz#a6575b0a018b4e263589c1e7bc2ceb73c1ee84de" + integrity sha512-WYa2tUVV5HiArWPB3ydlOc4R2ivq0IDrlqhMi3l7mVsFEXNcTfxYFPIHXHXIh/ca/y/V5N4E1zecyxdIBjYnkQ== dependencies: - detect-libc "^1.0.3" + detect-libc "^2.0.3" is-glob "^4.0.3" - micromatch "^4.0.5" node-addon-api "^7.0.0" + picomatch "^4.0.3" optionalDependencies: - "@parcel/watcher-android-arm64" "2.5.1" - "@parcel/watcher-darwin-arm64" "2.5.1" - "@parcel/watcher-darwin-x64" "2.5.1" - "@parcel/watcher-freebsd-x64" "2.5.1" - "@parcel/watcher-linux-arm-glibc" "2.5.1" - "@parcel/watcher-linux-arm-musl" "2.5.1" - "@parcel/watcher-linux-arm64-glibc" "2.5.1" - "@parcel/watcher-linux-arm64-musl" "2.5.1" - "@parcel/watcher-linux-x64-glibc" "2.5.1" - "@parcel/watcher-linux-x64-musl" "2.5.1" - "@parcel/watcher-win32-arm64" "2.5.1" - "@parcel/watcher-win32-ia32" "2.5.1" - "@parcel/watcher-win32-x64" "2.5.1" + "@parcel/watcher-android-arm64" "2.5.4" + "@parcel/watcher-darwin-arm64" "2.5.4" + "@parcel/watcher-darwin-x64" "2.5.4" + "@parcel/watcher-freebsd-x64" "2.5.4" + "@parcel/watcher-linux-arm-glibc" "2.5.4" + "@parcel/watcher-linux-arm-musl" "2.5.4" + "@parcel/watcher-linux-arm64-glibc" "2.5.4" + "@parcel/watcher-linux-arm64-musl" "2.5.4" + "@parcel/watcher-linux-x64-glibc" "2.5.4" + "@parcel/watcher-linux-x64-musl" "2.5.4" + "@parcel/watcher-win32-arm64" "2.5.4" + "@parcel/watcher-win32-ia32" "2.5.4" + "@parcel/watcher-win32-x64" "2.5.4" "@popperjs/core@^2.11.8": version "2.11.8" @@ -1894,9 +1935,9 @@ picomatch "^2.2.2" "@sinclair/typebox@^0.34.0": - version "0.34.46" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.46.tgz#33ea674ef3fbc46733c6d124ae94a6826abaf8cf" - integrity sha512-kiW7CtS/NkdvTUjkjUJo7d5JsFfbJ14YjdhDk9KoEgK6nFjKNXZPrX0jfLA8ZlET4cFLHxOZ/0vFKOP+bOxIOQ== + version "0.34.47" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.47.tgz#61b684d8a20d2890b9f1f7b0d4f76b4b39f5bc0d" + integrity sha512-ZGIBQ+XDvO5JQku9wmwtabcVTHJsgSWAHYtVuM9pBNNR5E88v6Jcj/llpmsjivig5X8A8HHOb4/mbEKPS5EvAw== "@surma/rollup-plugin-off-main-thread@^2.2.3": version "2.2.3" @@ -1908,6 +1949,9 @@ magic-string "^0.25.0" string.prototype.matchall "^4.0.6" +"@symfony/ux-translator@file:vendor/symfony/ux-translator/assets": + version "2.31.0" + "@symfony/webpack-encore@5.*": version "5.3.1" resolved "https://registry.yarnpkg.com/@symfony/webpack-encore/-/webpack-encore-5.3.1.tgz#a8b183bb8ba9f8ce0aa47be5f520ae194ffa1412" @@ -2047,9 +2091,9 @@ tailwindcss "4.1.18" "@tomickigrzegorz/autocomplete@^3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@tomickigrzegorz/autocomplete/-/autocomplete-3.0.3.tgz#0ca59e27bd7d2a03a26664745681a2dc6cf2f7e3" - integrity sha512-+6tqm8Tt4C8fDbhXDDZFcNHrcKYArSC7ONxBZ9bejWupSyls6Jyaeb243BbXjpLSM3X3q/Q9qtgYGh0QD6slNA== + version "3.0.4" + resolved "https://registry.yarnpkg.com/@tomickigrzegorz/autocomplete/-/autocomplete-3.0.4.tgz#f1cc21148fcc9dada14314114eabbe4992cc5d6b" + integrity sha512-HT+sUOxgiX5Yr+9h3UVtO88VcjVDp+s4aIpTq3CC5GVAV4a5WYll6sTluZcXLv6ACIT/3eLPT9d0OxVqWBnQLg== "@trevoreyre/autocomplete-js@3.*": version "3.0.3" @@ -2176,9 +2220,9 @@ integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== "@types/node@*": - version "25.0.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.3.tgz#79b9ac8318f373fbfaaf6e2784893efa9701f269" - integrity sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA== + version "25.0.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.9.tgz#81ce3579ddf67cae812a9d49c8a0ab90c82e7782" + integrity sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw== dependencies: undici-types "~7.16.0" @@ -2542,9 +2586,9 @@ balanced-match@^1.0.0: integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== baseline-browser-mapping@^2.9.0: - version "2.9.11" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz#53724708c8db5f97206517ecfe362dbe5181deea" - integrity sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ== + version "2.9.16" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.16.tgz#da1e893fd13aa6e8b6349b64b06399a0644c14b5" + integrity sha512-KeUZdBuxngy825i8xvzaK1Ncnkx0tBmb3k8DkEuqjKRkmtvNTjey2ZsNeh8Dw4lfKvbCOu9oeNx2TKm2vHqcRw== big.js@^5.2.2: version "5.2.2" @@ -2680,9 +2724,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001759, caniuse-lite@^1.0.30001760: - version "1.0.30001762" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz#e4dbfeda63d33258cdde93e53af2023a13ba27d4" - integrity sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw== + version "1.0.30001765" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001765.tgz#4a78d8a797fd4124ebaab2043df942eb091648ee" + integrity sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ== ccount@^2.0.0: version "2.0.1" @@ -2745,74 +2789,74 @@ ci-info@^4.2.0: "circle-flags@https://github.com/HatScripts/circle-flags": version "1.0.0" - resolved "https://github.com/HatScripts/circle-flags#f803b46170cc7d068b247c6a0993c7882429bb3d" - -ckeditor5@47.3.0, ckeditor5@^47: - version "47.3.0" - resolved "https://registry.yarnpkg.com/ckeditor5/-/ckeditor5-47.3.0.tgz#6378478ed869ccbb9885610d4d0d6223032627fc" - integrity sha512-3UDvnAi8TB/5i9flEFfOLIQAIWUoIbucvvFCqKWJqpfZy3F3k34GLEgDV/3VM6O6QV+UNHbzYaSTAl4yKVvoXg== - dependencies: - "@ckeditor/ckeditor5-adapter-ckfinder" "47.3.0" - "@ckeditor/ckeditor5-alignment" "47.3.0" - "@ckeditor/ckeditor5-autoformat" "47.3.0" - "@ckeditor/ckeditor5-autosave" "47.3.0" - "@ckeditor/ckeditor5-basic-styles" "47.3.0" - "@ckeditor/ckeditor5-block-quote" "47.3.0" - "@ckeditor/ckeditor5-bookmark" "47.3.0" - "@ckeditor/ckeditor5-ckbox" "47.3.0" - "@ckeditor/ckeditor5-ckfinder" "47.3.0" - "@ckeditor/ckeditor5-clipboard" "47.3.0" - "@ckeditor/ckeditor5-cloud-services" "47.3.0" - "@ckeditor/ckeditor5-code-block" "47.3.0" - "@ckeditor/ckeditor5-core" "47.3.0" - "@ckeditor/ckeditor5-easy-image" "47.3.0" - "@ckeditor/ckeditor5-editor-balloon" "47.3.0" - "@ckeditor/ckeditor5-editor-classic" "47.3.0" - "@ckeditor/ckeditor5-editor-decoupled" "47.3.0" - "@ckeditor/ckeditor5-editor-inline" "47.3.0" - "@ckeditor/ckeditor5-editor-multi-root" "47.3.0" - "@ckeditor/ckeditor5-emoji" "47.3.0" - "@ckeditor/ckeditor5-engine" "47.3.0" - "@ckeditor/ckeditor5-enter" "47.3.0" - "@ckeditor/ckeditor5-essentials" "47.3.0" - "@ckeditor/ckeditor5-find-and-replace" "47.3.0" - "@ckeditor/ckeditor5-font" "47.3.0" - "@ckeditor/ckeditor5-fullscreen" "47.3.0" - "@ckeditor/ckeditor5-heading" "47.3.0" - "@ckeditor/ckeditor5-highlight" "47.3.0" - "@ckeditor/ckeditor5-horizontal-line" "47.3.0" - "@ckeditor/ckeditor5-html-embed" "47.3.0" - "@ckeditor/ckeditor5-html-support" "47.3.0" - "@ckeditor/ckeditor5-icons" "47.3.0" - "@ckeditor/ckeditor5-image" "47.3.0" - "@ckeditor/ckeditor5-indent" "47.3.0" - "@ckeditor/ckeditor5-language" "47.3.0" - "@ckeditor/ckeditor5-link" "47.3.0" - "@ckeditor/ckeditor5-list" "47.3.0" - "@ckeditor/ckeditor5-markdown-gfm" "47.3.0" - "@ckeditor/ckeditor5-media-embed" "47.3.0" - "@ckeditor/ckeditor5-mention" "47.3.0" - "@ckeditor/ckeditor5-minimap" "47.3.0" - "@ckeditor/ckeditor5-page-break" "47.3.0" - "@ckeditor/ckeditor5-paragraph" "47.3.0" - "@ckeditor/ckeditor5-paste-from-office" "47.3.0" - "@ckeditor/ckeditor5-remove-format" "47.3.0" - "@ckeditor/ckeditor5-restricted-editing" "47.3.0" - "@ckeditor/ckeditor5-select-all" "47.3.0" - "@ckeditor/ckeditor5-show-blocks" "47.3.0" - "@ckeditor/ckeditor5-source-editing" "47.3.0" - "@ckeditor/ckeditor5-special-characters" "47.3.0" - "@ckeditor/ckeditor5-style" "47.3.0" - "@ckeditor/ckeditor5-table" "47.3.0" - "@ckeditor/ckeditor5-theme-lark" "47.3.0" - "@ckeditor/ckeditor5-typing" "47.3.0" - "@ckeditor/ckeditor5-ui" "47.3.0" - "@ckeditor/ckeditor5-undo" "47.3.0" - "@ckeditor/ckeditor5-upload" "47.3.0" - "@ckeditor/ckeditor5-utils" "47.3.0" - "@ckeditor/ckeditor5-watchdog" "47.3.0" - "@ckeditor/ckeditor5-widget" "47.3.0" - "@ckeditor/ckeditor5-word-count" "47.3.0" + resolved "https://github.com/HatScripts/circle-flags#85ab1fc0f04e6404768db89a27b2fb5bc1118ea4" + +ckeditor5@47.4.0, ckeditor5@^47: + version "47.4.0" + resolved "https://registry.yarnpkg.com/ckeditor5/-/ckeditor5-47.4.0.tgz#eb7879a23e780c356a2a48d477663d857494721a" + integrity sha512-6RTRV2w6nhmBSLBnA0O9QzcBC/Cf74ogziaKHOK61H+PcM6aP3ltb/fNScGyy3NVw3+OzaxjbPF7NSykVmmMMw== + dependencies: + "@ckeditor/ckeditor5-adapter-ckfinder" "47.4.0" + "@ckeditor/ckeditor5-alignment" "47.4.0" + "@ckeditor/ckeditor5-autoformat" "47.4.0" + "@ckeditor/ckeditor5-autosave" "47.4.0" + "@ckeditor/ckeditor5-basic-styles" "47.4.0" + "@ckeditor/ckeditor5-block-quote" "47.4.0" + "@ckeditor/ckeditor5-bookmark" "47.4.0" + "@ckeditor/ckeditor5-ckbox" "47.4.0" + "@ckeditor/ckeditor5-ckfinder" "47.4.0" + "@ckeditor/ckeditor5-clipboard" "47.4.0" + "@ckeditor/ckeditor5-cloud-services" "47.4.0" + "@ckeditor/ckeditor5-code-block" "47.4.0" + "@ckeditor/ckeditor5-core" "47.4.0" + "@ckeditor/ckeditor5-easy-image" "47.4.0" + "@ckeditor/ckeditor5-editor-balloon" "47.4.0" + "@ckeditor/ckeditor5-editor-classic" "47.4.0" + "@ckeditor/ckeditor5-editor-decoupled" "47.4.0" + "@ckeditor/ckeditor5-editor-inline" "47.4.0" + "@ckeditor/ckeditor5-editor-multi-root" "47.4.0" + "@ckeditor/ckeditor5-emoji" "47.4.0" + "@ckeditor/ckeditor5-engine" "47.4.0" + "@ckeditor/ckeditor5-enter" "47.4.0" + "@ckeditor/ckeditor5-essentials" "47.4.0" + "@ckeditor/ckeditor5-find-and-replace" "47.4.0" + "@ckeditor/ckeditor5-font" "47.4.0" + "@ckeditor/ckeditor5-fullscreen" "47.4.0" + "@ckeditor/ckeditor5-heading" "47.4.0" + "@ckeditor/ckeditor5-highlight" "47.4.0" + "@ckeditor/ckeditor5-horizontal-line" "47.4.0" + "@ckeditor/ckeditor5-html-embed" "47.4.0" + "@ckeditor/ckeditor5-html-support" "47.4.0" + "@ckeditor/ckeditor5-icons" "47.4.0" + "@ckeditor/ckeditor5-image" "47.4.0" + "@ckeditor/ckeditor5-indent" "47.4.0" + "@ckeditor/ckeditor5-language" "47.4.0" + "@ckeditor/ckeditor5-link" "47.4.0" + "@ckeditor/ckeditor5-list" "47.4.0" + "@ckeditor/ckeditor5-markdown-gfm" "47.4.0" + "@ckeditor/ckeditor5-media-embed" "47.4.0" + "@ckeditor/ckeditor5-mention" "47.4.0" + "@ckeditor/ckeditor5-minimap" "47.4.0" + "@ckeditor/ckeditor5-page-break" "47.4.0" + "@ckeditor/ckeditor5-paragraph" "47.4.0" + "@ckeditor/ckeditor5-paste-from-office" "47.4.0" + "@ckeditor/ckeditor5-remove-format" "47.4.0" + "@ckeditor/ckeditor5-restricted-editing" "47.4.0" + "@ckeditor/ckeditor5-select-all" "47.4.0" + "@ckeditor/ckeditor5-show-blocks" "47.4.0" + "@ckeditor/ckeditor5-source-editing" "47.4.0" + "@ckeditor/ckeditor5-special-characters" "47.4.0" + "@ckeditor/ckeditor5-style" "47.4.0" + "@ckeditor/ckeditor5-table" "47.4.0" + "@ckeditor/ckeditor5-theme-lark" "47.4.0" + "@ckeditor/ckeditor5-typing" "47.4.0" + "@ckeditor/ckeditor5-ui" "47.4.0" + "@ckeditor/ckeditor5-undo" "47.4.0" + "@ckeditor/ckeditor5-upload" "47.4.0" + "@ckeditor/ckeditor5-utils" "47.4.0" + "@ckeditor/ckeditor5-watchdog" "47.4.0" + "@ckeditor/ckeditor5-widget" "47.4.0" + "@ckeditor/ckeditor5-word-count" "47.4.0" clone-deep@^4.0.1: version "4.0.1" @@ -3163,10 +3207,15 @@ debug@^4.0.0, debug@^4.1.0, debug@^4.3.1, debug@^4.4.1: dependencies: ms "^2.1.3" +decimal.js@^10.4.3: + version "10.6.0" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a" + integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg== + decode-named-character-reference@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz#25c32ae6dd5e21889549d40f676030e9514cc0ed" - integrity sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q== + version "1.3.0" + resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz#3e40603760874c2e5867691b599d73a7da25b53f" + integrity sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q== dependencies: character-entities "^2.0.0" @@ -3203,11 +3252,6 @@ desandro-matches-selector@^2.0.0: resolved "https://registry.yarnpkg.com/desandro-matches-selector/-/desandro-matches-selector-2.0.2.tgz#717beed4dc13e7d8f3762f707a6d58a6774218e1" integrity sha512-+1q0nXhdzg1IpIJdMKalUwvvskeKnYyEe3shPRwedNcWtnhEKT3ZxvFjzywHDeGcKViIxTCAoOYQWP1qD7VNyg== -detect-libc@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== - detect-libc@^2.0.3: version "2.1.2" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" @@ -4061,6 +4105,16 @@ interpret@^3.1.1: resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== +intl-messageformat@^10.5.11: + version "10.7.18" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.7.18.tgz#51a6f387afbca9b0f881b2ec081566db8c540b0d" + integrity sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g== + dependencies: + "@formatjs/ecma402-abstract" "2.3.6" + "@formatjs/fast-memoize" "2.2.7" + "@formatjs/icu-messageformat-parser" "2.11.4" + tslib "^2.8.0" + is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" @@ -4389,7 +4443,12 @@ jquery-ui@1.13: dependencies: jquery ">=1.8.0 <4.0.0" -jquery@>=1.2.6, "jquery@>=1.8.0 <4.0.0", jquery@^3.5.0, jquery@^3.5.1, jquery@^3.6.0: +jquery@>=1.2.6: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-4.0.0.tgz#95c33ac29005ff72ec444c5ba1cf457e61404fbb" + integrity sha512-TXCHVR3Lb6TZdtw1l3RTLf8RBWVGexdxL6AC8/e0xZKEpBflBsjh9/8LXw+dkNFuOyW9B7iB3O1sP7hS0Kiacg== + +"jquery@>=1.8.0 <4.0.0", jquery@^3.5.0, jquery@^3.5.1, jquery@^3.6.0: version "3.7.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de" integrity sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg== @@ -5138,7 +5197,7 @@ micromark@^4.0.0: micromark-util-symbol "^2.0.0" micromark-util-types "^2.0.0" -micromatch@^4.0.0, micromatch@^4.0.5: +micromatch@^4.0.0: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -5176,9 +5235,9 @@ min-document@^2.19.0: dom-walk "^0.1.0" mini-css-extract-plugin@^2.6.0: - version "2.9.4" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz#cafa1a42f8c71357f49cd1566810d74ff1cb0200" - integrity sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ== + version "2.10.0" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.0.tgz#d801a1f388f8fac7333c01b7c15c9222c811def4" + integrity sha512-540P2c5dYnJlyJxTaSloliZexv8rji6rY8FhQN+WF/82iHQfA23j/xtJx97L+mXOML27EqksSek/g4eK7jaL3g== dependencies: schema-utils "^4.0.0" tapable "^2.2.1" @@ -5414,7 +5473,7 @@ picomatch@^2.2.2, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -picomatch@^4.0.2: +picomatch@^4.0.2, picomatch@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== @@ -6133,9 +6192,9 @@ sass-loader@16.*: neo-async "^2.6.2" sass@1.*: - version "1.97.1" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.97.1.tgz#f36e492baf8ccdd08d591b58d3d8b53ea35ab905" - integrity sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A== + version "1.97.2" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.97.2.tgz#e515a319092fd2c3b015228e3094b40198bff0da" + integrity sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw== dependencies: chokidar "^4.0.0" immutable "^5.0.2" @@ -6144,9 +6203,9 @@ sass@1.*: "@parcel/watcher" "^2.4.1" sax@^1.4.1: - version "1.4.3" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.3.tgz#fcebae3b756cdc8428321805f4b70f16ec0ab5db" - integrity sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ== + version "1.4.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.4.tgz#f29c2bba80ce5b86f4343b4c2be9f2b96627cf8b" + integrity sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw== scheduler@^0.20.2: version "0.20.2" @@ -6561,9 +6620,9 @@ terser-webpack-plugin@^5.3.0, terser-webpack-plugin@^5.3.16: terser "^5.31.1" terser@^5.0.0, terser@^5.31.1: - version "5.44.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.44.1.tgz#e391e92175c299b8c284ad6ded609e37303b0a9c" - integrity sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw== + version "5.46.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.46.0.tgz#1b81e560d584bbdd74a8ede87b4d9477b0ff9695" + integrity sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.15.0" @@ -6633,7 +6692,7 @@ ts-loader@^9.0.0: semver "^7.3.4" source-map "^0.7.4" -tslib@^2.4.0, tslib@^2.7.0: +tslib@^2.4.0, tslib@^2.7.0, tslib@^2.8.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -6832,10 +6891,14 @@ utila@~0.4: resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== +ux-translator@symfony/ux-translator: + version "0.0.0" + resolved "https://codeload.github.com/symfony/ux-translator/tar.gz/838aaf22e73d6fec2ff8960b1e3a0b4973e6e5b9" + vanilla-calendar-pro@^3.0: - version "3.0.5" - resolved "https://registry.yarnpkg.com/vanilla-calendar-pro/-/vanilla-calendar-pro-3.0.5.tgz#4832d508a7dcb7eeb7e104c8397d08526f99b64e" - integrity sha512-4X9bmTo1/KzbZrB7B6mZXtvVXIhcKxaVSnFZuaVtps7tshKJDxgaIElkgdia6IjB5qWetWuu7kZ+ZaV1sPxy6w== + version "3.1.0" + resolved "https://registry.yarnpkg.com/vanilla-calendar-pro/-/vanilla-calendar-pro-3.1.0.tgz#e64fb1e7bfad6f63858778c9130af8b75832a268" + integrity sha512-yXDtCaedcKz6i5OOdWGwui0C8MAmjXjj7JzKZyjDlkczSRqnhI8BDGFygqT2K+qL1uY7R2fLYlTlxA6oyFs2yg== vanilla-colorful@0.7.2: version "0.7.2" @@ -6866,9 +6929,9 @@ video-worker@^1.1.17: global "^4.4.0" watchpack@^2.4.4: - version "2.5.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.5.0.tgz#fa115d5ccaa4bf3aa594f586257c0bc4768939fd" - integrity sha512-e6vZvY6xboSwLz2GD36c16+O/2Z6fKvIf4pOXptw2rY9MVwE/TXc6RGqxD3I3x0a28lwBY7DE+76uTPSsBrrCA== + version "2.5.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.5.1.tgz#dd38b601f669e0cbf567cb802e75cead82cde102" + integrity sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -7021,9 +7084,9 @@ which-collection@^1.0.2: is-weakset "^2.0.3" which-typed-array@^1.1.16, which-typed-array@^1.1.19: - version "1.1.19" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" - integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== + version "1.1.20" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.20.tgz#3fdb7adfafe0ea69157b1509f3a1cd892bd1d122" + integrity sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg== dependencies: available-typed-arrays "^1.0.7" call-bind "^1.0.8"