<?php
/**
 * Plugin Name: HBDev Budget
 * Plugin URI: https://www.dev-becker.de/hbdev-budget/
 * Description: HBDev-Budget hilft Dir Deine monatlichen Ausgaben und Deine Abos zu verwalten und zu analysieren. Deine Daten sind nur für Dich sichtbar.
 * Author: Heiko Becker
 * Author URI: https://www.dev-becker.de/
 * Version: 1.1.5
 * Requires at least: 6.2
 * Tested up to: 6.8
 * Requires PHP: 8.0
 * Update URI: https://www.dev-becker.de/updates/hbdev-budget/hbdev-budget.json
 * Text Domain: hbdev-budget
 */

if ( ! defined('ABSPATH') ) exit;

define('HBDEV_BUDGET_VER', '1.1.4');
define('HBDEV_BUDGET_DIR', plugin_dir_path(__FILE__));
define('HBDEV_BUDGET_URL', plugin_dir_url(__FILE__));

// Composer Autoloader laden (falls verfügbar)
if (file_exists(__DIR__ . '/vendor/autoload.php')) {
	require __DIR__ . '/vendor/autoload.php';
}
// Fallback: Manuelle Includes, falls Composer-Autoloader nicht vorhanden ist
if (!class_exists('HBDEV_Budget_Admin_Page')) {
	require_once HBDEV_BUDGET_DIR . 'includes/helpers.php';
	require_once HBDEV_BUDGET_DIR . 'includes/class-admin-page.php';
	require_once HBDEV_BUDGET_DIR . 'includes/class-rest.php';
	require_once HBDEV_BUDGET_DIR . 'includes/class-metabox.php';
	require_once HBDEV_BUDGET_DIR . 'includes/class-subscription-metabox.php';
	require_once HBDEV_BUDGET_DIR . 'includes/class-subscriptions.php';
	require_once HBDEV_BUDGET_DIR . 'includes/class-tax-ownership.php';
}

class HBDEV_Budget_Plugin {
	public function __construct() {
		add_action('init', [$this, 'register_cpt_and_tax']);
		add_action('admin_init', [$this, 'register_caps']);
		add_action('admin_menu', ['HBDEV_Budget_Admin_Page', 'register_menu']);
		add_action('rest_api_init', ['HBDEV_Budget_REST', 'register_routes']);
		// Optionale Anzeige von Dashicons in linken Submenüs (inoffiziell via JS)
		add_action('admin_head', [$this, 'maybe_render_submenu_icons']);
		// Cache-Invalidierung bei Änderungen an Buchungen
		add_action('save_post_booking', [$this, 'on_booking_changed'], 20, 2);
		add_action('delete_post', [$this, 'on_post_deleted'], 10);
		// Nur eigene Daten sichtbar/bearbeitbar (Listen filtern, Autor erzwingen)
		add_action('pre_get_posts', [$this, 'restrict_admin_lists_to_own_items']);
		add_action('save_post_booking', [$this, 'enforce_ownership_on_save'], 1, 2);
		add_action('save_post_subscription', [$this, 'enforce_ownership_on_save'], 1, 2);
		// Neue Benutzer automatisch mit sekundärer Rolle ausstatten
		add_action('user_register', [$this, 'ensure_secondary_role_for_user']);
		register_activation_hook(__FILE__, [$this, 'on_activate']);
	}

	public function on_booking_changed($post_id, $post) {
		if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
		if ($post->post_type !== 'booking') return;
		// Ermittele betroffenen Monat aus Meta oder aus eingehendem POST
		$ym = '';
		if (!empty($_POST['hbdev_booking_date'])) {
			$ym = substr(sanitize_text_field($_POST['hbdev_booking_date']), 0, 7);
		}
		if (!$ym) {
			$date = get_post_meta($post_id, 'booking_date', true);
			if (is_string($date) && preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
				$ym = substr($date, 0, 7);
			}
		}
		if (!$ym) {
			$ym = (new DateTimeImmutable('now', wp_timezone()))->format('Y-m');
		}
		$owner_id = (int) get_post_field('post_author', $post_id) ?: get_current_user_id();
		HBDEV_Budget_Helpers::invalidate_stats_cache_from($ym, 36, $owner_id);
	}

