- 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.
576 lines
24 KiB
PHP
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'] : '',
|
|
];
|
|
}
|
|
}
|
|
?>
|