Compare commits

..

15 Commits

Author SHA1 Message Date
Terranom674
e8e845e039 Bump Bratonien Tools to 0.9.3.36 2026-08-18 08:07:26 +02:00
Terranom674
3753ceb20a NC Connector: open assistant only on demand in modal dialog 2026-08-18 08:07:09 +02:00
Terranom674
e64257eeca Bump Bratonien Tools to 0.9.3.35 2026-08-18 08:04:22 +02:00
Terranom674
81b0366609 Make updater snapshot and signature aware 2026-08-18 08:04:04 +02:00
Terranom674
ee37484dd2 Normalize wizard gallery root safely 2026-08-18 08:01:29 +02:00
Terranom674
da6e9f18d5 Bump version to 0.9.3.34 2026-08-18 08:01:02 +02:00
Terranom674
cce4984ed1 Finish NC connection assistant UI 2026-08-18 08:00:40 +02:00
Terranom674
b1efa5ca4e Store wizard-discovered Nextcloud metadata 2026-08-18 07:59:39 +02:00
Terranom674
78dc8a0ad7 Register complete NC wizard actions 2026-08-18 07:59:22 +02:00
Terranom674
8c54f8c97d Complete NC connection wizard flow 2026-08-18 07:59:04 +02:00
Terranom674
1c19760643 Bump Bratonien Tools to 0.9.3.33 2026-08-18 07:53:48 +02:00
Terranom674
cf49026f68 Add simple NC wizard and connection management UI 2026-08-18 07:53:29 +02:00
Terranom674
23d4d1f781 Expose wizard state and editable connector values 2026-08-18 07:52:43 +02:00
Terranom674
d61e239a24 Register NC wizard and connection edit handlers 2026-08-18 07:52:16 +02:00
Terranom674
0a052ba770 Add NC connector wizard foundation and connection editing 2026-08-18 07:51:58 +02:00
7 changed files with 1258 additions and 298 deletions

View File

