From 32c69d30c4f7245d83ebde5deb03ff9bbde78346 Mon Sep 17 00:00:00 2001 From: Andreas Date: Sun, 19 Apr 2026 00:06:24 +0200 Subject: [PATCH] Sync folders --- ansico-diagnosekoder/README.txt | 37 + ansico-diagnosekoder/ansico-diagnosekoder.php | 55 ++ ansico-diagnosekoder/assets/css/style.css | 438 ++++++++++ ansico-diagnosekoder/assets/js/block.js | 66 ++ ansico-diagnosekoder/assets/js/frontend.js | 774 ++++++++++++++++++ .../class-ansico-diagnosekoder-admin.php | 388 +++++++++ .../class-ansico-diagnosekoder-block.php | 350 ++++++++ .../class-ansico-diagnosekoder-parser.php | 198 +++++ .../class-ansico-diagnosekoder-rest.php | 387 +++++++++ 9 files changed, 2693 insertions(+) create mode 100644 ansico-diagnosekoder/README.txt create mode 100644 ansico-diagnosekoder/ansico-diagnosekoder.php create mode 100644 ansico-diagnosekoder/assets/css/style.css create mode 100644 ansico-diagnosekoder/assets/js/block.js create mode 100644 ansico-diagnosekoder/assets/js/frontend.js create mode 100644 ansico-diagnosekoder/includes/class-ansico-diagnosekoder-admin.php create mode 100644 ansico-diagnosekoder/includes/class-ansico-diagnosekoder-block.php create mode 100644 ansico-diagnosekoder/includes/class-ansico-diagnosekoder-parser.php create mode 100644 ansico-diagnosekoder/includes/class-ansico-diagnosekoder-rest.php diff --git a/ansico-diagnosekoder/README.txt b/ansico-diagnosekoder/README.txt new file mode 100644 index 0000000..e3e1fdb --- /dev/null +++ b/ansico-diagnosekoder/README.txt @@ -0,0 +1,37 @@ +=== Ansico Diagnosekoder === +Contributors: aphandersen +Tags: icd-10, diagnosis, gutenberg, search +Requires at least: 6.2 +Tested up to: 6.5 +Stable tag: 1.0.0 +License: GPLv3 or later +License URI: https://www.gnu.org/licenses/gpl-3.0.html + +Et WordPress-plugin der tilføjer Gutenberg-blokke til søgning i ICD-10 diagnosekoder baseret på en uploadet tekstfil. + +== Beskrivelse == +- Tilføjer blokken "Ansico Diagnosekoder" med live resultater +- Tilføjer blokken "Ansico Diagnosekoder Søgefelt" til sidebar eller andre kompakte områder +- Tilføjer blokken "Ansico Diagnosekoder Kodekort" til visning af side-tilknyttede koder +- Backend-side under Indstillinger +- Upload af ny tekstfil erstatter eksisterende datagrundlag +- Søger kun i felterne kode og tekst +- Søgeord highlightes i resultaterne +- Klik på et resultat kopierer diagnosekoden + +== Installation == +1. Upload plugin-mappen til /wp-content/plugins/ eller installer ZIP-filen via WordPress. +2. Aktivér pluginet. +3. Gå til Indstillinger → Ansico Diagnosekoder. +4. Upload din tekstfil. +5. Indsæt blokken "Ansico Diagnosekoder" på en side. + + +== Changelog == + += 1.0.0 = +* Første stabile release. + + +== Support == +Support: https://ansico.dk/Ansico/Ansico-diagnosekoder diff --git a/ansico-diagnosekoder/ansico-diagnosekoder.php b/ansico-diagnosekoder/ansico-diagnosekoder.php new file mode 100644 index 0000000..1558ef5 --- /dev/null +++ b/ansico-diagnosekoder/ansico-diagnosekoder.php @@ -0,0 +1,55 @@ + 0 ? pageId : 0; + } + + function preloadPageState(root) { + if (!root || !root.getAttribute) { + return; + } + + var stateNode = root.querySelector('.ansico-diagnosekoder__page-state'); + if (stateNode) { + try { + var state = JSON.parse(stateNode.textContent || '{}'); + if (Array.isArray(state.favorites)) { + favoriteCodes = state.favorites.map(normalizeCode); + } + if (state.links && typeof state.links === 'object') { + codeLinks = {}; + Object.keys(state.links).forEach(function (code) { + codeLinks[normalizeCode(code)] = state.links[code]; + }); + } + if (state.pageId && root.getAttribute('data-page-id') !== String(state.pageId)) { + root.setAttribute('data-page-id', String(state.pageId)); + } + return; + } catch (error) {} + } + + var favoritesRaw = root.getAttribute('data-page-favorites') || '[]'; + var linksRaw = root.getAttribute('data-page-links') || '{}'; + + try { + var parsedFavorites = JSON.parse(favoritesRaw); + if (Array.isArray(parsedFavorites)) { + favoriteCodes = parsedFavorites.map(normalizeCode); + } + } catch (error) {} + + try { + var parsedLinks = JSON.parse(linksRaw); + if (parsedLinks && typeof parsedLinks === 'object') { + codeLinks = {}; + Object.keys(parsedLinks).forEach(function (code) { + codeLinks[normalizeCode(code)] = parsedLinks[code]; + }); + } + } catch (error) {} + } + + function debounce(fn, delay) { + var timer; + return function () { + var context = this; + var args = arguments; + clearTimeout(timer); + timer = setTimeout(function () { + fn.apply(context, args); + }, delay); + }; + } + + function escapeHtml(str) { + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + function escapeRegExp(str) { + return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + + function normalizeCode(code) { + return String(code || '').replace(/\s+/g, '').toUpperCase(); + } + + function highlightText(text, query) { + var safeText = escapeHtml(text); + var trimmedQuery = String(query || '').trim(); + + if (!trimmedQuery) { + return safeText; + } + + var parts = trimmedQuery.split(/\s+/).filter(Boolean); + if (!parts.length) { + return safeText; + } + + var pattern = parts.map(escapeRegExp).join('|'); + return safeText.replace(new RegExp('(' + pattern + ')', 'gi'), '$1'); + } + + function getCopyIcon() { + return ''; + } + + function getStarIcon(isActive) { + if (isActive) { + return ''; + } + + return ''; + } + + function getPlusIcon() { + return ''; + } + + function getMinusIcon() { + return ''; + } + + function isFavorite(code) { + return favoriteCodes.indexOf(normalizeCode(code)) !== -1; + } + + function getLinkedUrl(code, fallback) { + var normalized = normalizeCode(code); + if (normalized && typeof codeLinks[normalized] === 'string' && codeLinks[normalized]) { + return codeLinks[normalized]; + } + return fallback || ''; + } + + function setTemporaryLabel(button, label, duration) { + var originalLabel = button.getAttribute('data-label-default') || button.getAttribute('aria-label') || ''; + button.setAttribute('aria-label', label); + button.classList.add('is-feedback-visible'); + window.setTimeout(function () { + button.setAttribute('aria-label', originalLabel); + button.classList.remove('is-feedback-visible'); + }, duration || 1400); + } + + function pulseButton(button) { + button.classList.remove('is-pressed'); + void button.offsetWidth; + button.classList.add('is-pressed'); + window.setTimeout(function () { + button.classList.remove('is-pressed'); + }, 220); + } + + function copyText(value, button) { + var successLabel = button.getAttribute('data-label-success') || button.getAttribute('aria-label') || ''; + var errorLabel = button.getAttribute('data-label-error') || button.getAttribute('aria-label') || ''; + pulseButton(button); + + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(value).then(function () { + setTemporaryLabel(button, successLabel, 1400); + }).catch(function () { + setTemporaryLabel(button, errorLabel, 1400); + }); + return; + } + + try { + var temp = document.createElement('textarea'); + temp.value = value; + temp.setAttribute('readonly', 'readonly'); + temp.style.position = 'absolute'; + temp.style.left = '-9999px'; + document.body.appendChild(temp); + temp.select(); + document.execCommand('copy'); + document.body.removeChild(temp); + setTemporaryLabel(button, successLabel, 1400); + } catch (error) { + setTemporaryLabel(button, errorLabel, 1400); + } + } + + + + + + function searchInternalContent(query) { + if (!AnsicoDiagnosekoderConfig.contentSearchUrl) { + return Promise.resolve([]); + } + + var url = AnsicoDiagnosekoderConfig.contentSearchUrl + '?q=' + encodeURIComponent(query) + '&limit=8'; + return fetch(url, { + credentials: 'same-origin', + headers: { + 'X-WP-Nonce': AnsicoDiagnosekoderConfig.nonce + }, + cache: 'no-store' + }).then(function (response) { + if (!response.ok) { + throw new Error('Internal content search failed'); + } + return response.json(); + }).then(function (data) { + return Array.isArray(data.results) ? data.results : []; + }); + } + + function ensureLinkModal() { + var existing = document.getElementById('ansico-diagnosekoder-link-modal'); + if (existing) { + return existing; + } + + var wrapper = document.createElement('div'); + wrapper.id = 'ansico-diagnosekoder-link-modal'; + wrapper.className = 'ansico-diagnosekoder__modal'; + wrapper.setAttribute('hidden', 'hidden'); + wrapper.innerHTML = '' + + '
' + + ''; + document.body.appendChild(wrapper); + return wrapper; + } + + function openLinkModal(code, currentUrl) { + var modal = ensureLinkModal(); + var titleNode = modal.querySelector('.ansico-diagnosekoder__modal-title'); + var labels = modal.querySelectorAll('.ansico-diagnosekoder__modal-label'); + var hintNode = modal.querySelector('.ansico-diagnosekoder__modal-hint'); + var codeInput = modal.querySelector('#ansico-diagnosekoder-link-modal-code'); + var urlInput = modal.querySelector('#ansico-diagnosekoder-link-modal-url'); + var searchInput = modal.querySelector('#ansico-diagnosekoder-link-modal-search'); + var resultsNode = modal.querySelector('.ansico-diagnosekoder__modal-search-results'); + var cancelButton = modal.querySelector('[data-modal-cancel="1"]'); + var saveButton = modal.querySelector('[data-modal-save="1"]'); + var closeButtons = modal.querySelectorAll('[data-modal-close="1"]'); + var activeSearchToken = 0; + + titleNode.textContent = currentUrl ? AnsicoDiagnosekoderConfig.strings.linkModalTitleEdit : AnsicoDiagnosekoderConfig.strings.linkModalTitleAdd; + labels[0].textContent = AnsicoDiagnosekoderConfig.strings.linkModalCodeLabel; + labels[1].textContent = AnsicoDiagnosekoderConfig.strings.linkModalUrlLabel; + labels[2].textContent = AnsicoDiagnosekoderConfig.strings.linkModalSearchLabel; + hintNode.textContent = AnsicoDiagnosekoderConfig.strings.linkModalHint; + cancelButton.textContent = AnsicoDiagnosekoderConfig.strings.linkModalCancel; + saveButton.textContent = AnsicoDiagnosekoderConfig.strings.linkModalSave; + codeInput.value = code || ''; + urlInput.value = currentUrl || ''; + urlInput.placeholder = AnsicoDiagnosekoderConfig.strings.linkPromptDefault || 'https://'; + searchInput.value = ''; + searchInput.placeholder = AnsicoDiagnosekoderConfig.strings.linkModalSearchPlaceholder; + resultsNode.innerHTML = '

