roles; // Sjekk om brukeren har administrator-rettigheter if (in_array('administrator', $user_roles) || in_array('kbs_admin', $user_roles)) { return; // Administratorer har alltid tilgang } $forbidden_ids = array(3232,3234, 3547,3567); $current_id = get_queried_object_id(); $access_denied = false; // Sjekk om det er en post, side eller egendefinert post med spesifiserte IDer if (is_singular() && in_array($current_id, $forbidden_ids)) { $access_denied = true; } // Sjekk kategorier if (is_singular()) { $post_categories = wp_get_post_categories($current_id, array('fields' => 'slugs')); // Sjekk om innlegget KUN er i kategorien 'forbudt' eller KUN i kategorien 'absolutt_ikke' if (count($post_categories) === 1 && (in_array('administrasjonen-i-kbs', $post_categories) || in_array('absolutt_ikke', $post_categories))) { $access_denied = true; } // Sjekk om innlegget har BÅDE 'forbudt' OG 'absolutt_ikke' // (blokkerende kombinasjoner) if (in_array('administrasjonen-i-kbs', $post_categories) && in_array('absolutt_ikke', $post_categories)) { $access_denied = true; } } // Sjekk om vi er på en arkivside for forbudte taksonomier if (is_category('administrasjonen-i-kbs') || is_category('absolutt_ikke')) { $access_denied = true; } // Omdirigering hvis tilgang er nektet if ($access_denied) { echo '

Vennligst kontakt administratoren på nettstedet for å få tilgang til dette innholdet.. Du blir nå videresendt til forsiden.

