Compare commits

...

5 Commits

Author SHA1 Message Date
Terranom674
65de8020e9 Update NC wizard status for 0.9.4.4 2026-08-18 11:16:49 +02:00
Terranom674
b264f6cf8c Document unified Nextcloud and database wizard credentials 2026-08-18 11:16:27 +02:00
Terranom674
efeebf9d56 Bump Bratonien Tools to 0.9.4.4 2026-08-18 11:15:48 +02:00
Terranom674
77988184a3 Remove duplicate database credentials from NC wizard 2026-08-18 11:15:27 +02:00
Terranom674
a1373aa6a1 Reuse Nextcloud credentials for database access in wizard 2026-08-18 11:14:20 +02:00
5 changed files with 600 additions and 308 deletions

View File

@@ -4,7 +4,7 @@ Stand: 18.08.2026
## Plugin ## Plugin
- Aktuelle Plugin-Version: **0.9.4.2** - Aktuelle Plugin-Version: **0.9.4.4**
- Aktueller Entwicklungsblock: **NC Connector Endnutzer-Assistent und Konsolidierung** - Aktueller Entwicklungsblock: **NC Connector Endnutzer-Assistent und Konsolidierung**
- `0.9.3.43` markiert den abgeschlossenen vorherigen Meilenstein. - `0.9.3.43` markiert den abgeschlossenen vorherigen Meilenstein.
- `0.9.4.x` ist der neue Entwicklungsblock. - `0.9.4.x` ist der neue Entwicklungsblock.
@@ -30,35 +30,36 @@ Die Statusaktualisierung im Admin-UI wurde von Seiten-Reload auf gezielte DOM-Ak
### Verbindungsassistent ### Verbindungsassistent
Der Assistent wurde erneut aus Endnutzersicht konsolidiert: Der Assistent wurde aus Endnutzersicht konsolidiert. Technische Werte werden nur gezielt abgefragt, wenn eine automatische Prüfung sie nicht bestätigen kann.
1. Nextcloud-Adresse + Benutzer + Passwort.
2. Automatischer Web-Scan über HTTP/HTTPS und OCS.
3. Der Nutzer sieht zunächst keine komplette Datenbank-Technikmaske.
4. Für den Datenzugriff werden zuerst nur Lese-Benutzer und Passwort abgefragt.
5. Host = Nextcloud-Host, Port 5432 und Datenbank `nextcloud` werden nur als automatischer Prüfversuch verwendet und nicht als sicher erkannt dargestellt.
6. Scheitert dieser Versuch, fragt der Assistent gezielt die abweichende Datenbank-Adresse, Port und Datenbank ab.
7. Storage-Mounts werden nur abgefragt, wenn sie nicht sicher aus vorhandenen Zuordnungen übernommen werden können.
8. Danach folgen Showcase-Benutzer, Piwigo-API-Test/Skip und Fallback.
9. Erst der letzte Schritt legt die Verbindung dauerhaft an.
Fehler leeren die Wizard-Eingaben nicht. Geheimnisse bleiben während des Wizards serverseitig in der PHP-Sitzung und werden nicht im Browser-Web-Storage persistiert.
### Storage-Mappings ### Storage-Mappings
Das Storage-Format erlaubt jetzt einen leeren `source_prefix`. Damit kann ein kompletter Storage direkt auf einen lokalen Mount zeigen. Die frühere Fehlermeldung Das Storage-Format erlaubt einen leeren `source_prefix`. Damit kann ein kompletter Storage direkt auf einen lokalen Mount zeigen.
`Storage-Zeilen muessen das Format storage_id | source_prefix | local_mount verwenden.`
tritt bei einem legitimen Root-Mapping nicht mehr auf.
### Native Aktivierung ### Native Aktivierung
`nc-connector-install.php` unterstützt jetzt auch API-only-Verbindungen ohne dauerhaft gespeicherten Login-Fallback. Ein Fallback ist nur nötig, wenn keine nutzbare API gespeichert ist. `nc-connector-install.php` unterstützt API-only-Verbindungen ohne dauerhaft gespeicherten Login-Fallback. Ein Fallback ist nur nötig, wenn keine nutzbare API gespeichert ist.
### Dokumentation ## 0.9.4.3
README und CURRENT_STATUS wurden auf den aktuellen `0.9.4.x`-Stand gebracht und beschreiben jetzt den realen API-first-Sync, den Wizard, die Statusaktualisierung ohne Reload und die aktuelle Runtime-Architektur. - Syntaxfehler im Wizard-Abschluss korrigiert.
- Neue Wizard-Durchläufe werden von alten serverseitigen Zuständen getrennt.
- Abbrechen und Schließen verwerfen den laufenden Wizard-State.
## 0.9.4.4
Der Connector-Benutzer aus Schritt 1 ist zugleich der lesende Datenbank-Benutzer. Der Assistent fragt deshalb Benutzername und Passwort nicht erneut ab.
Ablauf:
1. Nextcloud-Adresse + Connector-Benutzer + Passwort.
2. Web-Zugang und OCS werden geprüft.
3. Derselbe Benutzer und dasselbe Passwort werden automatisch für PostgreSQL verwendet.
4. Der Assistent prüft den üblichen Weg mit Nextcloud-Host, Port 5432 und Datenbank `nextcloud` bzw. bekannte passende Verbindungswerte.
5. Nur wenn die Datenbank dort nicht erreichbar ist, werden Host, Port und Datenbankname abgefragt. Benutzer und Passwort bleiben unverändert aus Schritt 1.
6. Danach folgen gegebenenfalls Storage-Zuordnung, Showcase-Benutzer, Piwigo-API und Fallback.
Die PHP-Datei `include/nc_connector_wizard.inc.php` wurde vor dem Versionsbump mit dem PHP-Parser geprüft.
## Architektur NC Connector ## Architektur NC Connector

View File

