'en_US', 'pl' => 'pl_PL', 'fr' => 'fr_FR', 'de' => 'de_DE', 'es' => 'es_ES']; // English has no catalogue: WordPress uses the source strings. return WP_LANG_DIR . '/plugins/inceptus-process-' . $locales[$language] . '.mo'; } add_filter('load_textdomain_mofile', 'inceptus_process_translation_file', 10, 2); /** * Let WordPress discover an external base-language pack for a regional locale. * Exact-locale packs keep priority; no bundled translation path is registered. */ function inceptus_process_translation_directory($path, $domain, $locale) { if ($domain !== 'inceptus-process' || $path) { return $path; } $locales = ['pl' => 'pl_PL', 'fr' => 'fr_FR', 'de' => 'de_DE', 'es' => 'es_ES']; $language = inceptus_process_normalize_language($locale); if (!isset($locales[$language])) { return $path; } $base = WP_LANG_DIR . '/plugins/inceptus-process-' . $locales[$language]; return is_readable($base . '.mo') || is_readable($base . '.l10n.php') ? WP_LANG_DIR . '/plugins/' : $path; } add_filter('lang_dir_for_domain', 'inceptus_process_translation_directory', 10, 3); /** * Detect the language of the currently displayed page. * * The front-end language is intentionally independent from the WordPress * administrator/user locale. Common multilingual plugins are checked first; * WordPress locale is used as the fallback. */ function inceptus_process_page_language() { // Polylang. if (function_exists('pll_current_language')) { $slug = pll_current_language('slug'); $language = inceptus_process_normalize_language($slug, ''); if ($language !== '') { return $language; } $locale = pll_current_language('locale'); $language = inceptus_process_normalize_language($locale, ''); if ($language !== '') { return $language; } if ((is_string($slug) && trim($slug) !== '') || (is_string($locale) && trim($locale) !== '')) { return 'en'; } } // WPML. $wpml_language = apply_filters('wpml_current_language', null); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML owns this documented hook name. if (is_string($wpml_language) && trim($wpml_language) !== '') { return inceptus_process_normalize_language($wpml_language); } // TranslatePress exposes the currently selected front-end language here. global $TRP_LANGUAGE; if (is_string($TRP_LANGUAGE) && trim($TRP_LANGUAGE) !== '') { return inceptus_process_normalize_language($TRP_LANGUAGE); } return inceptus_process_interface_language(); } /** * Fixed public-view labels are selected from the current page language. * Content coming from process.json is never translated by the plugin. */ function inceptus_process_frontend_labels($language = null) { $language = $language === null ? inceptus_process_page_language() : inceptus_process_normalize_language($language); $labels = [ 'en' => [ 'invalid_initial_view' => 'Invalid initial view. Use width, height or custom.', 'invalid_mode' => 'Invalid presentation mode. Use simulation or animation.', 'invalid_shortcode' => 'Use either a process code or a process group.', 'empty_group' => 'This group contains no available processes.', 'invalid_code' => 'Invalid or missing process code.', 'display_error' => 'The process could not be displayed.', 'start' => 'Start', 'select_start' => 'Select a start event', 'zoom_out' => 'Zoom out', 'zoom_in' => 'Zoom in', 'loading' => 'Loading process…', 'process_description' => 'Process description', 'process_elements' => 'Process elements', ], 'pl' => [ 'invalid_initial_view' => 'Nieprawidłowy widok początkowy. Użyj width, height lub custom.', 'invalid_mode' => 'Nieprawidłowy tryb prezentacji. Użyj simulation lub animation.', 'invalid_shortcode' => 'Podaj kod procesu albo kod grupy procesów.', 'empty_group' => 'Ta grupa nie zawiera dostępnych procesów.', 'invalid_code' => 'Nieprawidłowy lub brakujący kod procesu.', 'display_error' => 'Proces nie może zostać wyświetlony.', 'start' => 'Start', 'select_start' => 'Wybierz zdarzenie startowe', 'zoom_out' => 'Pomniejsz', 'zoom_in' => 'Powiększ', 'loading' => 'Ładowanie procesu…', 'process_description' => 'Opis procesu', 'process_elements' => 'Elementy procesu', ], 'fr' => [ 'invalid_initial_view' => 'Vue initiale non valide. Utilisez width, height ou custom.', 'invalid_mode' => 'Mode de présentation non valide. Utilisez simulation ou animation.', 'invalid_shortcode' => 'Indiquez soit un code de processus, soit un code de groupe de processus.', 'empty_group' => 'Ce groupe ne contient aucun processus disponible.', 'invalid_code' => 'Code de processus invalide ou manquant.', 'display_error' => 'Le processus n’a pas pu être affiché.', 'start' => 'Démarrer', 'select_start' => 'Sélectionnez un événement de début', 'zoom_out' => 'Zoom arrière', 'zoom_in' => 'Zoom avant', 'loading' => 'Chargement du processus…', 'process_description' => 'Description du processus', 'process_elements' => 'Éléments du processus', ], 'de' => [ 'invalid_initial_view' => 'Ungültige Anfangsansicht. Verwenden Sie width, height oder custom.', 'invalid_mode' => 'Ungültiger Präsentationsmodus. Verwenden Sie simulation oder animation.', 'invalid_shortcode' => 'Geben Sie entweder einen Prozesscode oder einen Prozessgruppencode an.', 'empty_group' => 'Diese Gruppe enthält keine verfügbaren Prozesse.', 'invalid_code' => 'Ungültiger oder fehlender Prozesscode.', 'display_error' => 'Der Prozess konnte nicht angezeigt werden.', 'start' => 'Start', 'select_start' => 'Startereignis auswählen', 'zoom_out' => 'Verkleinern', 'zoom_in' => 'Vergrößern', 'loading' => 'Prozess wird geladen…', 'process_description' => 'Prozessbeschreibung', 'process_elements' => 'Prozesselemente', ], 'es' => [ 'invalid_initial_view' => 'Vista inicial no válida. Use width, height o custom.', 'invalid_mode' => 'Modo de presentación no válido. Use simulation o animation.', 'invalid_shortcode' => 'Indique un código de proceso o un código de grupo de procesos.', 'empty_group' => 'Este grupo no contiene procesos disponibles.', 'invalid_code' => 'Código de proceso no válido o ausente.', 'display_error' => 'No se ha podido mostrar el proceso.', 'start' => 'Iniciar', 'select_start' => 'Seleccione un evento de inicio', 'zoom_out' => 'Alejar', 'zoom_in' => 'Acercar', 'loading' => 'Cargando proceso…', 'process_description' => 'Descripción del proceso', 'process_elements' => 'Elementos del proceso', ], ]; $selected = $labels[$language] ?? $labels['en']; /** * Allow wording to be customised without changing the plugin source. * The filter receives the selected labels and supported language code. */ return apply_filters('inceptus_process_frontend_labels', $selected, $language); } /** * Public storage for published process models. * * /wp-content/uploads/inceptus/processes/{code}/process.json */ function inceptus_process_storage() { $uploads = wp_upload_dir(); return [ 'dir' => trailingslashit($uploads['basedir']) . 'inceptus/processes', 'url' => trailingslashit($uploads['baseurl']) . 'inceptus/processes', ]; } function inceptus_process_ensure_storage() { $storage = inceptus_process_storage(); if (!is_dir($storage['dir'])) { wp_mkdir_p($storage['dir']); } return $storage; } function inceptus_process_activate() { inceptus_process_ensure_storage(); } register_activation_hook(__FILE__, 'inceptus_process_activate'); function inceptus_process_register_assets() { wp_register_style( 'inceptus-process', INCEPTUS_PROCESS_URL . 'assets/css/inceptus-process.css', [], INCEPTUS_PROCESS_VERSION ); wp_register_script( 'inceptus-published-viewer', INCEPTUS_PROCESS_URL . 'assets/js/inceptus.publish.js', [], INCEPTUS_PROCESS_VERSION, true ); wp_register_script( 'inceptus-process-animation', INCEPTUS_PROCESS_URL . 'assets/js/inceptus-process-animation.js', ['inceptus-published-viewer'], INCEPTUS_PROCESS_VERSION, true ); wp_register_script( 'inceptus-process-loader', INCEPTUS_PROCESS_URL . 'assets/js/inceptus-process-loader.js', ['inceptus-published-viewer', 'inceptus-process-animation'], INCEPTUS_PROCESS_VERSION, true ); wp_localize_script('inceptus-published-viewer', 'inceptusProcessLabels', inceptus_process_frontend_labels()); } add_action('wp_enqueue_scripts', 'inceptus_process_register_assets'); /** Load assets before Elementor inserts or refreshes a Shortcode widget. */ function inceptus_process_elementor_preview_assets() { inceptus_process_register_assets(); wp_enqueue_style('inceptus-process'); wp_enqueue_script('inceptus-process-loader'); } add_action('elementor/preview/enqueue_scripts', 'inceptus_process_elementor_preview_assets'); function inceptus_process_code_pattern() { return '/\A[a-z0-9][a-z0-9-]{0,79}\z/'; } function inceptus_process_valid_code($code) { return is_string($code) && (bool) preg_match(inceptus_process_code_pattern(), $code); } /** * process.json is the only supported publication format. */ function inceptus_process_decode_model($raw) { if (!is_string($raw) || trim($raw) === '') { return new WP_Error('empty_model', __('The process.json file is empty.', 'inceptus-process')); } $data = json_decode($raw, true); if (!is_array($data) || json_last_error() !== JSON_ERROR_NONE) { return new WP_Error('invalid_json', __('The file is not valid JSON.', 'inceptus-process')); } if (($data['format'] ?? null) !== 'Inceptus.PublishedProcess') { return new WP_Error('invalid_format', __('Unsupported process format. Expected Inceptus.PublishedProcess.', 'inceptus-process')); } if ((int) ($data['formatVersion'] ?? 0) !== 1) { return new WP_Error('invalid_version', __('Unsupported process format version.', 'inceptus-process')); } $code = $data['publication']['code'] ?? null; if (!inceptus_process_valid_code($code)) { return new WP_Error( 'invalid_publication_code', __('process.json must contain a valid publication.code: lowercase letters a-z, digits and hyphens.', 'inceptus-process') ); } if ( empty($data['presentation']) || !is_array($data['presentation']['nodes'] ?? null) || !is_array($data['presentation']['connectors'] ?? null) || !is_array($data['presentation']['items'] ?? null) || !is_array($data['presentation']['contentBounds'] ?? null) || empty($data['tokenGraph']) || !is_array($data['tokenGraph']['nodes'] ?? null) || empty($data['runtime']) ) { return new WP_Error('invalid_structure', __('The model does not contain the required published process structure.', 'inceptus-process')); } return $data; } function inceptus_process_model_path($code) { if (!inceptus_process_valid_code($code)) { return null; } $storage = inceptus_process_storage(); return trailingslashit($storage['dir']) . $code . '/process.json'; } function inceptus_process_model_url($code) { if (!inceptus_process_valid_code($code)) { return null; } $storage = inceptus_process_storage(); return trailingslashit($storage['url']) . rawurlencode($code) . '/process.json'; } function inceptus_process_load_model($code) { $path = inceptus_process_model_path($code); if (!$path || !is_readable($path)) { // translators: %s: Publication code. return new WP_Error('not_found', sprintf(__('Process "%s" was not found.', 'inceptus-process'), $code)); } $model = inceptus_process_decode_model(file_get_contents($path)); if (is_wp_error($model)) { return $model; } if (($model['publication']['code'] ?? '') !== $code) { return new WP_Error( 'code_mismatch', // translators: %s: Publication directory code. sprintf(__('The code in process.json does not match the process directory "%s".', 'inceptus-process'), $code) ); } return $model; } function inceptus_process_semantic_elements($model) { $elements = []; $seen = []; // presentation.nodes is the canonical publication source for element // labels and descriptions. publication.elements remains supported as a // backwards-compatible fallback for older publication files. if (!empty($model['presentation']['nodes']) && is_array($model['presentation']['nodes'])) { foreach ($model['presentation']['nodes'] as $node) { if (!is_array($node)) { continue; } $label = trim((string) ($node['label'] ?? '')); if ($label === '') { continue; } $id = trim((string) ($node['id'] ?? '')); $key = $id !== '' ? $id : $label; if (isset($seen[$key])) { continue; } $seen[$key] = true; $elements[] = [ 'id' => $id, 'label' => $label, 'description' => trim((string) ($node['description'] ?? '')), ]; } } elseif (!empty($model['publication']['elements']) && is_array($model['publication']['elements'])) { foreach ($model['publication']['elements'] as $element) { if (is_array($element)) { $label = trim((string) ($element['label'] ?? $element['name'] ?? '')); $description = trim((string) ($element['description'] ?? '')); $id = trim((string) ($element['id'] ?? '')); } else { $label = trim((string) $element); $description = ''; $id = ''; } if ($label === '') { continue; } $key = $id !== '' ? $id : $label; if (isset($seen[$key])) { continue; } $seen[$key] = true; $elements[] = [ 'id' => $id, 'label' => $label, 'description' => $description, ]; } } return $elements; } /** * Groups belong to WordPress, never to process.json. Reads tolerate missing or * malformed option data without writing it back or changing publications. */ function inceptus_process_groups() { $stored = get_option(INCEPTUS_PROCESS_GROUPS_OPTION, []); $groups = []; $assigned = []; if (!is_array($stored)) { return $groups; } foreach ($stored as $code => $group) { $code = (string) $code; if (!inceptus_process_valid_code($code) || !is_array($group) || !is_string($group['title'] ?? null)) { continue; } $title = sanitize_text_field($group['title']); if ($title === '') { continue; } $processes = []; foreach (is_array($group['processes'] ?? null) ? $group['processes'] : [] as $process_code) { if (inceptus_process_valid_code($process_code) && !isset($assigned[$process_code])) { $processes[] = $process_code; $assigned[$process_code] = true; } } $groups[$code] = ['title' => $title, 'processes' => $processes]; } return $groups; } function inceptus_process_group_code($value) { if (!is_string($value)) { return ''; } $value = strtolower(trim($value)); $code = sanitize_key($value); // Reject punctuation instead of silently turning it into another group code. return $code === $value && inceptus_process_valid_code($code) ? $code : ''; } function inceptus_process_save_groups($groups) { if (get_option(INCEPTUS_PROCESS_GROUPS_OPTION, []) === $groups) { return true; } if (!update_option(INCEPTUS_PROCESS_GROUPS_OPTION, $groups, false)) { return new WP_Error('groups_save_failed', __('Process groups could not be saved.', 'inceptus-process')); } return true; } function inceptus_process_save_group($code, $title, $create = false) { $title = is_string($title) ? sanitize_text_field($title) : ''; return inceptus_process_store_group_record($code, $title, $create); } /** Internal persistence boundary; callers sanitize the title exactly once per write. */ function inceptus_process_store_group_record($code, $title, $create = false) { $code = inceptus_process_group_code($code); if ($code === '') { return new WP_Error('invalid_group_code', __('Invalid group code. Use 1-80 lowercase letters, digits or hyphens, starting with a letter or digit.', 'inceptus-process')); } if (!is_string($title) || $title === '') { return new WP_Error('invalid_group_title', __('Enter a group title.', 'inceptus-process')); } $groups = inceptus_process_groups(); if ($create && isset($groups[$code])) { return new WP_Error('duplicate_group', __('This group code already exists.', 'inceptus-process')); } if (!$create && !isset($groups[$code])) { return new WP_Error('missing_group', __('Process group not found.', 'inceptus-process')); } $groups[$code] = ['title' => $title, 'processes' => $groups[$code]['processes'] ?? []]; return inceptus_process_save_groups($groups); } function inceptus_process_delete_group($code) { $code = inceptus_process_group_code($code); $groups = inceptus_process_groups(); if ($code === '' || !isset($groups[$code])) { return new WP_Error('missing_group', __('Process group not found.', 'inceptus-process')); } unset($groups[$code]); return inceptus_process_save_groups($groups); } function inceptus_process_groups_without_process($groups, $process_code) { foreach ($groups as &$group) { $group['processes'] = array_values(array_filter( $group['processes'], static fn($code) => $code !== $process_code )); } unset($group); return $groups; } function inceptus_process_assign_group($process_code, $group_code) { if (!inceptus_process_valid_code($process_code)) { return new WP_Error('invalid_code', __('Invalid process code.', 'inceptus-process')); } $model = inceptus_process_load_model($process_code); if (is_wp_error($model)) { return $model; } $groups = inceptus_process_groups(); $normalized_group = inceptus_process_group_code($group_code); if ($group_code !== '' && ($normalized_group === '' || !isset($groups[$normalized_group]))) { return new WP_Error('missing_group', __('Process group not found.', 'inceptus-process')); } $groups = inceptus_process_groups_without_process($groups, $process_code); if ($normalized_group !== '') { $groups[$normalized_group]['processes'][] = $process_code; } return inceptus_process_save_groups($groups); } function inceptus_process_group_members($group_code) { $group_code = inceptus_process_group_code($group_code); $groups = inceptus_process_groups(); $codes = []; foreach ($groups[$group_code]['processes'] ?? [] as $code) { // Loading also verifies JSON structure and publication.code, so a stale, // corrupt or mismatched file cannot participate in the random selection. if (!is_wp_error(inceptus_process_load_model($code))) { $codes[] = $code; } } return $codes; } function inceptus_process_shortcode($atts) { $atts = shortcode_atts( [ 'code' => '', 'group' => '', 'height' => 'auto', 'mode' => 'simulation', 'initial_view' => 'custom', ], $atts, 'inceptus_process' ); $page_language = inceptus_process_page_language(); $labels = inceptus_process_frontend_labels($page_language); if (!is_string($atts['initial_view']) && $atts['initial_view'] !== null) { return '
' . esc_html($labels['invalid_initial_view']) . '
'; } $initial_view = strtolower(trim((string) $atts['initial_view'])); $initial_view = $initial_view === '' ? 'custom' : $initial_view; if (!in_array($initial_view, ['width', 'height', 'custom'], true)) { return '' . esc_html($labels['invalid_initial_view']) . '
'; } if (!is_string($atts['mode']) && $atts['mode'] !== null) { return '' . esc_html($labels['invalid_mode']) . '
'; } $mode = strtolower(trim((string) $atts['mode'])); $mode = $mode === '' ? 'simulation' : $mode; if (!in_array($mode, ['simulation', 'animation'], true)) { return '' . esc_html($labels['invalid_mode']) . '
'; } if ((!is_scalar($atts['code']) && $atts['code'] !== null) || (!is_scalar($atts['group']) && $atts['group'] !== null)) { return '' . esc_html($labels['invalid_shortcode']) . '
'; } $code = strtolower(trim((string) $atts['code'])); $group = trim((string) $atts['group']); if ($code !== '' && $group !== '') { return '' . esc_html($labels['invalid_shortcode']) . '
'; } if ($group !== '') { $codes = inceptus_process_group_members($group); if (!$codes) { return '' . esc_html($labels['empty_group']) . '
'; } $code = $codes[count($codes) === 1 ? 0 : wp_rand(0, count($codes) - 1)]; } if (!inceptus_process_valid_code($code)) { return '' . esc_html($labels['invalid_code']) . '
'; } $model = inceptus_process_load_model($code); if (is_wp_error($model)) { return '' . esc_html($labels['display_error']) . '
'; } $height_value = strtolower(trim((string) $atts['height'])); $height_mode = 'auto'; $height = null; if ($height_value !== '' && $height_value !== 'auto' && is_numeric($height_value)) { $height = max(320, min(1400, (int) $height_value)); $height_mode = 'fixed'; } $content_bounds = $initial_view === 'custom' ? ($model['initialView']['visibleRegion'] ?? $model['presentation']['contentBounds'] ?? []) : ($model['presentation']['contentBounds'] ?? []); $content_width = isset($content_bounds['width']) && is_numeric($content_bounds['width']) ? max(1.0, (float) $content_bounds['width']) : 16.0; $content_height = isset($content_bounds['height']) && is_numeric($content_bounds['height']) ? max(1.0, (float) $content_bounds['height']) : 6.0; $aspect_ratio = $content_width . ' / ' . $content_height; $style_parts = [ '--inceptus-process-aspect-ratio: ' . $aspect_ratio, ]; if ($height_mode === 'fixed' && $height !== null) { $style_parts[] = '--inceptus-process-height: ' . $height . 'px'; } $style_attribute = implode('; ', $style_parts) . ';'; $title = trim((string) ($model['publication']['title'] ?? '')); if ($title === '') { $title = trim((string) ($model['source']['documentId'] ?? $code)); } $description = $mode === 'simulation' ? trim((string) ($model['publication']['description'] ?? '')) : ''; $elements = $mode === 'simulation' ? inceptus_process_semantic_elements($model) : []; $model_url = inceptus_process_model_url($code); $path = inceptus_process_model_path($code); $version = ($path && file_exists($path)) ? (string) filemtime($path) : INCEPTUS_PROCESS_VERSION; $model_url = add_query_arg('v', rawurlencode($version), $model_url); wp_enqueue_style('inceptus-process'); wp_enqueue_script('inceptus-published-viewer'); wp_enqueue_script('inceptus-process-loader'); ob_start(); ?>process.json file.', 'inceptus-process')); ?>
/wp-content/uploads/inceptus/processes/{code}/process.json.', 'inceptus-process')); ?>
publication.code. The title, description and revision also come from process.json and are not edited in WordPress.', 'inceptus-process')); ?>
|
|
|
|
Shortcode widget and use the publication code, e.g. [inceptus_process code="kompletacja-zamowienia"].', 'inceptus-process')); ?>
initial_view="width", initial_view="height" or initial_view="custom" (default, saved diagram parameters).', 'inceptus-process')); ?>
[inceptus_process code="kompletacja-zamowienia" height="800"].', 'inceptus-process')); ?>