	public function on_post_deleted($post_id) {
		$post = get_post($post_id);
		if (!$post || $post->post_type !== 'booking') return;
		$date = get_post_meta($post_id, 'booking_date', true);
		$ym = (is_string($date) && preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) ? substr($date,0,7) : (new DateTimeImmutable('now', wp_timezone()))->format('Y-m');
		$owner_id = (int) get_post_field('post_author', $post_id) ?: get_current_user_id();
		HBDEV_Budget_Helpers::invalidate_stats_cache_from($ym, 36, $owner_id);
	}

	/**
	 * Optional: Zeigt Dashicons vor den Submenü-Punkten im linken Admin-Menü an (inoffizieller Workaround).
	 * Aktivierung via Filter: `hbdev_budget_show_submenu_icons_in_left_nav` (bool, default false).
	 * Nutzt die bestehende Map aus `hbdev_budget_dashicons` und fügt per JS ein <span class="dashicons ..."> ein.
	 */
	public function maybe_render_submenu_icons() {
		$enabled = (bool) apply_filters('hbdev_budget_show_submenu_icons_in_left_nav', false);
		if (!is_admin() || !current_user_can('read') || !$enabled) return;

		$slugs = [
			'hbdev-budget-dashboard',
			'hbdev-active-subscriptions',
			'hbdev-cancelled-subscriptions',
		];
		$config = [];
		foreach ($slugs as $slug) {
			$icon = HBDEV_Budget_Helpers::get_page_dashicon_class($slug);
			if ($icon !== '') {
				$config[$slug] = $icon;
			}
		}
		if (empty($config)) return;

		// Inline-Script ausgeben, das vor die Link-Texte ein Dashicon setzt
		echo "\n<script id=\"hbdev-budget-submenu-icons\">(function(){\n";
		echo "function ready(fn){if(document.readyState!='loading'){fn();}else{document.addEventListener('DOMContentLoaded',fn);}}\n";
		echo "ready(function(){\n";
		foreach ($config as $slug => $icon) {
			$selector = '#adminmenu .wp-submenu a[href$="page=' . esc_js($slug) . '"]';
			$jsIcon = esc_js($icon);
			echo "var a=document.querySelector('{$selector}');if(a){var span=document.createElement('span');span.className='dashicons {$jsIcon}';span.style.marginRight='6px';span.style.verticalAlign='middle';a.prepend(span);}\n";
		}
		echo "});\n";
		echo "})();</script>\n";
	}

	public function register_cpt_and_tax() {
		// CPT: booking
		register_post_type('booking', [
			'label' => __('Budget', 'hbdev-budget'),
			'labels' => [
				'name' => __('Budget', 'hbdev-budget'),
				'menu_name' => __('HBDEV Budget', 'hbdev-budget'),
				'all_items' => __('Buchungen', 'hbdev-budget'),
				'singular_name' => __('Buchung', 'hbdev-budget'),
				'add_new_item' => __('Buchung hinzufügen', 'hbdev-budget'),
				'edit_item' => __('Buchung bearbeiten', 'hbdev-budget'),
			],
			'public' => false,
			'show_ui' => true,
			'menu_icon' => 'dashicons-chart-pie',
			'supports' => ['title','author'],
			'capability_type' => ['booking','bookings'],
			'map_meta_cap' => true
		]);

		// CPT: subscription (Abo)
		register_post_type('subscription', [
			'label' => __('Abos', 'hbdev-budget'),
			'labels' => [
				'name' => __('Abos', 'hbdev-budget'),
				'menu_name' => __('Abos erstellen/bearbeiten', 'hbdev-budget'),
				'all_items' => __('Abos erstellen/bearbeiten', 'hbdev-budget'),
				'singular_name' => __('Abo', 'hbdev-budget'),
				'add_new_item' => __('Abo hinzufügen', 'hbdev-budget'),
				'edit_item' => __('Abo bearbeiten', 'hbdev-budget'),
			],
			'public' => false,
			'show_ui' => true,
			'menu_icon' => 'dashicons-update',
			'supports' => ['title','author'],
			'capability_type' => ['subscription','subscriptions'],
			'map_meta_cap' => true,
			'show_in_menu' => 'edit.php?post_type=booking' // als Untermenü unter Budget
		]);

		// Taxonomie: booking_type (hierarchisch) für Buchungen und Abos
		// Eigene Capabilities, um Zuweisung/Verwaltung unabhängig von WP-Standardcaps zu steuern
		register_taxonomy('booking_type', ['booking','subscription'], [
			'label' => __('Buchungs-Kategorien', 'hbdev-budget'),
			'hierarchical' => true,
			'show_ui' => true,
			'show_admin_column' => true,
			'capabilities' => [
				// Verwaltung (Liste/Erstellen/Bearbeiten/Löschen) der Kategorien
				'manage_terms' => 'manage_booking_types',
				'edit_terms'   => 'manage_booking_types',
				'delete_terms' => 'manage_booking_types',
				// Zuweisung in Buchungen/Abos zulassen für Benutzer, die Buchungen bearbeiten dürfen
				'assign_terms' => 'edit_bookings',
			],
		]);
	}