@@ -2,7 +2,7 @@
Modulares Piwigo-Plugin für Administration, Bildverarbeitung, geschützte Freigaben, Fotoauswahl und die Anbindung von Nextcloud an Piwigo. Modulares Piwigo-Plugin für Administration, Bildverarbeitung, geschützte Freigaben, Fotoauswahl und die Anbindung von Nextcloud an Piwigo.
Aktuelle Plugin-Version: **0.9.4.2** Aktuelle Plugin-Version: **0.9.4.4**
## Grundprinzip ## Grundprinzip
@@ -32,9 +32,9 @@ Ablauf:
1. Nextcloud-Adresse, Benutzer und Passwort eingeben. 1. Nextcloud-Adresse, Benutzer und Passwort eingeben.
2. Der Assistent prüft HTTP/HTTPS, Nextcloud-Status und Anmeldung. 2. Der Assistent prüft HTTP/HTTPS, Nextcloud-Status und Anmeldung.
3. Vorhandene passende Connector-Daten werden automatisch wiederverwendet, wenn sie sicher zugeordnet werden können. 3. Derselbe in Schritt 1 angegebene Connector-Benutzer und dasselbe Passwort werden auch für den lesenden PostgreSQL-Zugriff verwendet; es gibt im normalen Ablauf keine zweite Benutzer-/Passwortabfrage.
4. Für den Nextcloud-Datenzugriff wird zunächst nur der eingerichtete Lese-Benutzer mit Passwort abgefragt. Der übliche PostgreSQL-Weg wird automatisch geprüft. 4. Vorhandene passende Connector-Werte für Host, Port, Datenbank und Storage-Zuordnungen werden wiederverwendet, aber erneut mit dem aktuellen Zugang geprüft.
5. Nur wenn diese Prüfung scheitert, werden abweichende Datenbank-Adresse, Port und Datenbankname gezielt abgefragt. 5. Nur wenn der übliche PostgreSQL-Weg nicht erreichbar ist, werden abweichende Datenbank-Adresse, Port und Datenbankname gezielt abgefragt. Benutzer und Passwort bleiben dabei die Angaben aus Schritt 1.
6. Nicht automatisch zuordenbare Storage-Mounts werden einzeln bestätigt. 6. Nicht automatisch zuordenbare Storage-Mounts werden einzeln bestätigt.
7. Danach wird der Nextcloud-Benutzer gewählt, dessen Freigaben verwendet werden sollen. Empfohlen wird ein eigener `showcase`-Benutzer. 7. Danach wird der Nextcloud-Benutzer gewählt, dessen Freigaben verwendet werden sollen. Empfohlen wird ein eigener `showcase`-Benutzer.
8. Piwigo-API-Zugang testen oder bewusst überspringen. 8. Piwigo-API-Zugang testen oder bewusst überspringen.

View File