@@ -78,9 +78,21 @@ $nc_connector = bratonien_tools_nc_connector_status();
foreach ($nc_connector['connections'] as &$nc_connection)
{
$nc_connection['last_sync'] = bratonien_tools_nc_connector_connection_last_status($nc_connection);
$nc_connection['display_name'] = $nc_connection['name'];
$storage_lines = array();
$storages = isset($nc_connection['config']['storages']) && is_array($nc_connection['config']['storages'])
? $nc_connection['config']['storages']
: array();
foreach ($storages as $storage)
{
$storage_lines[] = (string)($storage['storage_id'] ?? '').' | '.(string)($storage['source_prefix'] ?? '').' | '.(string)($storage['local_mount'] ?? '');
}
$nc_connection['storage_text'] = implode("\n", $storage_lines);
if (!empty($nc_connection['fallback_stored']))
{
$nc_connection['name'] .= ' · Fallback gespeichert';
$nc_connection['display_name'] .= ' · Fallback gespeichert';
$nc_connection['verification_checks'][] = array(
'name' => 'Piwigo-Fallback',
'ok' => true,
@@ -90,6 +102,7 @@ foreach ($nc_connector['connections'] as &$nc_connection)
}
unset($nc_connection);
$nc_connector['piwigo_api_test'] = $nc_piwigo_api_test;
$nc_connector['wizard'] = bratonien_tools_nc_wizard_state();
$nc_system_defaults = array(
'timer_name' => 'bratonien-nc-connector.timer',
'timer_active' => false,

View File

@@ -45,6 +45,15 @@ function bratonien_tools_nc_connector_create_local_api_first()
}
$gallery_root = rtrim(trim((string)$_POST['nc_gallery_root']), '/');
if (strpos($gallery_root, './') === 0)
{
$piwigo_root = realpath(PHPWG_ROOT_PATH);
if ($piwigo_root === false)
{
throw new RuntimeException('Piwigo-Stammverzeichnis konnte nicht aufgelöst werden.');
}
$gallery_root = rtrim($piwigo_root, '/').'/'.ltrim(substr($gallery_root, 2), '/');
}
if ($gallery_root === '' || $gallery_root[0] !== '/')
{
throw new RuntimeException('Der Galerie-Pfad muss ein absoluter Pfad sein.');
@@ -68,6 +77,21 @@ function bratonien_tools_nc_connector_create_local_api_first()
'piwigo_auth' => 'api-first',
);
foreach (array(
'nextcloud_url' => 'nc_nextcloud_url',
'showcase_user' => 'nc_showcase_user',
'nextcloud_access_user' => 'nc_access_user',
'nextcloud_product' => 'nc_product',
'nextcloud_version' => 'nc_version',
) as $config_key => $post_key)
{
$value = trim((string)($_POST[$post_key] ?? ''));
if ($value !== '')
{
$config[$config_key] = $value;
}
}
bratonien_tools_nc_connector_view_name($config['source_view']);
bratonien_tools_nc_connector_view_name($config['activity_view']);

View File

@@ -0,0 +1,694 @@
<?php
if (!defined('PHPWG_ROOT_PATH'))
{
die('Hacking attempt!');
}
function bratonien_tools_nc_wizard_state()
{
$state = isset($_SESSION['bratonien_nc_wizard']) && is_array($_SESSION['bratonien_nc_wizard'])
? $_SESSION['bratonien_nc_wizard']
: array();
return array_merge(array(
'step' => 1,
'scan_ok' => false,
'base_url' => '',
'host_input' => '',
'username' => '',
'display_name' => '',
'version' => '',
'product' => 'Nextcloud',
'users' => array(),
'can_list_users' => false,
'showcase_user' => '',
'connection_name' => '',
'scan_message' => '',
'technical_source' => '',
'technical_complete' => false,
'db_host' => '',
'db_port' => '5432',
'db_database' => 'nextcloud',
'db_user' => '',
'db_password_set' => false,
'source_view' => 'piwigo_showcase_sources',
'activity_view' => 'piwigo_showcase_activity',
'gallery_root' => '',
'storages' => array(),
'storage_candidates' => array(),
'api_status' => 'pending',
'api_username' => '',
'api_error' => '',
), $state);
}
function bratonien_tools_nc_wizard_store(array $state)
{
$_SESSION['bratonien_nc_wizard'] = $state;
}
function bratonien_tools_nc_wizard_reset()
{
unset($_SESSION['bratonien_nc_wizard']);
return array('message'=>'Verbindungsassistent wurde zurückgesetzt.');
}
function bratonien_tools_nc_wizard_normalize_url($host)
{
$host = trim((string)$host);
if ($host === '')
{
throw new RuntimeException('Nextcloud-Host fehlt.');
}
if (!preg_match('#^https?://#i', $host))
{
$host = 'https://'.$host;
}
$parts = parse_url($host);
if (!is_array($parts) || empty($parts['host']))
{
throw new RuntimeException('Nextcloud-Host ist ungültig.');
}
$scheme = strtolower((string)($parts['scheme'] ?? 'https'));
if (!in_array($scheme, array('http','https'), true))
{
throw new RuntimeException('Nextcloud muss per HTTP oder HTTPS erreichbar sein.');
}
$url = $scheme.'://'.$parts['host'];
if (!empty($parts['port']))
{
$url .= ':'.(int)$parts['port'];
}
if (!empty($parts['path']) && $parts['path'] !== '/')
{
$url .= '/'.trim($parts['path'], '/');
}
return rtrim($url, '/');
}
function bratonien_tools_nc_wizard_http($url, $username = '', $password = '', array $headers = array())
{
if (!function_exists('curl_init'))
{
throw new RuntimeException('cURL ist in PHP nicht verfügbar. Der Nextcloud-Scan kann nicht ausgeführt werden.');
}
$ch = curl_init($url);
$http_headers = array_merge(array('Accept: application/json'), $headers);
$options = array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
CURLOPT_CONNECTTIMEOUT => 8,
CURLOPT_TIMEOUT => 15,
CURLOPT_HTTPHEADER => $http_headers,
CURLOPT_USERAGENT => 'Bratonien-Tools-NC-Wizard/'.(function_exists('bratonien_tools_current_version') ? bratonien_tools_current_version() : 'dev'),
);
if ($username !== '')
{
$options[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
$options[CURLOPT_USERPWD] = $username.':'.$password;
}
curl_setopt_array($ch, $options);
$body = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($body === false || $errno !== 0)
{
throw new RuntimeException('Nextcloud konnte nicht erreicht werden: '.$error);
}
return array('status'=>$status, 'body'=>(string)$body);
}
function bratonien_tools_nc_wizard_ocs_data($body)
{
$decoded = json_decode((string)$body, true);
if (!is_array($decoded))
{
throw new RuntimeException('Nextcloud hat keine gültige JSON-Antwort geliefert.');
}
if (isset($decoded['ocs']['meta']['statuscode']) && (int)$decoded['ocs']['meta']['statuscode'] !== 100)
{
$message = (string)($decoded['ocs']['meta']['message'] ?? 'Nextcloud hat die Anfrage abgelehnt.');
throw new RuntimeException($message);
}
return isset($decoded['ocs']['data']) && is_array($decoded['ocs']['data']) ? $decoded['ocs']['data'] : array();
}
function bratonien_tools_nc_wizard_fill_profile(array &$state)
{
$url_host = strtolower((string)parse_url($state['base_url'], PHP_URL_HOST));
$state['db_host'] = $url_host;
$state['db_port'] = '5432';
$state['db_database'] = 'nextcloud';
$state['source_view'] = 'piwigo_showcase_sources';
$state['activity_view'] = 'piwigo_showcase_activity';
$state['gallery_root'] = rtrim(PHPWG_ROOT_PATH, '/').'/galleries/nextcloud';
$state['technical_source'] = 'Standardwerte vorbereitet';
foreach (bratonien_tools_nc_connector_connections() as $candidate)
{
$config = isset($candidate['config']) && is_array($candidate['config']) ? $candidate['config'] : array();
$candidate_url = trim((string)($config['nextcloud_url'] ?? ''));
$candidate_host = strtolower(trim((string)($config['host'] ?? '')));
$match = false;
if ($candidate_url !== '')
{
try
{
$match = bratonien_tools_nc_wizard_normalize_url($candidate_url) === $state['base_url'];
}
catch (Throwable $ignored)
{
$match = false;
}
}
if (!$match && $candidate_host !== '' && $candidate_host === $url_host)
{
$match = true;
}
if (!$match)
{
continue;
}
$full = bratonien_tools_nc_connector_connection((int)$candidate['id'], true);
if (!$full)
{
continue;
}
$credentials = bratonien_tools_nc_connector_credentials_from_blob($full['secret_blob'] ?? '');
$state['db_host'] = (string)($config['host'] ?? $state['db_host']);
$state['db_port'] = (string)($config['port'] ?? $state['db_port']);
$state['db_database'] = (string)($config['database'] ?? $state['db_database']);
$state['db_user'] = (string)($config['user'] ?? '');
$state['_db_password'] = (string)($credentials['db_password'] ?? '');
$state['db_password_set'] = $state['_db_password'] !== '';
$state['source_view'] = (string)($config['source_view'] ?? $state['source_view']);
$state['activity_view'] = (string)($config['activity_view'] ?? $state['activity_view']);
$state['gallery_root'] = (string)($config['gallery_root'] ?? $state['gallery_root']);
$state['storages'] = isset($config['storages']) && is_array($config['storages']) ? $config['storages'] : array();
$state['technical_source'] = 'Passende vorhandene Connector-Konfiguration erkannt';
break;
}
$state['technical_complete'] = $state['db_host'] !== ''
&& $state['db_port'] !== ''
&& $state['db_database'] !== ''
&& $state['db_user'] !== ''
&& !empty($state['db_password_set'])
&& $state['source_view'] !== ''
&& $state['activity_view'] !== ''
&& $state['gallery_root'] !== ''
&& !empty($state['storages']);
}
function bratonien_tools_nc_wizard_scan()
{
$host_input = trim((string)($_POST['nc_wizard_host'] ?? ''));
$username = trim((string)($_POST['nc_wizard_user'] ?? ''));
$password = (string)($_POST['nc_wizard_password'] ?? '');
if ($username === '' || $password === '')
{
throw new RuntimeException('Nextcloud-Benutzer und Passwort werden für den Scan benötigt.');
}
$base_url = bratonien_tools_nc_wizard_normalize_url($host_input);
$status_response = bratonien_tools_nc_wizard_http($base_url.'/status.php');
if ($status_response['status'] < 200 || $status_response['status'] >= 300)
{
throw new RuntimeException('Unter diesem Host wurde keine erreichbare Nextcloud-Instanz erkannt (HTTP '.$status_response['status'].').');
}
$status_data = json_decode($status_response['body'], true);
if (!is_array($status_data) || empty($status_data['installed']))
{
throw new RuntimeException('Der Host antwortet, wurde aber nicht als installierte Nextcloud-Instanz erkannt.');
}
$user_response = bratonien_tools_nc_wizard_http(
$base_url.'/ocs/v2.php/cloud/user?format=json',
$username,
$password,
array('OCS-APIRequest: true')
);
if ($user_response['status'] === 401 || $user_response['status'] === 403)
{
throw new RuntimeException('Nextcloud hat Benutzername oder Passwort abgelehnt.');
}
if ($user_response['status'] < 200 || $user_response['status'] >= 300)
{
throw new RuntimeException('Nextcloud-Benutzerprüfung ist fehlgeschlagen (HTTP '.$user_response['status'].').');
}
$user_data = bratonien_tools_nc_wizard_ocs_data($user_response['body']);
$users = array();
$can_list_users = false;
try
{
$users_response = bratonien_tools_nc_wizard_http(
$base_url.'/ocs/v2.php/cloud/users?format=json',
$username,
$password,
array('OCS-APIRequest: true')
);
if ($users_response['status'] >= 200 && $users_response['status'] < 300)
{
$users_data = bratonien_tools_nc_wizard_ocs_data($users_response['body']);
if (isset($users_data['users']) && is_array($users_data['users']))
{
$users = array_values(array_filter(array_map('strval', $users_data['users'])));
sort($users, SORT_NATURAL | SORT_FLAG_CASE);
$can_list_users = true;
}
}
}
catch (Throwable $ignored)
{
$users = array();
$can_list_users = false;
}
$url_host = (string)parse_url($base_url, PHP_URL_HOST);
$state = array(
'step' => 2,
'scan_ok' => true,
'base_url' => $base_url,
'host_input' => $host_input,
'username' => (string)($user_data['id'] ?? $username),
'display_name' => (string)($user_data['display-name'] ?? $user_data['displayname'] ?? ''),
'version' => (string)($status_data['versionstring'] ?? $status_data['version'] ?? ''),
'product' => (string)($status_data['productname'] ?? 'Nextcloud'),
'users' => $users,
'can_list_users' => $can_list_users,
'showcase_user' => '',
'connection_name' => $url_host !== '' ? $url_host : 'Nextcloud',
'scan_message' => 'Nextcloud wurde erkannt und der Benutzerzugriff wurde bestätigt.',
'_password' => $password,
'api_status' => 'pending',
'api_username' => '',
'api_error' => '',
);
bratonien_tools_nc_wizard_fill_profile($state);
bratonien_tools_nc_wizard_store($state);
return array('message'=>'Nextcloud-Scan erfolgreich. Erkannte Werte wurden übernommen; fehlende Angaben werden jetzt gezielt abgefragt.');
}
function bratonien_tools_nc_wizard_save_technical()
{
$state = bratonien_tools_nc_wizard_state();
if (empty($state['scan_ok']))
{
throw new RuntimeException('Bitte zuerst Nextcloud erfolgreich scannen.');
}
$state['connection_name'] = trim((string)($_POST['nc_wizard_connection_name'] ?? $state['connection_name']));
$state['db_host'] = trim((string)($_POST['nc_wizard_db_host'] ?? $state['db_host']));
$state['db_port'] = (string)max(1, min(65535, (int)($_POST['nc_wizard_db_port'] ?? $state['db_port'])));
$state['db_database'] = trim((string)($_POST['nc_wizard_db_database'] ?? $state['db_database']));
$state['db_user'] = trim((string)($_POST['nc_wizard_db_user'] ?? $state['db_user']));
$db_password = (string)($_POST['nc_wizard_db_password'] ?? '');
if ($db_password !== '')
{
$state['_db_password'] = $db_password;
$state['db_password_set'] = true;
}
if ($state['connection_name'] === '' || $state['db_host'] === '' || $state['db_database'] === '' || $state['db_user'] === '' || empty($state['db_password_set']))
{
bratonien_tools_nc_wizard_store($state);
throw new RuntimeException('Die noch fehlenden Datenbankangaben müssen vollständig angegeben werden.');
}
$config = array(
'host'=>$state['db_host'],
'port'=>$state['db_port'],
'database'=>$state['db_database'],
'user'=>$state['db_user'],
);
$password = (string)($state['_db_password'] ?? '');
bratonien_tools_nc_connector_psql($config, $password, 'SELECT 1');
$source_view = bratonien_tools_nc_connector_view_name($state['source_view']);
$activity_view = bratonien_tools_nc_connector_view_name($state['activity_view']);
bratonien_tools_nc_connector_psql($config, $password, 'SELECT COUNT(*) FROM '.$source_view);
bratonien_tools_nc_connector_psql($config, $password, 'SELECT 1 FROM '.$activity_view.' LIMIT 1');
$rows = bratonien_tools_nc_connector_psql(
$config,
$password,
"SELECT DISTINCT storage_id::text || E'\\t' || source_path FROM ".$source_view." ORDER BY 1"
);
$candidates = array();
foreach (preg_split('/\r\n|\r|\n/', trim($rows)) as $line)
{
if ($line === '') continue;
$parts = explode("\t", $line, 2);
if (count($parts) !== 2) continue;
$storage_id = trim($parts[0]);
$source_path = trim($parts[1], '/');
$prefix = $source_path;
if (strpos($source_path, '/') !== false)
{
$prefix = substr($source_path, 0, strpos($source_path, '/'));
}
if ($storage_id === '') continue;
if (!isset($candidates[$storage_id]))
{
$candidates[$storage_id] = array('storage_id'=>$storage_id, 'source_prefix'=>$prefix, 'local_mount'=>'');
}
}
if (!$candidates)
{
throw new RuntimeException('Die Source-View ist erreichbar, liefert aber keine Storage-Zuordnung.');
}
$known = array();
foreach (bratonien_tools_nc_connector_connections() as $connection)
{
foreach (($connection['config']['storages'] ?? array()) as $storage)
{
$key = (string)($storage['storage_id'] ?? '').'|'.(string)($storage['source_prefix'] ?? '');
if ($key !== '|') $known[$key] = (string)($storage['local_mount'] ?? '');
}
}
foreach ($candidates as &$candidate)
{
$key = $candidate['storage_id'].'|'.$candidate['source_prefix'];
if (!empty($known[$key]))
{
$candidate['local_mount'] = $known[$key];
}
}
unset($candidate);
$state['storage_candidates'] = array_values($candidates);
$state['storages'] = array_values(array_filter($state['storage_candidates'], function($storage)
{
return trim((string)($storage['local_mount'] ?? '')) !== '';
}));
$state['technical_source'] = 'Datenbank und Views erfolgreich geprüft';
$state['technical_complete'] = count($state['storages']) === count($state['storage_candidates']);
bratonien_tools_nc_wizard_store($state);
return array('message'=>$state['technical_complete']
? 'Technische Verbindung wurde automatisch vervollständigt.'
: 'Datenbank und Views wurden erkannt. Für nicht erkannte Storages wird nur noch der lokale Mount abgefragt.');
}
function bratonien_tools_nc_wizard_save_mounts()
{
$state = bratonien_tools_nc_wizard_state();
$candidates = isset($state['storage_candidates']) && is_array($state['storage_candidates']) ? $state['storage_candidates'] : array();
$mounts = isset($_POST['nc_wizard_storage_mount']) && is_array($_POST['nc_wizard_storage_mount']) ? $_POST['nc_wizard_storage_mount'] : array();
if (!$candidates)
{
throw new RuntimeException('Es wurden noch keine Storages erkannt.');
}
$storages = array();
foreach ($candidates as $index => $candidate)
{
$mount = trim((string)($candidate['local_mount'] ?? ''));
if ($mount === '')
{
$mount = trim((string)($mounts[$index] ?? ''));
}
if ($mount === '' || $mount[0] !== '/')
{
throw new RuntimeException('Für Storage '.(string)$candidate['storage_id'].' fehlt ein absoluter lokaler Mount-Pfad.');
}
$mount = rtrim($mount, '/');
if (!is_dir($mount) || !is_readable($mount))
{
throw new RuntimeException('Storage-Mount '.$mount.' ist nicht vorhanden oder nicht lesbar.');
}
$candidate['local_mount'] = $mount;
$storages[] = $candidate;
}
$state['storages'] = $storages;
$state['storage_candidates'] = $storages;
$state['technical_complete'] = true;
bratonien_tools_nc_wizard_store($state);
return array('message'=>'Storage-Zuordnung ist vollständig.');
}
function bratonien_tools_nc_wizard_select_user()
{
$state = bratonien_tools_nc_wizard_state();
if (empty($state['scan_ok']))
{
throw new RuntimeException('Bitte zuerst Nextcloud erfolgreich scannen.');
}
if (empty($state['technical_complete']))
{
throw new RuntimeException('Die automatische technische Erkennung ist noch nicht vollständig. Bitte nur die noch fehlenden Angaben ergänzen.');
}
$showcase_user = trim((string)($_POST['nc_wizard_showcase_user'] ?? ''));
if ($showcase_user === '')
{
throw new RuntimeException('Bitte einen Nextcloud-Benutzer für die Showcase-Freigaben auswählen.');
}
$connection_name = trim((string)($_POST['nc_wizard_connection_name'] ?? $state['connection_name']));
if ($connection_name === '')
{
throw new RuntimeException('Bitte einen Namen für die Verbindung angeben.');
}
$state['connection_name'] = $connection_name;
$state['showcase_user'] = $showcase_user;
$state['step'] = 3;
$state['api_error'] = '';
bratonien_tools_nc_wizard_store($state);
return array('message'=>'Showcase-Benutzer übernommen. Jetzt folgt der Piwigo-API-Zugang.');
}
function bratonien_tools_nc_wizard_api_test()
{
$state = bratonien_tools_nc_wizard_state();
if ((int)$state['step'] !== 3)
{
throw new RuntimeException('Der API-Test ist in diesem Assistentenschritt nicht verfügbar.');
}
$key_id = trim((string)($_POST['nc_wizard_api_key_id'] ?? ''));
$secret = trim((string)($_POST['nc_wizard_api_key_secret'] ?? ''));
if ($key_id === '' && $secret === '')
{
$stored = bratonien_tools_nc_api_credentials();
$key_id = trim((string)($stored['key_id'] ?? ''));
$secret = trim((string)($stored['key_secret'] ?? ''));
}
if ($key_id === '' || $secret === '')
{
throw new RuntimeException('API-Schlüssel-ID und API-Geheimnis fehlen.');
}
try
{
$status = bratonien_tools_nc_connector_piwigo_api_request($key_id, $secret, 'pwg.session.getStatus');
$user_status = strtolower((string)($status['status'] ?? ''));
if (!in_array($user_status, array('admin','webmaster'), true))
{
throw new RuntimeException('Der API-Key gehört keinem Piwigo-Administrator/Webmaster.');
}
$method_result = bratonien_tools_nc_connector_piwigo_api_request($key_id, $secret, 'reflection.getMethodList');
$method_map = array();
bratonien_tools_nc_connector_collect_method_names($method_result, $method_map);
$missing = array_values(array_diff(array('bratonien.nc.syncProductive','bratonien.nc.syncOrphans'), array_keys($method_map)));
if ($missing)
{
throw new RuntimeException('Benötigte Bratonien-Sync-Methoden fehlen: '.implode(', ', $missing).'.');
}
bratonien_tools_nc_api_credentials_store($key_id, $secret);
$state['api_status'] = 'ok';
$state['api_username'] = (string)($status['username'] ?? $status['user'] ?? '');
$state['api_error'] = '';
$state['step'] = 4;
bratonien_tools_nc_wizard_store($state);
return array('message'=>'Piwigo-API erfolgreich geprüft. Zum Abschluss kann jetzt der Fallback festgelegt werden.');
}
catch (Throwable $e)
{
$state['api_status'] = 'error';
$state['api_error'] = $e->getMessage();
bratonien_tools_nc_wizard_store($state);
throw $e;
}
}
function bratonien_tools_nc_wizard_api_skip()
{
$state = bratonien_tools_nc_wizard_state();
if ((int)$state['step'] !== 3)
{
throw new RuntimeException('Die API kann in diesem Assistentenschritt nicht übersprungen werden.');
}
$state['api_status'] = 'skipped';
$state['api_error'] = '';
$state['step'] = 4;
bratonien_tools_nc_wizard_store($state);
return array('message'=>'Piwigo-API wurde übersprungen. Für diese Verbindung ist deshalb ein Fallback-Zugang erforderlich.');
}
function bratonien_tools_nc_wizard_finish()
{
$state = bratonien_tools_nc_wizard_state();
if ((int)$state['step'] !== 4 || empty($state['technical_complete']) || trim((string)$state['showcase_user']) === '')
{
throw new RuntimeException('Der Assistent ist noch nicht vollständig.');
}
$fallback_user = trim((string)($_POST['nc_wizard_fallback_user'] ?? ''));
$fallback_password = (string)($_POST['nc_wizard_fallback_password'] ?? '');
if (($fallback_user === '') !== ($fallback_password === ''))
{
throw new RuntimeException('Fallback-Benutzer und Fallback-Passwort müssen entweder beide angegeben oder beide leer gelassen werden.');
}
if ($state['api_status'] !== 'ok' && $fallback_user === '')
{
throw new RuntimeException('Da die Piwigo-API übersprungen wurde, ist ein Fallback-Zugang erforderlich.');
}
$storage_lines = array();
foreach ($state['storages'] as $storage)
{
$storage_lines[] = (string)$storage['storage_id'].' | '.(string)$storage['source_prefix'].' | '.(string)$storage['local_mount'];
}
$_POST['nc_name'] = (string)$state['connection_name'];
$_POST['nc_host'] = (string)$state['db_host'];
$_POST['nc_port'] = (string)$state['db_port'];
$_POST['nc_database'] = (string)$state['db_database'];
$_POST['nc_user'] = (string)$state['db_user'];
$_POST['nc_db_password'] = (string)($state['_db_password'] ?? '');
$_POST['nc_source_view'] = (string)$state['source_view'];
$_POST['nc_activity_view'] = (string)$state['activity_view'];
$_POST['nc_gallery_root'] = (string)$state['gallery_root'];
$_POST['nc_storages'] = implode("\n", $storage_lines);
$_POST['nc_quiet_seconds'] = '120';
$_POST['nc_max_wait_seconds'] = '900';
$_POST['nc_full_sync_seconds'] = '86400';
$_POST['nc_piwigo_user'] = $fallback_user;
$_POST['nc_piwigo_password'] = $fallback_password;
$_POST['nc_nextcloud_url'] = (string)$state['base_url'];
$_POST['nc_showcase_user'] = (string)$state['showcase_user'];
$_POST['nc_access_user'] = (string)$state['username'];
$_POST['nc_product'] = (string)$state['product'];
$_POST['nc_version'] = (string)$state['version'];
$result = bratonien_tools_nc_connector_create_local_api_first();
unset($_SESSION['bratonien_nc_wizard']);
$result['message'] = 'Verbindung wurde vollständig durch den Assistenten angelegt. '.$result['message'];
return $result;
}
function bratonien_tools_nc_connector_update_name()
{
$id = (int)($_POST['connection_id'] ?? 0);
$name = trim((string)($_POST['connection_name'] ?? ''));
if ($id < 1 || $name === '')
{
throw new RuntimeException('Verbindung oder Name fehlt.');
}
$connection = bratonien_tools_nc_connector_connection($id, false);
if (!$connection)
{
throw new RuntimeException('Connector-Verbindung wurde nicht gefunden.');
}
$table = bratonien_tools_nc_connector_table();
$now = date('Y-m-d H:i:s');
pwg_query("UPDATE `$table` SET name='".pwg_db_real_escape_string($name)."', updated='".pwg_db_real_escape_string($now)."' WHERE id=".$id." LIMIT 1");
return array('message'=>'Verbindungsname wurde aktualisiert.');
}
function bratonien_tools_nc_connector_update_technical()
{
$id = (int)($_POST['connection_id'] ?? 0);
$connection = bratonien_tools_nc_connector_connection($id, true);
if (!$connection)
{
throw new RuntimeException('Connector-Verbindung wurde nicht gefunden.');
}
if (!empty($connection['enabled']) || (string)$connection['takeover_state'] === 'active')
{
throw new RuntimeException('Technische Einstellungen einer aktiven Verbindung können nicht geändert werden. Bitte zuerst deaktivieren.');
}
$config = $connection['config'];
$port = (int)($_POST['nc_port'] ?? ($config['port'] ?? 5432));
if ($port < 1 || $port > 65535)
{
throw new RuntimeException('Ungültiger PostgreSQL-Port.');
}
$gallery_root = rtrim(trim((string)($_POST['nc_gallery_root'] ?? ($config['gallery_root'] ?? ''))), '/');
if ($gallery_root === '' || $gallery_root[0] !== '/')
{
throw new RuntimeException('Der Galerie-Pfad muss ein absoluter Pfad sein.');
}
$config['host'] = trim((string)($_POST['nc_host'] ?? ($config['host'] ?? '')));
$config['port'] = (string)$port;
$config['database'] = trim((string)($_POST['nc_database'] ?? ($config['database'] ?? '')));
$config['user'] = trim((string)($_POST['nc_user'] ?? ($config['user'] ?? '')));
$config['source_view'] = trim((string)($_POST['nc_source_view'] ?? ($config['source_view'] ?? '')));
$config['activity_view'] = trim((string)($_POST['nc_activity_view'] ?? ($config['activity_view'] ?? '')));
$config['gallery_root'] = $gallery_root;
$config['quiet_seconds'] = max(0, (int)($_POST['nc_quiet_seconds'] ?? ($config['quiet_seconds'] ?? 120)));
$config['max_wait_seconds'] = max(60, (int)($_POST['nc_max_wait_seconds'] ?? ($config['max_wait_seconds'] ?? 900)));
$config['full_sync_seconds'] = max(300, (int)($_POST['nc_full_sync_seconds'] ?? ($config['full_sync_seconds'] ?? 86400)));
$config['storages'] = bratonien_tools_nc_connector_parse_storages($_POST['nc_storages'] ?? '');
unset($config['verification']);
bratonien_tools_nc_connector_view_name($config['source_view']);
bratonien_tools_nc_connector_view_name($config['activity_view']);
$credentials = bratonien_tools_nc_connector_credentials_from_blob($connection['secret_blob'] ?? '');
$db_password = (string)($_POST['nc_db_password'] ?? '');
if ($db_password === '')
{
$db_password = $credentials['db_password'];
}
$secret_blob = bratonien_tools_nc_connector_encrypt_credentials(
$db_password,
$credentials['piwigo_user'],
$credentials['piwigo_password']
);
$config_json = json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if (!is_string($config_json))
{
throw new RuntimeException('Connector-Konfiguration konnte nicht serialisiert werden.');
}
$table = bratonien_tools_nc_connector_table();
$now = date('Y-m-d H:i:s');
pwg_query("UPDATE `$table` SET takeover_state='disabled', enabled=0, config_json='".pwg_db_real_escape_string($config_json)."', secret_blob='".pwg_db_real_escape_string($secret_blob)."', updated='".pwg_db_real_escape_string($now)."' WHERE id=".$id." LIMIT 1");
return array('message'=>'Technische Verbindungseinstellungen wurden gespeichert. Die Verbindung muss erneut geprüft werden.');
}

View File

@@ -14,6 +14,53 @@ function bratonien_tools_current_version()
return '0.0.0';
}
function bratonien_tools_self_update_fetch_text($url, &$body, &$details)
{
$body = '';
$details = '';
if (function_exists('curl_init'))
{
$ch = curl_init($url);
curl_setopt_array($ch, array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 20,
CURLOPT_USERAGENT => 'Bratonien-Tools-Updater/'.bratonien_tools_current_version(),
CURLOPT_HTTPHEADER => array('Accept: application/vnd.github+json, text/plain;q=0.9, */*;q=0.8'),
));
$response = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$http = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response === false || $errno !== 0)
{
$details = 'cURL-Fehler '.$errno.': '.$error;
return false;
}
if ($http < 200 || $http >= 300)
{
$details = 'HTTP '.$http;
return false;
}
$body = (string)$response;
return true;
}
if (function_exists('fetchRemote') && fetchRemote($url, $body))
{
return true;
}
$details = 'Remote-Inhalt konnte nicht geladen werden.';
return false;
}
function bratonien_tools_remote_update_info($force = false)
{
$cache_key = 'bratonien_self_update_status';
@@ -28,28 +75,68 @@ function bratonien_tools_remote_update_info($force = false)
}
$current = bratonien_tools_current_version();
$remote_main = '';
$ok = function_exists('fetchRemote') && fetchRemote('https://raw.githubusercontent.com/Terranom674/Piwigo_Bratonien_Tools/main/main.inc.php', $remote_main);
if (!$ok || !preg_match('/Version:\s*([\w.-]+)/i', (string)$remote_main, $m))
$commit_json = '';
$details = '';
$commit_url = 'https://api.github.com/repos/Terranom674/Piwigo_Bratonien_Tools/commits/main';
if (!bratonien_tools_self_update_fetch_text($commit_url, $commit_json, $details))
{
$data = array(
'checked_at' => time(),
'current' => $current,
'remote' => null,
'signature' => null,
'main_sha256' => null,
'update_available' => false,
'error' => 'GitHub konnte nicht erreicht oder die Versionsnummer nicht gelesen werden.',
'error' => 'GitHub konnte nicht erreicht oder der aktuelle Commit nicht ermittelt werden.'.($details !== '' ? ' Details: '.$details : ''),
);
}
else
{
$remote = trim($m[1]);
$data = array(
'checked_at' => time(),
'current' => $current,
'remote' => $remote,
'update_available' => version_compare($remote, $current, '>'),
'error' => null,
);
$commit = json_decode($commit_json, true);
$signature = is_array($commit) ? strtolower(trim((string)($commit['sha'] ?? ''))) : '';
if (!preg_match('/^[a-f0-9]{40}$/', $signature))
{
$data = array(
'checked_at' => time(),
'current' => $current,
'remote' => null,
'signature' => null,
'main_sha256' => null,
'update_available' => false,
'error' => 'GitHub lieferte keine gültige Commit-Signatur für main.',
);
}
else
{
$remote_main = '';
$raw_url = 'https://raw.githubusercontent.com/Terranom674/Piwigo_Bratonien_Tools/'.$signature.'/main.inc.php';
if (!bratonien_tools_self_update_fetch_text($raw_url, $remote_main, $details) || !preg_match('/Version:\s*([\w.-]+)/i', (string)$remote_main, $m))
{
$data = array(
'checked_at' => time(),
'current' => $current,
'remote' => null,
'signature' => $signature,
'main_sha256' => null,
'update_available' => false,
'error' => 'Die Plugin-Version des ermittelten GitHub-Commits konnte nicht gelesen werden.'.($details !== '' ? ' Details: '.$details : ''),
);
}
else
{
$remote = trim($m[1]);
$data = array(
'checked_at' => time(),
'current' => $current,
'remote' => $remote,
'signature' => $signature,
'main_sha256' => hash('sha256', (string)$remote_main),
'update_available' => version_compare($remote, $current, '>'),
'error' => null,
);
}
}
}
if (function_exists('conf_update_param'))
@@ -72,16 +159,17 @@ function bratonien_tools_self_update_check()
throw new RuntimeException($info['error']);
}
$signature_label = !empty($info['signature']) ? substr($info['signature'], 0, 12) : 'unbekannt';
if (!empty($info['update_available']))
{
return array(
'message' => 'Update verfügbar: '.$info['current'].' → '.$info['remote'].'.',
'message' => 'Update verfügbar: '.$info['current'].' → '.$info['remote'].' · Signatur '.$signature_label.'.',
'self_update' => $info,
);
}
return array(
'message' => 'Bratonien Tools ist aktuell (Version '.$info['current'].').',
'message' => 'Bratonien Tools ist aktuell (Version '.$info['current'].', Signatur '.$signature_label.').',
'self_update' => $info,
);
}
@@ -172,6 +260,23 @@ function bratonien_tools_download_update_archive($url, &$data, &$details)
return true;
}
function bratonien_tools_self_update_find_source($extract_dir, $signature)
{
$expected = rtrim($extract_dir, '/').'/Piwigo_Bratonien_Tools-'.$signature;
if (is_dir($expected))
{
return $expected;
}
$candidates = glob(rtrim($extract_dir, '/').'/Piwigo_Bratonien_Tools-*', GLOB_ONLYDIR);
if (is_array($candidates) && count($candidates) === 1)
{
return $candidates[0];
}
return '';
}
function bratonien_tools_self_update_run()
{
global $template;
@@ -202,6 +307,13 @@ function bratonien_tools_self_update_run()
);
}
$signature = strtolower(trim((string)($info['signature'] ?? '')));
$expected_main_sha256 = strtolower(trim((string)($info['main_sha256'] ?? '')));
if (!preg_match('/^[a-f0-9]{40}$/', $signature) || !preg_match('/^[a-f0-9]{64}$/', $expected_main_sha256))
{
throw new RuntimeException('Update abgebrochen: Version oder Signatur des Zielstands ist unvollständig.');
}
$work_root = rtrim(PHPWG_ROOT_PATH, '/').'/_data/bratonien-updater';
if (!is_dir($work_root) && !@mkdir($work_root, 0755, true))
{
@@ -216,7 +328,7 @@ function bratonien_tools_self_update_run()
$zip_data = '';
$download_details = '';
$archive_url = 'https://codeload.github.com/Terranom674/Piwigo_Bratonien_Tools/zip/refs/heads/main';
$archive_url = 'https://codeload.github.com/Terranom674/Piwigo_Bratonien_Tools/zip/'.$signature;
if (!bratonien_tools_download_update_archive($archive_url, $zip_data, $download_details))
{
bratonien_tools_self_update_rrmdir($run_dir);
@@ -245,12 +357,12 @@ function bratonien_tools_self_update_run()
}
$zip->close();
$source = $extract_dir.'/Piwigo_Bratonien_Tools-main';
$source_main = $source.'/main.inc.php';
if (!is_file($source_main))
$source = bratonien_tools_self_update_find_source($extract_dir, $signature);
$source_main = $source !== '' ? $source.'/main.inc.php' : '';
if ($source === '' || !is_file($source_main))
{
bratonien_tools_self_update_rrmdir($run_dir);
throw new RuntimeException('Das geladene Archiv enthält kein gültiges Bratonien-Tools-Plugin. Erwartet wurde: '.$source_main);
throw new RuntimeException('Das geladene Archiv enthält kein gültiges Bratonien-Tools-Plugin für die erwartete Signatur '.substr($signature, 0, 12).'.');
}
$remote_main = @file_get_contents($source_main);
@@ -259,11 +371,18 @@ function bratonien_tools_self_update_run()
bratonien_tools_self_update_rrmdir($run_dir);
throw new RuntimeException('Die geladene Plugin-Version konnte nicht verifiziert werden. main.inc.php fehlt oder enthält keine lesbare Plugin-/Versionsangabe.');
}
$package_version = trim($vm[1]);
$package_main_sha256 = hash('sha256', (string)$remote_main);
if (!hash_equals($expected_main_sha256, $package_main_sha256))
{
bratonien_tools_self_update_rrmdir($run_dir);
throw new RuntimeException('Signaturprüfung fehlgeschlagen: Das geladene Paket gehört nicht exakt zum zuvor geprüften GitHub-Stand '.substr($signature, 0, 12).'.');
}
if ($package_version !== $info['remote'])
{
bratonien_tools_self_update_rrmdir($run_dir);
throw new RuntimeException('Versionsprüfung fehlgeschlagen: Erwartet '.$info['remote'].', erhalten '.$package_version.'.');
throw new RuntimeException('Versionsprüfung fehlgeschlagen: Erwartet '.$info['remote'].', erhalten '.$package_version.'; Signatur '.substr($signature, 0, 12).'.');
}
$plugin_dir = rtrim(BRATONIEN_TOOLS_PATH, '/');
@@ -320,11 +439,13 @@ function bratonien_tools_self_update_run()
'checked_at' => time(),
'current' => $package_version,
'remote' => $package_version,
'signature' => $signature,
'main_sha256' => $package_main_sha256,
'update_available' => false,
'error' => null,
);
return array(
'message' => 'Bratonien Tools wurde auf Version '.$package_version.' aktualisiert. Backup: '.$backup_dir,
'message' => 'Bratonien Tools wurde auf Version '.$package_version.' aktualisiert. Signatur '.substr($signature, 0, 12).'. Backup: '.$backup_dir,
'self_update' => $updated_info,
);
}