' + escapeHtml(AnsicoDiagnosekoderConfig.strings.linkModalSearchEmpty) + '

'; + modal.removeAttribute('hidden'); + document.body.classList.add('ansico-diagnosekoder-modal-open'); + + function closeModal() { + modal.setAttribute('hidden', 'hidden'); + document.body.classList.remove('ansico-diagnosekoder-modal-open'); + saveButton.onclick = null; + cancelButton.onclick = null; + closeButtons.forEach(function (button) { button.onclick = null; }); + searchInput.oninput = null; + document.removeEventListener('keydown', onKeyDown); + } + + function onKeyDown(event) { + if (event.key === 'Escape') { + closeModal(); + } + } + + function renderSearchResults(items) { + if (!items.length) { + resultsNode.innerHTML = '

' + escapeHtml(AnsicoDiagnosekoderConfig.strings.linkModalSearchNone) + '

'; + return; + } + + var html = ''; + resultsNode.innerHTML = html; + resultsNode.querySelectorAll('.ansico-diagnosekoder__modal-result-button').forEach(function (button) { + button.addEventListener('click', function () { + urlInput.value = button.getAttribute('data-url') || ''; + }); + }); + } + + searchInput.oninput = debounce(function () { + var query = searchInput.value.trim(); + activeSearchToken += 1; + var token = activeSearchToken; + + if (!query) { + resultsNode.innerHTML = '

' + escapeHtml(AnsicoDiagnosekoderConfig.strings.linkModalSearchEmpty) + '

'; + return; + } + + resultsNode.innerHTML = '

' + escapeHtml(AnsicoDiagnosekoderConfig.strings.loading) + '

'; + searchInternalContent(query).then(function (items) { + if (token !== activeSearchToken) { + return; + } + renderSearchResults(items); + }).catch(function () { + resultsNode.innerHTML = '

' + escapeHtml(AnsicoDiagnosekoderConfig.strings.linkSearchError) + '