@@ -7,271 +7,429 @@ if (!defined('PHPWG_ROOT_PATH'))
function bratonien_tools_nc_wizard_state() function bratonien_tools_nc_wizard_state()
{ {
$state = isset($_SESSION['bratonien_nc_wizard']) && is_array($_SESSION['bratonien_nc_wizard']) ? $_SESSION['bratonien_nc_wizard'] : array(); $state = isset($_SESSION['bratonien_nc_wizard']) && is_array($_SESSION['bratonien_nc_wizard']) ? $_SESSION['bratonien_nc_wizard'] : array();
return array_merge(array( return array_merge(array(
'step'=>1,'scan_ok'=>false,'base_url'=>'','host_input'=>'','username'=>'','display_name'=>'','version'=>'','product'=>'Nextcloud', 'step'=>1,
'users'=>array(),'can_list_users'=>false,'showcase_user'=>'','connection_name'=>'','scan_message'=>'', 'scan_ok'=>false,
'technical_stage'=>'reader_credentials','technical_source'=>'Noch nicht geprüft','technical_error'=>'','technical_complete'=>false, 'base_url'=>'',
'db_host'=>'','db_port'=>'5432','db_database'=>'nextcloud','db_user'=>'','db_password_set'=>false, 'host_input'=>'',
'source_view'=>'piwigo_showcase_sources','activity_view'=>'piwigo_showcase_activity','gallery_root'=>'','storages'=>array(),'storage_candidates'=>array(), 'username'=>'',
'api_status'=>'pending','api_username'=>'','api_error'=>'','_password'=>'','_db_password'=>'','_api_key_id'=>'','_api_key_secret'=>'', 'display_name'=>'',
'_fallback_user'=>'','_fallback_password'=>'', 'version'=>'',
'product'=>'Nextcloud',
'users'=>array(),
'can_list_users'=>false,
'showcase_user'=>'',
'connection_name'=>'',
'scan_message'=>'',
'technical_stage'=>'auto_check',
'technical_source'=>'Noch nicht geprüft',
'technical_error'=>'',
'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'=>'',
'_password'=>'',
'_db_password'=>'',
'_api_key_id'=>'',
'_api_key_secret'=>'',
'_fallback_user'=>'',
'_fallback_password'=>'',
), $state); ), $state);
} }
function bratonien_tools_nc_wizard_store(array $state) { $_SESSION['bratonien_nc_wizard'] = $state; } function bratonien_tools_nc_wizard_store(array $state)
function bratonien_tools_nc_wizard_reset() { unset($_SESSION['bratonien_nc_wizard']); return array('message'=>'Verbindungsassistent wurde zurückgesetzt.'); } {
$_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) function bratonien_tools_nc_wizard_normalize_url($host)
{ {
$host=trim((string)$host); $host = trim((string)$host);
if($host==='') throw new RuntimeException('Nextcloud-Adresse fehlt.'); if ($host === '') throw new RuntimeException('Nextcloud-Adresse fehlt.');
if(!preg_match('#^https?://#i',$host)) $host='https://'.$host; if (!preg_match('#^https?://#i', $host)) $host = 'https://'.$host;
$parts=parse_url($host);
if(!is_array($parts)||empty($parts['host'])) throw new RuntimeException('Die Nextcloud-Adresse ist ungültig.'); $parts = parse_url($host);
$scheme=strtolower((string)($parts['scheme']??'https')); if (!is_array($parts) || empty($parts['host'])) throw new RuntimeException('Die Nextcloud-Adresse ist ungültig.');
if(!in_array($scheme,array('http','https'),true)) throw new RuntimeException('Nextcloud muss per HTTP oder HTTPS erreichbar sein.');
$url=$scheme.'://'.$parts['host']; $scheme = strtolower((string)($parts['scheme'] ?? 'https'));
if(!empty($parts['port'])) $url.=':'.(int)$parts['port']; if (!in_array($scheme, array('http','https'), true)) throw new RuntimeException('Nextcloud muss per HTTP oder HTTPS erreichbar sein.');
if(!empty($parts['path'])&&$parts['path']!=='/') $url.='/'.trim($parts['path'],'/');
return rtrim($url,'/'); $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_candidate_urls($host) function bratonien_tools_nc_wizard_candidate_urls($host)
{ {
$host=trim((string)$host); $host = trim((string)$host);
if($host==='') throw new RuntimeException('Bitte die Adresse der Nextcloud angeben.'); if ($host === '') throw new RuntimeException('Bitte die Adresse der Nextcloud angeben.');
if(preg_match('#^https?://#i',$host)) return array(bratonien_tools_nc_wizard_normalize_url($host)); if (preg_match('#^https?://#i', $host)) return array(bratonien_tools_nc_wizard_normalize_url($host));
return array(bratonien_tools_nc_wizard_normalize_url('https://'.$host),bratonien_tools_nc_wizard_normalize_url('http://'.$host));
return array(
bratonien_tools_nc_wizard_normalize_url('https://'.$host),
bratonien_tools_nc_wizard_normalize_url('http://'.$host),
);
} }
function bratonien_tools_nc_wizard_http($url,$username='',$password='',array $headers=array()) function bratonien_tools_nc_wizard_http($url, $username = '', $password = '', array $headers = array())
{ {
if(!function_exists('curl_init')) throw new RuntimeException('Der Server kann Nextcloud derzeit nicht per HTTP prüfen.'); if (!function_exists('curl_init')) throw new RuntimeException('Der Server kann Nextcloud derzeit nicht per HTTP prüfen.');
$ch=curl_init($url);
$options=array( $ch = curl_init($url);
CURLOPT_RETURNTRANSFER=>true,CURLOPT_FOLLOWLOCATION=>true,CURLOPT_MAXREDIRS=>3,CURLOPT_CONNECTTIMEOUT=>8,CURLOPT_TIMEOUT=>15, $options = array(
CURLOPT_HTTPHEADER=>array_merge(array('Accept: application/json'),$headers), CURLOPT_RETURNTRANSFER=>true,
CURLOPT_USERAGENT=>'Bratonien-Tools-NC-Wizard/'.(function_exists('bratonien_tools_current_version')?bratonien_tools_current_version():'dev'), CURLOPT_FOLLOWLOCATION=>true,
CURLOPT_MAXREDIRS=>3,
CURLOPT_CONNECTTIMEOUT=>8,
CURLOPT_TIMEOUT=>15,
CURLOPT_HTTPHEADER=>array_merge(array('Accept: application/json'), $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); if ($username !== '')
$body=curl_exec($ch);$errno=curl_errno($ch);$status=(int)curl_getinfo($ch,CURLINFO_HTTP_CODE);curl_close($ch); {
if($body===false||$errno!==0) throw new RuntimeException('Verbindung fehlgeschlagen.'); $options[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
return array('status'=>$status,'body'=>(string)$body); $options[CURLOPT_USERPWD] = $username.':'.$password;
}
curl_setopt_array($ch, $options);
$body = curl_exec($ch);
$errno = curl_errno($ch);
$status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($body === false || $errno !== 0) throw new RuntimeException('Verbindung fehlgeschlagen.');
return array('status'=>$status, 'body'=>(string)$body);
} }
function bratonien_tools_nc_wizard_ocs_data($body) function bratonien_tools_nc_wizard_ocs_data($body)
{ {
$decoded=json_decode((string)$body,true); $decoded = json_decode((string)$body, true);
if(!is_array($decoded)||!isset($decoded['ocs'])||!is_array($decoded['ocs'])) throw new RuntimeException('Nextcloud hat keine gültige Antwort geliefert.'); if (!is_array($decoded) || !isset($decoded['ocs']) || !is_array($decoded['ocs'])) throw new RuntimeException('Nextcloud hat keine gültige Antwort geliefert.');
$meta=isset($decoded['ocs']['meta'])&&is_array($decoded['ocs']['meta'])?$decoded['ocs']['meta']:array();
$status=strtolower(trim((string)($meta['status']??'')));$status_code=isset($meta['statuscode'])?(int)$meta['statuscode']:0; $meta = isset($decoded['ocs']['meta']) && is_array($decoded['ocs']['meta']) ? $decoded['ocs']['meta'] : array();
if($status!=='ok'&&!in_array($status_code,array(100,200),true)) $status = strtolower(trim((string)($meta['status'] ?? '')));
$status_code = isset($meta['statuscode']) ? (int)$meta['statuscode'] : 0;
if ($status !== 'ok' && !in_array($status_code, array(100,200), true))
{ {
$message=trim((string)($meta['message']??'')); $message = trim((string)($meta['message'] ?? ''));
if($message===''||strtolower($message)==='ok') $message='Nextcloud hat die Anfrage abgelehnt.'; if ($message === '' || strtolower($message) === 'ok') $message = 'Nextcloud hat die Anfrage abgelehnt.';
throw new RuntimeException($message); throw new RuntimeException($message);
} }
return isset($decoded['ocs']['data'])&&is_array($decoded['ocs']['data'])?$decoded['ocs']['data']:array();
return isset($decoded['ocs']['data']) && is_array($decoded['ocs']['data']) ? $decoded['ocs']['data'] : array();
} }
function bratonien_tools_nc_wizard_fill_profile(array &$state) function bratonien_tools_nc_wizard_fill_profile(array &$state)
{ {
$url_host=strtolower((string)parse_url($state['base_url'],PHP_URL_HOST)); $url_host = strtolower((string)parse_url($state['base_url'], PHP_URL_HOST));
$state['db_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_stage']='reader_credentials';
$state['technical_source']='Noch nicht geprüft';
$state['technical_error']='';
$state['technical_complete']=false;
foreach(bratonien_tools_nc_connector_connections() as $candidate) $state['db_host'] = $url_host;
$state['db_port'] = '5432';
$state['db_database'] = 'nextcloud';
$state['db_user'] = (string)$state['username'];
$state['_db_password'] = (string)$state['_password'];
$state['db_password_set'] = $state['_db_password'] !== '';
$state['source_view'] = 'piwigo_showcase_sources';
$state['activity_view'] = 'piwigo_showcase_activity';
$state['gallery_root'] = rtrim(PHPWG_ROOT_PATH, '/').'/galleries/nextcloud';
$state['storages'] = array();
$state['storage_candidates'] = array();
$state['technical_stage'] = 'auto_check';
$state['technical_source'] = 'Automatische Prüfung';
$state['technical_error'] = '';
$state['technical_complete'] = false;
foreach (bratonien_tools_nc_connector_connections() as $candidate)
{ {
$config=isset($candidate['config'])&&is_array($candidate['config'])?$candidate['config']:array(); $config = isset($candidate['config']) && is_array($candidate['config']) ? $candidate['config'] : array();
$candidate_url=trim((string)($config['nextcloud_url']??'')); $candidate_url = trim((string)($config['nextcloud_url'] ?? ''));
$match=false; $match = false;
if($candidate_url!=='')
if ($candidate_url !== '')
{ {
try{$match=bratonien_tools_nc_wizard_normalize_url($candidate_url)===$state['base_url'];}catch(Throwable $ignored){$match=false;} try
}
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_port']=(string)($config['port']??'5432');
$state['db_database']=(string)($config['database']??'nextcloud');
$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['storage_candidates']=$state['storages'];
$state['technical_complete']=$state['db_host']!==''&&$state['db_user']!==''&&$state['db_password_set']&&!empty($state['storages']);
$state['technical_stage']=$state['technical_complete']?'ready':'reader_credentials';
$state['technical_source']='Passende vorhandene Verbindung übernommen';
break;
}
if($state['db_host']==='') $state['db_host']=$url_host;
}
function bratonien_tools_nc_wizard_scan()
{
$state=bratonien_tools_nc_wizard_state();
$host_input=trim((string)($_POST['nc_wizard_host']??$state['host_input']));
$username=trim((string)($_POST['nc_wizard_user']??$state['username']));
$password=array_key_exists('nc_wizard_password',$_POST)?(string)$_POST['nc_wizard_password']:(string)$state['_password'];
$state['step']=1;$state['host_input']=$host_input;$state['username']=$username;$state['_password']=$password;bratonien_tools_nc_wizard_store($state);
if($username===''||$password==='') throw new RuntimeException('Nextcloud-Benutzer und Passwort werden für den Scan benötigt.');
$base_url='';$status_data=null;
foreach(bratonien_tools_nc_wizard_candidate_urls($host_input) as $candidate_url)
{
try
{
$response=bratonien_tools_nc_wizard_http($candidate_url.'/status.php');
if($response['status']<200||$response['status']>=300) continue;
$candidate_status=json_decode($response['body'],true);
if(!is_array($candidate_status)||empty($candidate_status['installed'])) continue;
$base_url=$candidate_url;$status_data=$candidate_status;break;
}
catch(Throwable $ignored){}
}
if($base_url===''||!is_array($status_data)) throw new RuntimeException('Unter dieser Adresse konnte keine Nextcloud erreicht werden. HTTP und HTTPS wurden automatisch geprüft.');
$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 ist erreichbar, aber die Anmeldung konnte nicht geprüft werden.');
$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; $match = bratonien_tools_nc_wizard_normalize_url($candidate_url) === $state['base_url'];
}
catch (Throwable $ignored)
{
$match = false;
} }
} }
}
catch(Throwable $ignored){}
$url_host=(string)parse_url($base_url,PHP_URL_HOST); if (!$match) continue;
$state=array_merge($state,array(
'step'=>2,'scan_ok'=>true,'base_url'=>$base_url,'host_input'=>$host_input, $state['db_host'] = trim((string)($config['host'] ?? $state['db_host']));
'username'=>(string)($user_data['id']??$username),'display_name'=>(string)($user_data['display-name']??$user_data['displayname']??''), $state['db_port'] = trim((string)($config['port'] ?? $state['db_port']));
'version'=>(string)($status_data['versionstring']??$status_data['version']??''),'product'=>(string)($status_data['productname']??'Nextcloud'), $state['db_database'] = trim((string)($config['database'] ?? $state['db_database']));
'users'=>$users,'can_list_users'=>$can_list_users,'showcase_user'=>'','connection_name'=>$url_host!==''?$url_host:'Nextcloud', $state['source_view'] = trim((string)($config['source_view'] ?? $state['source_view']));
'scan_message'=>'Nextcloud wurde erkannt und der Benutzerzugriff wurde bestätigt.','_password'=>$password,'api_status'=>'pending','api_username'=>'','api_error'=>'' $state['activity_view'] = trim((string)($config['activity_view'] ?? $state['activity_view']));
)); $state['gallery_root'] = trim((string)($config['gallery_root'] ?? $state['gallery_root']));
bratonien_tools_nc_wizard_fill_profile($state);bratonien_tools_nc_wizard_store($state); $state['storages'] = isset($config['storages']) && is_array($config['storages']) ? $config['storages'] : array();
return array('message'=>'Nextcloud wurde gefunden und der Zugang bestätigt.'); $state['storage_candidates'] = $state['storages'];
$state['technical_source'] = 'Bekannte Verbindungswerte werden erneut geprüft';
break;
}
} }
function bratonien_tools_nc_wizard_finish_data_access(array &$state) function bratonien_tools_nc_wizard_finish_data_access(array &$state)
{ {
$config=array('host'=>$state['db_host'],'port'=>$state['db_port'],'database'=>$state['db_database'],'user'=>$state['db_user']); $config = array(
$password=(string)$state['_db_password']; 'host'=>$state['db_host'],
bratonien_tools_nc_connector_psql($config,$password,'SELECT 1'); 'port'=>$state['db_port'],
'database'=>$state['db_database'],
'user'=>$state['db_user'],
);
$password = (string)$state['_db_password'];
$source_view=bratonien_tools_nc_connector_view_name($state['source_view']); bratonien_tools_nc_connector_psql($config, $password, 'SELECT 1');
$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' || COALESCE(source_path, '') FROM ".$source_view." ORDER BY 1"); $source_view = bratonien_tools_nc_connector_view_name($state['source_view']);
$candidates=array(); $activity_view = bratonien_tools_nc_connector_view_name($state['activity_view']);
foreach(preg_split('/\r\n|\r|\n/',trim($rows)) as $line) 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' || COALESCE(source_path, '') FROM ".$source_view." ORDER BY 1"
);
$candidates = array();
foreach (preg_split('/\r\n|\r|\n/', trim($rows)) as $line)
{ {
if($line==='') continue; if ($line === '') continue;
$parts=explode("\t",$line,2);
if(count($parts)!==2) continue;
$storage_id=trim($parts[0]);$source_path=trim($parts[1],'/');
if($storage_id==='') continue;
$prefix=$source_path;
if(strpos($source_path,'/')!==false) $prefix=substr($source_path,0,strpos($source_path,'/'));
if(!isset($candidates[$storage_id])) $candidates[$storage_id]=array('storage_id'=>$storage_id,'source_prefix'=>$prefix,'local_mount'=>'');
}
if(!$candidates) throw new RuntimeException('Die Verbindung zur Datenquelle funktioniert, aber es wurden noch keine freigegebenen Speicherorte gefunden.');
$known=array(); $parts = explode("\t", $line, 2);
foreach(bratonien_tools_nc_connector_connections() as $connection) if (count($parts) !== 2) continue;
{
foreach(($connection['config']['storages']??array()) as $storage) $storage_id = trim($parts[0]);
$source_path = trim($parts[1], '/');
if ($storage_id === '') continue;
$prefix = $source_path;
if (strpos($source_path, '/') !== false) $prefix = substr($source_path, 0, strpos($source_path, '/'));
if (!isset($candidates[$storage_id]))
{ {
$key=(string)($storage['storage_id']??'').'|'.trim((string)($storage['source_prefix']??''),'/'); $candidates[$storage_id] = array(
if($key!=='|') $known[$key]=(string)($storage['local_mount']??''); 'storage_id'=>$storage_id,
'source_prefix'=>$prefix,
'local_mount'=>'',
);
} }
} }
foreach($candidates as &$candidate)
if (!$candidates) throw new RuntimeException('Die Verbindung zur Datenquelle funktioniert, aber es wurden noch keine freigegebenen Speicherorte gefunden.');
$known = array();
foreach (bratonien_tools_nc_connector_connections() as $connection)
{ {
$key=$candidate['storage_id'].'|'.trim($candidate['source_prefix'],'/'); foreach (($connection['config']['storages'] ?? array()) as $storage)
if(!empty($known[$key])&&is_dir($known[$key])&&is_readable($known[$key])) $candidate['local_mount']=$known[$key]; {
$key = (string)($storage['storage_id'] ?? '').'|'.trim((string)($storage['source_prefix'] ?? ''), '/');
if ($key !== '|') $known[$key] = (string)($storage['local_mount'] ?? '');
}
}
foreach ($candidates as &$candidate)
{
$key = $candidate['storage_id'].'|'.trim($candidate['source_prefix'], '/');
if (!empty($known[$key]) && is_dir($known[$key]) && is_readable($known[$key])) $candidate['local_mount'] = $known[$key];
} }
unset($candidate); unset($candidate);
$state['storage_candidates']=array_values($candidates); $state['storage_candidates'] = array_values($candidates);
$state['storages']=array_values(array_filter($state['storage_candidates'],function($storage){return trim((string)($storage['local_mount']??''))!=='';})); $state['storages'] = array_values(array_filter($state['storage_candidates'], function($storage) {
$state['technical_error']=''; return trim((string)($storage['local_mount'] ?? '')) !== '';
$state['technical_source']='Datenzugriff erfolgreich geprüft'; }));
$state['technical_complete']=count($state['storages'])===count($state['storage_candidates']); $state['technical_error'] = '';
$state['technical_stage']=$state['technical_complete']?'ready':'mounts'; $state['technical_source'] = 'Datenzugriff erfolgreich geprüft';
$state['technical_complete'] = count($state['storages']) === count($state['storage_candidates']);
$state['technical_stage'] = $state['technical_complete'] ? 'ready' : 'mounts';
}
function bratonien_tools_nc_wizard_scan()
{
$state = bratonien_tools_nc_wizard_state();
$host_input = trim((string)($_POST['nc_wizard_host'] ?? $state['host_input']));
$username = trim((string)($_POST['nc_wizard_user'] ?? $state['username']));
$password = array_key_exists('nc_wizard_password', $_POST) ? (string)$_POST['nc_wizard_password'] : (string)$state['_password'];
$state['step'] = 1;
$state['host_input'] = $host_input;
$state['username'] = $username;
$state['_password'] = $password;
bratonien_tools_nc_wizard_store($state);
if ($username === '' || $password === '') throw new RuntimeException('Nextcloud-Benutzer und Passwort werden für den Scan benötigt.');
$base_url = '';
$status_data = null;
foreach (bratonien_tools_nc_wizard_candidate_urls($host_input) as $candidate_url)
{
try
{
$response = bratonien_tools_nc_wizard_http($candidate_url.'/status.php');
if ($response['status'] < 200 || $response['status'] >= 300) continue;
$candidate_status = json_decode($response['body'], true);
if (!is_array($candidate_status) || empty($candidate_status['installed'])) continue;
$base_url = $candidate_url;
$status_data = $candidate_status;
break;
}
catch (Throwable $ignored)
{
}
}
if ($base_url === '' || !is_array($status_data)) throw new RuntimeException('Unter dieser Adresse konnte keine Nextcloud erreicht werden. HTTP und HTTPS wurden automatisch geprüft.');
$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 ist erreichbar, aber die Anmeldung konnte nicht geprüft werden.');
$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)
{
}
$resolved_username = trim((string)($user_data['id'] ?? $username));
if ($resolved_username === '') $resolved_username = $username;
$url_host = (string)parse_url($base_url, PHP_URL_HOST);
$state = array_merge($state, array(
'step'=>2,
'scan_ok'=>true,
'base_url'=>$base_url,
'host_input'=>$host_input,
'username'=>$resolved_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);
try
{
bratonien_tools_nc_wizard_finish_data_access($state);
}
catch (Throwable $e)
{
$state['technical_complete'] = false;
$state['technical_stage'] = 'database_details';
$state['technical_error'] = $e->getMessage();
}
bratonien_tools_nc_wizard_store($state);
if ($state['technical_stage'] === 'database_details')
{
return array('message'=>'Nextcloud wurde gefunden. Die Datenbank-Adresse muss noch bestätigt werden.');
}
if ($state['technical_stage'] === 'mounts')
{
return array('message'=>'Nextcloud und Datenzugriff wurden bestätigt. Ein Speicherort muss noch zugeordnet werden.');
}
return array('message'=>'Nextcloud und Datenzugriff wurden erfolgreich bestätigt.');
} }
function bratonien_tools_nc_wizard_save_technical() function bratonien_tools_nc_wizard_save_technical()
{ {
$state=bratonien_tools_nc_wizard_state(); $state = bratonien_tools_nc_wizard_state();
if(empty($state['scan_ok'])) throw new RuntimeException('Bitte zuerst Nextcloud erfolgreich scannen.'); if (empty($state['scan_ok'])) throw new RuntimeException('Bitte zuerst Nextcloud erfolgreich scannen.');
if ((string)$state['username'] === '' || (string)$state['_password'] === '') throw new RuntimeException('Die Zugangsdaten aus dem ersten Schritt sind nicht mehr verfügbar. Bitte den Assistenten neu starten.');
$state['connection_name']=trim((string)($_POST['nc_wizard_connection_name']??$state['connection_name']));
$state['db_user']=trim((string)($_POST['nc_wizard_db_user']??$state['db_user']));
$db_password=array_key_exists('nc_wizard_db_password',$_POST)?(string)$_POST['nc_wizard_db_password']:(string)$state['_db_password'];
if($db_password!==''){$state['_db_password']=$db_password;$state['db_password_set']=true;}
$advanced=isset($_POST['nc_wizard_db_host'])||$state['technical_stage']==='database_details';
if($advanced)
{
$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']));
}
else
{
$state['db_host']=(string)parse_url($state['base_url'],PHP_URL_HOST);
$state['db_port']='5432';
$state['db_database']='nextcloud';
}
$state['db_user'] = (string)$state['username'];
$state['_db_password'] = (string)$state['_password'];
$state['db_password_set'] = true;
$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['technical_stage'] = 'database_details';
bratonien_tools_nc_wizard_store($state); bratonien_tools_nc_wizard_store($state);
if($state['db_user']===''||empty($state['db_password_set'])) throw new RuntimeException('Für den Datenzugriff werden Lese-Benutzer und Passwort benötigt.');
if($state['db_host']===''||$state['db_database']==='') throw new RuntimeException('Die Adresse der Datenbank ist noch unvollständig.'); if ($state['db_host'] === '' || $state['db_database'] === '') throw new RuntimeException('Die Adresse der Datenbank ist noch unvollständig.');
try try
{ {
bratonien_tools_nc_wizard_finish_data_access($state); bratonien_tools_nc_wizard_finish_data_access($state);
bratonien_tools_nc_wizard_store($state); bratonien_tools_nc_wizard_store($state);
return array('message'=>$state['technical_complete']?'Datenzugriff wurde erfolgreich geprüft.':'Datenzugriff funktioniert. Ein Speicherort muss noch bestätigt werden.');
return array(
'message'=>$state['technical_complete']
? 'Datenzugriff wurde erfolgreich geprüft.'
: 'Datenzugriff funktioniert. Ein Speicherort muss noch bestätigt werden.'
);
} }
catch(Throwable $e) catch (Throwable $e)
{ {
$state['technical_error']=$e->getMessage(); $state['technical_error'] = $e->getMessage();
if(!$advanced) $state['technical_stage'] = 'database_details';
{
$state['technical_stage']='database_details';
bratonien_tools_nc_wizard_store($state);
throw new RuntimeException('Der übliche Datenbankzugang konnte nicht automatisch bestätigt werden. Bitte die Datenbank-Adresse prüfen.');
}
bratonien_tools_nc_wizard_store($state); bratonien_tools_nc_wizard_store($state);
throw $e; throw $e;
} }
@@ -279,99 +437,247 @@ function bratonien_tools_nc_wizard_save_technical()
function bratonien_tools_nc_wizard_save_mounts() function bratonien_tools_nc_wizard_save_mounts()
{ {
$state=bratonien_tools_nc_wizard_state(); $state = bratonien_tools_nc_wizard_state();
$candidates=is_array($state['storage_candidates'])?$state['storage_candidates']:array(); $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(); $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 Speicherorte erkannt.');
$storages=array(); if (!$candidates) throw new RuntimeException('Es wurden noch keine Speicherorte erkannt.');
foreach($candidates as $index=>$candidate)
$storages = array();
foreach ($candidates as $index=>$candidate)
{ {
$mount=trim((string)($candidate['local_mount']??'')); $mount = trim((string)($candidate['local_mount'] ?? ''));
if($mount==='') $mount=trim((string)($mounts[$index]??'')); if ($mount === '') $mount = trim((string)($mounts[$index] ?? ''));
if($mount===''||$mount[0]!=='/') throw new RuntimeException('Für einen Speicherort fehlt ein gültiger lokaler Pfad.'); if ($mount === '' || $mount[0] !== '/') throw new RuntimeException('Für einen Speicherort fehlt ein gültiger lokaler Pfad.');
$mount=rtrim($mount,'/');
if(!is_dir($mount)||!is_readable($mount)) throw new RuntimeException('Der angegebene Speicherort ist nicht vorhanden oder nicht lesbar: '.$mount); $mount = rtrim($mount, '/');
$candidate['local_mount']=$mount;$storages[]=$candidate; if (!is_dir($mount) || !is_readable($mount)) throw new RuntimeException('Der angegebene Speicherort ist nicht vorhanden oder nicht lesbar: '.$mount);
$candidate['local_mount'] = $mount;
$storages[] = $candidate;
} }
$state['storages']=$storages;$state['storage_candidates']=$storages;$state['technical_complete']=true;$state['technical_stage']='ready';$state['technical_error']='';bratonien_tools_nc_wizard_store($state);
$state['storages'] = $storages;
$state['storage_candidates'] = $storages;
$state['technical_complete'] = true;
$state['technical_stage'] = 'ready';
$state['technical_error'] = '';
bratonien_tools_nc_wizard_store($state);
return array('message'=>'Speicherzuordnung wurde erfolgreich geprüft.'); return array('message'=>'Speicherzuordnung wurde erfolgreich geprüft.');
} }
function bratonien_tools_nc_wizard_select_user() function bratonien_tools_nc_wizard_select_user()
{ {
$state=bratonien_tools_nc_wizard_state(); $state = bratonien_tools_nc_wizard_state();
if(empty($state['scan_ok'])) throw new RuntimeException('Bitte zuerst Nextcloud erfolgreich scannen.'); if (empty($state['scan_ok'])) throw new RuntimeException('Bitte zuerst Nextcloud erfolgreich scannen.');
if(empty($state['technical_complete'])) throw new RuntimeException('Die Verbindung ist noch nicht vollständig geprüft.'); if (empty($state['technical_complete'])) throw new RuntimeException('Die Verbindung ist noch nicht vollständig geprüft.');
$showcase_user=trim((string)($_POST['nc_wizard_showcase_user']??''));
$connection_name=trim((string)($_POST['nc_wizard_connection_name']??$state['connection_name'])); $showcase_user = trim((string)($_POST['nc_wizard_showcase_user'] ?? ''));
if($showcase_user==='') throw new RuntimeException('Bitte den Benutzer auswählen, der die Showcase-Freigaben bereitstellt.'); $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.'); if ($showcase_user === '') throw new RuntimeException('Bitte den Benutzer auswählen, der die Showcase-Freigaben bereitstellt.');
$state['connection_name']=$connection_name;$state['showcase_user']=$showcase_user;$state['step']=3;$state['api_error']='';bratonien_tools_nc_wizard_store($state); 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.'); return array('message'=>'Showcase-Benutzer übernommen. Jetzt folgt der Piwigo-API-Zugang.');
} }
function bratonien_tools_nc_wizard_api_test() function bratonien_tools_nc_wizard_api_test()
{ {
$state=bratonien_tools_nc_wizard_state(); $state = bratonien_tools_nc_wizard_state();
if((int)$state['step']!==3) throw new RuntimeException('Der API-Test ist in diesem Assistentenschritt nicht verfügbar.'); if ((int)$state['step'] !== 3) throw new RuntimeException('Der API-Test ist in diesem Assistentenschritt nicht verfügbar.');
$posted_id=trim((string)($_POST['nc_wizard_api_key_id']??''));$posted_secret=trim((string)($_POST['nc_wizard_api_key_secret']??''));
if($posted_id!=='')$state['_api_key_id']=$posted_id;if($posted_secret!=='')$state['_api_key_secret']=$posted_secret;bratonien_tools_nc_wizard_store($state); $posted_id = trim((string)($_POST['nc_wizard_api_key_id'] ?? ''));
$key_id=trim((string)$state['_api_key_id']);$secret=trim((string)$state['_api_key_secret']); $posted_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 ($posted_id !== '') $state['_api_key_id'] = $posted_id;
if($key_id===''||$secret==='') throw new RuntimeException('API-Schlüssel-ID und API-Geheimnis fehlen.'); if ($posted_secret !== '') $state['_api_key_secret'] = $posted_secret;
bratonien_tools_nc_wizard_store($state);
$key_id = trim((string)$state['_api_key_id']);
$secret = trim((string)$state['_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 try
{ {
$status=bratonien_tools_nc_connector_piwigo_api_request($key_id,$secret,'pwg.session.getStatus'); $status = bratonien_tools_nc_connector_piwigo_api_request($key_id, $secret, 'pwg.session.getStatus');
$user_status=strtolower((string)($status['status']??'')); $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.'); 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))); $method_result = bratonien_tools_nc_connector_piwigo_api_request($key_id, $secret, 'reflection.getMethodList');
if($missing) throw new RuntimeException('Benötigte Bratonien-Sync-Methoden fehlen: '.implode(', ',$missing).'.'); $method_map = array();
$state['_api_key_id']=$key_id;$state['_api_key_secret']=$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); 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).'.');
$state['_api_key_id'] = $key_id;
$state['_api_key_secret'] = $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. Gespeichert wird sie erst beim Abschluss des Assistenten.'); return array('message'=>'Piwigo-API erfolgreich geprüft. Gespeichert wird sie erst beim Abschluss des Assistenten.');
} }
catch(Throwable $e){$state['api_status']='error';$state['api_error']=$e->getMessage();bratonien_tools_nc_wizard_store($state);throw $e;} 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() 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 = bratonien_tools_nc_wizard_state();
$state['api_status']='skipped';$state['api_error']='';$state['step']=4;bratonien_tools_nc_wizard_store($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.'); return array('message'=>'Piwigo-API wurde übersprungen. Für diese Verbindung ist deshalb ein Fallback-Zugang erforderlich.');
} }
function bratonien_tools_nc_wizard_finish() function bratonien_tools_nc_wizard_finish()
{ {
$state=bratonien_tools_nc_wizard_state(); $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.'); if ((int)$state['step'] !== 4 || empty($state['technical_complete']) || trim((string)$state['showcase_user']) === '') throw new RuntimeException('Der Assistent ist noch nicht vollständig.');
if(array_key_exists('nc_wizard_fallback_user',$_POST)) $state['_fallback_user']=trim((string)$_POST['nc_wizard_fallback_user']);
if(array_key_exists('nc_wizard_fallback_password',$_POST)) $state['_fallback_password']=(string)$_POST['nc_wizard_fallback_password'];
bratonien_tools_nc_wizard_store($state);
$fallback_user=trim((string)$state['_fallback_user']);$fallback_password=(string)$state['_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(); if (array_key_exists('nc_wizard_fallback_user', $_POST)) $state['_fallback_user'] = trim((string)$_POST['nc_wizard_fallback_user']);
foreach($state['storages'] as $storage) if (array_key_exists('nc_wizard_fallback_password', $_POST)) $state['_fallback_password'] = (string)$_POST['nc_wizard_fallback_password'];
bratonien_tools_nc_wizard_store($state);
$fallback_user = trim((string)$state['_fallback_user']);
$fallback_password = (string)$state['_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'].' | '.trim((string)$storage['source_prefix'],'/').' | '.(string)$storage['local_mount']; $storage_lines[] = (string)$storage['storage_id'].' | '.trim((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'];$_POST['nc_api_validated']=$state['api_status']==='ok'?'1':'0';
$result=bratonien_tools_nc_connector_create_local_api_first();$connection_id=(int)($result['connection_id']??0); $_POST['nc_name'] = (string)$state['connection_name'];
try{if($state['api_status']==='ok')bratonien_tools_nc_api_credentials_store((string)$state['_api_key_id'],(string)$state['_api_key_secret']);} $_POST['nc_host'] = (string)$state['db_host'];
catch(Throwable $e){if($connection_id>0){$table=bratonien_tools_nc_connector_table();pwg_query("DELETE FROM `$table` WHERE id=".$connection_id." LIMIT 1");}throw new RuntimeException('Die Verbindung wurde nicht übernommen, weil der geprüfte API-Zugang nicht gespeichert werden konnte: '.$e->getMessage());} $_POST['nc_port'] = (string)$state['db_port'];
unset($_SESSION['bratonien_nc_wizard']);unset($result['connection_id']);$result['message']='Verbindung wurde vollständig durch den Assistenten angelegt. '.$result['message'];return $result; $_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'];
$_POST['nc_api_validated'] = $state['api_status'] === 'ok' ? '1' : '0';
$result = bratonien_tools_nc_connector_create_local_api_first();
$connection_id = (int)($result['connection_id'] ?? 0);
try
{
if ($state['api_status'] === 'ok') bratonien_tools_nc_api_credentials_store((string)$state['_api_key_id'], (string)$state['_api_key_secret']);
}
catch (Throwable $e)
{
if ($connection_id > 0)
{
$table = bratonien_tools_nc_connector_table();
pwg_query("DELETE FROM `$table` WHERE id=".$connection_id." LIMIT 1");
}
throw new RuntimeException('Die Verbindung wurde nicht übernommen, weil der geprüfte API-Zugang nicht gespeichert werden konnte: '.$e->getMessage());
}
unset($_SESSION['bratonien_nc_wizard']);
unset($result['connection_id']);
$result['message'] = 'Verbindung wurde vollständig durch den Assistenten angelegt. '.$result['message'];
return $result;
} }
function bratonien_tools_nc_connector_update_name() 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.'); $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() 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.'); $id = (int)($_POST['connection_id'] ?? 0);
$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.'); $connection = bratonien_tools_nc_connector_connection($id, true);
$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']); if (!$connection) throw new RuntimeException('Connector-Verbindung wurde nicht gefunden.');
$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'];if($db_password==='')throw new RuntimeException('Datenbankpasswort fehlt.');$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.'); 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'];
if ($db_password === '') throw new RuntimeException('Datenbankpasswort fehlt.');
$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

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

View File

@@ -52,30 +52,17 @@
</form> </form>
{elseif $NC_CONNECTOR.wizard.step == 2} {elseif $NC_CONNECTOR.wizard.step == 2}
<p class="bratonien-base-note"><strong>Nextcloud wurde gefunden.</strong> Der Assistent prüft jetzt, wie die freigegebenen Dateien gelesen werden können.</p> <p class="bratonien-base-note"><strong>Nextcloud wurde gefunden.</strong> Der Assistent verwendet den Zugang aus dem ersten Schritt auch für den benötigten Lesezugriff.</p>
<div class="bratonien-form-grid"> <div class="bratonien-form-grid">
<span class="bratonien-label">Adresse</span><strong>{$NC_CONNECTOR.wizard.base_url|escape:html}</strong> <span class="bratonien-label">Adresse</span><strong>{$NC_CONNECTOR.wizard.base_url|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">Version</span><strong>{if $NC_CONNECTOR.wizard.version}{$NC_CONNECTOR.wizard.version|escape:html}{else}Nicht gemeldet{/if}</strong>
<span class="bratonien-label">Angemeldet 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">Angemeldet als</span><strong>{$NC_CONNECTOR.wizard.username|escape:html}{if $NC_CONNECTOR.wizard.display_name} · {$NC_CONNECTOR.wizard.display_name|escape:html}{/if}</strong>
</div> </div>
{if $NC_CONNECTOR.wizard.technical_stage == 'reader_credentials'} {if $NC_CONNECTOR.wizard.technical_stage == 'database_details'}
<hr>
<h5>Datenzugriff</h5>
<p class="bratonien-base-note">Nextcloud gibt den internen Lesezugang aus Sicherheitsgründen nicht über die normale API aus. Bitte nur den dafür eingerichteten Lese-Benutzer angeben. Die üblichen Verbindungswerte prüft der Assistent selbst.</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">Lese-Benutzer</label><input name="nc_wizard_db_user" type="text" value="{$NC_CONNECTOR.wizard.db_user|escape:html}" required>
<label class="bratonien-label">Passwort</label><input name="nc_wizard_db_password" type="password" autocomplete="new-password" value="{$NC_CONNECTOR.wizard._db_password|escape:html}" required>
</div>
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_save_technical">Automatisch prüfen</button></p>
</form>
{elseif $NC_CONNECTOR.wizard.technical_stage == 'database_details'}
<hr> <hr>
<h5>Datenbank-Adresse prüfen</h5> <h5>Datenbank-Adresse prüfen</h5>
<p class="bratonien-base-note">Der übliche Weg hat nicht funktioniert. Deshalb werden jetzt nur die Angaben abgefragt, die davon abweichen können.</p> <p class="bratonien-base-note">Der Zugang aus dem ersten Schritt bleibt unverändert. Nur die Verbindungsadresse konnte nicht automatisch bestätigt werden.</p>
{if $NC_CONNECTOR.wizard.technical_error}<details><summary>Technische Details</summary><p class="bratonien-main-cache__warning">{$NC_CONNECTOR.wizard.technical_error|escape:html}</p></details>{/if} {if $NC_CONNECTOR.wizard.technical_error}<details><summary>Technische Details</summary><p class="bratonien-main-cache__warning">{$NC_CONNECTOR.wizard.technical_error|escape:html}</p></details>{/if}
<form method="post" data-bratonien-wizard-form> <form method="post" data-bratonien-wizard-form>
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}"> <input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
@@ -83,8 +70,6 @@
<label class="bratonien-label">Datenbank-Adresse</label><input name="nc_wizard_db_host" type="text" value="{$NC_CONNECTOR.wizard.db_host|escape:html}" required> <label class="bratonien-label">Datenbank-Adresse</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">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">Datenbank</label><input name="nc_wizard_db_database" type="text" value="{$NC_CONNECTOR.wizard.db_database|escape:html}" required>
<label class="bratonien-label">Lese-Benutzer</label><input name="nc_wizard_db_user" type="text" value="{$NC_CONNECTOR.wizard.db_user|escape:html}" required>
<label class="bratonien-label">Passwort</label><input name="nc_wizard_db_password" type="password" autocomplete="new-password" value="{$NC_CONNECTOR.wizard._db_password|escape:html}" required>
</div> </div>
<p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_save_technical">Erneut prüfen</button></p> <p><button class="buttonLike" type="submit" name="bratonien_tool" value="nc_connector_wizard_save_technical">Erneut prüfen</button></p>
</form> </form>