View File

@@ -21,109 +21,54 @@ require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_takeover.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_auth.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_create_api.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_piwigo_api.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_wizard.inc.php');
function bratonien_tools_get_tools()
{
return array(
'image_cache_clear' => array(
'handler' => 'bratonien_tools_clear_image_cache',
),
'image_cache_build' => array(
'handler' => 'bratonien_tools_start_main_cache_build',
),
'image_cache_cancel' => array(
'handler' => 'bratonien_tools_cancel_main_cache_build',
),
'image_cache_worker_settings' => array(
'handler' => 'bratonien_tools_save_cache_worker_settings',
),
'watermark_save' => array(
'handler' => 'bratonien_tools_save_watermark',
),
'watermark_file_delete' => array(
'handler' => 'bratonien_tools_delete_watermark_file',
),
'watermark_engine' => array(
'handler' => 'bratonien_tools_handle_watermark_engine',
),
'watermark_profile_save' => array(
'handler' => 'bratonien_tools_save_watermark_profile',
),
'watermark_profile_delete' => array(
'handler' => 'bratonien_tools_delete_watermark_profile',
),
'watermark_profile_duplicate' => array(
'handler' => 'bratonien_tools_duplicate_watermark_profile',
),
'watermark_defaults' => array(
'handler' => 'bratonien_tools_save_watermark_defaults',
),
'watermark_rule' => array(
'handler' => 'bratonien_tools_save_album_rule',
),
'public_selection_settings' => array(
'handler' => 'bratonien_tools_save_public_selection_settings',
),
'asset_upload' => array(
'handler' => 'bratonien_tools_upload_asset',
),
'asset_delete' => array(
'handler' => 'bratonien_tools_delete_asset',
),
'asset_upload_limits' => array(
'handler' => 'bratonien_tools_save_upload_limits',
),
'album_lock_toggle' => array(
'handler' => 'bratonien_tools_toggle_album_lock',
),
'album_share_create' => array(
'handler' => 'bratonien_tools_create_album_share',
),
'album_share_regenerate_link' => array(
'handler' => 'bratonien_tools_regenerate_album_share_link',
),
'album_share_revoke' => array(
'handler' => 'bratonien_tools_revoke_album_share',
),
'nc_connector_create_local' => array(
'handler' => 'bratonien_tools_nc_connector_create_local_api_first',
),
'nc_connector_delete' => array(
'handler' => 'bratonien_tools_nc_connector_delete',
),
'nc_connector_import_legacy' => array(
'handler' => 'bratonien_tools_nc_connector_import_legacy',
),
'nc_connector_verify' => array(
'handler' => 'bratonien_tools_nc_connector_verify_managed',
),
'nc_connector_prepare_takeover' => array(
'handler' => 'bratonien_tools_nc_connector_prepare_takeover',
),
'nc_connector_cancel_takeover' => array(
'handler' => 'bratonien_tools_nc_connector_cancel_takeover',
),
'nc_connector_piwigo_api_test' => array(
'handler' => 'bratonien_tools_nc_connector_piwigo_api_test',
),
'nc_connector_piwigo_api_delete' => array(
'handler' => 'bratonien_tools_nc_connector_api_delete',
),
'nc_connector_fallback_save' => array(
'handler' => 'bratonien_tools_nc_connector_fallback_save',
),
'nc_connector_fallback_delete' => array(
'handler' => 'bratonien_tools_nc_connector_fallback_delete',
),
'nc_connector_fallback_once' => array(
'handler' => 'bratonien_tools_nc_connector_fallback_once',
),
'self_update_check' => array(
'handler' => 'bratonien_tools_self_update_check',
),
'self_update_run' => array(
'handler' => 'bratonien_tools_self_update_run',
),
'image_cache_clear' => array('handler' => 'bratonien_tools_clear_image_cache'),
'image_cache_build' => array('handler' => 'bratonien_tools_start_main_cache_build'),
'image_cache_cancel' => array('handler' => 'bratonien_tools_cancel_main_cache_build'),
'image_cache_worker_settings' => array('handler' => 'bratonien_tools_save_cache_worker_settings'),
'watermark_save' => array('handler' => 'bratonien_tools_save_watermark'),
'watermark_file_delete' => array('handler' => 'bratonien_tools_delete_watermark_file'),
'watermark_engine' => array('handler' => 'bratonien_tools_handle_watermark_engine'),
'watermark_profile_save' => array('handler' => 'bratonien_tools_save_watermark_profile'),
'watermark_profile_delete' => array('handler' => 'bratonien_tools_delete_watermark_profile'),
'watermark_profile_duplicate' => array('handler' => 'bratonien_tools_duplicate_watermark_profile'),
'watermark_defaults' => array('handler' => 'bratonien_tools_save_watermark_defaults'),
'watermark_rule' => array('handler' => 'bratonien_tools_save_album_rule'),
'public_selection_settings' => array('handler' => 'bratonien_tools_save_public_selection_settings'),
'asset_upload' => array('handler' => 'bratonien_tools_upload_asset'),
'asset_delete' => array('handler' => 'bratonien_tools_delete_asset'),
'asset_upload_limits' => array('handler' => 'bratonien_tools_save_upload_limits'),
'album_lock_toggle' => array('handler' => 'bratonien_tools_toggle_album_lock'),
'album_share_create' => array('handler' => 'bratonien_tools_create_album_share'),
'album_share_regenerate_link' => array('handler' => 'bratonien_tools_regenerate_album_share_link'),
'album_share_revoke' => array('handler' => 'bratonien_tools_revoke_album_share'),
'nc_connector_create_local' => array('handler' => 'bratonien_tools_nc_connector_create_local_api_first'),
'nc_connector_delete' => array('handler' => 'bratonien_tools_nc_connector_delete'),
'nc_connector_update_name' => array('handler' => 'bratonien_tools_nc_connector_update_name'),
'nc_connector_update_technical' => array('handler' => 'bratonien_tools_nc_connector_update_technical'),
'nc_connector_wizard_scan' => array('handler' => 'bratonien_tools_nc_wizard_scan'),
'nc_connector_wizard_save_technical' => array('handler' => 'bratonien_tools_nc_wizard_save_technical'),
'nc_connector_wizard_save_mounts' => array('handler' => 'bratonien_tools_nc_wizard_save_mounts'),
'nc_connector_wizard_select_user' => array('handler' => 'bratonien_tools_nc_wizard_select_user'),
'nc_connector_wizard_api_test' => array('handler' => 'bratonien_tools_nc_wizard_api_test'),
'nc_connector_wizard_api_skip' => array('handler' => 'bratonien_tools_nc_wizard_api_skip'),
'nc_connector_wizard_finish' => array('handler' => 'bratonien_tools_nc_wizard_finish'),
'nc_connector_wizard_reset' => array('handler' => 'bratonien_tools_nc_wizard_reset'),
'nc_connector_import_legacy' => array('handler' => 'bratonien_tools_nc_connector_import_legacy'),
'nc_connector_verify' => array('handler' => 'bratonien_tools_nc_connector_verify_managed'),
'nc_connector_prepare_takeover' => array('handler' => 'bratonien_tools_nc_connector_prepare_takeover'),
'nc_connector_cancel_takeover' => array('handler' => 'bratonien_tools_nc_connector_cancel_takeover'),
'nc_connector_piwigo_api_test' => array('handler' => 'bratonien_tools_nc_connector_piwigo_api_test'),
'nc_connector_piwigo_api_delete' => array('handler' => 'bratonien_tools_nc_connector_api_delete'),
'nc_connector_fallback_save' => array('handler' => 'bratonien_tools_nc_connector_fallback_save'),
'nc_connector_fallback_delete' => array('handler' => 'bratonien_tools_nc_connector_fallback_delete'),
'nc_connector_fallback_once' => array('handler' => 'bratonien_tools_nc_connector_fallback_once'),
'self_update_check' => array('handler' => 'bratonien_tools_self_update_check'),
'self_update_run' => array('handler' => 'bratonien_tools_self_update_run'),
);
}