'; + }); + }, 180); + + var resolver; + var promise = new Promise(function (resolve) { + resolver = resolve; + }); + + saveButton.onclick = function () { + resolver(String(urlInput.value || '').trim()); + closeModal(); + }; + cancelButton.onclick = function () { + resolver(null); + closeModal(); + }; + closeButtons.forEach(function (button) { + button.onclick = function () { + resolver(null); + closeModal(); + }; + }); + + document.addEventListener('keydown', onKeyDown); + window.setTimeout(function () { urlInput.focus(); urlInput.select(); }, 0); + return promise; + } + + function fetchPageState(pageId) { + if (!pageId || !AnsicoDiagnosekoderConfig.pageStateUrl) { + return Promise.resolve({ favorites: favoriteCodes, links: codeLinks, page_id: pageId || 0 }); + } + + var url = AnsicoDiagnosekoderConfig.pageStateUrl + '?page_id=' + encodeURIComponent(pageId) + '&_ansico_ts=' + Date.now(); + return fetch(url, { + credentials: 'same-origin', + cache: 'no-store' + }).then(function (response) { + if (!response.ok) { + throw new Error('Page state failed'); + } + return response.json(); + }).then(function (data) { + syncClientCollections(data || {}); + return data || {}; + }).catch(function () { + return { favorites: favoriteCodes, links: codeLinks, page_id: pageId || 0 }; + }); + } + + function mergeAndSortResults(results) { + var merged = (Array.isArray(results) ? results : []).map(function (item, index) { + var normalizedCode = normalizeCode(item.code); + var favorite = isFavorite(normalizedCode) || !!item.is_favorite; + var linkedUrl = getLinkedUrl(normalizedCode, item.linked_url || ''); + return Object.assign({}, item, { + is_favorite: favorite, + linked_url: linkedUrl, + _normalized_code: normalizedCode, + _render_index: index + }); + }); + + merged.sort(function (a, b) { + if (!!a.is_favorite !== !!b.is_favorite) { + return a.is_favorite ? -1 : 1; + } + return a._render_index - b._render_index; + }); + + return merged; + } + + function renderResults(container, results, query) { + if (!results.length) { + container.innerHTML = '

' + escapeHtml(AnsicoDiagnosekoderConfig.strings.noResults) + '

'; + return; + } + + var sortedResults = mergeAndSortResults(results); + var html = ''; + container.innerHTML = html; + } + + function getQueryParam(name) { + var params = new URLSearchParams(window.location.search); + return params.get(name) || ''; + } + + function getSelectorEscaped(value) { + if (window.CSS && typeof window.CSS.escape === 'function') { + return window.CSS.escape(value); + } + return String(value).replace(/"/g, '\\"'); + } + + function updateFavoriteButtons(code, isFav, pageId) { + var normalizedCode = normalizeCode(code); + var selector = '.ansico-diagnosekoder__favorite-button[data-code="' + getSelectorEscaped(normalizedCode) + '"]'; + if (pageId) { + selector = '.ansico-diagnosekoder[data-page-id="' + getSelectorEscaped(String(pageId)) + '"] ' + selector; + } + var buttons = document.querySelectorAll(selector); + buttons.forEach(function (button) { + var label = isFav ? AnsicoDiagnosekoderConfig.strings.removeFavorite : AnsicoDiagnosekoderConfig.strings.addFavorite; + button.classList.toggle('is-active', isFav); + button.setAttribute('aria-pressed', isFav ? 'true' : 'false'); + button.setAttribute('aria-label', label); + button.setAttribute('data-label-default', label); + button.innerHTML = getStarIcon(isFav); + var item = button.closest('.ansico-diagnosekoder__item'); + if (item) { + item.classList.toggle('is-favorite', isFav); + item.setAttribute('data-is-favorite', isFav ? '1' : '0'); + } + }); + } + + function updateLinkButtons(code, url) { + var normalizedCode = normalizeCode(code); + var buttons = document.querySelectorAll('.ansico-diagnosekoder__link-button[data-code="' + getSelectorEscaped(normalizedCode) + '"]'); + var hasLink = !!url; + buttons.forEach(function (button) { + var label = hasLink ? AnsicoDiagnosekoderConfig.strings.removeLink : AnsicoDiagnosekoderConfig.strings.addLink; + button.classList.toggle('is-active', hasLink); + button.setAttribute('aria-label', label); + button.setAttribute('data-label-default', label); + button.setAttribute('data-current-url', url || ''); + button.setAttribute('title', hasLink ? AnsicoDiagnosekoderConfig.strings.removeLinkIcon : AnsicoDiagnosekoderConfig.strings.addLinkIcon); + button.innerHTML = hasLink ? getMinusIcon() : getPlusIcon(); + var item = button.closest('.ansico-diagnosekoder__item'); + if (item) { + item.classList.toggle('has-link', hasLink); + item.setAttribute('data-has-link', hasLink ? '1' : '0'); + } + }); + } + + function syncClientCollections(data) { + if (Array.isArray(data.favorites)) { + favoriteCodes = data.favorites.map(normalizeCode); + } + if (data.links && typeof data.links === 'object') { + codeLinks = {}; + Object.keys(data.links).forEach(function (code) { + codeLinks[normalizeCode(code)] = data.links[code]; + }); + } + } + + function toggleFavorite(code, button, refreshCallback, pageId) { + fetch(AnsicoDiagnosekoderConfig.favoritesUrl, { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + 'X-WP-Nonce': AnsicoDiagnosekoderConfig.nonce + }, + body: JSON.stringify({ code: normalizeCode(code), page_id: pageId || 0 }) + }).then(function (response) { + if (!response.ok) { + throw new Error('Favorite toggle failed'); + } + return response.json(); + }).then(function (data) { + syncClientCollections(data); + pulseButton(button); + updateFavoriteButtons(code, !!data.is_favorite, pageId || 0); + if (typeof refreshCallback === 'function') { + refreshCallback(); + } + }).catch(function () { + setTemporaryLabel(button, AnsicoDiagnosekoderConfig.strings.favoriteError, 1400); + }); + } + + function saveCodeLink(code, url, button, refreshCallback, pageId) { + fetch(AnsicoDiagnosekoderConfig.linksUrl, { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + 'X-WP-Nonce': AnsicoDiagnosekoderConfig.nonce + }, + body: JSON.stringify({ code: normalizeCode(code), url: url, page_id: pageId || 0 }) + }).then(function (response) { + if (!response.ok) { + throw new Error('Link save failed'); + } + return response.json(); + }).then(function (data) { + syncClientCollections(data); + pulseButton(button); + updateLinkButtons(code, data.url || ''); + setTemporaryLabel(button, AnsicoDiagnosekoderConfig.strings.linkSaved, 1200); + if (typeof refreshCallback === 'function') { + refreshCallback(); + } + }).catch(function () { + setTemporaryLabel(button, AnsicoDiagnosekoderConfig.strings.linkError, 1400); + }); + } + + function removeCodeLink(code, button, refreshCallback, pageId) { + var url = AnsicoDiagnosekoderConfig.linksUrl + '?code=' + encodeURIComponent(normalizeCode(code)) + '&page_id=' + encodeURIComponent(pageId || 0); + fetch(url, { + method: 'DELETE', + credentials: 'same-origin', + headers: { + 'X-WP-Nonce': AnsicoDiagnosekoderConfig.nonce + } + }).then(function (response) { + if (!response.ok) { + throw new Error('Link removal failed'); + } + return response.json(); + }).then(function (data) { + syncClientCollections(data); + pulseButton(button); + updateLinkButtons(code, ''); + setTemporaryLabel(button, AnsicoDiagnosekoderConfig.strings.linkRemoved, 1200); + if (typeof refreshCallback === 'function') { + refreshCallback(); + } + }).catch(function () { + setTemporaryLabel(button, AnsicoDiagnosekoderConfig.strings.linkError, 1400); + }); + } + + + function initResultsBlock(root) { + preloadPageState(root); + var input = root.querySelector('.ansico-diagnosekoder__input'); + var results = root.querySelector('.ansico-diagnosekoder__results'); + var hasData = root.getAttribute('data-has-data') === '1'; + + if (!input || !results || !hasData) { + return; + } + + var runSearch = function () { + var query = input.value.trim(); + + if (!query) { + results.innerHTML = '