	public function register_caps() {
		// Basis-Caps für Administratoren
		$admin = get_role('administrator');
		if ($admin) {
			foreach ([
				// Buchungen
				'edit_booking','read_booking','delete_booking','edit_bookings','edit_others_bookings','publish_bookings','read_private_bookings','delete_bookings','delete_private_bookings','delete_published_bookings','delete_others_bookings','edit_published_bookings',
				// Abos
				'edit_subscription','read_subscription','delete_subscription','edit_subscriptions','edit_others_subscriptions','publish_subscriptions','read_private_subscriptions','delete_subscriptions','delete_private_subscriptions','delete_published_subscriptions','delete_others_subscriptions','edit_published_subscriptions'
			] as $cap) {
				$admin->add_cap($cap);
			}
			// Dashboard-Ansicht
			$admin->add_cap('manage_bookings');
			// Darf Buchungs-Kategorien verwalten (Eigentums-Filter greifen separat)
			$admin->add_cap('manage_booking_types');
		}

		// Optionale Rollen (Viewer / Manager)
		if (! get_role('booking_viewer')) {
			add_role('booking_viewer', 'Budget Viewer', [
				'read' => true,
				'read_booking' => true,
				'read_private_bookings' => true,
				'manage_bookings' => true, // darf Dashboard sehen
			]);
		}
		if (! get_role('booking_manager')) {
			add_role('booking_manager', 'Budget Manager', [
				'read' => true,
				// Buchungen
				'edit_bookings' => true,
				'publish_bookings' => true,
				'delete_bookings' => true,
				// Abos
				'edit_subscriptions' => true,
				'publish_subscriptions' => true,
				'delete_subscriptions' => true,
				'manage_bookings' => true,
			]);
		}

		// HBDEV-Budget-User (sekundäre Rolle für alle Benutzer): darf eigene Buchungen/Abos verwalten + Dashboard
		$role = get_role('hbdev_budget_user');
		$caps_for_user = [
			'read' => true,
			// Buchungen (nur eigene, da KEIN edit_others_*)
			'edit_bookings' => true,
			'publish_bookings' => true,
			'delete_bookings' => true,
			'read_booking' => true,
			// Abos (nur eigene)
			'edit_subscriptions' => true,
			'publish_subscriptions' => true,
			'delete_subscriptions' => true,
			'read_subscription' => true,
			// Dashboard/REST
			'manage_bookings' => true,
			// Eigene Kategorien verwalten
			'manage_booking_types' => true,
		];
		if (! $role) {
			add_role('hbdev_budget_user', 'HBDEV-Budget-User', $caps_for_user);
		} else {
			foreach ($caps_for_user as $cap => $grant) {
				if ($grant) $role->add_cap($cap);
			}
		}
	}

