repository = $repository ?? new CalendarConnectionsRepository(); } public function register(): void { add_action( 'admin_menu', array( $this, 'menu' ) ); add_action( 'abientot_staff_extension_panels', array( $this, 'panel' ) ); add_action( 'admin_post_abientot_calendar_disconnect_local', array( $this, 'handle' ) ); } public function menu(): void { if ( get_option( 'abientot_calendar_inbound_schema_version', '' ) !== '' ) { add_submenu_page( '', __( 'Retained external calendar protection', 'abientot' ), __( 'Retained external calendar protection', 'abientot' ), 'abientot_manage_settings', 'abientot-retained-calendars', array( $this, 'page' ) ); } } public function page(): void { if ( ! current_user_can( 'abientot_manage_settings' ) ) { wp_die( esc_html__( 'You do not have permission to manage settings.', 'abientot' ) ); } echo '

' . esc_html__( 'Retained external calendar protection', 'abientot' ) . '

'; try { $page = self::integer( \Abientot\Support\Request::view( 'calendar_staff_page' ) ?? 1 ); $ids = $this->repository->staff_ids( $page ); $staff_id = \Abientot\Support\Request::view( 'staff_id' ); $selected = null !== $staff_id ? self::integer( $staff_id ) : ( $ids[0] ?? 0 ); echo ''; if ( $selected ) { $this->panel( array( 'id' => $selected ) ); } foreach ( array( -1 => __( 'Previous', 'abientot' ), 1 => __( 'Next', 'abientot' ) ) as $delta => $label ) { if ( $page + $delta > 0 && ( $delta < 0 ? $page > 1 : count( $ids ) > 25 ) ) { echo '' . esc_html( $label ) . ' '; } } } catch ( \Throwable $e ) { echo '

' . esc_html__( 'Retained calendar data is unavailable. Existing protection remains blocked until its storage is repaired or an authorized disconnect succeeds.', 'abientot' ) . '

'; } echo '
'; } private static function integer( $value ): int { if ( ( ! is_int( $value ) && ! is_string( $value ) ) || ! preg_match( '/^[1-9][0-9]*$/D', (string) $value ) || false === ( $number = filter_var( $value, FILTER_VALIDATE_INT, array( 'options' => array( 'min_range' => 1, 'max_range' => PHP_INT_MAX - 1 ) ) ) ) ) { throw new \DomainException( 'calendar_retained_input' ); } return $number; } /** * Disconnects one retained calendar connection. The caller has verified the nonce and the * POST method; $input holds sanitized fields only. */ public function request( array $input ): array { try { if ( ! current_user_can( 'abientot_manage_settings' ) ) { throw new \DomainException( 'calendar_retained_permission' ); } if ( ( $input['risk_accepted'] ?? '' ) !== '1' ) { throw new \DomainException( 'calendar_retained_permission' ); } return $this->repository->disconnect( self::integer( $input['connection_id'] ?? null ), self::integer( $input['revision'] ?? null ), true ); } catch ( \Throwable $e ) { return array( 'error' => 'calendar_retained_unavailable' ); } } public function handle(): void { check_admin_referer( 'abientot_calendar_disconnect_local' ); if ( ! current_user_can( 'abientot_manage_settings' ) ) { wp_die( esc_html__( 'You do not have permission to manage settings.', 'abientot' ), '', array( 'response' => 403 ) ); } nocache_headers(); $result = \Abientot\Support\Request::is_post() ? $this->request( \Abientot\Support\Request::post_fields( 'abientot_calendar_disconnect_local', array( 'connection_id', 'revision', 'risk_accepted' ) ) ) : array( 'error' => 'calendar_retained_unavailable' ); if ( isset( $result['error'] ) ) { wp_die( esc_html__( 'Retained calendar protection could not be disconnected. Check permission, confirmation and current connection revision.', 'abientot' ), '', array( 'response' => 400, 'back_link' => true ) ); } wp_safe_redirect( admin_url( 'admin.php?page=abientot-staff&calendar_disconnected=1' ) ); exit; } public function panel( array $staff ): void { if ( ! current_user_can( 'abientot_manage_settings' ) ) { return; } try { $staffId = self::integer( $staff['id'] ?? null ); $page = self::integer( \Abientot\Support\Request::view( 'calendar_connection_page' ) ?? 1 ); if ( ! $this->repository->present() ) { return; } $rows = $this->repository->listing( $staffId, $page ); if ( ! $rows ) { return; } if ( \Abientot\Support\Request::page() !== 'abientot-retained-calendars' ) { echo '

' . esc_html__( 'View all retained calendar connections', 'abientot' ) . '

'; } $more = count( $rows ) > 25; $rows = array_slice( $rows, 0, 25 ); echo '

' . esc_html__( 'Retained external calendar protection', 'abientot' ) . '

' . esc_html__( 'Existing calendar protection remains active after changing edition. Pending, incomplete or older-than-15-minute coverage blocks new time changes. This local control cannot connect accounts or refresh provider data.', 'abientot' ) . '

'; if ( ! $rows ) { echo '

' . esc_html__( 'No retained calendar connections for this staff profile.', 'abientot' ) . '

'; } foreach ( $rows as $row ) { $enabled = (int) $row['enabled'] === 1; echo '

' . esc_html( ( $row['provider'] === 'google' ? 'Google' : 'Outlook' ) ) . '

' . esc_html__( 'Protection', 'abientot' ) . ': ' . esc_html( $enabled ? __( 'Enabled', 'abientot' ) : __( 'Disabled', 'abientot' ) ) . ' · ' . esc_html__( 'Timezone', 'abientot' ) . ': ' . esc_html( $row['timezone'] ) . '

' . esc_html__( 'Technical details', 'abientot' ) . '

' . esc_html__( 'Complete coverage (UTC)', 'abientot' ) . ': ' . esc_html( $row['coverage_start'] !== null ? $row['coverage_start'] . ' — ' . $row['coverage_end'] : __( 'No complete coverage', 'abientot' ) ) . '

' . esc_html__( 'Last successful sync (UTC)', 'abientot' ) . ': ' . esc_html( $row['last_success'] ?? __( 'Never', 'abientot' ) ) . '

'; if ( $row['error_code'] !== '' ) { echo '

' . esc_html__( 'Connection status code', 'abientot' ) . ': ' . esc_html( $row['error_code'] ) . '

'; } echo '
'; if ( $enabled ) { $success = $row['last_success'] !== null ? strtotime( $row['last_success'] . ' UTC' ) : false; if ( $row['error_code'] !== '' || $success === false || time() - $success > 900 || $success - time() > 60 || $row['coverage_start'] === null || (int) $row['sync_revision'] < 1 ) { echo '

' . esc_html__( 'Protection blocks new time changes because complete fresh coverage is unavailable. Restore a complete sync or explicitly disconnect after accepting the conflict risk.', 'abientot' ) . '

'; } } if ( $row['authority'] !== 'revoked' || $enabled ) { echo '
'; wp_nonce_field( 'abientot_calendar_disconnect_local' ); echo '

' . esc_html__( 'Disconnecting clears local credentials and busy protection. New bookings may conflict with this external calendar. It does not cancel existing bookings or revoke the provider account itself.', 'abientot' ) . '

'; } echo '
'; } echo '

'; foreach ( array( -1 => __( 'Previous', 'abientot' ), 1 => __( 'Next', 'abientot' ) ) as $delta => $label ) { if ( $page + $delta > 0 && ( $delta < 0 ? $page > 1 : $more ) ) { echo '' . esc_html( $label ) . ' '; } } echo '

'; } catch ( \Throwable $e ) { echo '

' . esc_html__( 'Retained calendar data is unavailable. Existing protection remains blocked until its storage is repaired or an authorized disconnect succeeds.', 'abientot' ) . '

'; } } }