' + escapeHtml(AnsicoDiagnosekoderConfig.strings.empty) + '

'; + return; + } + + results.innerHTML = '

' + escapeHtml(AnsicoDiagnosekoderConfig.strings.loading) + '

'; + var pageId = getPageId(root); + var url = AnsicoDiagnosekoderConfig.restUrl + '?q=' + encodeURIComponent(query) + '&limit=50&page_id=' + encodeURIComponent(pageId) + '&_ansico_ts=' + Date.now(); + + fetchPageState(pageId).then(function () { + return fetch(url, { + credentials: 'same-origin', + cache: 'no-store' + }); + }) + .then(function (response) { + if (!response.ok) { + throw new Error('Request failed'); + } + return response.json(); + }) + .then(function (data) { + syncClientCollections(data); + if ( + (!Array.isArray(data.favorites) || !data.favorites.length) && + (!data.links || !Object.keys(data.links).length) && + (!favoriteCodes.length && !Object.keys(codeLinks).length) + ) { + preloadPageState(root); + } + renderResults(results, data.results || [], query); + }) + .catch(function () { + results.innerHTML = '

' + escapeHtml(AnsicoDiagnosekoderConfig.strings.error) + '

'; + }); + }; + + var debouncedSearch = debounce(runSearch, 200); + input.addEventListener('input', debouncedSearch); + + results.addEventListener('click', function (event) { + var copyButton = event.target.closest('.ansico-diagnosekoder__copy-button'); + if (copyButton) { + copyText(copyButton.getAttribute('data-copy-value') || '', copyButton); + return; + } + + var favoriteButton = event.target.closest('.ansico-diagnosekoder__favorite-button'); + if (favoriteButton) { + toggleFavorite(favoriteButton.getAttribute('data-code') || '', favoriteButton, runSearch, getPageId(root)); + return; + } + + var linkButton = event.target.closest('.ansico-diagnosekoder__link-button'); + if (linkButton) { + var code = linkButton.getAttribute('data-code') || ''; + var currentUrl = linkButton.getAttribute('data-current-url') || ''; + if (currentUrl) { + removeCodeLink(code, linkButton, runSearch, getPageId(root)); + return; + } + openLinkModal(code, currentUrl).then(function (enteredUrl) { + if (enteredUrl === null) { + return; + } + enteredUrl = String(enteredUrl).trim(); + if (!enteredUrl) { + return; + } + saveCodeLink(code, enteredUrl, linkButton, runSearch, getPageId(root)); + }); + } + }); + + var stateNode = root.querySelector('.ansico-diagnosekoder__page-state'); + var hasServerRenderedResults = false; + + if (stateNode) { + try { + var initialState = JSON.parse(stateNode.textContent || '{}'); + hasServerRenderedResults = !!(initialState && initialState.hasInitialResults); + } catch (error) {} + } + + fetchPageState(getPageId(root)).then(function () { + var presetQuery = getQueryParam('q'); + if (presetQuery) { + input.value = presetQuery; + if (!hasServerRenderedResults) { + runSearch(); + } + } + }); + } + + function initSearchOnlyBlock(form) { + form.addEventListener('submit', function (event) { + var input = form.querySelector('.ansico-diagnosekoder__input'); + var query = input ? input.value.trim() : ''; + var resultsUrl = form.getAttribute('data-results-url') || AnsicoDiagnosekoderConfig.resultsUrl || ''; + + if (!resultsUrl) { + event.preventDefault(); + var message = form.querySelector('.ansico-diagnosekoder__message'); + if (message) { + message.textContent = AnsicoDiagnosekoderConfig.strings.missingResultsUrl; + } + return; + } + + if (!query) { + event.preventDefault(); + return; + } + + event.preventDefault(); + var url = new URL(resultsUrl, window.location.origin); + url.searchParams.set('q', query); + window.location.href = url.toString(); + }); + } + + document.addEventListener('DOMContentLoaded', function () { + var resultsBlocks = document.querySelectorAll('.wp-block-ansico-diagnosekoder.ansico-diagnosekoder'); + resultsBlocks.forEach(initResultsBlock); + + var searchOnlyBlocks = document.querySelectorAll('.wp-block-ansico-diagnosekoder-soegefelt.ansico-diagnosekoder--search-only'); + searchOnlyBlocks.forEach(initSearchOnlyBlock); + }); +})(); diff --git a/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-admin.php b/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-admin.php new file mode 100644 index 0000000..f40593a --- /dev/null +++ b/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-admin.php @@ -0,0 +1,388 @@ + +
+

+ + +

+ +

+ +

+ +

+ +

+ +

+ +

+ + +

+ + + + + + + + + + + + + + + + + + +
+
+ + + + +
+
+ +

+ + +

+ + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+
+ +

+ + +

+ + + + + + + + + + + + + + + + + + + + +
+ +

+ + +

+

+ +
+
+ + + + + + + + + + + +
+ +
+ + + +
+
+ +