	public function on_activate() {
		$this->register_cpt_and_tax();
		$this->register_caps(); // sicherstellen, dass Rollen/Caps existieren
		flush_rewrite_rules();

		// Stamm-Terme: Einnahmen, Ausgaben
		$root_income = term_exists('Einnahmen', 'booking_type');
		if (!$root_income) wp_insert_term('Einnahmen', 'booking_type', ['slug'=>'einnahmen']);
		$root_expense = term_exists('Ausgaben', 'booking_type');
		if (!$root_expense) {
			$res = wp_insert_term('Ausgaben', 'booking_type', ['slug'=>'ausgaben']);
			$expense_root_id = is_wp_error($res) ? 0 : $res['term_id'];
			// Optionale Unterlevel-Beispiele (2 weitere Stufen als Gerüst)
			if ($expense_root_id) {
				$lvl1 = wp_insert_term('Fixkosten', 'booking_type', ['parent'=>$expense_root_id,'slug'=>'fixkosten']);
				$lvl1_id = is_wp_error($lvl1) ? 0 : $lvl1['term_id'];
				if ($lvl1_id) {
					wp_insert_term('Miete', 'booking_type', ['parent'=>$lvl1_id,'slug'=>'miete']);
					wp_insert_term('Versicherungen', 'booking_type', ['parent'=>$lvl1_id,'slug'=>'versicherungen']);
				}
				$lvl1b = wp_insert_term('Variabel', 'booking_type', ['parent'=>$expense_root_id,'slug'=>'variabel']);
				$lvl1b_id = is_wp_error($lvl1b) ? 0 : $lvl1b['term_id'];
				if ($lvl1b_id) {
					wp_insert_term('Einkauf', 'booking_type', ['parent'=>$lvl1b_id,'slug'=>'einkauf']);
					wp_insert_term('Freizeit', 'booking_type', ['parent'=>$lvl1b_id,'slug'=>'freizeit']);
				}
			}
		}

		// Sekundäre Rolle allen existierenden Benutzern hinzufügen
		$users = get_users([ 'fields' => [ 'ID', 'roles' ] ]);
		if (is_array($users)) {
			foreach ($users as $u) {
				$user = get_user_by('id', is_object($u) ? $u->ID : (int)$u);
				if ($user && ! in_array('hbdev_budget_user', (array)$user->roles, true)) {
					$user->add_role('hbdev_budget_user');
				}
			}
		}
	}

	/**
	 * Filtert die Admin-Listenansicht so, dass Nicht-Admins (ohne edit_others_*) nur eigene Einträge sehen.
	 */
	public function restrict_admin_lists_to_own_items($query) {
		if (!is_admin() || !function_exists('get_current_screen')) return;
		if (!($query instanceof WP_Query)) return;
		$screen = function_exists('get_current_screen') ? get_current_screen() : null;
		$post_type = $query->get('post_type');
		if (!$post_type && $screen && isset($screen->post_type)) {
			$post_type = $screen->post_type;
		}
		if (!in_array($post_type, ['booking','subscription'], true)) return;
		// Wenn Nutzer nicht die Fremd-Bearbeitungsrechte hat, nach Autor filtern
		if ($post_type === 'booking' && ! current_user_can('edit_others_bookings')) {
			$query->set('author', get_current_user_id());
		}
		if ($post_type === 'subscription' && ! current_user_can('edit_others_subscriptions')) {
			$query->set('author', get_current_user_id());
		}
	}

	/**
	 * Erzwingt beim Speichern, dass der aktuelle Benutzer als Autor gesetzt ist,
	 * sofern er keine Fremd-Bearbeitungsrechte hat. Verhindert Fremdzuweisungen.
	 */
	public function enforce_ownership_on_save($post_id, $post) {
		if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
		if (!($post instanceof WP_Post)) return;
		if (!in_array($post->post_type, ['booking','subscription'], true)) return;
		// Nur eingreifen, wenn Nutzer nicht "edit_others_*" kann
		$needs_own = (
			($post->post_type === 'booking' && ! current_user_can('edit_others_bookings')) ||
			($post->post_type === 'subscription' && ! current_user_can('edit_others_subscriptions'))
		);
		if (!$needs_own) return;
		$current = get_current_user_id();
		if ((int)$post->post_author !== (int)$current) {
			// Direkt den Autor setzen
			remove_action('save_post_booking', [$this, 'enforce_ownership_on_save'], 1);
			remove_action('save_post_subscription', [$this, 'enforce_ownership_on_save'], 1);
			wp_update_post([
				'ID' => $post_id,
				'post_author' => $current,
			]);
			add_action('save_post_booking', [$this, 'enforce_ownership_on_save'], 1, 2);
			add_action('save_post_subscription', [$this, 'enforce_ownership_on_save'], 1, 2);
		}
	}

