| Server IP : 202.61.199.114 / Your IP : 216.73.217.139 Web Server : nginx/1.22.1 System : Linux de.arni-solutions.de 6.1.0-49-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64 User : web20 ( 1018) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /var/www/seaside.pacim.de/web/ |
Upload File : |
<?php
/**
* accounting-api.php – Monatsabrechnung: Erstellen (mit Fortschritt), Status, ZIP-Download.
*
* action=create POST year,month -> erzeugt PDFs + Excel in datas/month_reports/{year}-{month}/,
* schreibt laufend _status.json; gibt am Ende JSON zurück.
* action=status GET year,month -> { ok, status:{phase,total,done,percent,message,finished,error} }
* action=zip GET year,month[,dl] -> streamt das Monatsverzeichnis als ZIP
*
* (Google-Drive-Upload folgt in Phase 2.)
*/
require_once( __DIR__ . '/includes/config.inc.php' );
@ini_set( 'display_errors', '0' );
error_reporting( 0 );
if ( ! isset( $_SESSION['user'] ) || $_SESSION['user'] != LOGIN_USER ) {
header( 'Content-Type: application/json; charset=utf-8' );
http_response_code( 403 );
echo json_encode( array( 'ok' => false, 'error' => 'unauthorized' ) );
exit();
}
// Session-Lock früh freigeben, damit Status-Polling NICHT durch die lange create-Anfrage blockiert wird.
session_write_close();
$action = isset( $_GET['action'] ) ? $_GET['action'] : ( isset( $_POST['action'] ) ? $_POST['action'] : '' );
function ac_year( $v ) { return ( is_string( $v ) && preg_match( '/^\d{4}$/', $v ) ) ? $v : null; }
function ac_month( $v ) { return ( is_string( $v ) && preg_match( '/^(0[1-9]|1[0-2])$/', $v ) ) ? $v : null; }
function ac_json( $arr ) { while ( ob_get_level() > 0 ) ob_end_clean(); header( 'Content-Type: application/json; charset=utf-8' ); echo json_encode( $arr ); exit(); }
function ac_dir( $year, $month ) { return PATH_SYSTEM . '/datas/month_reports/' . $year . '-' . $month . '/'; }
function ac_statusfile( $year, $month ) { return ac_dir( $year, $month ) . '_status.json'; }
/* ====================== STATUS ====================== */
if ( $action === 'status' ) {
$year = ac_year( isset( $_GET['year'] ) ? $_GET['year'] : '' );
$month = ac_month( isset( $_GET['month'] ) ? $_GET['month'] : '' );
if ( ! $year || ! $month ) ac_json( array( 'ok' => false, 'error' => 'bad_params' ) );
$sf = ac_statusfile( $year, $month );
if ( ! is_file( $sf ) ) {
ac_json( array( 'ok' => true, 'status' => array( 'phase' => 'idle', 'percent' => 0, 'message' => '', 'finished' => false, 'error' => false ) ) );
}
$st = json_decode( file_get_contents( $sf ), true );
if ( ! is_array( $st ) ) $st = array( 'phase' => 'idle', 'percent' => 0, 'finished' => false, 'error' => false );
ac_json( array( 'ok' => true, 'status' => $st ) );
}
/* ====================== CREATE (mit Fortschritt) ====================== */
if ( $action === 'create' ) {
$year = ac_year( isset( $_POST['year'] ) ? $_POST['year'] : '' );
$month = ac_month( isset( $_POST['month'] ) ? $_POST['month'] : '' );
if ( ! $year || ! $month ) ac_json( array( 'ok' => false, 'error' => 'bad_params' ) );
@set_time_limit( 0 );
@ignore_user_abort( true );
$dir = ac_dir( $year, $month );
if ( ! is_dir( $dir ) ) @mkdir( $dir, 0777, true );
$sf = ac_statusfile( $year, $month );
// Startstatus sofort schreiben (Frontend pollt bereits)
@file_put_contents( $sf, json_encode( array( 'phase' => 'start', 'total' => 0, 'done' => 0, 'percent' => 0, 'message' => 'Wird gestartet …', 'finished' => false, 'error' => false, 'updated' => date( 'c' ) ) ) );
require_once( PATH_SYSTEM . '/includes/accounting.create.php' );
global $oData, $oPDF;
try {
$count = accounting_create_month( $oData, $oPDF, $year, $month, $sf );
// Monat als "erstellt" in der DB vermerken (Flag für die Übersicht).
if ( class_exists( 'settings' ) ) {
settings::set( 'accounting_created:' . $year . '-' . $month, date( 'c' ) );
}
ac_json( array( 'ok' => true, 'count' => (int) $count ) );
} catch ( Throwable $e ) {
@file_put_contents( $sf, json_encode( array( 'phase' => 'error', 'percent' => 0, 'message' => 'Fehler bei der Erstellung.', 'finished' => true, 'error' => true, 'detail' => $e->getMessage(), 'updated' => date( 'c' ) ) ) );
ac_json( array( 'ok' => false, 'error' => 'create_failed' ) );
}
}
/* ====================== GOOGLE-DRIVE-Upload ====================== */
if ( $action === 'drive' ) {
$year = ac_year( isset( $_POST['year'] ) ? $_POST['year'] : '' );
$month = ac_month( isset( $_POST['month'] ) ? $_POST['month'] : '' );
if ( ! $year || ! $month ) ac_json( array( 'ok' => false, 'error' => 'bad_params' ) );
$dir = ac_dir( $year, $month );
if ( ! is_dir( $dir ) ) ac_json( array( 'ok' => false, 'error' => 'not_created' ) );
$sf = ac_statusfile( $year, $month );
// Startstatus sofort setzen, damit das Polling unmittelbar etwas sieht.
@file_put_contents( $sf, json_encode( array( 'phase' => 'drive', 'total' => 0, 'done' => 0, 'percent' => 1, 'message' => 'Wird gestartet …', 'finished' => false, 'error' => false, 'updated' => date( 'c' ) ) ) );
// Upload als entkoppelten CLI-Prozess starten -> kein FPM-Zeitlimit (php_admin max_execution_time=300),
// kein Webserver-Timeout/-Retry. Fortschritt/Abschluss kommen über das Status-Polling.
$php = is_executable( '/usr/bin/php7.3' ) ? '/usr/bin/php7.3' : 'php';
$script = PATH_SYSTEM . '/includes/accounting.drive.cli.php';
$cmd = 'nohup ' . $php . ' ' . escapeshellarg( $script ) . ' ' . escapeshellarg( $year ) . ' ' . escapeshellarg( $month ) . ' >/dev/null 2>&1 &';
@exec( $cmd );
ac_json( array( 'ok' => true, 'started' => true ) );
}
/* ====================== ZIP-Download ====================== */
if ( $action === 'zip' ) {
$year = ac_year( isset( $_GET['year'] ) ? $_GET['year'] : '' );
$month = ac_month( isset( $_GET['month'] ) ? $_GET['month'] : '' );
if ( ! $year || ! $month ) { while ( ob_get_level() > 0 ) ob_end_clean(); http_response_code( 400 ); echo 'bad request'; exit(); }
$dir = ac_dir( $year, $month );
if ( ! is_dir( $dir ) ) { while ( ob_get_level() > 0 ) ob_end_clean(); http_response_code( 404 ); echo 'not found'; exit(); }
$files = array();
foreach ( (array) glob( $dir . '*' ) as $f ) {
if ( ! is_file( $f ) ) continue;
$base = basename( $f );
if ( strpos( $base, '_status' ) === 0 ) continue; // interne Statusdatei nicht mitpacken
$files[] = $f;
}
if ( ! $files ) { while ( ob_get_level() > 0 ) ob_end_clean(); http_response_code( 404 ); echo 'leer'; exit(); }
$tmp = tempnam( sys_get_temp_dir(), 'pacimzip' );
$zip = new ZipArchive();
if ( $zip->open( $tmp, ZipArchive::CREATE | ZipArchive::OVERWRITE ) !== true ) { @unlink( $tmp ); while ( ob_get_level() > 0 ) ob_end_clean(); http_response_code( 500 ); echo 'zip error'; exit(); }
foreach ( $files as $f ) $zip->addFile( $f, basename( $f ) );
$zip->close();
$down = $year . '-' . $month . '-Monatsabrechnung.zip';
while ( ob_get_level() > 0 ) ob_end_clean();
header( 'Content-Type: application/zip' );
header( 'Content-Disposition: attachment; filename="' . $down . '"' );
header( 'Content-Length: ' . filesize( $tmp ) );
header( 'X-Content-Type-Options: nosniff' );
readfile( $tmp );
@unlink( $tmp );
exit();
}
http_response_code( 400 );
ac_json( array( 'ok' => false, 'error' => 'unknown_action' ) );