diff --git a/d2s-discourse-map/d2s-discourse-map.php b/d2s-discourse-map/d2s-discourse-map.php new file mode 100644 index 0000000..6c7c289 --- /dev/null +++ b/d2s-discourse-map/d2s-discourse-map.php @@ -0,0 +1,275 @@ + 'logbuch', + 'category_map' => ['privatlog' => 'privatlog'], + ], false); + } + } + + public function add_menu() { + add_options_page( + 'Discourse Tag Automapper', + 'Discourse Tag Automapper', + 'manage_options', + 'd2s-discourse-tag-automapper', + [$this, 'render_page'] + ); + } + + private function sanitize_tag($s) { + $s = is_string($s) ? trim($s) : ''; + $s = strtolower($s); + $s = str_replace([',',';'], '', $s); + $s = preg_replace('/\s+/', '-', $s); + $s = preg_replace('/[^a-z0-9\-_]/', '', $s); + return $s; + } + + private function handle_save() { + if (!current_user_can('manage_options')) return; + if (!isset($_POST['d2s_discourse_tag_nonce']) || !wp_verify_nonce($_POST['d2s_discourse_tag_nonce'], 'd2s_discourse_tag_save')) return; + + $fallback = isset($_POST['fallback_tag']) ? $this->sanitize_tag(wp_unslash($_POST['fallback_tag'])) : ''; + $raw_map = isset($_POST['category_map']) ? (array) $_POST['category_map'] : []; + + $map = []; + foreach ($raw_map as $cat_id => $tag) { + $tag = $this->sanitize_tag(wp_unslash($tag)); + $cat_id = intval($cat_id); + if ($cat_id > 0 && $tag !== '') { + $term = get_term($cat_id, 'category'); + if ($term && !is_wp_error($term)) $map[$term->slug] = $tag; + } + } + + update_option(self::OPTION, [ + 'fallback_tag' => $fallback !== '' ? $fallback : 'logbuch', + 'category_map' => $map, + ], false); + + add_settings_error('d2s_discourse_tag_automapper', 'saved', 'Einstellungen gespeichert.', 'updated'); + } + + public function render_page() { + if ($_SERVER['REQUEST_METHOD'] === 'POST') $this->handle_save(); + + $opt = get_option(self::OPTION); + $fallback = $opt['fallback_tag'] ?? 'logbuch'; + $map = (array)($opt['category_map'] ?? []); + + $cats = get_categories(['hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC']); + + settings_errors('d2s_discourse_tag_automapper'); ?> +
+

Discourse Tag Automapper

+
+ + + + + + + + +

Kategorie → Discourse-Tag

+

Pro WP-Kategorie optional ein Discourse-Tag. Leer lassen = kein spezielles Tag → Fallback greift.

+ + + + + slug; + $current = $map[$slug] ?? ''; + ?> + + + + + + +
KategorieDiscourse-Tag
+ name); ?> +
Slug: (ID term_id); ?>)
+
+ +
+ + +
+
+ slug; + } + return $list; + } + + private function compute_tags_for_post($post) { + $opt = get_option(self::OPTION); + $fallback = $opt['fallback_tag'] ?? 'logbuch'; + $map = (array)($opt['category_map'] ?? []); + + $auto = $fallback; + $terms = get_the_terms($post, 'category'); + + if (!empty($terms) && !is_wp_error($terms)) { + usort($terms, fn($a,$b)=> strcmp($a->name, $b->name)); + foreach ($terms as $t) { + if (isset($map[$t->slug]) && $map[$t->slug] !== '') { $auto = $map[$t->slug]; break; } + } + } + + $tags = [$auto]; // kein Merge mit älteren Meta-Werten + $tags = array_map([$this, 'sanitize_tag'], $tags); + $tags = array_values(array_unique(array_filter($tags))); + + $this->log('[compute] post_id='.$post->ID.' cats='.wp_json_encode($this->get_post_categories_debug($post->ID)).' -> tags='.wp_json_encode($tags)); + return $tags; + } + + /* ===== ältere Filterpfade: falls deine Version sie nutzt ===== */ + + public function apply_mapping_to_params($params, $post) { + if (!$post instanceof WP_Post) return $params; + $tags = $this->compute_tags_for_post($post); + $params['tags'] = $tags; + $params['tag_names'] = $tags; + $this->log('[params] post_id='.$post->ID.' tags='.wp_json_encode($tags)); + return $params; + } + + /* ===== Späte/robuste Wege ===== */ + + public function apply_mapping_on_save($post_id, $post, $update) { + if (wp_is_post_revision($post_id)) return; + if ($post->post_type !== 'post') return; + + $tags = $this->compute_tags_for_post($post); + + update_post_meta($post_id, 'wpdc_discourse_tags', implode(',', $tags)); + update_post_meta($post_id, 'discourse_tags', implode(',', $tags)); + $this->log('[save_post] post_id='.$post_id.' tags='.wp_json_encode($tags)); + } + + public function apply_mapping_after_insert($post_id, $post, $update, $post_before) { + if ($post->post_type !== 'post') return; + $tags = $this->compute_tags_for_post($post); + update_post_meta($post_id, 'wpdc_discourse_tags', implode(',', $tags)); + update_post_meta($post_id, 'discourse_tags', implode(',', $tags)); + $this->log('[after_insert] post_id='.$post_id.' tags='.wp_json_encode($tags)); + } + + public function remember_post_on_publish($new_status, $old_status, $post) { + if (!$post instanceof WP_Post || $post->post_type !== 'post') return; + if ($new_status === 'publish' && $old_status !== 'publish') { + // ID 30s merken – reicht für den direkt anschließenden Publish-Request + set_transient(self::LAST_PUB_TX, intval($post->ID), 30); + $this->log('[remember] post_id='.$post->ID.' (publish transient gesetzt)'); + } + } + + public function apply_mapping_on_status_change($new_status, $old_status, $post) { + if (!$post instanceof WP_Post || $post->post_type !== 'post') return; + if ($new_status === 'publish' && $old_status !== 'publish') { + $this->apply_mapping_on_save($post->ID, $post, true); + $this->log('[status_change] post_id='.$post->ID.' '.$old_status.'→'.$new_status); + } + } + + /* ===== Final: Publish-Body patchen (garantiert im Request) ===== */ + + public function inject_tags_into_publish_body($body, $remote_post_type) { + // 1) Versuche $_REQUEST (falls die WP-Discourse-Version das setzt) + $post_id = isset($_REQUEST['post_id']) ? intval($_REQUEST['post_id']) : (isset($_REQUEST['post']) ? intval($_REQUEST['post']) : 0); + + // 2) Fallback: nimm die zuletzt veröffentlichte ID aus dem Transient + if (!$post_id) { + $post_id = intval(get_transient(self::LAST_PUB_TX)); + if ($post_id) { + $this->log('[publish_body] fallback via transient post_id='.$post_id); + } + } + + $post = $post_id ? get_post($post_id) : null; + + if ($post instanceof WP_Post && $post->post_type === 'post') { + $tags = $this->compute_tags_for_post($post); // frisch, aus den aktuellen Kategorien + if (!empty($tags)) { + $body['tags'] = $tags; + $body['tag_names'] = $tags; + $this->log('[publish_body] post_id='.$post->ID.' type='.$remote_post_type.' cats='.wp_json_encode($this->get_post_categories_debug($post->ID)).' tags='.wp_json_encode($tags)); + } else { + $this->log('[publish_body] post_id='.$post->ID.' type='.$remote_post_type.' tags=[] (leer)'); + } + } else { + $this->log('[publish_body] kein gültiger Post-Kontext'); + } + return $body; + } + +private function log($line) { + $file = trailingslashit(WP_CONTENT_DIR) . self::LOGFILE; + if (file_exists($file) && filesize($file) > 200 * 1024) { + @unlink($file); // rotate + } + $date = gmdate('Y-m-d H:i:s'); + @file_put_contents($file, '['.$date.' UTC] '.$line.PHP_EOL, FILE_APPEND); +} + + public static function on_uninstall() { + delete_option(self::OPTION); + // Log bleibt liegen + } +} + +new D2S_Discourse_Tag_Automapper(); +register_uninstall_hook(__FILE__, ['D2S_Discourse_Tag_Automapper', 'on_uninstall']); \ No newline at end of file diff --git a/d2s-discourse-map/d2s-discourse-map.zip b/d2s-discourse-map/d2s-discourse-map.zip new file mode 100644 index 0000000..964323e Binary files /dev/null and b/d2s-discourse-map/d2s-discourse-map.zip differ