View File

@@ -1,7 +1,7 @@
<?php
/*
Plugin Name: Bratonien Tools
Version: 0.9.3.32
Version: 0.9.3.36
Description: Erweiterbare Administrationswerkzeuge fuer die Bratonien-Piwigo-Installation.
Plugin URI: https://github.com/Terranom674/Piwigo_Bratonien_Tools
Author: Bratonien

View File

@@ -40,90 +40,206 @@
</div>
<div class="bratonien-card" style="grid-column:1/-1">
<h4>Piwigo API bevorzugter Sync-Zugang</h4>
<p class="bratonien-base-note">Der NC Connector versucht bei jedem produktiven Lauf zuerst die Piwigo-API. Ein erfolgreich geprüfter API-Key wird verschlüsselt gespeichert. Die produktive API-Synchronisierung ist ausdrücklich an die im Plugin freigegebene Piwigo-Version gebunden. Ist diese Version nicht freigegeben oder die API nicht nutzbar, darf nur der Benutzername/Passwort-Fallback übernehmen.</p>
<form method="post">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<div class="bratonien-form-grid">
<label class="bratonien-label" for="nc_piwigo_api_key_id">API-Schlüssel-ID</label>
<input id="nc_piwigo_api_key_id" name="nc_piwigo_api_key_id" type="text" autocomplete="off" placeholder="pkid-..." required>
<label class="bratonien-label" for="nc_piwigo_api_key_secret">API-Geheimnis</label>
<input id="nc_piwigo_api_key_secret" name="nc_piwigo_api_key_secret" type="password" autocomplete="off" required>
</div>
<div class="bratonien-actions">
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_piwigo_api_test">API prüfen und speichern</button>
<button class="buttonLike bratonien-delete-button" type="submit" name="bratonien_tool" value="nc_connector_piwigo_api_delete" formnovalidate onclick="return confirm('Gespeicherte Piwigo-API-Zugangsdaten wirklich löschen?');">Gespeicherte API löschen</button>
</div>
</form>
<h4>Neue Verbindung</h4>
<p class="bratonien-base-note">Wähle den geführten Assistenten oder öffne die technische Einrichtung bewusst manuell.</p>
<div class="bratonien-actions">
<button class="buttonLike" type="button" id="bratonien-nc-wizard-open">Mit Assistent anlegen</button>
<button class="buttonLike" type="button" id="bratonien-nc-technical-open">Ohne Assistent anlegen</button>
</div>
{if isset($NC_CONNECTOR.piwigo_api_test) && $NC_CONNECTOR.piwigo_api_test}
<hr>
<h5>Prüfergebnis</h5>
<div class="bratonien-form-grid">
<span class="bratonien-label">Benutzer</span><strong>{$NC_CONNECTOR.piwigo_api_test.username|escape:html}</strong>
<span class="bratonien-label">Piwigo-Status</span><strong>{$NC_CONNECTOR.piwigo_api_test.status|escape:html}</strong>
<span class="bratonien-label">Administrator/Webmaster</span><strong>{if $NC_CONNECTOR.piwigo_api_test.admin}Ja{else}Nein{/if}</strong>
<span class="bratonien-label">Sichtbare API-Methoden</span><strong>{$NC_CONNECTOR.piwigo_api_test.method_count|escape:html}</strong>
<span class="bratonien-label">Bratonien-Sync-API</span><strong>{if $NC_CONNECTOR.piwigo_api_test.sync_api_detected}Bereit{else}Nicht bereit{/if}</strong>
</div>
<p class="bratonien-base-note"><strong>Bewertung:</strong> {$NC_CONNECTOR.piwigo_api_test.conclusion|escape:html}</p>
{/if}
</div>
<dialog id="bratonien-nc-wizard-dialog" style="width:min(1100px,calc(100vw - 3rem));max-height:88vh;overflow:auto;background:#444;color:inherit;border:1px solid #777;border-radius:4px;padding:0;box-shadow:0 18px 60px rgba(0,0,0,.55)">
<div style="padding:1.25rem 1.5rem">
<div style="display:flex;align-items:center;justify-content:space-between;gap:1rem;margin-bottom:1rem">
<div>
<h4 style="margin:0">Neue Verbindung</h4>
<p class="bratonien-base-note" style="margin:.35rem 0 0"><strong>Assistent · Schritt {$NC_CONNECTOR.wizard.step|escape:html} von 4</strong></p>
</div>
<button class="buttonLike" type="button" id="bratonien-nc-wizard-close">Schließen</button>
</div>
<div class="bratonien-card" style="grid-column:1/-1">
<h4>Benutzername/Passwort-Fallback</h4>
<p class="bratonien-base-note">Dieser Zugang wird nur verwendet, wenn die bevorzugte API nicht genutzt werden kann. Zugangsdaten können einmalig für einen manuellen Fallback verwendet, verschlüsselt dauerhaft hinterlegt oder vollständig gelöscht werden.</p>
{if $NC_CONNECTOR.connection_count > 0}
{if $NC_CONNECTOR.wizard.step == 1}
<p class="bratonien-base-note">Wir beginnen nur mit der Adresse der Nextcloud und einem Benutzer, mit dem die Instanz auf den Scan antworten kann.</p>
<form method="post" data-bratonien-wizard-form>
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<div class="bratonien-form-grid">
<label class="bratonien-label" for="nc_wizard_host">Nextcloud-Host</label>
<input id="nc_wizard_host" name="nc_wizard_host" type="text" placeholder="cloud.example.de" required>
<label class="bratonien-label" for="nc_wizard_user">Nextcloud-Benutzer</label>
<input id="nc_wizard_user" name="nc_wizard_user" type="text" autocomplete="username" required>
<label class="bratonien-label" for="nc_wizard_password">Passwort</label>
<input id="nc_wizard_password" name="nc_wizard_password" type="password" autocomplete="current-password" required>
</div>
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_scan">Verbinden und scannen</button></p>
</form>
{elseif $NC_CONNECTOR.wizard.step == 2}
<p class="bratonien-base-note"><strong>Nextcloud erkannt.</strong> Alles, was zuverlässig ermittelt werden konnte, wurde bereits übernommen.</p>
<div class="bratonien-form-grid">
<span class="bratonien-label">Nextcloud</span><strong>{$NC_CONNECTOR.wizard.base_url|escape:html}</strong>
<span class="bratonien-label">Produkt</span><strong>{$NC_CONNECTOR.wizard.product|escape:html}</strong>
<span class="bratonien-label">Version</span><strong>{if $NC_CONNECTOR.wizard.version}{$NC_CONNECTOR.wizard.version|escape:html}{else}nicht gemeldet{/if}</strong>
<span class="bratonien-label">Zugriff als</span><strong>{$NC_CONNECTOR.wizard.username|escape:html}{if $NC_CONNECTOR.wizard.display_name} · {$NC_CONNECTOR.wizard.display_name|escape:html}{/if}</strong>
<span class="bratonien-label">Technische Erkennung</span><strong>{$NC_CONNECTOR.wizard.technical_source|escape:html}</strong>
<span class="bratonien-label">Piwigo-Ziel</span><strong>{$NC_CONNECTOR.wizard.gallery_root|escape:html}</strong>
</div>
{if !$NC_CONNECTOR.wizard.db_password_set || !$NC_CONNECTOR.wizard.db_user}
<hr>
<h5>Für den Datenabgleich fehlt noch der Datenbankzugang</h5>
<p class="bratonien-base-note">Der Assistent konnte diese Angaben nicht sicher aus Nextcloud ableiten. Deshalb werden nur die fehlenden Werte abgefragt.</p>
<form method="post" data-bratonien-wizard-form>
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<div class="bratonien-form-grid">
<label class="bratonien-label">Verbindungsname</label><input name="nc_wizard_connection_name" type="text" value="{$NC_CONNECTOR.wizard.connection_name|escape:html}" required>
<label class="bratonien-label">Datenbank-Host</label><input name="nc_wizard_db_host" type="text" value="{$NC_CONNECTOR.wizard.db_host|escape:html}" required>
<label class="bratonien-label">Port</label><input name="nc_wizard_db_port" type="number" min="1" max="65535" value="{$NC_CONNECTOR.wizard.db_port|escape:html}" required>
<label class="bratonien-label">Datenbank</label><input name="nc_wizard_db_database" type="text" value="{$NC_CONNECTOR.wizard.db_database|escape:html}" required>
<label class="bratonien-label">Reader-Benutzer</label><input name="nc_wizard_db_user" type="text" value="{$NC_CONNECTOR.wizard.db_user|escape:html}" required>
<label class="bratonien-label">Reader-Passwort</label><input name="nc_wizard_db_password" type="password" autocomplete="new-password" required>
</div>
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_save_technical">Prüfen und automatisch ergänzen</button></p>
</form>
{elseif !$NC_CONNECTOR.wizard.technical_complete && $NC_CONNECTOR.wizard.storage_candidates|@count > 0}
<hr>
<h5>Einige Storages konnten nicht automatisch zugeordnet werden</h5>
<p class="bratonien-main-cache__warning">Mount-Pfade werden nur hier abgefragt, weil sie nicht sicher erkannt werden konnten. Bereits erkannte Zuordnungen sind gesperrt.</p>
<form method="post" data-bratonien-wizard-form>
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<div class="bratonien-form-grid">
{foreach from=$NC_CONNECTOR.wizard.storage_candidates item=storage key=storage_index}
<span class="bratonien-label">Storage {$storage.storage_id|escape:html} · {$storage.source_prefix|escape:html}</span>
{if $storage.local_mount}
<strong>{$storage.local_mount|escape:html}</strong>
<input type="hidden" name="nc_wizard_storage_mount[{$storage_index|escape:html}]" value="{$storage.local_mount|escape:html}">
{else}
<input name="nc_wizard_storage_mount[{$storage_index|escape:html}]" type="text" placeholder="/mnt/nextcloud/..." required>
{/if}
{/foreach}
</div>
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_save_mounts">Zuordnung prüfen</button></p>
</form>
{elseif $NC_CONNECTOR.wizard.technical_complete}
<hr>
<h5>Erkannte Verbindung</h5>
<div class="bratonien-form-grid">
<span class="bratonien-label">Datenbank</span><strong>{$NC_CONNECTOR.wizard.db_host|escape:html}:{$NC_CONNECTOR.wizard.db_port|escape:html} / {$NC_CONNECTOR.wizard.db_database|escape:html}</strong>
<span class="bratonien-label">Reader</span><strong>{$NC_CONNECTOR.wizard.db_user|escape:html}</strong>
<span class="bratonien-label">Source-View</span><strong>{$NC_CONNECTOR.wizard.source_view|escape:html}</strong>
<span class="bratonien-label">Activity-View</span><strong>{$NC_CONNECTOR.wizard.activity_view|escape:html}</strong>
<span class="bratonien-label">Storages</span><strong>{$NC_CONNECTOR.wizard.storages|@count}</strong>
</div>
<hr>
<h5>Welcher Benutzer stellt die Bilder bereit?</h5>
<p class="bratonien-base-note"><strong>Empfehlung:</strong> ein eigener Showcase-Benutzer. So bleibt die Connector-Quelle unabhängig von persönlichen Konten.</p>
<form method="post" data-bratonien-wizard-form>
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<div class="bratonien-form-grid">
<label class="bratonien-label" for="nc_wizard_connection_name">Verbindungsname</label>
<input id="nc_wizard_connection_name" name="nc_wizard_connection_name" type="text" value="{$NC_CONNECTOR.wizard.connection_name|escape:html}" required>
<label class="bratonien-label" for="nc_wizard_showcase_user">Showcase-Benutzer</label>
{if $NC_CONNECTOR.wizard.can_list_users && $NC_CONNECTOR.wizard.users|@count > 0}
<select id="nc_wizard_showcase_user" name="nc_wizard_showcase_user" required>
{foreach from=$NC_CONNECTOR.wizard.users item=nc_user}
<option value="{$nc_user|escape:html}"{if $nc_user == 'showcase'} selected{/if}>{$nc_user|escape:html}{if $nc_user == 'showcase'} · empfohlen{/if}</option>
{/foreach}
</select>
{else}
<input id="nc_wizard_showcase_user" name="nc_wizard_showcase_user" type="text" value="showcase" required>
{/if}
</div>
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_select_user">Weiter zur Piwigo-API</button></p>
</form>
{/if}
<form method="post" style="margin-top:1rem" data-bratonien-wizard-form>
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_reset" data-bratonien-wizard-end>Neu beginnen</button>
</form>
{elseif $NC_CONNECTOR.wizard.step == 3}
<p class="bratonien-base-note"><strong>Nextcloud verbunden.</strong> Showcase-Benutzer: <strong>{$NC_CONNECTOR.wizard.showcase_user|escape:html}</strong></p>
<h5>Piwigo-API</h5>
<p class="bratonien-base-note">Die API ist der bevorzugte Weg. Du kannst neue Zugangsdaten eingeben, einen bereits gespeicherten API-Zugang testen oder diesen Schritt überspringen.</p>
{if $NC_CONNECTOR.wizard.api_error}
<p class="bratonien-main-cache__warning"><strong>Letzter API-Test:</strong> {$NC_CONNECTOR.wizard.api_error|escape:html}</p>
{/if}
<form method="post" data-bratonien-wizard-form>
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<div class="bratonien-form-grid">
<label class="bratonien-label" for="nc_wizard_api_key_id">API-Schlüssel-ID</label>
<input id="nc_wizard_api_key_id" name="nc_wizard_api_key_id" type="text" autocomplete="off" placeholder="leer = gespeicherten Zugang verwenden">
<label class="bratonien-label" for="nc_wizard_api_key_secret">API-Geheimnis</label>
<input id="nc_wizard_api_key_secret" name="nc_wizard_api_key_secret" type="password" autocomplete="off" placeholder="leer = gespeicherten Zugang verwenden">
</div>
<div class="bratonien-actions">
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_api_test">API testen</button>
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_api_skip" formnovalidate>API überspringen</button>
</div>
</form>
{elseif $NC_CONNECTOR.wizard.step == 4}
<p class="bratonien-base-note"><strong>Abschluss.</strong> Nextcloud und technische Verbindung sind vorbereitet.</p>
<div class="bratonien-form-grid">
<span class="bratonien-label">Verbindung</span><strong>{$NC_CONNECTOR.wizard.connection_name|escape:html}</strong>
<span class="bratonien-label">Nextcloud</span><strong>{$NC_CONNECTOR.wizard.base_url|escape:html}</strong>
<span class="bratonien-label">Showcase-Benutzer</span><strong>{$NC_CONNECTOR.wizard.showcase_user|escape:html}</strong>
<span class="bratonien-label">Piwigo-API</span><strong>{if $NC_CONNECTOR.wizard.api_status == 'ok'}Erfolgreich getestet{if $NC_CONNECTOR.wizard.api_username} · {$NC_CONNECTOR.wizard.api_username|escape:html}{/if}{else}Übersprungen{/if}</strong>
</div>
<hr>
<h5>Fallback</h5>
{if $NC_CONNECTOR.wizard.api_status == 'ok'}
<p class="bratonien-base-note">Optional. Der Fallback wird nur benötigt, wenn die API später nicht nutzbar ist.</p>
{else}
<p class="bratonien-main-cache__warning">Die API wurde übersprungen. Deshalb ist ein Fallback-Zugang erforderlich.</p>
{/if}
<form method="post" data-bratonien-wizard-form>
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<div class="bratonien-form-grid">
<label class="bratonien-label" for="nc_wizard_fallback_user">Piwigo-Benutzer</label>
<input id="nc_wizard_fallback_user" name="nc_wizard_fallback_user" type="text" autocomplete="username"{if $NC_CONNECTOR.wizard.api_status != 'ok'} required{/if}>
<label class="bratonien-label" for="nc_wizard_fallback_password">Piwigo-Passwort</label>
<input id="nc_wizard_fallback_password" name="nc_wizard_fallback_password" type="password" autocomplete="current-password"{if $NC_CONNECTOR.wizard.api_status != 'ok'} required{/if}>
</div>
<div class="bratonien-actions">
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_finish" data-bratonien-wizard-end>Verbindung anlegen</button>
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_reset" formnovalidate data-bratonien-wizard-end>Abbrechen</button>
</div>
</form>
{/if}
</div>
</dialog>
<details id="bratonien-nc-technical-create" style="margin-top:1.5rem">
<summary style="display:none">Ohne Assistent anlegen</summary>
<p class="bratonien-main-cache__warning">Technische Einrichtung. Falsche Storage- oder Galeriepfade können die Synchronisierung auf den falschen Datenbestand richten.</p>
<form method="post">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<div class="bratonien-form-grid">
<label class="bratonien-label" for="nc_fallback_connection">Verbindung</label>
<select id="nc_fallback_connection" name="connection_id" required>
{foreach from=$NC_CONNECTOR.connections item=connection}
<option value="{$connection.id|escape:html}">{$connection.name|escape:html}</option>
{/foreach}
</select>
<label class="bratonien-label" for="nc_fallback_user">Piwigo-Benutzer</label>
<input id="nc_fallback_user" name="nc_fallback_user" type="text" autocomplete="username">
<label class="bratonien-label" for="nc_fallback_password">Piwigo-Passwort</label>
<input id="nc_fallback_password" name="nc_fallback_password" type="password" autocomplete="current-password">
</div>
<div class="bratonien-actions">
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_fallback_once">Einmalig verwenden</button>
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_fallback_save">Fest speichern</button>
<button class="buttonLike bratonien-delete-button" type="submit" name="bratonien_tool" value="nc_connector_fallback_delete" formnovalidate onclick="return confirm('Gespeicherten Benutzername/Passwort-Fallback wirklich löschen?');">Gespeicherten Fallback löschen</button>
<label class="bratonien-label" for="nc_name">Name</label><input id="nc_name" name="nc_name" type="text" required>
<label class="bratonien-label" for="nc_host">PostgreSQL-Host</label><input id="nc_host" name="nc_host" type="text" required>
<label class="bratonien-label" for="nc_port">PostgreSQL-Port</label><input id="nc_port" name="nc_port" type="number" min="1" max="65535" value="5432" required>
<label class="bratonien-label" for="nc_database">Datenbank</label><input id="nc_database" name="nc_database" type="text" value="nextcloud" required>
<label class="bratonien-label" for="nc_user">Reader-Benutzer</label><input id="nc_user" name="nc_user" type="text" required>
<label class="bratonien-label" for="nc_db_password">Reader-Passwort</label><input id="nc_db_password" name="nc_db_password" type="password" autocomplete="new-password" required>
<label class="bratonien-label" for="nc_source_view">Source-View</label><input id="nc_source_view" name="nc_source_view" type="text" value="piwigo_showcase_sources" required>
<label class="bratonien-label" for="nc_activity_view">Activity-View</label><input id="nc_activity_view" name="nc_activity_view" type="text" value="piwigo_showcase_activity" required>
<label class="bratonien-label" for="nc_gallery_root">Piwigo-Galeriepfad</label><input id="nc_gallery_root" name="nc_gallery_root" type="text" placeholder="/var/www/piwigo/galleries/nextcloud" required>
<label class="bratonien-label" for="nc_piwigo_user">Piwigo-Fallback-Benutzer</label><input id="nc_piwigo_user" name="nc_piwigo_user" type="text">
<label class="bratonien-label" for="nc_piwigo_password">Piwigo-Fallback-Passwort</label><input id="nc_piwigo_password" name="nc_piwigo_password" type="password" autocomplete="new-password">
<label class="bratonien-label" for="nc_quiet_seconds">Ruhezeit</label><input id="nc_quiet_seconds" name="nc_quiet_seconds" type="number" min="0" value="120">
<label class="bratonien-label" for="nc_max_wait_seconds">Maximale Wartezeit</label><input id="nc_max_wait_seconds" name="nc_max_wait_seconds" type="number" min="60" value="900">
<label class="bratonien-label" for="nc_full_sync_seconds">Vollprüfung nach</label><input id="nc_full_sync_seconds" name="nc_full_sync_seconds" type="number" min="300" value="86400">
</div>
<p><label for="nc_storages"><strong>Storage-Zuordnungen</strong></label></p>
<p class="bratonien-base-note">Eine Zeile pro Storage: <code>storage_id | source_prefix | /lokaler/mount</code></p>
<textarea id="nc_storages" name="nc_storages" rows="4" style="width:100%" required></textarea>
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_create_local">Verbindung anlegen</button></p>
</form>
{else}
<p class="bratonien-base-note">Noch keine Connector-Verbindung vorhanden.</p>
{/if}
</div>
<div class="bratonien-card" style="grid-column:1/-1">
<h4>Neue lokale Verbindung</h4>
<form method="post">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<div class="bratonien-form-grid">
<label class="bratonien-label" for="nc_name">Name</label><input id="nc_name" name="nc_name" type="text" required>
<label class="bratonien-label" for="nc_host">PostgreSQL-Host</label><input id="nc_host" name="nc_host" type="text" required>
<label class="bratonien-label" for="nc_port">PostgreSQL-Port</label><input id="nc_port" name="nc_port" type="number" min="1" max="65535" value="5432" required>
<label class="bratonien-label" for="nc_database">Datenbank</label><input id="nc_database" name="nc_database" type="text" value="nextcloud" required>
<label class="bratonien-label" for="nc_user">Reader-Benutzer</label><input id="nc_user" name="nc_user" type="text" required>
<label class="bratonien-label" for="nc_db_password">Reader-Passwort</label><input id="nc_db_password" name="nc_db_password" type="password" autocomplete="new-password" required>
<label class="bratonien-label" for="nc_source_view">Source-View</label><input id="nc_source_view" name="nc_source_view" type="text" value="piwigo_showcase_sources" required>
<label class="bratonien-label" for="nc_activity_view">Activity-View</label><input id="nc_activity_view" name="nc_activity_view" type="text" value="piwigo_showcase_activity" required>
<label class="bratonien-label" for="nc_gallery_root">Piwigo-Galeriepfad</label><input id="nc_gallery_root" name="nc_gallery_root" type="text" placeholder="/var/www/piwigo/galleries/nextcloud" required>
<label class="bratonien-label" for="nc_piwigo_user">Piwigo-Fallback-Benutzer</label><input id="nc_piwigo_user" name="nc_piwigo_user" type="text" required>
<label class="bratonien-label" for="nc_piwigo_password">Piwigo-Fallback-Passwort</label><input id="nc_piwigo_password" name="nc_piwigo_password" type="password" autocomplete="new-password" required>
<label class="bratonien-label" for="nc_quiet_seconds">Ruhezeit</label><input id="nc_quiet_seconds" name="nc_quiet_seconds" type="number" min="0" value="120">
<label class="bratonien-label" for="nc_max_wait_seconds">Maximale Wartezeit</label><input id="nc_max_wait_seconds" name="nc_max_wait_seconds" type="number" min="60" value="900">
<label class="bratonien-label" for="nc_full_sync_seconds">Vollprüfung nach</label><input id="nc_full_sync_seconds" name="nc_full_sync_seconds" type="number" min="300" value="86400">
</div>
<p><label for="nc_storages"><strong>Storage-Zuordnungen</strong></label></p>
<p class="bratonien-base-note">Eine Zeile pro Storage: <code>storage_id | source_prefix | /lokaler/mount</code></p>
<textarea id="nc_storages" name="nc_storages" rows="4" style="width:100%" required></textarea>
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_create_local">Verbindung anlegen</button></p>
</form>
</details>
</div>
<div class="bratonien-card" style="grid-column:1/-1">
@@ -131,100 +247,80 @@
{if $NC_CONNECTOR.connection_count > 0}
<table class="table2">
<thead>
<tr>
<th>Name</th>
<th>Adapter</th>
<th>Host</th>
<th>Quelle</th>
<th>Storages</th>
<th>Status</th>
<th>Aktion</th>
</tr>
<tr><th>Name</th><th>Adapter</th><th>Host</th><th>Quelle</th><th>Storages</th><th>Status</th><th>Aktion</th></tr>
</thead>
<tbody>
{foreach from=$NC_CONNECTOR.connections item=connection}
<tr>
<td>{$connection.name|escape:html}</td>
<td>{$connection.display_name|escape:html}</td>
<td>{$connection.adapter|escape:html}</td>
<td>{if $connection.host}{$connection.host|escape:html}{else}{/if}</td>
<td>{if $connection.source_view}{$connection.source_view|escape:html}{else}{/if}</td>
<td>{$connection.storage_count|escape:html}</td>
<td>{$connection.takeover_state|escape:html}{if $connection.enabled} · aktiv{/if}</td>
<td>
{if $connection.takeover_state == 'active'}
<code>php /var/www/piwigo/plugins/bratonien_tools/nc-connector-disable.php {$connection.id|escape:html}</code>
{elseif $connection.takeover_state == 'verified' && isset($connection.config.origin) && $connection.config.origin == 'native'}
<code>php /var/www/piwigo/plugins/bratonien_tools/nc-connector-install.php {$connection.id|escape:html}</code>
<form method="post" class="bratonien-actions">
<details>
<summary>Verwalten</summary>
<form method="post" class="bratonien-actions" style="margin-top:.75rem">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<input type="hidden" name="connection_id" value="{$connection.id|escape:html}">
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_verify">Erneut prüfen</button>
<input name="connection_name" type="text" value="{$connection.name|escape:html}" required>
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_update_name">Name speichern</button>
</form>
{elseif $connection.takeover_state == 'ready'}
<form method="post" class="bratonien-actions">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<input type="hidden" name="connection_id" value="{$connection.id|escape:html}">
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_cancel_takeover">Vorbereitung zurücknehmen</button>
</form>
{elseif $connection.takeover_state == 'verified' && !$connection.enabled}
<form method="post" class="bratonien-actions">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<input type="hidden" name="connection_id" value="{$connection.id|escape:html}">
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_prepare_takeover">Übergabe vorbereiten</button>
</form>
{elseif $connection.adapter == 'local' && !$connection.enabled}
<form method="post" class="bratonien-actions">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<input type="hidden" name="connection_id" value="{$connection.id|escape:html}">
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_verify">Verbindung prüfen</button>
</form>
<form method="post" class="bratonien-actions">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<input type="hidden" name="connection_id" value="{$connection.id|escape:html}">
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_delete">Löschen</button>
</form>
{else}
{/if}
<div class="bratonien-actions">
{if $connection.adapter == 'local' && !$connection.enabled}
<form method="post">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<input type="hidden" name="connection_id" value="{$connection.id|escape:html}">
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_verify">Verbindung prüfen</button>
</form>
{/if}
{if !$connection.enabled && $connection.takeover_state != 'active'}
<form method="post">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<input type="hidden" name="connection_id" value="{$connection.id|escape:html}">
<button class="buttonLike bratonien-delete-button" type="submit" name="bratonien_tool" value="nc_connector_delete" onclick="return confirm('Verbindung wirklich löschen? Es werden nur die Connector-Einstellungen entfernt, keine Nextcloud- oder Piwigo-Bilder.');">Löschen</button>
</form>
{/if}
</div>
<details style="margin-top:.75rem">
<summary>Technische Einstellungen</summary>
{if $connection.enabled || $connection.takeover_state == 'active'}
<p class="bratonien-main-cache__warning">Eine aktive Verbindung muss vor technischen Änderungen deaktiviert werden.</p>
{else}
<p class="bratonien-main-cache__warning">Nur ändern, wenn die technische Zuordnung bekannt ist. Storage- und Galeriepfade sind sicherheitskritisch.</p>
<form method="post">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<input type="hidden" name="connection_id" value="{$connection.id|escape:html}">
<div class="bratonien-form-grid">
<label class="bratonien-label">PostgreSQL-Host</label><input name="nc_host" type="text" value="{$connection.config.host|escape:html}" required>
<label class="bratonien-label">Port</label><input name="nc_port" type="number" min="1" max="65535" value="{$connection.config.port|escape:html}" required>
<label class="bratonien-label">Datenbank</label><input name="nc_database" type="text" value="{$connection.config.database|escape:html}" required>
<label class="bratonien-label">Reader-Benutzer</label><input name="nc_user" type="text" value="{$connection.config.user|escape:html}" required>
<label class="bratonien-label">Reader-Passwort</label><input name="nc_db_password" type="password" placeholder="leer = unverändert">
<label class="bratonien-label">Source-View</label><input name="nc_source_view" type="text" value="{$connection.config.source_view|escape:html}" required>
<label class="bratonien-label">Activity-View</label><input name="nc_activity_view" type="text" value="{$connection.config.activity_view|escape:html}" required>
<label class="bratonien-label">Piwigo-Galeriepfad</label><input name="nc_gallery_root" type="text" value="{$connection.config.gallery_root|escape:html}" required>
<label class="bratonien-label">Ruhezeit</label><input name="nc_quiet_seconds" type="number" min="0" value="{$connection.config.quiet_seconds|escape:html}">
<label class="bratonien-label">Maximale Wartezeit</label><input name="nc_max_wait_seconds" type="number" min="60" value="{$connection.config.max_wait_seconds|escape:html}">
<label class="bratonien-label">Vollprüfung nach</label><input name="nc_full_sync_seconds" type="number" min="300" value="{$connection.config.full_sync_seconds|escape:html}">
</div>
<p><strong>Storage-Zuordnungen</strong></p>
<textarea name="nc_storages" rows="4" style="width:100%" required>{$connection.storage_text|escape:html}</textarea>
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_update_technical">Technische Einstellungen speichern</button></p>
</form>
{/if}
</details>
</details>
</td>
</tr>
{if $connection.verification_checks|@count > 0}
<tr>
<td colspan="7">
<strong>Letzte Verifikation{if $connection.verified_at} · {$connection.verified_at|escape:html}{/if}</strong>
<ul>
{foreach from=$connection.verification_checks item=check}
<li>{if $check.ok}{else}{/if} <strong>{$check.name|escape:html}:</strong> {$check.detail|escape:html}</li>
{/foreach}
</ul>
{if $connection.takeover_state == 'active'}
<p class="bratonien-base-note"><strong>Connector aktiv.</strong> State: <code>{$connection.config.state_dir|escape:html}</code></p>
{elseif $connection.takeover_state == 'verified' && isset($connection.config.origin) && $connection.config.origin == 'native'}
<p class="bratonien-base-note">Verifiziert. Der angezeigte Root-Befehl richtet Runtime-Konfiguration, State-Verzeichnis und gemeinsamen systemd-Timer ein und führt vor der Aktivierung einen Testlauf aus.</p>
{/if}
</td>
</tr>
<tr><td colspan="7"><strong>Letzte Verifikation{if $connection.verified_at} · {$connection.verified_at|escape:html}{/if}</strong><ul>{foreach from=$connection.verification_checks item=check}<li>{if $check.ok}{else}{/if} <strong>{$check.name|escape:html}:</strong> {$check.detail|escape:html}</li>{/foreach}</ul></td></tr>
{/if}
{if isset($connection.last_sync) && $connection.last_sync.timestamp > 0}
<tr>
<td colspan="7">
<strong>Letzter Sync · {$connection.last_sync.label|escape:html}</strong>
<p class="bratonien-base-note"><strong>Ergebnis:</strong> {$connection.last_sync.message|escape:html}</p>
{if $connection.last_sync.api_state == 'ok'}
<p class="bratonien-base-note"><strong>API:</strong> erfolgreich</p>
{elseif $connection.last_sync.api_state == 'error'}
<p class="bratonien-main-cache__warning"><strong>API fehlgeschlagen:</strong> {$connection.last_sync.api_message|escape:html}</p>
{/if}
{if $connection.last_sync.fallback_state == 'ok'}
<p class="bratonien-base-note"><strong>Fallback:</strong> erfolgreich</p>
{elseif $connection.last_sync.fallback_state == 'error'}
<p class="bratonien-main-cache__warning"><strong>Fallback fehlgeschlagen:</strong> {$connection.last_sync.fallback_message|escape:html}</p>
{/if}
{if $connection.last_sync.error_detail}
<p class="bratonien-main-cache__warning"><strong>Technischer Fehler:</strong> {$connection.last_sync.error_detail|escape:html}</p>
{/if}
</td>
</tr>
<tr><td colspan="7"><strong>Letzter Sync · {$connection.last_sync.label|escape:html}</strong><p class="bratonien-base-note"><strong>Ergebnis:</strong> {$connection.last_sync.message|escape:html}</p>{if $connection.last_sync.api_state == 'error'}<p class="bratonien-main-cache__warning"><strong>API fehlgeschlagen:</strong> {$connection.last_sync.api_message|escape:html}</p>{/if}{if $connection.last_sync.fallback_state == 'error'}<p class="bratonien-main-cache__warning"><strong>Fallback fehlgeschlagen:</strong> {$connection.last_sync.fallback_message|escape:html}</p>{/if}{if $connection.last_sync.error_detail}<p class="bratonien-main-cache__warning"><strong>Technischer Fehler:</strong> {$connection.last_sync.error_detail|escape:html}</p>{/if}</td></tr>
{/if}
{/foreach}
</tbody>
@@ -234,32 +330,99 @@
{/if}
</div>
<div class="bratonien-card">
<h4>Sync-Zustand</h4>
<div class="bratonien-form-grid">
<span class="bratonien-label">Connector-Timer</span><strong>{if $nc_system_available && $NC_CONNECTOR.system.timer_active}Aktiv{else}Nicht aktiv{/if}</strong>
<span class="bratonien-label">Letzter Lauf</span><strong>{if $nc_system_available}{$NC_CONNECTOR.system.last_run_label|escape:html}{else}Nicht verfügbar{/if}</strong>
<span class="bratonien-label">Nächster Lauf</span><strong>{if $nc_system_available}{$NC_CONNECTOR.system.next_run_label|escape:html}{else}Nicht verfügbar{/if}</strong>
</div>
{if $nc_system_available && $NC_CONNECTOR.system.last_run_message}
<p class="bratonien-base-note"><strong>Ergebnis:</strong> {$NC_CONNECTOR.system.last_run_message|escape:html}</p>
{/if}
{if $nc_system_available && $NC_CONNECTOR.system.last_run_api_state == 'error'}
<p class="bratonien-main-cache__warning"><strong>API fehlgeschlagen:</strong> {$NC_CONNECTOR.system.last_run_api_message|escape:html}</p>
{/if}
{if $nc_system_available && $NC_CONNECTOR.system.last_run_fallback_state == 'ok'}
<p class="bratonien-base-note"><strong>Fallback:</strong> erfolgreich</p>
{elseif $nc_system_available && $NC_CONNECTOR.system.last_run_fallback_state == 'error'}
<p class="bratonien-main-cache__warning"><strong>Fallback fehlgeschlagen:</strong> {$NC_CONNECTOR.system.last_run_fallback_message|escape:html}</p>
{/if}
{if $nc_system_available && $NC_CONNECTOR.system.last_run_error_detail}
<p class="bratonien-main-cache__warning"><strong>Technischer Fehler:</strong> {$NC_CONNECTOR.system.last_run_error_detail|escape:html}</p>
<div class="bratonien-card" style="grid-column:1/-1">
<h4>Piwigo API bevorzugter Sync-Zugang</h4>
<p class="bratonien-base-note">Hier kann der globale API-Zugang unabhängig vom Assistenten geprüft, ersetzt oder gelöscht werden.</p>
<form method="post">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<div class="bratonien-form-grid">
<label class="bratonien-label" for="nc_piwigo_api_key_id">API-Schlüssel-ID</label><input id="nc_piwigo_api_key_id" name="nc_piwigo_api_key_id" type="text" autocomplete="off" placeholder="pkid-..." required>
<label class="bratonien-label" for="nc_piwigo_api_key_secret">API-Geheimnis</label><input id="nc_piwigo_api_key_secret" name="nc_piwigo_api_key_secret" type="password" autocomplete="off" required>
</div>
<div class="bratonien-actions">
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_piwigo_api_test">API prüfen und speichern</button>
<button class="buttonLike bratonien-delete-button" type="submit" name="bratonien_tool" value="nc_connector_piwigo_api_delete" formnovalidate onclick="return confirm('Gespeicherte Piwigo-API-Zugangsdaten wirklich löschen?');">Gespeicherte API löschen</button>
</div>
</form>
{if isset($NC_CONNECTOR.piwigo_api_test) && $NC_CONNECTOR.piwigo_api_test}
<p class="bratonien-base-note"><strong>Bewertung:</strong> {$NC_CONNECTOR.piwigo_api_test.conclusion|escape:html}</p>
{/if}
</div>
<div class="bratonien-card">
<h4>Neuinstallation</h4>
<p class="bratonien-base-note">Eine frische Installation benötigt keinen Legacy-Sync. Ablauf: Verbindung anlegen → prüfen → Root-Aktivierung ausführen. Danach arbeitet ausschließlich die Plugin-Runtime.</p>
<div class="bratonien-card" style="grid-column:1/-1">
<h4>Benutzername/Passwort-Fallback</h4>
{if $NC_CONNECTOR.connection_count > 0}
<form method="post">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<div class="bratonien-form-grid">
<label class="bratonien-label" for="nc_fallback_connection">Verbindung</label><select id="nc_fallback_connection" name="connection_id" required>{foreach from=$NC_CONNECTOR.connections item=connection}<option value="{$connection.id|escape:html}">{$connection.name|escape:html}</option>{/foreach}</select>
<label class="bratonien-label" for="nc_fallback_user">Piwigo-Benutzer</label><input id="nc_fallback_user" name="nc_fallback_user" type="text" autocomplete="username">
<label class="bratonien-label" for="nc_fallback_password">Piwigo-Passwort</label><input id="nc_fallback_password" name="nc_fallback_password" type="password" autocomplete="current-password">
</div>
<div class="bratonien-actions">
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_fallback_once">Einmalig verwenden</button>
<button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_fallback_save">Fest speichern</button>
<button class="buttonLike bratonien-delete-button" type="submit" name="bratonien_tool" value="nc_connector_fallback_delete" formnovalidate onclick="return confirm('Gespeicherten Benutzername/Passwort-Fallback wirklich löschen?');">Gespeicherten Fallback löschen</button>
</div>
</form>
{else}<p class="bratonien-base-note">Noch keine Connector-Verbindung vorhanden.</p>{/if}
</div>
</div>
<script>
(function () {
var dialog = document.getElementById('bratonien-nc-wizard-dialog');
var openButton = document.getElementById('bratonien-nc-wizard-open');
var closeButton = document.getElementById('bratonien-nc-wizard-close');
var technicalButton = document.getElementById('bratonien-nc-technical-open');
var technical = document.getElementById('bratonien-nc-technical-create');
var storageKey = 'bratonienNcWizardOpen';
if (openButton && dialog) {
openButton.addEventListener('click', function () {
sessionStorage.setItem(storageKey, '1');
if (typeof dialog.showModal === 'function') dialog.showModal();
else dialog.setAttribute('open', 'open');
});
}
if (closeButton && dialog) {
closeButton.addEventListener('click', function () {
sessionStorage.removeItem(storageKey);
if (typeof dialog.close === 'function') dialog.close();
else dialog.removeAttribute('open');
});
}
if (dialog) {
dialog.addEventListener('cancel', function () {
sessionStorage.removeItem(storageKey);
});
dialog.addEventListener('click', function (event) {
if (event.target === dialog) {
sessionStorage.removeItem(storageKey);
if (typeof dialog.close === 'function') dialog.close();
}
});
dialog.querySelectorAll('form[data-bratonien-wizard-form]').forEach(function (form) {
form.addEventListener('submit', function (event) {
var submitter = event.submitter;
if (submitter && submitter.hasAttribute('data-bratonien-wizard-end')) sessionStorage.removeItem(storageKey);
else sessionStorage.setItem(storageKey, '1');
});
});
if (sessionStorage.getItem(storageKey) === '1') {
if (typeof dialog.showModal === 'function' && !dialog.open) dialog.showModal();
else dialog.setAttribute('open', 'open');
}
}
if (technicalButton && technical) {
technicalButton.addEventListener('click', function () {
technical.open = !technical.open;
if (technical.open) technical.scrollIntoView({behavior:'smooth', block:'start'});
});
}
}());
</script>
</section>