+
+ + + + + + + + + + + +
+
+ 'ansico-diagnosekoder', + 'status' => 'settings_saved', + ], admin_url('options-general.php'))); + exit; + } + + public static function handle_import_data() { + if (!current_user_can('manage_options')) { + wp_die(esc_html__('Du har ikke rettigheder til dette.', 'ansico-diagnosekoder')); + } + + check_admin_referer('ansico_diagnosekoder_import_data'); + + if (empty($_FILES['ansico_diagnosekoder_file']) || empty($_FILES['ansico_diagnosekoder_file']['tmp_name'])) { + wp_safe_redirect(add_query_arg([ + 'page' => 'ansico-diagnosekoder', + 'status' => 'no_file', + ], admin_url('options-general.php'))); + exit; + } + + $file = $_FILES['ansico_diagnosekoder_file']; + $overrides = [ + 'test_form' => false, + 'mimes' => [ + 'txt' => 'text/plain', + 'csv' => 'text/plain', + ], + ]; + + require_once ABSPATH . 'wp-admin/includes/file.php'; + $uploaded = wp_handle_upload($file, $overrides); + + if (!empty($uploaded['error'])) { + self::redirect_with_error($uploaded['error']); + } + + $parsed = Ansico_Diagnosekoder_Parser::parse_file($uploaded['file']); + if (!empty($parsed['meta']['error'])) { + self::redirect_with_error($parsed['meta']['error']); + } + + if (empty($parsed['rows'])) { + self::redirect_with_error(__('Filen indeholdt ingen importerbare diagnosekoder.', 'ansico-diagnosekoder')); + } + + update_option(ANSICO_DIAGNOSEKODER_OPTION_DATA, $parsed['rows'], false); + update_option(ANSICO_DIAGNOSEKODER_OPTION_META, $parsed['meta'], false); + + wp_safe_redirect(add_query_arg([ + 'page' => 'ansico-diagnosekoder', + 'status' => 'success', + ], admin_url('options-general.php'))); + exit; + } + + public static function handle_remove_data() { + if (!current_user_can('manage_options')) { + wp_die(esc_html__('Du har ikke rettigheder til dette.', 'ansico-diagnosekoder')); + } + + check_admin_referer('ansico_diagnosekoder_remove_data'); + + delete_option(ANSICO_DIAGNOSEKODER_OPTION_DATA); + delete_option(ANSICO_DIAGNOSEKODER_OPTION_META); + + wp_safe_redirect(add_query_arg([ + 'page' => 'ansico-diagnosekoder', + 'status' => 'data_removed', + ], admin_url('options-general.php'))); + exit; + } + + public static function handle_remove_favorite() { + if (!current_user_can('manage_options')) { + wp_die(esc_html__('Du har ikke rettigheder til dette.', 'ansico-diagnosekoder')); + } + + $code = isset($_POST['code']) ? sanitize_text_field(wp_unslash($_POST['code'])) : ''; + if ($code === '') { + self::redirect_with_error(__('Der mangler en kode.', 'ansico-diagnosekoder')); + } + + check_admin_referer('ansico_diagnosekoder_remove_favorite_' . $code); + + $results_page_id = self::get_results_page_id(); + if (!$results_page_id) { + self::redirect_with_error(__('Resultatsiden kunne ikke findes.', 'ansico-diagnosekoder')); + } + + $favorites = Ansico_Diagnosekoder_REST::get_page_favorites($results_page_id); + $normalized_code = strtoupper(preg_replace('/\s+/', '', $code)); + $favorites = array_values(array_filter($favorites, static function ($item) use ($normalized_code) { + return $item !== $normalized_code; + })); + update_post_meta($results_page_id, ANSICO_DIAGNOSEKODER_PAGE_FAVORITES_META, $favorites); + + wp_safe_redirect(add_query_arg([ + 'page' => 'ansico-diagnosekoder', + 'status' => 'favorite_removed', + ], admin_url('options-general.php'))); + exit; + } + + public static function handle_remove_link() { + if (!current_user_can('manage_options')) { + wp_die(esc_html__('Du har ikke rettigheder til dette.', 'ansico-diagnosekoder')); + } + + $code = isset($_POST['code']) ? sanitize_text_field(wp_unslash($_POST['code'])) : ''; + if ($code === '') { + self::redirect_with_error(__('Der mangler en kode.', 'ansico-diagnosekoder')); + } + + check_admin_referer('ansico_diagnosekoder_remove_link_' . $code); + + $results_page_id = self::get_results_page_id(); + if (!$results_page_id) { + self::redirect_with_error(__('Resultatsiden kunne ikke findes.', 'ansico-diagnosekoder')); + } + + $normalized_code = strtoupper(preg_replace('/\s+/', '', $code)); + $links = Ansico_Diagnosekoder_REST::get_page_links($results_page_id); + unset($links[$normalized_code]); + update_post_meta($results_page_id, ANSICO_DIAGNOSEKODER_PAGE_LINKS_META, $links); + + wp_safe_redirect(add_query_arg([ + 'page' => 'ansico-diagnosekoder', + 'status' => 'link_removed', + ], admin_url('options-general.php'))); + exit; + } + + protected static function get_results_page_favorite_rows() { + $results_page_id = self::get_results_page_id(); + if (!$results_page_id) { + return []; + } + + return Ansico_Diagnosekoder_Parser::get_rows_by_codes(Ansico_Diagnosekoder_REST::get_page_favorites($results_page_id)); + } + + protected static function get_results_page_id() { + $results_url = (string) get_option(ANSICO_DIAGNOSEKODER_OPTION_RESULTS_URL, ''); + if ($results_url === '') { + return 0; + } + + $page_id = url_to_postid($results_url); + return absint($page_id); + } + + protected static function get_results_page_link_rows() { + $results_page_id = self::get_results_page_id(); + if (!$results_page_id) { + return []; + } + + $links = Ansico_Diagnosekoder_REST::get_page_links($results_page_id); + if (empty($links)) { + return []; + } + + $rows = Ansico_Diagnosekoder_Parser::get_rows_by_codes(array_keys($links)); + foreach ($rows as &$row) { + $row['url'] = isset($links[$row['code']]) ? $links[$row['code']] : ''; + } + unset($row); + + return $rows; + } + + protected static function redirect_with_error($message) { + set_transient('ansico_diagnosekoder_admin_error', wp_strip_all_tags((string) $message), 60); + wp_safe_redirect(add_query_arg([ + 'page' => 'ansico-diagnosekoder', + 'status' => 'error', + ], admin_url('options-general.php'))); + exit; + } +} diff --git a/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-block.php b/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-block.php new file mode 100644 index 0000000..f074294 --- /dev/null +++ b/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-block.php @@ -0,0 +1,350 @@ + esc_url_raw(rest_url('ansico-diagnosekoder/v1/search')), + 'favoritesUrl' => esc_url_raw(rest_url('ansico-diagnosekoder/v1/favorites')), + 'pageStateUrl' => esc_url_raw(rest_url('ansico-diagnosekoder/v1/page-state')), + 'contentSearchUrl' => esc_url_raw(rest_url('ansico-diagnosekoder/v1/content-search')), + 'linksUrl' => esc_url_raw(rest_url('ansico-diagnosekoder/v1/links')), + 'resultsUrl' => esc_url_raw((string) get_option(ANSICO_DIAGNOSEKODER_OPTION_RESULTS_URL, '')), + 'nonce' => wp_create_nonce('wp_rest'), + 'isLoggedIn' => is_user_logged_in(), + 'resultsPageId' => self::get_results_page_id(), + 'strings' => [ + 'placeholder' => __('Søg efter diagnosekode eller beskrivelse', 'ansico-diagnosekoder'), + 'empty' => __('Skriv for at søge i diagnosekoder.', 'ansico-diagnosekoder'), + 'noResults' => __('Ingen resultater fundet.', 'ansico-diagnosekoder'), + 'loading' => __('Søger...', 'ansico-diagnosekoder'), + 'error' => __('Der opstod en fejl under søgningen.', 'ansico-diagnosekoder'), + 'goToResults' => __('Gå til søgeresultater', 'ansico-diagnosekoder'), + 'missingResultsUrl' => __('Resultatsidens URL er ikke angivet i pluginets indstillinger.', 'ansico-diagnosekoder'), + 'copyCode' => __('Kopiér kode og beskrivelse', 'ansico-diagnosekoder'), + 'copied' => __('Kopieret', 'ansico-diagnosekoder'), + 'copyError' => __('Kunne ikke kopiere', 'ansico-diagnosekoder'), + 'copyCodeAria' => __('Kopiér %s', 'ansico-diagnosekoder'), + 'addFavorite' => __('Tilføj til favoritter', 'ansico-diagnosekoder'), + 'removeFavorite' => __('Fjern fra favoritter', 'ansico-diagnosekoder'), + 'favoriteAdded' => __('Favorit gemt', 'ansico-diagnosekoder'), + 'favoriteRemoved' => __('Favorit fjernet', 'ansico-diagnosekoder'), + 'favoriteError' => __('Kunne ikke gemme favorit', 'ansico-diagnosekoder'), + 'copyIcon' => __('Kopiér', 'ansico-diagnosekoder'), + 'favoriteIcon' => __('Favorit', 'ansico-diagnosekoder'), + 'addLink' => __('Tilknyt URL', 'ansico-diagnosekoder'), + 'removeLink' => __('Fjern URL-tilknytning', 'ansico-diagnosekoder'), + 'linkSaved' => __('URL gemt', 'ansico-diagnosekoder'), + 'linkRemoved' => __('URL fjernet', 'ansico-diagnosekoder'), + 'linkError' => __('Kunne ikke gemme URL', 'ansico-diagnosekoder'), + 'linkPrompt' => __('Indtast URL for %s', 'ansico-diagnosekoder'), + 'linkPromptDefault' => __('https://', 'ansico-diagnosekoder'), + 'openLink' => __('Åbn link', 'ansico-diagnosekoder'), + 'linkIcon' => __('Link', 'ansico-diagnosekoder'), + 'addLinkIcon' => __('Tilknyt URL', 'ansico-diagnosekoder'), + 'removeLinkIcon' => __('Fjern URL', 'ansico-diagnosekoder'), + 'linkModalTitleAdd' => __('Tilknyt link til kode', 'ansico-diagnosekoder'), + 'linkModalTitleEdit' => __('Rediger link til kode', 'ansico-diagnosekoder'), + 'linkModalCodeLabel' => __('Diagnosekode', 'ansico-diagnosekoder'), + 'linkModalUrlLabel' => __('URL-adresse', 'ansico-diagnosekoder'), + 'linkModalSearchLabel' => __('Søg efter interne sider', 'ansico-diagnosekoder'), + 'linkModalSearchPlaceholder' => __('Søg efter side eller indlæg', 'ansico-diagnosekoder'), + 'linkModalSearchEmpty' => __('Skriv for at søge efter interne sider.', 'ansico-diagnosekoder'), + 'linkModalSearchNone' => __('Ingen interne sider fundet.', 'ansico-diagnosekoder'), + 'linkModalChoose' => __('Vælg', 'ansico-diagnosekoder'), + 'linkModalCancel' => __('Annuller', 'ansico-diagnosekoder'), + 'linkModalSave' => __('Gem link', 'ansico-diagnosekoder'), + 'linkModalOpen' => __('Åbn link-dialog', 'ansico-diagnosekoder'), + 'linkModalHint' => __('Du kan enten indsætte en URL eller vælge en intern side.', 'ansico-diagnosekoder'), + 'linkSearchError' => __('Kunne ikke søge efter interne sider.', 'ansico-diagnosekoder'), + ], + ]); + + wp_register_style( + 'ansico-diagnosekoder-style', + ANSICO_DIAGNOSEKODER_PLUGIN_URL . 'assets/css/style.css', + [], + ANSICO_DIAGNOSEKODER_VERSION + ); + } + + public static function register_block() { + register_block_type('ansico/diagnosekoder', [ + 'api_version' => 2, + 'editor_script' => 'ansico-diagnosekoder-block-editor', + 'style' => 'ansico-diagnosekoder-style', + 'script' => 'ansico-diagnosekoder-frontend', + 'render_callback' => [__CLASS__, 'render_block'], + ]); + + register_block_type('ansico/diagnosekoder-soegefelt', [ + 'api_version' => 2, + 'editor_script' => 'ansico-diagnosekoder-block-editor', + 'style' => 'ansico-diagnosekoder-style', + 'script' => 'ansico-diagnosekoder-frontend', + 'render_callback' => [__CLASS__, 'render_search_form_block'], + ]); + + register_block_type('ansico/diagnosekoder-kodekort', [ + 'api_version' => 2, + 'editor_script' => 'ansico-diagnosekoder-block-editor', + 'style' => 'ansico-diagnosekoder-style', + 'render_callback' => [__CLASS__, 'render_linked_code_block'], + ]); + } + + public static function render_block($attributes = [], $content = '', $block = null) { + $meta = get_option(ANSICO_DIAGNOSEKODER_OPTION_META, []); + $input_id = wp_unique_id('ansico-diagnosekoder-input-'); + $page_id = self::get_current_page_id(); + if (!$page_id) { + $page_id = self::get_results_page_id(); + } + $favorites = Ansico_Diagnosekoder_REST::get_page_favorites($page_id); + $links = Ansico_Diagnosekoder_REST::get_page_links($page_id); + $page_state = wp_json_encode([ + 'pageId' => absint($page_id), + 'favorites' => array_values($favorites), + 'links' => $links, + ]); + $preset_query = isset($_GET['q']) ? sanitize_text_field(wp_unslash($_GET['q'])) : ''; + $initial_results = $preset_query !== '' ? Ansico_Diagnosekoder_Parser::search($preset_query, 50, $favorites, $links) : []; + ob_start(); + ?> +
+ + +
+ + + +

+ +

+ +
+
+ +
+ + +

+ +
+ $url) { + if (self::normalize_url($url) === $current_url) { + $matched_codes[] = $code; + } + } + + if (empty($matched_codes)) { + return ''; + } + + $rows = Ansico_Diagnosekoder_Parser::get_rows_by_codes($matched_codes); + if (empty($rows)) { + return ''; + } + + ob_start(); + ?> +
+
+ +
+ + + +
+ +
+
+ ID)) { + $page_id = absint($post->ID); + } + + return $page_id; + } + +} diff --git a/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-parser.php b/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-parser.php new file mode 100644 index 0000000..e7535e6 --- /dev/null +++ b/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-parser.php @@ -0,0 +1,198 @@ + [], + 'meta' => [ + 'error' => __('Filen kunne ikke læses.', 'ansico-diagnosekoder'), + ], + ]; + } + + $handle = fopen($file_path, 'r'); + if (!$handle) { + return [ + 'rows' => [], + 'meta' => [ + 'error' => __('Filen kunne ikke åbnes.', 'ansico-diagnosekoder'), + ], + ]; + } + + $rows = []; + $header_found = false; + $line_number = 0; + + while (($line = fgets($handle)) !== false) { + $line_number++; + $line = trim($line); + + if ($line === '') { + continue; + } + + $columns = str_getcsv($line, ';', '"', '\\'); + if (!is_array($columns) || count($columns) < 2) { + continue; + } + + $col1 = isset($columns[0]) ? trim((string) $columns[0]) : ''; + $col2 = isset($columns[1]) ? trim((string) $columns[1]) : ''; + + if (!$header_found) { + if (strcasecmp($col1, 'Kode') === 0 && strcasecmp($col2, 'Tekst') === 0) { + $header_found = true; + } + continue; + } + + if ($col1 === '' || $col2 === '') { + continue; + } + + $rows[] = [ + 'code' => sanitize_text_field($col1), + 'text' => sanitize_text_field($col2), + ]; + } + + fclose($handle); + + return [ + 'rows' => $rows, + 'meta' => [ + 'imported_at' => current_time('mysql'), + 'row_count' => count($rows), + 'source_name' => basename($file_path), + 'header_found' => $header_found, + 'line_count' => $line_number, + ], + ]; + } + + public static function search($query, $limit = 50, $favorite_codes = [], $links = []) { + $query = trim((string) $query); + if ($query === '') { + return []; + } + + $data = get_option(ANSICO_DIAGNOSEKODER_OPTION_DATA, []); + if (!is_array($data) || empty($data)) { + return []; + } + + $limit = $limit > 0 ? min($limit, 100) : 50; + $query_lower = function_exists('mb_strtolower') ? mb_strtolower($query, 'UTF-8') : strtolower($query); + $favorite_lookup = []; + foreach ((array) $favorite_codes as $favorite_code) { + $favorite_lookup[self::normalize_code($favorite_code)] = true; + } + $link_lookup = is_array($links) ? $links : []; + + $results = []; + foreach ($data as $index => $row) { + if (!is_array($row) || empty($row['code']) || empty($row['text'])) { + continue; + } + + $code = (string) $row['code']; + $text = (string) $row['text']; + $normalized_code = self::normalize_code($code); + $haystack_code = function_exists('mb_strtolower') ? mb_strtolower($code, 'UTF-8') : strtolower($code); + $haystack_text = function_exists('mb_strtolower') ? mb_strtolower($text, 'UTF-8') : strtolower($text); + + if (strpos($haystack_code, $query_lower) === false && strpos($haystack_text, $query_lower) === false) { + continue; + } + + $results[] = [ + 'code' => $code, + 'text' => $text, + 'is_favorite' => isset($favorite_lookup[$normalized_code]), + 'linked_url' => isset($link_lookup[$normalized_code]) ? $link_lookup[$normalized_code] : '', + '_original_index' => $index, + ]; + } + + if (empty($results)) { + return []; + } + + usort($results, static function ($a, $b) { + $a_favorite = !empty($a['is_favorite']); + $b_favorite = !empty($b['is_favorite']); + + if ($a_favorite !== $b_favorite) { + return $a_favorite ? -1 : 1; + } + + return ((int) ($a['_original_index'] ?? 0)) <=> ((int) ($b['_original_index'] ?? 0)); + }); + + foreach ($results as &$result) { + unset($result['_original_index']); + } + unset($result); + + return array_slice($results, 0, $limit); + } + + public static function get_rows_by_codes($codes = []) { + $normalized_codes = []; + foreach ((array) $codes as $code) { + $normalized = self::normalize_code($code); + if ($normalized !== '') { + $normalized_codes[] = $normalized; + } + } + $normalized_codes = array_values(array_unique($normalized_codes)); + if (empty($normalized_codes)) { + return []; + } + + $lookup = array_fill_keys($normalized_codes, true); + $data = get_option(ANSICO_DIAGNOSEKODER_OPTION_DATA, []); + if (!is_array($data) || empty($data)) { + return []; + } + + $rows = []; + foreach ($data as $row) { + if (!is_array($row) || empty($row['code']) || empty($row['text'])) { + continue; + } + + $code = (string) $row['code']; + $normalized_code = self::normalize_code($code); + if (!isset($lookup[$normalized_code])) { + continue; + } + + $rows[$normalized_code] = [ + 'code' => $code, + 'text' => (string) $row['text'], + ]; + } + + $ordered = []; + foreach ($normalized_codes as $normalized_code) { + if (isset($rows[$normalized_code])) { + $ordered[] = $rows[$normalized_code]; + } + } + + return $ordered; + } + + protected static function normalize_code($code) { + $code = sanitize_text_field((string) $code); + $code = preg_replace('/\s+/', '', $code); + return strtoupper((string) $code); + } +} diff --git a/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-rest.php b/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-rest.php new file mode 100644 index 0000000..8b2675a --- /dev/null +++ b/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-rest.php @@ -0,0 +1,387 @@ + WP_REST_Server::READABLE, + 'callback' => [__CLASS__, 'search'], + 'permission_callback' => '__return_true', + 'args' => [ + 'q' => [ + 'type' => 'string', + 'required' => false, + 'sanitize_callback' => 'sanitize_text_field', + ], + 'limit' => [ + 'type' => 'integer', + 'required' => false, + 'default' => 50, + 'sanitize_callback' => 'absint', + ], + 'page_id' => [ + 'type' => 'integer', + 'required' => false, + 'default' => 0, + 'sanitize_callback' => 'absint', + ], + ], + ]); + + register_rest_route('ansico-diagnosekoder/v1', '/page-state', [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [__CLASS__, 'page_state'], + 'permission_callback' => '__return_true', + 'args' => [ + 'page_id' => [ + 'type' => 'integer', + 'required' => false, + 'default' => 0, + 'sanitize_callback' => 'absint', + ], + ], + ]); + + register_rest_route('ansico-diagnosekoder/v1', '/content-search', [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [__CLASS__, 'content_search'], + 'permission_callback' => static function () { + return is_user_logged_in(); + }, + 'args' => [ + 'q' => [ + 'type' => 'string', + 'required' => false, + 'sanitize_callback' => 'sanitize_text_field', + ], + 'limit' => [ + 'type' => 'integer', + 'required' => false, + 'default' => 8, + 'sanitize_callback' => 'absint', + ], + ], + ]); + + register_rest_route('ansico-diagnosekoder/v1', '/favorites', [ + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => [__CLASS__, 'toggle_favorite'], + 'permission_callback' => static function () { + return is_user_logged_in(); + }, + 'args' => [ + 'code' => [ + 'type' => 'string', + 'required' => true, + 'sanitize_callback' => 'sanitize_text_field', + ], + 'page_id' => [ + 'type' => 'integer', + 'required' => true, + 'sanitize_callback' => 'absint', + ], + ], + ]); + + register_rest_route('ansico-diagnosekoder/v1', '/links', [ + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => [__CLASS__, 'save_link'], + 'permission_callback' => static function () { + return is_user_logged_in(); + }, + 'args' => [ + 'code' => [ + 'type' => 'string', + 'required' => true, + 'sanitize_callback' => 'sanitize_text_field', + ], + 'url' => [ + 'type' => 'string', + 'required' => true, + ], + 'page_id' => [ + 'type' => 'integer', + 'required' => true, + 'sanitize_callback' => 'absint', + ], + ], + ]); + + register_rest_route('ansico-diagnosekoder/v1', '/links', [ + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => [__CLASS__, 'remove_link'], + 'permission_callback' => static function () { + return is_user_logged_in(); + }, + 'args' => [ + 'code' => [ + 'type' => 'string', + 'required' => true, + 'sanitize_callback' => 'sanitize_text_field', + ], + 'page_id' => [ + 'type' => 'integer', + 'required' => true, + 'sanitize_callback' => 'absint', + ], + ], + ]); + } + + public static function search(WP_REST_Request $request) { + $query = (string) $request->get_param('q'); + $limit = (int) $request->get_param('limit'); + $page_id = absint($request->get_param('page_id')); + if (!$page_id) { + $page_id = self::get_results_page_id(); + } + $limit = $limit > 0 ? min($limit, 100) : 50; + + $favorite_codes = self::get_page_favorites($page_id); + $links = self::get_page_links($page_id); + + $response = rest_ensure_response([ + 'query' => $query, + 'results' => Ansico_Diagnosekoder_Parser::search($query, $limit, $favorite_codes, $links), + 'meta' => get_option(ANSICO_DIAGNOSEKODER_OPTION_META, []), + 'favorites' => $favorite_codes, + 'links' => $links, + 'is_logged_in' => is_user_logged_in(), + 'page_id' => $page_id, + ]); + + $response->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0'); + $response->header('Pragma', 'no-cache'); + return $response; + } + + public static function page_state(WP_REST_Request $request) { + $page_id = absint($request->get_param('page_id')); + if (!$page_id) { + $page_id = self::get_results_page_id(); + } + + $response = rest_ensure_response([ + 'page_id' => $page_id, + 'favorites' => self::get_page_favorites($page_id), + 'links' => self::get_page_links($page_id), + ]); + $response->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0'); + $response->header('Pragma', 'no-cache'); + return $response; + } + + public static function content_search(WP_REST_Request $request) { + $query = trim((string) $request->get_param('q')); + $limit = (int) $request->get_param('limit'); + $limit = $limit > 0 ? min($limit, 20) : 8; + + if ($query === '') { + return rest_ensure_response(['results' => []]); + } + + $all_post_types = get_post_types([], 'objects'); + $excluded_types = ['attachment', 'revision', 'nav_menu_item', 'custom_css', 'customize_changeset', 'oembed_cache', 'user_request', 'wp_block', 'wp_navigation', 'wp_template', 'wp_template_part', 'wp_global_styles', 'wp_font_family', 'wp_font_face']; + + $post_types = []; + foreach ($all_post_types as $post_type => $object) { + if (in_array($post_type, $excluded_types, true)) { + continue; + } + + if (strpos($post_type, 'wp_') === 0) { + continue; + } + + // Include posts, pages, and all custom post types that can reasonably have frontend URLs, + // even when the CPT is excluded from normal WordPress search. + if (!empty($object->public) || !empty($object->publicly_queryable) || !empty($object->show_ui)) { + $post_types[] = $post_type; + } + } + + $post_types = array_values(array_unique($post_types)); + + if (empty($post_types)) { + $post_types = ['page', 'post']; + } + + $posts = get_posts([ + 'post_type' => $post_types, + 'post_status' => 'publish', + 's' => $query, + 'posts_per_page' => $limit, + 'orderby' => 'relevance', + 'order' => 'DESC', + 'suppress_filters' => false, + ]); + + $results = []; + foreach ($posts as $post) { + $results[] = [ + 'id' => (int) $post->ID, + 'title' => get_the_title($post), + 'url' => get_permalink($post), + 'type' => get_post_type($post), + ]; + } + + return rest_ensure_response(['results' => $results]); + } + + public static function toggle_favorite(WP_REST_Request $request) { + $code = self::normalize_code((string) $request->get_param('code')); + $page_id = absint($request->get_param('page_id')); + if (!$page_id) { + $page_id = self::get_results_page_id(); + } + + if ($code === '') { + return new WP_Error('ansico_empty_code', __('Der mangler en kode.', 'ansico-diagnosekoder'), ['status' => 400]); + } + + if (!$page_id || get_post_status($page_id) === false) { + return new WP_Error('ansico_invalid_page', __('Der mangler en gyldig side.', 'ansico-diagnosekoder'), ['status' => 400]); + } + + $favorites = self::get_page_favorites($page_id); + $index = array_search($code, $favorites, true); + $is_favorite = false; + + if ($index === false) { + $favorites[] = $code; + $is_favorite = true; + } else { + unset($favorites[$index]); + $favorites = array_values($favorites); + } + + update_post_meta($page_id, ANSICO_DIAGNOSEKODER_PAGE_FAVORITES_META, $favorites); + + return rest_ensure_response([ + 'code' => $code, + 'is_favorite' => $is_favorite, + 'favorites' => $favorites, + 'page_id' => $page_id, + ]); + } + + public static function get_page_favorites($page_id) { + $page_id = absint($page_id); + if (!$page_id) { + return []; + } + + $favorites = get_post_meta($page_id, ANSICO_DIAGNOSEKODER_PAGE_FAVORITES_META, true); + if (!is_array($favorites)) { + return []; + } + + $favorites = array_map([__CLASS__, 'normalize_code'], $favorites); + return array_values(array_unique(array_filter($favorites))); + } + + public static function save_link(WP_REST_Request $request) { + $code = self::normalize_code((string) $request->get_param('code')); + $url = esc_url_raw((string) $request->get_param('url')); + $page_id = absint($request->get_param('page_id')); + if (!$page_id) { + $page_id = self::get_results_page_id(); + } + + if ($code === '') { + return new WP_Error('ansico_empty_code', __('Der mangler en kode.', 'ansico-diagnosekoder'), ['status' => 400]); + } + + if (!$url || !wp_http_validate_url($url)) { + return new WP_Error('ansico_invalid_url', __('URL-adressen er ikke gyldig.', 'ansico-diagnosekoder'), ['status' => 400]); + } + + if (!$page_id || get_post_status($page_id) === false) { + return new WP_Error('ansico_invalid_page', __('Der mangler en gyldig side.', 'ansico-diagnosekoder'), ['status' => 400]); + } + + $links = self::get_page_links($page_id); + $links[$code] = $url; + update_post_meta($page_id, ANSICO_DIAGNOSEKODER_PAGE_LINKS_META, $links); + + return rest_ensure_response([ + 'code' => $code, + 'url' => $url, + 'links' => $links, + 'page_id' => $page_id, + ]); + } + + public static function remove_link(WP_REST_Request $request) { + $code = self::normalize_code((string) $request->get_param('code')); + $page_id = absint($request->get_param('page_id')); + if (!$page_id) { + $page_id = self::get_results_page_id(); + } + + if (!$page_id || get_post_status($page_id) === false) { + return new WP_Error('ansico_invalid_page', __('Der mangler en gyldig side.', 'ansico-diagnosekoder'), ['status' => 400]); + } + + $links = self::get_page_links($page_id); + if ($code === '') { + return new WP_Error('ansico_empty_code', __('Der mangler en kode.', 'ansico-diagnosekoder'), ['status' => 400]); + } + + unset($links[$code]); + update_post_meta($page_id, ANSICO_DIAGNOSEKODER_PAGE_LINKS_META, $links); + + return rest_ensure_response([ + 'code' => $code, + 'links' => $links, + 'page_id' => $page_id, + ]); + } + + public static function get_page_links($page_id) { + $page_id = absint($page_id); + if (!$page_id) { + return []; + } + + $links = get_post_meta($page_id, ANSICO_DIAGNOSEKODER_PAGE_LINKS_META, true); + if (!is_array($links)) { + return []; + } + + $normalized = []; + foreach ($links as $code => $url) { + $normalized_code = self::normalize_code((string) $code); + $normalized_url = esc_url_raw((string) $url); + if ($normalized_code === '' || $normalized_url === '' || !wp_http_validate_url($normalized_url)) { + continue; + } + $normalized[$normalized_code] = $normalized_url; + } + + return $normalized; + } + + protected static function get_results_page_id() { + $results_url = (string) get_option(ANSICO_DIAGNOSEKODER_OPTION_RESULTS_URL, ''); + if ($results_url === '') { + return 0; + } + + return absint(url_to_postid($results_url)); + } + + protected static function normalize_code($code) { + $code = sanitize_text_field((string) $code); + $code = preg_replace('/\s+/', '', $code); + return strtoupper((string) $code); + } +}