.. _developer: ========= Developer ========= Architecture Overview ====================== The Kanban Workspaces extension follows modern TYPO3 v13 development practices, emphasizing clean architecture, dependency injection, and separation of concerns. **Key Principles:** * **TYPO3 v13 Compatibility** - Built using TYPO3 v13 features and APIs * **Dependency Injection** - All services use constructor injection * **Event Listeners** - Hooks into TYPO3 workspace events * **Service Container** - Automatic service registration and autowiring * **Modern PHP** - Requires PHP 8.2+ with type declarations * **PSR-4 Autoloading** - Organized class structure following PSR-4 standard Core Components =============== Controller ---------- **KanbanWorkspacesController** *Namespace:* ``WebVision\KanbanWorkspaces\Controller\KanbanWorkspacesController`` *Responsibility:* Main backend module controller handling HTTP requests and view rendering. Key Methods: .. php:method:: indexAction(): ResponseInterface Main action rendering the kanban board interface. Responsibilities: * Retrieves active workspace and page context * Fetches configured workspace stages * Prepares filter options (depth, language, stage) * Loads CSS and JavaScript assets * Renders ModuleTemplate with configured data **Returns:** ResponseInterface containing rendered module HTML **Access:** Requires workspace and page access .. php:method:: addAssets(): void Loads CSS and JavaScript modules required for the kanban board. **Loaded Assets:** * ``Resources/Public/Css/Styles.css`` - Kanban board styles * ``Resources/Public/Css/Fontawesome.min.css`` - Icon fonts * ``@web-vision/kanban-workspaces/App.js`` - Main JavaScript application * ``@typo3/workspaces/renderable/send-to-stage-form.js`` - Workspace form component .. php:method:: configureKanban(array $stageConfig): void Configures the JavaScript frontend with stage and filter data. **Parameters:** * ``$stageConfig`` - Array of stage configuration with page ID, workspace, stages, and filters **Example:** .. code-block:: php $this->configureKanban([ 'pageUid' => 123, 'workspaceId' => 1, 'stages' => $stageConfig, 'selectedLanguage' => 'en', 'selectedDepth' => 2, 'selectedStage' => -99, 'filters' => [ 'depth' => [...], 'language' => [...], 'stage' => [...], ], ]); .. php:method:: getSystemLanguages(int $pageId, string $selectedLanguage = ''): array Retrieves available system languages for the given page. **Parameters:** * ``$pageId`` - Page UID to get languages for * ``$selectedLanguage`` - Currently selected language (for marking as active) **Returns:** Array of language configuration objects with id, label, and flag **Example Return:** .. code-block:: php [ ['id' => 'all', 'label' => 'All', 'flag' => ''], ['id' => '0', 'label' => 'English', 'flag' => 'en'], ['id' => '1', 'label' => 'Deutsch', 'flag' => 'de'], ] Extension Configuration ======================= Extension Manager settings are read through the ``WebVision\KanbanWorkspaces\Configuration\EmConfiguration`` accessor. It is instantiated by the static ``create()`` factory, wired via the ``#[Autoconfigure(constructor: 'create')]`` attribute. The factory receives the ``ExtensionConfiguration`` service, reads the raw values and type-casts them, so the immutable value object only exposes properly typed settings: .. code-block:: php #[Autoconfigure(constructor: 'create')] final readonly class EmConfiguration { public function __construct( private bool $disableResetToEditingStage = false, ) {} public static function create(ExtensionConfiguration $extensionConfiguration): self { // read + cast the raw extension configuration values here } } .. php:method:: getDisableResetToEditingStage(): bool Returns whether resetting a workspace record to the editing stage (stage 0) after a field update should be prevented. .. php:method:: getCustomStageEditTitle(): string getCustomStageReadyToPublishTitle(): string getCustomStagePublishTitle(): string Return the configured custom titles for the default "Editing", "Ready to publish" and "Publish" stages. Each value is either a plain string, a ``LLL:EXT:...`` reference, or empty (use the TYPO3 default). Custom Stage Titles (StagesService override) ============================================ ``WebVision\KanbanWorkspaces\Service\StagesService`` extends the core ``TYPO3\CMS\Workspaces\Service\StagesService`` and overrides ``getStageTitle()`` to apply the ``customStage*Title`` options for the three default stages (``STAGE_EDIT_ID``, ``STAGE_PUBLISH_ID``, ``STAGE_PUBLISH_EXECUTE_ID``). When the matching option is empty, the parent implementation is called; a ``LLL:EXT:...`` value is resolved through the language service, any other non-empty value is used verbatim. The core service is replaced through Symfony DI using the ``#[AsAlias]`` attribute, so all consumers (and ``GeneralUtility::makeInstance()``) receive the extended service. The constructor is intentionally not overridden, so the parent dependencies keep being autowired through the inherited constructor. The ``EmConfiguration`` value object (a public service) is fetched on demand via ``GeneralUtility::makeInstance()`` inside ``getStageTitle()``: .. code-block:: php #[AsAlias(id: \TYPO3\CMS\Workspaces\Service\StagesService::class, public: true)] final readonly class StagesService extends \TYPO3\CMS\Workspaces\Service\StagesService { public function getStageTitle(int $stageId): string { $emConfiguration = GeneralUtility::makeInstance(EmConfiguration::class); // return the configured title (resolving LLL:) or fall back to parent } } DataHandler Hook ================ ``WebVision\KanbanWorkspaces\Hooks\PreventResetToEditingStageDataHandlerHook`` is registered on the ``processDatamapClass`` hook in ``ext_localconf.php`` and implements ``processDatamap_postProcessFieldArray``. It only acts when the operation is an ``update``, the affected table has a workspace-aware TCA schema (checked via ``TcaSchemaFactory``), and ``disableResetToEditingStage`` is enabled. It then removes ``t3ver_stage`` from the field array so TYPO3 does not reset the record to the editing stage when the record is persisted. Event Listeners =============== The extension registers its event listeners via PHP attributes (``#[AsEventListener]``) in ``Classes/EventListener/``: * **AssigneeEnrichmentListener** – listens to ``TYPO3\CMS\Workspaces\Event\AfterDataGeneratedForWorkspaceEvent`` and enriches the generated workspace records with their assignment information. * **AssigneeCleanupAfterPublishListener** – removes assignment rows for a record and workspace after the record is published. * **WorkspaceLanguageFallbackListener** – applies language fallback handling for workspace records. Frontend JavaScript Architecture ================================ The frontend is delivered as ES6 modules under ``Resources/Public/JavaScript/``, import-mapped to ``@web-vision/kanban-workspaces/`` (see ``Configuration/JavaScriptModules.php``). What used to be a single ``Workspace.js`` file is now a central orchestrator with focused, single-responsibility collaborators. Entry point and orchestrator ---------------------------- * ``App.js`` – entry point loaded as the module trigger. It enables horizontal drag-to-scroll (``core/HorizontalScroll.js``), instantiates ``WorkspaceBoard`` with the runtime options (API URLs, feature flags such as ``enableDragDrop`` / ``enableFilters`` / ``enableSearch``, ``mockData``), and registers the application-level event handlers — most importantly ``card:moved``, which POSTs the move to the workspace dispatch endpoint. * ``WorkspaceBoard.js`` – the ``WorkspaceBoard`` class. It owns the shared state (cards, stages, filters, selection, undo/redo history, current workspace, search query) and the lifecycle wiring, and delegates the real work to its collaborators. Collaborators are constructed with a back-reference to the board and reach shared state and each other through it (e.g. ``this.board.data``, ``this.board.renderer.renderBoard()``). Collaborators ------------- .. list-table:: :header-rows: 1 :widths: 22 28 50 * - Collaborator - File - Responsibility * - ``WorkspaceApi`` - ``data/WorkspaceApi.js`` - Thin AJAX transport over the workspace dispatch endpoint; hands raw responses to the transformer. Does not touch the DOM. * - ``DataTransformer`` - ``data/DataTransformer.js`` - Stateless conversion of raw TYPO3 workspace payloads into the card / comment / history / diff shapes consumed by the UI. * - ``BoardRenderer`` - ``ui/BoardRenderer.js`` - Generates all board markup (columns, cards, filter sidebar) and re-attaches drag-and-drop after a render. * - ``DragController`` - ``ui/DragController.js`` - Card drag-and-drop and column drop targets, drop placeholders, and starting the stage-transition workflow on drop. * - ``ModalController`` - ``ui/ModalController.js`` - Preview modal (summary / comments / history tabs) and the custom Send-to-Stage modal, including the revert / approve workflow. Holds the transient send-to-stage form state. * - ``FilterController`` - ``ui/FilterController.js`` - Search and filter interaction: keeps active filters and search query in sync, persists selections and triggers reloads / re-renders. * - ``CardActions`` - ``ui/CardActions.js`` - Per-card context-menu actions: preview, edit, open page version, assign a backend user, discard the version, and the move / revert primitives. * - ``EventEmitter`` - ``core/EventEmitter.js`` - Minimal pub/sub used for the board's custom events. * - ``initHorizontalScroll`` - ``core/HorizontalScroll.js`` - Drag-to-scroll panning of the horizontal board (ignores drags starting on a card or column). * - utilities - ``core/utils.js`` - Stateless helpers shared across components (HTML escaping, initials, date/icon formatting, toasts, loading indicators, ``debounce``). Events ------ ``WorkspaceBoard`` exposes ``on(event, handler)`` / ``off(event, handler)`` / ``emit(event, …)`` through its ``EventEmitter``. Subscribe to observe and extend behaviour. Notable events: * ``board:initialized``, ``board:rendered``, ``board:destroyed`` – board lifecycle. * ``data:loaded`` – workspace records fetched and transformed. * ``card:moved``, ``card:drop``, ``card:dragstart``, ``card:dragend``, ``card:click`` – card interactions. * ``filter:change``, ``filter:clear``, ``search:change``, ``search:clear`` – filter / search changes. * ``comment:added`` – a comment was added in the preview modal. Stage Checklist Feature (Implementation) ========================================= The Stage Checklist feature adds optional checklist items per workspace stage. When editors move a card to a stage (drag or Approve/Revert), the "Send to Stage" modal shows that stage's checklist at the top. The checklist is display-only (no checkboxes or submission). Database and TCA ---------------- * **Table:** ``tx_kanbanworkspaces_stage_checklist`` (see ``ext_tables.sql``). Columns: ``uid``, ``pid``, ``tstamp``, ``crdate``, ``deleted``, ``sorting``, ``stage`` (FK to ``sys_workspace_stage.uid``), ``title``. * **TCA:** ``Configuration/TCA/tx_kanbanworkspaces_stage_checklist.php`` defines the checklist table (label, sorting, delete, columns ``stage``, ``title``; record icon ``kanban-workspaces-stage-checklist``). * **TCA override:** ``Configuration/TCA/Overrides/sys_workspace_stage.php`` adds ``checklist_items`` inline field to ``sys_workspace_stage`` (after ``responsible_persons``). Inline: sortable, ``expandSingle``, no sync/localization links. Icons ~~~~~ * **File:** ``Configuration/Icons.php`` registers ``kanban-workspaces-stage-checklist`` with ``SvgIconProvider``, source ``EXT:kanban_workspaces/Resources/Public/Icons/checklist.svg``. Used for TCA record icons and for each checklist row in the Send to Stage modal. Controller ~~~~~~~~~~ * In ``KanbanWorkspacesController::indexAction()``, each stage in the config is enriched with a ``checklist`` array. For each stage with ``uid >= 1``, ``getChecklistForStage($stageUid)`` is called. * ``getChecklistForStage(int $stageUid): array`` – queries ``tx_kanbanworkspaces_stage_checklist`` for the given ``stage``, ``deleted = 0``, ordered by ``sorting``. Deduplicates by ``uid`` and by ``title``. Returns ``[ ['id' => uid, 'title' => title], ... ]``. The array is part of ``WorkspaceConfig`` (JSON) passed to the frontend. Frontend (Template, JavaScript, CSS) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * **Template:** Send to Stage modal (``#sendToStageModal``) body order: ``#stageInfoBanner``, then ``#stageChecklistSection`` / ``#stageChecklistList``, then recipients, additional recipients, comments. * **JavaScript:** ``openSendToStageModal(formData, context)`` in ``ui/ModalController.js`` reads ``context.targetStage?.checklist``, deduplicates by ``id``/``title``, renders ``