Files
Jacek Pyziak cd264483f8 Add PSR HTTP Message Interfaces and Dependencies
- Implemented StreamInterface, UploadedFileInterface, and UriInterface as per PSR standards.
- Added getallheaders function to retrieve HTTP headers in a compatible manner.
- Included LICENSE files for ralouphie/getallheaders and symfony/deprecation-contracts.
- Introduced function for triggering deprecation notices in Symfony.
2025-12-28 12:44:00 +01:00

576 lines
24 KiB
PHP

<?php
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
if (!class_exists('Atfpp_Glossary')) {
class Atfpp_Glossary {
/**
* Init
* @var object
*/
private static $init;
/**
* Instance
* @return object
*/
public static function instance() {
if (!isset(self::$init)) {
self::$init = new self();
}
return self::$init;
}
/**
* Constructor
*/
public function __construct() {
// Add AJAX hooks here
add_action('wp_ajax_atfpp_import_glossary', array($this, 'atfpp_import_glossary_ajax'));
add_action('wp_ajax_atfpp_update_glossary', array($this, 'atfpp_update_glossary_ajax'));
add_action('wp_ajax_atfpp_delete_glossary', array($this, 'atfpp_delete_glossary_ajax'));
add_action('wp_ajax_atfpp_add_glossary', array($this, 'atfpp_add_glossary_ajax'));
add_action('wp_ajax_atfpp_export_glossary', array($this, 'atfpp_export_glossary_ajax'));
add_action('wp_ajax_atfpp_get_glossary', array($this, 'atfpp_get_glossary_ajax'));
add_action('wp_ajax_nopriv_atfpp_get_glossary', array($this, 'atfpp_get_glossary_ajax'));
}
/**
* Store glossary entry
*/
public static function store_glossary_data($glossary_data = array()) {
$glossary_type = sanitize_text_field($glossary_data['type'] ?? '');
$glossary_term = sanitize_text_field($glossary_data['term'] ?? '');
$glossary_desc = sanitize_textarea_field($glossary_data['description'] ?? '');
$source_language_code = sanitize_text_field($glossary_data['source_lang'] ?? '');
// Support single or multiple translations
$target_langs = (array) ($glossary_data['target_lang'] ?? []);
$translated_terms = (array) ($glossary_data['translated_term'] ?? []);
$all_glossaries = get_option('atfpp_glossary_data', array());
$found = false;
foreach ($all_glossaries as &$entry) {
if (
($entry['original_term'] ?? '') === $glossary_term &&
($entry['original_language_code'] ?? '') === $source_language_code
) {
// Entry exists, add new translations if not duplicates
if (!isset($entry['translations']) || !is_array($entry['translations'])) {
$entry['translations'] = array();
}
$existing_langs = array_column($entry['translations'], 'target_language_code');
foreach ($target_langs as $idx => $lang) {
$lang = sanitize_text_field($lang);
// SKIP if $lang is the same as $source_language_code
if ($lang === $source_language_code) continue;
$term = sanitize_text_field($translated_terms[$idx] ?? '');
// Only add non-empty translations
if (!empty(trim($term)) && !in_array($lang, $existing_langs, true)) {
$entry['translations'][] = array(
'target_language_code' => $lang,
'translated_term' => $term
);
$found = true;
}
}
// If no new translation was added, return false
if (!$found) {
return false;
}
break;
}
}
unset($entry);
if (!$found) {
// New entry
$translations = [];
foreach ($target_langs as $idx => $lang) {
$lang = sanitize_text_field($lang);
if ($lang === $source_language_code) continue;
$translated_term = sanitize_text_field($translated_terms[$idx] ?? '');
// Only save non-empty translations
if (!empty(trim($translated_term))) {
$translations[] = array(
'target_language_code' => $lang,
'translated_term' => $translated_term
);
}
}
$all_glossaries[] = array(
'description' => $glossary_desc,
'kind' => sanitize_text_field($glossary_data['type'] ?? 'general'),
'original_language_code' => $source_language_code,
'original_term' => $glossary_term,
'translations' => $translations,
);
}
update_option('atfpp_glossary_data', $all_glossaries);
return true;
}
/**
* Optional: Get all glossary entries (for debugging or display)
*/
public static function get_all_glossaries() {
return get_option('atfpp_glossary_data', array());
}
// Add this function to handle CSV import
public static function import_glossary_csv($csv_path) {
if (!file_exists($csv_path) || !is_readable($csv_path)) {
return false;
}
$header = null;
if (($handle = fopen($csv_path, 'r')) !== false) {
while (($row = fgetcsv($handle, 1000, ',')) !== false) {
if (!$header) {
$header = $row;
} else {
$row_data = array_combine($header, $row);
// Create glossary entry with proper mapping
$glossary_entry = array(
'type' => $row_data['type'] ?? 'general', // Get type from CSV
'term' => $row_data['original_term'] ?? '',
'description' => $row_data['description'] ?? '',
'source_lang' => $row_data['original_language_code'] ?? '',
'target_lang' => array($row_data['target_language_code'] ?? ''),
'translated_term' => array($row_data['translated_term'] ?? '')
);
// Map 'type' to 'kind' when storing
$glossary_entry['kind'] = $glossary_entry['type'];
if ($row_data['target_language_code'] !== $row_data['original_language_code']) {
self::store_glossary_data($glossary_entry);
}
}
}
fclose($handle);
}
return true;
}
public function atfpp_import_glossary_ajax() {
check_ajax_referer('atfpp_glossary_nonce', '_wpnonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Permission denied');
}
if (empty($_FILES['csv_file']['tmp_name'])) {
wp_send_json_error('No file uploaded');
}
if ($_FILES['csv_file']['type'] !== 'text/csv' && pathinfo($_FILES['csv_file']['name'], PATHINFO_EXTENSION) !== 'csv') {
wp_send_json_error('Invalid file type');
}
if ($_FILES['csv_file']['size'] > 2 * 1024 * 1024) { // 2MB limit
wp_send_json_error('File too large');
}
$csv_path = $_FILES['csv_file']['tmp_name'];
$result = self::import_glossary_csv($csv_path);
if ($result) {
wp_send_json_success('Glossary imported');
} else {
wp_send_json_error('Import failed');
}
}
/**
* Update glossary entry
*/
public static function update_glossary_data($glossary_data = array()) {
$glossary_type = sanitize_text_field($glossary_data['type'] ?? '');
$glossary_term = sanitize_text_field($glossary_data['term'] ?? '');
$glossary_desc = sanitize_textarea_field($glossary_data['description'] ?? '');
$source_language_code = sanitize_text_field($glossary_data['source_lang'] ?? '');
$translations = $glossary_data['translations'] ?? [];
$all_glossaries = get_option('atfpp_glossary_data', array());
$updated = false;
foreach ($all_glossaries as $i => &$entry) {
$existing_name = sanitize_text_field($entry['original_term'] ?? '');
$existing_source = sanitize_text_field($entry['original_language_code'] ?? '');
if (
$existing_name === $glossary_data['original_term'] &&
$existing_source === $glossary_data['original_source_lang']
) {
// Check if term or language changed
if (
$glossary_term !== $existing_name ||
$source_language_code !== $existing_source
) {
// Remove just this entry
unset($all_glossaries[$i]);
// ✅ Add the updated entry now
$translations_arr = [];
foreach ($translations as $lang => $translated_term) {
if ($lang === $source_language_code) continue;
$sanitized_term = sanitize_text_field($translated_term);
// Only save non-empty translations
if (!empty(trim($sanitized_term))) {
$translations_arr[] = array(
'target_language_code' => $lang,
'translated_term' => $sanitized_term
);
}
}
$all_glossaries[] = array(
'description' => $glossary_desc,
'kind' => $glossary_type,
'original_language_code' => $source_language_code,
'original_term' => $glossary_term,
'translations' => $translations_arr,
);
$updated = true;
break;
} else {
// Only description or translations changed — update in place
$entry['description'] = $glossary_desc;
$entry['kind'] = $glossary_type;
$entry['original_term'] = $glossary_term;
$entry['original_language_code'] = $source_language_code;
$entry['translations'] = [];
foreach ($translations as $lang => $translated_term) {
if ($lang === $source_language_code) continue;
$sanitized_term = sanitize_text_field($translated_term);
// Only save non-empty translations
if (!empty(trim($sanitized_term))) {
$entry['translations'][] = array(
'target_language_code' => $lang,
'translated_term' => $sanitized_term
);
}
}
$updated = true;
break;
}
}
}
unset($entry);
if ($updated) {
// Reindex array to avoid gaps
$all_glossaries = array_values($all_glossaries);
update_option('atfpp_glossary_data', $all_glossaries);
return true;
}
return false;
}
public function atfpp_update_glossary_ajax() {
check_ajax_referer('atfpp_glossary_nonce', '_wpnonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Permission denied');
}
$data = $_POST['data'] ?? [];
$source_lang = sanitize_text_field($data['source_lang'] ?? '');
$translations = isset($data['translations']) && is_array($data['translations']) ? $data['translations'] : [];
// Remove translation for original language if present
if (isset($translations[$source_lang])) {
unset($translations[$source_lang]);
}
$result = self::update_glossary_data($data);
if ($result) {
// Get the updated entry from database
$updated_entry = self::get_updated_glossary_entry(
sanitize_text_field($data['term'] ?? ''),
$source_lang
);
wp_send_json_success([
'message' => 'Glossary updated',
'updated_entry' => $updated_entry
]);
} else {
wp_send_json_error('Update failed');
}
}
/**
* Get updated glossary entry after update
*/
private static function get_updated_glossary_entry($term, $source_lang) {
$all_glossaries = get_option('atfpp_glossary_data', array());
foreach ($all_glossaries as $entry) {
if (
($entry['original_term'] ?? '') === $term &&
($entry['original_language_code'] ?? '') === $source_lang
) {
return $entry;
}
}
return null;
}
/**
* Delete glossary entry
*/
public static function delete_glossary_data($term, $source_lang) {
$all_glossaries = get_option('atfpp_glossary_data', array());
$updated = false;
foreach ($all_glossaries as $i => $entry) {
if (
strtolower(trim($entry['original_term'])) === strtolower(trim($term)) &&
strtolower(trim($entry['original_language_code'])) === strtolower(trim($source_lang))
) {
unset($all_glossaries[$i]);
$updated = true;
break;
}
}
if ($updated) {
$all_glossaries = array_values($all_glossaries);
update_option('atfpp_glossary_data', $all_glossaries);
return true;
}
return false;
}
public function atfpp_delete_glossary_ajax() {
check_ajax_referer('atfpp_glossary_nonce', '_wpnonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Permission denied');
}
$term = sanitize_text_field($_POST['term'] ?? '');
$source_lang = sanitize_text_field($_POST['source_lang'] ?? '');
if (empty($term) || empty($source_lang)) {
wp_send_json_error('Missing data');
}
$result = self::delete_glossary_data($term, $source_lang);
if ($result) {
wp_send_json_success('Glossary entry deleted');
} else {
wp_send_json_error('Delete failed');
}
}
public function atfpp_add_glossary_ajax() {
check_ajax_referer('atfpp_glossary_nonce', '_wpnonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Permission denied');
}
$type = sanitize_text_field($_POST['type'] ?? '');
$term = sanitize_text_field($_POST['term'] ?? '');
$description = sanitize_textarea_field($_POST['description'] ?? '');
$source_lang = sanitize_text_field($_POST['source_lang'] ?? '');
$translations = isset($_POST['translations']) && is_array($_POST['translations']) ? array_map('sanitize_text_field', $_POST['translations']) : [];
// Remove translation for original language if present
if (isset($translations[$source_lang])) {
unset($translations[$source_lang]);
}
// Only require type, term, and source_lang
if (empty($type) || empty($term) || empty($source_lang)) {
wp_send_json_error('Type, Term, and Original Language are required.');
}
// Save the main term
$main_entry = [
'type' => $type,
'term' => $term,
'description' => $description,
'source_lang' => $source_lang,
'target_lang' => [],
'translated_term' => []
];
foreach ($translations as $code => $translated) {
// Strict empty check - must have non-whitespace content
if ($translated !== '' && trim($translated) !== '') {
$main_entry['target_lang'][] = $code;
$main_entry['translated_term'][] = trim($translated);
}
}
$glossary_data = get_option('atfpp_glossary_data', []);
$duplicate = false;
foreach ($glossary_data as $entry) {
if (
isset($entry['original_term'], $entry['original_language_code']) &&
$entry['original_term'] === $term &&
$entry['original_language_code'] === $source_lang
) {
$duplicate = true;
break;
}
}
if ($duplicate) {
wp_send_json_error('This term already exists in this language.');
}
$result = self::store_glossary_data($main_entry);
if ($result) {
// Get the newly added entry from database
$added_entry = self::get_updated_glossary_entry($term, $source_lang);
wp_send_json_success([
'message' => 'Glossary term added successfully',
'added_entry' => $added_entry
]);
} else {
wp_send_json_error('Could not add glossary term (maybe duplicate?)');
}
}
public function atfpp_export_glossary_ajax() {
if (!current_user_can('manage_options')) {
wp_die('Permission denied');
}
$glossary_data = get_option('atfpp_glossary_data', []);
if (!is_array($glossary_data)) {
$glossary_data = [];
}
// Set headers for CSV download
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=glossary-export-' . date('Y-m-d') . '.csv');
$output = fopen('php://output', 'w');
// CSV header
fputcsv($output, [
'type',
'original_term',
'description',
'original_language_code',
'target_language_code',
'translated_term'
]);
foreach ($glossary_data as $entry) {
$type = $entry['kind'] ?? '';
$term = $entry['original_term'] ?? '';
$desc = $entry['description'] ?? '';
$orig_lang = $entry['original_language_code'] ?? '';
if (!empty($entry['translations']) && is_array($entry['translations'])) {
foreach ($entry['translations'] as $trans) {
$target_lang = $trans['target_language_code'] ?? '';
$translated = $trans['translated_term'] ?? '';
fputcsv($output, [
$type,
$term,
$desc,
$orig_lang,
$target_lang,
$translated
]);
}
} else {
// No translations, just output the main term
fputcsv($output, [
$type,
$term,
$desc,
$orig_lang,
'',
''
]);
}
}
fclose($output);
exit;
}
public function atfpp_get_glossary_ajax() {
if (!current_user_can('read')) {
wp_send_json_error('Permission denied');
}
$source_lang = isset($_GET['sourceLang']) ? sanitize_text_field($_GET['sourceLang']) : '';
$target_lang = isset($_GET['targetLang']) ? sanitize_text_field($_GET['targetLang']) : '';
$glossary_data = self::get_all_glossaries();
$filtered = [];
foreach ($glossary_data as $entry) {
// Filter by source_lang
if (
$source_lang &&
(!isset($entry['original_language_code']) || $entry['original_language_code'] !== $source_lang)
) {
continue; // Skip if source_lang is provided and doesn't match
}
// Ensure the entry has a translations array and it's not empty
if (!isset($entry['translations']) || !is_array($entry['translations']) || empty($entry['translations'])) {
continue; // Skip if no translations array or it's empty
}
// Filter for valid translations (must have target_language_code and non-empty translated_term)
$valid_translations = array_filter($entry['translations'], function($trans) {
return isset($trans['target_language_code']) && !empty($trans['target_language_code']) &&
isset($trans['translated_term']) && $trans['translated_term'] !== '';
});
if (empty($valid_translations)) {
continue; // Skip if no valid (non-empty) translations exist for this entry
}
if ($target_lang) {
// If target_lang is provided, filter valid_translations by this target_lang
$matching_target_translations = array_filter($valid_translations, function($trans) use ($target_lang) {
return $trans['target_language_code'] === $target_lang;
});
if (!empty($matching_target_translations)) {
// If matching translations exist, replace the entry's translations with only these.
$entry['translations'] = array_values($matching_target_translations);
$filtered[] = $entry;
}
// If no valid translations match the target_lang, this entry is skipped.
} else {
// If target_lang is not provided, include the entry with all its valid_translations.
$entry['translations'] = array_values($valid_translations);
$filtered[] = $entry;
}
}
wp_send_json_success(['terms' => $filtered]);
}
}
}
// --- ADD THIS BLOCK: Fetch Polylang languages for use everywhere ---
$pll_languages_serialized = get_option('_transient_pll_languages_list');
$pll_languages = [];
if ($pll_languages_serialized) {
$pll_languages = maybe_unserialize($pll_languages_serialized);
}
// Build $languages array for use in modal and table
$languages = [];
if (is_array($pll_languages)) {
foreach ($pll_languages as $pll_lang) {
$languages[] = [
'code' => $pll_lang['slug'],
'img' => $pll_lang['flag_url'],
'alt' => $pll_lang['name'],
'flag' => isset($pll_lang['flag']) ? $pll_lang['flag'] : '',
];
}
}
?>