1438 lines
42 KiB
Text
Executable file
1438 lines
42 KiB
Text
Executable file
<?php
|
|
|
|
use Drupal\Core\Routing\RouteMatchInterface;
|
|
use Drupal\Core\Form\FormStateInterface;
|
|
use Drupal\Core\Template\Attribute;
|
|
use Drupal\Core\Url;
|
|
use Drupal\image\Entity\ImageStyle;
|
|
use Drupal\Core\Archiver\Zip;
|
|
use Drupal\Core\Archiver\ArchiverException;
|
|
use Symfony\Component\Yaml\Yaml;
|
|
use Symfony\Component\Process\Process;
|
|
use Drupal\dfg_3dviewer\ModelFormatManager;
|
|
|
|
function dfg_3dviewer_config(): array {
|
|
static $cfg;
|
|
|
|
if ($cfg === NULL) {
|
|
$config = \Drupal::config('dfg_3dviewer.settings');
|
|
|
|
$main_url = trim((string) (
|
|
$config->get('dfg_3dviewer_main_url')
|
|
?? $config->get('main_url')
|
|
)) ?: 'https://3d-repository.hs-mainz.de';
|
|
|
|
$json_export_base_url = trim((string) (
|
|
$config->get('dfg_3dviewer_json_export_base_url')
|
|
?? $config->get('json_export_base_url')
|
|
)) ?: $main_url;
|
|
|
|
$entity_bundle = trim((string) (
|
|
$config->get('dfg_3dviewer_entitybundle')
|
|
?? $config->get('entitybundle')
|
|
)) ?: 'bd3d7baa74856d141bcff7b4193fa128';
|
|
|
|
$viewer_file_upload = trim((string) (
|
|
$config->get('dfg_3dviewer_viewer_file_upload')
|
|
?? $config->get('viewer_file_upload')
|
|
)) ?: 'fad29437cb2a561b91b26aca5dbb7c42';
|
|
|
|
$viewer_file_name = trim((string) (
|
|
$config->get('dfg_3dviewer_viewer_file_name')
|
|
?? $config->get('viewer_file_name')
|
|
)) ?: 'fb76901eb219495fee0512b5cdfdaa18';
|
|
|
|
$api_3d_file_field = trim((string) (
|
|
$config->get('dfg_3dviewer_api_3d_file_field')
|
|
?? $config->get('api_3d_file_field')
|
|
));
|
|
|
|
$image_generation = trim((string) (
|
|
$config->get('dfg_3dviewer_image_generation')
|
|
?? $config->get('image_generation')
|
|
)) ?: 'f605dc6b727a1099b9e52b3ccbdf5673';
|
|
|
|
$field_df = trim((string) (
|
|
$config->get('dfg_3dviewer_field_df')
|
|
?? $config->get('field_df')
|
|
)) ?: 'field_df';
|
|
|
|
$export_viewer = trim((string) (
|
|
$config->get('dfg_3dviewer_export_viewer')
|
|
?? $config->get('export_viewer')
|
|
)) ?: 'export_viewer';
|
|
|
|
$export_viewer_url = trim((string) (
|
|
$config->get('dfg_3dviewer_export_viewer_url')
|
|
?? $config->get('export_viewer_url')
|
|
)) ?: '';
|
|
|
|
$lightweight = filter_var(
|
|
$config->get('dfg_3dviewer_lightweight')
|
|
?? $config->get('lightweight')
|
|
?? false,
|
|
FILTER_VALIDATE_BOOLEAN
|
|
);
|
|
|
|
$gallery_image_class = trim((string) (
|
|
$config->get('dfg_3dviewer_gallery_image_class')
|
|
?? $config->get('gallery_image_class')
|
|
)) ?: 'field--name-fd6a974b7120d422c7b21b5f1f2315d9';
|
|
|
|
$cfg = [
|
|
'main_url' => $main_url,
|
|
'json_export_base_url' => $json_export_base_url,
|
|
'entity_bundle' => $entity_bundle,
|
|
'viewer_file_upload' => $viewer_file_upload,
|
|
'viewer_file_name' => $viewer_file_name,
|
|
'api_3d_file_field' => $api_3d_file_field,
|
|
'image_generation' => $image_generation,
|
|
'field_df' => $field_df,
|
|
'lightweight' => $lightweight,
|
|
'gallery_image_class' => $gallery_image_class,
|
|
'export_viewer' => $export_viewer,
|
|
'export_viewer_url' => $export_viewer_url,
|
|
];
|
|
}
|
|
return $cfg;
|
|
}
|
|
|
|
function preload() {
|
|
$cfg = dfg_3dviewer_config();
|
|
if (isset($cfg)) {
|
|
foreach ($cfg as $key => $value) {
|
|
if (!defined($key)) {
|
|
define($key, $value);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
\Drupal::messenger()->addMessage("Cant load settings file", 'warning');
|
|
}
|
|
}
|
|
|
|
function dfg_3dviewer_init_constants(): void {
|
|
static $done = false;
|
|
if ($done) {
|
|
return;
|
|
}
|
|
|
|
$cfg = dfg_3dviewer_config();
|
|
|
|
foreach ($cfg as $key => $value) {
|
|
$const = 'DFG_3DVIEWER_' . strtoupper($key);
|
|
|
|
if (!defined($const)) {
|
|
define($const, $value);
|
|
}
|
|
}
|
|
|
|
$done = true;
|
|
}
|
|
|
|
/**
|
|
* Returns the queue runner log path.
|
|
*/
|
|
function dfg_3dviewer_queue_runner_log_path(): string {
|
|
return '/opt/drupal/dfg3dworker.log';
|
|
}
|
|
|
|
/**
|
|
* Returns the persistent worker PID file path.
|
|
*/
|
|
function dfg_3dviewer_queue_runner_pid_path(): string {
|
|
return '/opt/drupal/dfg3dworker.pid';
|
|
}
|
|
|
|
/**
|
|
* Checks whether a persistent queue worker loop is already running.
|
|
*/
|
|
function dfg_3dviewer_has_persistent_convert_worker(): bool {
|
|
$pid_file = dfg_3dviewer_queue_runner_pid_path();
|
|
if (!is_file($pid_file) || !is_readable($pid_file)) {
|
|
return FALSE;
|
|
}
|
|
|
|
$pid = trim((string) @file_get_contents($pid_file));
|
|
if ($pid === '' || !ctype_digit($pid)) {
|
|
@unlink($pid_file);
|
|
return FALSE;
|
|
}
|
|
|
|
$pid_int = (int) $pid;
|
|
if ($pid_int <= 0) {
|
|
@unlink($pid_file);
|
|
return FALSE;
|
|
}
|
|
|
|
$is_running = FALSE;
|
|
if (function_exists('posix_kill')) {
|
|
$is_running = @posix_kill($pid_int, 0);
|
|
}
|
|
else {
|
|
$is_running = is_dir('/proc/' . $pid);
|
|
}
|
|
|
|
if (!$is_running) {
|
|
@unlink($pid_file);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Builds a detached drush queue runner command with line timestamps.
|
|
*/
|
|
function dfg_3dviewer_build_timestamped_queue_runner_command(array $arguments, string $runner_log): string {
|
|
$escaped = array_map('escapeshellarg', $arguments);
|
|
$drush_command = implode(' ', $escaped);
|
|
$awk_script = "awk '{ print strftime(\"%Y-%m-%d %H:%M:%S\"), \$0; fflush(); }'";
|
|
|
|
return sprintf(
|
|
'{ %s 2>&1 | %s >> %s; } &',
|
|
$drush_command,
|
|
$awk_script,
|
|
escapeshellarg($runner_log)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Starts a background queue runner in a non-blocking way.
|
|
*/
|
|
function dfg_3dviewer_kick_convert_worker(): void {
|
|
\Drupal::logger('dfg_3dviewer')->notice('dfg_3dviewer_kick_convert_worker() invoked.');
|
|
|
|
if (!empty($GLOBALS['dfg_3dviewer_worker_running'])) {
|
|
return;
|
|
}
|
|
|
|
$lock = \Drupal::lock();
|
|
$lock_name = 'dfg_3dviewer_convert_runner_kick';
|
|
if (!$lock->acquire($lock_name, 30.0)) {
|
|
\Drupal::logger('dfg_3dviewer')->notice('Skip queue runner kick: lock "@lock" is already held.', ['@lock' => $lock_name]);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$queue = \Drupal::service('queue')->get('dfg_3dviewer_convert');
|
|
$queue_size = method_exists($queue, 'numberOfItems') ? (int) $queue->numberOfItems() : -1;
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'Queue runner kick requested. Queue size before start: @size',
|
|
['@size' => (string) $queue_size]
|
|
);
|
|
|
|
if (dfg_3dviewer_has_persistent_convert_worker()) {
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'Skip queue runner kick: persistent worker loop is already active (pid file: @pid_file).',
|
|
['@pid_file' => dfg_3dviewer_queue_runner_pid_path()]
|
|
);
|
|
return;
|
|
}
|
|
|
|
$drush = dfg_3dviewer_find_drush_binary();
|
|
if ($drush === '') {
|
|
\Drupal::logger('dfg_3dviewer')->warning('Cannot kick queue runner: drush binary not found.');
|
|
return;
|
|
}
|
|
|
|
$arguments = [$drush];
|
|
$site_uri = '';
|
|
$site_uri_candidates = [];
|
|
try {
|
|
$request = \Drupal::service('request_stack')->getCurrentRequest();
|
|
if ($request) {
|
|
$site_uri_candidates[] = $request->getSchemeAndHttpHost();
|
|
}
|
|
}
|
|
catch (\Throwable $e) {
|
|
// Fall back to the configured canonical URL below.
|
|
}
|
|
$site_uri_candidates[] = trim((string) (dfg_3dviewer_config()['main_url'] ?? ''));
|
|
|
|
foreach ($site_uri_candidates as $candidate) {
|
|
$candidate = trim((string) $candidate);
|
|
$candidate_parts = parse_url($candidate);
|
|
$candidate_host = is_array($candidate_parts) ? (string) ($candidate_parts['host'] ?? '') : '';
|
|
if (is_array($candidate_parts)
|
|
&& !empty($candidate_parts['scheme'])
|
|
&& $candidate_host !== ''
|
|
&& strpos($candidate_host, '_') === FALSE
|
|
&& strtolower($candidate_host) !== 'default') {
|
|
$site_uri = rtrim($candidate, '/');
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($site_uri !== '') {
|
|
$arguments[] = '--uri=' . $site_uri;
|
|
}
|
|
else {
|
|
\Drupal::logger('dfg_3dviewer')->warning(
|
|
'Queue runner starts without --uri because neither the current request nor configured main URL is a safe absolute URL.'
|
|
);
|
|
}
|
|
$arguments = array_merge($arguments, [
|
|
'queue:run',
|
|
'dfg_3dviewer_convert',
|
|
'--time-limit=30',
|
|
]);
|
|
|
|
// Using Process::start() here is unreliable because the Process object
|
|
// is destroyed at the end of this function and may terminate the child.
|
|
// Spawn a detached shell command instead.
|
|
$runner_log = dfg_3dviewer_queue_runner_log_path();
|
|
$command = dfg_3dviewer_build_timestamped_queue_runner_command($arguments, $runner_log);
|
|
|
|
// Drush may fail in web/PHP contexts when HOME is missing.
|
|
$home = (string) getenv('HOME');
|
|
if ($home === '') {
|
|
$home = '/tmp';
|
|
}
|
|
$composer_home = (string) getenv('COMPOSER_HOME');
|
|
if ($composer_home === '') {
|
|
$composer_home = rtrim($home, '/') . '/.composer';
|
|
}
|
|
$xdg_config_home = (string) getenv('XDG_CONFIG_HOME');
|
|
if ($xdg_config_home === '') {
|
|
$xdg_config_home = rtrim($home, '/') . '/.config';
|
|
}
|
|
|
|
$env = [
|
|
'HOME' => $home,
|
|
'COMPOSER_HOME' => $composer_home,
|
|
'XDG_CONFIG_HOME' => $xdg_config_home,
|
|
];
|
|
|
|
$process = Process::fromShellCommandline($command, DRUPAL_ROOT, $env);
|
|
$process->disableOutput();
|
|
$process->run();
|
|
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'Triggered background queue runner for dfg_3dviewer_convert (log: @log).',
|
|
['@log' => $runner_log]
|
|
);
|
|
}
|
|
catch (\Throwable $e) {
|
|
\Drupal::logger('dfg_3dviewer')->warning(
|
|
'Failed to trigger background queue runner: @msg',
|
|
['@msg' => $e->getMessage()]
|
|
);
|
|
}
|
|
finally {
|
|
$lock->release($lock_name);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds a usable drush executable path.
|
|
*/
|
|
function dfg_3dviewer_find_drush_binary(): string {
|
|
$candidates = [
|
|
DRUPAL_ROOT . '/vendor/bin/drush',
|
|
DRUPAL_ROOT . '/vendor/bin/drush.bat',
|
|
DRUPAL_ROOT . '/../vendor/bin/drush',
|
|
DRUPAL_ROOT . '/../vendor/bin/drush.bat',
|
|
];
|
|
|
|
foreach ($candidates as $candidate) {
|
|
if (is_file($candidate)) {
|
|
return $candidate;
|
|
}
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Tries to resolve file ID from entity field values.
|
|
*/
|
|
function dfg_3dviewer_extract_file_id(array $file_info): ?int {
|
|
$first = $file_info[0] ?? NULL;
|
|
if (!is_array($first)) {
|
|
return NULL;
|
|
}
|
|
|
|
foreach (['target_id', 'fid', 'id', 'value', 'original_target_id', 'uri'] as $key) {
|
|
if (!array_key_exists($key, $first)) {
|
|
continue;
|
|
}
|
|
$candidate = (string) $first[$key];
|
|
if ($candidate !== '' && ctype_digit($candidate)) {
|
|
return (int) $candidate;
|
|
}
|
|
if ($candidate !== '') {
|
|
$mapped = dfg_3dviewer_file_id_from_location($candidate);
|
|
if ($mapped !== NULL) {
|
|
return $mapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Maps URL/public path to a file entity ID when possible.
|
|
*/
|
|
function dfg_3dviewer_file_id_from_location(string $location): ?int {
|
|
$uri = dfg_3dviewer_location_to_public_uri($location);
|
|
if ($uri === NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
try {
|
|
$storage = \Drupal::entityTypeManager()->getStorage('file');
|
|
$existing = $storage->loadByProperties(['uri' => $uri]);
|
|
if (!empty($existing)) {
|
|
$file = reset($existing);
|
|
return $file ? (int) $file->id() : NULL;
|
|
}
|
|
}
|
|
catch (\Throwable $e) {
|
|
return NULL;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Converts full URL or public path into public:// URI.
|
|
*/
|
|
function dfg_3dviewer_location_to_public_uri(string $location): ?string {
|
|
$location = trim($location);
|
|
if ($location === '') {
|
|
return NULL;
|
|
}
|
|
|
|
if (str_starts_with($location, 'public://')) {
|
|
return $location;
|
|
}
|
|
|
|
if (preg_match('#^[a-z][a-z0-9+.-]*://#i', $location)) {
|
|
$path = parse_url($location, PHP_URL_PATH);
|
|
if (!is_string($path) || $path === '') {
|
|
return NULL;
|
|
}
|
|
$location = $path;
|
|
}
|
|
|
|
$location = ltrim($location, '/');
|
|
if (str_starts_with($location, 'sites/default/files/')) {
|
|
return 'public://' . substr($location, strlen('sites/default/files/'));
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Tries to detect a non-empty file reference field on entity.
|
|
*
|
|
* @return array{field_name:string,file_id:int}|null
|
|
*/
|
|
function dfg_3dviewer_detect_file_field_fallback(Drupal\Core\Entity\EntityInterface $entity): ?array {
|
|
if (!method_exists($entity, 'getFields')) {
|
|
return NULL;
|
|
}
|
|
|
|
foreach ($entity->getFields() as $field_name => $field_item_list) {
|
|
try {
|
|
if ($field_item_list->isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
$definition = $field_item_list->getFieldDefinition();
|
|
$storage_definition = $definition ? $definition->getFieldStorageDefinition() : NULL;
|
|
if (!$storage_definition) {
|
|
continue;
|
|
}
|
|
|
|
$field_type = (string) $storage_definition->getType();
|
|
if (!in_array($field_type, ['entity_reference', 'file'], TRUE)) {
|
|
continue;
|
|
}
|
|
|
|
$settings = $definition->getSettings();
|
|
$target_type = (string) ($settings['target_type'] ?? '');
|
|
if ($target_type !== '' && $target_type !== 'file') {
|
|
continue;
|
|
}
|
|
|
|
$file_info = $field_item_list->getValue();
|
|
$file_id = dfg_3dviewer_extract_file_id($file_info);
|
|
if ($file_id !== NULL) {
|
|
return [
|
|
'field_name' => (string) $field_name,
|
|
'file_id' => $file_id,
|
|
];
|
|
}
|
|
}
|
|
catch (\Throwable $e) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Tries to resolve file ID from a field on base/untranslated/translated entity.
|
|
*/
|
|
function dfg_3dviewer_extract_file_id_from_entity_field(
|
|
Drupal\Core\Entity\EntityInterface $entity,
|
|
string $field_name
|
|
): ?int {
|
|
if ($field_name === '' || !method_exists($entity, 'hasField') || !$entity->hasField($field_name)) {
|
|
return NULL;
|
|
}
|
|
|
|
$file_info = $entity->{$field_name}->getValue();
|
|
$file_id = dfg_3dviewer_extract_file_id($file_info);
|
|
if ($file_id !== NULL) {
|
|
return $file_id;
|
|
}
|
|
|
|
if (method_exists($entity, 'getUntranslated')) {
|
|
try {
|
|
$untranslated = $entity->getUntranslated();
|
|
if ($untranslated && method_exists($untranslated, 'hasField') && $untranslated->hasField($field_name)) {
|
|
$file_info = $untranslated->{$field_name}->getValue();
|
|
$file_id = dfg_3dviewer_extract_file_id($file_info);
|
|
if ($file_id !== NULL) {
|
|
return $file_id;
|
|
}
|
|
}
|
|
}
|
|
catch (\Throwable $e) {
|
|
// Ignore and continue.
|
|
}
|
|
}
|
|
|
|
if (method_exists($entity, 'getTranslationLanguages')
|
|
&& method_exists($entity, 'hasTranslation')
|
|
&& method_exists($entity, 'getTranslation')) {
|
|
try {
|
|
foreach ($entity->getTranslationLanguages() as $langcode => $language) {
|
|
if (!method_exists($entity, 'hasTranslation') || !$entity->hasTranslation($langcode)) {
|
|
continue;
|
|
}
|
|
$translation = $entity->getTranslation($langcode);
|
|
if (!method_exists($translation, 'hasField') || !$translation->hasField($field_name)) {
|
|
continue;
|
|
}
|
|
$file_info = $translation->{$field_name}->getValue();
|
|
$file_id = dfg_3dviewer_extract_file_id($file_info);
|
|
if ($file_id !== NULL) {
|
|
return $file_id;
|
|
}
|
|
}
|
|
}
|
|
catch (\Throwable $e) {
|
|
// Ignore and return NULL below.
|
|
}
|
|
}
|
|
|
|
if (!empty($entity->original)
|
|
&& method_exists($entity->original, 'hasField')
|
|
&& $entity->original->hasField($field_name)) {
|
|
try {
|
|
$file_info = $entity->original->{$field_name}->getValue();
|
|
$file_id = dfg_3dviewer_extract_file_id($file_info);
|
|
if ($file_id !== NULL) {
|
|
return $file_id;
|
|
}
|
|
}
|
|
catch (\Throwable $e) {
|
|
// Ignore and return NULL below.
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Resolves current file reference for entity from configured field or fallback.
|
|
*
|
|
* @return array{field_name:string,file_id:int,configured_field:string,used_fallback:bool,configured_raw:array}
|
|
*/
|
|
function dfg_3dviewer_resolve_current_file_reference(
|
|
Drupal\Core\Entity\EntityInterface $entity,
|
|
string $configured_field
|
|
): ?array {
|
|
$configured_raw = [];
|
|
if ($configured_field !== ''
|
|
&& method_exists($entity, 'hasField')
|
|
&& $entity->hasField($configured_field)) {
|
|
$file_info = $entity->{$configured_field}->getValue();
|
|
$configured_raw = is_array($file_info[0] ?? NULL) ? $file_info[0] : [];
|
|
$file_id = dfg_3dviewer_extract_file_id_from_entity_field($entity, $configured_field);
|
|
if ($file_id !== NULL) {
|
|
return [
|
|
'field_name' => $configured_field,
|
|
'file_id' => $file_id,
|
|
'configured_field' => $configured_field,
|
|
'used_fallback' => FALSE,
|
|
'configured_raw' => $configured_raw,
|
|
];
|
|
}
|
|
}
|
|
|
|
$fallback = dfg_3dviewer_detect_file_field_fallback($entity);
|
|
if ($fallback !== NULL) {
|
|
return [
|
|
'field_name' => (string) $fallback['field_name'],
|
|
'file_id' => (int) $fallback['file_id'],
|
|
'configured_field' => $configured_field,
|
|
'used_fallback' => TRUE,
|
|
'configured_raw' => $configured_raw,
|
|
];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Resolves previous file ID from original entity by selected field.
|
|
*/
|
|
function dfg_3dviewer_resolve_previous_file_id(
|
|
Drupal\Core\Entity\EntityInterface $entity,
|
|
string $field_name
|
|
): ?int {
|
|
if (empty($entity->original)
|
|
|| !method_exists($entity->original, 'hasField')
|
|
|| !$entity->original->hasField($field_name)) {
|
|
return NULL;
|
|
}
|
|
|
|
$old = $entity->original->{$field_name}->getValue();
|
|
return dfg_3dviewer_extract_file_id($old);
|
|
}
|
|
|
|
/**
|
|
* Stores last resolved file ID per entity for fallback re-queueing.
|
|
*/
|
|
function dfg_3dviewer_store_last_file_id(string $entity_type, string $entity_id, int $file_id): void {
|
|
if ($entity_type === '' || $entity_id === '' || $file_id <= 0) {
|
|
return;
|
|
}
|
|
$key = 'dfg_3dviewer.last_file_id.' . $entity_type . '.' . $entity_id;
|
|
\Drupal::state()->set($key, $file_id);
|
|
}
|
|
|
|
/**
|
|
* Loads last resolved file ID per entity.
|
|
*/
|
|
function dfg_3dviewer_load_last_file_id(string $entity_type, string $entity_id): ?int {
|
|
if ($entity_type === '' || $entity_id === '') {
|
|
return NULL;
|
|
}
|
|
$key = 'dfg_3dviewer.last_file_id.' . $entity_type . '.' . $entity_id;
|
|
$value = \Drupal::state()->get($key);
|
|
if ($value === NULL) {
|
|
return NULL;
|
|
}
|
|
$candidate = (string) $value;
|
|
return ($candidate !== '' && ctype_digit($candidate)) ? (int) $candidate : NULL;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_help().
|
|
*/
|
|
function dfg_3dviewer_help($route_name, RouteMatchInterface $route_match) {
|
|
switch ($route_name) {
|
|
case 'help.page.wisski_iip_image':
|
|
return '<p>' . t('This is the WissKI module for the integration ' .
|
|
'of IIP (https://iipimage.sourceforge.io/).') . '</p>';
|
|
}
|
|
}
|
|
|
|
function in_arrayi($needle, $haystack)
|
|
{
|
|
return in_array(strtolower($needle), array_map('strtolower', $haystack));
|
|
}
|
|
|
|
function array_searchi($needle, $haystack)
|
|
{
|
|
return array_search(strtolower($needle), array_map('strtolower', $haystack));
|
|
}
|
|
|
|
function url_exists(string $url): bool {
|
|
$ch = curl_init($url);
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_NOBODY => true,
|
|
CURLOPT_FOLLOWLOCATION => true,
|
|
CURLOPT_TIMEOUT => 5,
|
|
CURLOPT_SSL_VERIFYPEER => true,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
]);
|
|
|
|
curl_exec($ch);
|
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
return $code >= 200 && $code < 400;
|
|
}
|
|
|
|
function automatic_helper (&$autoPath, $prefix, $filename, $value) {
|
|
$autoPath = $prefix . "/" . $filename . "." . $value;
|
|
if(!file_exists($autoPath)) {
|
|
$autoPath = '';
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_file_validate().
|
|
*/
|
|
function dfg_3dviewer_hook_file_validate(Drupal\file\FileInterface $file) {
|
|
|
|
$errors = [];
|
|
if (!is_array($filename)) {
|
|
$filename = $file->getFilename();
|
|
if (!$filename) {
|
|
$errors[] = t("The file's name is empty. Give a name to the file.");
|
|
}
|
|
if (strlen($filename) > 255) {
|
|
$errors[] = t("The file's name exceeds the 255 characters limit. Rename the file and try again.");
|
|
}
|
|
if (preg_match('/\s/', $filename)) {
|
|
$errors[] = t("Whitespaces are not allowed in filenames");
|
|
}
|
|
}
|
|
|
|
return $errors;
|
|
|
|
}
|
|
|
|
/**
|
|
* Implements hook_entity_presave().
|
|
*/
|
|
function dfg_3dviewer_entity_presave(Drupal\Core\Entity\EntityInterface $entity) {
|
|
if (!empty($GLOBALS['dfg_3dviewer_worker_running'])) {
|
|
return;
|
|
}
|
|
|
|
$cfg = dfg_3dviewer_config();
|
|
if (empty($cfg) || $entity->bundle() !== $cfg['entity_bundle']) {
|
|
return;
|
|
}
|
|
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_presave matched target entity: type=@type bundle=@bundle id=@id',
|
|
[
|
|
'@type' => $entity->getEntityTypeId(),
|
|
'@bundle' => (string) $entity->bundle(),
|
|
'@id' => (string) ($entity->id() ?? ''),
|
|
]
|
|
);
|
|
|
|
$configured_raw_first = [];
|
|
if (method_exists($entity, 'hasField') && $entity->hasField((string) $cfg['viewer_file_upload'])) {
|
|
$configured_values = $entity->{(string) $cfg['viewer_file_upload']}->getValue();
|
|
$configured_raw_first = is_array($configured_values[0] ?? NULL) ? $configured_values[0] : [];
|
|
}
|
|
|
|
$resolved = dfg_3dviewer_resolve_current_file_reference($entity, (string) $cfg['viewer_file_upload']);
|
|
if ($resolved === NULL) {
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_presave skip: upload field "@field" has no file ID in first item on entity @entity_id. Raw value: @raw',
|
|
[
|
|
'@field' => $cfg['viewer_file_upload'],
|
|
'@entity_id' => (string) ($entity->id() ?? ''),
|
|
'@raw' => json_encode($configured_raw_first, JSON_UNESCAPED_SLASHES),
|
|
]
|
|
);
|
|
return;
|
|
}
|
|
$file_id = (int) $resolved['file_id'];
|
|
$source_field = (string) $resolved['field_name'];
|
|
if (!empty($resolved['used_fallback'])) {
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_presave fallback: configured upload field "@field" is empty; using "@fallback_field" with file_id=@file_id for entity @entity_id. Configured raw: @raw',
|
|
[
|
|
'@field' => (string) $resolved['configured_field'],
|
|
'@fallback_field' => $source_field,
|
|
'@file_id' => (string) $file_id,
|
|
'@entity_id' => (string) ($entity->id() ?? ''),
|
|
'@raw' => json_encode($resolved['configured_raw'], JSON_UNESCAPED_SLASHES),
|
|
]
|
|
);
|
|
}
|
|
$entity_id = $entity->id();
|
|
|
|
$uploaded_file = \Drupal\file\Entity\File::load($file_id);
|
|
if ($uploaded_file) {
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'Upload source field "@field" on entity @entity_id resolved to file_id=@file_id, filename="@filename", uri="@uri".',
|
|
[
|
|
'@field' => $source_field,
|
|
'@entity_id' => (string) ($entity_id ?? ''),
|
|
'@file_id' => $file_id,
|
|
'@filename' => $uploaded_file->getFilename(),
|
|
'@uri' => $uploaded_file->getFileUri(),
|
|
]
|
|
);
|
|
}
|
|
else {
|
|
\Drupal::logger('dfg_3dviewer')->warning(
|
|
'Upload source field "@field" on entity @entity_id points to missing file_id=@file_id.',
|
|
[
|
|
'@field' => $source_field,
|
|
'@entity_id' => (string) ($entity_id ?? ''),
|
|
'@file_id' => $file_id,
|
|
]
|
|
);
|
|
}
|
|
|
|
if (empty($entity_id) && $entity->hasField('eid')) {
|
|
$entity_id = $entity->get('eid')->value;
|
|
}
|
|
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_presave force requeue on save: entity @entity_id file @file_id.',
|
|
[
|
|
'@entity_id' => (string) ($entity_id ?? $entity->id() ?? ''),
|
|
'@file_id' => (string) $file_id,
|
|
]
|
|
);
|
|
|
|
if ($entity->hasField('field_processing_progress')) {
|
|
$entity->set('field_processing_progress', 0);
|
|
}
|
|
if ($entity->hasField('field_processing_status')) {
|
|
$entity->set('field_processing_status', 'queued');
|
|
}
|
|
if ($entity->hasField('field_processing_message')) {
|
|
$entity->set('field_processing_message', 'Queued for conversion');
|
|
}
|
|
$source_filename = $uploaded_file ? $uploaded_file->getFilename() : '';
|
|
$source_uri = $uploaded_file ? $uploaded_file->getFileUri() : '';
|
|
|
|
if (empty($entity_id)) {
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'Entity ID is not available in presave; enqueue deferred to entity_insert for file @file_id.',
|
|
[
|
|
'@file_id' => $file_id,
|
|
]
|
|
);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
\Drupal::service('queue')->get('dfg_3dviewer_convert')->createItem([
|
|
'entity_type' => $entity->getEntityTypeId(),
|
|
'entity_id' => $entity_id,
|
|
'file_id' => $file_id,
|
|
'source_filename' => $source_filename,
|
|
'source_uri' => $source_uri,
|
|
]);
|
|
dfg_3dviewer_store_last_file_id((string) $entity->getEntityTypeId(), (string) $entity_id, (int) $file_id);
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_presave enqueue ok: entity @entity_id file @file_id.',
|
|
[
|
|
'@entity_id' => (string) $entity_id,
|
|
'@file_id' => (string) $file_id,
|
|
]
|
|
);
|
|
dfg_3dviewer_kick_convert_worker();
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'Queued conversion for entity @entity_id and file @file_id.',
|
|
[
|
|
'@entity_id' => $entity_id,
|
|
'@file_id' => $file_id,
|
|
]
|
|
);
|
|
}
|
|
catch (\Throwable $e) {
|
|
\Drupal::logger('dfg_3dviewer')->error(
|
|
'Failed to enqueue conversion in presave for entity @entity_id and file @file_id: @msg',
|
|
[
|
|
'@entity_id' => $entity_id,
|
|
'@file_id' => $file_id,
|
|
'@msg' => $e->getMessage(),
|
|
]
|
|
);
|
|
}
|
|
|
|
$key = $entity->getEntityTypeId() . ':' . $entity_id . ':' . $file_id;
|
|
$GLOBALS['dfg_3dviewer_enqueued_in_presave'][$key] = TRUE;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_entity_insert().
|
|
*/
|
|
function dfg_3dviewer_entity_insert(Drupal\Core\Entity\EntityInterface $entity) {
|
|
if (!empty($GLOBALS['dfg_3dviewer_worker_running'])) {
|
|
return;
|
|
}
|
|
|
|
$cfg = dfg_3dviewer_config();
|
|
if (empty($cfg) || $entity->bundle() !== $cfg['entity_bundle']) {
|
|
return;
|
|
}
|
|
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_insert matched target entity: type=@type bundle=@bundle id=@id',
|
|
[
|
|
'@type' => $entity->getEntityTypeId(),
|
|
'@bundle' => (string) $entity->bundle(),
|
|
'@id' => (string) ($entity->id() ?? ''),
|
|
]
|
|
);
|
|
|
|
$status = $entity->hasField('field_processing_status')
|
|
? (string) $entity->get('field_processing_status')->value
|
|
: '';
|
|
|
|
if ($status !== 'queued') {
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_insert skip: processing status is "@status" (expected "queued") for entity @entity_id.',
|
|
[
|
|
'@status' => $status,
|
|
'@entity_id' => (string) ($entity->id() ?? ''),
|
|
]
|
|
);
|
|
return;
|
|
}
|
|
|
|
$configured_raw_first = [];
|
|
if (method_exists($entity, 'hasField') && $entity->hasField((string) $cfg['viewer_file_upload'])) {
|
|
$configured_values = $entity->{(string) $cfg['viewer_file_upload']}->getValue();
|
|
$configured_raw_first = is_array($configured_values[0] ?? NULL) ? $configured_values[0] : [];
|
|
}
|
|
|
|
$resolved = dfg_3dviewer_resolve_current_file_reference($entity, (string) $cfg['viewer_file_upload']);
|
|
if ($resolved === NULL) {
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_insert skip: upload field "@field" has no file ID in first item on entity @entity_id. Raw value: @raw',
|
|
[
|
|
'@field' => $cfg['viewer_file_upload'],
|
|
'@entity_id' => (string) ($entity->id() ?? ''),
|
|
'@raw' => json_encode($configured_raw_first, JSON_UNESCAPED_SLASHES),
|
|
]
|
|
);
|
|
return;
|
|
}
|
|
$file_id = (int) $resolved['file_id'];
|
|
if (!empty($resolved['used_fallback'])) {
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_insert fallback: configured upload field "@field" is empty; using "@fallback_field" with file_id=@file_id for entity @entity_id. Configured raw: @raw',
|
|
[
|
|
'@field' => (string) $resolved['configured_field'],
|
|
'@fallback_field' => (string) $resolved['field_name'],
|
|
'@file_id' => (string) $file_id,
|
|
'@entity_id' => (string) ($entity->id() ?? ''),
|
|
'@raw' => json_encode($resolved['configured_raw'], JSON_UNESCAPED_SLASHES),
|
|
]
|
|
);
|
|
}
|
|
$entity_id = $entity->id();
|
|
if (empty($entity_id) && $entity->hasField('eid')) {
|
|
$entity_id = $entity->get('eid')->value;
|
|
}
|
|
if (empty($entity_id)) {
|
|
\Drupal::logger('dfg_3dviewer')->warning('Cannot enqueue conversion on insert: missing entity ID.');
|
|
return;
|
|
}
|
|
|
|
$key = $entity->getEntityTypeId() . ':' . $entity_id . ':' . $file_id;
|
|
if (!empty($GLOBALS['dfg_3dviewer_enqueued_in_presave'][$key])) {
|
|
return;
|
|
}
|
|
|
|
$uploaded_file = \Drupal\file\Entity\File::load($file_id);
|
|
$source_filename = $uploaded_file ? $uploaded_file->getFilename() : '';
|
|
$source_uri = $uploaded_file ? $uploaded_file->getFileUri() : '';
|
|
|
|
try {
|
|
\Drupal::service('queue')->get('dfg_3dviewer_convert')->createItem([
|
|
'entity_type' => $entity->getEntityTypeId(),
|
|
'entity_id' => $entity_id,
|
|
'file_id' => $file_id,
|
|
'source_filename' => $source_filename,
|
|
'source_uri' => $source_uri,
|
|
]);
|
|
dfg_3dviewer_store_last_file_id((string) $entity->getEntityTypeId(), (string) $entity_id, (int) $file_id);
|
|
dfg_3dviewer_kick_convert_worker();
|
|
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'Queued conversion on insert for entity @entity_id and file @file_id.',
|
|
[
|
|
'@entity_id' => $entity_id,
|
|
'@file_id' => $file_id,
|
|
]
|
|
);
|
|
}
|
|
catch (\Throwable $e) {
|
|
\Drupal::logger('dfg_3dviewer')->error(
|
|
'Failed to enqueue conversion on insert for entity @entity_id and file @file_id: @msg',
|
|
[
|
|
'@entity_id' => $entity_id,
|
|
'@file_id' => $file_id,
|
|
'@msg' => $e->getMessage(),
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_entity_update().
|
|
*/
|
|
function dfg_3dviewer_entity_update(Drupal\Core\Entity\EntityInterface $entity) {
|
|
if (!empty($GLOBALS['dfg_3dviewer_worker_running'])) {
|
|
return;
|
|
}
|
|
|
|
$cfg = dfg_3dviewer_config();
|
|
if (empty($cfg) || $entity->bundle() !== $cfg['entity_bundle']) {
|
|
return;
|
|
}
|
|
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_update matched target entity: type=@type bundle=@bundle id=@id',
|
|
[
|
|
'@type' => $entity->getEntityTypeId(),
|
|
'@bundle' => (string) $entity->bundle(),
|
|
'@id' => (string) ($entity->id() ?? ''),
|
|
]
|
|
);
|
|
|
|
$configured_raw_first = [];
|
|
if (method_exists($entity, 'hasField') && $entity->hasField((string) $cfg['viewer_file_upload'])) {
|
|
$configured_values = $entity->{(string) $cfg['viewer_file_upload']}->getValue();
|
|
$configured_raw_first = is_array($configured_values[0] ?? NULL) ? $configured_values[0] : [];
|
|
}
|
|
|
|
$resolved = dfg_3dviewer_resolve_current_file_reference($entity, (string) $cfg['viewer_file_upload']);
|
|
if ($resolved === NULL) {
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_update skip: upload field "@field" has no file ID in first item on entity @entity_id. Raw value: @raw',
|
|
[
|
|
'@field' => $cfg['viewer_file_upload'],
|
|
'@entity_id' => (string) ($entity->id() ?? ''),
|
|
'@raw' => json_encode($configured_raw_first, JSON_UNESCAPED_SLASHES),
|
|
]
|
|
);
|
|
|
|
$entity_id_fallback = $entity->id();
|
|
$entity_type_fallback = $entity->getEntityTypeId();
|
|
if (!empty($entity_id_fallback)) {
|
|
try {
|
|
$storage = \Drupal::entityTypeManager()->getStorage($entity_type_fallback);
|
|
$candidates = [];
|
|
if (!empty($entity->original)) {
|
|
$candidates[] = $entity->original;
|
|
}
|
|
$loaded = $storage->load($entity_id_fallback);
|
|
if ($loaded) {
|
|
$candidates[] = $loaded;
|
|
}
|
|
if (method_exists($storage, 'loadUnchanged')) {
|
|
$loaded_unchanged = $storage->loadUnchanged($entity_id_fallback);
|
|
if ($loaded_unchanged) {
|
|
$candidates[] = $loaded_unchanged;
|
|
}
|
|
}
|
|
|
|
foreach ($candidates as $candidate_entity) {
|
|
$stored_resolved = dfg_3dviewer_resolve_current_file_reference($candidate_entity, (string) $cfg['viewer_file_upload']);
|
|
if ($stored_resolved === NULL) {
|
|
continue;
|
|
}
|
|
|
|
$stored_file_id = (int) $stored_resolved['file_id'];
|
|
$stored_file = \Drupal\file\Entity\File::load($stored_file_id);
|
|
$stored_source_filename = $stored_file ? $stored_file->getFilename() : '';
|
|
$stored_source_uri = $stored_file ? $stored_file->getFileUri() : '';
|
|
|
|
\Drupal::service('queue')->get('dfg_3dviewer_convert')->createItem([
|
|
'entity_type' => $entity_type_fallback,
|
|
'entity_id' => $entity_id_fallback,
|
|
'file_id' => $stored_file_id,
|
|
'source_filename' => $stored_source_filename,
|
|
'source_uri' => $stored_source_uri,
|
|
]);
|
|
dfg_3dviewer_store_last_file_id((string) $entity_type_fallback, (string) $entity_id_fallback, (int) $stored_file_id);
|
|
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_update fallback enqueue from candidate entity: entity @entity_id file @file_id.',
|
|
[
|
|
'@entity_id' => (string) $entity_id_fallback,
|
|
'@file_id' => (string) $stored_file_id,
|
|
]
|
|
);
|
|
dfg_3dviewer_kick_convert_worker();
|
|
return;
|
|
}
|
|
}
|
|
catch (\Throwable $e) {
|
|
\Drupal::logger('dfg_3dviewer')->warning(
|
|
'entity_update stored fallback enqueue failed for entity @entity_id: @msg',
|
|
[
|
|
'@entity_id' => (string) $entity_id_fallback,
|
|
'@msg' => $e->getMessage(),
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!empty($entity_id_fallback)) {
|
|
$last_file_id = dfg_3dviewer_load_last_file_id((string) $entity_type_fallback, (string) $entity_id_fallback);
|
|
if ($last_file_id !== NULL) {
|
|
try {
|
|
$last_file = \Drupal\file\Entity\File::load($last_file_id);
|
|
$last_source_filename = $last_file ? $last_file->getFilename() : '';
|
|
$last_source_uri = $last_file ? $last_file->getFileUri() : '';
|
|
\Drupal::service('queue')->get('dfg_3dviewer_convert')->createItem([
|
|
'entity_type' => $entity_type_fallback,
|
|
'entity_id' => $entity_id_fallback,
|
|
'file_id' => $last_file_id,
|
|
'source_filename' => $last_source_filename,
|
|
'source_uri' => $last_source_uri,
|
|
]);
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_update state fallback enqueue: entity @entity_id file @file_id.',
|
|
[
|
|
'@entity_id' => (string) $entity_id_fallback,
|
|
'@file_id' => (string) $last_file_id,
|
|
]
|
|
);
|
|
dfg_3dviewer_kick_convert_worker();
|
|
return;
|
|
}
|
|
catch (\Throwable $e) {
|
|
\Drupal::logger('dfg_3dviewer')->warning(
|
|
'entity_update state fallback enqueue failed for entity @entity_id: @msg',
|
|
[
|
|
'@entity_id' => (string) $entity_id_fallback,
|
|
'@msg' => $e->getMessage(),
|
|
]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_update fallback behavior: kicking queue runner even without resolved file_id for entity @entity_id.',
|
|
['@entity_id' => (string) ($entity->id() ?? '')]
|
|
);
|
|
try {
|
|
$entity_id_fallback = (string) ($entity->id() ?? '');
|
|
if ($entity_id_fallback !== '') {
|
|
\Drupal::service('queue')->get('dfg_3dviewer_convert')->createItem([
|
|
'entity_type' => (string) $entity->getEntityTypeId(),
|
|
'entity_id' => $entity_id_fallback,
|
|
'file_id' => 0,
|
|
'source_filename' => '',
|
|
'source_uri' => '',
|
|
]);
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_update enqueued entity-only fallback job for entity @entity_id.',
|
|
['@entity_id' => $entity_id_fallback]
|
|
);
|
|
}
|
|
}
|
|
catch (\Throwable $e) {
|
|
\Drupal::logger('dfg_3dviewer')->warning(
|
|
'entity_update failed to enqueue entity-only fallback job for entity @entity_id: @msg',
|
|
[
|
|
'@entity_id' => (string) ($entity->id() ?? ''),
|
|
'@msg' => $e->getMessage(),
|
|
]
|
|
);
|
|
}
|
|
dfg_3dviewer_kick_convert_worker();
|
|
return;
|
|
}
|
|
$file_id = (int) $resolved['file_id'];
|
|
if (!empty($resolved['used_fallback'])) {
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_update fallback: configured upload field "@field" is empty; using "@fallback_field" with file_id=@file_id for entity @entity_id. Configured raw: @raw',
|
|
[
|
|
'@field' => (string) $resolved['configured_field'],
|
|
'@fallback_field' => (string) $resolved['field_name'],
|
|
'@file_id' => (string) $file_id,
|
|
'@entity_id' => (string) ($entity->id() ?? ''),
|
|
'@raw' => json_encode($resolved['configured_raw'], JSON_UNESCAPED_SLASHES),
|
|
]
|
|
);
|
|
}
|
|
|
|
$entity_id = $entity->id();
|
|
if (empty($entity_id) && $entity->hasField('eid')) {
|
|
$entity_id = $entity->get('eid')->value;
|
|
}
|
|
if (empty($entity_id)) {
|
|
\Drupal::logger('dfg_3dviewer')->warning('entity_update cannot enqueue: missing entity ID.');
|
|
dfg_3dviewer_kick_convert_worker();
|
|
return;
|
|
}
|
|
|
|
$uploaded_file = \Drupal\file\Entity\File::load($file_id);
|
|
$source_filename = $uploaded_file ? $uploaded_file->getFilename() : '';
|
|
$source_uri = $uploaded_file ? $uploaded_file->getFileUri() : '';
|
|
$key = $entity->getEntityTypeId() . ':' . $entity_id . ':' . $file_id;
|
|
if (!empty($GLOBALS['dfg_3dviewer_enqueued_in_presave'][$key])) {
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_update skip: conversion was already queued in presave for entity @entity_id file @file_id.',
|
|
[
|
|
'@entity_id' => (string) $entity_id,
|
|
'@file_id' => (string) $file_id,
|
|
]
|
|
);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
\Drupal::service('queue')->get('dfg_3dviewer_convert')->createItem([
|
|
'entity_type' => $entity->getEntityTypeId(),
|
|
'entity_id' => $entity_id,
|
|
'file_id' => $file_id,
|
|
'source_filename' => $source_filename,
|
|
'source_uri' => $source_uri,
|
|
]);
|
|
dfg_3dviewer_store_last_file_id((string) $entity->getEntityTypeId(), (string) $entity_id, (int) $file_id);
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_update enqueue ok: entity @entity_id file @file_id.',
|
|
[
|
|
'@entity_id' => (string) $entity_id,
|
|
'@file_id' => (string) $file_id,
|
|
]
|
|
);
|
|
}
|
|
catch (\Throwable $e) {
|
|
\Drupal::logger('dfg_3dviewer')->error(
|
|
'entity_update enqueue failed for entity @entity_id file @file_id: @msg',
|
|
[
|
|
'@entity_id' => (string) $entity_id,
|
|
'@file_id' => (string) $file_id,
|
|
'@msg' => $e->getMessage(),
|
|
]
|
|
);
|
|
}
|
|
|
|
\Drupal::logger('dfg_3dviewer')->notice(
|
|
'entity_update force kick on save: entity @entity_id file @file_id.',
|
|
[
|
|
'@entity_id' => (string) $entity_id,
|
|
'@file_id' => (string) $file_id,
|
|
]
|
|
);
|
|
|
|
dfg_3dviewer_kick_convert_worker();
|
|
}
|
|
/**
|
|
* Normalizes the viewer asset base path to a path-first value.
|
|
*/
|
|
function dfg_3dviewer_normalize_base_module_path(string $value): string {
|
|
$value = trim($value);
|
|
|
|
if ($value === '') {
|
|
return $value;
|
|
}
|
|
|
|
if (preg_match('@^https?://@i', $value)) {
|
|
$parts = parse_url($value);
|
|
if (!empty($parts['path'])) {
|
|
$value = $parts['path'];
|
|
}
|
|
}
|
|
elseif (preg_match('@^/[^/]+\.[^/]+/.+@', $value)) {
|
|
$segments = array_values(array_filter(explode('/', $value), 'strlen'));
|
|
array_shift($segments);
|
|
$value = '/' . implode('/', $segments);
|
|
}
|
|
elseif (preg_match('@^[^/]+\.[^/]+/.+@', $value)) {
|
|
$segments = explode('/', $value);
|
|
array_shift($segments);
|
|
$value = '/' . implode('/', $segments);
|
|
}
|
|
|
|
$value = preg_replace('@/{2,}@', '/', $value);
|
|
|
|
if ($value !== '' && $value[0] !== '/') {
|
|
$value = '/' . $value;
|
|
}
|
|
|
|
return rtrim($value, '/');
|
|
}
|
|
|
|
/**
|
|
* Builds the viewer runtime settings object for drupalSettings / JS CONFIG.
|
|
*/
|
|
function dfg_3dviewer_build_js_settings(): array {
|
|
$config = \Drupal::config('dfg_3dviewer.settings');
|
|
|
|
$main_url = trim((string) (
|
|
$config->get('dfg_3dviewer_main_url')
|
|
?? $config->get('main_url')
|
|
?? ''
|
|
));
|
|
|
|
$metadata_url = trim((string) (
|
|
$config->get('dfg_3dviewer_metadata_url')
|
|
?? $config->get('metadata_url')
|
|
?? ''
|
|
));
|
|
|
|
$json_export_base_url = trim((string) (
|
|
$config->get('dfg_3dviewer_json_export_base_url')
|
|
?? $config->get('json_export_base_url')
|
|
?? ''
|
|
));
|
|
|
|
$base_namespace = trim((string) (
|
|
$config->get('dfg_3dviewer_basenamespace')
|
|
?? $config->get('base_namespace')
|
|
?? ''
|
|
));
|
|
|
|
$base_module_path = dfg_3dviewer_normalize_base_module_path(trim((string) (
|
|
$config->get('dfg_3dviewer_base_module_path')
|
|
?? $config->get('base_path')
|
|
?? ''
|
|
)));
|
|
|
|
if ($base_module_path === '') {
|
|
$base_module_path = '/libraries/dfg-3dviewer/assets';
|
|
}
|
|
|
|
$lightweight = filter_var(
|
|
$config->get('dfg_3dviewer_lightweight')
|
|
?? $config->get('lightweight')
|
|
?? FALSE,
|
|
FILTER_VALIDATE_BOOLEAN
|
|
);
|
|
|
|
return [
|
|
'baseNamespace' => $base_namespace,
|
|
'mainUrl' => $main_url,
|
|
'metadataUrl' => $metadata_url,
|
|
'jsonExportBaseUrl' => $json_export_base_url,
|
|
'baseModulePath' => $base_module_path,
|
|
'entity' => [
|
|
'bundle' => trim((string) (
|
|
$config->get('dfg_3dviewer_entitybundle')
|
|
?? $config->get('entity_bundle')
|
|
?? ''
|
|
)),
|
|
'fieldDf' => trim((string) (
|
|
$config->get('dfg_3dviewer_field_df')
|
|
?? $config->get('field_df')
|
|
?? 'field_df'
|
|
)),
|
|
'idUri' => trim((string) (
|
|
$config->get('dfg_3dviewer_entity_id_uri')
|
|
?? $config->get('entity_id_uri')
|
|
?? '/wisski/navigate/(.*)/view'
|
|
)),
|
|
'viewEntityPath' => trim((string) (
|
|
$config->get('dfg_3dviewer_view_entity_path')
|
|
?? $config->get('view_entity_path')
|
|
?? '/wisski/navigate/'
|
|
)),
|
|
'attributeId' => trim((string) (
|
|
$config->get('dfg_3dviewer_attribute_id')
|
|
?? $config->get('attribute_id')
|
|
?? 'wisski_id'
|
|
)),
|
|
'exportViewer' => trim((string) (
|
|
$config->get('dfg_3dviewer_export_viewer')
|
|
?? $config->get('export_viewer')
|
|
?? 'field_df'
|
|
)),
|
|
'exportViewerUrl' => trim((string) (
|
|
$config->get('dfg_3dviewer_export_viewer_url')
|
|
?? $config->get('export_viewer_url')
|
|
?? ''
|
|
)),
|
|
'metadata' => [
|
|
'source' => 'Drupal',
|
|
'sourceType' => '',
|
|
'url' => '',
|
|
],
|
|
],
|
|
'viewer' => [
|
|
'container' => trim((string) (
|
|
$config->get('dfg_3dviewer_container')
|
|
?? $config->get('container')
|
|
?? 'DFG_3DViewer'
|
|
)),
|
|
'fileUpload' => trim((string) (
|
|
$config->get('dfg_3dviewer_viewer_file_upload')
|
|
?? $config->get('viewer_file_upload')
|
|
?? ''
|
|
)),
|
|
'fileName' => trim((string) (
|
|
$config->get('dfg_3dviewer_viewer_file_name')
|
|
?? $config->get('viewer_file_name')
|
|
?? ''
|
|
)),
|
|
'api3dFileField' => trim((string) (
|
|
$config->get('dfg_3dviewer_api_3d_file_field')
|
|
?? $config->get('api_3d_file_field')
|
|
?? ''
|
|
)),
|
|
'imageGeneration' => trim((string) (
|
|
$config->get('dfg_3dviewer_image_generation')
|
|
?? $config->get('image_generation')
|
|
?? ''
|
|
)),
|
|
'lightweight' => $lightweight,
|
|
'editor' => !$lightweight,
|
|
'scaleContainer' => [
|
|
'x' => (string) (
|
|
$config->get('dfg_3dviewer_scale_container_x')
|
|
?? $config->get('scale_container_x')
|
|
?? '1'
|
|
),
|
|
'y' => (string) (
|
|
$config->get('dfg_3dviewer_scale_container_y')
|
|
?? $config->get('scale_container_y')
|
|
?? '1'
|
|
),
|
|
],
|
|
'gallery' => [
|
|
'container' => trim((string) (
|
|
$config->get('dfg_3dviewer_gallery_container')
|
|
?? $config->get('gallery_container')
|
|
?? ''
|
|
)),
|
|
'imageClass' => trim((string) (
|
|
$config->get('dfg_3dviewer_gallery_image_class')
|
|
?? $config->get('gallery_image_class')
|
|
?? ''
|
|
)),
|
|
'imageId' => trim((string) (
|
|
$config->get('dfg_3dviewer_gallery_image_id')
|
|
?? $config->get('gallery_image_id')
|
|
?? ''
|
|
)),
|
|
'build' => TRUE,
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Attaches viewer settings and CSRF token to a render element.
|
|
*/
|
|
function dfg_3dviewer_attach_settings(array &$element): void {
|
|
$settings = dfg_3dviewer_build_js_settings();
|
|
$token = \Drupal::csrfToken()->get('rest');
|
|
$settings['csrfToken'] = $token;
|
|
$element['#attached']['drupalSettings']['dfg_3dviewer'] = $settings;
|
|
$element['#attached']['html_head'][] = [
|
|
[
|
|
'#tag' => 'script',
|
|
'#value' => 'window.CSRF_TOKEN = ' . json_encode($token, JSON_THROW_ON_ERROR) . ';',
|
|
],
|
|
'dfg_3dviewer_csrf_token',
|
|
];
|
|
}
|
|
|
|
function dfg_3dviewer_get_library(): string {
|
|
return 'dfg_3dviewer/dfg_3dviewer.viewer';
|
|
}
|