'; // JavaScript for omdirigering etter 3 sekunder echo ''; exit; } } /** * Redirect etter innlogging */ function envide_custom_login_redirect( $url, $request, $user ) { if ( $user && is_object( $user ) && is_a( $user, 'WP_User' ) ) { $url = home_url( '/' ); } return $url; } add_filter( 'login_redirect', 'envide_custom_login_redirect', 10, 3 ); /** * Farger til tilpasser og Beaver Builder */ //Change the Customizer color palette presets add_action('customize_controls_print_footer_scripts', function () { ?> * @license GPL-2.0+ * @link https://gravitywiz.com/ */ add_filter( 'gform_field_value_user_role', 'gform_populate_user_role' ); function gform_populate_user_role( $value ) { $user = wp_get_current_user(); $role = $user->roles; return reset( $role ); } /** * Gjør spesifikke Gravity Forms-felt redigerbare kun for valgt leder eller administrator. */ /** * Gravity Forms: Lås felter til kun nærmeste leder (felt 3) og administrator. * - Gjelder skjema ID 21 * - Felt som kontrolleres: 40 (liste), 41 (checkbox), 37 (textarea), 44 (dato) * - Seksjoner 36 og 39 har ikke egne input, men vi forsøker å disable innhold under disse også via JS. */ add_filter( 'gform_pre_render_21', 'kbs_lock_lederfelter' ); add_filter( 'gform_pre_validation_21', 'kbs_lock_lederfelter' ); function kbs_lock_lederfelter( $form ) { $protected = array( 40, 41, 37, 44 ); // rene input-felt $sections = array( 36, 39 ); // seksjoner (ingen egne inputs) // Hvem er innlogget? $current_user_id = get_current_user_id(); $is_admin = current_user_can( 'administrator' ) || current_user_can( 'manage_options' ); // Hent valgt leder-ID fra POST (på visning/validering), evt. fra eksisterende entry hvis tilgjengelig. $leader_id = isset( $_POST['input_3'] ) ? absint( $_POST['input_3'] ) : null; // Hvis vi viser et lagret oppslag (f.eks. via GravityView/entry edit), prøv å hente entry-verdi for felt 3: if ( ! $leader_id ) { if ( class_exists( 'GFFormsModel' ) && ( $lead = GFFormsModel::get_current_lead() ) ) { $leader_id = isset( $lead['3'] ) ? absint( $lead['3'] ) : null; } } $can_edit = $is_admin || ( $leader_id && (int) $leader_id === (int) $current_user_id ); // Merk feltene som "readonly" i markup (vi setter CSS-klasse; JS skrur av inputs). if ( ! $can_edit ) { foreach ( $form['fields'] as &$field ) { if ( in_array( (int) $field->id, $protected, true ) ) { $field->cssClass .= ' kbs-readonly-field'; } if ( in_array( (int) $field->id, $sections, true ) ) { $field->cssClass .= ' kbs-readonly-section'; } } // Legg til init-script som gjør alle inputs i disse feltene readonly/disabled add_filter( 'gform_register_init_scripts_21', function( $form ) use ( $protected, $sections ) { $ids = implode( ',', array_map( 'intval', array_merge( $protected, $sections ) ) ); $script = " (function() { var form = document.getElementById('gform_wrapper_{$form['id']}') || document.getElementById('gform_{$form['id']}'); if (!form) return; var protectIds = '{$ids}'.split(',').map(function(x){return parseInt(x,10);}); protectIds.forEach(function(fid){ var rows = form.querySelectorAll('.gfield.gfield_id_'+fid+', #field_{$form['id']}_'+fid); rows.forEach(function(row){ // Finn alle Inputs/Textareas/Selects i raden og lås dem row.querySelectorAll('input, textarea, select, button').forEach(function(el){ // Ikke lås submit-knapper if (el.type === 'submit' || el.classList.contains('gform_button')) return; // For checkbox/radio/select/listefelt, bruk disabled; for tekstfelt, bruk readonly: if (['checkbox','radio','select-one','select-multiple','button'].includes(el.type) || el.tagName==='SELECT') { el.setAttribute('disabled','disabled'); } else { el.setAttribute('readonly','readonly'); } }); // Visuelt hint row.classList.add('kbs-locked'); }); }); })();"; GFFormDisplay::add_init_script( $form['id'], 'kbs_lock', GFFormDisplay::ON_PAGE_RENDER, $script ); return $form; }, 10, 1 ); } return $form; } /** * Serverside vern: Hvis bruker IKKE har lov, nullstill endringer i beskyttede felt før innsending prosesseres. */ add_action( 'gform_pre_submission_21', function( $form ) { $protected = array( 40, 41, 37, 44 ); $current_user_id = get_current_user_id(); $is_admin = current_user_can( 'administrator' ) || current_user_can( 'manage_options' ); $leader_id = isset( $_POST['input_3'] ) ? absint( $_POST['input_3'] ) : null; $can_edit = $is_admin || ( $leader_id && (int) $leader_id === (int) $current_user_id ); if ( $can_edit ) { return; } // Dersom dette er en oppdatering av eksisterende entry, hent opprinnelige verdier og skriv dem tilbake. $entry = class_exists( 'GFFormsModel' ) ? GFFormsModel::get_current_lead() : null; foreach ( $protected as $fid ) { // List (40) kan ha flere inputs (kolonner/rader); vi fjerner alle POST-verdier for sikkerhets skyld. foreach ( $_POST as $key => $val ) { if ( preg_match( '/^input_' . $fid . '(\\D|$)/', $key ) ) { unset( $_POST[ $key ] ); } } // Sett tilbake opprinnelig verdi hvis vi har en entry (unngå at feltene blir blanket ut ved redigering). if ( $entry && isset( $entry[ (string) $fid ] ) ) { $_POST[ 'input_' . $fid ] = $entry[ (string) $fid ]; } } }, 10, 1 ); /** * Litt enkel styling (valgfritt): grå ut låste felter. */ add_action( 'wp_head', function() { echo ''; } ); /** * KBS App API Samlet */ /* Plugin Name: KBS App API Samlet Description: Alle endepunkter for Android-appen. V16 - Fix Class Not Found. Version: 16 Author: KBS */ // ============================================================================= // --- GOOGLE CLIENT (Flyttet til toppen for å hindre "Class not found") --- // ============================================================================= if (!class_exists('SimpleGoogleClient')) { class SimpleGoogleClient { private $key_file; private $key_data; public function __construct($path) { if (!file_exists($path)) throw new Exception("Finner ikke nøkkelfil: " . $path); $this->key_data = json_decode(file_get_contents($path), true); if (!$this->key_data) throw new Exception("Ugyldig JSON i nøkkelfil"); } public function getProjectId() { return $this->key_data['project_id'] ?? null; } public function getAccessToken($scopes = ['https://www.googleapis.com/auth/calendar']) { $header = base64_encode(json_encode(['alg' => 'RS256', 'typ' => 'JWT'])); $now = time(); $claim = base64_encode(json_encode([ 'iss' => $this->key_data['client_email'], 'scope' => implode(' ', $scopes), 'aud' => 'https://oauth2.googleapis.com/token', 'exp' => $now + 3600, 'iat' => $now ])); $sig_input = str_replace(['+', '/', '='], ['-', '_', ''], $header . "." . $claim); $pk = $this->key_data['private_key']; $signature = ''; if (!openssl_sign($sig_input, $signature, $pk, 'SHA256')) throw new Exception("Signering feilet"); $jwt = $sig_input . "." . str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature)); $res = wp_remote_post('https://oauth2.googleapis.com/token', ['body' => ['grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', 'assertion' => $jwt]]); if (is_wp_error($res)) throw new Exception($res->get_error_message()); $data = json_decode(wp_remote_retrieve_body($res), true); return $data['access_token'] ?? null; } } } // ============================================================================= // --- SIKKERHET (INTRANETT HARDENING) --- // ============================================================================= add_filter('rest_authentication_errors', function ($result) { if (!empty($result)) return $result; $route = untrailingslashit($GLOBALS['wp']->query_vars['rest_route'] ?? ''); if (strpos($route, '/kbs/v1/login') !== false) return $result; if (strpos($route, '/kbs/v1/lookup-id') !== false) return $result; if (!is_user_logged_in()) { return new WP_Error('rest_not_logged_in', 'Intranett: Krever innlogging.', ['status' => 401]); } return $result; }); add_action('pre_comment_on_post', function($comment_post_ID) { if (!is_user_logged_in()) { wp_die('Sikkerhetsvarsel: Du må være logget inn for å kommentere.'); } }); // ============================================================================= // --- KONFIGURASJON --- // ============================================================================= function kbs_get_calendar_config() { return [ 'felles' => [ 'name' => 'Felles', 'id' => 'c_23c125bd7c6d0366f16fbffb0442b19bb41a5baaaa3a5e9cf8ac9f8a0de23119@group.calendar.google.com', 'color' => '#0069B3' ], 'amu_hms' => [ 'name' => 'AMU/HMS/Miljø', 'id' => 'kbservice.no_o2bmp5f9f540vedveit51optfo@group.calendar.google.com', 'color' => '#2E7D32' ], 'administrasjonen' => [ 'name' => 'Administrasjonen', 'id' => 'c_19e4d6b3bfeff2ca7aeb44609abf1b7d930677c72ed4197d45737e62ed5230dd@group.calendar.google.com', 'color' => '#607D8B' ], 'service' => [ 'name' => 'Serviceavdelingen', 'id' => 'c_a2268eec031a87c8c2cd9abc5e5f7d8dc9655ea3c2800fe189a2e838a7b806f8@group.calendar.google.com', 'color' => '#E65100' ], 'automasjon' => [ 'name' => 'Automasjonsavdelingen', 'id' => 'c_273285bf75ff112f16f0ebc8cd91b36f3d4bc6239b26c85b2e10f4f62249db3d@group.calendar.google.com', 'color' => '#1B5E20' ], 'prosjekt' => [ 'name' => 'Prosjektavdelingen', 'id' => 'c_2c09ebaf1badc240f317bdd1343dea6caf17e6d9469bcee440d205a675ebfd31@group.calendar.google.com', 'color' => '#7B1FA2' ] ]; } function kbs_get_json_path() { $root_dir = dirname($_SERVER['DOCUMENT_ROOT']); // SJEKK AT DETTE FILNAVNET STEMMER: return $root_dir . '/httpd.private/intranet-glogin-452614-bec002e385d7.json'; } // ============================================================================= // --- API ROUTES --- // ============================================================================= add_action('rest_api_init', function () { // Auth & Brukere register_rest_route('kbs/v1', '/login', array('methods' => 'POST', 'callback' => 'kbs_login_endpoint', 'permission_callback' => '__return_true')); register_rest_route('kbs/v1', '/device/register', array('methods' => 'POST', 'callback' => 'kbs_register_device_endpoint', 'permission_callback' => 'kbs_check_auth')); register_rest_route('kbs/v1', '/users', array('methods' => 'GET', 'callback' => 'kbs_get_users_list_endpoint', 'permission_callback' => 'kbs_check_auth')); // Kalender register_rest_route('kbs/v1', '/calendar/events', array('methods' => 'GET', 'callback' => 'kbs_get_calendar_events_endpoint', 'permission_callback' => '__return_true')); register_rest_route('kbs/v1', '/calendar/create', array('methods' => 'POST', 'callback' => 'kbs_create_event_endpoint', 'permission_callback' => 'kbs_check_auth')); register_rest_route('kbs/v1', '/calendar/update', array('methods' => 'POST', 'callback' => 'kbs_update_event_endpoint', 'permission_callback' => 'kbs_check_auth')); register_rest_route('kbs/v1', '/calendar/delete', array('methods' => 'POST', 'callback' => 'kbs_delete_event_endpoint', 'permission_callback' => 'kbs_check_auth')); // Skjema & Håndbok register_rest_route('kbs/v1', '/forms', array('methods' => 'GET', 'callback' => 'kbs_get_active_forms_list', 'permission_callback' => 'kbs_check_auth')); register_rest_route('kbs/v1', '/forms/(?P\d+)', array('methods' => 'GET', 'callback' => 'kbs_get_single_form', 'permission_callback' => 'kbs_check_auth')); register_rest_route('kbs/v1', '/handbook', array('methods' => 'GET', 'callback' => 'kbs_get_handbook_items_from_app_page', 'permission_callback' => 'kbs_check_auth')); register_rest_route('kbs/v1', '/handbook/(?P\d+)', array('methods' => 'GET', 'callback' => 'kbs_get_handbook_page_content', 'permission_callback' => 'kbs_check_auth')); register_rest_route('kbs/v1', '/lookup-id', array('methods' => 'GET', 'callback' => 'kbs_lookup_url_to_id', 'permission_callback' => '__return_true')); // Oppgaver (Tasks) register_rest_route('kbs/v1', '/tasks', array('methods' => 'GET', 'callback' => 'kbs_get_tasks', 'permission_callback' => 'kbs_check_auth')); register_rest_route('kbs/v1', '/tasks/sync', array('methods' => 'POST', 'callback' => 'kbs_sync_tasks', 'permission_callback' => 'kbs_check_auth')); }); // ============================================================================= // --- HJELPEFUNKSJONER --- // ============================================================================= function kbs_check_auth($request) { if (is_user_logged_in()) return true; $cookie = $request->get_header('cookie'); if ($cookie && defined('LOGGED_IN_COOKIE')) { $cookies = []; foreach (explode(';', $cookie) as $pair) { $parts = explode('=', trim($pair), 2); if (count($parts) == 2) $cookies[$parts[0]] = $parts[1]; } if (isset($cookies[LOGGED_IN_COOKIE])) { $uid = wp_validate_auth_cookie($cookies[LOGGED_IN_COOKIE], 'logged_in'); if ($uid) { wp_set_current_user($uid); return true; } } } return false; } function kbs_user_can_access_calendar($slug, $mode = 'read') { $user = wp_get_current_user(); $roles = (array) $user->roles; if (in_array('administrator', $roles) || in_array('editor', $roles)) return true; switch ($slug) { case 'felles': return true; case 'amu_hms': return in_array('amuhmsmiljogruppa', $roles); case 'administrasjonen': return in_array('administrasjonen', $roles); case 'service': return in_array('serviceavdelingen', $roles); case 'automasjon': return in_array('automasjonsavdelingen', $roles); case 'prosjekt': return in_array('prosjektavdelingen', $roles); default: return false; } } function kbs_get_writeable_calendars_for_user() { $out = []; foreach (kbs_get_calendar_config() as $slug => $conf) { if (kbs_user_can_access_calendar($slug, 'write')) { $out[] = $conf['name']; } } return $out; } // ============================================================================= // --- PUSH VARSLING (FCM) --- // ============================================================================= function kbs_get_fcm_tokens_for_calendar($slug) { $args = ['meta_key' => 'android_fcm_token']; $users = get_users($args); $tokens = []; $current_user_id = get_current_user_id(); foreach ($users as $user) { if ($user->ID == $current_user_id) continue; $original_user = wp_get_current_user(); wp_set_current_user($user->ID); $has_access = kbs_user_can_access_calendar($slug, 'read'); wp_set_current_user($original_user->ID); if ($has_access) { $token = get_user_meta($user->ID, 'android_fcm_token', true); if ($token) $tokens[] = $token; } } return array_unique($tokens); } function kbs_get_all_fcm_tokens() { $args = ['meta_key' => 'android_fcm_token']; $users = get_users($args); $tokens = []; foreach ($users as $user) { $t = get_user_meta($user->ID, 'android_fcm_token', true); if ($t) $tokens[] = $t; } return array_unique($tokens); } function kbs_send_fcm_broadcast($title, $body, $data_payload = [], $tokens = null) { if ($tokens === null) $tokens = kbs_get_all_fcm_tokens(); if (empty($tokens)) return; try { $client = new SimpleGoogleClient(kbs_get_json_path()); $access_token = $client->getAccessToken(['https://www.googleapis.com/auth/calendar', 'https://www.googleapis.com/auth/firebase.messaging']); $project_id = $client->getProjectId(); if (!$project_id) { error_log("KBS Push: Fant ikke project_id"); return; } $url = "https://fcm.googleapis.com/v1/projects/{$project_id}/messages:send"; foreach ($tokens as $token) { $message = ['token' => $token]; if ($title && $body) { $message['notification'] = ['title' => $title, 'body' => $body]; } if (!empty($data_payload)) { $message['data'] = $data_payload; } $payload = ['message' => $message]; wp_remote_post($url, [ 'headers' => ['Authorization' => 'Bearer ' . $access_token, 'Content-Type' => 'application/json'], 'body' => json_encode($payload) ]); } } catch (Exception $e) { error_log("KBS Push Broadcast Error: " . $e->getMessage()); } } function kbs_send_fcm_notification($title, $body, $tokens) { kbs_send_fcm_broadcast($title, $body, ['force_refresh' => 'true', 'type' => 'calendar_update'], $tokens); } add_action('publish_post', 'kbs_notify_new_news', 10, 2); function kbs_notify_new_news($ID, $post) { if ($post->post_type !== 'post') return; kbs_send_fcm_broadcast( "Nyhet: " . $post->post_title, "Les siste nytt på Intranettet nå.", ['type' => 'news_update', 'force_refresh' => 'true'] ); } // ============================================================================= // --- ENDEPUNKTER --- // ============================================================================= function kbs_login_endpoint($request) { $params = $request->get_json_params(); $token = $params['token'] ?? ''; if (!$token) return new WP_Error('no_token', 'Mangler token', ['status'=>400]); $email = ''; // --- GOOGLE REVIEW BYPASS START --- // Hvis appen sender denne spesifikke koden, logger vi inn testbrukeren direkte if ($token === 'KBS_REVIEW_BYPASS_2026_SECRET') { $email = 'google-test@kbs.no'; // E-posten du lagde i trinn 1 } else { // Standard Google-verifisering for vanlige brukere $res = wp_remote_get('https://oauth2.googleapis.com/tokeninfo?id_token=' . $token); if (is_wp_error($res)) return new WP_Error('google_error', 'Google feil', ['status'=>500]); $body = json_decode(wp_remote_retrieve_body($res), true); if (($body['email_verified'] ?? '') !== 'true') return new WP_Error('invalid_token', 'Ugyldig token', ['status'=>401]); $email = $body['email']; } // --- GOOGLE REVIEW BYPASS END --- $user = get_user_by('email', $email); if (!$user) return new WP_Error('user_not_found', 'Ingen bruker funnet: ' . $email, ['status'=>401]); wp_set_current_user($user->ID); $cookie = wp_generate_auth_cookie($user->ID, time()+1209600, 'logged_in'); return new WP_REST_Response([ 'success' => true, 'full_cookie' => LOGGED_IN_COOKIE . '=' . $cookie, 'role' => reset($user->roles), 'user_id' => $user->ID, 'first_name' => $user->first_name, 'last_name' => $user->last_name, 'stilling' => get_user_meta($user->ID, 'stilling', true), 'mobiltelefon' => get_user_meta($user->ID, 'mobiltelefon', true), 'writeable_calendars' => kbs_get_writeable_calendars_for_user() ], 200); } function kbs_register_device_endpoint($request) { if (!is_user_logged_in()) kbs_check_auth($request); $user_id = get_current_user_id(); if (!$user_id) return new WP_Error('not_logged_in', 'Bruker ikke logget inn', ['status' => 401]); $params = $request->get_json_params(); $token = $params['fcm_token'] ?? ''; $platform = $params['platform'] ?? 'android'; if (empty($token)) return new WP_Error('missing_token', 'Mangler FCM token', ['status' => 400]); update_user_meta($user_id, 'android_fcm_token', sanitize_text_field($token)); update_user_meta($user_id, 'last_device_platform', sanitize_text_field($platform)); return new WP_REST_Response(['success' => true, 'message' => 'Enhet registrert'], 200); } function kbs_get_users_list_endpoint($request) { $users = get_users(['orderby' => 'display_name']); $list = []; foreach ($users as $user) { $list[] = [ 'id' => $user->ID, 'name' => $user->display_name, 'email' => $user->user_email, 'roles' => $user->roles ]; } return $list; } function kbs_get_calendar_events_endpoint($request) { if (!kbs_check_auth($request)) return []; $config = kbs_get_calendar_config(); $all_events = []; $client = null; $token = null; $current_user = wp_get_current_user(); $is_super_admin = in_array('administrator', (array)$current_user->roles); foreach ($config as $slug => $conf) { if (!kbs_user_can_access_calendar($slug, 'read')) continue; $cal_id = $conf['id']; $cal_display_name = $conf['name']; $cal_color = $conf['color'] ?? '#0069B3'; $cache_key = 'kbs_cal_' . md5($cal_id); $cached_events = get_transient($cache_key); if ($cached_events === false) { if ($client === null) { try { $client = new SimpleGoogleClient(kbs_get_json_path()); $token = $client->getAccessToken(['https://www.googleapis.com/auth/calendar']); } catch (Exception $e) { error_log('[KBS API] Auth feil: ' . $e->getMessage()); return []; } } $url = "https://www.googleapis.com/calendar/v3/calendars/" . urlencode($cal_id) . "/events?" . http_build_query([ 'singleEvents' => 'true', 'orderBy' => 'startTime', 'timeMin' => date('c', strtotime('-3 month')), 'maxResults' => 150 ]); $res = wp_remote_get($url, ['headers' => ['Authorization' => 'Bearer ' . $token]]); if (is_wp_error($res) || wp_remote_retrieve_response_code($res) >= 400) { error_log("[KBS API] Feil ved henting av kalender $slug: " . wp_remote_retrieve_response_code($res)); continue; } $data = json_decode(wp_remote_retrieve_body($res), true); $parsed_items = []; if (isset($data['items'])) { foreach ($data['items'] as $item) { $desc = $item['description'] ?? ''; $reminders = []; if (preg_match('/#varsel:([\d,]+)/i', $desc, $m)) { foreach(explode(',', $m[1]) as $p) $reminders[] = intval($p); } else { $reminders[] = 15; } $parsed_items[] = [ 'id' => $item['id'], 'title' => $item['summary'] ?? '(Uten tittel)', 'start_date' => $item['start']['dateTime'] ?? $item['start']['date'] ?? '', 'end_date' => $item['end']['dateTime'] ?? $item['end']['date'] ?? '', 'description' => $desc, 'location' => $item['location'] ?? '', 'reminders' => $reminders, 'calendar_name' => $cal_display_name, 'calendar_color' => $cal_color, 'calendar_slug' => $slug ]; } } set_transient($cache_key, $parsed_items, 900); $cached_events = $parsed_items; } foreach ($cached_events as $event) { $desc = $event['description']; $is_private = false; $participants = []; if (preg_match('/#deltakere:([^\s]+)/', $desc, $matches)) { $is_private = true; $participants = explode(',', strtolower($matches[1])); } if (!$is_private || $is_super_admin || in_array(strtolower($current_user->user_email), $participants)) { $all_events[] = $event; } } } return $all_events; } function kbs_handle_event_save($params, $is_update) { $cal_input = $params['calendar_type'] ?? 'felles'; $config = kbs_get_calendar_config(); $target_slug = ''; if (isset($config[$cal_input])) { $target_slug = $cal_input; } else { foreach ($config as $slug => $conf) { if ($conf['name'] === $cal_input) { $target_slug = $slug; break; } } } if (!$target_slug || !kbs_user_can_access_calendar($target_slug, 'write')) { return new WP_Error('forbidden', 'Ingen tilgang', ['status'=>403]); } $cal_id = $config[$target_slug]['id']; $title = sanitize_text_field($params['title']); $desc = sanitize_textarea_field($params['description'] ?? ''); $loc = sanitize_text_field($params['location'] ?? ''); $reminders = $params['reminders'] ?? []; if (!empty($reminders)) { $desc = preg_replace('/#varsel:[\d,]+/', '', $desc); $desc = trim($desc) . "\n\n#varsel:" . implode(',', $reminders); } $event_data = ['summary' => $title, 'description' => $desc, 'location' => $loc]; if (!empty($params['is_all_day'])) { $d_end = new DateTime($params['end_time']); if ($params['start_time'] == $params['end_time']) $d_end->modify('+1 day'); $event_data['start'] = ['date' => $params['start_time']]; $event_data['end'] = ['date' => $d_end->format('Y-m-d')]; } else { $event_data['start'] = ['dateTime' => $params['start_time'] . ':00', 'timeZone' => 'Europe/Oslo']; $event_data['end'] = ['dateTime' => $params['end_time'] . ':00', 'timeZone' => 'Europe/Oslo']; } if (!empty($params['recurrence']) && $params['recurrence'] !== 'NONE') { $event_data['recurrence'] = [$params['recurrence']]; } try { $client = new SimpleGoogleClient(kbs_get_json_path()); $token = $client->getAccessToken(['https://www.googleapis.com/auth/calendar']); $url = "https://www.googleapis.com/calendar/v3/calendars/" . urlencode($cal_id) . "/events"; $method = 'POST'; if ($is_update && !empty($params['id'])) { $url .= "/" . $params['id']; $method = 'PUT'; } $res = wp_remote_request($url, [ 'method' => $method, 'headers' => ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'], 'body' => json_encode($event_data) ]); if (is_wp_error($res) || wp_remote_retrieve_response_code($res) >= 300) throw new Exception("Google feil"); delete_transient('kbs_cal_' . md5($cal_id)); $tokens = kbs_get_fcm_tokens_for_calendar($target_slug); $action_text = $is_update ? "endret" : "opprettet"; kbs_send_fcm_notification( "Kalender oppdatert", "Hendelse i {$config[$target_slug]['name']} ble {$action_text}: {$title}", $tokens ); return new WP_REST_Response(['success' => true], 200); } catch (Exception $e) { return new WP_Error('google_error', $e->getMessage(), ['status'=>500]); } } function kbs_create_event_endpoint($request) { return kbs_handle_event_save($request->get_json_params(), false); } function kbs_update_event_endpoint($request) { return kbs_handle_event_save($request->get_json_params(), true); } function kbs_delete_event_endpoint($request) { $params = $request->get_json_params(); $cal_input = $params['calendar_type'] ?? 'felles'; $config = kbs_get_calendar_config(); $target_slug = ''; if (isset($config[$cal_input])) { $target_slug = $cal_input; } else { foreach ($config as $slug => $conf) { if ($conf['name'] === $cal_input) { $target_slug = $slug; break; } } } if (!$target_slug || !kbs_user_can_access_calendar($target_slug, 'write')) { return new WP_Error('forbidden', 'Ingen tilgang', ['status'=>403]); } try { $cal_id = $config[$target_slug]['id']; $client = new SimpleGoogleClient(kbs_get_json_path()); $token = $client->getAccessToken(['https://www.googleapis.com/auth/calendar']); $res = wp_remote_request("https://www.googleapis.com/calendar/v3/calendars/" . urlencode($cal_id) . "/events/" . $params['id'], [ 'method' => 'DELETE', 'headers' => ['Authorization' => 'Bearer ' . $token] ]); if (is_wp_error($res) || wp_remote_retrieve_response_code($res) >= 300) throw new Exception("Sletting feilet"); delete_transient('kbs_cal_' . md5($cal_id)); return new WP_REST_Response(['success' => true], 200); } catch (Exception $e) { return new WP_Error('error', $e->getMessage(), ['status'=>500]); } } // --- SKJEMA/HÅNDBOK --- function kbs_get_active_forms_list() { if (!class_exists('GFAPI')) return []; $forms = GFAPI::get_forms(); $out = []; foreach ($forms as $f) if (($f['is_active'] ?? false) && !($f['is_trash'] ?? false)) $out[] = array('id' => (int)$f['id'], 'title' => $f['title'], 'description' => $f['description'] ?? '', 'is_active' => true); return array_values($out); } function kbs_get_single_form($data) { if (!class_exists('GFAPI')) return new WP_Error('gf_missing', 'Mangler GF', array('status' => 500)); return GFAPI::get_form($data['id']) ?: new WP_Error('404', 'Ikke funnet', array('status' => 404)); } function kbs_get_handbook_items_from_app_page() { $p = get_page_by_path('app/interninstruks-hms'); if(!$p) return []; $c = get_children(['post_parent'=>$p->ID,'post_type'=>'page','post_status'=>'publish']); $o=[]; foreach($c as $i) $o[]=['id'=>$i->ID,'title'=>$i->post_title,'desc'=>$i->post_excerpt?:'Les mer...','icon_type'=>'doc','url'=>get_permalink($i->ID),'menu_order'=>$i->menu_order]; usort($o, function($a, $b) { return strnatcasecmp($a['title'], $b['title']); }); return array_values($o); } function kbs_get_handbook_page_content($d) { $p=get_post($d['id']); return $p?['id'=>$p->ID,'title'=>$p->post_title,'content'=>apply_filters('the_content',$p->post_content)]:[]; } function kbs_lookup_url_to_id($r) { $u=$r->get_param('url'); $id=url_to_postid($u); if($id)return['id'=>$id]; $s=basename(parse_url($u,PHP_URL_PATH)); global $wpdb; $id=$wpdb->get_var($wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE post_name=%s AND post_status='publish' LIMIT 1",$s)); return['id'=>(int)$id]; } // ============================================================================= // --- OPPGAVER (TASKS) LOGIKK --- // ============================================================================= /** * Hjelpefunksjon: Henter FCM-token for en spesifikk e-postadresse. */ function kbs_get_fcm_token_by_email($email) { $user = get_user_by('email', $email); if ($user) { return get_user_meta($user->ID, 'android_fcm_token', true); } return false; } /** * Endepunkt: Henter alle globale oppgaver. */ function kbs_get_tasks() { $tasks_json = get_option('kbs_global_tasks', '[]'); $decoded = json_decode($tasks_json); if ($decoded === null) $decoded = json_decode(stripslashes($tasks_json)); if ($decoded === null) $decoded = []; return new WP_REST_Response($decoded, 200); } /** * Endepunkt: Synkroniserer oppgaver og sender push-varsler ved endringer. */ function kbs_sync_tasks($request) { $new_tasks = $request->get_json_params(); if (!is_array($new_tasks)) { return new WP_Error('invalid_data', 'Data må være en array', array('status' => 400)); } // 1. Hent gamle data for sammenligning $old_tasks_json = get_option('kbs_global_tasks', '[]'); $old_tasks = json_decode($old_tasks_json, true) ?: []; // 2. Sikkerhet: Ikke slett alt hvis det skjer en feil i appen if (empty($new_tasks) && count($old_tasks) > 5) { return new WP_REST_Response(['success' => false, 'message' => 'Ignorerte tømming'], 200); } // 3. Lagre nye data update_option('kbs_global_tasks', json_encode($new_tasks, JSON_UNESCAPED_UNICODE)); // 4. Varsle om NYE oppgaver $old_ids = array_column($old_tasks, 'id'); $current_user = wp_get_current_user(); foreach ($new_tasks as $task) { // Sjekk om dette er en helt ny ID if (!in_array($task['id'], $old_ids)) { $title = $task['title']; $creator = $task['createdByName'] ?? 'En kollega'; $assignees = $task['assigneeStatus'] ?? []; foreach ($assignees as $email => $is_done) { // Ikke varsle meg selv if (strtolower($email) === strtolower($current_user->user_email)) continue; $token = kbs_get_fcm_token_by_email($email); if ($token) { kbs_send_fcm_broadcast( "Ny oppgave: $title", "Tildelt av $creator", ['type' => 'tasks_update', 'force_refresh' => 'true'], [$token] ); } } } } // 5. Trigger bakgrunnsoppdatering (Silent update) for alle andre enheter kbs_send_fcm_broadcast( null, null, ['type' => 'tasks_update', 'force_refresh' => 'true'] ); return new WP_REST_Response(array('success' => true), 200); } /** * Snippet for lightbox utenfor Beaver Builder */ add_action('wp_footer', 'kbs_clean_lightbox_fix', 9999); function kbs_clean_lightbox_fix() { ?>