'', 'upload' => '', 'eye' => '', 'check' => '', 'help' => '', ]; if (!isset($paths[$key])) return ''; return ''; } function mkb_glossar_csv_fields() { return [ 'title' => __('Term title', 'mkb-glossar'), 'slug' => __('URL slug', 'mkb-glossar'), 'summary' => __('Short definition', 'mkb-glossar'), 'definition' => __('Full definition', 'mkb-glossar'), 'synonyms' => __('Synonyms', 'mkb-glossar'), 'topics' => __('Topics (slug::name | …)', 'mkb-glossar'), 'language' => __('Language code', 'mkb-glossar'), 'status' => __('Publication status', 'mkb-glossar'), 'related_terms' => __('Related terms (slugs)', 'mkb-glossar'), 'references' => __('Sources and references', 'mkb-glossar'), 'external_url' => __('External link destination', 'mkb-glossar'), 'use_external_target' => __('Use external link destination', 'mkb-glossar'), 'external_new_tab' => __('Open external destination in new tab', 'mkb-glossar'), 'external_nofollow' => __('External destination: nofollow', 'mkb-glossar'), 'external_sponsored' => __('External destination: sponsored', 'mkb-glossar'), 'external_ugc' => __('External destination: ugc', 'mkb-glossar'), 'case_sensitive' => __('Exact capitalization', 'mkb-glossar'), 'translation_group' => __('Translation group', 'mkb-glossar'), ]; } function mkb_glossar_csv_aliases() { return [ 'title'=>['title','term','begriff','name','begriffstitel'], 'slug'=>['slug','url_slug','permalink'], 'summary'=>['summary','short_definition','kurzdefinition','excerpt'], 'definition'=>['definition','content','beschreibung','description','post_content'], 'synonyms'=>['synonyms','synonyme','alternative_names'], 'topics'=>['topics','themen','categories','kategorien'], 'language'=>['language','sprache','lang','locale'], 'status'=>['status','post_status','veroeffentlichung'], 'related_terms'=>['related_terms','related','verwandte_begriffe'], 'references'=>['references','sources','quellen','referenzen'], 'external_url'=>['external_url','external_link','externer_link'], 'use_external_target'=>['use_external_target','external_target','external_enabled'], 'external_new_tab'=>['external_new_tab','external_newtab','neuer_tab'], 'external_nofollow'=>['external_nofollow'], 'external_sponsored'=>['external_sponsored'], 'external_ugc'=>['external_ugc'], 'case_sensitive'=>['case_sensitive','match_case','gross_kleinschreibung'], 'translation_group'=>['translation_group','translation_key','uebersetzungsgruppe'], ]; } function mkb_glossar_csv_default_map($headers) { $result=[]; foreach (mkb_glossar_csv_aliases() as $field => $aliases) { $result[$field]=''; foreach ($headers as $i=>$name) { $value=strtolower(trim((string)$name)); $value=preg_replace('/[\s\-]+/u','_', $value); if (in_array($value,$aliases,true)) { $result[$field]=(string)$i; break; } } } return $result; } function mkb_glossar_csv_normalize_map($raw, $headers) { $defaults=mkb_glossar_csv_default_map($headers); if ($raw===null) return $defaults; if (!is_array($raw)) return new WP_Error('mkb_csv_mapping', __('Invalid CSV field mapping.', 'mkb-glossar')); $out=[]; $used=[]; foreach ($defaults as $field=>$default) { $n=$raw[$field]??''; if ($n==='') { $out[$field]=''; continue; } if (!is_scalar($n) || !ctype_digit((string)$n) || (int)$n>=count($headers)) return new WP_Error('mkb_csv_mapping', __('Invalid CSV field mapping.', 'mkb-glossar')); $n=(string)(int)$n; if (in_array($n,$used,true)) return new WP_Error('mkb_csv_mapping_duplicate', __('A CSV column can only be assigned once.', 'mkb-glossar')); $out[$field]=$n; $used[]=$n; } return $out; } function mkb_glossar_csv_cell($row, $mapping, $key) { $index = $mapping[$key]??''; return $index==='' ? '' : (string)($row[(int)$index]??''); } function mkb_glossar_csv_upload($file) { if (!is_array($file) || !isset($file['error']) || $file['error']!==UPLOAD_ERR_OK || !isset($file['tmp_name'],$file['name'],$file['size'])) { return new WP_Error('mkb_csv_upload', __('Please select a valid CSV file.', 'mkb-glossar')); } if (!is_scalar($file['name']) || strtolower(pathinfo((string)$file['name'],PATHINFO_EXTENSION))!=='csv') { return new WP_Error('mkb_csv_extension', __('Only .csv files are accepted.', 'mkb-glossar')); } if (!is_int($file['size']) && !ctype_digit((string)$file['size'])) return new WP_Error('mkb_csv_size', __('Invalid file size.', 'mkb-glossar')); if ((int)$file['size']<2 || (int)$file['size']>8*1024*1024) return new WP_Error('mkb_csv_size', __('The CSV must be smaller than 8 MB.', 'mkb-glossar')); if (!is_string($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) return new WP_Error('mkb_csv_upload', __('The CSV upload could not be verified.', 'mkb-glossar')); return $file['tmp_name']; } /** Streaming CSV requires native PHP fgetcsv/fputcsv. These handles exclusively * read PHP-verified uploaded temp files or write php://output; no permanent * WordPress filesystem files are modified. WP_Filesystem cannot express a * bounded, quoted-multiline RFC4180 CSV stream. */ function mkb_glossar_csv_open_stream($path, $mode) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Verified temporary CSV upload or php://output streaming response. return @fopen($path, $mode); } function mkb_glossar_csv_close_stream($handle) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Close the temporary stream opened exclusively for CSV processing. return fclose($handle); } function mkb_glossar_csv_write_bom($stream) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- UTF-8 BOM is written to php://output, not the persistent filesystem. return fwrite($stream, "\xEF\xBB\xBF"); } /** Parser is separated from upload validation so isolated tests can exercise it. */ function mkb_glossar_csv_parse($path) { $fh=mkb_glossar_csv_open_stream($path,'rb'); if (!$fh) return new WP_Error('mkb_csv_read', __('The CSV could not be read.', 'mkb-glossar')); $first=fgets($fh,65536); if (!is_string($first)) {mkb_glossar_csv_close_stream($fh);return new WP_Error('mkb_csv_header', __('The CSV needs a header row.', 'mkb-glossar'));} $first=preg_replace('/^\xEF\xBB\xBF/','',$first); $counts=[]; foreach ([',',';',"\t"] as $delimiter) $counts[$delimiter]=count(str_getcsv($first,$delimiter,'"','')); arsort($counts); $delimiter=(string)array_key_first($counts); rewind($fh); $headers=fgetcsv($fh,65536,$delimiter,'"',''); if (!is_array($headers) || count($headers)<1 || count($headers)>50) {mkb_glossar_csv_close_stream($fh);return new WP_Error('mkb_csv_header', __('The CSV header is invalid.', 'mkb-glossar'));} $headers[0]=preg_replace('/^\xEF\xBB\xBF/','',(string)$headers[0]); $headers=array_map('trim',$headers); if (count(array_filter($headers,static fn($x)=>$x!==''))!==count($headers) || count(array_unique($headers))!==count($headers)) { mkb_glossar_csv_close_stream($fh);return new WP_Error('mkb_csv_header', __('CSV column names must be unique and nonempty.', 'mkb-glossar')); } $rows=[]; $line=1; while (($row=fgetcsv($fh,65536,$delimiter,'"',''))!==false) { $line++; if ($row===[null]) continue; if (count($rows)>=2000) {mkb_glossar_csv_close_stream($fh);return new WP_Error('mkb_csv_rows', __('Use no more than 2,000 rows per import.', 'mkb-glossar'));} /* translators: %d is the one-based CSV row number. */ if (count($row)!==count($headers)) {mkb_glossar_csv_close_stream($fh);return new WP_Error('mkb_csv_columns', sprintf(__('Row %d has a different number of columns.', 'mkb-glossar'),$line));} foreach ($row as $value) { if (!is_string($value) || strlen($value)>50000 || !preg_match('//u',$value)) { /* translators: %d is the one-based CSV row number. */ mkb_glossar_csv_close_stream($fh);return new WP_Error('mkb_csv_encoding', sprintf(__('Row %d contains invalid or oversized text. Use UTF-8.', 'mkb-glossar'),$line)); } } $rows[]=['line'=>$line,'cells'=>$row]; } mkb_glossar_csv_close_stream($fh); if (!$rows) return new WP_Error('mkb_csv_empty', __('No glossary rows were found.', 'mkb-glossar')); return ['headers'=>$headers,'rows'=>$rows,'delimiter'=>$delimiter]; } /** CSV formula neutralization for spreadsheet viewers, round-tripped on import. */ function mkb_glossar_csv_escape_cell($value) { $value=(string)$value; return preg_match('/^[\s]*[=+@\-]/u',$value) ? "'".$value : $value; } function mkb_glossar_csv_unescape_cell($value) { $value=(string)$value; return preg_match("/^'[\\s]*[=+@\\-]/u",$value) ? substr($value,1) : $value; } function mkb_glossar_csv_language($raw) { $raw=trim((string)$raw); $active=mkb_glossar_wpml_enabled() || mkb_glossar_polylang_enabled(); if (!$active) return $raw==='' || $raw==='site-default' ? 'site-default' : null; $code=($raw!=='' && $raw!=='site-default')?$raw:mkb_glossar_default_language(); return in_array($code,mkb_glossar_url_languages(),true) ? $code : null; } function mkb_glossar_csv_split($value) { return array_values(array_unique(array_filter(array_map('trim', explode('|',(string)$value)),static fn($v)=>$v!==''))); } function mkb_glossar_csv_existing_index() { $posts=get_posts(['post_type'=>'mkb_glossar','post_status'=>['publish','draft','pending','private','future'], // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- Gather entries from every language for CSV identity reconciliation. 'posts_per_page'=>-1,'orderby'=>'ID','order'=>'ASC','suppress_filters'=>true]); $result=['names'=>[],'slugs'=>[]]; foreach ($posts as $post) { $lang=mkb_glossar_post_language($post->ID); if ($lang===null) continue; $slug=(string)$post->post_name; if ($slug!=='') $result['slugs'][$lang][$slug]=(int)$post->ID; $names=array_merge([get_the_title($post->ID)],preg_split('/\r\n|\r|\n/',(string)get_post_meta($post->ID,'mkb_glossar_synonyms',true)) ?: []); foreach ($names as $name) { $key=mkb_glossar_identity_key($name); if ($key!=='') $result['names'][$lang][$key]=(int)$post->ID; } } return $result; } function mkb_glossar_csv_normalize_row($cells, $map, $line) { $read=static fn($key) => mkb_glossar_csv_unescape_cell(mkb_glossar_csv_cell($cells,$map,$key)); $title=sanitize_text_field($read('title')); /* translators: %d is the one-based CSV row number. */ if ($title==='' || (function_exists('mb_strlen')?mb_strlen($title,'UTF-8'):strlen($title))>250) return new WP_Error('mkb_csv_title',sprintf(__('Row %d needs a term title (up to 250 characters).','mkb-glossar'),$line)); $slug=$read('slug'); $slug=$slug!==''?sanitize_title($slug):sanitize_title($title); /* translators: %d is the one-based CSV row number. */ if ($slug==='') return new WP_Error('mkb_csv_slug',sprintf(__('Row %d has an invalid URL slug.','mkb-glossar'),$line)); $language=mkb_glossar_csv_language($read('language')); /* translators: %d is the one-based CSV row number. */ if ($language===null) return new WP_Error('mkb_csv_language',sprintf(__('Row %d has an unsupported language code.','mkb-glossar'),$line)); $status=sanitize_key($read('status')); if ($status==='' || !in_array($status,['publish','draft','pending','private'],true)) $status='draft'; $summary=sanitize_textarea_field($read('summary')); $synonyms=sanitize_textarea_field($read('synonyms')); $raw_definition=$read('definition'); $definition=current_user_can('unfiltered_html')?$raw_definition:wp_kses_post($raw_definition); $editorial=mkb_glossar_editorial_prepare([ 'term_references'=>$read('references'), 'term_external_url'=>$read('external_url'), 'term_use_external'=>in_array(strtolower($read('use_external_target')),['1','true','yes','ja'],true)?'1':'0', 'term_external_newtab'=>($map['external_new_tab']??'')==='' ? '1' : (in_array(strtolower($read('external_new_tab')),['1','true','yes','ja'],true)?'1':'0'), 'term_external_nofollow'=>in_array(strtolower($read('external_nofollow')),['1','true','yes','ja'],true)?'1':'0', 'term_external_sponsored'=>in_array(strtolower($read('external_sponsored')),['1','true','yes','ja'],true)?'1':'0', 'term_external_ugc'=>in_array(strtolower($read('external_ugc')),['1','true','yes','ja'],true)?'1':'0', 'term_case_sensitive'=>in_array(strtolower($read('case_sensitive')),['1','true','yes','ja'],true)?'1':'0', ],0,$language,false); /* translators: %1$d is the CSV row number, %2$s is the validation error message. */ if (is_wp_error($editorial)) return new WP_Error('mkb_csv_editorial',sprintf(__('Row %1$d: %2$s','mkb-glossar'),$line,$editorial->get_error_message())); $topics=[]; foreach (mkb_glossar_csv_split($read('topics')) as $item) { $pair=explode('::',$item,4); $slug_part=sanitize_title($pair[0]); if ($slug_part==='') continue; $topics[$slug_part]=[ 'name'=>isset($pair[1])?sanitize_text_field($pair[1]):ucwords(str_replace('-',' ',$slug_part)), 'parent_slug'=>isset($pair[2])?sanitize_title($pair[2]):'', 'parent_name'=>isset($pair[3])?sanitize_text_field($pair[3]):'', ]; } $related=[]; foreach (mkb_glossar_csv_split($read('related_terms')) as $part) { $related_slug=sanitize_title($part); if ($related_slug!=='') $related[]=$related_slug; } /* translators: %d is the one-based CSV row number. */ if (count($related)>12) return new WP_Error('mkb_csv_related',sprintf(__('Row %d has too many related terms.','mkb-glossar'),$line)); $translation_group=trim($read('translation_group')); /* translators: %d is the one-based CSV row number. */ if ($translation_group!=='' && !preg_match('/^[A-Za-z0-9_.:-]{1,100}$/D',$translation_group)) return new WP_Error('mkb_csv_group',sprintf(__('Row %d has an invalid translation group.','mkb-glossar'),$line)); return compact('title','slug','language','status','summary','synonyms','definition','editorial','topics','related','translation_group') + ['line'=>$line]; } /** One shared preflight for preview and final import; final import must revalidate. */ function mkb_glossar_csv_review($parsed, $mapping, $mode='skip') { if (($mapping['title']??'')==='') return new WP_Error('mkb_csv_title_mapping', __('Assign a CSV column to the term title.', 'mkb-glossar')); $index=mkb_glossar_csv_existing_index(); $seen=[]; $groups=[]; $entries=[]; $counts=['create'=>0,'update'=>0,'skip'=>0,'error'=>0]; foreach ($parsed['rows'] as $item) { $record=mkb_glossar_csv_normalize_row($item['cells'],$mapping,$item['line']); if (is_wp_error($record)) { $entries[]=['line'=>$item['line'],'title'=>'','action'=>'error','message'=>$record->get_error_message()];$counts['error']++;continue; } $lang=$record['language'];$name=mkb_glossar_identity_key($record['title']); $existing_title_id=$index['names'][$lang][$name]??0; $slug_id=$index['slugs'][$lang][$record['slug']]??0; $collision=0; if ($existing_title_id) { $actual=mkb_glossar_identity_key(get_the_title($existing_title_id)); if ($actual!==$name) $collision=$existing_title_id; } if ($slug_id && $existing_title_id && $slug_id!==$existing_title_id) $collision=$slug_id; if ($slug_id && !$existing_title_id) $collision=$slug_id; foreach (array_merge([$record['title']],preg_split('/\r\n|\r|\n/',$record['synonyms'])?:[]) as $alias) { $key=mkb_glossar_identity_key($alias); if ($key==='') continue; $other=$index['names'][$lang][$key]??0; if ($other && $other!==$existing_title_id) $collision=$other; if (isset($seen[$lang][$key])) $collision=-1; $seen[$lang][$key]=true; } $group=$record['translation_group']; if ($group!=='' && isset($groups[$group][$lang])) $collision=-2; if ($group!=='') $groups[$group][$lang]=true; $action=$collision?'error':($existing_title_id?($mode==='update'?'update':'skip'):'create'); if ($action==='create') { // Reserve future title, synonyms and slug to detect subsequent rows. $index['slugs'][$lang][$record['slug']]=-1; } $entries[]=['line'=>$item['line'],'title'=>$record['title'],'action'=>$action, 'message'=>$collision ? __('Conflicting term, synonym or URL slug. No changes will be made to this row.', 'mkb-glossar') : ($action==='skip'?__('Already exists; will be skipped.', 'mkb-glossar') : ''), 'existing_id'=>$existing_title_id,'record'=>$record]; $counts[$action]++; } return ['entries'=>$entries,'counts'=>$counts,'mapping'=>$mapping]; } function mkb_glossar_csv_topic_id($slug,$name,$language,$allow_create,$parent_slug='',$parent_name='') { $terms=get_terms(['taxonomy'=>'mkb_glossar_topic','hide_empty'=>false,'slug'=>$slug,'suppress_filter'=>true]); if (is_wp_error($terms)) return 0; foreach ($terms as $term) { if (!(mkb_glossar_wpml_enabled() || mkb_glossar_polylang_enabled()) || mkb_glossar_topic_language($term->term_id)===$language) return (int)$term->term_id; } if (!$allow_create) return 0; $parent_id=0; if ($parent_slug!=='' && $parent_slug!==$slug) { $parent_id=mkb_glossar_csv_topic_id($parent_slug,$parent_name!==''?$parent_name:ucwords(str_replace('-',' ',$parent_slug)),$language,$allow_create); } $created=wp_insert_term($name,'mkb_glossar_topic',['slug'=>$slug,'parent'=>$parent_id]); if (is_wp_error($created)) return 0; $id=(int)$created['term_id']; if (mkb_glossar_polylang_enabled() && function_exists('pll_set_term_language')) pll_set_term_language($id,$language); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Required third-party WPML/Elementor API hook name; changing it would break integration. elseif (mkb_glossar_wpml_enabled()) do_action('wpml_set_element_language_details',['element_id'=>$id,'element_type'=>'tax_mkb_glossar_topic','trid'=>false,'language_code'=>$language]); return $id; } function mkb_glossar_csv_assign_language($id,$language) { if ($language==='site-default') return; if (mkb_glossar_polylang_enabled() && function_exists('pll_set_post_language')) pll_set_post_language($id,$language); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Required third-party WPML/Elementor API hook name; changing it would break integration. elseif (mkb_glossar_wpml_enabled()) do_action('wpml_set_element_language_details',['element_id'=>$id,'element_type'=>'post_mkb_glossar','trid'=>false,'language_code'=>$language]); } function mkb_glossar_csv_apply($review,$mapping,$options) { $report=['created'=>0,'updated'=>0,'skipped'=>0,'errors'=>0,'warnings'=>[],'saved'=>[]]; $related_pending=[]; $topic_cache=[]; $translation_pending=[]; foreach ($review['entries'] as $entry) { $action=$entry['action']; if ($action==='skip') { $report['skipped']++;continue; } if ($action==='error') { $report['errors']++;continue; } $r=$entry['record']; $status=($options['drafts'] && $action==='create')?'draft':$r['status']; if (!current_user_can('publish_posts') && $status==='publish') $status='pending'; $fields=['post_type'=>'mkb_glossar','post_status'=>$status,'post_title'=>$r['title']]; if ($action==='create') $fields['post_name']=$r['slug']; else $fields['ID']=(int)$entry['existing_id']; if (($mapping['definition']??'')!=='') $fields['post_content']=$r['definition']; if ($action==='update' && ($options['drafts'] || ($mapping['status']??'')==='')) unset($fields['post_status']); $id=wp_insert_post(wp_slash($fields),true); /* translators: %d is the one-based CSV row number. */ if (is_wp_error($id) || !$id) { $report['errors']++;$report['warnings'][]=sprintf(__('Row %d could not be saved.','mkb-glossar'),$entry['line']);continue; } if ($action==='create') mkb_glossar_csv_assign_language($id,$r['language']); if (($mapping['summary']??'')!=='' || $action==='create') update_post_meta($id,'mkb_glossar_summary',$r['summary']); if (($mapping['synonyms']??'')!=='' || $action==='create') update_post_meta($id,'mkb_glossar_synonyms',$r['synonyms']); $meta_mapping=['references'=>'mkb_glossar_references','external_url'=>'mkb_glossar_external_url', 'use_external_target'=>'mkb_glossar_use_external','external_new_tab'=>'mkb_glossar_external_newtab','external_nofollow'=>'mkb_glossar_external_nofollow','external_sponsored'=>'mkb_glossar_external_sponsored','external_ugc'=>'mkb_glossar_external_ugc','case_sensitive'=>'mkb_glossar_case_sensitive']; foreach ($meta_mapping as $field=>$meta) { if (($mapping[$field]??'')!=='' || $action==='create') update_post_meta($id,$meta,$r['editorial'][$meta]); } if (($mapping['topics']??'')!=='') { $term_ids=[]; $missing_topic=false; foreach ($r['topics'] as $slug=>$topic) { $key=$r['language'].'/'.$slug; if (!array_key_exists($key,$topic_cache)) $topic_cache[$key]=mkb_glossar_csv_topic_id($slug,$topic['name'],$r['language'],$options['create_topics'],$topic['parent_slug'],$topic['parent_name']); if ($topic_cache[$key]) $term_ids[]=$topic_cache[$key]; else { $missing_topic=true; /* translators: %1$d is the CSV row number, %2$s is the topic name. */ $report['warnings'][]=sprintf(__('Row %1$d: topic “%2$s” was not assigned. Existing topic assignments were preserved.','mkb-glossar'),$entry['line'],$topic['name']); } } // Never replace existing assignments with a partial/incomplete import. if (!$missing_topic) { $assigned=wp_set_object_terms($id,$term_ids,'mkb_glossar_topic'); /* translators: %d is the one-based CSV row number. */ if (is_wp_error($assigned)) $report['warnings'][]=sprintf(__('Row %d: topic assignments could not be saved.','mkb-glossar'),$entry['line']); } } if (($mapping['related_terms']??'')!=='') $related_pending[]=['id'=>$id,'record'=>$r]; if (($mapping['translation_group']??'')!=='' && $r['translation_group']!=='') $translation_pending[$r['translation_group']][$r['language']]=$id; $report[$action==='create'?'created':'updated']++; $report['saved'][]=$id; } // Relations are resolved after all terms were created, regardless of CSV order. if ($related_pending) { $all=mkb_glossar_csv_existing_index(); foreach ($related_pending as $pending) { $ids=[]; $r=$pending['record']; foreach ($r['related'] as $slug) { $id=$all['slugs'][$r['language']][$slug]??0; /* translators: %1$d is the CSV row number, %2$s is the related term slug. */ if (!$id || $id===$pending['id']) $report['warnings'][]=sprintf(__('Row %1$d: related term “%2$s” was not found.','mkb-glossar'),$r['line'],$slug); else $ids[]=$id; } if (count($ids)===count($r['related'])) update_post_meta($pending['id'],'mkb_glossar_related_ids',implode(',',array_unique($ids))); } } if ($translation_pending) { foreach ($translation_pending as $group=>$translations) { if (count($translations)<2) continue; if (mkb_glossar_polylang_enabled() && function_exists('pll_save_post_translations')) { // Keep already-linked languages that are not present in this CSV. // Partial imports must not dismantle existing translation groups. $complete=$translations; if (function_exists('pll_get_post_translations')) { foreach ($translations as $id) { foreach ((array)pll_get_post_translations((int)$id) as $lang=>$linked_id) { if (!isset($complete[$lang]) && (int)$linked_id>0) $complete[$lang]=(int)$linked_id; } } } pll_save_post_translations($complete); } elseif (mkb_glossar_wpml_enabled()) { $first=(int)reset($translations); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Required third-party WPML/Elementor API hook name; changing it would break integration. $trid=apply_filters('wpml_element_trid',null,$first,'post_mkb_glossar'); if (!$trid) { /* translators: %s is the translation group identifier. */ $report['warnings'][]=sprintf(__('Translation group “%s” could not be linked.','mkb-glossar'),$group); continue; } foreach ($translations as $lang=>$id) { if ((int)$id===$first) continue; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Required third-party WPML/Elementor API hook name; changing it would break integration. do_action('wpml_set_element_language_details',['element_id'=>(int)$id,'element_type'=>'post_mkb_glossar','trid'=>$trid,'language_code'=>$lang]); } } } } mkb_glossar_reset_request_cache(); return $report; } function mkb_glossar_csv_translation_group($id) { if (mkb_glossar_polylang_enabled() && function_exists('pll_get_post_translations')) { $linked=(array)pll_get_post_translations((int)$id); $ids=array_filter(array_map('absint',$linked)); return count($ids)>1?'group-'.min($ids):''; } if (mkb_glossar_wpml_enabled()) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Required third-party WPML/Elementor API hook name; changing it would break integration. $trid=apply_filters('wpml_element_trid',null,(int)$id,'post_mkb_glossar'); return $trid ? 'group-'.$trid : ''; } return ''; } function mkb_glossar_csv_export_rows() { $posts=get_posts(['post_type'=>'mkb_glossar','post_status'=>['publish','draft','pending','private'], // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- Gather entries from every language for CSV identity reconciliation. 'posts_per_page'=>-1,'orderby'=>'ID','order'=>'ASC','suppress_filters'=>true]); $rows=[]; foreach ($posts as $post) { $related=[]; foreach (mkb_glossar_editorial_related_ids($post->ID) as $id) { $target=get_post($id);if ($target) $related[]=$target->post_name; } $topics=[]; $terms=wp_get_post_terms($post->ID,'mkb_glossar_topic'); if (!is_wp_error($terms)) foreach ($terms as $term) { $parent=(int)($term->parent??0); $parent_term=$parent?get_term($parent,'mkb_glossar_topic'):null; $suffix=($parent_term && !is_wp_error($parent_term))?'::'.$parent_term->slug.'::'.$parent_term->name:''; $topics[]=$term->slug.'::'.$term->name.$suffix; } $rows[]=[ $post->post_title,$post->post_name,(string)get_post_meta($post->ID,'mkb_glossar_summary',true), $post->post_content,(string)get_post_meta($post->ID,'mkb_glossar_synonyms',true), implode('|',$topics),mkb_glossar_post_language($post->ID)??'', $post->post_status,implode('|',$related), (string)get_post_meta($post->ID,'mkb_glossar_references',true), (string)get_post_meta($post->ID,'mkb_glossar_external_url',true), (string)get_post_meta($post->ID,'mkb_glossar_use_external',true)==='1'?'1':'0', (string)get_post_meta($post->ID,'mkb_glossar_external_newtab',true)==='0'?'0':'1', (string)get_post_meta($post->ID,'mkb_glossar_external_nofollow',true)==='1'?'1':'0', (string)get_post_meta($post->ID,'mkb_glossar_external_sponsored',true)==='1'?'1':'0', (string)get_post_meta($post->ID,'mkb_glossar_external_ugc',true)==='1'?'1':'0', (string)get_post_meta($post->ID,'mkb_glossar_case_sensitive',true)==='1'?'1':'0', mkb_glossar_csv_translation_group($post->ID), ]; } return $rows; } /** The sample always uses the same stable column keys as a real export. */ function mkb_glossar_csv_sample_row() { return [ 'title' => 'Beispielbegriff', 'slug' => 'beispielbegriff', 'summary' => 'Eine kurze Erklärung des Begriffs.', 'definition' => 'Hier steht die ausführliche Beschreibung. Ersetze diesen Beispieltext durch deinen eigenen Inhalt.', 'synonyms' => 'Alternativer Begriff', 'topics' => 'beispiele::Beispiele', 'language' => '', 'status' => 'draft', 'related_terms' => '', 'references' => '', 'external_url' => '', 'use_external_target' => '0', 'external_new_tab' => '1', 'external_nofollow' => '0', 'external_sponsored' => '0', 'external_ugc' => '0', 'case_sensitive' => '0', 'translation_group' => '', ]; } /** Browser download: 18 current columns, UTF-8 BOM, semicolon and one draft example. */ add_action('admin_post_mkb_glossar_csv_template', static function() { if (!current_user_can('manage_options')) wp_die(esc_html__('Not allowed.', 'mkb-glossar'), 403); check_admin_referer('mkb_glossar_csv_template'); if (headers_sent()) wp_die(esc_html__('CSV download cannot start after headers were sent.', 'mkb-glossar')); nocache_headers(); header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="mkb-glossar-muster.csv"'); header('X-Content-Type-Options: nosniff'); $output = mkb_glossar_csv_open_stream('php://output', 'wb'); mkb_glossar_csv_write_bom($output); $keys = array_keys(mkb_glossar_csv_fields()); $sample = mkb_glossar_csv_sample_row(); fputcsv($output, $keys, ';', '"', ''); fputcsv($output, array_map('mkb_glossar_csv_escape_cell', array_values(array_replace(array_fill_keys($keys, ''), $sample))), ';', '"', ''); mkb_glossar_csv_close_stream($output); exit; }); add_action('admin_post_mkb_glossar_csv_export', static function() { if (!current_user_can('manage_options')) wp_die(esc_html__('Not allowed.','mkb-glossar'),403); check_admin_referer('mkb_glossar_csv_export'); if (headers_sent()) wp_die(esc_html__('CSV export cannot start after headers were sent.','mkb-glossar')); nocache_headers(); header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="mkb-glossar-'.gmdate('Y-m-d-His').'.csv"'); header('X-Content-Type-Options: nosniff'); $output=mkb_glossar_csv_open_stream('php://output','wb'); mkb_glossar_csv_write_bom($output); fputcsv($output,array_keys(mkb_glossar_csv_fields()),';','"',''); foreach (mkb_glossar_csv_export_rows() as $row) fputcsv($output,array_map('mkb_glossar_csv_escape_cell',$row),';','"',''); mkb_glossar_csv_close_stream($output); exit; }); function mkb_glossar_csv_options($input) { return [ 'mode'=>isset($input['mode']) && is_scalar($input['mode']) && (string)$input['mode']==='update' ? 'update' : 'skip', 'drafts'=>!empty($input['drafts']) && is_scalar($input['drafts']) && (string)$input['drafts']==='1', 'create_topics'=>!empty($input['create_topics']) && is_scalar($input['create_topics']) && (string)$input['create_topics']==='1', ]; } /** * A CSV mapping is a small, known-key index dictionary, not arbitrary POST data. * This parser is called only after the AJAX administrator nonce has passed. */ function mkb_glossar_csv_request_mapping($request) { if (!is_array($request) || !array_key_exists('map', $request)) return null; if (!is_array($request['map'])) { return new WP_Error('mkb_csv_mapping', __('Invalid CSV field mapping.', 'mkb-glossar')); } $fields = mkb_glossar_csv_fields(); if (count($request['map']) > count($fields)) { return new WP_Error('mkb_csv_mapping', __('Invalid CSV field mapping.', 'mkb-glossar')); } $result = []; foreach ($request['map'] as $field => $column) { if (!is_string($field) || !array_key_exists($field, $fields) || !is_scalar($column)) { return new WP_Error('mkb_csv_mapping', __('Invalid CSV field mapping.', 'mkb-glossar')); } $result[$field] = sanitize_text_field(wp_unslash((string) $column)); } return $result; } function mkb_glossar_csv_ajax_init() { if (!current_user_can('manage_options')) wp_send_json_error(['message'=>__('Not allowed.','mkb-glossar')],403); check_ajax_referer('mkb_glossar_csv','nonce'); // Uploaded PHP file descriptors must not be text-sanitized: validate error, // size, suffix and is_uploaded_file() before ever opening the temp path. // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- PHP-managed upload descriptor is validated as a structure and verified with is_uploaded_file(). $tmp=mkb_glossar_csv_upload($_FILES['csv']??null); if (is_wp_error($tmp)) wp_send_json_error(['message'=>$tmp->get_error_message()],400); $parsed=mkb_glossar_csv_parse($tmp); if (is_wp_error($parsed)) wp_send_json_error(['message'=>$parsed->get_error_message()],400); // The file and field mapping are intentionally re-validated for BOTH // preview and import; no client-supplied preview mapping is trusted. $raw_map = mkb_glossar_csv_request_mapping($_POST); if (is_wp_error($raw_map)) wp_send_json_error(['message'=>$raw_map->get_error_message()],400); $map=mkb_glossar_csv_normalize_map($raw_map,$parsed['headers']); if (is_wp_error($map)) wp_send_json_error(['message'=>$map->get_error_message()],400); $options=mkb_glossar_csv_options($_POST); $review=mkb_glossar_csv_review($parsed,$map,$options['mode']); if (is_wp_error($review)) wp_send_json_error(['message'=>$review->get_error_message(),'headers'=>$parsed['headers'],'mapping'=>$map],400); // The import's review token is read only inside this verified AJAX entry. $review_token = isset($_POST['review_token']) && is_string($_POST['review_token']) ? sanitize_text_field(wp_unslash($_POST['review_token'])) : ''; return compact('tmp','parsed','map','options','review','review_token'); } add_action('wp_ajax_mkb_glossar_csv_preview', static function() { $data=mkb_glossar_csv_ajax_init(); $token=wp_generate_password(30,false,false); set_transient('mkb_glossar_csv_'.get_current_user_id().'_'.$token,[ 'sha256'=>hash_file('sha256',$data['tmp']),'mapping'=>$data['map'],'options'=>$data['options'] ],15*MINUTE_IN_SECONDS); $sample=[]; foreach ($data['review']['entries'] as $row) $sample[]=[ 'line'=>$row['line'],'title'=>$row['title'],'action'=>$row['action'],'message'=>$row['message'] ]; wp_send_json_success(['token'=>$token,'headers'=>$data['parsed']['headers'],'mapping'=>$data['map'], 'counts'=>$data['review']['counts'],'total'=>count($data['review']['entries']),'sample'=>$sample]); }); add_action('wp_ajax_mkb_glossar_csv_import', static function() { $data=mkb_glossar_csv_ajax_init(); $token = $data['review_token']; if (!preg_match('/^[A-Za-z0-9]{30}$/D',$token)) wp_send_json_error(['message'=>__('Please preview the CSV before importing.','mkb-glossar')],409); $key='mkb_glossar_csv_'.get_current_user_id().'_'.$token; $reviewed=get_transient($key); if (!is_array($reviewed) || !hash_equals((string)$reviewed['sha256'],hash_file('sha256',$data['tmp'])) || $reviewed['mapping']!==$data['map'] || $reviewed['options']!==$data['options']) { wp_send_json_error(['message'=>__('The file or import choices changed. Please preview again.','mkb-glossar')],409); } delete_transient($key); if ($data['review']['counts']['error']>0) wp_send_json_error(['message'=>__('Please correct all conflicting or invalid rows before importing. Nothing was saved.','mkb-glossar')],409); $report=mkb_glossar_csv_apply($data['review'],$data['map'],$data['options']); wp_send_json_success($report); }); add_action('admin_menu',static function(){ add_submenu_page('edit.php?post_type=mkb_glossar',__('CSV import and export','mkb-glossar'),__('Import / Export','mkb-glossar'), 'manage_options','mkb-glossar-data','mkb_glossar_csv_page'); },25); function mkb_glossar_csv_page() { if (!current_user_can('manage_options')) wp_die(esc_html__('Not allowed.','mkb-glossar'),403); ?>

admin_url('admin-ajax.php'),'nonce'=>wp_create_nonce('mkb_glossar_csv'), 'fields'=>mkb_glossar_csv_fields(), 'none'=>__('— Do not import this field —','mkb-glossar'), 'actions'=>['create'=>__('New','mkb-glossar'),'update'=>__('Update','mkb-glossar'), 'skip'=>__('Skip','mkb-glossar'),'error'=>__('Conflict','mkb-glossar')], 'preview'=>__('Review before importing','mkb-glossar'), 'finished'=>__('Import finished.','mkb-glossar'), 'failed'=>__('The import failed. No changes were made if the review was not confirmed.','mkb-glossar'), 'counts'=>__('New / Update / Skip / Conflict','mkb-glossar'), 'line'=>__('Row','mkb-glossar'), 'done'=>__('Created / Updated / Skipped / Errors','mkb-glossar'), 'confirm'=>__('Import the reviewed changes now? This can create or update glossary terms.','mkb-glossar'), 'rows'=>__('rows','mkb-glossar'),'status'=>__('Status','mkb-glossar'),'details'=>__('Details','mkb-glossar'), 'showing'=>__('Showing rows','mkb-glossar'), 'previous'=>__('Previous results','mkb-glossar'), 'next'=>__('Next results','mkb-glossar'), 'correctConflicts'=>__('Please correct the conflicting CSV rows before importing.','mkb-glossar'), 'selectFile'=>__('Please select a CSV file.','mkb-glossar'),'checking'=>__('Checking CSV…','mkb-glossar'), 'importing'=>__('Importing reviewed data…','mkb-glossar'),'warnings'=>__('warnings in total','mkb-glossar'), ]).';','before'); });