validator = new Validator();
$this->sections = new SectionRepository();
$this->parcels = new ParcelRepository();
$this->meters = new MeterRepository();
$this->readings = new MeterReadingRepository();
$this->tenants = new TenantRepository();
$this->assignments = new AssignmentRepository();
$this->chat = new ChatRepository();
$this->costs = new CostRepository();
$this->work = new WorkRepository();
$this->parcel_service = new ParcelService();
global $wpdb;
$this->data_transfer = new DataTransfer( $wpdb );
}
/**
* Register WP hooks.
*
* @return void
*/
public function register_hooks() {
add_action( 'admin_menu', array( $this, 'register_menus' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
add_action( 'admin_init', array( $this, 'handle_requests' ) );
add_action( 'wp_ajax_kgvvm_fetch_chat_messages', array( $this, 'ajax_fetch_chat_messages' ) );
add_action( 'wp_ajax_kgvvm_send_chat_message', array( $this, 'ajax_send_chat_message' ) );
}
/**
* Register admin pages.
*
* @return void
*/
public function register_menus() {
$cap = 'manage_kleingarten';
if ( current_user_can( $cap ) ) {
add_menu_page(
__( 'Kleingartenverwaltung', KGVVM_TEXT_DOMAIN ),
__( 'Kleingarten', KGVVM_TEXT_DOMAIN ),
$cap,
'kgvvm-dashboard',
array( $this, 'render_dashboard' ),
'dashicons-admin-multisite',
24
);
add_submenu_page( 'kgvvm-dashboard', __( 'Dashboard', KGVVM_TEXT_DOMAIN ), __( 'Dashboard', KGVVM_TEXT_DOMAIN ), $cap, 'kgvvm-dashboard', array( $this, 'render_dashboard' ) );
add_submenu_page( 'kgvvm-dashboard', __( 'Meine Parzellen', KGVVM_TEXT_DOMAIN ), __( 'Meine Parzellen', KGVVM_TEXT_DOMAIN ), 'view_assigned_parcels', 'kgvvm-my-parcels', array( $this, 'render_my_parcels_page' ) );
add_submenu_page( 'kgvvm-dashboard', __( 'Vereinschat', KGVVM_TEXT_DOMAIN ), __( 'Vereinschat', KGVVM_TEXT_DOMAIN ), 'view_assigned_parcels', 'kgvvm-chat', array( $this, 'render_chat_page' ) );
add_submenu_page( 'kgvvm-dashboard', __( 'Sparten', KGVVM_TEXT_DOMAIN ), __( 'Sparten', KGVVM_TEXT_DOMAIN ), 'edit_sparten', 'kgvvm-sparten', array( $this, 'render_sections_page' ) );
add_submenu_page( 'kgvvm-dashboard', __( 'Parzellen', KGVVM_TEXT_DOMAIN ), __( 'Parzellen', KGVVM_TEXT_DOMAIN ), 'edit_parzellen', 'kgvvm-parzellen', array( $this, 'render_parcels_page' ) );
add_submenu_page( 'kgvvm-dashboard', __( 'Zähler', KGVVM_TEXT_DOMAIN ), __( 'Zähler', KGVVM_TEXT_DOMAIN ), 'edit_zaehler', 'kgvvm-zaehler', array( $this, 'render_meters_page' ) );
add_submenu_page( 'kgvvm-dashboard', __( 'Verbrauch', KGVVM_TEXT_DOMAIN ), __( 'Verbrauch', KGVVM_TEXT_DOMAIN ), $cap, 'kgvvm-consumption', array( $this, 'render_consumption_page' ) );
add_submenu_page( 'kgvvm-dashboard', __( 'Kosten', KGVVM_TEXT_DOMAIN ), __( 'Kosten', KGVVM_TEXT_DOMAIN ), $cap, 'kgvvm-costs', array( $this, 'render_costs_page' ) );
add_submenu_page( 'kgvvm-dashboard', __( 'Arbeitsstunden', KGVVM_TEXT_DOMAIN ), __( 'Arbeitsstunden', KGVVM_TEXT_DOMAIN ), $cap, 'kgvvm-arbeit', array( $this, 'render_work_page' ) );
add_submenu_page( 'kgvvm-dashboard', __( 'Pächter', KGVVM_TEXT_DOMAIN ), __( 'Pächter', KGVVM_TEXT_DOMAIN ), 'edit_paechter', 'kgvvm-paechter', array( $this, 'render_tenants_page' ) );
add_submenu_page( 'kgvvm-dashboard', __( 'Einstellungen', KGVVM_TEXT_DOMAIN ), __( 'Einstellungen', KGVVM_TEXT_DOMAIN ), Roles::SETTINGS_CAP, 'kgvvm-settings', array( $this, 'render_settings_page' ) );
}
if ( ! current_user_can( $cap ) && current_user_can( 'view_assigned_parcels' ) ) {
add_menu_page(
__( 'Meine Parzellen', KGVVM_TEXT_DOMAIN ),
__( 'Meine Parzellen', KGVVM_TEXT_DOMAIN ),
'view_assigned_parcels',
'kgvvm-my-parcels',
array( $this, 'render_my_parcels_page' ),
'dashicons-location-alt',
24
);
add_menu_page(
__( 'Vereinschat', KGVVM_TEXT_DOMAIN ),
__( 'Vereinschat', KGVVM_TEXT_DOMAIN ),
'view_assigned_parcels',
'kgvvm-chat',
array( $this, 'render_chat_page' ),
'dashicons-format-chat',
25
);
}
}
/**
* Load admin stylesheet on plugin pages.
*
* @param string $hook Current admin hook.
* @return void
*/
public function enqueue_assets( $hook ) {
if ( false === strpos( (string) $hook, 'kgvvm' ) ) {
return;
}
wp_enqueue_style( 'kgvvm-admin', KGVVM_PLUGIN_URL . 'assets/css/admin.css', array(), KGVVM_VERSION );
if ( false !== strpos( (string) $hook, 'kgvvm-chat' ) ) {
wp_enqueue_script( 'kgvvm-chat', KGVVM_PLUGIN_URL . 'assets/js/chat.js', array(), KGVVM_VERSION, true );
wp_localize_script(
'kgvvm-chat',
'kgvvmChatConfig',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'kgvvm_chat_nonce' ),
'refreshInterval' => 7000,
'i18n' => array(
'empty' => __( 'Noch keine Nachrichten in diesem Raum.', KGVVM_TEXT_DOMAIN ),
'loading' => __( 'Nachrichten werden geladen …', KGVVM_TEXT_DOMAIN ),
'sending' => __( 'Nachricht wird gesendet …', KGVVM_TEXT_DOMAIN ),
'fetchError' => __( 'Der Chat konnte gerade nicht aktualisiert werden.', KGVVM_TEXT_DOMAIN ),
'sendError' => __( 'Die Nachricht konnte nicht gesendet werden.', KGVVM_TEXT_DOMAIN ),
),
)
);
}
}
/**
* Handle save/delete requests.
*
* @return void
*/
public function handle_requests() {
if ( ! is_admin() || ( ! current_user_can( 'manage_kleingarten' ) && ! current_user_can( Roles::SETTINGS_CAP ) && ! current_user_can( 'submit_meter_readings' ) ) ) {
return;
}
$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
$view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : '';
$output = isset( $_GET['output'] ) ? sanitize_key( wp_unslash( $_GET['output'] ) ) : '';
if ( current_user_can( 'manage_kleingarten' ) && 'kgvvm-costs' === $page && 'statement' === $view && 'pdf' === $output ) {
$this->render_cost_statement_page();
return;
}
if ( 'POST' === strtoupper( isset( $_SERVER['REQUEST_METHOD'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : '' ) && ! empty( $_POST['kgvvm_action'] ) ) {
$this->handle_post_action();
}
if ( ! empty( $_GET['kgvvm_action'] ) ) {
$this->handle_get_action();
}
}
/**
* Dispatch POST actions.
*
* @return void
*/
private function handle_post_action() {
$action = sanitize_key( wp_unslash( $_POST['kgvvm_action'] ) );
switch ( $action ) {
case 'save_section':
$this->save_section();
break;
case 'save_parcel':
$this->save_parcel();
break;
case 'save_meter':
$this->save_meter();
break;
case 'swap_meter':
$this->swap_meter();
break;
case 'save_tenant':
$this->save_tenant();
break;
case 'save_cost_year':
$this->save_cost_year();
break;
case 'save_cost_prices':
$this->save_cost_prices();
break;
case 'save_cost':
$this->save_cost();
break;
case 'save_meter_reading':
$this->save_meter_reading();
break;
case 'save_settings':
$this->save_settings();
break;
case 'save_work_year_config':
$this->save_work_year_config();
break;
case 'save_work_job':
$this->save_work_job();
break;
case 'save_work_log':
$this->save_work_log();
break;
case 'import_plugin_data':
$this->import_plugin_data();
break;
case 'toggle_parcel_cost_assignment':
$this->toggle_parcel_cost_assignment();
break;
}
}
/**
* Dispatch delete actions.
*
* @return void
*/
private function handle_get_action() {
$action = sanitize_key( wp_unslash( $_GET['kgvvm_action'] ) );
$id = absint( isset( $_GET['id'] ) ? $_GET['id'] : 0 );
$nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
switch ( $action ) {
case 'delete_section':
$this->require_cap( 'edit_sparten' );
if ( ! wp_verify_nonce( $nonce, 'kgvvm_delete_section_' . $id ) ) {
$this->redirect_with_notice( 'kgvvm-sparten', 'error', __( 'Der Löschvorgang für die Sparte wurde aus Sicherheitsgründen abgebrochen.', KGVVM_TEXT_DOMAIN ) );
}
if ( $this->sections->is_in_use( $id ) ) {
$this->redirect_with_notice( 'kgvvm-sparten', 'error', __( 'Diese Sparte ist noch mit Parzellen oder Zählern verknüpft und kann nicht gelöscht werden.', KGVVM_TEXT_DOMAIN ) );
}
$this->sections->delete( $id );
$this->redirect_with_notice( 'kgvvm-sparten', 'success', __( 'Sparte wurde gelöscht.', KGVVM_TEXT_DOMAIN ) );
break;
case 'delete_parcel':
$this->require_cap( 'edit_parzellen' );
if ( ! wp_verify_nonce( $nonce, 'kgvvm_delete_parcel_' . $id ) ) {
$this->redirect_with_notice( 'kgvvm-parzellen', 'error', __( 'Der Löschvorgang für die Parzelle wurde aus Sicherheitsgründen abgebrochen.', KGVVM_TEXT_DOMAIN ) );
}
$this->parcel_service->delete( $id );
$this->redirect_with_notice( 'kgvvm-parzellen', 'success', __( 'Parzelle wurde gelöscht.', KGVVM_TEXT_DOMAIN ) );
break;
case 'delete_meter':
$this->require_cap( 'edit_zaehler' );
if ( ! wp_verify_nonce( $nonce, 'kgvvm_delete_meter_' . $id ) ) {
$this->redirect_with_notice( 'kgvvm-zaehler', 'error', __( 'Der Löschvorgang für den Zähler wurde aus Sicherheitsgründen abgebrochen.', KGVVM_TEXT_DOMAIN ) );
}
if ( $this->meters->is_assigned( $id ) ) {
$this->redirect_with_notice( 'kgvvm-zaehler', 'error', __( 'Ein zugeordneter Zähler kann nicht gelöscht werden. Bitte zuerst die Parzellenzuordnung entfernen.', KGVVM_TEXT_DOMAIN ) );
}
$this->meters->delete( $id );
$this->redirect_with_notice( 'kgvvm-zaehler', 'success', __( 'Zähler wurde gelöscht.', KGVVM_TEXT_DOMAIN ) );
break;
case 'delete_tenant':
$this->require_cap( 'edit_paechter' );
if ( ! wp_verify_nonce( $nonce, 'kgvvm_delete_tenant_' . $id ) ) {
$this->redirect_with_notice( 'kgvvm-paechter', 'error', __( 'Der Löschvorgang für den Pächter wurde aus Sicherheitsgründen abgebrochen.', KGVVM_TEXT_DOMAIN ) );
}
$this->assignments->purge_tenant( $id );
$this->tenants->delete( $id );
$this->redirect_with_notice( 'kgvvm-paechter', 'success', __( 'Pächter wurde gelöscht.', KGVVM_TEXT_DOMAIN ) );
break;
case 'delete_cost':
$this->require_cap( 'manage_kleingarten' );
$year = absint( isset( $_GET['year'] ) ? $_GET['year'] : current_time( 'Y' ) );
if ( ! wp_verify_nonce( $nonce, 'kgvvm_delete_cost_' . $id ) ) {
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Der Löschvorgang für den Kostenposten wurde aus Sicherheitsgründen abgebrochen.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
}
$cost = $this->costs->find( $id );
if ( ! $cost ) {
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Der gewünschte Kostenposten wurde nicht gefunden.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
}
$year = (int) $cost->entry_year;
$this->costs->delete_assignments_for_entry( $id );
$this->costs->delete( $id );
$this->redirect_with_notice( 'kgvvm-costs', 'success', __( 'Kostenposten wurde gelöscht.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
break;
case 'export_readings_csv':
$this->export_readings_csv();
break;
case 'export_plugin_data':
$this->require_cap( Roles::SETTINGS_CAP );
if ( ! wp_verify_nonce( $nonce, 'kgvvm_export_plugin_data' ) ) {
$this->redirect_with_notice( 'kgvvm-settings', 'error', __( 'Export wurde aus Sicherheitsgründen abgebrochen.', KGVVM_TEXT_DOMAIN ) );
}
$this->data_transfer->send_download();
break;
case 'delete_work_job':
$this->require_cap( 'manage_kleingarten' );
if ( ! wp_verify_nonce( $nonce, 'kgvvm_delete_work_job_' . $id ) ) {
$this->redirect_with_notice( 'kgvvm-arbeit', 'error', __( 'Der Löschvorgang wurde aus Sicherheitsgründen abgebrochen.', KGVVM_TEXT_DOMAIN ) );
}
$this->work->delete_job( $id );
$this->redirect_with_notice( 'kgvvm-arbeit', 'success', __( 'Arbeitsart wurde gelöscht.', KGVVM_TEXT_DOMAIN ), array( 'tab' => 'jobs' ) );
break;
case 'delete_work_log':
$this->require_cap( 'manage_kleingarten' );
$year = absint( isset( $_GET['year'] ) ? $_GET['year'] : current_time( 'Y' ) );
if ( ! wp_verify_nonce( $nonce, 'kgvvm_delete_work_log_' . $id ) ) {
$this->redirect_with_notice( 'kgvvm-arbeit', 'error', __( 'Der Löschvorgang wurde aus Sicherheitsgründen abgebrochen.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
}
$this->work->delete_log( $id );
$this->redirect_with_notice( 'kgvvm-arbeit', 'success', __( 'Arbeitseintrag wurde gelöscht.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
break;
}
}
/**
* Save section.
*
* @return void
*/
private function save_section() {
$this->require_cap( 'edit_sparten' );
check_admin_referer( 'kgvvm_save_section' );
$id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
$data = $this->validator->sanitize_section( $_POST );
$errors = $this->validator->validate_section( $data );
if ( $this->sections->name_exists( $data['name'], $id ) ) {
$errors->add( 'duplicate_name', __( 'Es existiert bereits eine Sparte mit diesem Namen.', KGVVM_TEXT_DOMAIN ) );
}
if ( $errors->has_errors() ) {
$this->redirect_with_notice( 'kgvvm-sparten', 'error', $errors->get_error_message(), array( 'view' => 'form', 'id' => $id ) );
}
$section_id = $this->sections->save( $data, $id );
$main_ids = isset( $_POST['main_meter_ids'] ) ? array_map( 'absint', (array) wp_unslash( $_POST['main_meter_ids'] ) ) : array();
$this->meters->sync_main_meters_for_section( $section_id, $main_ids );
$this->redirect_with_notice( 'kgvvm-sparten', 'success', __( 'Sparte wurde gespeichert.', KGVVM_TEXT_DOMAIN ) );
}
/**
* Save parcel.
*
* @return void
*/
private function save_parcel() {
$this->require_cap( 'edit_parzellen' );
check_admin_referer( 'kgvvm_save_parcel' );
$id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
$result = $this->parcel_service->save( $id, $_POST );
if ( is_wp_error( $result ) ) {
$this->redirect_with_notice( 'kgvvm-parzellen', 'error', $result->get_error_message(), array( 'view' => 'form', 'id' => $id ) );
}
$this->redirect_with_notice( 'kgvvm-parzellen', 'success', __( 'Parzelle wurde gespeichert.', KGVVM_TEXT_DOMAIN ) );
}
/**
* Save meter.
*
* @return void
*/
private function save_meter() {
$this->require_cap( 'edit_zaehler' );
check_admin_referer( 'kgvvm_save_meter' );
$id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
$data = $this->validator->sanitize_meter( $_POST );
$errors = $this->validator->validate_meter( $data );
if ( $this->meters->meter_number_exists( $data['meter_number'], $data['type'], $id ) ) {
$errors->add( 'duplicate_meter_number', __( 'Diese Zählernummer existiert für den gewählten Typ bereits.', KGVVM_TEXT_DOMAIN ) );
}
if ( $errors->has_errors() ) {
$this->redirect_with_notice( 'kgvvm-zaehler', 'error', $errors->get_error_message(), array( 'view' => 'form', 'id' => $id ) );
}
$this->meters->save( $data, $id );
$this->redirect_with_notice( 'kgvvm-zaehler', 'success', __( 'Zähler wurde gespeichert.', KGVVM_TEXT_DOMAIN ) );
}
/**
* Swap a meter and archive the old one.
*
* @return void
*/
private function swap_meter() {
$this->require_cap( 'edit_zaehler' );
check_admin_referer( 'kgvvm_swap_meter' );
$id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
$old_meter = $id ? $this->meters->find( $id ) : null;
if ( ! $old_meter ) {
$this->redirect_with_notice( 'kgvvm-zaehler', 'error', __( 'Der gewünschte Zähler wurde nicht gefunden.', KGVVM_TEXT_DOMAIN ) );
}
$swap_note = sanitize_textarea_field( wp_unslash( isset( $_POST['swap_note'] ) ? $_POST['swap_note'] : '' ) );
$new_data = $this->validator->sanitize_meter(
array(
'type' => $old_meter->type,
'meter_number' => isset( $_POST['new_meter_number'] ) ? $_POST['new_meter_number'] : '',
'section_id' => $old_meter->section_id,
'installed_at' => isset( $_POST['swap_date'] ) ? $_POST['swap_date'] : '',
'calibration_year' => isset( $_POST['new_calibration_year'] ) ? $_POST['new_calibration_year'] : '',
'is_main_meter' => ! empty( $old_meter->is_main_meter ) ? 1 : 0,
'is_active' => 1,
'note' => $swap_note,
)
);
$errors = $this->validator->validate_meter( $new_data );
if ( $this->meters->meter_number_exists( $new_data['meter_number'], $new_data['type'] ) ) {
$errors->add( 'duplicate_meter_number', __( 'Die neue Zählernummer existiert für diesen Typ bereits.', KGVVM_TEXT_DOMAIN ) );
}
if ( '' === $new_data['installed_at'] ) {
$errors->add( 'swap_date_required', __( 'Bitte ein Tauschdatum angeben.', KGVVM_TEXT_DOMAIN ) );
}
$parcel_id = (int) $old_meter->parcel_id;
$latest = $this->readings->get_latest_for_meter( $old_meter->id );
$old_final_raw = isset( $_POST['old_final_reading'] ) ? str_replace( ',', '.', wp_unslash( $_POST['old_final_reading'] ) ) : '';
$new_initial_raw = isset( $_POST['new_initial_reading'] ) ? str_replace( ',', '.', wp_unslash( $_POST['new_initial_reading'] ) ) : '';
$old_final_reading = '' === trim( (string) $old_final_raw ) ? '' : (float) $old_final_raw;
$new_initial_reading = '' === trim( (string) $new_initial_raw ) ? '' : (float) $new_initial_raw;
if ( '' !== trim( (string) $old_final_raw ) ) {
if ( ! is_numeric( $old_final_raw ) ) {
$errors->add( 'old_final_invalid', __( 'Bitte einen gültigen Endstand für den alten Zähler eingeben.', KGVVM_TEXT_DOMAIN ) );
} elseif ( (float) $old_final_reading < 0 ) {
$errors->add( 'old_final_negative', __( 'Der Endstand des alten Zählers darf nicht negativ sein.', KGVVM_TEXT_DOMAIN ) );
}
}
if ( $parcel_id > 0 ) {
if ( '' === trim( (string) $new_initial_raw ) ) {
$errors->add( 'new_initial_required', __( 'Bitte einen Startstand für den neuen Zähler eingeben.', KGVVM_TEXT_DOMAIN ) );
} elseif ( ! is_numeric( $new_initial_raw ) ) {
$errors->add( 'new_initial_invalid', __( 'Bitte einen gültigen Startstand für den neuen Zähler eingeben.', KGVVM_TEXT_DOMAIN ) );
} elseif ( (float) $new_initial_reading < 0 ) {
$errors->add( 'new_initial_negative', __( 'Der Startstand des neuen Zählers darf nicht negativ sein.', KGVVM_TEXT_DOMAIN ) );
}
}
if ( $latest && $new_data['installed_at'] && strtotime( $new_data['installed_at'] ) < strtotime( $latest->reading_date ) ) {
$errors->add( 'swap_date_older', __( 'Das Tauschdatum darf nicht vor der letzten gespeicherten Ablesung liegen.', KGVVM_TEXT_DOMAIN ) );
}
if ( $latest && '' !== $old_final_reading && (float) $old_final_reading < (float) $latest->reading_value ) {
$errors->add( 'swap_final_low', __( 'Der Endstand des alten Zählers darf nicht kleiner als die letzte Ablesung sein.', KGVVM_TEXT_DOMAIN ) );
}
if ( $errors->has_errors() ) {
$this->redirect_with_notice( 'kgvvm-zaehler', 'error', $errors->get_error_message(), array( 'id' => $id, 'open_swap' => 1 ) );
}
$swap_label = wp_date( 'd.m.Y', strtotime( $new_data['installed_at'] ) );
$new_data['note'] = trim(
implode(
"\n\n",
array_filter(
array(
sprintf( __( 'Ersatzzähler für %1$s seit %2$s.', KGVVM_TEXT_DOMAIN ), $old_meter->meter_number, $swap_label ),
$swap_note,
)
)
)
);
$new_meter_id = $this->meters->save( $new_data, 0 );
if ( ! $new_meter_id ) {
$this->redirect_with_notice( 'kgvvm-zaehler', 'error', __( 'Der neue Zähler konnte nicht angelegt werden.', KGVVM_TEXT_DOMAIN ), array( 'id' => $id, 'open_swap' => 1 ) );
}
if ( $parcel_id > 0 && '' !== $old_final_reading ) {
$this->readings->save(
array(
'meter_id' => $old_meter->id,
'parcel_id' => $parcel_id,
'reading_value' => $old_final_reading,
'reading_date' => $new_data['installed_at'],
'note' => __( 'Abschlussstand beim Zählerwechsel.', KGVVM_TEXT_DOMAIN ),
'submitted_by' => get_current_user_id(),
)
);
}
$this->meters->release_meter( $old_meter->id );
$this->meters->save(
array(
'type' => $old_meter->type,
'meter_number' => $old_meter->meter_number,
'section_id' => $old_meter->section_id,
'installed_at' => $old_meter->installed_at,
'calibration_year' => ! empty( $old_meter->calibration_year ) ? (int) $old_meter->calibration_year : null,
'is_active' => 0,
'note' => trim(
implode(
"\n\n",
array_filter(
array(
$old_meter->note,
sprintf( __( 'Zählerwechsel am %1$s. Neuer Zähler: %2$s.', KGVVM_TEXT_DOMAIN ), $swap_label, $new_data['meter_number'] ),
$swap_note ? sprintf( __( 'Tauschhinweis: %s', KGVVM_TEXT_DOMAIN ), $swap_note ) : '',
)
)
)
),
),
$old_meter->id
);
if ( $parcel_id > 0 ) {
$this->meters->assign_to_parcel( $new_meter_id, $parcel_id );
$this->readings->save(
array(
'meter_id' => $new_meter_id,
'parcel_id' => $parcel_id,
'reading_value' => $new_initial_reading,
'reading_date' => $new_data['installed_at'],
'note' => __( 'Startstand nach Zählerwechsel.', KGVVM_TEXT_DOMAIN ),
'submitted_by' => get_current_user_id(),
)
);
}
$this->redirect_with_notice( 'kgvvm-zaehler', 'success', __( 'Der Zähler wurde erfolgreich getauscht.', KGVVM_TEXT_DOMAIN ) );
}
/**
* Save tenant.
*
* @return void
*/
private function save_tenant() {
$this->require_cap( 'edit_paechter' );
check_admin_referer( 'kgvvm_save_tenant' );
$id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
$data = $this->validator->sanitize_tenant( $_POST );
$errors = $this->validator->validate_tenant( $data );
if ( $errors->has_errors() ) {
$this->redirect_with_notice( 'kgvvm-paechter', 'error', $errors->get_error_message(), array( 'view' => 'form', 'id' => $id ) );
}
$this->tenants->save( $data, $id );
$this->redirect_with_notice( 'kgvvm-paechter', 'success', __( 'Pächter wurde gespeichert.', KGVVM_TEXT_DOMAIN ) );
}
/**
* Save a meter reading from a member or administrator.
*
* @return void
*/
private function save_meter_reading() {
if ( ! current_user_can( 'submit_meter_readings' ) && ! current_user_can( 'manage_kleingarten' ) ) {
wp_die( esc_html__( 'Sie haben keine Berechtigung für diese Aktion.', KGVVM_TEXT_DOMAIN ) );
}
check_admin_referer( 'kgvvm_save_meter_reading' );
$return_page = isset( $_POST['return_page'] ) ? sanitize_key( wp_unslash( $_POST['return_page'] ) ) : 'kgvvm-my-parcels';
$return_view = isset( $_POST['return_view'] ) ? sanitize_key( wp_unslash( $_POST['return_view'] ) ) : '';
$return_id = absint( isset( $_POST['return_id'] ) ? $_POST['return_id'] : 0 );
$return_args = array();
if ( ! in_array( $return_page, array( 'kgvvm-my-parcels', 'kgvvm-sparten', 'kgvvm-zaehler', 'kgvvm-consumption' ), true ) ) {
$return_page = 'kgvvm-my-parcels';
}
if ( $return_view ) {
$return_args['view'] = $return_view;
}
if ( $return_id > 0 ) {
$return_args['id'] = $return_id;
}
$data = $this->validator->sanitize_meter_reading( $_POST );
$errors = $this->validator->validate_meter_reading( $data );
$meter = $this->meters->find( $data['meter_id'] );
if ( ! current_user_can( 'manage_kleingarten' ) && ! $this->assignments->user_has_parcel( get_current_user_id(), $data['parcel_id'] ) ) {
$errors->add( 'forbidden_parcel', __( 'Sie dürfen nur Ablesungen für Ihre zugewiesenen Parzellen erfassen.', KGVVM_TEXT_DOMAIN ) );
}
if ( ! $meter || (int) $meter->parcel_id !== (int) $data['parcel_id'] ) {
$errors->add( 'invalid_meter_link', __( 'Der ausgewählte Zähler ist dieser Parzelle nicht zugeordnet.', KGVVM_TEXT_DOMAIN ) );
}
$latest = $meter ? $this->readings->get_latest_for_meter( $data['meter_id'] ) : null;
if ( $latest && $data['reading_date'] && strtotime( $data['reading_date'] ) < strtotime( $latest->reading_date ) ) {
$errors->add( 'reading_date_older', __( 'Das Ablesedatum darf nicht vor der letzten gespeicherten Ablesung liegen.', KGVVM_TEXT_DOMAIN ) );
}
if ( $latest && (float) $data['reading_value'] < (float) $latest->reading_value ) {
$errors->add( 'reading_too_low', __( 'Der neue Zählerstand darf nicht kleiner als die letzte erfasste Ablesung sein.', KGVVM_TEXT_DOMAIN ) );
}
if ( $errors->has_errors() ) {
$this->redirect_with_notice( $return_page, 'error', $errors->get_error_message(), $return_args );
}
$data['submitted_by'] = get_current_user_id();
$data['is_self_reading'] = ( ! current_user_can( 'manage_kleingarten' ) && current_user_can( 'submit_meter_readings' ) ) ? 1 : 0;
$warning_message = $this->build_meter_jump_warning_message( $meter, $latest, (float) $data['reading_value'] );
$this->readings->save( $data );
if ( $warning_message ) {
$this->redirect_with_notice( $return_page, 'warning', sprintf( __( 'Die Ablesung wurde gespeichert. %s', KGVVM_TEXT_DOMAIN ), $warning_message ), $return_args );
}
$this->redirect_with_notice( $return_page, 'success', __( 'Die Ablesung wurde gespeichert.', KGVVM_TEXT_DOMAIN ), $return_args );
}
/**
* Save general settings.
*
* @return void
*/
private function save_settings() {
$this->require_cap( Roles::SETTINGS_CAP );
check_admin_referer( 'kgvvm_save_settings' );
$current_settings = wp_parse_args( get_option( 'kgvvm_settings', array() ), $this->get_settings_defaults() );
$water_threshold = isset( $_POST['water_usage_alert_threshold'] ) ? (float) str_replace( ',', '.', wp_unslash( $_POST['water_usage_alert_threshold'] ) ) : 25;
$power_threshold = isset( $_POST['power_usage_alert_threshold'] ) ? (float) str_replace( ',', '.', wp_unslash( $_POST['power_usage_alert_threshold'] ) ) : 1000;
$power_unit = isset( $_POST['power_unit'] ) ? sanitize_key( wp_unslash( $_POST['power_unit'] ) ) : 'kwh';
$power_unit = in_array( $power_unit, array( 'kwh', 'mwh' ), true ) ? $power_unit : 'kwh';
$pdf_club_name = isset( $_POST['pdf_club_name'] ) ? sanitize_text_field( wp_unslash( $_POST['pdf_club_name'] ) ) : $current_settings['pdf_club_name'];
$pdf_logo_url = isset( $_POST['pdf_logo_url'] ) ? esc_url_raw( trim( wp_unslash( $_POST['pdf_logo_url'] ) ) ) : '';
$pdf_contact = isset( $_POST['pdf_contact_block'] ) ? sanitize_textarea_field( wp_unslash( $_POST['pdf_contact_block'] ) ) : '';
$pdf_intro_text = isset( $_POST['pdf_intro_text'] ) ? sanitize_textarea_field( wp_unslash( $_POST['pdf_intro_text'] ) ) : '';
$pdf_footer_text = isset( $_POST['pdf_footer_text'] ) ? sanitize_textarea_field( wp_unslash( $_POST['pdf_footer_text'] ) ) : '';
update_option(
'kgvvm_settings',
array_merge(
$current_settings,
array(
'allow_multiple_member_parcels' => ! empty( $_POST['allow_multiple_member_parcels'] ) ? 1 : 0,
'water_usage_alert_threshold' => max( 0, $water_threshold ),
'power_usage_alert_threshold' => max( 0, $power_threshold ),
'power_unit' => $power_unit,
'pdf_club_name' => $pdf_club_name,
'pdf_logo_url' => $pdf_logo_url,
'pdf_contact_block' => $pdf_contact,
'pdf_intro_text' => $pdf_intro_text,
'pdf_footer_text' => $pdf_footer_text,
)
),
false
);
$this->redirect_with_notice( 'kgvvm-settings', 'success', __( 'Einstellungen wurden gespeichert.', KGVVM_TEXT_DOMAIN ) );
}
/**
* Handle plugin data import from an uploaded JSON file.
*
* @return void
*/
private function import_plugin_data() {
$this->require_cap( Roles::SETTINGS_CAP );
check_admin_referer( 'kgvvm_import_plugin_data' );
if ( empty( $_FILES['kgvvm_import_file']['tmp_name'] ) ) {
$this->redirect_with_notice( 'kgvvm-settings', 'error', __( 'Bitte eine JSON-Datei auswählen.', KGVVM_TEXT_DOMAIN ) );
}
$file = $_FILES['kgvvm_import_file'];
// Validate MIME type.
$finfo = new \finfo( FILEINFO_MIME_TYPE );
$mimetype = $finfo->file( $file['tmp_name'] );
if ( ! in_array( $mimetype, array( 'application/json', 'text/plain' ), true ) ) {
$this->redirect_with_notice( 'kgvvm-settings', 'error', __( 'Die hochgeladene Datei ist keine gültige JSON-Datei.', KGVVM_TEXT_DOMAIN ) );
}
$raw = file_get_contents( $file['tmp_name'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
if ( false === $raw ) {
$this->redirect_with_notice( 'kgvvm-settings', 'error', __( 'Die Datei konnte nicht gelesen werden.', KGVVM_TEXT_DOMAIN ) );
}
$payload = json_decode( $raw, true );
if ( ! is_array( $payload ) ) {
$this->redirect_with_notice( 'kgvvm-settings', 'error', __( 'Die JSON-Datei konnte nicht verarbeitet werden. Bitte eine gültige Exportdatei hochladen.', KGVVM_TEXT_DOMAIN ) );
}
$result = $this->data_transfer->import( $payload );
if ( ! empty( $result['errors'] ) ) {
$this->redirect_with_notice( 'kgvvm-settings', 'error', implode( ' | ', $result['errors'] ) );
}
$summary = array();
foreach ( $result['imported'] as $key => $count ) {
$summary[] = $key . ': ' . $count;
}
$this->redirect_with_notice(
'kgvvm-settings',
'success',
sprintf(
/* translators: %s: summary of imported rows per table */
__( 'Import erfolgreich. Importierte Datensätze – %s', KGVVM_TEXT_DOMAIN ),
implode( ', ', $summary )
)
);
}
/**
* Persist one year for the cost dropdown.
*
* @return void
*/
private function save_cost_year() {
$this->require_cap( 'manage_kleingarten' );
check_admin_referer( 'kgvvm_save_cost_year' );
$entry_year = $this->validator->sanitize_cost_year( $_POST );
$errors = $this->validator->validate_cost_year( $entry_year );
if ( $errors->has_errors() ) {
$this->redirect_with_notice( 'kgvvm-costs', 'error', $errors->get_error_message(), array( 'year' => current_time( 'Y' ) ) );
}
if ( ! $this->costs->save_year( $entry_year ) ) {
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Das Jahr konnte nicht gespeichert werden.', KGVVM_TEXT_DOMAIN ), array( 'year' => $entry_year ) );
}
$this->redirect_with_notice( 'kgvvm-costs', 'success', __( 'Das Jahr wurde zur Auswahlliste hinzugefügt.', KGVVM_TEXT_DOMAIN ), array( 'year' => $entry_year ) );
}
/**
* Save section-specific yearly prices.
*
* @return void
*/
private function save_cost_prices() {
$this->require_cap( 'manage_kleingarten' );
check_admin_referer( 'kgvvm_save_cost_prices' );
$data = $this->validator->sanitize_cost_year_settings( $_POST );
$errors = $this->validator->validate_cost_year_settings( $data );
if ( $errors->has_errors() ) {
$this->redirect_with_notice( 'kgvvm-costs', 'error', $errors->get_error_message(), array( 'year' => $data['entry_year'] ) );
}
if ( ! $this->costs->save_section_prices( $data ) ) {
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Die Preise für diese Sparte konnten nicht gespeichert werden.', KGVVM_TEXT_DOMAIN ), array( 'year' => $data['entry_year'] ) );
}
$this->redirect_with_notice( 'kgvvm-costs', 'success', __( 'Die Preise pro Sparte wurden gespeichert.', KGVVM_TEXT_DOMAIN ), array( 'year' => $data['entry_year'] ) );
}
/**
* Save one annual cost entry.
*
* @return void
*/
private function save_cost() {
$this->require_cap( 'manage_kleingarten' );
check_admin_referer( 'kgvvm_save_cost' );
$id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
$data = $this->validator->sanitize_cost_entry( $_POST );
$errors = $this->validator->validate_cost_entry( $data );
if ( $errors->has_errors() ) {
$this->redirect_with_notice( 'kgvvm-costs', 'error', $errors->get_error_message(), array( 'year' => $data['entry_year'], 'id' => $id ) );
}
$active_parcels = array_values(
array_filter(
$this->parcels->search(),
static function( $parcel ) {
return isset( $parcel->status ) && 'inactive' !== $parcel->status;
}
)
);
$member_count = count( $this->assignments->get_member_users() );
$multiplier = 'member' === $data['distribution_type'] ? $member_count : count( $active_parcels );
$data['total_cost'] = (float) $data['unit_amount'] * max( 0, $multiplier );
$saved = $this->costs->save( $data, $id );
if ( ! $saved ) {
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Der Kostenposten konnte nicht gespeichert werden.', KGVVM_TEXT_DOMAIN ), array( 'year' => $data['entry_year'], 'id' => $id ) );
}
$this->redirect_with_notice( 'kgvvm-costs', 'success', __( 'Kostenposten wurde gespeichert.', KGVVM_TEXT_DOMAIN ), array( 'year' => $data['entry_year'] ) );
}
/**
* Add or remove a cost entry assignment for a specific parcel.
*
* @return void
*/
private function toggle_parcel_cost_assignment() {
$this->require_cap( 'manage_kleingarten' );
check_admin_referer( 'kgvvm_toggle_parcel_cost' );
$parcel_id = absint( isset( $_POST['parcel_id'] ) ? $_POST['parcel_id'] : 0 );
$cost_entry_id = absint( isset( $_POST['cost_entry_id'] ) ? $_POST['cost_entry_id'] : 0 );
$mode = isset( $_POST['assignment_mode'] ) ? sanitize_key( wp_unslash( $_POST['assignment_mode'] ) ) : '';
$year = absint( isset( $_POST['year'] ) ? $_POST['year'] : 0 );
if ( $parcel_id < 1 || $cost_entry_id < 1 || ! in_array( $mode, array( 'add', 'remove' ), true ) ) {
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Ungültige Anfrage.', KGVVM_TEXT_DOMAIN ), array( 'view' => 'statement', 'statement_type' => 'parcel', 'subject_id' => $parcel_id, 'year' => $year ) );
}
if ( 'add' === $mode ) {
$this->costs->assign_to_parcel( $parcel_id, $cost_entry_id );
} else {
$this->costs->unassign_from_parcel( $parcel_id, $cost_entry_id );
}
wp_safe_redirect( $this->admin_url( 'kgvvm-costs', array( 'view' => 'statement', 'statement_type' => 'parcel', 'subject_id' => $parcel_id, 'year' => $year ) ) );
exit;
}
/**
* Render dashboard.
*
* @return void
*/
public function render_dashboard() {
$this->require_cap( 'manage_kleingarten' );
$sections_total = count( $this->sections->search() );
$parcels_total = count( $this->parcels->search() );
$meters_free = count( $this->meters->search( array( 'assignment' => 'free' ) ) );
$meters_used = count( $this->meters->search( array( 'assignment' => 'assigned' ) ) );
$tenants_total = count( $this->tenants->search() );
$all_members = $this->assignments->get_member_users();
$members_total = count( $all_members );
// Arbeitsstunden laufendes Jahr
$work_year = (int) current_time( 'Y' );
$work_config = $this->work->get_year_config( $work_year );
$work_summary = $this->work->get_member_summary( $work_year, $all_members );
$members_ok = 0;
$members_open = 0;
$total_missing_h = 0.0;
$total_surcharge = 0.0;
foreach ( $work_summary as $ws ) {
if ( $ws->missing_hours > 0 ) {
$members_open++;
$total_missing_h += $ws->missing_hours;
$total_surcharge += $ws->surcharge;
} else {
$members_ok++;
}
}
?>
require_cap( 'manage_kleingarten' );
$section_id = isset( $_GET['section_id'] ) ? absint( $_GET['section_id'] ) : 0;
$date_from = isset( $_GET['date_from'] ) ? sanitize_text_field( wp_unslash( $_GET['date_from'] ) ) : '';
$date_to = isset( $_GET['date_to'] ) ? sanitize_text_field( wp_unslash( $_GET['date_to'] ) ) : '';
$order = isset( $_GET['order'] ) ? strtoupper( sanitize_key( wp_unslash( $_GET['order'] ) ) ) : 'DESC';
$order = 'ASC' === $order ? 'ASC' : 'DESC';
$sections = $this->sections->all_for_options();
if ( $date_from && $date_to && strtotime( $date_from ) > strtotime( $date_to ) ) {
$tmp = $date_from;
$date_from = $date_to;
$date_to = $tmp;
}
$rows = $this->readings->get_consumption_report( $section_id, $date_from, $date_to, $order );
$rate_lookup = $this->build_consumption_rate_lookup( $rows );
$water_total = 0.0;
$power_total = 0.0;
$water_cost_total = 0.0;
$power_cost_total = 0.0;
$consumed_count = 0;
$section_totals = array();
foreach ( $rows as $row ) {
$row->unit_price = $this->get_consumption_unit_price( $row, $rate_lookup );
$row->calculated_cost = $this->calculate_consumption_cost( $row->consumption, $row->type, $row->unit_price );
if ( null === $row->consumption ) {
continue;
}
$consumed_count++;
if ( 'power' === $row->type ) {
$power_total += (float) $row->consumption;
if ( null !== $row->calculated_cost ) {
$power_cost_total += (float) $row->calculated_cost;
}
} else {
$water_total += (float) $row->consumption;
if ( null !== $row->calculated_cost ) {
$water_cost_total += (float) $row->calculated_cost;
}
}
$section_key = $row->section_id ? (int) $row->section_id : 0;
if ( ! isset( $section_totals[ $section_key ] ) ) {
$section_totals[ $section_key ] = array(
'name' => $row->section_name ? $row->section_name : __( 'Ohne Sparte', KGVVM_TEXT_DOMAIN ),
'water' => 0.0,
'power' => 0.0,
'water_cost' => 0.0,
'power_cost' => 0.0,
);
}
$section_totals[ $section_key ][ $row->type ] += (float) $row->consumption;
if ( null !== $row->calculated_cost ) {
$section_totals[ $section_key ][ 'power' === $row->type ? 'power_cost' : 'water_cost' ] += (float) $row->calculated_cost;
}
}
?>
render_notice(); ?>
format_meter_value_with_unit( $water_total, 'water' ) ); ?>
format_meter_value_with_unit( $power_total, 'power' ) ); ?>
format_currency( $water_cost_total ) ); ?>
format_currency( $power_cost_total ) ); ?>
|
|
|
|
|
|
format_meter_value_with_unit( $section_total['water'], 'water' ) ); ?> |
0 ? $this->format_currency( $section_total['water_cost'] ) : '—' ); ?> |
format_meter_value_with_unit( $section_total['power'], 'power' ) ); ?> |
0 ? $this->format_currency( $section_total['power_cost'] ) : '—' ); ?> |
|
|
|
|
|
|
|
|
|
|
| reading_date ) ) ); ?> |
section_name ? $row->section_name : '—' ); ?> |
parcel_label ? $row->parcel_label : '—' ); ?> |
meter_type_label( $row->type ) . ' – ' . $row->meter_number ); ?> |
previous_value ? $this->format_meter_value_with_unit( $row->previous_value, $row->type ) : '—' ); ?> |
format_meter_value_with_unit( $row->reading_value, $row->type ) ); ?> |
consumption ? $this->format_meter_value_with_unit( $row->consumption, $row->type ) : '—' ); ?> |
unit_price ? $this->format_price_per_unit( $row->unit_price, 'power' === $row->type ? 'kWh' : 'm³' ) : '—' ); ?> |
calculated_cost ? $this->format_currency( $row->calculated_cost ) : '—' ); ?> |
require_cap( 'manage_kleingarten' );
$view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : 'list';
if ( 'statement' === $view ) {
$this->render_cost_statement_page();
return;
}
$selected_year = isset( $_GET['year'] ) ? absint( $_GET['year'] ) : (int) current_time( 'Y' );
$selected_year = $selected_year > 0 ? $selected_year : (int) current_time( 'Y' );
$search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
$edit_id = absint( isset( $_GET['id'] ) ? $_GET['id'] : 0 );
$edit_rate_section_id = absint( isset( $_GET['edit_rate_section_id'] ) ? $_GET['edit_rate_section_id'] : 0 );
$cost = $edit_id ? $this->costs->find( $edit_id ) : null;
if ( $cost && ! empty( $cost->entry_year ) ) {
$selected_year = (int) $cost->entry_year;
}
$years = $this->costs->get_years( $selected_year );
$sections = $this->sections->all_for_options( true );
$section_rates = $this->costs->get_section_prices( $selected_year );
$edit_rate = null;
foreach ( $section_rates as $section_rate ) {
if ( (int) $section_rate->section_id === $edit_rate_section_id ) {
$edit_rate = $section_rate;
break;
}
}
$rows = $this->costs->search(
array(
'year' => $selected_year,
's' => $search,
'orderby' => isset( $_GET['orderby'] ) ? sanitize_key( wp_unslash( $_GET['orderby'] ) ) : 'name',
'order' => isset( $_GET['order'] ) ? sanitize_key( wp_unslash( $_GET['order'] ) ) : 'ASC',
)
);
$entry_count = count( $rows );
$parcel_rows = array_values(
array_filter(
$this->parcels->search(),
static function( $parcel ) {
return isset( $parcel->status ) && 'inactive' !== $parcel->status;
}
)
);
$tenant_rows = $this->tenants->search( array( 'status' => 'active' ) );
$member_rows = $this->assignments->get_member_users();
$parcel_count = count( $parcel_rows );
$member_count = count( $member_rows );
$parcel_share = 0.0;
$member_share = 0.0;
$year_total = 0.0;
foreach ( $rows as $row ) {
$row->distribution_type = $this->get_cost_distribution_type( $row );
$row->unit_amount = $this->get_cost_unit_amount( $row, $parcel_count, $member_count );
$row->calculated_total_cost = $this->get_cost_total_amount( $row, $parcel_count, $member_count );
$year_total += (float) $row->calculated_total_cost;
if ( 'member' === $row->distribution_type ) {
$member_share += (float) $row->unit_amount;
} else {
$parcel_share += (float) $row->unit_amount;
}
}
if ( $cost ) {
$cost->distribution_type = $this->get_cost_distribution_type( $cost );
$cost->unit_amount = $this->get_cost_unit_amount( $cost, $parcel_count, $member_count );
$cost->calculated_total_cost = $this->get_cost_total_amount( $cost, $parcel_count, $member_count );
}
?>
$selected_year ) ) ); ?>' class='page-title-action'>
render_notice(); ?>
format_currency( $year_total ) ); ?>
0 ? $this->format_currency( $parcel_share ) : '—' ); ?>
0 ? $this->format_currency( $member_share ) : '—' ); ?>
|
|
|
|
|
| section_name ? $rate->section_name : '—' ); ?> |
power_price_per_kwh ? $this->format_price_per_unit( $rate->power_price_per_kwh, 'kWh' ) : '—' ); ?> |
water_price_per_m3 ? $this->format_price_per_unit( $rate->water_price_per_m3, 'm³' ) : '—' ); ?> |
updated_at ) ? wp_date( 'd.m.Y H:i', strtotime( $rate->updated_at ) ) : '—' ); ?> |
$selected_year, 'edit_rate_section_id' => $rate->section_id ) ) ); ?>'> |
|
|
|
|
|
| label ); ?> |
section_name ? $parcel->section_name : '—' ); ?> |
parcel_status_label( $parcel->status ) ); ?> |
tenant_count ) ? number_format_i18n( (int) $parcel->tenant_count, 0 ) : '—' ); ?> |
format_currency( $parcel_share ) ); ?> |
|
|
|
| display_name ); ?> |
user_email ? $member->user_email : '—' ); ?> |
0 ? $this->format_currency( $member_share ) : '—' ); ?> |
get_settings_defaults() );
$year = isset( $_GET['year'] ) ? absint( $_GET['year'] ) : (int) current_time( 'Y' );
$statement_type = isset( $_GET['statement_type'] ) ? sanitize_key( wp_unslash( $_GET['statement_type'] ) ) : 'parcel';
$output = isset( $_GET['output'] ) ? sanitize_key( wp_unslash( $_GET['output'] ) ) : 'html';
$subject_id = absint( isset( $_GET['subject_id'] ) ? $_GET['subject_id'] : 0 );
$date_from = sprintf( '%04d-01-01', $year );
$date_to = sprintf( '%04d-12-31', $year );
$all_rows = $this->readings->get_consumption_report( 0, $date_from, $date_to, 'ASC' );
$active_parcels = array_values(
array_filter(
$this->parcels->search(),
static function( $parcel ) {
return isset( $parcel->status ) && 'inactive' !== $parcel->status;
}
)
);
$active_members = $this->assignments->get_member_users();
$cost_entries = $this->costs->search( array( 'year' => $year, 'orderby' => 'name', 'order' => 'ASC' ) );
$subject_label = '';
$subject_meta = array();
$parcel_ids = array();
$subject_parcel_count = 0;
$subject_member_count = 0;
if ( 'tenant' === $statement_type ) {
$tenant = $this->tenants->find( $subject_id );
if ( ! $tenant ) {
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Der gewünschte Pächter wurde nicht gefunden.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
}
$tenant_parcels = $this->assignments->get_parcels_for_tenant( $subject_id );
$tenant_members = $this->assignments->get_members_for_tenant( $subject_id );
$tenant_member_names = array_values( array_unique( array_filter( wp_list_pluck( $tenant_members, 'display_name' ) ) ) );
$parcel_ids = wp_list_pluck( $tenant_parcels, 'id' );
$subject_parcel_count = count( $tenant_parcels );
$subject_member_count = count( array_unique( array_filter( array_map( 'intval', wp_list_pluck( $tenant_members, 'ID' ) ) ) ) );
$subject_label = sprintf( __( 'Jahresabrechnung Pächter %1$s – %2$s', KGVVM_TEXT_DOMAIN ), $tenant->last_name . ', ' . $tenant->first_name, $year );
$subject_meta[] = array(
'label' => __( 'Mitglied/Pächter', KGVVM_TEXT_DOMAIN ),
'value' => empty( $tenant_member_names ) ? '—' : implode( ', ', $tenant_member_names ),
);
$subject_meta[] = array(
'label' => __( 'Pächter', KGVVM_TEXT_DOMAIN ),
'value' => $tenant->last_name . ', ' . $tenant->first_name,
);
$subject_meta[] = array(
'label' => __( 'Parzellen', KGVVM_TEXT_DOMAIN ),
'value' => empty( $tenant_parcels ) ? '—' : implode( ', ', wp_list_pluck( $tenant_parcels, 'label' ) ),
);
} else {
$parcel = $this->parcels->find( $subject_id );
if ( ! $parcel ) {
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Die gewünschte Parzelle wurde nicht gefunden.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
}
$section = $parcel->section_id ? $this->sections->find( $parcel->section_id ) : null;
$parcel_tenants = $this->assignments->get_tenants_for_parcel( $subject_id );
$parcel_members = $this->assignments->get_members_for_parcel( $subject_id );
$parcel_member_names = array_values( array_unique( array_filter( wp_list_pluck( $parcel_members, 'display_name' ) ) ) );
$parcel_ids = array( $subject_id );
$subject_parcel_count = 1;
$subject_member_count = count( $parcel_members );
$subject_label = sprintf( __( 'Jahresabrechnung Parzelle %1$s – %2$s', KGVVM_TEXT_DOMAIN ), $parcel->label, $year );
$subject_meta[] = array(
'label' => __( 'Parzelle', KGVVM_TEXT_DOMAIN ),
'value' => $parcel->label,
);
$subject_meta[] = array(
'label' => __( 'Sparte', KGVVM_TEXT_DOMAIN ),
'value' => $section ? $section->name : '—',
);
$subject_meta[] = array(
'label' => __( 'Mitglied/Pächter', KGVVM_TEXT_DOMAIN ),
'value' => empty( $parcel_member_names ) ? '—' : implode( ', ', $parcel_member_names ),
);
$subject_meta[] = array(
'label' => __( 'Pächter', KGVVM_TEXT_DOMAIN ),
'value' => empty( $parcel_tenants ) ? '—' : implode( ', ', array_map( array( $this, 'format_tenant_full_name' ), $parcel_tenants ) ),
);
}
$statement_rows = array();
foreach ( $all_rows as $row ) {
if ( in_array( (int) $row->parcel_id, array_map( 'intval', $parcel_ids ), true ) ) {
$statement_rows[] = $row;
}
}
$rate_lookup = $this->build_consumption_rate_lookup( $statement_rows );
$water_total = 0.0;
$power_total = 0.0;
$water_cost_total = 0.0;
$power_cost_total = 0.0;
$utility_rows = array();
foreach ( $statement_rows as $row ) {
$row->unit_price = $this->get_consumption_unit_price( $row, $rate_lookup );
$row->calculated_cost = $this->calculate_consumption_cost( $row->consumption, $row->type, $row->unit_price );
if ( null === $row->consumption ) {
continue;
}
$utility_rows[] = $row;
if ( 'power' === $row->type ) {
$power_total += (float) $row->consumption;
if ( null !== $row->calculated_cost ) {
$power_cost_total += (float) $row->calculated_cost;
}
} else {
$water_total += (float) $row->consumption;
if ( null !== $row->calculated_cost ) {
$water_cost_total += (float) $row->calculated_cost;
}
}
}
$fixed_items = array();
$fixed_total = 0.0;
// Load parcel-specific cost assignments so entries can either apply to
// all parcels (mandatory) or only to explicitly assigned parcels (manual).
$entries_with_assignments = $this->costs->get_entry_ids_with_assignments( $year );
$subject_assigned_ids = array();
foreach ( array_map( 'intval', $parcel_ids ) as $parcel_id ) {
$subject_assigned_ids = array_merge( $subject_assigned_ids, $this->costs->get_assigned_entry_ids( $parcel_id ) );
}
$subject_assigned_ids = array_values( array_unique( array_map( 'intval', $subject_assigned_ids ) ) );
foreach ( $cost_entries as $entry ) {
$entry_id = (int) $entry->id;
$has_assignments = in_array( $entry_id, $entries_with_assignments, true );
$is_assigned = in_array( $entry_id, $subject_assigned_ids, true );
$is_mandatory = ! isset( $entry->is_mandatory ) || (bool) $entry->is_mandatory;
if ( ! $is_mandatory && ! $is_assigned ) {
continue;
}
if ( $is_mandatory && $has_assignments && ! $is_assigned ) {
continue;
}
$distribution_type = $this->get_cost_distribution_type( $entry );
$unit_amount = $this->get_cost_unit_amount( $entry, count( $active_parcels ), count( $active_members ) );
$subject_units = 'member' === $distribution_type ? $subject_member_count : $subject_parcel_count;
$share = $unit_amount * max( 0, $subject_units );
$total_amount = $this->get_cost_total_amount( $entry, count( $active_parcels ), count( $active_members ) );
$fixed_total += $share;
$fixed_items[] = array(
'name' => $entry->name,
'distribution_label' => $this->get_cost_distribution_label( $distribution_type ),
'unit_amount' => $unit_amount,
'units' => $subject_units,
'total' => $total_amount,
'share' => $share,
'note' => $entry->note,
);
}
$utility_total = $water_cost_total + $power_cost_total;
$grand_total = $fixed_total + $utility_total;
if ( 'pdf' === $output ) {
$this->render_cost_statement_pdf(
array(
'year' => $year,
'statement_type' => $statement_type,
'subject_id' => $subject_id,
'subject_label' => $subject_label,
'subject_meta' => $subject_meta,
'fixed_items' => $fixed_items,
'utility_rows' => $utility_rows,
'fixed_total' => $fixed_total,
'water_cost_total' => $water_cost_total,
'power_cost_total' => $power_cost_total,
'grand_total' => $grand_total,
'settings' => $settings,
)
);
return;
}
?>
' alt='' style='max-width:120px; height:auto;' />
|
|
format_currency( $fixed_total ) ); ?>
format_currency( $water_cost_total ) ); ?>
format_currency( $power_cost_total ) ); ?>
format_currency( $grand_total ) ); ?>
|
|
format_currency( $item['unit_amount'] ) ) ); ?>
' . esc_html( $item['note'] ) . '' : ''; ?>
|
format_currency( $item['share'] ) ); ?> |
build_statement_meter_detail_table( $this->get_statement_rows_by_meter_type( $utility_rows, 'water' ), 'water' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
build_statement_meter_detail_table( $this->get_statement_rows_by_meter_type( $utility_rows, 'power' ), 'power' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
get_settings_defaults() ) : $this->get_settings_defaults();
$document_author = ! empty( $settings['pdf_club_name'] ) ? $settings['pdf_club_name'] : 'KGV Vereinsverwaltung';
if ( function_exists( 'nocache_headers' ) ) {
nocache_headers();
}
while ( ob_get_level() > 0 ) {
ob_end_clean();
}
if ( ! class_exists( '\\TCPDF' ) && file_exists( $tcpdf_path ) ) {
require_once $tcpdf_path;
}
if ( ! class_exists( '\\TCPDF' ) ) {
wp_die( esc_html__( 'TCPDF konnte nicht geladen werden. Bitte die Bibliothek prüfen.', KGVVM_TEXT_DOMAIN ) );
}
$pdf = new \TCPDF( PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false );
$pdf->SetCreator( $document_author );
$pdf->SetAuthor( $document_author );
$pdf->SetTitle( $statement['subject_label'] );
$pdf->SetMargins( 12, 12, 12 );
$pdf->SetAutoPageBreak( true, 12 );
$pdf->setPrintHeader( false );
$pdf->setPrintFooter( false );
$pdf->AddPage();
$pdf->SetFont( 'dejavusans', '', 10 );
$pdf->writeHTML( $this->build_cost_statement_pdf_html( $statement ), true, false, true, false, '' );
$filename = sanitize_file_name( sprintf( 'jahresabrechnung-%s-%s-%s.pdf', $statement['year'], $statement['statement_type'], $statement['subject_id'] ) );
$pdf->Output( $filename, 'I' );
exit;
}
/**
* Build the HTML markup for the PDF statement.
*
* @param array $statement Statement payload.
* @return string
*/
private function build_cost_statement_pdf_html( $statement ) {
$settings = isset( $statement['settings'] ) && is_array( $statement['settings'] ) ? wp_parse_args( $statement['settings'], $this->get_settings_defaults() ) : $this->get_settings_defaults();
ob_start();
?>
|
|
|
|
|
format_currency( $statement['fixed_total'] ) ); ?> |
|
format_currency( $statement['water_cost_total'] ) ); ?> |
|
format_currency( $statement['power_cost_total'] ) ); ?> |
|
format_currency( $statement['grand_total'] ) ); ?> |
|
|
| format_currency( $item['unit_amount'] ) ) ); ?> |
format_currency( $item['share'] ) ); ?> |
build_statement_meter_detail_table( $this->get_statement_rows_by_meter_type( $statement['utility_rows'], 'water' ), 'water', true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
build_statement_meter_detail_table( $this->get_statement_rows_by_meter_type( $statement['utility_rows'], 'power' ), 'power', true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
require_cap( 'edit_sparten' );
$view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : 'list';
if ( 'form' === $view ) {
$this->render_section_form();
return;
}
$rows = $this->sections->search( $_GET );
?>
sections->find( $id ) : null;
$selected_main_meters = $id ? $this->meters->get_main_for_section( $id ) : array();
$main_meter_options = $id ? $this->meters->get_available_main_for_section( $id ) : array();
$selected_main_meter_ids = $id ? wp_list_pluck( $selected_main_meters, 'id' ) : array();
$today = current_time( 'Y-m-d' );
?>
render_notice(); ?>
readings->get_latest_for_meter( $main_meter->id );
$recent_main = $this->readings->get_all_for_meter( $main_meter->id, 5 );
$monthly_summary = $this->readings->get_monthly_summary_for_meter( $main_meter->id );
$step = 'water' === $main_meter->type ? '0.01' : '1';
?>
require_cap( 'edit_parzellen' );
$view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : 'list';
if ( 'form' === $view ) {
$this->render_parcel_form();
return;
}
$paged = max( 1, absint( isset( $_GET['paged'] ) ? $_GET['paged'] : 1 ) );
$per_page = 50;
$total_items = $this->parcels->count_filtered( $_GET );
$total_pages = max( 1, (int) ceil( $total_items / $per_page ) );
$rows = $this->parcels->search( array_merge( $_GET, array( 'limit' => $per_page, 'paged' => $paged ) ) );
$sections = $this->sections->all_for_options();
?>
parcels->find( $id ) : null;
$sections = $this->sections->all_for_options( true );
$selected_section = $parcel ? absint( $parcel->section_id ) : 0;
$water_selected = $parcel ? $this->meters->get_assigned_to_parcel( $id, 'water' ) : null;
$power_selected = $parcel ? $this->meters->get_assigned_to_parcel( $id, 'power' ) : null;
$water_meters = $this->meters->get_free_by_type( 'water', $selected_section, $id );
$power_meters = $this->meters->get_free_by_type( 'power', $selected_section, $id );
$member_users = $this->assignments->get_member_users();
$member_ids = $id ? $this->assignments->get_member_ids_for_parcel( $id ) : array();
$tenant_ids = $id ? $this->assignments->get_tenant_ids_for_parcel( $id ) : array();
$tenants = $this->tenants->all_active();
?>
require_cap( 'edit_zaehler' );
$view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : 'list';
if ( 'form' === $view ) {
$this->render_meter_form();
return;
}
if ( 'swap' === $view ) {
$this->render_meter_swap_form();
return;
}
$rows = $this->meters->search( $_GET );
$sections = $this->sections->all_for_options();
?>
'form' ) ) ); ?>' class='page-title-action'>
render_notice(); ?>
| '> |
'> |
|
|
'> |
'> |
|
|
|
readings->get_latest_for_meter( $row->id ); ?>
| meter_number ); ?> |
meter_type_label( $row->type ) ); ?> |
section_name ? $row->section_name : '—' ); ?> |
parcel_label ? $row->parcel_label : ( ! empty( $row->is_main_meter ) ? __( 'Hauptzähler der Sparte', KGVVM_TEXT_DOMAIN ) : __( 'frei', KGVVM_TEXT_DOMAIN ) ) ); ?> |
installed_at ? wp_date( 'd.m.Y', strtotime( $row->installed_at ) ) : '—' ); ?> |
calibration_year ) ? (string) $row->calibration_year : '—' ); ?> |
status_badge( (int) $row->is_active === 1 ? ( ! empty( $row->parcel_id ) ? __( 'Zugeordnet', KGVVM_TEXT_DOMAIN ) : ( ! empty( $row->is_main_meter ) ? __( 'Hauptzähler', KGVVM_TEXT_DOMAIN ) : __( 'Frei', KGVVM_TEXT_DOMAIN ) ) ) : __( 'Inaktiv', KGVVM_TEXT_DOMAIN ), (int) $row->is_active === 1 ? ( ! empty( $row->parcel_id ) ? 'orange' : ( ! empty( $row->is_main_meter ) ? 'blue' : 'green' ) ) : 'gray' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> |
'form', 'id' => $row->id ) ) ); ?>'>
is_active === 1 ) : ?>
|
|
'delete_meter', 'id' => $row->id ) ), 'kgvvm_delete_meter_' . $row->id ) ); ?>' onclick='return confirm("");'>
|
render_meter_swap_modal(); ?>
meters->find( $id ) : null;
$sections = $this->sections->all_for_options();
$history = $id ? $this->readings->get_all_for_meter( $id, 50 ) : array();
?>
render_notice(); ?>
|
|
|
|
|
| reading_date ) ) ); ?> |
parcel_label ? $entry->parcel_label : '—' ); ?> |
format_meter_value_with_unit( $entry->reading_value, $meter->type ) ); ?> |
reading_flag_badge( $entry ) ); ?> |
note ? $entry->note : '—' ); ?> |
meters->find( $id ) : null;
$parcel = $meter && ! empty( $meter->parcel_id ) ? $this->parcels->find( (int) $meter->parcel_id ) : null;
$latest = $meter ? $this->readings->get_latest_for_meter( $meter->id ) : null;
$today = current_time( 'Y-m-d' );
$unit = $meter ? $this->meter_unit_label( $meter->type ) : '';
if ( ! $meter ) {
$this->redirect_with_notice( 'kgvvm-zaehler', 'error', __( 'Der gewünschte Zähler wurde nicht gefunden.', KGVVM_TEXT_DOMAIN ) );
}
?>
render_notice(); ?>
meter_number ); ?>
(meter_type_label( $meter->type ) ); ?>)
label : ' – ' . __( 'aktuell keiner Parzelle zugeordnet', KGVVM_TEXT_DOMAIN ) ); ?>
format_meter_value( $latest->reading_value, $meter->type ), $unit, wp_date( 'd.m.Y', strtotime( $latest->reading_date ) ) ) ); ?>
require_cap( 'edit_paechter' );
$view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : 'list';
if ( 'form' === $view ) {
$this->render_tenant_form();
return;
}
$rows = $this->tenants->search( $_GET );
?>
tenants->find( $id ) : null;
$tenant_members = $id ? $this->assignments->get_members_for_tenant( $id ) : array();
?>
require_cap( 'view_assigned_parcels' );
$user_id = get_current_user_id();
$parcels = $this->assignments->get_parcels_for_user( $user_id );
?>
render_notice(); ?>
meters->get_assigned_to_parcel( $parcel->id, 'water' );
$power_meter = $this->meters->get_assigned_to_parcel( $parcel->id, 'power' );
$water_latest = $water_meter ? $this->readings->get_latest_for_meter( $water_meter->id ) : null;
$power_latest = $power_meter ? $this->readings->get_latest_for_meter( $power_meter->id ) : null;
$recent = $this->readings->get_recent_for_parcel( $parcel->id, 8 );
?>
section_name ? $parcel->section_name : '—' ); ?>
area ? esc_html( number_format_i18n( (float) $parcel->area, 2 ) . ' m²' ) : '—'; ?>
annual_rent ) && null !== $parcel->annual_rent ? esc_html( $this->format_currency( $parcel->annual_rent ) ) : '—'; ?>
parcel_status_label( $parcel->status ) ); ?>
note ) ) : ?>
note ); ?>
array( 'meter' => $water_meter, 'latest' => $water_latest, 'label' => __( 'Wasserzähler', KGVVM_TEXT_DOMAIN ) ),
'power' => array( 'meter' => $power_meter, 'latest' => $power_latest, 'label' => __( 'Stromzähler', KGVVM_TEXT_DOMAIN ) ),
) as $meter_config ) : ?>
|
|
|
|
|
| reading_date ) ) ); ?> |
meter_type_label( $reading->type ) ); ?> |
meter_number ); ?> |
format_meter_value_with_unit( $reading->reading_value, $reading->type ) ); ?> |
reading_flag_badge( $reading ) ); ?> |
require_chat_access();
$rooms = $this->get_chat_rooms();
$requested = isset( $_GET['room'] ) ? sanitize_key( wp_unslash( $_GET['room'] ) ) : '';
$room_keys = array_keys( $rooms );
$current_room = ( $requested && isset( $rooms[ $requested ] ) ) ? $requested : reset( $room_keys );
$messages = $this->normalize_chat_messages( $this->chat->get_recent_messages( $current_room, 60 ) );
$last_message = ! empty( $messages ) ? end( $messages ) : null;
if ( ! empty( $messages ) ) {
reset( $messages );
}
?>
require_cap( Roles::SETTINGS_CAP );
$settings = wp_parse_args( get_option( 'kgvvm_settings', array() ), $this->get_settings_defaults() );
?>
require_chat_access();
check_ajax_referer( 'kgvvm_chat_nonce', 'nonce' );
$room_key = isset( $_POST['room'] ) ? sanitize_key( wp_unslash( $_POST['room'] ) ) : 'general';
$after_id = absint( isset( $_POST['after_id'] ) ? wp_unslash( $_POST['after_id'] ) : 0 );
if ( ! $this->is_chat_room_allowed( $room_key ) ) {
wp_send_json_error( array( 'message' => __( 'Sie dürfen diesen Chatraum nicht öffnen.', KGVVM_TEXT_DOMAIN ) ), 403 );
}
wp_send_json_success(
array(
'messages' => $this->normalize_chat_messages( $this->chat->get_recent_messages( $room_key, $after_id > 0 ? 100 : 60, $after_id ) ),
)
);
}
/**
* Handle AJAX submission for one new chat message.
*
* @return void
*/
public function ajax_send_chat_message() {
$this->require_chat_access();
check_ajax_referer( 'kgvvm_chat_nonce', 'nonce' );
$room_key = isset( $_POST['room'] ) ? sanitize_key( wp_unslash( $_POST['room'] ) ) : 'general';
$message = isset( $_POST['message'] ) ? sanitize_textarea_field( wp_unslash( $_POST['message'] ) ) : '';
$message = trim( substr( $message, 0, 1000 ) );
if ( ! $this->is_chat_room_allowed( $room_key ) ) {
wp_send_json_error( array( 'message' => __( 'Sie dürfen in diesem Chatraum nicht schreiben.', KGVVM_TEXT_DOMAIN ) ), 403 );
}
if ( '' === $message ) {
wp_send_json_error( array( 'message' => __( 'Bitte zuerst eine Nachricht eingeben.', KGVVM_TEXT_DOMAIN ) ), 400 );
}
$created = $this->chat->save_message( $room_key, get_current_user_id(), $message );
if ( ! $created ) {
wp_send_json_error( array( 'message' => __( 'Die Nachricht konnte nicht gespeichert werden.', KGVVM_TEXT_DOMAIN ) ), 500 );
}
wp_send_json_success(
array(
'messages' => $this->normalize_chat_messages( array( $created ) ),
)
);
}
/**
* Return plugin settings defaults.
*
* @return array
*/
private function get_settings_defaults() {
return array(
'allow_multiple_member_parcels' => 1,
'water_usage_alert_threshold' => 25,
'power_usage_alert_threshold' => 1000,
'power_unit' => 'kwh',
'pdf_club_name' => get_bloginfo( 'name' ),
'pdf_logo_url' => '',
'pdf_contact_block' => '',
'pdf_intro_text' => __( 'Diese Jahresabrechnung wurde automatisch mit der KGV Vereinsverwaltung erstellt.', KGVVM_TEXT_DOMAIN ),
'pdf_footer_text' => __( 'Bitte prüfen Sie die Angaben und melden Sie Rückfragen an den Vorstand.', KGVVM_TEXT_DOMAIN ),
);
}
/**
* Export one parcel's reading history as CSV.
*
* @return void
*/
private function export_readings_csv() {
$this->require_cap( 'view_assigned_parcels' );
$parcel_id = absint( isset( $_GET['parcel_id'] ) ? $_GET['parcel_id'] : 0 );
$nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
if ( ! $parcel_id || ! wp_verify_nonce( $nonce, 'kgvvm_export_readings_' . $parcel_id ) ) {
$this->redirect_with_notice( 'kgvvm-my-parcels', 'error', __( 'Der CSV-Export wurde aus Sicherheitsgründen abgebrochen.', KGVVM_TEXT_DOMAIN ) );
}
if ( ! current_user_can( 'manage_kleingarten' ) && ! $this->assignments->user_has_parcel( get_current_user_id(), $parcel_id ) ) {
wp_die( esc_html__( 'Sie dürfen nur die Ablesungen Ihrer eigenen Parzellen exportieren.', KGVVM_TEXT_DOMAIN ) );
}
$parcel = $this->parcels->find( $parcel_id );
if ( ! $parcel ) {
$this->redirect_with_notice( 'kgvvm-my-parcels', 'error', __( 'Die gewünschte Parzelle wurde nicht gefunden.', KGVVM_TEXT_DOMAIN ) );
}
$rows = $this->readings->get_all_for_parcel( $parcel_id );
nocache_headers();
header( 'Content-Type: text/csv; charset=utf-8' );
header( 'Content-Disposition: attachment; filename=ablesungen-' . sanitize_file_name( $parcel->label ) . '-' . gmdate( 'Ymd-His' ) . '.csv' );
$output = fopen( 'php://output', 'w' );
if ( false === $output ) {
exit;
}
fwrite( $output, "\xEF\xBB\xBF" );
fputcsv( $output, array( 'Parzelle', 'Datum', 'Typ', 'Zählernummer', 'Zählerstand', 'Einheit', 'Flag', 'Notiz' ), ';' );
foreach ( $rows as $row ) {
fputcsv(
$output,
array(
$parcel->label,
wp_date( 'd.m.Y', strtotime( $row->reading_date ) ),
$this->meter_type_label( $row->type ),
$row->meter_number,
$this->format_meter_value( $row->reading_value, $row->type ),
$this->meter_unit_label( $row->type ),
! empty( $row->is_self_reading ) ? __( 'Selbstablesung', KGVVM_TEXT_DOMAIN ) : '',
$row->note,
),
';'
);
}
fclose( $output );
exit;
}
/**
* Ensure the current user may access the chat.
*
* @return void
*/
private function require_chat_access() {
if ( ! current_user_can( 'view_assigned_parcels' ) && ! current_user_can( 'manage_kleingarten' ) ) {
wp_die( esc_html__( 'Sie haben keine Berechtigung für den Vereinschat.', KGVVM_TEXT_DOMAIN ) );
}
}
/**
* List all chat rooms visible for the current user.
*
* @return array
*/
private function get_chat_rooms() {
$rooms = array(
'general' => array(
'label' => __( 'Vereinschat', KGVVM_TEXT_DOMAIN ),
'description' => __( 'Offener Austausch für alle Mitglieder, Vorstand und Verwaltung.', KGVVM_TEXT_DOMAIN ),
),
);
$is_management = current_user_can( Roles::SETTINGS_CAP ) || current_user_can( 'manage_kleingarten' );
if ( $is_management ) {
$rooms['board'] = array(
'label' => __( 'Vorstand', KGVVM_TEXT_DOMAIN ),
'description' => __( 'Interner Raum für Vorstand und Verwaltung.', KGVVM_TEXT_DOMAIN ),
);
}
$sections = array();
if ( $is_management ) {
$sections = $this->sections->all_for_options();
} else {
$user_parcels = $this->assignments->get_parcels_for_user( get_current_user_id() );
$seen_sections = array();
foreach ( (array) $user_parcels as $parcel ) {
$section_id = ! empty( $parcel->section_id ) ? (int) $parcel->section_id : 0;
if ( $section_id > 0 && ! isset( $seen_sections[ $section_id ] ) ) {
$section = $this->sections->find( $section_id );
if ( $section ) {
$sections[] = $section;
}
$seen_sections[ $section_id ] = true;
}
}
}
foreach ( (array) $sections as $section ) {
$section_id = ! empty( $section->id ) ? (int) $section->id : 0;
$section_name = ! empty( $section->name ) ? $section->name : '';
if ( $section_id < 1 ) {
continue;
}
$section_room_key = 'section-' . $section_id;
$rooms[ $section_room_key ] = array(
'label' => sprintf( __( 'Sparte: %s', KGVVM_TEXT_DOMAIN ), $section_name ? $section_name : $section_id ),
'description' => sprintf( __( 'Austausch für Mitglieder der Sparte %s.', KGVVM_TEXT_DOMAIN ), $section_name ? $section_name : $section_id ),
);
}
return $rooms;
}
/**
* Check whether a chat room is available for the current user.
*
* @param string $room_key Room key.
* @return bool
*/
private function is_chat_room_allowed( $room_key ) {
$rooms = $this->get_chat_rooms();
return isset( $rooms[ sanitize_key( $room_key ) ] );
}
/**
* Normalize chat messages for HTML and AJAX responses.
*
* @param array $messages Raw database rows.
* @return array
*/
private function normalize_chat_messages( $messages ) {
$current_user_id = get_current_user_id();
$normalized = array();
foreach ( (array) $messages as $message ) {
$normalized[] = array(
'id' => isset( $message->id ) ? (int) $message->id : 0,
'user_id' => isset( $message->user_id ) ? (int) $message->user_id : 0,
'user' => ! empty( $message->display_name ) ? (string) $message->display_name : __( 'Mitglied', KGVVM_TEXT_DOMAIN ),
'role' => $this->get_chat_role_label( isset( $message->user_id ) ? (int) $message->user_id : 0 ),
'message' => isset( $message->message ) ? (string) $message->message : '',
'time' => ! empty( $message->created_at ) ? wp_date( 'd.m.Y H:i', strtotime( $message->created_at ) ) : '',
'is_own' => isset( $message->user_id ) && (int) $message->user_id === (int) $current_user_id,
);
}
return $normalized;
}
/**
* Resolve a readable sender label.
*
* @param int $user_id Sender ID.
* @return string
*/
private function get_chat_role_label( $user_id ) {
$user_id = absint( $user_id );
if ( $user_id > 0 && ( user_can( $user_id, Roles::SETTINGS_CAP ) || user_can( $user_id, 'manage_kleingarten' ) ) ) {
return __( 'Vorstand / Verwaltung', KGVVM_TEXT_DOMAIN );
}
return __( 'Mitglied', KGVVM_TEXT_DOMAIN );
}
/**
* Return a visible badge for self-readings.
*
* @param object $reading Reading row.
* @return string
*/
private function reading_flag_badge( $reading ) {
if ( empty( $reading ) || empty( $reading->is_self_reading ) ) {
return '—';
}
return $this->status_badge( __( 'Selbstablesung', KGVVM_TEXT_DOMAIN ), 'blue' );
}
/**
* Build a plausibility warning for unusual jumps.
*
* @param object $meter Meter object.
* @param object|null $latest Latest previous reading.
* @param float $new_reading New reading value.
* @return string
*/
private function build_meter_jump_warning_message( $meter, $latest, $new_reading ) {
if ( ! $meter || ! $latest ) {
return '';
}
$settings = wp_parse_args( get_option( 'kgvvm_settings', array() ), $this->get_settings_defaults() );
$threshold = 'water' === $meter->type ? (float) $settings['water_usage_alert_threshold'] : (float) $settings['power_usage_alert_threshold'];
if ( $threshold <= 0 ) {
return '';
}
$delta = (float) $new_reading - (float) $latest->reading_value;
if ( $delta <= $threshold ) {
return '';
}
return sprintf(
/* translators: 1: meter number, 2: consumption increase */
__( 'Auffälliger Verbrauchssprung beim Zähler %1$s: +%2$s seit der letzten Ablesung. Bitte prüfen.', KGVVM_TEXT_DOMAIN ),
$meter->meter_number,
$this->format_meter_value_with_unit( $delta, $meter->type )
);
}
/**
* Format one meter value for display.
*
* @param float|int|string $value Numeric value.
* @param string $type Meter type.
* @return string
*/
private function format_meter_value( $value, $type ) {
$number = (float) $value;
if ( 'water' === $type ) {
return number_format( $number, 2, ',', '' );
}
return number_format( $number, 0, '', '' );
}
/**
* Format one meter value together with its unit.
*
* @param float|int|string $value Numeric value.
* @param string $type Meter type.
* @return string
*/
private function format_meter_value_with_unit( $value, $type ) {
return $this->format_meter_value( $value, $type ) . ' ' . $this->meter_unit_label( $type );
}
/**
* Format a currency value for display.
*
* @param float|int|string $value Numeric value.
* @return string
*/
private function format_currency( $value ) {
return number_format_i18n( (float) $value, 2 ) . ' €';
}
/**
* Format a yearly unit price for display.
*
* @param float|int|string $value Numeric value.
* @param string $unit Unit label.
* @return string
*/
private function format_price_per_unit( $value, $unit ) {
return number_format_i18n( (float) $value, 4 ) . ' € / ' . $unit;
}
/**
* Normalize the saved distribution type for one cost entry.
*
* @param object|array $entry Cost entry.
* @return string
*/
private function get_cost_distribution_type( $entry ) {
$type = 'parcel';
if ( is_object( $entry ) && isset( $entry->distribution_type ) ) {
$type = sanitize_key( (string) $entry->distribution_type );
} elseif ( is_array( $entry ) && isset( $entry['distribution_type'] ) ) {
$type = sanitize_key( (string) $entry['distribution_type'] );
}
return in_array( $type, array( 'parcel', 'member' ), true ) ? $type : 'parcel';
}
/**
* Return the UI label for one cost distribution type.
*
* @param string $distribution_type Distribution type.
* @return string
*/
private function get_cost_distribution_label( $distribution_type ) {
return 'member' === $distribution_type ? __( 'Mitglied', KGVVM_TEXT_DOMAIN ) : __( 'Parzelle', KGVVM_TEXT_DOMAIN );
}
/**
* Resolve the amount per parcel/member for one cost entry.
*
* @param object|array $entry Cost entry.
* @param int $parcel_count Active parcel count.
* @param int $member_count Active member count.
* @return float
*/
private function get_cost_unit_amount( $entry, $parcel_count = 0, $member_count = 0 ) {
if ( is_object( $entry ) && isset( $entry->unit_amount ) && '' !== trim( (string) $entry->unit_amount ) ) {
return (float) $entry->unit_amount;
}
if ( is_array( $entry ) && isset( $entry['unit_amount'] ) && '' !== trim( (string) $entry['unit_amount'] ) ) {
return (float) $entry['unit_amount'];
}
$distribution_type = $this->get_cost_distribution_type( $entry );
$total_cost = 0.0;
if ( is_object( $entry ) && isset( $entry->total_cost ) ) {
$total_cost = (float) $entry->total_cost;
} elseif ( is_array( $entry ) && isset( $entry['total_cost'] ) ) {
$total_cost = (float) $entry['total_cost'];
}
$divisor = 'member' === $distribution_type ? max( 1, (int) $member_count ) : max( 1, (int) $parcel_count );
return $divisor > 0 ? $total_cost / $divisor : 0.0;
}
/**
* Calculate the overall yearly total for one cost entry.
*
* @param object|array $entry Cost entry.
* @param int $parcel_count Active parcel count.
* @param int $member_count Active member count.
* @return float
*/
private function get_cost_total_amount( $entry, $parcel_count = 0, $member_count = 0 ) {
$distribution_type = $this->get_cost_distribution_type( $entry );
$unit_amount = $this->get_cost_unit_amount( $entry, $parcel_count, $member_count );
$multiplier = 'member' === $distribution_type ? max( 0, (int) $member_count ) : max( 0, (int) $parcel_count );
if ( $multiplier < 1 ) {
if ( is_object( $entry ) && isset( $entry->total_cost ) ) {
return (float) $entry->total_cost;
}
if ( is_array( $entry ) && isset( $entry['total_cost'] ) ) {
return (float) $entry['total_cost'];
}
}
return round( $unit_amount * $multiplier, 2 );
}
/**
* Filter statement utility rows by meter type.
*
* @param array $rows Utility rows.
* @param string $type Meter type.
* @return array
*/
private function get_statement_rows_by_meter_type( $rows, $type ) {
return array_values(
array_filter(
(array) $rows,
static function( $row ) use ( $type ) {
return isset( $row->type ) && $type === $row->type;
}
)
);
}
/**
* Build the detailed meter table for HTML/PDF annual statements.
*
* @param array $rows Statement rows.
* @param string $type Meter type.
* @param bool $is_pdf Whether PDF markup is requested.
* @return string
*/
private function build_statement_meter_detail_table( $rows, $type, $is_pdf = false ) {
$rows = array_values( (array) $rows );
$unit = 'power' === $type ? 'kWh' : 'm³';
$empty_msg = 'power' === $type ? __( 'Keine auswertbaren Stromdaten vorhanden.', KGVVM_TEXT_DOMAIN ) : __( 'Keine auswertbaren Wasserdaten vorhanden.', KGVVM_TEXT_DOMAIN );
ob_start();
if ( empty( $rows ) ) {
echo '' . esc_html( $empty_msg ) . '
';
return (string) ob_get_clean();
}
?>
|
|
|
|
|
|
|
| meter_number ) ? $row->meter_number : '—' ); ?> |
previous_value ? $this->format_meter_value_with_unit( $row->previous_value, $type ) : '—' ); ?> |
reading_value ? $this->format_meter_value_with_unit( $row->reading_value, $type ) : '—' ); ?> |
consumption ? $this->format_meter_value_with_unit( $row->consumption, $type ) : '—' ); ?> |
unit_price ? $this->format_price_per_unit( $row->unit_price, $unit ) : '—' ); ?> |
calculated_cost ? $this->format_currency( $row->calculated_cost ) : '—' ); ?> |
loss_value ) && null !== $row->loss_value ? $this->format_meter_value_with_unit( $row->loss_value, $type ) : '—' ); ?> |
section_id ) || empty( $row->reading_date ) ) {
continue;
}
$years[] = (int) gmdate( 'Y', strtotime( $row->reading_date ) );
}
$years = array_values( array_unique( array_filter( $years ) ) );
foreach ( $years as $year ) {
$rates = $this->costs->get_section_prices( $year );
foreach ( $rates as $rate ) {
$lookup[ $year ][ (int) $rate->section_id ] = $rate;
}
}
return $lookup;
}
/**
* Resolve the saved unit price for one consumption row.
*
* @param object $row Consumption row.
* @param array $rate_lookup Saved rate map.
* @return float|null
*/
private function get_consumption_unit_price( $row, $rate_lookup ) {
$year = ! empty( $row->reading_date ) ? (int) gmdate( 'Y', strtotime( $row->reading_date ) ) : 0;
$section_id = ! empty( $row->section_id ) ? (int) $row->section_id : 0;
if ( ! $year || ! $section_id || empty( $rate_lookup[ $year ][ $section_id ] ) ) {
return null;
}
$rate = $rate_lookup[ $year ][ $section_id ];
if ( 'power' === $row->type ) {
return null !== $rate->power_price_per_kwh ? (float) $rate->power_price_per_kwh : null;
}
return null !== $rate->water_price_per_m3 ? (float) $rate->water_price_per_m3 : null;
}
/**
* Calculate the cost for a consumption delta.
*
* @param float|int|null $consumption Consumption amount.
* @param string $type Meter type.
* @param float|null $unit_price Saved price per unit.
* @return float|null
*/
private function calculate_consumption_cost( $consumption, $type, $unit_price ) {
if ( null === $consumption || null === $unit_price ) {
return null;
}
$amount = (float) $consumption;
if ( 'power' === $type ) {
$settings = wp_parse_args( get_option( 'kgvvm_settings', array() ), $this->get_settings_defaults() );
if ( 'mwh' === $settings['power_unit'] ) {
$amount *= 1000;
}
}
return $amount * (float) $unit_price;
}
/**
* Return the display unit for a meter type.
*
* @param string $type Meter type.
* @return string
*/
private function meter_unit_label( $type ) {
if ( 'water' === $type ) {
return 'm³';
}
$settings = wp_parse_args( get_option( 'kgvvm_settings', array() ), $this->get_settings_defaults() );
return 'mwh' === $settings['power_unit'] ? 'MWh' : 'kWh';
}
/**
* Build colored status badge HTML.
*
* @param string $label Visible label.
* @param string $color green|orange|gray.
* @return string
*/
private function status_badge( $label, $color ) {
return '' . esc_html( $label ) . '';
}
/**
* Human parcel status label.
*
* @param string $status Raw status.
* @return string
*/
private function parcel_status_label( $status ) {
$map = array(
'free' => __( 'Frei', KGVVM_TEXT_DOMAIN ),
'assigned' => __( 'Vergeben', KGVVM_TEXT_DOMAIN ),
'reserved' => __( 'Reserviert', KGVVM_TEXT_DOMAIN ),
'inactive' => __( 'Inaktiv', KGVVM_TEXT_DOMAIN ),
);
return isset( $map[ $status ] ) ? $map[ $status ] : $status;
}
/**
* Human meter type label.
*
* @param string $type water|power.
* @return string
*/
private function meter_type_label( $type ) {
return 'water' === $type ? __( 'Wasser', KGVVM_TEXT_DOMAIN ) : __( 'Strom', KGVVM_TEXT_DOMAIN );
}
/**
* Format a short meter label for lists and select boxes.
*
* @param object $meter Meter record.
* @return string
*/
private function format_meter_short_label( $meter ) {
$parts = array(
$this->meter_type_label( $meter->type ),
$meter->meter_number,
);
if ( ! empty( $meter->calibration_year ) ) {
$parts[] = sprintf( __( 'Eichjahr %s', KGVVM_TEXT_DOMAIN ), $meter->calibration_year );
}
return implode( ' – ', array_filter( $parts ) );
}
/**
* Format one member label for tenant views.
*
* @param object $member Member row.
* @return string
*/
private function format_tenant_member_label( $member ) {
$label = $member->display_name;
if ( ! empty( $member->parcel_label ) ) {
$label .= ' – ' . sprintf( __( 'Parzelle %s', KGVVM_TEXT_DOMAIN ), $member->parcel_label );
}
return $label;
}
/**
* Format a full tenant name.
*
* @param object $tenant Tenant row.
* @return string
*/
private function format_tenant_full_name( $tenant ) {
return trim( $tenant->last_name . ', ' . $tenant->first_name, ' ,' );
}
/**
* Ensure capability.
*
* @param string $cap Capability name.
* @return void
*/
private function require_cap( $cap ) {
if ( ! current_user_can( $cap ) ) {
wp_die( esc_html__( 'Sie haben keine Berechtigung für diese Aktion.', KGVVM_TEXT_DOMAIN ) );
}
}
/**
* Render one-time admin notice.
*
* @return void
*/
private function render_notice() {
$key = 'kgvvm_notice_' . get_current_user_id();
$notice = get_transient( $key );
if ( empty( $notice['message'] ) ) {
return;
}
delete_transient( $key );
$type = ! empty( $notice['type'] ) ? $notice['type'] : 'success';
switch ( $type ) {
case 'warning':
$class = 'notice notice-warning';
break;
case 'success':
$class = 'notice notice-success is-dismissible';
break;
default:
$class = 'notice notice-error';
break;
}
echo '' . esc_html( $notice['message'] ) . '
';
}
/**
* Redirect with a transient notice.
*
* @param string $page Target page slug.
* @param string $type success|error.
* @param string $message Notice text.
* @param array $args Extra query args.
* @return void
*/
private function redirect_with_notice( $page, $type, $message, $args = array() ) {
set_transient(
'kgvvm_notice_' . get_current_user_id(),
array(
'type' => $type,
'message' => wp_strip_all_tags( $message ),
),
60
);
wp_safe_redirect( $this->admin_url( $page, $args ) );
exit;
}
/**
* Build admin page URL.
*
* @param string $page Page slug.
* @param array $args Additional args.
* @return string
*/
private function admin_url( $page, $args = array() ) {
return add_query_arg( array_merge( array( 'page' => $page ), $args ), admin_url( 'admin.php' ) );
}
/**
* Build sortable list URL while preserving filters.
*
* @param string $page Page slug.
* @param string $column Sort column.
* @return string
*/
private function sort_url( $page, $column ) {
$current_orderby = isset( $_GET['orderby'] ) ? sanitize_key( wp_unslash( $_GET['orderby'] ) ) : '';
$current_order = isset( $_GET['order'] ) ? strtolower( sanitize_key( wp_unslash( $_GET['order'] ) ) ) : 'asc';
$next_order = ( $current_orderby === $column && 'asc' === $current_order ) ? 'desc' : 'asc';
$args = array();
foreach ( $_GET as $key => $value ) {
if ( in_array( $key, array( '_wpnonce', 'kgvvm_action', 'view', 'id' ), true ) ) {
continue;
}
$args[ $key ] = wp_unslash( $value );
}
$args['page'] = $page;
$args['orderby'] = $column;
$args['order'] = $next_order;
return add_query_arg( $args, admin_url( 'admin.php' ) );
}
// =========================================================================
// ARBEITSSTUNDEN
// =========================================================================
/**
* Save work year configuration.
*
* @return void
*/
private function save_work_year_config() {
$this->require_cap( 'manage_kleingarten' );
check_admin_referer( 'kgvvm_save_work_year_config' );
$year = absint( isset( $_POST['entry_year'] ) ? $_POST['entry_year'] : 0 );
$required_hours = isset( $_POST['required_hours'] ) ? (float) $_POST['required_hours'] : 0;
$price_per_missing_hour = isset( $_POST['price_per_missing_hour'] ) ? (float) $_POST['price_per_missing_hour'] : 0;
if ( $year < 2000 || $year > 2100 ) {
$this->redirect_with_notice( 'kgvvm-arbeit', 'error', __( 'Bitte ein gültiges Jahr eingeben.', KGVVM_TEXT_DOMAIN ) );
}
$this->work->save_year_config( $year, $required_hours, $price_per_missing_hour );
$this->redirect_with_notice( 'kgvvm-arbeit', 'success', __( 'Jahreseinstellungen wurden gespeichert.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
}
/**
* Save work job (add/update).
*
* @return void
*/
private function save_work_job() {
$this->require_cap( 'manage_kleingarten' );
check_admin_referer( 'kgvvm_save_work_job' );
$id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
$name = sanitize_text_field( wp_unslash( isset( $_POST['job_name'] ) ? $_POST['job_name'] : '' ) );
if ( '' === $name ) {
$this->redirect_with_notice( 'kgvvm-arbeit', 'error', __( 'Bitte einen Namen für die Arbeitsart eingeben.', KGVVM_TEXT_DOMAIN ), array( 'tab' => 'jobs' ) );
}
if ( $this->work->job_name_exists( $name, $id ) ) {
$this->redirect_with_notice( 'kgvvm-arbeit', 'error', __( 'Eine Arbeitsart mit diesem Namen existiert bereits.', KGVVM_TEXT_DOMAIN ), array( 'tab' => 'jobs' ) );
}
$this->work->save_job(
array(
'name' => $name,
'description' => isset( $_POST['job_description'] ) ? wp_unslash( $_POST['job_description'] ) : '',
),
$id
);
$this->redirect_with_notice( 'kgvvm-arbeit', 'success', __( 'Arbeitsart wurde gespeichert.', KGVVM_TEXT_DOMAIN ), array( 'tab' => 'jobs' ) );
}
/**
* Save work log entry (add/update).
*
* @return void
*/
private function save_work_log() {
$this->require_cap( 'manage_kleingarten' );
check_admin_referer( 'kgvvm_save_work_log' );
$id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
$job_id = absint( isset( $_POST['job_id'] ) ? $_POST['job_id'] : 0 );
$work_date = sanitize_text_field( wp_unslash( isset( $_POST['work_date'] ) ? $_POST['work_date'] : '' ) );
$year = $work_date ? (int) substr( $work_date, 0, 4 ) : (int) current_time( 'Y' );
if ( '' === $work_date ) {
$this->redirect_with_notice( 'kgvvm-arbeit', 'error', __( 'Bitte ein gültiges Datum eingeben.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
}
$raw_members = isset( $_POST['member_hours'] ) ? (array) wp_unslash( $_POST['member_hours'] ) : array();
$members = array();
foreach ( $raw_members as $uid => $hours ) {
$uid = absint( $uid );
$hours = (float) $hours;
if ( $uid > 0 && $hours > 0 ) {
$members[ $uid ] = $hours;
}
}
if ( empty( $members ) ) {
$this->redirect_with_notice( 'kgvvm-arbeit', 'error', __( 'Bitte mindestens ein Mitglied mit Stundenzahl zuordnen.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
}
$this->work->save_log(
array(
'job_id' => $job_id,
'work_date' => $work_date,
'note' => isset( $_POST['note'] ) ? wp_unslash( $_POST['note'] ) : '',
),
$id,
$members
);
$this->redirect_with_notice( 'kgvvm-arbeit', 'success', __( 'Arbeitseintrag wurde gespeichert.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
}
/**
* Render the Arbeitsstunden admin page.
*
* @return void
*/
public function render_work_page() {
$this->require_cap( 'manage_kleingarten' );
$tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'logs';
$selected_year = isset( $_GET['year'] ) ? absint( $_GET['year'] ) : (int) current_time( 'Y' );
$selected_year = $selected_year > 0 ? $selected_year : (int) current_time( 'Y' );
$edit_log_id = absint( isset( $_GET['id'] ) ? $_GET['id'] : 0 );
$edit_job_id = absint( isset( $_GET['job_id'] ) ? $_GET['job_id'] : 0 );
$years = $this->work->get_years( $selected_year );
$year_config = $this->work->get_year_config( $selected_year );
$jobs = $this->work->get_jobs();
$all_members = $this->assignments->get_member_users();
$edit_log = null;
$edit_job = null;
if ( $edit_log_id ) {
$edit_log = $this->work->find_log( $edit_log_id );
}
if ( $edit_job_id ) {
$edit_job = $this->work->find_job( $edit_job_id );
}
$logs = $this->work->search_logs(
array(
'year' => $selected_year,
'orderby' => isset( $_GET['orderby'] ) ? sanitize_key( wp_unslash( $_GET['orderby'] ) ) : 'work_date',
'order' => isset( $_GET['order'] ) ? sanitize_key( wp_unslash( $_GET['order'] ) ) : 'DESC',
's' => isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '',
)
);
$summary = $this->work->get_member_summary( $selected_year, $all_members );
?>