Compare commits

..

2 Commits

Author SHA1 Message Date
Terranom674
298710e9d6 Version auf 0.9.6.29 anheben 2026-08-19 19:49:19 +02:00
Terranom674
8f1162519e 0.9.6.29: Mehrdeutigkeitspruefung bei Albumnamen entfernen 2026-08-19 19:49:04 +02:00
31 changed files with 509 additions and 1804 deletions

View File

@@ -1 +0,0 @@
0.9.7.7

View File

@@ -28,34 +28,18 @@ function bratonien_tools_nc_connector_webdav_site_id(array $connection)
return (int)$row['id']; return (int)$row['id'];
} }
function bratonien_tools_nc_connector_gallery_db_prefix(array $connection)
{
$config = isset($connection['config']) && is_array($connection['config']) ? $connection['config'] : array();
$gallery_root = rtrim((string)($config['parallel_gallery_root'] ?? ''), '/');
$piwigo_root = rtrim(PHPWG_ROOT_PATH, '/');
if ($gallery_root === '' || strpos($gallery_root, $piwigo_root.'/') !== 0) return '';
$relative = ltrim(substr($gallery_root, strlen($piwigo_root)), '/');
return $relative === '' ? '' : './'.rtrim($relative, '/').'/';
}
function bratonien_tools_nc_connector_remove_webdav_piwigo_content(array $connection) function bratonien_tools_nc_connector_remove_webdav_piwigo_content(array $connection)
{ {
$site_id = bratonien_tools_nc_connector_webdav_site_id($connection);
if ($site_id < 1) return array('site_id'=>0, 'images'=>0);
include_once(PHPWG_ROOT_PATH.'admin/include/functions.php'); include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
$site_id = bratonien_tools_nc_connector_webdav_site_id($connection);
$db_prefix = bratonien_tools_nc_connector_gallery_db_prefix($connection);
if ($db_prefix === '') return array('site_id'=>$site_id, 'images'=>0);
$escaped = pwg_db_real_escape_string(addcslashes($db_prefix, '_%\\'));
$image_rows = array(); $image_rows = array();
$result = pwg_query( $query = '\nSELECT DISTINCT i.id, i.path, i.representative_ext\n FROM '.IMAGES_TABLE.' AS i\n LEFT JOIN '.CATEGORIES_TABLE.' AS sc ON sc.id = i.storage_category_id\n LEFT JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON ic.image_id = i.id\n LEFT JOIN '.CATEGORIES_TABLE.' AS vc ON vc.id = ic.category_id\n WHERE sc.site_id = '.$site_id.' OR vc.site_id = '.$site_id.'\n;';
"SELECT id, path, representative_ext FROM ".IMAGES_TABLE. $result = pwg_query($query);
" WHERE path LIKE '".$escaped."%' ESCAPE '\\\\'"
);
while ($row = pwg_db_fetch_assoc($result)) while ($row = pwg_db_fetch_assoc($result))
{ {
if (strpos((string)$row['path'], $db_prefix) !== 0) continue;
$row['id'] = (int)$row['id']; $row['id'] = (int)$row['id'];
$image_rows[$row['id']] = $row; $image_rows[$row['id']] = $row;
} }
@@ -72,14 +56,20 @@ function bratonien_tools_nc_connector_remove_webdav_piwigo_content(array $connec
} }
} }
delete_site($site_id);
if ($image_rows) if ($image_rows)
{ {
delete_elements(array_map('intval', array_keys($image_rows)), false); $ids = array_keys($image_rows);
} $remaining = query2array(
'SELECT id FROM '.IMAGES_TABLE.' WHERE id IN ('.implode(',', array_map('intval', $ids)).')',
if ($site_id > 0) null,
'id'
);
if ($remaining)
{ {
delete_site($site_id); delete_elements(array_map('intval', $remaining), false);
}
} }
invalidate_user_cache(true); invalidate_user_cache(true);
@@ -120,7 +110,14 @@ function bratonien_tools_nc_connector_delete_safe()
@file_put_contents($status_dir.'/deleted-'.$id, date('c')."\n", LOCK_EX); @file_put_contents($status_dir.'/deleted-'.$id, date('c')."\n", LOCK_EX);
} }
if ((int)$cleanup['site_id'] > 0)
{
return array( return array(
'message'=>'Verbindung wurde gelöscht. '.(int)$cleanup['images'].' eindeutig zu dieser Verbindung gehörende Bilder wurden aus Piwigo entfernt. Nextcloud-Dateien blieben unverändert.', 'message'=>'Verbindung wurde gelöscht. Die zugehörigen Piwigo-Alben und '.(int)$cleanup['images'].' Bilder wurden aus Piwigo entfernt. Nextcloud-Dateien blieben unverändert.',
);
}
return array(
'message'=>'Verbindung wurde gelöscht. Verbliebene Laufzeitdaten werden automatisch bereinigt. Quelldateien blieben unverändert.',
); );
} }

View File