	/**
	 * Stellt sicher, dass neue Benutzer automatisch die sekundäre Rolle erhalten.
	 */
	public function ensure_secondary_role_for_user($user_id) {
		$user = get_user_by('id', (int)$user_id);
		if ($user && ! in_array('hbdev_budget_user', (array)$user->roles, true)) {
			$user->add_role('hbdev_budget_user');
		}
	}
}
new HBDEV_Budget_Plugin();

// Variante A: Strikte User-Isolation
// 1) map_meta_cap: Nur Besitzer darf booking/subscription lesen/bearbeiten/löschen
add_filter('map_meta_cap', function(array $caps, string $cap, int $user_id, array $args){
	if (!in_array($cap, ['edit_post','read_post','delete_post'], true)) {
		return $caps;
	}
	$post_id = isset($args[0]) ? (int)$args[0] : 0;
	if ($post_id <= 0) return $caps;
	$post = get_post($post_id);
	if (!$post || !in_array($post->post_type, ['booking','subscription'], true)) {
		return $caps;
	}
	if ((int)$post->post_author !== (int)$user_id) {
		return ['do_not_allow'];
	}
	switch ($cap) {
		case 'read_post':
			return ['read'];
		case 'edit_post':
			return ['edit_' . $post->post_type . 's'];
		case 'delete_post':
			return ['delete_' . $post->post_type . 's'];
	}
	return $caps;
}, 10, 4);

// 2) Beim Speichern Eigentümer erzwingen
add_action('save_post_booking', function($post_id, $post){
	if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
	if ($post->post_type !== 'booking') return;
	if (! current_user_can('edit_post', $post_id)) {
		wp_die(__('Kein Zugriff.', 'hbdev-budget'));
	}
	$uid = get_current_user_id();
	if ((int)$post->post_author !== (int)$uid) {
		wp_update_post(['ID' => $post_id, 'post_author' => $uid]);
	}
}, 9, 2);

add_action('save_post_subscription', function($post_id, $post){
	if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
	if ($post->post_type !== 'subscription') return;
	if (! current_user_can('edit_post', $post_id)) {
		wp_die(__('Kein Zugriff.', 'hbdev-budget'));
	}
	$uid = get_current_user_id();
	if ((int)$post->post_author !== (int)$uid) {
		wp_update_post(['ID' => $post_id, 'post_author' => $uid]);
	}
}, 9, 2);

// 3) Admin-Listen auf eigene Beiträge einschränken
add_action('pre_get_posts', function(WP_Query $q){
	if (!is_admin() || !$q->is_main_query()) return;
	$pt = $q->get('post_type');
	if (!in_array($pt, ['booking','subscription'], true)) return;
	$q->set('author', get_current_user_id());
});

// 4) Rolle hbdev_budget_user sicherstellen und automatisch zuweisen
add_action('init', function(){
	if (! get_role('hbdev_budget_user')) {
		add_role('hbdev_budget_user', 'HBDEV-Budget-User', [
			'read' => true,
			'edit_bookings' => true,
			'publish_bookings' => true,
			'delete_bookings' => true,
			'edit_subscriptions' => true,
			'publish_subscriptions' => true,
			'delete_subscriptions' => true,
		]);
	}
});

register_activation_hook(__FILE__, function(){
	if (! get_role('hbdev_budget_user')) return;
	$users = get_users(['fields' => ['ID']]);
	foreach ($users as $u) {
		$user = new WP_User($u->ID);
		if (! in_array('hbdev_budget_user', (array)$user->roles, true)) {
			$user->add_role('hbdev_budget_user');
		}
	}
});

add_action('user_register', function($user_id){
	$user = new WP_User($user_id);
	$user->add_role('hbdev_budget_user');
});

add_action('add_user_to_blog', function($user_id, $role, $blog_id){
	if ((int)$blog_id !== (int)get_current_blog_id()) return;
	$user = new WP_User($user_id);
	if (! in_array('hbdev_budget_user', (array)$user->roles, true)) {
		$user->add_role('hbdev_budget_user');
	}
}, 10, 3);

add_action('set_user_role', function($user_id){
	$user = new WP_User($user_id);
	if (! in_array('hbdev_budget_user', (array)$user->roles, true)) {
		$user->add_role('hbdev_budget_user');
	}
}, 10, 1);