@@ -1,210 +0,0 @@
<?php
if (!defined('PHPWG_ROOT_PATH'))
{
die('Hacking attempt!');
}
function bratonien_tools_nc_scheduler_paths()
{
$base = rtrim(PHPWG_ROOT_PATH, '/').'/_data/bratonien-tools';
return array(
'base'=>$base,
'runtime'=>$base.'/nc-connector-runtime',
'state_root'=>$base.'/nc-connector-state',
'scheduler'=>$base.'/nc-connector-scheduler',
'state'=>$base.'/nc-connector-scheduler/state.json',
'trigger_lock'=>$base.'/nc-connector-scheduler/trigger.lock',
'worker_lock'=>$base.'/nc-connector-scheduler/worker.lock',
'log'=>$base.'/nc-connector-scheduler/last-worker.log',
'status_root'=>$base.'/nc-connector-status',
);
}
function bratonien_tools_nc_scheduler_interval()
{
global $conf;
$interval = isset($conf['bratonien_nc_scheduler_interval']) ? (int)$conf['bratonien_nc_scheduler_interval'] : 60;
return max(60, $interval);
}
function bratonien_tools_nc_scheduler_ensure_dirs()
{
foreach (bratonien_tools_nc_scheduler_paths() as $key=>$path)
{
if (in_array($key, array('state','trigger_lock','worker_lock','log'), true)) continue;
if (!is_dir($path) && !@mkdir($path, 0750, true) && !is_dir($path))
{
return false;
}
}
return true;
}
function bratonien_tools_nc_scheduler_read_state()
{
$paths = bratonien_tools_nc_scheduler_paths();
if (!is_readable($paths['state'])) return array();
$decoded = json_decode((string)@file_get_contents($paths['state']), true);
return is_array($decoded) ? $decoded : array();
}
function bratonien_tools_nc_scheduler_write_state(array $state)
{
$paths = bratonien_tools_nc_scheduler_paths();
if (!bratonien_tools_nc_scheduler_ensure_dirs()) return false;
$json = json_encode($state, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
if (!is_string($json)) return false;
$tmp = $paths['state'].'.tmp';
if (@file_put_contents($tmp, $json."\n", LOCK_EX) === false) return false;
@chmod($tmp, 0640);
return @rename($tmp, $paths['state']);
}
function bratonien_tools_nc_scheduler_write_connection_status($connection_id, $state, $message)
{
$connection_id = (int)$connection_id;
if ($connection_id < 1) return false;
$paths = bratonien_tools_nc_scheduler_paths();
if (!bratonien_tools_nc_scheduler_ensure_dirs()) return false;
$target = $paths['status_root'].'/connection-'.$connection_id.'.json';
$existing = array();
if (is_readable($target))
{
$decoded = json_decode((string)@file_get_contents($target), true);
if (is_array($decoded)) $existing = $decoded;
}
$existing['state'] = (string)$state;
$existing['message'] = (string)$message;
$existing['timestamp'] = time();
$existing['connection_id'] = $connection_id;
$json = json_encode($existing, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
if (!is_string($json)) return false;
$tmp = $target.'.tmp';
if (@file_put_contents($tmp, $json."\n", LOCK_EX) === false) return false;
@chmod($tmp, 0644);
return @rename($tmp, $target);
}
function bratonien_tools_nc_scheduler_install()
{
if (!bratonien_tools_nc_scheduler_ensure_dirs()) return false;
$state = bratonien_tools_nc_scheduler_read_state();
if (empty($state['next_due']))
{
$state['next_due'] = time();
$state['enabled'] = true;
$state['mode'] = 'piwigo-native';
bratonien_tools_nc_scheduler_write_state($state);
}
return true;
}
function bratonien_tools_nc_scheduler_php_binary()
{
foreach (array('/usr/bin/php', '/usr/local/bin/php', PHP_BINARY) as $candidate)
{
if (is_string($candidate) && $candidate !== '' && is_executable($candidate)) return $candidate;
}
return '';
}
function bratonien_tools_nc_scheduler_spawn($force = false, $connection_id = 0)
{
$connection_id = max(0, (int)$connection_id);
$paths = bratonien_tools_nc_scheduler_paths();
if (!bratonien_tools_nc_scheduler_ensure_dirs())
{
throw new RuntimeException('Der native NC-Scheduler kann sein Laufzeitverzeichnis nicht anlegen.');
}
$lock = @fopen($paths['trigger_lock'], 'c+');
if (!is_resource($lock) || !@flock($lock, LOCK_EX | LOCK_NB))
{
if (is_resource($lock)) fclose($lock);
return array('started'=>false, 'message'=>'Ein NC-Abgleich wird bereits vorbereitet.');
}
$state = bratonien_tools_nc_scheduler_read_state();
$now = time();
$next_due = (int)($state['next_due'] ?? 0);
if (!$force && $next_due > $now)
{
@flock($lock, LOCK_UN);
fclose($lock);
return array('started'=>false, 'message'=>'Der nächste NC-Abgleich ist noch nicht fällig.');
}
$php = bratonien_tools_nc_scheduler_php_binary();
if ($php === '')
{
@flock($lock, LOCK_UN);
fclose($lock);
throw new RuntimeException('Kein ausführbares PHP-CLI für den nativen NC-Scheduler gefunden.');
}
$state['enabled'] = true;
$state['mode'] = 'piwigo-native';
$state['state'] = 'queued';
$state['message'] = $connection_id > 0 ? 'NC-Abgleich für Verbindung #'.$connection_id.' wurde angefordert.' : 'NC-Abgleich für alle Verbindungen wurde angefordert.';
$state['queued_at'] = $now;
$state['timestamp'] = $now;
$state['connection_id'] = $connection_id;
$state['next_due'] = $now + bratonien_tools_nc_scheduler_interval();
bratonien_tools_nc_scheduler_write_state($state);
if ($connection_id > 0)
{
bratonien_tools_nc_scheduler_write_connection_status($connection_id, 'queued', 'Abgleich wurde angefordert.');
}
$runner = BRATONIEN_TOOLS_PATH.'runtime/native-runner.php';
$runner_args = $connection_id > 0 ? ' --connection-id='.escapeshellarg((string)$connection_id) : '';
$command = escapeshellarg($php).' '.escapeshellarg($runner).$runner_args.' >> '.escapeshellarg($paths['log']).' 2>&1 &';
$spec = array(
0=>array('file','/dev/null','r'),
1=>array('file','/dev/null','a'),
2=>array('file','/dev/null','a'),
);
$process = function_exists('proc_open') ? @proc_open(array('/bin/sh','-c',$command), $spec, $pipes) : false;
$exit = is_resource($process) ? proc_close($process) : 1;
@flock($lock, LOCK_UN);
fclose($lock);
if ($exit !== 0)
{
$state = bratonien_tools_nc_scheduler_read_state();
$state['state'] = 'error';
$state['message'] = 'Der native NC-Abgleich konnte nicht gestartet werden.';
$state['timestamp'] = time();
bratonien_tools_nc_scheduler_write_state($state);
if ($connection_id > 0)
{
bratonien_tools_nc_scheduler_write_connection_status($connection_id, 'error', 'Abgleich konnte nicht gestartet werden.');
}
throw new RuntimeException('Der native NC-Abgleich konnte nicht gestartet werden.');
}
return array('started'=>true, 'message'=>$connection_id > 0 ? 'Abgleich für Verbindung #'.$connection_id.' wurde angefordert.' : 'Abgleich für alle Verbindungen wurde angefordert.');
}
function bratonien_tools_nc_scheduler_tick()
{
$state = bratonien_tools_nc_scheduler_read_state();
if (isset($state['enabled']) && !$state['enabled']) return;
$next_due = (int)($state['next_due'] ?? 0);
if ($next_due > time()) return;
try
{
bratonien_tools_nc_scheduler_spawn(false, 0);
}
catch (Throwable $e)
{
$state = bratonien_tools_nc_scheduler_read_state();
$state['state'] = 'error';
$state['message'] = $e->getMessage();
$state['timestamp'] = time();
$state['next_due'] = time() + bratonien_tools_nc_scheduler_interval();
bratonien_tools_nc_scheduler_write_state($state);
}
}

View File

@@ -4,13 +4,109 @@ if (!defined('PHPWG_ROOT_PATH'))
die('Hacking attempt!'); die('Hacking attempt!');
} }
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_scheduler.inc.php'); function bratonien_tools_nc_connector_systemctl_value(array $args)
{
if (!function_exists('proc_open'))
{
return '';
}
$command = array_merge(array('/usr/bin/systemctl'), $args);
$spec = array(
0 => array('file', '/dev/null', 'r'),
1 => array('pipe', 'w'),
2 => array('pipe', 'w'),
);
$environment = array_merge($_ENV, array('LC_ALL'=>'C', 'LANG'=>'C'));
$process = @proc_open($command, $spec, $pipes, null, $environment);
if (!is_resource($process))
{
return '';
}
$stdout = stream_get_contents($pipes[1]);
stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$exit = proc_close($process);
return $exit === 0 ? trim((string)$stdout) : '';
}
function bratonien_tools_nc_connector_parse_systemd_time($value)
{
$value = trim((string)$value);
if ($value === '' || strtolower($value) === 'n/a')
{
return 0;
}
$parsed = strtotime($value);
return $parsed === false ? 0 : (int)$parsed;
}
function bratonien_tools_nc_connector_monotonic_to_timestamp($value)
{
$value = trim((string)$value);
if ($value === '' || $value === '0' || strtolower($value) === 'n/a')
{
return 0;
}
if (preg_match('/^([0-9]+)(?:us)?$/', $value, $matches))
{
$next_boot_seconds = ((float)$matches[1]) / 1000000;
}
else
{
return 0;
}
$uptime_raw = @file_get_contents('/proc/uptime');
if (!is_string($uptime_raw) || !preg_match('/^([0-9]+(?:\.[0-9]+)?)/', trim($uptime_raw), $uptime_match))
{
return 0;
}
$remaining = $next_boot_seconds - (float)$uptime_match[1];
if ($remaining < -1)
{
return 0;
}
return (int)round(time() + max(0, $remaining));
}
function bratonien_tools_nc_connector_next_from_timer_list($timer)
{
$line = bratonien_tools_nc_connector_systemctl_value(array(
'list-timers', '--all', '--no-pager', '--no-legend', $timer,
));
if ($line === '')
{
return 0;
}
$first_line = trim((string)strtok($line, "\n"));
if (preg_match('/^(\S+\s+\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s+\S+)/', $first_line, $matches))
{
$parsed = strtotime($matches[1]);
return $parsed === false ? 0 : (int)$parsed;
}
return 0;
}
function bratonien_tools_nc_connector_connection_last_status(array $connection) function bratonien_tools_nc_connector_connection_last_status(array $connection)
{ {
$empty = array( $empty = array(
'timestamp'=>0,'label'=>'Nicht verfügbar','state'=>'','message'=>'','auth_mode'=>'', 'timestamp'=>0,
'api_state'=>'','api_message'=>'','fallback_state'=>'','fallback_message'=>'','error_detail'=>'', 'label'=>'Nicht verfügbar',
'state'=>'',
'message'=>'',
'auth_mode'=>'',
'api_state'=>'',
'api_message'=>'',
'fallback_state'=>'',
'fallback_message'=>'',
'error_detail'=>'',
); );
$connection_id = (int)($connection['id'] ?? 0); $connection_id = (int)($connection['id'] ?? 0);
@@ -18,25 +114,42 @@ function bratonien_tools_nc_connector_connection_last_status(array $connection)
if ($connection_id > 0) if ($connection_id > 0)
{ {
$candidates[] = rtrim(PHPWG_ROOT_PATH, '/').'/_data/bratonien-tools/nc-connector-status/connection-'.$connection_id.'.json'; $candidates[] = rtrim(PHPWG_ROOT_PATH, '/').'/_data/bratonien-tools/nc-connector-status/connection-'.$connection_id.'.json';
$candidates[] = rtrim(PHPWG_ROOT_PATH, '/').'/_data/bratonien-tools/nc-connector-state/connection-'.$connection_id.'/connector-status.json';
} }
$config = isset($connection['config']) && is_array($connection['config']) ? $connection['config'] : array(); $config = isset($connection['config']) && is_array($connection['config']) ? $connection['config'] : array();
$state_dir = rtrim((string)($config['state_dir'] ?? ''), '/'); $state_dir = rtrim((string)($config['state_dir'] ?? ''), '/');
if ($state_dir !== '') $candidates[] = $state_dir.'/connector-status.json'; if ($state_dir === '' && $connection_id > 0)
{
$state_dir = '/var/lib/bratonien-tools/nc-connector/connection-'.$connection_id;
}
if ($state_dir !== '')
{
$candidates[] = $state_dir.'/connector-status.json';
}
$decoded = null; $decoded = null;
foreach (array_unique($candidates) as $candidate) foreach ($candidates as $candidate)
{ {
if (!is_readable($candidate)) continue; if (!is_readable($candidate))
$value = json_decode((string)@file_get_contents($candidate), true); {
if (is_array($value)) { $decoded = $value; break; } continue;
}
$value = json_decode((string)@file_get_contents($candidate), true);
if (is_array($value))
{
$decoded = $value;
break;
}
}
if (!is_array($decoded))
{
return $empty;
} }
if (!is_array($decoded)) return $empty;
$timestamp = (int)($decoded['timestamp'] ?? 0); $timestamp = (int)($decoded['timestamp'] ?? 0);
$api = isset($decoded['api']) && is_array($decoded['api']) ? $decoded['api'] : array(); $api = isset($decoded['api']) && is_array($decoded['api']) ? $decoded['api'] : array();
$fallback = isset($decoded['fallback']) && is_array($decoded['fallback']) ? $decoded['fallback'] : array(); $fallback = isset($decoded['fallback']) && is_array($decoded['fallback']) ? $decoded['fallback'] : array();
return array( return array(
'timestamp'=>$timestamp, 'timestamp'=>$timestamp,
'label'=>$timestamp > 0 ? date('d.m.Y H:i:s', $timestamp) : 'Nicht verfügbar', 'label'=>$timestamp > 0 ? date('d.m.Y H:i:s', $timestamp) : 'Nicht verfügbar',
@@ -54,74 +167,85 @@ function bratonien_tools_nc_connector_connection_last_status(array $connection)
function bratonien_tools_nc_connector_last_status(array $connections) function bratonien_tools_nc_connector_last_status(array $connections)
{ {
$latest = array('timestamp'=>0, 'state'=>'', 'message'=>'', 'auth_mode'=>'', 'api_state'=>'', 'api_message'=>'', 'fallback_state'=>'', 'fallback_message'=>'', 'error_detail'=>''); $latest = array('timestamp'=>0, 'state'=>'', 'message'=>'', 'auth_mode'=>'', 'api_state'=>'', 'api_message'=>'', 'fallback_state'=>'', 'fallback_message'=>'', 'error_detail'=>'');
foreach ($connections as $connection) foreach ($connections as $connection)
{ {
if (empty($connection['enabled'])) continue; if (empty($connection['enabled']))
$status = bratonien_tools_nc_connector_connection_last_status($connection); {
if ((int)$status['timestamp'] >= (int)$latest['timestamp']) $latest = $status; continue;
} }
$status = bratonien_tools_nc_connector_connection_last_status($connection);
if ((int)$status['timestamp'] >= (int)$latest['timestamp'])
{
$latest = $status;
}
}
return $latest; return $latest;
} }
function bratonien_tools_nc_connector_system_status(array $connections = array()) function bratonien_tools_nc_connector_system_status(array $connections = array())
{ {
$scheduler = bratonien_tools_nc_scheduler_read_state(); $timer = 'bratonien-nc-connector.timer';
$enabled = !isset($scheduler['enabled']) || !empty($scheduler['enabled']); $service = 'bratonien-nc-connector.service';
$scheduler_state = (string)($scheduler['state'] ?? '');
$running = $scheduler_state === 'running';
$queued = $scheduler_state === 'queued';
$started = (int)($scheduler['started_at'] ?? 0);
$next = (int)($scheduler['next_due'] ?? 0);
$last = bratonien_tools_nc_connector_last_status($connections);
if ($next > 0) $active = bratonien_tools_nc_connector_systemctl_value(array('is-active', $timer));
$enabled = bratonien_tools_nc_connector_systemctl_value(array('is-enabled', $timer));
$service_active_raw = bratonien_tools_nc_connector_systemctl_value(array('is-active', $service));
$service_active = in_array($service_active_raw, array('active', 'activating'), true);
$service_started_raw = bratonien_tools_nc_connector_systemctl_value(array('show', $service, '--property=ActiveEnterTimestamp', '--value'));
$service_started_timestamp = bratonien_tools_nc_connector_parse_systemd_time($service_started_raw);
$next_realtime_raw = bratonien_tools_nc_connector_systemctl_value(array('show', $timer, '--property=NextElapseUSecRealtime', '--value'));
$next_timestamp = bratonien_tools_nc_connector_parse_systemd_time($next_realtime_raw);
if ($next_timestamp <= 0)
{ {
$next_label = date('d.m.Y H:i:s', $next).' (beim nächsten Piwigo-Aufruf)'; $next_monotonic_raw = bratonien_tools_nc_connector_systemctl_value(array('show', $timer, '--property=NextElapseUSecMonotonic', '--value'));
$next_timestamp = bratonien_tools_nc_connector_monotonic_to_timestamp($next_monotonic_raw);
}
if ($next_timestamp <= 0)
{
$next_timestamp = bratonien_tools_nc_connector_next_from_timer_list($timer);
}
$last = bratonien_tools_nc_connector_last_status($connections);
if ($last['timestamp'] <= 0)
{
$last_raw = bratonien_tools_nc_connector_systemctl_value(array('show', $service, '--property=ExecMainExitTimestamp', '--value'));
$last['timestamp'] = bratonien_tools_nc_connector_parse_systemd_time($last_raw);
}
if ($next_timestamp > 0)
{
$next_label = date('d.m.Y H:i:s', $next_timestamp);
}
elseif ($service_active && $active === 'active' && $enabled === 'enabled')
{
$next_label = 'Nach Abschluss des aktuellen Laufs';
}
elseif ($active === 'active' && $enabled === 'enabled')
{
$next_label = 'Wird von systemd neu geplant';
} }
else else
{ {
$next_label = 'Beim nächsten Piwigo-Aufruf'; $next_label = 'Nicht verfügbar';
} }
$scheduler_timestamp = (int)($scheduler['timestamp'] ?? 0);
if ($scheduler_timestamp > (int)$last['timestamp'])
{
$last['timestamp'] = $scheduler_timestamp;
$last['state'] = $scheduler_state;
$last['message'] = (string)($scheduler['message'] ?? '');
if ($scheduler_state === 'error')
{
$detail = trim((string)($scheduler['stderr'] ?? ''));
if ($detail === '') $detail = trim((string)($scheduler['stdout'] ?? ''));
$last['error_detail'] = $detail;
}
}
elseif ((int)$last['timestamp'] <= 0 && !empty($scheduler['finished_at']))
{
$last['timestamp'] = (int)$scheduler['finished_at'];
$last['state'] = $scheduler_state;
$last['message'] = (string)($scheduler['message'] ?? '');
if ((string)$last['state'] === 'error')
{
$detail = trim((string)($scheduler['stderr'] ?? ''));
if ($detail === '') $detail = trim((string)($scheduler['stdout'] ?? ''));
$last['error_detail'] = $detail;
}
}
$current_label = 'Kein Lauf aktiv';
if ($queued) $current_label = 'Abgleich angefordert';
if ($running) $current_label = $started > 0 ? 'Läuft seit '.date('d.m.Y H:i:s', $started) : 'Läuft gerade';
return array( return array(
'timer_name'=>'Piwigo nativer NC-Scheduler', 'timer_name' => $timer,
'timer_active'=>$enabled, 'timer_active' => $active === 'active',
'timer_enabled'=>$enabled, 'timer_enabled' => $enabled === 'enabled',
'service_active'=>$running || $queued, 'service_active' => $service_active,
'current_run_timestamp'=>$queued ? (int)($scheduler['queued_at'] ?? 0) : $started, 'current_run_timestamp' => $service_started_timestamp,
'current_run_label'=>$current_label, 'current_run_label' => $service_active
? ($service_started_timestamp > 0 ? 'Läuft seit '.date('d.m.Y H:i:s', $service_started_timestamp) : 'Läuft gerade')
: 'Kein Lauf aktiv',
'last_run_timestamp' => (int)$last['timestamp'], 'last_run_timestamp' => (int)$last['timestamp'],
'last_run_label'=>(int)$last['timestamp'] > 0 ? date('d.m.Y H:i:s', (int)$last['timestamp']) : 'Nicht verfügbar', 'last_run_label' => $last['timestamp'] > 0 ? date('d.m.Y H:i:s', (int)$last['timestamp']) : 'Nicht verfügbar',
'last_run_state' => (string)$last['state'], 'last_run_state' => (string)$last['state'],
'last_run_message' => (string)$last['message'], 'last_run_message' => (string)$last['message'],
'last_run_auth_mode' => (string)$last['auth_mode'], 'last_run_auth_mode' => (string)$last['auth_mode'],
@@ -130,7 +254,7 @@ function bratonien_tools_nc_connector_system_status(array $connections = array()
'last_run_fallback_state' => (string)$last['fallback_state'], 'last_run_fallback_state' => (string)$last['fallback_state'],
'last_run_fallback_message' => (string)$last['fallback_message'], 'last_run_fallback_message' => (string)$last['fallback_message'],
'last_run_error_detail' => (string)$last['error_detail'], 'last_run_error_detail' => (string)$last['error_detail'],
'next_run_timestamp'=>$next, 'next_run_timestamp' => $next_timestamp,
'next_run_label' => $next_label, 'next_run_label' => $next_label,
'legacy_runtime_exists' => is_dir('/opt/piwigo-sync'), 'legacy_runtime_exists' => is_dir('/opt/piwigo-sync'),
'legacy_config_exists' => is_dir('/etc/piwigo-sync'), 'legacy_config_exists' => is_dir('/etc/piwigo-sync'),

View File

@@ -28,7 +28,7 @@ function bratonien_tools_nc_wizard_scan_webdav_first()
{ {
try try
{ {
$response = bratonien_tools_nc_transport_http($candidate_url.'/status.php'); $response = bratonien_tools_nc_wizard_http($candidate_url.'/status.php');
if ($response['status'] < 200 || $response['status'] >= 300) continue; if ($response['status'] < 200 || $response['status'] >= 300) continue;
$candidate_status = json_decode($response['body'], true); $candidate_status = json_decode($response['body'], true);
if (!is_array($candidate_status) || empty($candidate_status['installed'])) continue; if (!is_array($candidate_status) || empty($candidate_status['installed'])) continue;
@@ -44,7 +44,7 @@ function bratonien_tools_nc_wizard_scan_webdav_first()
throw new RuntimeException('Unter dieser Adresse konnte keine Nextcloud erreicht werden. HTTP und HTTPS wurden automatisch geprüft.'); throw new RuntimeException('Unter dieser Adresse konnte keine Nextcloud erreicht werden. HTTP und HTTPS wurden automatisch geprüft.');
} }
$user_response = bratonien_tools_nc_transport_http( $user_response = bratonien_tools_nc_wizard_http(
$base_url.'/ocs/v2.php/cloud/user?format=json', $base_url.'/ocs/v2.php/cloud/user?format=json',
$username, $username,
$password, $password,
@@ -104,7 +104,7 @@ function bratonien_tools_nc_wizard_scan_webdav_first()
'api_error'=>'', 'api_error'=>'',
)); ));
bratonien_tools_nc_transport_refresh_directory_state($state, ''); bratonien_tools_nc_wizard_refresh_directory_state($state, '');
bratonien_tools_nc_wizard_store($state); bratonien_tools_nc_wizard_store($state);
return array('message'=>'Nextcloud und WebDAV wurden bestätigt. Jetzt können die Verzeichnisse des angemeldeten Benutzers ausgewählt werden.'); return array('message'=>'Nextcloud und WebDAV wurden bestätigt. Jetzt können die Verzeichnisse des angemeldeten Benutzers ausgewählt werden.');

View File

@@ -1,115 +0,0 @@
<?php
if (!defined('PHPWG_ROOT_PATH'))
{
die('Hacking attempt!');
}
function bratonien_tools_nc_legacy_remove_tree($path, $allowed_root)
{
$path = rtrim(str_replace('\\', '/', (string)$path), '/');
$allowed_root = rtrim(str_replace('\\', '/', (string)$allowed_root), '/');
if ($path === '' || $allowed_root === '' || $path === $allowed_root || strpos($path, $allowed_root.'/') !== 0)
{
return 0;
}
if (!file_exists($path) && !is_link($path)) return 0;
if (is_link($path) || is_file($path))
{
return @unlink($path) ? 1 : 0;
}
$removed = 0;
$items = @scandir($path);
if (is_array($items))
{
foreach ($items as $item)
{
if ($item === '.' || $item === '..') continue;
$removed += bratonien_tools_nc_legacy_remove_tree($path.'/'.$item, $allowed_root);
}
}
if (@rmdir($path)) $removed++;
return $removed;
}
function bratonien_tools_nc_legacy_remove_state_files($state_root)
{
$removed = 0;
foreach (glob(rtrim($state_root, '/').'/connection-*') ?: array() as $connection_dir)
{
if (!is_dir($connection_dir)) continue;
foreach (array('webdav-map.json', 'webdav-manifest.tsv', 'webdav-shadow-map.json') as $name)
{
$path = $connection_dir.'/'.$name;
if (is_file($path) && @unlink($path)) $removed++;
}
}
return $removed;
}
function bratonien_tools_nc_reset_legacy_imports()
{
include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
$ids = array();
$result = pwg_query(
"SELECT id FROM ".IMAGES_TABLE.
" WHERE path LIKE '%nc-webdav-source/connection-%'"
);
while ($row = pwg_db_fetch_assoc($result))
{
$ids[] = (int)$row['id'];
}
if ($ids)
{
delete_elements($ids, false);
}
$data_root = rtrim(PHPWG_ROOT_PATH, '/').'/_data';
$tool_root = $data_root.'/bratonien-tools';
$removed_files = 0;
foreach (array(
$tool_root.'/nc-webdav-source',
$tool_root.'/nc-webdav-preview',
$tool_root.'/nc-webdav-gallery',
) as $generated_root)
{
$removed_files += bratonien_tools_nc_legacy_remove_tree($generated_root, $data_root);
}
if (defined('PWG_DERIVATIVE_DIR'))
{
$derivative_root = rtrim(PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR, '/');
foreach (array(
$derivative_root.'/_data/bratonien-tools/nc-webdav-source',
$derivative_root.'/_data/bratonien-tools/nc-webdav-gallery',
) as $generated_derivative_root)
{
$removed_files += bratonien_tools_nc_legacy_remove_tree($generated_derivative_root, $derivative_root);
}
}
$removed_state_files = bratonien_tools_nc_legacy_remove_state_files($tool_root.'/nc-connector-state');
if ($ids)
{
update_category('all');
invalidate_user_cache(true);
}
$result = array(
'timestamp'=>time(),
'removed_image_records'=>count($ids),
'removed_generated_entries'=>$removed_files,
'removed_state_files'=>$removed_state_files,
);
if (function_exists('conf_update_param'))
{
conf_update_param('bratonien_nc_legacy_reset_0977', json_encode($result));
}
return $result;
}

View File

@@ -52,17 +52,7 @@ function bratonien_tools_nc_find_album($parent_id, $dir, $name, $excluded_site_i
$where_parent = $parent_id === null ? 'id_uppercat IS NULL' : 'id_uppercat='.(int)$parent_id; $where_parent = $parent_id === null ? 'id_uppercat IS NULL' : 'id_uppercat='.(int)$parent_id;
$dir_sql = pwg_db_real_escape_string((string)$dir); $dir_sql = pwg_db_real_escape_string((string)$dir);
$name_sql = pwg_db_real_escape_string((string)$name); $name_sql = pwg_db_real_escape_string((string)$name);
$query = ' $query = '\nSELECT id, dir, name\n FROM '.CATEGORIES_TABLE.'\n WHERE '.$where_parent.'\n AND (site_id IS NULL OR site_id <> '.(int)$excluded_site_id.')\n AND (\n dir = \\''.$dir_sql.'\\'\n OR LOWER(name) = LOWER(\\''.$name_sql.'\\')\n )\n ORDER BY CASE WHEN dir = \\''.$dir_sql.'\\' THEN 0 ELSE 1 END, id\n LIMIT 1\n;';
SELECT id, dir, name
FROM '.CATEGORIES_TABLE.'
WHERE '.$where_parent.'
AND (
dir = \''.$dir_sql.'\'
OR LOWER(name) = LOWER(\''.$name_sql.'\')
)
ORDER BY CASE WHEN dir = \''.$dir_sql.'\' THEN 0 ELSE 1 END, id
LIMIT 1
;';
$result = pwg_query($query); $result = pwg_query($query);
if (!pwg_db_num_rows($result)) return null; if (!pwg_db_num_rows($result)) return null;
$row = pwg_db_fetch_assoc($result); $row = pwg_db_fetch_assoc($result);
@@ -121,9 +111,10 @@ function bratonien_tools_nc_managed_images($basedir)
function bratonien_tools_nc_remove_storage_categories($site_id) function bratonien_tools_nc_remove_storage_categories($site_id)
{ {
// Bestehende Piwigo-Alben gehoeren nicht automatisch dem Connector. $ids = query2array('SELECT id FROM '.CATEGORIES_TABLE.' WHERE site_id='.(int)$site_id.' AND dir IS NOT NULL', null, 'id');
// Ohne eindeutige Connector-Eigentumsmarkierung darf hier nichts geloescht werden. if (!$ids) return 0;
return 0; delete_categories(array_map('intval', $ids));
return count($ids);
} }
function bratonien_tools_ws_nc_sync_productive($params, &$service) function bratonien_tools_ws_nc_sync_productive($params, &$service)
@@ -167,6 +158,8 @@ function bratonien_tools_ws_nc_sync_productive($params, &$service)
try try
{ {
$counts['removed_duplicate_categories'] = bratonien_tools_nc_remove_storage_categories($site_id);
list($dbnow) = pwg_db_fetch_row(pwg_query('SELECT NOW()')); list($dbnow) = pwg_db_fetch_row(pwg_query('SELECT NOW()'));
$fs_dirs = $site_reader->get_full_directories($basedir); $fs_dirs = $site_reader->get_full_directories($basedir);
usort($fs_dirs, function($a, $b) usort($fs_dirs, function($a, $b)
@@ -195,10 +188,20 @@ function bratonien_tools_ws_nc_sync_productive($params, &$service)
$db_elements = bratonien_tools_nc_managed_images($basedir); $db_elements = bratonien_tools_nc_managed_images($basedir);
$db_by_path = array_flip($db_elements); $db_by_path = array_flip($db_elements);
// Nicht-destruktiver Schutz: Bestehende Piwigo-Bilder werden niemals allein $to_delete = array();
// deshalb geloescht, weil sie im aktuellen WebDAV-Scan nicht vorkommen. foreach ($db_elements as $id=>$path)
// Das Entfernen ist erst wieder zulaessig, wenn Connector-Eigentum eindeutig {
// und verbindungsbezogen gespeichert wird. if (!array_key_exists($path, $fs)) $to_delete[] = (int)$id;
}
if ($to_delete)
{
delete_elements($to_delete, false);
$counts['del_elements'] = count($to_delete);
foreach ($to_delete as $id)
{
if (isset($db_elements[$id])) unset($db_by_path[$db_elements[$id]], $db_elements[$id]);
}
}
$next_element_id = pwg_db_nextval('id', IMAGES_TABLE); $next_element_id = pwg_db_nextval('id', IMAGES_TABLE);
$image_inserts = array(); $image_inserts = array();
@@ -218,9 +221,13 @@ function bratonien_tools_ws_nc_sync_productive($params, &$service)
if (isset($db_by_path[$path])) if (isset($db_by_path[$path]))
{ {
// Altbestand niemals umhaengen. Vorhandene Bild-Album-Zuordnungen bleiben $id = (int)$db_by_path[$path];
// exakt bestehen; der Connector darf nur neue Datensaetze ergaenzen. $all_ids[] = $id;
$all_ids[] = (int)$db_by_path[$path]; pwg_query('DELETE FROM '.IMAGE_CATEGORY_TABLE.' WHERE image_id='.$id);
if ($category_id !== null)
{
single_insert(IMAGE_CATEGORY_TABLE, array('image_id'=>$id, 'category_id'=>$category_id));
}
continue; continue;
} }
@@ -258,10 +265,8 @@ function bratonien_tools_ws_nc_sync_productive($params, &$service)
$counts['new_elements'] = count($new_ids); $counts['new_elements'] = count($new_ids);
} }
// Bestehende Bilder werden nicht durch den Connector aktualisiert. Nur neu
// angelegte Connector-Bilder erhalten die aus der Quelle ermittelten Attribute.
$updates = array(); $updates = array();
foreach ($new_ids as $id) foreach ($all_ids as $id)
{ {
$path_result = pwg_query('SELECT path FROM '.IMAGES_TABLE.' WHERE id='.(int)$id.' LIMIT 1'); $path_result = pwg_query('SELECT path FROM '.IMAGES_TABLE.' WHERE id='.(int)$id.' LIMIT 1');
if (!pwg_db_num_rows($path_result)) continue; if (!pwg_db_num_rows($path_result)) continue;

View File

@@ -1,340 +0,0 @@
<?php
function bratonien_tools_nc_transport_host($url)
{
$host = trim((string)parse_url((string)$url, PHP_URL_HOST));
if ($host === '') throw new RuntimeException('Die Nextcloud-Adresse enthält keinen gültigen Hostnamen oder keine IP-Adresse.');
return trim($host, '[]');
}
function bratonien_tools_nc_transport_scheme($url)
{
$scheme = strtolower(trim((string)parse_url((string)$url, PHP_URL_SCHEME)));
if (!in_array($scheme, array('http','https'), true)) throw new RuntimeException('Nextcloud muss per HTTP oder HTTPS angesprochen werden.');
return $scheme;
}
function bratonien_tools_nc_transport_is_ip($host)
{
return filter_var(trim((string)$host, '[]'), FILTER_VALIDATE_IP) !== false;
}
function bratonien_tools_nc_transport_public_ip($host)
{
static $cache = array();
$host = strtolower(trim((string)$host, '[]'));
if ($host === '') throw new RuntimeException('Für die Nextcloud-Verbindung fehlt der Hostname.');
if (bratonien_tools_nc_transport_is_ip($host)) return $host;
if (isset($cache[$host])) return $cache[$host];
if (!function_exists('curl_init')) throw new RuntimeException('Der öffentliche DNS-Abgleich benötigt PHP-cURL.');
$providers = array(
array('host'=>'dns.google', 'ips'=>array('8.8.8.8','8.8.4.4'), 'url'=>'https://dns.google/resolve?name='.rawurlencode($host).'&type=A'),
array('host'=>'cloudflare-dns.com', 'ips'=>array('1.1.1.1','1.0.0.1'), 'url'=>'https://cloudflare-dns.com/dns-query?name='.rawurlencode($host).'&type=A'),
);
foreach ($providers as $provider)
{
foreach ($provider['ips'] as $resolver_ip)
{
$ch = curl_init($provider['url']);
$options = array(
CURLOPT_RETURNTRANSFER=>true,
CURLOPT_FOLLOWLOCATION=>false,
CURLOPT_CONNECTTIMEOUT=>5,
CURLOPT_TIMEOUT=>10,
CURLOPT_HTTPHEADER=>array('Accept: application/dns-json'),
CURLOPT_USERAGENT=>'Bratonien-Tools-DNS/0.9.7.1',
);
if (defined('CURLOPT_RESOLVE'))
{
$options[CURLOPT_RESOLVE] = array($provider['host'].':443:'.$resolver_ip);
}
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 || $status < 200 || $status >= 300) continue;
$decoded = json_decode((string)$body, true);
if (!is_array($decoded) || !isset($decoded['Answer']) || !is_array($decoded['Answer'])) continue;
foreach ($decoded['Answer'] as $answer)
{
if ((int)($answer['type'] ?? 0) !== 1) continue;
$ip = trim((string)($answer['data'] ?? ''));
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) continue;
return $cache[$host] = $ip;
}
}
}
throw new RuntimeException('Für '.$host.' konnte keine öffentliche IPv4-Adresse ermittelt werden.');
}
function bratonien_tools_nc_transport_resolve_entry($url)
{
$scheme = bratonien_tools_nc_transport_scheme($url);
$host = bratonien_tools_nc_transport_host($url);
if (bratonien_tools_nc_transport_is_ip($host)) return null;
$port = (int)parse_url((string)$url, PHP_URL_PORT);
if ($port < 1) $port = $scheme === 'https' ? 443 : 80;
$ip = bratonien_tools_nc_transport_public_ip($host);
return $host.':'.$port.':'.$ip;
}
function bratonien_tools_nc_transport_apply_curl(array &$options, $url)
{
$entry = bratonien_tools_nc_transport_resolve_entry($url);
if ($entry !== null)
{
if (!defined('CURLOPT_RESOLVE')) throw new RuntimeException('Diese cURL-Version unterstützt keine direkte Host-zu-IP-Zuordnung.');
$options[CURLOPT_RESOLVE] = array($entry);
}
}
function bratonien_tools_nc_transport_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.');
$ch = curl_init($url);
$options = array(
CURLOPT_RETURNTRANSFER=>true,
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/0.9.7.1',
);
bratonien_tools_nc_transport_apply_curl($options, $url);
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('Verbindung fehlgeschlagen'.($error !== '' ? ': '.$error : '.'));
}
return array('status'=>$status, 'body'=>(string)$body);
}
function bratonien_tools_nc_transport_webdav_list(array $state, $path = '')
{
if (empty($state['scan_ok']) || trim((string)$state['base_url']) === '' || trim((string)$state['username']) === '' || (string)$state['_password'] === '')
{
throw new RuntimeException('Die Nextcloud-Sitzung des Assistenten ist nicht vollständig.');
}
if (!function_exists('curl_init')) throw new RuntimeException('cURL ist für die Verzeichnisauswahl nicht verfügbar.');
$path = trim((string)$path, '/');
if ($path !== '' && preg_match('#(^|/)\.\.(/|$)#', $path)) throw new RuntimeException('Ungültiger Verzeichnispfad.');
$segments = $path === '' ? array() : array_map('rawurlencode', explode('/', $path));
$user = rawurlencode((string)$state['username']);
$url = rtrim((string)$state['base_url'], '/').'/remote.php/dav/files/'.$user.'/'.implode('/', $segments);
if (substr($url, -1) !== '/') $url .= '/';
$body = '<?xml version="1.0"?><d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns"><d:prop><d:resourcetype/><d:displayname/><oc:fileid/></d:prop></d:propfind>';
$ch = curl_init($url);
$options = array(
CURLOPT_RETURNTRANSFER=>true,
CURLOPT_CUSTOMREQUEST=>'PROPFIND',
CURLOPT_POSTFIELDS=>$body,
CURLOPT_HTTPHEADER=>array('Depth: 1','Content-Type: application/xml; charset=utf-8'),
CURLOPT_HTTPAUTH=>CURLAUTH_BASIC,
CURLOPT_USERPWD=>(string)$state['username'].':'.(string)$state['_password'],
CURLOPT_CONNECTTIMEOUT=>8,
CURLOPT_TIMEOUT=>20,
);
bratonien_tools_nc_transport_apply_curl($options, $url);
curl_setopt_array($ch, $options);
$response = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response === false || $errno !== 0) throw new RuntimeException('Nextcloud-Verzeichnisse konnten nicht geladen werden'.($error !== '' ? ': '.$error : '.'));
if ($status === 401 || $status === 403) throw new RuntimeException('Nextcloud hat den Zugriff auf dieses Verzeichnis abgelehnt.');
if ($status !== 207) throw new RuntimeException('Nextcloud-Verzeichnisabfrage antwortete mit HTTP '.$status.'.');
libxml_use_internal_errors(true);
$xml = simplexml_load_string((string)$response);
if ($xml === false) throw new RuntimeException('Nextcloud hat eine ungültige WebDAV-Antwort geliefert.');
$xml->registerXPathNamespace('d', 'DAV:');
$xml->registerXPathNamespace('oc', 'http://owncloud.org/ns');
$children = array();
$fileids = array();
$current_fileid = 0;
$base_path = (string)parse_url($url, PHP_URL_PATH);
foreach ($xml->xpath('//d:response') as $item)
{
$item->registerXPathNamespace('d', 'DAV:');
$item->registerXPathNamespace('oc', 'http://owncloud.org/ns');
$hrefs = $item->xpath('d:href');
$collections = $item->xpath('d:propstat/d:prop/d:resourcetype/d:collection');
$ids = $item->xpath('d:propstat/d:prop/oc:fileid');
if (!$hrefs || !$collections || !$ids) continue;
$fileid = (int)trim((string)$ids[0]);
if ($fileid < 1) continue;
$href = rawurldecode((string)$hrefs[0]);
$href_path = (string)parse_url($href, PHP_URL_PATH);
if (rtrim($href_path, '/') === rtrim($base_path, '/'))
{
$current_fileid = $fileid;
continue;
}
$name = basename(rtrim($href_path, '/'));
if ($name === '') continue;
$child_path = $path === '' ? $name : $path.'/'.$name;
$children[$child_path] = $name;
$fileids[$child_path] = $fileid;
}
natcasesort($children);
if ($current_fileid < 1) throw new RuntimeException('Nextcloud hat für das aktuelle Verzeichnis keine eindeutige Datei-ID geliefert.');
$parent = '';
if ($path !== '')
{
$parts = explode('/', $path);
array_pop($parts);
$parent = implode('/', $parts);
}
return array(
'current'=>$path,
'parent'=>$parent,
'children'=>$children,
'current_fileid'=>$current_fileid,
'fileids'=>$fileids,
);
}
function bratonien_tools_nc_transport_refresh_directory_state(array &$state, $path = null)
{
if ($path === null) $path = (string)($state['directory_path'] ?? '');
$listing = bratonien_tools_nc_transport_webdav_list($state, $path);
$state['directory_path'] = (string)$listing['current'];
$state['directory_parent'] = (string)$listing['parent'];
$state['directory_children'] = (array)$listing['children'];
$state['directory_current_fileid'] = (int)$listing['current_fileid'];
$state['directory_fileids'] = (array)$listing['fileids'];
if (!isset($state['directory_selected']) || !is_array($state['directory_selected'])) $state['directory_selected'] = array();
if (!isset($state['directory_selected_fileids']) || !is_array($state['directory_selected_fileids'])) $state['directory_selected_fileids'] = array();
}
function bratonien_tools_nc_transport_wizard_directory_browse()
{
$state = bratonien_tools_nc_wizard_state();
if ((int)$state['step'] !== 2 || (string)$state['technical_stage'] !== 'mounts' || empty($state['directory_selection_ready'])) throw new RuntimeException('Die Verzeichnisauswahl ist in diesem Fenster nicht verfügbar.');
$path = trim((string)($_POST['nc_wizard_directory_path'] ?? ''), '/');
bratonien_tools_nc_transport_refresh_directory_state($state, $path);
bratonien_tools_nc_wizard_store($state);
return array('message'=>'Verzeichnis geöffnet.');
}
function bratonien_tools_nc_transport_edit_start()
{
$id = (int)($_POST['connection_id'] ?? 0);
$connection = bratonien_tools_nc_connector_connection($id, true);
if (!$connection) throw new RuntimeException('Connector-Verbindung wurde nicht gefunden.');
$config = isset($connection['config']) && is_array($connection['config']) ? $connection['config'] : array();
$is_webdav = (string)$connection['adapter'] === 'remote'
&& (string)($config['source_mode'] ?? '') === 'webdav-placeholder';
if (!$is_webdav) return bratonien_tools_nc_connector_edit_start();
$credentials = bratonien_tools_nc_connector_scoped_secret($connection);
$base_url = trim((string)($config['nextcloud_url'] ?? ''));
$username = trim((string)($credentials['nextcloud_user'] ?? ''));
if ($username === '') $username = trim((string)($config['nextcloud_access_user'] ?? $config['access_user'] ?? ''));
$password = (string)($credentials['nextcloud_password'] ?? '');
$roots = isset($config['roots']) && is_array($config['roots']) ? array_values($config['roots']) : array();
$selected = array();
$selected_ids = array();
foreach ($roots as $root)
{
$path = trim((string)($root['webdav_path'] ?? ''), '/');
$fileid = (int)($root['fileid'] ?? 0);
if ($fileid < 1) continue;
$selected[] = $path;
$selected_ids[$path] = $fileid;
}
$state = bratonien_tools_nc_wizard_state();
$state = array_merge($state, array(
'editing_connection_id'=>$id,
'editing_adapter'=>(string)$connection['adapter'],
'editing_mode'=>'update',
'connection_name'=>(string)$connection['name'],
'host_input'=>$base_url,
'base_url'=>$base_url,
'username'=>$username,
'_password'=>$password,
'_fallback_user'=>(string)($credentials['piwigo_user'] ?? ''),
'_fallback_password'=>(string)($credentials['piwigo_password'] ?? ''),
'_api_key_id'=>(string)($credentials['api_key_id'] ?? ''),
'_api_key_secret'=>(string)($credentials['api_key_secret'] ?? ''),
'api_status'=>trim((string)($credentials['api_key_id'] ?? '')) !== '' && trim((string)($credentials['api_key_secret'] ?? '')) !== '' ? 'ok' : 'pending',
'roots'=>$roots,
'directory_selected'=>$selected,
'directory_selected_fileids'=>$selected_ids,
'source_mode'=>'webdav-placeholder',
'transport'=>'webdav',
));
if ($base_url !== '' && $username !== '' && $password !== '')
{
$state['step'] = 2;
$state['scan_ok'] = true;
$state['technical_stage'] = 'mounts';
$state['technical_source'] = 'WebDAV';
$state['technical_error'] = '';
$state['technical_complete'] = false;
$state['directory_selection_ready'] = true;
$state['directory_path'] = '';
$state['directory_parent'] = '';
$state['directory_children'] = array();
$state['directory_current_fileid'] = 0;
try
{
bratonien_tools_nc_transport_refresh_directory_state($state, '');
}
catch (Throwable $e)
{
$state['step'] = 1;
$state['scan_ok'] = false;
$state['technical_error'] = $e->getMessage();
}
}
else
{
$state['step'] = 1;
$state['scan_ok'] = false;
}
bratonien_tools_nc_wizard_store($state);
return array('message'=>'Verbindung #'.$id.' wurde zum Bearbeiten geöffnet.');
}

View File

@@ -6,7 +6,7 @@ if (!defined('PHPWG_ROOT_PATH'))
if (isset($GLOBALS['template']) && is_object($GLOBALS['template']) && method_exists($GLOBALS['template'], 'func_combine_script')) if (isset($GLOBALS['template']) && is_object($GLOBALS['template']) && method_exists($GLOBALS['template'], 'func_combine_script'))
{ {
$script_version = function_exists('bratonien_tools_current_version') ? bratonien_tools_current_version() : '0.9.7.1'; $script_version = function_exists('bratonien_tools_current_version') ? bratonien_tools_current_version() : '0.9.6.27';
$GLOBALS['template']->func_combine_script(array( $GLOBALS['template']->func_combine_script(array(
'id'=>'bratonien_nc_connector_edit_v2', 'id'=>'bratonien_nc_connector_edit_v2',
'path'=>BRATONIEN_TOOLS_PATH.'js/nc_connector_edit_v2.js', 'path'=>BRATONIEN_TOOLS_PATH.'js/nc_connector_edit_v2.js',
@@ -41,23 +41,57 @@ require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_delete_safe.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_webdav.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_webdav.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_wizard_webdav_flow.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_wizard_webdav_flow.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_edit.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_edit.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_transport.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_scheduler.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_system.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_system.inc.php');
function bratonien_tools_nc_connector_run_now() function bratonien_tools_nc_connector_run_now()
{ {
$connection_id = isset($_POST['connection_id']) ? max(0, (int)$_POST['connection_id']) : 0; $service = 'bratonien-nc-connector.service';
if ($connection_id > 0) $active = bratonien_tools_nc_connector_systemctl_value(array('is-active', $service));
if ($active === 'active' || $active === 'activating')
{ {
$connection = bratonien_tools_nc_connector_connection($connection_id, false); return array('message'=>'Der NC-Abgleich läuft bereits.');
if (!$connection) }
if (!function_exists('proc_open'))
{ {
throw new RuntimeException('Die ausgewählte Verbindung existiert nicht.'); throw new RuntimeException('Der NC-Abgleich konnte nicht gestartet werden: proc_open ist nicht verfügbar.');
} }
$commands = array(
array('/usr/bin/sudo', '-n', '/usr/bin/systemctl', 'start', '--no-block', $service),
array('/usr/bin/systemctl', 'start', '--no-block', $service),
);
$last_error = '';
foreach ($commands as $command)
{
if (!is_executable($command[0]))
{
continue;
} }
$result = bratonien_tools_nc_scheduler_spawn(true, $connection_id); $spec = array(
return array('message'=>(string)($result['message'] ?? ($connection_id > 0 ? 'Abgleich wurde gestartet.' : 'Abgleich für alle Verbindungen wurde gestartet.'))); 0=>array('file','/dev/null','r'),
1=>array('pipe','w'),
2=>array('pipe','w'),
);
$process = @proc_open($command, $spec, $pipes, null, array_merge($_ENV, array('LC_ALL'=>'C','LANG'=>'C')));
if (!is_resource($process))
{
continue;
}
$stdout = trim((string)stream_get_contents($pipes[1]));
$stderr = trim((string)stream_get_contents($pipes[2]));
fclose($pipes[1]);
fclose($pipes[2]);
$exit = proc_close($process);
if ($exit === 0)
{
return array('message'=>'NC-Abgleich wurde gestartet.');
}
$last_error = $stderr !== '' ? $stderr : $stdout;
}
throw new RuntimeException('Der NC-Abgleich konnte nicht gestartet werden'.($last_error !== '' ? ': '.$last_error : '.'));
} }
function bratonien_tools_get_tools() function bratonien_tools_get_tools()
@@ -84,7 +118,7 @@ function bratonien_tools_get_tools()
'album_share_regenerate_link' => array('handler' => 'bratonien_tools_regenerate_album_share_link'), 'album_share_regenerate_link' => array('handler' => 'bratonien_tools_regenerate_album_share_link'),
'album_share_revoke' => array('handler' => 'bratonien_tools_revoke_album_share'), 'album_share_revoke' => array('handler' => 'bratonien_tools_revoke_album_share'),
'nc_connector_create_webdav_parallel' => array('handler' => 'bratonien_tools_nc_connector_create_webdav_placeholder_from_wizard'), 'nc_connector_create_webdav_parallel' => array('handler' => 'bratonien_tools_nc_connector_create_webdav_placeholder_from_wizard'),
'nc_connector_edit_start' => array('handler' => 'bratonien_tools_nc_transport_edit_start'), 'nc_connector_edit_start' => array('handler' => 'bratonien_tools_nc_connector_edit_start'),
'nc_connector_update_local' => array('handler' => 'bratonien_tools_nc_connector_update_local_friendly'), 'nc_connector_update_local' => array('handler' => 'bratonien_tools_nc_connector_update_local_friendly'),
'nc_connector_delete' => array('handler' => 'bratonien_tools_nc_connector_delete_safe'), 'nc_connector_delete' => array('handler' => 'bratonien_tools_nc_connector_delete_safe'),
'nc_connector_update_name' => array('handler' => 'bratonien_tools_nc_connector_update_name'), 'nc_connector_update_name' => array('handler' => 'bratonien_tools_nc_connector_update_name'),
@@ -92,7 +126,7 @@ function bratonien_tools_get_tools()
'nc_connector_run_now' => array('handler' => 'bratonien_tools_nc_connector_run_now'), 'nc_connector_run_now' => array('handler' => 'bratonien_tools_nc_connector_run_now'),
'nc_connector_wizard_scan' => array('handler' => 'bratonien_tools_nc_wizard_scan_webdav_first'), 'nc_connector_wizard_scan' => array('handler' => 'bratonien_tools_nc_wizard_scan_webdav_first'),
'nc_connector_wizard_save_technical' => array('handler' => 'bratonien_tools_nc_wizard_save_technical_flow'), 'nc_connector_wizard_save_technical' => array('handler' => 'bratonien_tools_nc_wizard_save_technical_flow'),
'nc_connector_wizard_directory_browse' => array('handler' => 'bratonien_tools_nc_transport_wizard_directory_browse'), 'nc_connector_wizard_directory_browse' => array('handler' => 'bratonien_tools_nc_wizard_directory_browse'),
'nc_connector_wizard_directory_add' => array('handler' => 'bratonien_tools_nc_wizard_directory_add'), 'nc_connector_wizard_directory_add' => array('handler' => 'bratonien_tools_nc_wizard_directory_add'),
'nc_connector_wizard_directory_remove' => array('handler' => 'bratonien_tools_nc_wizard_directory_remove'), 'nc_connector_wizard_directory_remove' => array('handler' => 'bratonien_tools_nc_wizard_directory_remove'),
'nc_connector_wizard_save_mounts' => array('handler' => 'bratonien_tools_nc_wizard_save_sources_dispatch'), 'nc_connector_wizard_save_mounts' => array('handler' => 'bratonien_tools_nc_wizard_save_sources_dispatch'),

View File

@@ -8,22 +8,12 @@ require_once(BRATONIEN_TOOLS_PATH . 'include/watermark_base.inc.php');
function bratonien_tools_watermark_engine_enabled() function bratonien_tools_watermark_engine_enabled()
{ {
static $initialized = false;
static $enabled = false;
if ($initialized)
{
return $enabled;
}
$config = bratonien_tools_get_watermark_engine_config(); $config = bratonien_tools_get_watermark_engine_config();
$enabled = !empty($config['enabled']); $enabled = !empty($config['enabled']);
$initialized = true;
// Piwigo recalculates use_watermark for custom derivatives from the native // Piwigo recalculates use_watermark for custom derivatives from the native
// watermark file itself. Therefore an active Bratonien engine must keep the // watermark file itself. Therefore an active Bratonien engine must keep the
// native watermark file empty, not only the stored use_watermark flags false. // native watermark file empty, not only the stored use_watermark flags false.
// This initialization is required only once per PHP request.
if ($enabled) if ($enabled)
{ {
bratonien_tools_disable_piwigo_watermarks(); bratonien_tools_disable_piwigo_watermarks();

View File

@@ -27,72 +27,6 @@ function bratonien_tools_runtime_category_id()
return 0; return 0;
} }
function bratonien_tools_watermark_album_cover_category($src_image, $category_id=null)
{
static $map = null;
if ($map === null)
{
$map = new SplObjectStorage();
}
if (!is_object($src_image))
{
return 0;
}
if ($category_id !== null)
{
$category_id = (int)$category_id;
if ($category_id > 0)
{
$map[$src_image] = $category_id;
}
return $category_id;
}
return $map->contains($src_image) ? (int)$map[$src_image] : 0;
}
function bratonien_tools_watermark_prepare_album_overview($thumbnails)
{
if (!is_array($thumbnails))
{
return $thumbnails;
}
foreach ($thumbnails as &$thumbnail)
{
$category_id = (int)($thumbnail['id'] ?? $thumbnail['ID'] ?? 0);
if ($category_id < 1 || empty($thumbnail['representative']['src_image']) || !is_object($thumbnail['representative']['src_image']))
{
continue;
}
// Ein Repraesentantenbild kann fuer mehrere Alben verwendet werden. Piwigo
// reicht in der Albumuebersicht aber nur das SrcImage an get_derivative_url
// weiter. Deshalb bekommt jedes Album-Cover eine eigene SrcImage-Instanz,
// damit seine konkrete Albumregel erhalten bleibt.
$src_image = clone $thumbnail['representative']['src_image'];
$thumbnail['representative']['src_image'] = $src_image;
bratonien_tools_watermark_album_cover_category($src_image, $category_id);
}
unset($thumbnail);
return $thumbnails;
}
function bratonien_tools_runtime_derivative_category_id($src_image)
{
$category_id = bratonien_tools_runtime_category_id();
if ($category_id > 0)
{
return $category_id;
}
return bratonien_tools_watermark_album_cover_category($src_image);
}
function bratonien_tools_runtime_effective_rule($category_id) function bratonien_tools_runtime_effective_rule($category_id)
{ {
static $categories = null; static $categories = null;
@@ -296,7 +230,7 @@ function bratonien_tools_filter_derivative_url($url, $params, $src_image, $rel_u
return $url; return $url;
} }
$rule = bratonien_tools_runtime_effective_rule(bratonien_tools_runtime_derivative_category_id($src_image)); $rule = bratonien_tools_runtime_effective_rule(bratonien_tools_runtime_category_id());
if ($rule['mode'] !== 'profile' || empty($rule['profile_id'])) if ($rule['mode'] !== 'profile' || empty($rule['profile_id']))
{ {
return $url; return $url;

View File

@@ -7,9 +7,6 @@ if (!defined('PHPWG_ROOT_PATH'))
function bratonien_tools_webdav_image_source_info($image_id) function bratonien_tools_webdav_image_source_info($image_id)
{ {
static $cache = array(); static $cache = array();
static $connection_cache = array();
static $mapping_cache = array();
$image_id = (int)$image_id; $image_id = (int)$image_id;
if ($image_id < 1) return null; if ($image_id < 1) return null;
if (array_key_exists($image_id, $cache)) return $cache[$image_id]; if (array_key_exists($image_id, $cache)) return $cache[$image_id];
@@ -23,12 +20,12 @@ function bratonien_tools_webdav_image_source_info($image_id)
$absolute = $path; $absolute = $path;
if (strpos($absolute, '/') !== 0) if (strpos($absolute, '/') !== 0)
{ {
$absolute = PHPWG_ROOT_PATH.ltrim(preg_replace('#^\\./#', '', $absolute), '/'); $absolute = PHPWG_ROOT_PATH.ltrim(preg_replace('#^\./#', '', $absolute), '/');
} }
$resolved = realpath($absolute); $resolved = realpath($absolute);
if ($resolved === false) return $cache[$image_id] = null; if ($resolved === false) return $cache[$image_id] = null;
$normalized = str_replace('\\\\', '/', $resolved); $normalized = str_replace('\\', '/', $resolved);
if (!preg_match('#/nc-webdav-source/connection-([0-9]+)/root-([0-9]+)/(.*)$#', $normalized, $match)) if (!preg_match('#/nc-webdav-source/connection-([0-9]+)/root-([0-9]+)/(.*)$#', $normalized, $match))
{ {
return $cache[$image_id] = null; return $cache[$image_id] = null;
@@ -39,53 +36,35 @@ function bratonien_tools_webdav_image_source_info($image_id)
$relative_path = trim((string)$match[3], '/'); $relative_path = trim((string)$match[3], '/');
if ($relative_path === '') return $cache[$image_id] = null; if ($relative_path === '') return $cache[$image_id] = null;
if (!array_key_exists($connection_id, $connection_cache))
{
$table = defined('BRATONIEN_TOOLS_NC_CONNECTIONS_TABLE') $table = defined('BRATONIEN_TOOLS_NC_CONNECTIONS_TABLE')
? BRATONIEN_TOOLS_NC_CONNECTIONS_TABLE ? BRATONIEN_TOOLS_NC_CONNECTIONS_TABLE
: $GLOBALS['prefixeTable'].'bratonien_tools_nc_connections'; : $GLOBALS['prefixeTable'].'bratonien_tools_nc_connections';
$connection_result = pwg_query('SELECT config_json FROM `'.$table.'` WHERE id='.$connection_id.' LIMIT 1'); $connection_result = pwg_query('SELECT config_json FROM `'.$table.'` WHERE id='.$connection_id.' LIMIT 1');
if (!pwg_db_num_rows($connection_result)) if (!pwg_db_num_rows($connection_result)) return $cache[$image_id] = null;
{
$connection_cache[$connection_id] = null;
}
else
{
$connection_row = pwg_db_fetch_assoc($connection_result); $connection_row = pwg_db_fetch_assoc($connection_result);
$decoded = json_decode((string)$connection_row['config_json'], true); $config = json_decode((string)$connection_row['config_json'], true);
$connection_cache[$connection_id] = is_array($decoded) ? $decoded : null;
}
}
$config = $connection_cache[$connection_id];
if (!is_array($config) || (string)($config['source_mode'] ?? '') !== 'webdav-placeholder') if (!is_array($config) || (string)($config['source_mode'] ?? '') !== 'webdav-placeholder')
{ {
return $cache[$image_id] = null; return $cache[$image_id] = null;
} }
$root_path = ''; $root_path = '';
$root_found = false;
$roots = isset($config['roots']) && is_array($config['roots']) ? $config['roots'] : array(); $roots = isset($config['roots']) && is_array($config['roots']) ? $config['roots'] : array();
foreach ($roots as $root) foreach ($roots as $root)
{ {
if ((int)($root['fileid'] ?? 0) === $root_fileid) if ((int)($root['fileid'] ?? 0) === $root_fileid)
{ {
$root_path = trim((string)($root['webdav_path'] ?? ''), '/'); $root_path = trim((string)($root['webdav_path'] ?? ''), '/');
$root_found = true;
break; break;
} }
} }
if (!$root_found) return $cache[$image_id] = null; if ($root_path === '') return $cache[$image_id] = null;
$root_is_base = $root_path === ''; $webdav_path = $root_path.'/'.$relative_path;
$webdav_path = $root_is_base ? $relative_path : $root_path.'/'.$relative_path;
$content_type = ''; $content_type = '';
$size = 0; $size = 0;
$etag = ''; $etag = '';
if (!array_key_exists($connection_id, $mapping_cache))
{
$mapping_cache[$connection_id] = array();
$state_dir = rtrim((string)($config['state_dir'] ?? ''), '/'); $state_dir = rtrim((string)($config['state_dir'] ?? ''), '/');
if ($state_dir !== '') if ($state_dir !== '')
{ {
@@ -95,13 +74,7 @@ function bratonien_tools_webdav_image_source_info($image_id)
$mapping = json_decode((string)file_get_contents($mapping_file), true); $mapping = json_decode((string)file_get_contents($mapping_file), true);
if (is_array($mapping) && isset($mapping['files']) && is_array($mapping['files'])) if (is_array($mapping) && isset($mapping['files']) && is_array($mapping['files']))
{ {
$mapping_cache[$connection_id] = $mapping['files']; $entry = $mapping['files'][$resolved] ?? $mapping['files'][$normalized] ?? null;
}
}
}
}
$entry = $mapping_cache[$connection_id][$resolved] ?? $mapping_cache[$connection_id][$normalized] ?? null;
if (is_array($entry) && (string)($entry['kind'] ?? '') === 'file') if (is_array($entry) && (string)($entry['kind'] ?? '') === 'file')
{ {
$webdav_path = trim((string)($entry['webdav_path'] ?? $webdav_path), '/'); $webdav_path = trim((string)($entry['webdav_path'] ?? $webdav_path), '/');
@@ -109,12 +82,14 @@ function bratonien_tools_webdav_image_source_info($image_id)
$size = (int)($entry['size'] ?? 0); $size = (int)($entry['size'] ?? 0);
$etag = (string)($entry['etag'] ?? ''); $etag = (string)($entry['etag'] ?? '');
} }
}
}
}
return $cache[$image_id] = array( return $cache[$image_id] = array(
'image_id'=>$image_id, 'image_id'=>$image_id,
'connection_id'=>$connection_id, 'connection_id'=>$connection_id,
'webdav_path'=>$webdav_path, 'webdav_path'=>$webdav_path,
'root_is_base'=>$root_is_base,
'content_type'=>$content_type, 'content_type'=>$content_type,
'size'=>$size, 'size'=>$size,
'etag'=>$etag, 'etag'=>$etag,
@@ -437,16 +412,8 @@ function bratonien_tools_filter_webdav_src_url($url, $src_image)
function bratonien_tools_filter_webdav_derivative_url($url, $params, $src_image, $rel_url) function bratonien_tools_filter_webdav_derivative_url($url, $params, $src_image, $rel_url)
{ {
if (!is_object($src_image) || empty($src_image->id)) return $url; if (!is_object($src_image) || empty($src_image->id)) return $url;
$info = bratonien_tools_webdav_image_source_info((int)$src_image->id);
// Hotpath: Bei einem bereits vorbereiteten WebDAV-Derivat keinerlei if (!$info) return $url;
// Connection-DB oder Mapping-Datei mehr anfassen. Der reale Quellpfad
// reicht aus, um Connector-Bilder sicher zu erkennen.
$source_path = $src_image->get_path();
$resolved_source = $source_path !== '' ? realpath($source_path) : false;
if ($resolved_source === false || !preg_match('#/nc-webdav-source/connection-[0-9]+/root-[0-9]+/#', str_replace('\\\\', '/', $resolved_source)))
{
return $url;
}
try try
{ {

View File

@@ -1,7 +1,7 @@
<?php <?php
/* /*
Plugin Name: Bratonien Tools Plugin Name: Bratonien Tools
Version: 0.9.7.17 Version: 0.9.6.29
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
@@ -24,15 +24,13 @@ require_once(BRATONIEN_TOOLS_PATH . 'include/album_shares.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_ws.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_ws.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_orphan_ws.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/nc_orphan_ws.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_productive_ws.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/nc_productive_ws.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_scheduler.inc.php');
add_event_handler('get_admin_plugin_menu_links', 'bratonien_tools_admin_menu'); add_event_handler('get_admin_plugin_menu_links', 'bratonien_tools_admin_menu');
add_event_handler('loc_end_index_category_thumbnails', 'bratonien_tools_watermark_prepare_album_overview');
add_event_handler('get_derivative_url', 'bratonien_tools_filter_derivative_url', EVENT_HANDLER_PRIORITY_NEUTRAL, 4); add_event_handler('get_derivative_url', 'bratonien_tools_filter_derivative_url', EVENT_HANDLER_PRIORITY_NEUTRAL, 4);
add_event_handler('get_src_image_url', 'bratonien_tools_filter_webdav_src_url', EVENT_HANDLER_PRIORITY_NEUTRAL + 50, 2); add_event_handler('get_src_image_url', 'bratonien_tools_filter_webdav_src_url', EVENT_HANDLER_PRIORITY_NEUTRAL + 50, 2);
add_event_handler('get_derivative_url', 'bratonien_tools_filter_webdav_derivative_url', EVENT_HANDLER_PRIORITY_NEUTRAL + 50, 4);
add_event_handler('loc_end_element_set_global', 'bratonien_tools_batch_titles_register_action'); add_event_handler('loc_end_element_set_global', 'bratonien_tools_batch_titles_register_action');
add_event_handler('element_set_global_action', 'bratonien_tools_batch_titles_apply', EVENT_HANDLER_PRIORITY_NEUTRAL, 2); add_event_handler('element_set_global_action', 'bratonien_tools_batch_titles_apply', EVENT_HANDLER_PRIORITY_NEUTRAL, 2);
add_event_handler('init', 'bratonien_tools_watermark_cache_upgrade', EVENT_HANDLER_PRIORITY_NEUTRAL - 40);
add_event_handler('init', 'bratonien_tools_prepare_connector_private_import', EVENT_HANDLER_PRIORITY_NEUTRAL - 30); add_event_handler('init', 'bratonien_tools_prepare_connector_private_import', EVENT_HANDLER_PRIORITY_NEUTRAL - 30);
add_event_handler('init', 'bratonien_tools_prepare_private_album_permissions', EVENT_HANDLER_PRIORITY_NEUTRAL - 20); add_event_handler('init', 'bratonien_tools_prepare_private_album_permissions', EVENT_HANDLER_PRIORITY_NEUTRAL - 20);
add_event_handler('init', 'bratonien_tools_preserve_private_album_access', EVENT_HANDLER_PRIORITY_NEUTRAL - 10); add_event_handler('init', 'bratonien_tools_preserve_private_album_access', EVENT_HANDLER_PRIORITY_NEUTRAL - 10);

View File

@@ -8,8 +8,6 @@ require_once(dirname(__FILE__) . '/include/album_shares.inc.php');
require_once(dirname(__FILE__) . '/include/database.class.php'); require_once(dirname(__FILE__) . '/include/database.class.php');
require_once(dirname(__FILE__) . '/tools/watermark_profiles.inc.php'); require_once(dirname(__FILE__) . '/tools/watermark_profiles.inc.php');
require_once(dirname(__FILE__) . '/include/dependencies.inc.php'); require_once(dirname(__FILE__) . '/include/dependencies.inc.php');
require_once(dirname(__FILE__) . '/include/nc_connector_scheduler.inc.php');
require_once(dirname(__FILE__) . '/include/nc_legacy_reset.inc.php');
class bratonien_tools_maintain extends PluginMaintain class bratonien_tools_maintain extends PluginMaintain
{ {
@@ -20,14 +18,9 @@ class bratonien_tools_maintain extends PluginMaintain
$dependency_messages = array(); $dependency_messages = array();
bratonien_tools_ensure_dependencies($dependency_messages); bratonien_tools_ensure_dependencies($dependency_messages);
if (!bratonien_tools_nc_scheduler_install())
{
$dependency_messages[] = 'Der native NC-Scheduler konnte sein Piwigo-Laufzeitverzeichnis nicht anlegen.';
}
if (function_exists('conf_update_param')) if (function_exists('conf_update_param'))
{ {
conf_update_param('bratonien_nc_scheduler_interval', 60);
conf_update_param('bratonien_dependency_status', json_encode(array( conf_update_param('bratonien_dependency_status', json_encode(array(
'checked_at' => time(), 'checked_at' => time(),
'messages' => $dependency_messages, 'messages' => $dependency_messages,
@@ -52,18 +45,6 @@ class bratonien_tools_maintain extends PluginMaintain
public function update($old_version, $new_version, &$errors = array()) public function update($old_version, $new_version, &$errors = array())
{ {
$this->prepare($errors); $this->prepare($errors);
if (version_compare((string)$old_version, '0.9.7.7', '<') && version_compare((string)$new_version, '0.9.7.7', '>='))
{
try
{
bratonien_tools_nc_reset_legacy_imports();
}
catch (Throwable $e)
{
$errors[] = 'Connector-Altbestand konnte nicht vollständig bereinigt werden: '.$e->getMessage();
}
}
} }
public function uninstall() public function uninstall()

View File

@@ -1,6 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
umask 0002
CONFIG_FILE="${PIWIGO_CONFIG:-}" CONFIG_FILE="${PIWIGO_CONFIG:-}"
[[ -n "$CONFIG_FILE" && -r "$CONFIG_FILE" ]] || { echo "WebDAV-Konfiguration fehlt: ${CONFIG_FILE:-<leer>}" >&2; exit 1; } [[ -n "$CONFIG_FILE" && -r "$CONFIG_FILE" ]] || { echo "WebDAV-Konfiguration fehlt: ${CONFIG_FILE:-<leer>}" >&2; exit 1; }
@@ -23,47 +22,6 @@ exec 9>"$LOCK_FILE"
flock -n 9 || exit 0 flock -n 9 || exit 0
PREVIEW_CACHE="$PIWIGO_ROOT/_data/bratonien-tools/nc-webdav-preview/connection-$CONNECTION_ID" PREVIEW_CACHE="$PIWIGO_ROOT/_data/bratonien-tools/nc-webdav-preview/connection-$CONNECTION_ID"
SOURCE_CACHE="$PIWIGO_ROOT/_data/bratonien-tools/nc-webdav-gallery/connection-$CONNECTION_ID"
DERIVATIVE_CACHE="$PIWIGO_ROOT/_data/i/_data/bratonien-tools/nc-webdav-gallery/connection-$CONNECTION_ID"
PIWIGO_DATA="$PIWIGO_ROOT/_data"
normalize_connector_cache_permissions() {
[[ -d "$PIWIGO_DATA" ]] || return 0
local data_uid data_gid current_uid path
data_uid="$(stat -c '%u' "$PIWIGO_DATA")"
data_gid="$(stat -c '%g' "$PIWIGO_DATA")"
current_uid="$(id -u)"
for path in "$SOURCE_CACHE" "$PREVIEW_CACHE" "$DERIVATIVE_CACHE"; do
[[ -e "$path" ]] || continue
if [[ "$current_uid" -eq 0 ]]; then
chown -R "$data_uid:$data_gid" -- "$path"
fi
if ! find "$path" -type d -exec chmod 2775 {} + 2>/dev/null; then
echo "Hinweis: Verzeichnisrechte konnten ohne Root-Rechte nicht vollständig repariert werden: $path" >&2
fi
if ! find "$path" -type f -exec chmod 0664 {} + 2>/dev/null; then
echo "Hinweis: Dateirechte konnten ohne Root-Rechte nicht vollständig repariert werden: $path" >&2
fi
done
}
# Altbestände aus früheren root/systemd-Läufen werden repariert, sobald dieser
# Lauf mit ausreichenden Rechten ausgeführt wird. Bei normalen Webserver-Läufen
# werden zumindest alle eigenen Dateien gruppenschreibbar gehalten.
normalize_connector_cache_permissions
# Dieser Medienlauf wird erst nach einem erfolgreichen WebDAV-Aufbau und einer
# erfolgreichen Piwigo-Synchronisierung gestartet. Deshalb darf erst hier aus
# "lokale Connector-Datei fehlt" auf eine in Nextcloud entfernte Freigabe/Datei
# geschlossen werden. Bei einer nicht erreichbaren Verbindung wird dieser Block
# niemals ausgeführt und der vorhandene Piwigo-Bestand bleibt unverändert.
php "$SCRIPT_DIR/cleanup-missing-webdav-images.php" \
--piwigo-root="$PIWIGO_ROOT" \
--connection-id="$CONNECTION_ID"
php "$SCRIPT_DIR/lib/precache-webdav-previews.php" \ php "$SCRIPT_DIR/lib/precache-webdav-previews.php" \
--mapping="$WEBDAV_MAPPING_FILE" \ --mapping="$WEBDAV_MAPPING_FILE" \
@@ -75,5 +33,3 @@ php "$SCRIPT_DIR/lib/precache-webdav-previews.php" \
php "$SCRIPT_DIR/lib/build-webdav-derivatives.php" \ php "$SCRIPT_DIR/lib/build-webdav-derivatives.php" \
--piwigo-root="$PIWIGO_ROOT" \ --piwigo-root="$PIWIGO_ROOT" \
--connection-id="$CONNECTION_ID" --connection-id="$CONNECTION_ID"
normalize_connector_cache_permissions

View File

@@ -1,124 +0,0 @@
#!/usr/bin/env php
<?php
if (PHP_SAPI !== 'cli')
{
fwrite(STDERR, "CLI only\n");
exit(1);
}
$options = getopt('', array('piwigo-root:', 'connection-id:'));
$piwigo_root = rtrim((string)($options['piwigo-root'] ?? ''), '/');
$connection_id = (int)($options['connection-id'] ?? 0);
if ($piwigo_root === '' || $connection_id < 1)
{
fwrite(STDERR, "Parameter --piwigo-root und --connection-id werden benoetigt.\n");
exit(1);
}
$db_config = $piwigo_root.'/local/config/database.inc.php';
if (!is_readable($db_config))
{
fwrite(STDERR, "Piwigo-Datenbankkonfiguration ist nicht lesbar.\n");
exit(1);
}
$conf = array();
$prefixeTable = 'piwigo_';
require $db_config;
foreach (array('db_host','db_user','db_password','db_base') as $key)
{
if (!isset($conf[$key]))
{
fwrite(STDERR, "Piwigo-Datenbankkonfiguration ist unvollstaendig: $key\n");
exit(1);
}
}
$db = new mysqli($conf['db_host'], $conf['db_user'], $conf['db_password'], $conf['db_base']);
if ($db->connect_errno)
{
fwrite(STDERR, "Piwigo-Datenbank ist nicht erreichbar: ".$db->connect_error."\n");
exit(1);
}
$db->set_charset('utf8mb4');
$table = $prefixeTable.'bratonien_tools_nc_connections';
$result = $db->query('SELECT config_json FROM `'.$table.'` WHERE id='.$connection_id.' LIMIT 1');
if (!$result || !$result->num_rows)
{
fwrite(STDERR, "Connector-Verbindung #$connection_id wurde nicht gefunden.\n");
exit(1);
}
$config = json_decode((string)$result->fetch_assoc()['config_json'], true);
if (!is_array($config) || (string)($config['source_mode'] ?? '') !== 'webdav-placeholder')
{
echo "Keine WebDAV-Bereinigung erforderlich.\n";
exit(0);
}
$gallery_root = rtrim((string)($config['parallel_gallery_root'] ?? ''), '/');
if ($gallery_root === '' || strpos($gallery_root, $piwigo_root.'/') !== 0)
{
fwrite(STDERR, "Verbindungseigene Gallery-Root ist ungueltig.\n");
exit(1);
}
$relative = ltrim(substr($gallery_root, strlen($piwigo_root)), '/');
$db_prefix = './'.rtrim($relative, '/').'/';
$escaped_prefix = $db->real_escape_string(addcslashes($db_prefix, "_%\\"));
$images_table = $prefixeTable.'images';
$query = "SELECT id, path FROM `{$images_table}` WHERE path LIKE '{$escaped_prefix}%' ESCAPE '\\\\'";
$rows = $db->query($query);
if (!$rows)
{
fwrite(STDERR, "Connector-Bilder konnten nicht gelesen werden: ".$db->error."\n");
exit(1);
}
$missing_ids = array();
while ($row = $rows->fetch_assoc())
{
$path = (string)$row['path'];
if (strpos($path, $db_prefix) !== 0)
{
continue;
}
$absolute = $piwigo_root.'/'.ltrim(preg_replace('#^\\./#', '', $path), '/');
if (!is_file($absolute))
{
$missing_ids[] = (int)$row['id'];
}
}
$db->close();
if (!$missing_ids)
{
echo "WebDAV-Bereinigung: keine fehlenden Piwigo-Bilder.\n";
exit(0);
}
// Erst jetzt Piwigo bootstrappen. Dieser Prozess wird nur nach einem erfolgreich
// abgeschlossenen WebDAV-Abruf gestartet. Eine nicht erreichbare Verbindung kann
// daher niemals ueber diesen Pfad Bilder loeschen.
define('PHPWG_ROOT_PATH', $piwigo_root.'/');
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
$_SERVER['SERVER_ADDR'] = '127.0.0.1';
$_SERVER['SERVER_NAME'] = 'localhost';
$_SERVER['HTTP_HOST'] = 'localhost';
$_SERVER['SERVER_PORT'] = '80';
$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['REQUEST_URI'] = '/';
$_SERVER['SCRIPT_NAME'] = '/plugins/bratonien_tools/runtime/cleanup-missing-webdav-images.php';
$_SERVER['PHP_SELF'] = $_SERVER['SCRIPT_NAME'];
$_SERVER['QUERY_STRING'] = '';
$_SERVER['HTTPS'] = 'off';
require_once(PHPWG_ROOT_PATH.'include/common.inc.php');
include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
delete_elements($missing_ids, false);
update_category('all');
invalidate_user_cache(true);
echo 'WebDAV-Bereinigung: '.count($missing_ids)." nicht mehr freigegebene/ vorhandene Bilder aus Piwigo entfernt.\n";

View File

@@ -6,7 +6,7 @@ if (PHP_SAPI !== 'cli')
exit(1); exit(1);
} }
const BRATONIEN_WEBDAV_DERIVATIVE_BUILDER_VERSION = '0.9.7.8'; const BRATONIEN_WEBDAV_DERIVATIVE_BUILDER_VERSION = '0.9.6.1';
$options = getopt('', array('piwigo-root:', 'connection-id:')); $options = getopt('', array('piwigo-root:', 'connection-id:'));
$piwigo_root = rtrim((string)($options['piwigo-root'] ?? ''), '/'); $piwigo_root = rtrim((string)($options['piwigo-root'] ?? ''), '/');
@@ -46,84 +46,20 @@ if (!function_exists('bratonien_tools_webdav_image_source_info') || !function_ex
exit(1); exit(1);
} }
function bratonien_tools_webdav_modus_config()
{
global $conf;
$modus = $conf['modus_theme'] ?? array();
if (is_string($modus) && $modus !== '')
{
$decoded = @unserialize($modus);
if (is_array($decoded)) $modus = $decoded;
}
if (!is_array($modus)) $modus = array();
return array_merge(
array(
'index_photo_deriv'=>'2small',
'index_photo_deriv_hdpi'=>'xsmall',
),
$modus
);
}
function bratonien_tools_webdav_gallery_base_params()
{
$modus = bratonien_tools_webdav_modus_config();
$type = trim((string)($modus['index_photo_deriv'] ?? '2small'));
$params = $type !== '' ? @ImageStdParams::get_by_type($type) : null;
if ($params) return $params;
$params = ImageStdParams::get_by_type(IMG_THUMB);
if (!$params)
{
throw new RuntimeException('Piwigo-Galeriederivat ist nicht konfiguriert.');
}
return $params;
}
function bratonien_tools_webdav_modus_gallery_params(SrcImage $src, $base_params)
{
$row_height = (int)$base_params->max_height();
$candidates = array($base_params);
foreach (ImageStdParams::get_defined_type_map() as $params)
{
if (
$params->max_height() > $row_height
&& $params->sizing->max_crop == $base_params->sizing->max_crop
)
{
$candidates[] = $params;
if (count($candidates) === 3) break;
}
}
$selected = $base_params;
foreach ($candidates as $params)
{
$selected = $params;
$probe = new DerivativeImage($params, $src);
$size = $probe->get_size();
if ((int)$size[1] >= $row_height - 2) break;
}
return $selected;
}
try try
{ {
$gallery_base_params = bratonien_tools_webdav_gallery_base_params(); $variants = bratonien_tools_webdav_derivative_variants();
$gallery_base_type = (string)$gallery_base_params->type; if (!$variants)
{
throw new RuntimeException('Keine Piwigo-Derivate konfiguriert.');
}
$images = 0; $images = 0;
$generated_or_ready = 0; $generated_or_ready = 0;
$identity = 0; $identity = 0;
$metadata_repaired = 0; $metadata_repaired = 0;
$legacy_derivatives_rebuilt = 0;
$errors = 0; $errors = 0;
$error_lines = array(); $error_lines = array();
$variant_counts = array();
$result = pwg_query('SELECT * FROM '.IMAGES_TABLE.' ORDER BY id'); $result = pwg_query('SELECT * FROM '.IMAGES_TABLE.' ORDER BY id');
while ($row = pwg_db_fetch_assoc($result)) while ($row = pwg_db_fetch_assoc($result))
@@ -173,58 +109,33 @@ try
} }
$src = new SrcImage($row); $src = new SrcImage($row);
foreach ($variants as $variant_name => $params)
{
try try
{ {
$gallery_params = bratonien_tools_webdav_modus_gallery_params($src, $gallery_base_params); $probe = new DerivativeImage($params, $src);
$variant = (string)$gallery_params->type;
if ($variant === '') $variant = 'custom';
$variant_counts[$variant] = ($variant_counts[$variant] ?? 0) + 1;
$probe = new DerivativeImage($gallery_params, $src);
if ($probe->same_as_source()) if ($probe->same_as_source())
{ {
$identity++; $identity++;
continue; continue;
} }
$target = $probe->get_path();
if ($target !== '' && is_file($target) && is_readable($target))
{
$expected_size = $gallery_params->compute_final_size(array($preview_width, $preview_height));
$existing_size = @getimagesize($target);
if (
is_array($expected_size)
&& isset($expected_size[0], $expected_size[1])
&& is_array($existing_size)
&& isset($existing_size[0], $existing_size[1])
&& ((int)$existing_size[0] !== (int)$expected_size[0] || (int)$existing_size[1] !== (int)$expected_size[1])
)
{
if (!@unlink($target))
{
$errors++;
$error_lines[] = 'Bild #'.$image_id.' Galerie: falsches Alt-Derivat konnte nicht entfernt werden: '.$target;
continue;
}
$legacy_derivatives_rebuilt++;
}
}
$detail = ''; $detail = '';
if (bratonien_tools_webdav_generate_derivative($gallery_params, $src, $detail)) if (bratonien_tools_webdav_generate_derivative($params, $src, $detail))
{ {
$generated_or_ready++; $generated_or_ready++;
} }
else else
{ {
$errors++; $errors++;
$error_lines[] = 'Bild #'.$image_id.' Galerie: '.($detail !== '' ? $detail : 'Galeriederivat konnte nicht erzeugt werden.'); $error_lines[] = 'Bild #'.$image_id.' '.$variant_name.': '.($detail !== '' ? $detail : 'Derivat konnte nicht erzeugt werden.');
} }
} }
catch (Throwable $e) catch (Throwable $e)
{ {
$errors++; $errors++;
$error_lines[] = 'Bild #'.$image_id.' Galerie: '.get_class($e).': '.$e->getMessage(); $error_lines[] = 'Bild #'.$image_id.' '.$variant_name.': '.get_class($e).': '.$e->getMessage();
}
} }
} }
@@ -246,22 +157,12 @@ try
} }
} }
ksort($variant_counts);
$variant_parts = array();
foreach ($variant_counts as $type=>$count)
{
$variant_parts[] = $type.':'.$count;
}
echo 'WebDAV-Derivative-Builder: version='.BRATONIEN_WEBDAV_DERIVATIVE_BUILDER_VERSION. echo 'WebDAV-Derivative-Builder: version='.BRATONIEN_WEBDAV_DERIVATIVE_BUILDER_VERSION.
' bilder='.$images. ' bilder='.$images.
' varianten_pro_bild=1'. ' varianten='.count($variants).
' modus_basis='.$gallery_base_type.
' modus_varianten='.implode(',', $variant_parts).
' bereit='.$generated_or_ready. ' bereit='.$generated_or_ready.
' identisch='.$identity. ' identisch='.$identity.
' metadaten_repariert='.$metadata_repaired. ' metadaten_repariert='.$metadata_repaired.
' alt_derivate_neu='.$legacy_derivatives_rebuilt.
' fehler='.$errors."\n"; ' fehler='.$errors."\n";
exit($errors > 0 ? 1 : 0); exit($errors > 0 ? 1 : 0);

66
runtime/lib/build_webdav_placeholder_source.py Executable file → Normal file
View File

@@ -3,7 +3,7 @@
This creates only tiny placeholder files plus a metadata mapping; no Nextcloud This creates only tiny placeholder files plus a metadata mapping; no Nextcloud
original media is downloaded. The authenticated Nextcloud user is never used as original media is downloaded. The authenticated Nextcloud user is never used as
an album name. an album name. Selecting the user's WebDAV root mirrors its children directly.
""" """
from __future__ import annotations from __future__ import annotations
@@ -14,7 +14,6 @@ import getpass
import json import json
import os import os
import shutil import shutil
import socket
import ssl import ssl
import sys import sys
import tempfile import tempfile
@@ -22,7 +21,6 @@ import urllib.error
import urllib.parse import urllib.parse
import urllib.request import urllib.request
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from contextlib import contextmanager
from pathlib import Path, PurePosixPath from pathlib import Path, PurePosixPath
DAV = "DAV:" DAV = "DAV:"
@@ -54,37 +52,9 @@ def safe_local_name(name: str) -> str:
return name return name
@contextmanager
def pinned_resolution(host: str, ip: str):
host = host.strip("[]").lower()
ip = ip.strip("[]")
if not host or not ip or host == ip:
yield
return
original = socket.getaddrinfo
def resolve(name, port, family=0, type=0, proto=0, flags=0):
normalized = str(name).strip("[]").lower()
if normalized == host:
return original(ip, port, family, type, proto, flags)
return original(name, port, family, type, proto, flags)
socket.getaddrinfo = resolve
try:
yield
finally:
socket.getaddrinfo = original
class WebDavClient: class WebDavClient:
def __init__(self, base_url: str, user: str, password: str, timeout: int = 30, connect_ip: str = "") -> None: def __init__(self, base_url: str, user: str, password: str, timeout: int = 30) -> None:
self.base_url = base_url.rstrip("/") self.base_url = base_url.rstrip("/")
parsed = urllib.parse.urlparse(self.base_url)
if parsed.scheme not in {"http", "https"} or not parsed.hostname:
fail("Nextcloud base URL must use HTTP or HTTPS and contain a host")
self.host = parsed.hostname.strip("[]")
self.connect_ip = connect_ip.strip("[]") or self.host
self.user = user self.user = user
self.password = password self.password = password
self.timeout = timeout self.timeout = timeout
@@ -114,7 +84,6 @@ class WebDavClient:
request.add_header("Depth", "1") request.add_header("Depth", "1")
request.add_header("Content-Type", "application/xml; charset=utf-8") request.add_header("Content-Type", "application/xml; charset=utf-8")
try: try:
with pinned_resolution(self.host, self.connect_ip):
with urllib.request.urlopen(request, timeout=self.timeout, context=self.context) as response: with urllib.request.urlopen(request, timeout=self.timeout, context=self.context) as response:
status = response.status status = response.status
payload = response.read() payload = response.read()
@@ -127,14 +96,14 @@ class WebDavClient:
if status != 207: if status != 207:
fail(f"Nextcloud PROPFIND returned HTTP {status}") fail(f"Nextcloud PROPFIND returned HTTP {status}")
base_path = urllib.parse.unquote(urllib.parse.urlparse(url).path).rstrip("/")
current: dict[str, object] | None = None
children: list[dict[str, object]] = []
try: try:
root = ET.fromstring(payload) root = ET.fromstring(payload)
except ET.ParseError as error: except ET.ParseError as error:
raise RuntimeError("Nextcloud returned invalid WebDAV XML") from error raise RuntimeError("Nextcloud returned invalid WebDAV XML") from error
base_path = urllib.parse.unquote(urllib.parse.urlparse(url).path).rstrip("/")
current: dict[str, object] | None = None
children: list[dict[str, object]] = []
for response in root.findall(f"{{{DAV}}}response"): for response in root.findall(f"{{{DAV}}}response"):
href = response.findtext(f"{{{DAV}}}href", default="") href = response.findtext(f"{{{DAV}}}href", default="")
href_path = urllib.parse.unquote(urllib.parse.urlparse(href).path).rstrip("/") href_path = urllib.parse.unquote(urllib.parse.urlparse(href).path).rstrip("/")
@@ -244,7 +213,6 @@ def atomic_text(path: Path, text: str) -> None:
def main() -> int: def main() -> int:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("--base-url", required=True) parser.add_argument("--base-url", required=True)
parser.add_argument("--connect-ip", default="", help="IP address used for the TCP connection while preserving the URL host for HTTP Host and TLS SNI")
parser.add_argument("--user", required=True) parser.add_argument("--user", required=True)
parser.add_argument("--password-file", type=Path) parser.add_argument("--password-file", type=Path)
parser.add_argument("--root", action="append", required=True, help="WebDAV path relative to the authenticated user's files root") parser.add_argument("--root", action="append", required=True, help="WebDAV path relative to the authenticated user's files root")
@@ -273,7 +241,7 @@ def main() -> int:
shutil.rmtree(staging) shutil.rmtree(staging)
staging.mkdir(parents=True) staging.mkdir(parents=True)
client = WebDavClient(args.base_url, args.user, password, max(1, args.timeout), args.connect_ip) client = WebDavClient(args.base_url, args.user, password, max(1, args.timeout))
mapping: dict[str, dict[str, object]] = {} mapping: dict[str, dict[str, object]] = {}
manifest: list[str] = [] manifest: list[str] = []
total_files = total_folders = total_skipped = 0 total_files = total_folders = total_skipped = 0
@@ -281,32 +249,20 @@ def main() -> int:
for remote_root_raw in args.root: for remote_root_raw in args.root:
remote_root = validate_relative(remote_root_raw) remote_root = validate_relative(remote_root_raw)
current, root_children = client.list_collection(remote_root) current, _ = client.list_collection(remote_root)
fileid = int(current["fileid"]) fileid = int(current["fileid"])
if fileid in used_fileids: if fileid in used_fileids:
fail(f"duplicate selected Nextcloud root fileid: {fileid}") fail(f"duplicate selected Nextcloud root fileid: {fileid}")
used_fileids.add(fileid) used_fileids.add(fileid)
# An explicitly selected folder keeps its own name. The authenticated
# user's WebDAV root is transparent and must never become an album.
display = "" if remote_root == "" else (str(current.get("display_name", "")).strip() or PurePosixPath(remote_root).name)
local_name = f"root-{fileid}" local_name = f"root-{fileid}"
local_root = staging / local_name local_root = staging / local_name
files, folders, skipped = build_root(client, remote_root, local_root, seed, mapping) files, folders, skipped = build_root(client, remote_root, local_root, seed, mapping)
total_files += files total_files += files
total_folders += folders total_folders += folders
total_skipped += skipped total_skipped += skipped
if remote_root == "":
for child in sorted(root_children, key=lambda item: str(item.get("display_name", "")).casefold()):
name = safe_local_name(str(child.get("display_name", "")))
child_fileid = int(child.get("fileid", 0))
if child_fileid < 1:
fail(f"Nextcloud returned no stable fileid for root child {name!r}")
child_path = source_dir / local_name / name
if bool(child.get("is_dir")):
manifest.append(f"webdav:{child_fileid}\tfolder\t{name}\t{child_path}")
elif Path(name).suffix.lower() in SUPPORTED_IMAGE_EXTENSIONS:
manifest.append(f"webdav:{child_fileid}\tfile\t{name}\t{child_path}")
continue
display = str(current.get("display_name", "")).strip() or PurePosixPath(remote_root).name
manifest.append(f"webdav:{fileid}\tfolder\t{display}\t{source_dir / local_name}") manifest.append(f"webdav:{fileid}\tfolder\t{display}\t{source_dir / local_name}")
if previous.exists(): if previous.exists():
@@ -335,7 +291,6 @@ def main() -> int:
atomic_json(args.mapping, { atomic_json(args.mapping, {
"version": 1, "version": 1,
"base_url": args.base_url.rstrip("/"), "base_url": args.base_url.rstrip("/"),
"connect_ip": client.connect_ip,
"user": args.user, "user": args.user,
"files": final_mapping, "files": final_mapping,
}) })
@@ -344,7 +299,6 @@ def main() -> int:
"files": total_files, "files": total_files,
"folders": total_folders, "folders": total_folders,
"skipped": total_skipped, "skipped": total_skipped,
"connect_ip": client.connect_ip,
"source_dir": str(source_dir), "source_dir": str(source_dir),
"manifest": str(args.manifest), "manifest": str(args.manifest),
"mapping": str(args.mapping), "mapping": str(args.mapping),

0
runtime/lib/piwigo-sync.php Executable file → Normal file
View File

View File

@@ -6,8 +6,6 @@ if (PHP_SAPI !== 'cli')
exit(1); exit(1);
} }
require_once(dirname(__DIR__, 2).'/include/nc_transport.inc.php');
const BRATONIEN_WEBDAV_PREVIEW_VERSION = 2; const BRATONIEN_WEBDAV_PREVIEW_VERSION = 2;
const BRATONIEN_WEBDAV_PREVIEW_MAX_EDGE = 4096; const BRATONIEN_WEBDAV_PREVIEW_MAX_EDGE = 4096;
const BRATONIEN_WEBDAV_PREVIEW_JPEG_QUALITY = 88; const BRATONIEN_WEBDAV_PREVIEW_JPEG_QUALITY = 88;
@@ -26,7 +24,7 @@ function quote_webdav_path($path)
function fetch_remote_blob($url, $user, $password) function fetch_remote_blob($url, $user, $password)
{ {
$ch = curl_init($url); $ch = curl_init($url);
$options = array( curl_setopt_array($ch, array(
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false, CURLOPT_FOLLOWLOCATION => false,
CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_CONNECTTIMEOUT => 10,
@@ -34,10 +32,8 @@ function fetch_remote_blob($url, $user, $password)
CURLOPT_HTTPAUTH => CURLAUTH_BASIC, CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
CURLOPT_USERPWD => $user.':'.$password, CURLOPT_USERPWD => $user.':'.$password,
CURLOPT_FAILONERROR => false, CURLOPT_FAILONERROR => false,
CURLOPT_USERAGENT => 'Bratonien-Tools-WebDAV-Precache/0.9.7.16', CURLOPT_USERAGENT => 'Bratonien-Tools-WebDAV-Precache/0.9.6.1',
); ));
bratonien_tools_nc_transport_apply_curl($options, $url);
curl_setopt_array($ch, $options);
$body = curl_exec($ch); $body = curl_exec($ch);
$errno = curl_errno($ch); $errno = curl_errno($ch);
$error = curl_error($ch); $error = curl_error($ch);
@@ -52,11 +48,12 @@ function fetch_remote_blob($url, $user, $password)
function preview_extension_for_entry(array $entry) function preview_extension_for_entry(array $entry)
{ {
$content_type = strtolower(trim((string)($entry['content_type'] ?? '')));
$path = strtolower((string)($entry['webdav_path'] ?? '')); $path = strtolower((string)($entry['webdav_path'] ?? ''));
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); if ($content_type === 'image/png' || preg_match('/\.png$/', $path))
if ($extension === 'png') return 'png'; {
if ($extension === 'gif') return 'gif'; return 'png';
if ($extension === 'webp') return 'webp'; }
return 'jpg'; return 'jpg';
} }
@@ -67,7 +64,7 @@ function preview_target_for_entry($cache_dir, $key, array $entry)
function remove_other_preview_format($cache_dir, $key, $keep_target) function remove_other_preview_format($cache_dir, $key, $keep_target)
{ {
foreach (array('jpg', 'png', 'gif', 'webp') as $ext) foreach (array('jpg', 'png', 'webp') as $ext)
{ {
$candidate = $cache_dir.'/'.$key.'.'.$ext; $candidate = $cache_dir.'/'.$key.'.'.$ext;
if ($candidate !== $keep_target && is_file($candidate)) @unlink($candidate); if ($candidate !== $keep_target && is_file($candidate)) @unlink($candidate);
@@ -89,19 +86,13 @@ function write_preview_imagick($blob, $target, $extension)
{ {
$image->setImageFormat('png'); $image->setImageFormat('png');
} }
elseif ($extension === 'gif')
{
$image->setImageFormat('gif');
}
elseif ($extension === 'webp')
{
$image->setImageFormat('webp');
$image->setImageCompressionQuality(BRATONIEN_WEBDAV_PREVIEW_JPEG_QUALITY);
}
else else
{ {
$image->setImageBackgroundColor('white'); $image->setImageBackgroundColor('white');
if (method_exists($image, 'mergeImageLayers')) $image = $image->mergeImageLayers(Imagick::LAYERMETHOD_FLATTEN); if (method_exists($image, 'mergeImageLayers'))
{
$image = $image->mergeImageLayers(Imagick::LAYERMETHOD_FLATTEN);
}
$image->setImageFormat('jpeg'); $image->setImageFormat('jpeg');
$image->setImageCompression(Imagick::COMPRESSION_JPEG); $image->setImageCompression(Imagick::COMPRESSION_JPEG);
$image->setImageCompressionQuality(BRATONIEN_WEBDAV_PREVIEW_JPEG_QUALITY); $image->setImageCompressionQuality(BRATONIEN_WEBDAV_PREVIEW_JPEG_QUALITY);
@@ -136,7 +127,7 @@ function write_preview_gd($blob, $target, $extension)
try try
{ {
if (in_array($extension, array('png', 'gif', 'webp'), true)) if ($extension === 'png')
{ {
imagealphablending($preview, false); imagealphablending($preview, false);
imagesavealpha($preview, true); imagesavealpha($preview, true);
@@ -154,12 +145,10 @@ function write_preview_gd($blob, $target, $extension)
fail_preview('GD konnte das Preview nicht skalieren.'); fail_preview('GD konnte das Preview nicht skalieren.');
} }
if ($extension === 'png') $written = imagepng($preview, $target, 6); $written = $extension === 'png'
elseif ($extension === 'gif') $written = function_exists('imagegif') ? imagegif($preview, $target) : false; ? imagepng($preview, $target, 6)
elseif ($extension === 'webp') $written = function_exists('imagewebp') ? imagewebp($preview, $target, BRATONIEN_WEBDAV_PREVIEW_JPEG_QUALITY) : false; : imagejpeg($preview, $target, BRATONIEN_WEBDAV_PREVIEW_JPEG_QUALITY);
else $written = imagejpeg($preview, $target, BRATONIEN_WEBDAV_PREVIEW_JPEG_QUALITY); if (!$written) fail_preview('GD konnte das Preview nicht schreiben.');
if (!$written) fail_preview('GD konnte das Preview im Format '.$extension.' nicht schreiben.');
} }
finally finally
{ {
@@ -175,7 +164,7 @@ function write_preview_gd($blob, $target, $extension)
function write_preview($blob, $target, $extension) function write_preview($blob, $target, $extension)
{ {
$dir = dirname($target); $dir = dirname($target);
if (!is_dir($dir) && !mkdir($dir, 0775, true) && !is_dir($dir)) fail_preview('Preview-Verzeichnis konnte nicht angelegt werden.'); if (!is_dir($dir) && !mkdir($dir, 0755, true) && !is_dir($dir)) fail_preview('Preview-Verzeichnis konnte nicht angelegt werden.');
if (class_exists('Imagick')) if (class_exists('Imagick'))
{ {
@@ -197,48 +186,7 @@ function write_preview($blob, $target, $extension)
@unlink($target); @unlink($target);
fail_preview('Das erzeugte Preview ist ungültig.'); fail_preview('Das erzeugte Preview ist ungültig.');
} }
@chmod($target, 0664); @chmod($target, 0644);
}
function local_source_is_hydrated($source_path, $preview)
{
if (!is_file($source_path) || !is_readable($source_path)) return false;
$source_size = @getimagesize($source_path);
$preview_size = @getimagesize($preview);
if (!is_array($source_size) || !is_array($preview_size)) return false;
if ((int)$source_size[0] !== (int)$preview_size[0] || (int)$source_size[1] !== (int)$preview_size[1]) return false;
$source_bytes = @filesize($source_path);
$preview_bytes = @filesize($preview);
return $source_bytes !== false && $preview_bytes !== false && (int)$source_bytes === (int)$preview_bytes;
}
function hydrate_local_source($source_path, $preview)
{
$normalized = str_replace('\\', '/', (string)$source_path);
if (!preg_match('#/nc-webdav-source/connection-[0-9]+/root-[0-9]+/#', $normalized))
{
fail_preview('Unsicherer lokaler Connector-Quellpfad: '.$source_path);
}
if (!is_file($preview) || !is_readable($preview)) fail_preview('Preview für lokale Quelle fehlt: '.$preview);
if (local_source_is_hydrated($source_path, $preview)) return false;
$dir = dirname($source_path);
if (!is_dir($dir) && !mkdir($dir, 0775, true) && !is_dir($dir)) fail_preview('Lokales Connector-Verzeichnis konnte nicht angelegt werden: '.$dir);
$tmp = $source_path.'.bratonien-next-'.getmypid();
if (!@copy($preview, $tmp)) fail_preview('Preview konnte nicht in lokale Piwigo-Quelle kopiert werden: '.$source_path);
@chmod($tmp, 0664);
$mtime = @filemtime($preview);
if ($mtime) @touch($tmp, $mtime);
if (!@rename($tmp, $source_path))
{
@unlink($tmp);
fail_preview('Lokale Piwigo-Quelle konnte nicht ersetzt werden: '.$source_path);
}
clearstatcache(true, $source_path);
$size = @getimagesize($source_path);
if (!is_array($size) || empty($size[0]) || empty($size[1])) fail_preview('Lokale Piwigo-Quelle ist nach dem Ersetzen ungültig: '.$source_path);
return true;
} }
try try
@@ -260,8 +208,8 @@ try
$mapping = json_decode((string)file_get_contents($mapping_file), true); $mapping = json_decode((string)file_get_contents($mapping_file), true);
if (!is_array($mapping) || !isset($mapping['files']) || !is_array($mapping['files'])) fail_preview('WebDAV-Mapping ist ungültig.'); if (!is_array($mapping) || !isset($mapping['files']) || !is_array($mapping['files'])) fail_preview('WebDAV-Mapping ist ungültig.');
if (!is_dir($cache_dir) && !mkdir($cache_dir, 0775, true) && !is_dir($cache_dir)) fail_preview('Preview-Cache konnte nicht angelegt werden.'); if (!is_dir($cache_dir) && !mkdir($cache_dir, 0755, true) && !is_dir($cache_dir)) fail_preview('Preview-Cache konnte nicht angelegt werden.');
@chmod($cache_dir, 2775); @chmod($cache_dir, 0755);
$state_file = $cache_dir.'/state.json'; $state_file = $cache_dir.'/state.json';
$old_state = array(); $old_state = array();
@@ -274,10 +222,9 @@ try
$new_state = array(); $new_state = array();
$generated = 0; $generated = 0;
$cached = 0; $cached = 0;
$hydrated = 0;
$errors = 0; $errors = 0;
foreach ($mapping['files'] as $local_path=>$entry) foreach ($mapping['files'] as $entry)
{ {
if (!is_array($entry) || (string)($entry['kind'] ?? '') !== 'file') continue; if (!is_array($entry) || (string)($entry['kind'] ?? '') !== 'file') continue;
$webdav_path = trim((string)($entry['webdav_path'] ?? ''), '/'); $webdav_path = trim((string)($entry['webdav_path'] ?? ''), '/');
@@ -294,16 +241,20 @@ try
'version' => BRATONIEN_WEBDAV_PREVIEW_VERSION, 'version' => BRATONIEN_WEBDAV_PREVIEW_VERSION,
); );
try
{
$old = $old_state[$key] ?? null; $old = $old_state[$key] ?? null;
$valid_cached = is_file($target) if (
is_file($target)
&& is_array($old) && is_array($old)
&& (string)($old['etag'] ?? '') === $etag && (string)($old['etag'] ?? '') === $etag
&& (string)($old['format'] ?? '') === $extension && (string)($old['format'] ?? '') === $extension
&& (int)($old['version'] ?? 0) === BRATONIEN_WEBDAV_PREVIEW_VERSION; && (int)($old['version'] ?? 0) === BRATONIEN_WEBDAV_PREVIEW_VERSION
)
{
$cached++;
continue;
}
if (!$valid_cached) try
{ {
$url = $base_url.'/remote.php/dav/files/'.rawurlencode($user).'/'.quote_webdav_path($webdav_path); $url = $base_url.'/remote.php/dav/files/'.rawurlencode($user).'/'.quote_webdav_path($webdav_path);
$blob = fetch_remote_blob($url, $user, $password); $blob = fetch_remote_blob($url, $user, $password);
@@ -311,13 +262,6 @@ try
remove_other_preview_format($cache_dir, $key, $target); remove_other_preview_format($cache_dir, $key, $target);
$generated++; $generated++;
} }
else
{
$cached++;
}
if (hydrate_local_source((string)$local_path, $target)) $hydrated++;
}
catch (Throwable $e) catch (Throwable $e)
{ {
$errors++; $errors++;
@@ -329,14 +273,14 @@ try
{ {
if (!is_file($file) || basename($file) === 'state.json') continue; if (!is_file($file) || basename($file) === 'state.json') continue;
$name = basename($file); $name = basename($file);
if (!preg_match('/^([a-f0-9]{40})\.(jpg|png|gif|webp)$/', $name, $m)) continue; if (!preg_match('/^([a-f0-9]{40})\.(jpg|png|webp)$/', $name, $m)) continue;
if (!isset($new_state[$m[1]])) @unlink($file); if (!isset($new_state[$m[1]])) @unlink($file);
} }
file_put_contents($state_file, json_encode($new_state, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)."\n", LOCK_EX); file_put_contents($state_file, json_encode($new_state, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)."\n", LOCK_EX);
@chmod($state_file, 0664); @chmod($state_file, 0644);
echo 'WebDAV-Previews: erzeugt='.$generated.' vorhanden='.$cached.' lokale_quellen='.$hydrated.' fehler='.$errors."\n"; echo 'WebDAV-Previews: erzeugt='.$generated.' vorhanden='.$cached.' fehler='.$errors."\n";
exit($errors > 0 ? 1 : 0); exit($errors > 0 ? 1 : 0);
} }
catch (Throwable $e) catch (Throwable $e)

View File

@@ -1,24 +0,0 @@
#!/usr/bin/env php
<?php
if (PHP_SAPI !== 'cli')
{
fwrite(STDERR, "CLI only\n");
exit(1);
}
require_once(dirname(__DIR__, 2).'/include/nc_transport.inc.php');
try
{
if ($argc !== 2) throw new RuntimeException('Aufruf: resolve-nextcloud-target.php <nextcloud-url>');
$url = rtrim(trim((string)$argv[1]), '/');
bratonien_tools_nc_transport_scheme($url);
$host = bratonien_tools_nc_transport_host($url);
echo bratonien_tools_nc_transport_public_ip($host)."\n";
exit(0);
}
catch (Throwable $e)
{
fwrite(STDERR, $e->getMessage()."\n");
exit(1);
}

31
runtime/lib/shadow_tree.py Executable file → Normal file
View File

@@ -79,15 +79,23 @@ def preferred_target(source_key: str, raw_name: str, parent_target: Path, old_ma
return safe_name(raw_name) return safe_name(raw_name)
def mirror_directory(source: Path, target: Path, source_key: str, target_key: Path, old_map: dict[str, str], new_map: dict[str, str]) -> None: def mirror_directory(
source: Path,
target: Path,
source_key: str,
target_key: Path,
old_map: dict[str, str],
new_map: dict[str, str],
used: set[str] | None = None,
) -> None:
target.mkdir(parents=True, exist_ok=True) target.mkdir(parents=True, exist_ok=True)
used: set[str] = set() used_names = used if used is not None else set()
for child in sorted(source.iterdir(), key=lambda item: (item.name.casefold(), item.name)): for child in sorted(source.iterdir(), key=lambda item: (item.name.casefold(), item.name)):
if child.is_symlink(): if child.is_symlink():
continue continue
child_source_key = f"{source_key}/{child.name}" child_source_key = f"{source_key}/{child.name}"
preferred = preferred_target(child_source_key, child.name, target_key, old_map) preferred = preferred_target(child_source_key, child.name, target_key, old_map)
child_name = unique_name(preferred, used, child.is_file()) child_name = unique_name(preferred, used_names, child.is_file())
child_target = target / child_name child_target = target / child_name
child_target_key = target_key / child_name child_target_key = target_key / child_name
new_map[child_source_key] = child_target_key.as_posix() new_map[child_source_key] = child_target_key.as_posix()
@@ -120,11 +128,26 @@ def build(manifest: Path, destination: Path, state_file: Path) -> None:
try: try:
used_roots: set[str] = set() used_roots: set[str] = set()
transparent_roots = 0
for entry in sorted(entries, key=lambda item: (item["display_name"].casefold(), item["share_id"])): for entry in sorted(entries, key=lambda item: (item["display_name"].casefold(), item["share_id"])):
source = Path(entry["source_path"]) source = Path(entry["source_path"])
source_key = f"share:{entry['share_id']}" source_key = f"share:{entry['share_id']}"
preferred = preferred_target(source_key, entry["display_name"], Path("."), old_map)
is_file_share = entry["item_type"] == "file" is_file_share = entry["item_type"] == "file"
# Empty display_name is intentional: it represents the authenticated
# user's WebDAV root. Its children belong directly at destination;
# the Nextcloud username must never become an album wrapper.
if not is_file_share and entry["display_name"] == "":
transparent_roots += 1
if transparent_roots > 1:
raise ValueError("only one transparent WebDAV root is allowed")
if not source.is_dir():
raise FileNotFoundError(f"source is not a readable directory: {source}")
new_map[source_key] = "."
mirror_directory(source, staging, source_key, Path("."), old_map, new_map, used_roots)
continue
preferred = preferred_target(source_key, entry["display_name"], Path("."), old_map)
root_name = unique_name(preferred, used_roots, is_file_share) root_name = unique_name(preferred, used_roots, is_file_share)
root_key = Path(root_name) root_key = Path(root_name)
new_map[source_key] = root_key.as_posix() new_map[source_key] = root_key.as_posix()

View File

@@ -1,108 +0,0 @@
#!/usr/bin/env php
<?php
if (PHP_SAPI !== 'cli')
{
fwrite(STDERR, "CLI only\n");
exit(1);
}
$options = getopt('', array('connection-id::'));
$connectionId = isset($options['connection-id']) ? (int)$options['connection-id'] : 0;
$pluginRoot = dirname(__DIR__);
$piwigoRoot = dirname($pluginRoot, 2);
$base = rtrim($piwigoRoot, '/').'/_data/bratonien-tools';
$schedulerDir = $base.'/nc-connector-scheduler';
$stateFile = $schedulerDir.'/state.json';
$runtimeDir = $base.'/nc-connector-runtime';
$stateRoot = $base.'/nc-connector-state';
function native_scheduler_state($path)
{
if (!is_readable($path)) return array();
$decoded = json_decode((string)@file_get_contents($path), true);
return is_array($decoded) ? $decoded : array();
}
function native_scheduler_write($path, array $state)
{
$json = json_encode($state, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
if (!is_string($json)) return false;
$tmp = $path.'.tmp';
if (@file_put_contents($tmp, $json."\n", LOCK_EX) === false) return false;
@chmod($tmp, 0640);
return @rename($tmp, $path);
}
foreach (array($schedulerDir, $runtimeDir, $stateRoot) as $dir)
{
if (!is_dir($dir) && !@mkdir($dir, 0750, true) && !is_dir($dir))
{
fwrite(STDERR, "Runtime-Verzeichnis konnte nicht angelegt werden: {$dir}\n");
exit(1);
}
}
$state = native_scheduler_state($stateFile);
$state['enabled'] = true;
$state['mode'] = 'piwigo-native';
$state['state'] = 'running';
$state['message'] = $connectionId > 0 ? 'NC-Abgleich für Verbindung #'.$connectionId.' läuft.' : 'NC-Abgleich läuft.';
$state['started_at'] = time();
$state['timestamp'] = time();
$state['connection_id'] = $connectionId;
native_scheduler_write($stateFile, $state);
$env = $_ENV;
$env['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
$env['BRATONIEN_NC_NATIVE'] = '1';
$env['BRATONIEN_NC_PIWIGO_ROOT'] = $piwigoRoot;
$env['BRATONIEN_NC_CONFIG_DIR'] = $runtimeDir;
$env['BRATONIEN_NC_STATE_ROOT'] = $stateRoot;
$env['BRATONIEN_NC_CONNECTION_ID'] = (string)$connectionId;
$env['LC_ALL'] = 'C';
$env['LANG'] = 'C';
$bash = is_executable('/usr/bin/bash') ? '/usr/bin/bash' : '/bin/bash';
$command = array($bash, $pluginRoot.'/runtime/run-all.sh');
$spec = array(
0=>array('file','/dev/null','r'),
1=>array('pipe','w'),
2=>array('pipe','w'),
);
$process = @proc_open($command, $spec, $pipes, null, $env);
$stdout = '';
$stderr = '';
$exit = 1;
if (is_resource($process))
{
$stdout = (string)stream_get_contents($pipes[1]);
$stderr = (string)stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$exit = proc_close($process);
}
else
{
$stderr = 'run-all.sh konnte nicht gestartet werden.';
}
$state = native_scheduler_state($stateFile);
$state['enabled'] = true;
$state['mode'] = 'piwigo-native';
$state['state'] = $exit === 0 ? 'success' : 'error';
$state['message'] = $exit === 0
? ($connectionId > 0 ? 'NC-Abgleich für Verbindung #'.$connectionId.' erfolgreich abgeschlossen.' : 'NC-Abgleich erfolgreich abgeschlossen.')
: ($connectionId > 0 ? 'NC-Abgleich für Verbindung #'.$connectionId.' fehlgeschlagen.' : 'NC-Abgleich fehlgeschlagen.');
$state['timestamp'] = time();
$state['finished_at'] = time();
$state['exit_code'] = $exit;
$state['connection_id'] = $connectionId;
$state['stdout'] = trim($stdout);
$state['stderr'] = trim($stderr);
if (empty($state['next_due']) || (int)$state['next_due'] < time())
{
$state['next_due'] = time() + 60;
}
native_scheduler_write($stateFile, $state);
exit($exit === 0 ? 0 : 1);

View File

@@ -78,11 +78,8 @@ function webdav_source_fingerprint($baseUrl, $user, array $roots)
$pluginRoot = dirname(__DIR__); $pluginRoot = dirname(__DIR__);
$piwigoRoot = dirname($pluginRoot, 2); $piwigoRoot = dirname($pluginRoot, 2);
$dbConfig = $piwigoRoot.'/local/config/database.inc.php'; $dbConfig = $piwigoRoot.'/local/config/database.inc.php';
$nativeMode = getenv('BRATONIEN_NC_NATIVE') === '1'; $configDir = '/etc/bratonien-tools/nc-connector';
$configDir = trim((string)getenv('BRATONIEN_NC_CONFIG_DIR')); $stateRoot = '/var/lib/bratonien-tools/nc-connector';
if ($configDir === '') $configDir = $nativeMode ? rtrim($piwigoRoot, '/').'/_data/bratonien-tools/nc-connector-runtime' : '/etc/bratonien-tools/nc-connector';
$stateRoot = trim((string)getenv('BRATONIEN_NC_STATE_ROOT'));
if ($stateRoot === '') $stateRoot = $nativeMode ? rtrim($piwigoRoot, '/').'/_data/bratonien-tools/nc-connector-state' : '/var/lib/bratonien-tools/nc-connector';
$publicSourceRoot = rtrim($piwigoRoot, '/').'/_data/bratonien-tools/nc-webdav-source'; $publicSourceRoot = rtrim($piwigoRoot, '/').'/_data/bratonien-tools/nc-webdav-source';
$publicGalleryRoot = rtrim($piwigoRoot, '/').'/_data/bratonien-tools/nc-webdav-gallery'; $publicGalleryRoot = rtrim($piwigoRoot, '/').'/_data/bratonien-tools/nc-webdav-gallery';
$legacyGalleryRoot = rtrim($piwigoRoot, '/').'/galleries'; $legacyGalleryRoot = rtrim($piwigoRoot, '/').'/galleries';
@@ -110,12 +107,11 @@ try
$rows = $db->query("SELECT id,name,adapter,config_json,secret_blob FROM `{$table}` ORDER BY id DESC"); $rows = $db->query("SELECT id,name,adapter,config_json,secret_blob FROM `{$table}` ORDER BY id DESC");
if (!$rows) fail_webdav_reconcile('Connector-Verbindungen konnten nicht gelesen werden: '.$db->error); if (!$rows) fail_webdav_reconcile('Connector-Verbindungen konnten nicht gelesen werden: '.$db->error);
foreach (array($configDir, $stateRoot, $publicSourceRoot, $publicGalleryRoot) as $dir) foreach (array($configDir, $publicSourceRoot, $publicGalleryRoot) as $dir)
{ {
if (!is_dir($dir) && !mkdir($dir, $dir === $configDir ? 0700 : 0750, true)) fail_webdav_reconcile('Runtime-Verzeichnis konnte nicht angelegt werden: '.$dir); if (!is_dir($dir) && !mkdir($dir, $dir === $configDir ? 0700 : 0755, true)) fail_webdav_reconcile('Runtime-Verzeichnis konnte nicht angelegt werden: '.$dir);
} }
@chmod($configDir, 0700); @chmod($configDir, 0700);
@chmod($stateRoot, 0750);
@chmod($publicSourceRoot, 0755); @chmod($publicSourceRoot, 0755);
@chmod($publicGalleryRoot, 0755); @chmod($publicGalleryRoot, 0755);
@@ -154,7 +150,7 @@ try
$seenFingerprints[$fingerprint] = $id; $seenFingerprints[$fingerprint] = $id;
$known[$id] = true; $known[$id] = true;
$stateDir = $nativeMode ? $stateRoot.'/connection-'.$id : rtrim((string)($config['state_dir'] ?? ''), '/'); $stateDir = rtrim((string)($config['state_dir'] ?? ''), '/');
if ($stateDir === '') $stateDir = $stateRoot.'/connection-'.$id; if ($stateDir === '') $stateDir = $stateRoot.'/connection-'.$id;
if (!is_dir($stateDir) && !mkdir($stateDir, 0750, true)) fail_webdav_reconcile('State-Verzeichnis konnte nicht angelegt werden.'); if (!is_dir($stateDir) && !mkdir($stateDir, 0750, true)) fail_webdav_reconcile('State-Verzeichnis konnte nicht angelegt werden.');
@chmod($stateDir, 0750); @chmod($stateDir, 0750);
@@ -223,7 +219,7 @@ try
$config['parallel_gallery_root'] = $galleryRoot; $config['parallel_gallery_root'] = $galleryRoot;
$config['source_fingerprint'] = $fingerprint; $config['source_fingerprint'] = $fingerprint;
$config['runtime'] = array( $config['runtime'] = array(
'mode'=>$nativeMode ? 'piwigo-native-webdav' : 'webdav', 'mode'=>'webdav',
'config'=>$configPath, 'config'=>$configPath,
'piwigo_sync_enabled'=>true, 'piwigo_sync_enabled'=>true,
'reconciled_at'=>date('Y-m-d H:i:s'), 'reconciled_at'=>date('Y-m-d H:i:s'),

View File

@@ -1,36 +0,0 @@
#!/usr/bin/env bash
set -Eeuo pipefail
PIWIGO_ROOT="${1:-/var/www/piwigo}"
PIWIGO_ROOT="${PIWIGO_ROOT%/}"
DATA_DIR="$PIWIGO_ROOT/_data"
[[ -d "$DATA_DIR" ]] || { echo "Piwigo-_data wurde nicht gefunden: $DATA_DIR" >&2; exit 1; }
[[ "$(id -u)" -eq 0 ]] || { echo "Die Reparatur muss als root ausgeführt werden." >&2; exit 1; }
DATA_UID="$(stat -c '%u' "$DATA_DIR")"
DATA_GID="$(stat -c '%g' "$DATA_DIR")"
PATHS=(
"$DATA_DIR/bratonien-tools/nc-webdav-gallery"
"$DATA_DIR/bratonien-tools/nc-webdav-preview"
"$DATA_DIR/i/_data/bratonien-tools/nc-webdav-gallery"
"$DATA_DIR/i/bratonien-watermark"
)
found=0
for path in "${PATHS[@]}"; do
[[ -e "$path" ]] || continue
found=1
echo "Repariere: $path"
chown -R "$DATA_UID:$DATA_GID" -- "$path"
find "$path" -type d -exec chmod 2775 {} +
find "$path" -type f -exec chmod 0664 {} +
done
if [[ "$found" -eq 0 ]]; then
echo "Keine Bratonien-Cache-Verzeichnisse gefunden."
exit 0
fi
echo "Bratonien-Cache-Rechte wurden an $DATA_DIR angeglichen (UID $DATA_UID, GID $DATA_GID)."

View File

@@ -1,26 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
CONFIG_DIR="/etc/bratonien-tools/nc-connector"
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
PIWIGO_ROOT_DEFAULT="${BRATONIEN_NC_PIWIGO_ROOT:-$(cd -- "$SCRIPT_DIR/../../.." && pwd)}"
CONFIG_DIR="${BRATONIEN_NC_CONFIG_DIR:-/etc/bratonien-tools/nc-connector}"
NATIVE_MODE="${BRATONIEN_NC_NATIVE:-0}"
TARGET_CONNECTION_ID="${BRATONIEN_NC_CONNECTION_ID:-0}"
GLOBAL_LOCK_DIR="${PIWIGO_ROOT_DEFAULT%/}/_data/bratonien-tools/nc-connector-scheduler"
GLOBAL_LOCK_FILE="$GLOBAL_LOCK_DIR/worker.lock"
mkdir -p -- "$GLOBAL_LOCK_DIR"
exec 8>"$GLOBAL_LOCK_FILE"
if ! flock -n 8; then
echo "NC Connector: ein Lauf ist bereits aktiv."
exit 0
fi
shopt -s nullglob shopt -s nullglob
if [[ ! "$TARGET_CONNECTION_ID" =~ ^[0-9]+$ ]]; then
echo "NC Connector: ungültige Ziel-Verbindungs-ID: $TARGET_CONNECTION_ID" >&2
exit 1
fi
read_config_value() { read_config_value() {
local key="$1" local key="$1"
local file="$2" local file="$2"
@@ -55,14 +39,10 @@ write_route_status() {
' "$ROUTE_STATUS_FILE" "$route" "$label" "$detail" "$success" ' "$ROUTE_STATUS_FILE" "$route" "$label" "$detail" "$success"
} }
if [[ "$NATIVE_MODE" != "1" ]]; then
php "$SCRIPT_DIR/reconcile.php" php "$SCRIPT_DIR/reconcile.php"
fi
php "$SCRIPT_DIR/reconcile-webdav.php" php "$SCRIPT_DIR/reconcile-webdav.php"
php "$SCRIPT_DIR/cleanup-webdav-piwigo.php" php "$SCRIPT_DIR/cleanup-webdav-piwigo.php"
if [[ "$NATIVE_MODE" != "1" ]]; then
php "$SCRIPT_DIR/cleanup-stale.php" php "$SCRIPT_DIR/cleanup-stale.php"
fi
configs=("$CONFIG_DIR"/connection-*.conf) configs=("$CONFIG_DIR"/connection-*.conf)
webdav_configs=("$CONFIG_DIR"/webdav-connection-*.conf) webdav_configs=("$CONFIG_DIR"/webdav-connection-*.conf)
@@ -75,21 +55,16 @@ fi
route_piwigo_root="" route_piwigo_root=""
for candidate in "${webdav_configs[@]}" "${configs[@]}"; do for candidate in "${webdav_configs[@]}" "${configs[@]}"; do
[[ -f "$candidate" ]] || continue [[ -f "$candidate" ]] || continue
candidate_id="$(read_config_value CONNECTION_ID "$candidate")"
if [[ "$TARGET_CONNECTION_ID" -gt 0 && "$candidate_id" != "$TARGET_CONNECTION_ID" ]]; then
continue
fi
route_piwigo_root="$(read_config_value PIWIGO_ROOT "$candidate")" route_piwigo_root="$(read_config_value PIWIGO_ROOT "$candidate")"
[[ -n "$route_piwigo_root" ]] && break [[ -n "$route_piwigo_root" ]] && break
done done
[[ -n "$route_piwigo_root" ]] || route_piwigo_root="$PIWIGO_ROOT_DEFAULT" [[ -n "$route_piwigo_root" ]] || route_piwigo_root="/var/www/piwigo"
ROUTE_STATUS_FILE="${route_piwigo_root%/}/_data/bratonien-tools/nc-connector-status/route-status.json" ROUTE_STATUS_FILE="${route_piwigo_root%/}/_data/bratonien-tools/nc-connector-status/route-status.json"
failure_count=0 failure_count=0
webdav_count=0 webdav_count=0
local_count=0 local_count=0
summary_parts=() summary_parts=()
matched_count=0
for config in "${webdav_configs[@]}"; do for config in "${webdav_configs[@]}"; do
[[ -f "$config" ]] || continue [[ -f "$config" ]] || continue
@@ -101,11 +76,7 @@ for config in "${webdav_configs[@]}"; do
summary_parts+=("$name: ungueltige Verbindungs-ID") summary_parts+=("$name: ungueltige Verbindungs-ID")
continue continue
fi fi
if [[ "$TARGET_CONNECTION_ID" -gt 0 && "$connection_id" != "$TARGET_CONNECTION_ID" ]]; then
continue
fi
matched_count=$((matched_count + 1))
webdav_count=$((webdav_count + 1)) webdav_count=$((webdav_count + 1))
echo "NC Connector WebDAV #$connection_id: $name" echo "NC Connector WebDAV #$connection_id: $name"
output="" output=""
@@ -120,7 +91,6 @@ for config in "${webdav_configs[@]}"; do
fi fi
done done
if [[ "$NATIVE_MODE" != "1" ]]; then
for config in "${configs[@]}"; do for config in "${configs[@]}"; do
[[ -f "$config" ]] || continue [[ -f "$config" ]] || continue
name="$(basename "$config")" name="$(basename "$config")"
@@ -134,13 +104,9 @@ if [[ "$NATIVE_MODE" != "1" ]]; then
summary_parts+=("$name: ungueltige Verbindungs-ID") summary_parts+=("$name: ungueltige Verbindungs-ID")
continue continue
fi fi
if [[ "$TARGET_CONNECTION_ID" -gt 0 && "$connection_id" != "$TARGET_CONNECTION_ID" ]]; then
continue
fi
matched_count=$((matched_count + 1))
piwigo_root="$(read_config_value PIWIGO_ROOT "$config")" piwigo_root="$(read_config_value PIWIGO_ROOT "$config")"
[[ -n "$piwigo_root" ]] || piwigo_root="$PIWIGO_ROOT_DEFAULT" [[ -n "$piwigo_root" ]] || piwigo_root="/var/www/piwigo"
tombstone_dir="${piwigo_root%/}/_data/bratonien-tools/nc-connector-status" tombstone_dir="${piwigo_root%/}/_data/bratonien-tools/nc-connector-status"
if [[ -f "$tombstone_dir/deleted-$connection_id" ]]; then if [[ -f "$tombstone_dir/deleted-$connection_id" ]]; then
echo "NC Connector: Verbindung $connection_id wurde geloescht; Laufzeitdateien werden entfernt." echo "NC Connector: Verbindung $connection_id wurde geloescht; Laufzeitdateien werden entfernt."
@@ -162,13 +128,6 @@ if [[ "$NATIVE_MODE" != "1" ]]; then
summary_parts+=("Local #$connection_id fehlgeschlagen") summary_parts+=("Local #$connection_id fehlgeschlagen")
fi fi
done done
fi
if [[ "$TARGET_CONNECTION_ID" -gt 0 && "$matched_count" -eq 0 ]]; then
write_route_status "failed" "FEHLER - Verbindung #$TARGET_CONNECTION_ID" "Keine Laufzeitkonfiguration für Verbindung #$TARGET_CONNECTION_ID gefunden." "0"
echo "NC Connector: keine Laufzeitkonfiguration für Verbindung #$TARGET_CONNECTION_ID gefunden." >&2
exit 1
fi
summary_detail="$(IFS='; '; printf '%s' "${summary_parts[*]}")" summary_detail="$(IFS='; '; printf '%s' "${summary_parts[*]}")"
[[ -n "$summary_detail" ]] || summary_detail="Keine Verbindung wurde ausgefuehrt." [[ -n "$summary_detail" ]] || summary_detail="Keine Verbindung wurde ausgefuehrt."
@@ -185,10 +144,10 @@ if [[ "$failure_count" -eq 0 ]]; then
label="Local" label="Local"
fi fi
write_route_status "$route" "$label" "$summary_detail" "1" write_route_status "$route" "$label" "$summary_detail" "1"
echo "NC Connector: angeforderte Verbindung wurde erfolgreich verarbeitet." echo "NC Connector: alle Verbindungen wurden erfolgreich verarbeitet."
exit 0 exit 0
fi fi
write_route_status "failed" "FEHLER - angeforderte Verbindung" "$summary_detail" "0" write_route_status "failed" "FEHLER - mindestens eine Verbindung" "$summary_detail" "0"
echo "NC Connector: die angeforderte Verbindung ist fehlgeschlagen." >&2 echo "NC Connector: mindestens eine Verbindung ist fehlgeschlagen." >&2
exit 1 exit 1

View File

@@ -75,12 +75,8 @@ done < "$WEBDAV_ROOTS_FILE"
[[ ${#ROOT_ARGS[@]} -gt 0 ]] || { write_status error "Keine WebDAV-Wurzeln konfiguriert"; exit 1; } [[ ${#ROOT_ARGS[@]} -gt 0 ]] || { write_status error "Keine WebDAV-Wurzeln konfiguriert"; exit 1; }
WEBDAV_CONNECT_IP="$(php "$SCRIPT_DIR/lib/resolve-nextcloud-target.php" "$WEBDAV_BASE_URL")"
[[ -n "$WEBDAV_CONNECT_IP" ]] || { write_status error "Nextcloud-Zieladresse konnte nicht ermittelt werden"; exit 1; }
python3 "$SCRIPT_DIR/lib/build_webdav_placeholder_source.py" \ python3 "$SCRIPT_DIR/lib/build_webdav_placeholder_source.py" \
--base-url "$WEBDAV_BASE_URL" \ --base-url "$WEBDAV_BASE_URL" \
--connect-ip "$WEBDAV_CONNECT_IP" \
--user "$WEBDAV_USER" \ --user "$WEBDAV_USER" \
--password-file "$WEBDAV_PASSWORD_FILE" \ --password-file "$WEBDAV_PASSWORD_FILE" \
"${ROOT_ARGS[@]}" \ "${ROOT_ARGS[@]}" \
@@ -138,17 +134,6 @@ if [[ "${PIWIGO_SYNC_ENABLED:-0}" == "1" ]]; then
exit "$PIWIGO_EXIT" exit "$PIWIGO_EXIT"
fi fi
if [[ "${BRATONIEN_NC_NATIVE:-0}" == "1" ]]; then
if command -v timeout >/dev/null 2>&1; then
if ! timeout 30m env PIWIGO_CONFIG="$CONFIG_FILE" bash "$SCRIPT_DIR/build-webdav-media.sh"; then
write_status error "Bildaufbereitung ist fehlgeschlagen oder hat das 30-Minuten-Limit erreicht"
exit 1
fi
elif ! env PIWIGO_CONFIG="$CONFIG_FILE" bash "$SCRIPT_DIR/build-webdav-media.sh"; then
write_status error "Bildaufbereitung ist fehlgeschlagen"
exit 1
fi
else
MEDIA_UNIT="bratonien-nc-media-${CONNECTION_ID}-$(date +%s)" MEDIA_UNIT="bratonien-nc-media-${CONNECTION_ID}-$(date +%s)"
if ! systemd-run \ if ! systemd-run \
--quiet \ --quiet \
@@ -160,11 +145,10 @@ if [[ "${PIWIGO_SYNC_ENABLED:-0}" == "1" ]]; then
write_status error "Bildaufbereitung konnte nicht im Hintergrund gestartet werden" write_status error "Bildaufbereitung konnte nicht im Hintergrund gestartet werden"
exit 1 exit 1
fi fi
fi
if grep -q 'Piwigo-Synchronisierung per API erfolgreich' <<<"$PIWIGO_OUTPUT"; then if grep -q 'Piwigo-Synchronisierung per API erfolgreich' <<<"$PIWIGO_OUTPUT"; then
write_status ok \ write_status ok \
"WebDAV eingelesen und Piwigo synchronisiert; Bildaufbereitung abgeschlossen" \ "WebDAV eingelesen und Piwigo synchronisiert; Bildaufbereitung läuft im Hintergrund" \
"" \ "" \
"api" \ "api" \
"ok" \ "ok" \
@@ -173,7 +157,7 @@ if [[ "${PIWIGO_SYNC_ENABLED:-0}" == "1" ]]; then
"Fallback wurde nicht benötigt" "Fallback wurde nicht benötigt"
elif grep -q 'Piwigo-Datenbanksynchronisierung per Benutzername/Passwort-Fallback erfolgreich' <<<"$PIWIGO_OUTPUT"; then elif grep -q 'Piwigo-Datenbanksynchronisierung per Benutzername/Passwort-Fallback erfolgreich' <<<"$PIWIGO_OUTPUT"; then
write_status ok \ write_status ok \
"WebDAV eingelesen und Piwigo über Fallback synchronisiert; Bildaufbereitung abgeschlossen" \ "WebDAV eingelesen und Piwigo über Fallback synchronisiert; Bildaufbereitung läuft im Hintergrund" \
"" \ "" \
"fallback" \ "fallback" \
"not_used" \ "not_used" \
@@ -181,7 +165,7 @@ if [[ "${PIWIGO_SYNC_ENABLED:-0}" == "1" ]]; then
"ok" \ "ok" \
"Benutzername/Passwort-Fallback erfolgreich" "Benutzername/Passwort-Fallback erfolgreich"
else else
write_status ok "WebDAV eingelesen und Piwigo synchronisiert; Bildaufbereitung abgeschlossen" write_status ok "WebDAV eingelesen und Piwigo synchronisiert; Bildaufbereitung läuft im Hintergrund"
fi fi
else else
write_status ok "WebDAV eingelesen; Piwigo-Synchronisierung ist für diese Verbindung deaktiviert" write_status ok "WebDAV eingelesen; Piwigo-Synchronisierung ist für diese Verbindung deaktiviert"

View File

@@ -61,7 +61,6 @@ function bratonien_tools_save_album_rule()
))); )));
} }
bratonien_tools_clear_watermark_cache();
return array('message'=>'Albumregel gespeichert.'); return array('message'=>'Albumregel gespeichert.');
} }
@@ -94,34 +93,7 @@ function bratonien_tools_resolve_album_rule($category_id, array $categories, arr
$by_id[(int)$category['id']] = $category; $by_id[(int)$category['id']] = $category;
} }
$category_id = (int)$category_id; $current = (int)$category_id;
$root = $by_id[$category_id] ?? null;
$is_private = $root && isset($root['status']) && $root['status'] === 'private';
if ($is_private)
{
if (isset($rules[$category_id]))
{
$rule = $rules[$category_id];
if ($rule['mode'] === 'disabled')
{
return array('mode'=>'disabled','profile_id'=>null,'source'=>'album');
}
if ($rule['mode'] === 'profile')
{
return array('mode'=>'profile','profile_id'=>(int)$rule['profile_id'],'source'=>'album');
}
}
$profile_id = $defaults['private_profile'] ?? null;
if (empty($profile_id))
{
return array('mode'=>'disabled','profile_id'=>null,'source'=>'global');
}
return array('mode'=>'profile','profile_id'=>(int)$profile_id,'source'=>'global');
}
$current = $category_id;
$visited = array(); $visited = array();
while ($current > 0 && isset($by_id[$current]) && !isset($visited[$current])) while ($current > 0 && isset($by_id[$current]) && !isset($visited[$current]))
@@ -144,7 +116,10 @@ function bratonien_tools_resolve_album_rule($category_id, array $categories, arr
$current = (int)($by_id[$current]['id_uppercat'] ?? 0); $current = (int)($by_id[$current]['id_uppercat'] ?? 0);
} }
$profile_id = $defaults['public_profile'] ?? null; $root = $by_id[(int)$category_id] ?? null;
$is_private = $root && isset($root['status']) && $root['status'] === 'private';
$profile_id = $is_private ? ($defaults['private_profile'] ?? null) : ($defaults['public_profile'] ?? null);
if (empty($profile_id)) if (empty($profile_id))
{ {
return array('mode'=>'disabled','profile_id'=>null,'source'=>'global'); return array('mode'=>'disabled','profile_id'=>null,'source'=>'global');

View File

@@ -128,7 +128,6 @@ function bratonien_tools_save_watermark_profile()
mass_inserts($table, array_keys($data), array($data)); mass_inserts($table, array_keys($data), array($data));
} }
bratonien_tools_clear_watermark_cache();
return array('message'=>'Wasserzeichenprofil gespeichert.'); return array('message'=>'Wasserzeichenprofil gespeichert.');
} }
@@ -159,7 +158,6 @@ function bratonien_tools_delete_watermark_profile()
} }
pwg_query('DELETE FROM '.bratonien_tools_table('watermark_profiles').' WHERE id='.$id); pwg_query('DELETE FROM '.bratonien_tools_table('watermark_profiles').' WHERE id='.$id);
bratonien_tools_clear_watermark_cache();
return array('message'=>'Wasserzeichenprofil geloescht.'); return array('message'=>'Wasserzeichenprofil geloescht.');
} }
@@ -176,7 +174,6 @@ function bratonien_tools_duplicate_watermark_profile()
$profile['name'] .= ' (Kopie)'; $profile['name'] .= ' (Kopie)';
$profile['created'] = date('Y-m-d H:i:s'); $profile['created'] = date('Y-m-d H:i:s');
mass_inserts(bratonien_tools_table('watermark_profiles'), array_keys($profile), array($profile)); mass_inserts(bratonien_tools_table('watermark_profiles'), array_keys($profile), array($profile));
bratonien_tools_clear_watermark_cache();
return array('message'=>'Wasserzeichenprofil dupliziert.'); return array('message'=>'Wasserzeichenprofil dupliziert.');
} }

View File

@@ -4,59 +4,6 @@ if (!defined('PHPWG_ROOT_PATH'))
die('Hacking attempt!'); die('Hacking attempt!');
} }
function bratonien_tools_watermark_cache_dir()
{
return PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR.'bratonien-watermark';
}
function bratonien_tools_remove_tree($path)
{
$path = rtrim((string)$path, DIRECTORY_SEPARATOR);
if ($path === '' || !file_exists($path)) return;
if (is_file($path) || is_link($path))
{
@unlink($path);
return;
}
foreach (scandir($path) ?: array() as $entry)
{
if ($entry === '.' || $entry === '..') continue;
bratonien_tools_remove_tree($path.DIRECTORY_SEPARATOR.$entry);
}
@rmdir($path);
}
function bratonien_tools_clear_watermark_cache()
{
$dir = bratonien_tools_watermark_cache_dir();
$derivative_root = realpath(PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR);
$parent = realpath(dirname($dir));
if ($derivative_root === false || $parent === false || $parent !== $derivative_root)
{
throw new RuntimeException('Wasserzeichen-Cachepfad ist ungueltig.');
}
if (is_dir($dir))
{
bratonien_tools_remove_tree($dir);
}
}
function bratonien_tools_watermark_cache_upgrade()
{
$version = '0.9.7.13';
if ((string)conf_get_param('bratonien_watermark_cache_version', '') === $version)
{
return;
}
bratonien_tools_clear_watermark_cache();
conf_update_param('bratonien_watermark_cache_version', $version);
}
function bratonien_tools_get_watermark_defaults() function bratonien_tools_get_watermark_defaults()
{ {
$defaults = conf_get_param('bratonien_watermark_defaults', null); $defaults = conf_get_param('bratonien_watermark_defaults', null);
@@ -93,7 +40,6 @@ function bratonien_tools_save_watermark_defaults()
); );
conf_update_param('bratonien_watermark_defaults', json_encode($config)); conf_update_param('bratonien_watermark_defaults', json_encode($config));
bratonien_tools_clear_watermark_cache();
return array('message'=>'Globale Wasserzeichenregeln gespeichert.'); return array('message'=>'Globale Wasserzeichenregeln gespeichert.');
} }

View File

@@ -8,7 +8,6 @@ if (!defined('BRATONIEN_TOOLS_PATH'))
define('BRATONIEN_TOOLS_PATH', PHPWG_ROOT_PATH.'plugins/'.BRATONIEN_TOOLS_ID.'/'); define('BRATONIEN_TOOLS_PATH', PHPWG_ROOT_PATH.'plugins/'.BRATONIEN_TOOLS_ID.'/');
} }
require_once(BRATONIEN_TOOLS_PATH.'include/webdav_image_runtime.inc.php'); require_once(BRATONIEN_TOOLS_PATH.'include/webdav_image_runtime.inc.php');
require_once(BRATONIEN_TOOLS_PATH.'include/nc_transport.inc.php');
function bratonien_tools_webdav_image_abort($status, $message) function bratonien_tools_webdav_image_abort($status, $message)
{ {
@@ -121,7 +120,7 @@ $options = array(
CURLOPT_USERPWD => $user.':'.$password, CURLOPT_USERPWD => $user.':'.$password,
CURLOPT_RETURNTRANSFER => false, CURLOPT_RETURNTRANSFER => false,
CURLOPT_FAILONERROR => false, CURLOPT_FAILONERROR => false,
CURLOPT_USERAGENT => 'Bratonien-Tools-WebDAV-Image/0.9.7.16', CURLOPT_USERAGENT => 'Bratonien-Tools-WebDAV-Image/0.9.6.1',
CURLOPT_HEADERFUNCTION => function($ch, $line) CURLOPT_HEADERFUNCTION => function($ch, $line)
{ {
$length = strlen($line); $length = strlen($line);
@@ -148,7 +147,6 @@ $options = array(
return strlen($data); return strlen($data);
}, },
); );
bratonien_tools_nc_transport_apply_curl($options, $url);
if (!empty($_SERVER['HTTP_RANGE'])) if (!empty($_SERVER['HTTP_RANGE']))
{ {
$range = trim((string)$_SERVER['HTTP_RANGE']); $range = trim((string)$_SERVER['HTTP_RANGE']);