From 273a2437e1c05540762a0d41ecec6c2fb6c4c82a Mon Sep 17 00:00:00 2001 From: Lee Rowlands <lee.rowlands@previousnext.com.au> Date: Tue, 14 Jan 2025 10:21:41 +1000 Subject: [PATCH 01/30] Issue #3498419: Support PHP defined component lists --- dictionary.txt | 4 + openapi.yml | 520 ++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 455 insertions(+), 69 deletions(-) diff --git a/dictionary.txt b/dictionary.txt index c1402f512b..579baf9f7f 100644 --- a/dictionary.txt +++ b/dictionary.txt @@ -59,3 +59,7 @@ cebe inwidget uuidv Linkit +Modulz +Pilcrow +Vercel + diff --git a/openapi.yml b/openapi.yml index 2aa4d5d3b1..74903124f6 100644 --- a/openapi.yml +++ b/openapi.yml @@ -159,76 +159,451 @@ paths: examples: singleAvailableComponent: value: - 'sdc_test:my-cta': - id: 'sdc_test:my-cta' - name: Call to Action - source: Module component - category: Buttons - transforms: - text: - extractMainPropertyName: - mainPropertyName: value - href: - extractMainPropertyName: - mainPropertyName: value - target: - passThrough: { } - metadata: - path: >- - core/modules/system/tests/modules/sdc_test/components/my-cta - documentation: >- - No documentation found. Add a README.md in your - component directory. - status: stable - machineName: my-cta + lists: + components: + label: Components + position: library + icon: Component1 + weight: 1 + global_components: + label: Dynamic Components + position: header + icon: File + weight: 2 + components: + 'sdc_test:my-cta': + id: 'sdc_test:my-cta' name: Call to Action - group: All Components + source: Module component + category: Buttons + transforms: + text: + extractMainPropertyName: + mainPropertyName: value + href: + extractMainPropertyName: + mainPropertyName: value + target: + passThrough: { } + metadata: + path: >- + core/modules/system/tests/modules/sdc_test/components/my-cta + documentation: >- + No documentation found. Add a README.md in your + component directory. + status: stable + machineName: my-cta + name: Call to Action + group: All Components + schema: + type: object + required: + - text + properties: + text: + type: + - string + - object + title: Title + description: The title for the cta + examples: + - Press + - Submit now + href: + type: + - string + - object + title: URL + format: uri + target: + type: + - string + - object + title: Target + enum: + - '' + - _blank + attributes: + type: + - Drupal\Core\Template\Attribute + - object + name: Attributes + additionalProperties: false + description: Call to action link. + mandatorySchemas: true + slots: [] + schema: + type: object + required: + - components + - lists + properties: + components: + title: 'Available components' + type: object + minProperties: 1 + # @todo Validate the property keys match the + # '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$' pattern in + # https://drupal.org/i/3506543. + # @see \Drupal\Core\Plugin\Component::$machineName + additionalProperties: + $ref: '#/components/schemas/Component' + lists: + type: object + minProperties: 1 + patternProperties: + '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$': schema: type: object required: - - text + - label + - position + - icon + - weight properties: - text: - type: - - string - - object - title: Title - description: The title for the cta - examples: - - Press - - Submit now - href: - type: - - string - - object - title: URL - format: uri - target: - type: - - string - - object - title: Target + label: + type: string + title: Component list label + example: Components + position: + type: string + title: Where the component list should be shown + example: library enum: - - '' - - _blank - attributes: - type: - - Drupal\Core\Template\Attribute - - object - name: Attributes - additionalProperties: false - description: Call to action link. - mandatorySchemas: true - slots: [] - schema: - type: object - minProperties: 1 - # @todo Validate the property keys match the - # '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$' pattern in - # https://drupal.org/i/3506543. - # @see \Drupal\Core\Plugin\Component::$machineName - additionalProperties: - $ref: '#/components/schemas/Component' + # This may be extended as the UI evolves. + - library + - header + weight: + type: integer + title: List weight + example: -1 + description: > + Weight of lists relative to other lists in the same position. + Larger numbers appear later in the order. + icon: + type: string + title: Icon to use for list + example: Component1 + enum: + # Taken from https://www.radix-ui.com/icons + - Accessibility + - ActivityLog + - AlignBaseline + - AlignBottom + - AlignCenterHorizontally + - AlignCenterVertically + - AlignLeft + - AlignRight + - AlignTop + - AllSides + - Angle + - Archive + - ArrowBottomLeft + - ArrowBottomRight + - ArrowDown + - ArrowLeft + - ArrowRight + - ArrowTopLeft + - ArrowTopRight + - ArrowUp + - AspectRatio + - Avatar + - Backpack + - Badge + - BarChart + - Bell + - BlendingMode + - Bookmark + - BookmarkFilled + - BorderAll + - BorderBottom + - BorderDashed + - BorderDotted + - BorderLeft + - BorderNone + - BorderRight + - BorderSolid + - BorderSplit + - BorderStyle + - BorderTop + - BorderWidth + - Box + - BoxModel + - Button + - Calendar + - Camera + - CardStack + - CardStackMinus + - CardStackPlus + - CaretDown + - CaretLeft + - CaretRight + - CaretSort + - CaretUp + - ChatBubble + - Check + - CheckCircled + - Checkbox + - ChevronDown + - ChevronLeft + - ChevronRight + - ChevronUp + - Circle + - CircleBackslash + - Clipboard + - ClipboardCopy + - Clock + - Code + - CodeSandboxLogo + - ColorWheel + - ColumnSpacing + - Columns + - Commit + - Component1 + - Component2 + - ComponentBoolean + - ComponentInstance + - ComponentNone + - ComponentPlaceholder + - Container + - Cookie + - Copy + - CornerBottomLeft + - CornerBottomRight + - CornerTopLeft + - CornerTopRight + - Corners + - CountdownTimer + - CounterClockwiseClock + - Crop + - Cross1 + - Cross2 + - CrossCircled + - Crosshair1 + - Crosshair2 + - CrumpledPaper + - Cube + - CursorArrow + - CursorText + - Dash + - Dashboard + - Desktop + - Dimensions + - Disc + - DiscordLogo + - DividerHorizontal + - DividerVertical + - Dot + - DotFilled + - DotsHorizontal + - DotsVertical + - DoubleArrowDown + - DoubleArrowLeft + - DoubleArrowRight + - DoubleArrowUp + - Download + - DragHandleDots1 + - DragHandleDots2 + - DragHandleHorizontal + - DragHandleVertical + - DrawingPin + - DrawingPinFilled + - DropdownMenu + - Enter + - EnterFullScreen + - EnvelopeClosed + - EnvelopeOpen + - Eraser + - ExclamationTriangle + - Exit + - ExitFullScreen + - ExternalLink + - EyeClosed + - EyeNone + - EyeOpen + - Face + - FigmaLogo + - File + - FileMinus + - FilePlus + - FileText + - FontBold + - FontFamily + - FontItalic + - FontRoman + - FontSize + - FontStyle + - Frame + - FramerLogo + - Gear + - GitHubLogo + - Globe + - Grid + - Group + - Half1 + - Half2 + - HamburgerMenu + - Hand + - Heading + - Heart + - HeartFilled + - Height + - HobbyKnife + - Home + - IconJarLogo + - IdCard + - Image + - InfoCircled + - Input + - InstagramLogo + - Keyboard + - LapTimer + - Laptop + - Layers + - Layout + - LetterCaseCapitalize + - LetterCaseLowercase + - LetterCaseToggle + - LetterCaseUppercase + - LetterSpacing + - LightningBolt + - LineHeight + - Link1 + - Link2 + - LinkBreak1 + - LinkBreak2 + - LinkNone1 + - LinkNone2 + - LinkedInLogo + - ListBullet + - LockClosed + - LockOpen1 + - LockOpen2 + - Loop + - MagicWand + - MagnifyingGlass + - Margin + - MaskOff + - MaskOn + - Minus + - MinusCircled + - Mix + - MixerHorizontal + - MixerVertical + - Mobile + - ModulzLogo + - Moon + - Move + - NotionLogo + - Opacity + - OpenInNewWindow + - Overline + - Padding + - PaperPlane + - Pause + - Pencil1 + - Pencil2 + - Person + - PieChart + - Pilcrow + - PinBottom + - PinLeft + - PinRight + - PinTop + - Play + - Plus + - PlusCircled + - QuestionMark + - QuestionMarkCircled + - Quote + - Radiobutton + - Reader + - Reload + - Reset + - Resume + - Rocket + - RotateCounterClockwise + - RowSpacing + - Rows + - RulerHorizontal + - RulerSquare + - Scissors + - Section + - SewingPin + - SewingPinFilled + - Shadow + - ShadowInner + - ShadowNone + - ShadowOuter + - Share1 + - Share2 + - Shuffle + - Size + - SketchLogo + - Slash + - Slider + - SpaceBetweenHorizontally + - SpaceBetweenVertically + - SpaceEvenlyHorizontally + - SpaceEvenlyVertically + - SpeakerLoud + - SpeakerModerate + - SpeakerOff + - SpeakerQuiet + - Square + - Stack + - Star + - StarFilled + - StitchesLogo + - Stop + - Stopwatch + - StretchHorizontally + - StretchVertically + - Strikethrough + - Sun + - Switch + - Symbol + - Table + - Target + - Text + - TextAlignBottom + - TextAlignCenter + - TextAlignJustify + - TextAlignLeft + - TextAlignMiddle + - TextAlignRight + - TextAlignTop + - TextNone + - ThickArrowDown + - ThickArrowLeft + - ThickArrowRight + - ThickArrowUp + - Timer + - Tokens + - TrackNext + - TrackPrevious + - Transform + - TransparencyGrid + - Trash + - TriangleDown + - TriangleLeft + - TriangleRight + - TriangleUp + - TwitterLogo + - Underline + - Update + - Upload + - Value + - ValueNone + - VercelLogo + - Video + - ViewGrid + - ViewHorizontal + - ViewNone + - ViewVertical + - Width + - ZoomIn + - ZoomOut /xb/api/config/js_component: get: description: Collection of Code Components @@ -1089,6 +1464,7 @@ components: - id - category - default_markup + - list properties: name: type: string @@ -1136,6 +1512,12 @@ components: dynamic_prop_source_candidates: type: object description: the DynamicPropSources that match each + list: + type: string + label: Component list + description: > + The list in which this component should appear. A list is used to group components in the UI. + additionalProperties: false CodeComponent: title: Code Component type: object @@ -1287,11 +1669,11 @@ components: - uri - id examples: - - Logged in user: - name: 'Garrett Bobby Ferguson, Jr' - avatar: /sites/default/files/user-pictures/garrett.jpg - uri: /user/12 - id: 12 + Logged in user: + name: 'Garrett Bobby Ferguson, Jr' + avatar: /sites/default/files/user-pictures/garrett.jpg + uri: /user/12 + id: 12 properties: name: type: string -- GitLab From b2e8f046f5779bbd35a38809758f53878cb70c0e Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <felix.mazeikis@acquia.com> Date: Thu, 20 Feb 2025 13:49:08 +0000 Subject: [PATCH 02/30] Revert openapi changes --- openapi.yml | 520 +++++++--------------------------------------------- 1 file changed, 69 insertions(+), 451 deletions(-) diff --git a/openapi.yml b/openapi.yml index 74903124f6..2aa4d5d3b1 100644 --- a/openapi.yml +++ b/openapi.yml @@ -159,451 +159,76 @@ paths: examples: singleAvailableComponent: value: - lists: - components: - label: Components - position: library - icon: Component1 - weight: 1 - global_components: - label: Dynamic Components - position: header - icon: File - weight: 2 - components: - 'sdc_test:my-cta': - id: 'sdc_test:my-cta' + 'sdc_test:my-cta': + id: 'sdc_test:my-cta' + name: Call to Action + source: Module component + category: Buttons + transforms: + text: + extractMainPropertyName: + mainPropertyName: value + href: + extractMainPropertyName: + mainPropertyName: value + target: + passThrough: { } + metadata: + path: >- + core/modules/system/tests/modules/sdc_test/components/my-cta + documentation: >- + No documentation found. Add a README.md in your + component directory. + status: stable + machineName: my-cta name: Call to Action - source: Module component - category: Buttons - transforms: - text: - extractMainPropertyName: - mainPropertyName: value - href: - extractMainPropertyName: - mainPropertyName: value - target: - passThrough: { } - metadata: - path: >- - core/modules/system/tests/modules/sdc_test/components/my-cta - documentation: >- - No documentation found. Add a README.md in your - component directory. - status: stable - machineName: my-cta - name: Call to Action - group: All Components - schema: - type: object - required: - - text - properties: - text: - type: - - string - - object - title: Title - description: The title for the cta - examples: - - Press - - Submit now - href: - type: - - string - - object - title: URL - format: uri - target: - type: - - string - - object - title: Target - enum: - - '' - - _blank - attributes: - type: - - Drupal\Core\Template\Attribute - - object - name: Attributes - additionalProperties: false - description: Call to action link. - mandatorySchemas: true - slots: [] - schema: - type: object - required: - - components - - lists - properties: - components: - title: 'Available components' - type: object - minProperties: 1 - # @todo Validate the property keys match the - # '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$' pattern in - # https://drupal.org/i/3506543. - # @see \Drupal\Core\Plugin\Component::$machineName - additionalProperties: - $ref: '#/components/schemas/Component' - lists: - type: object - minProperties: 1 - patternProperties: - '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$': + group: All Components schema: type: object required: - - label - - position - - icon - - weight + - text properties: - label: - type: string - title: Component list label - example: Components - position: - type: string - title: Where the component list should be shown - example: library - enum: - # This may be extended as the UI evolves. - - library - - header - weight: - type: integer - title: List weight - example: -1 - description: > - Weight of lists relative to other lists in the same position. - Larger numbers appear later in the order. - icon: - type: string - title: Icon to use for list - example: Component1 + text: + type: + - string + - object + title: Title + description: The title for the cta + examples: + - Press + - Submit now + href: + type: + - string + - object + title: URL + format: uri + target: + type: + - string + - object + title: Target enum: - # Taken from https://www.radix-ui.com/icons - - Accessibility - - ActivityLog - - AlignBaseline - - AlignBottom - - AlignCenterHorizontally - - AlignCenterVertically - - AlignLeft - - AlignRight - - AlignTop - - AllSides - - Angle - - Archive - - ArrowBottomLeft - - ArrowBottomRight - - ArrowDown - - ArrowLeft - - ArrowRight - - ArrowTopLeft - - ArrowTopRight - - ArrowUp - - AspectRatio - - Avatar - - Backpack - - Badge - - BarChart - - Bell - - BlendingMode - - Bookmark - - BookmarkFilled - - BorderAll - - BorderBottom - - BorderDashed - - BorderDotted - - BorderLeft - - BorderNone - - BorderRight - - BorderSolid - - BorderSplit - - BorderStyle - - BorderTop - - BorderWidth - - Box - - BoxModel - - Button - - Calendar - - Camera - - CardStack - - CardStackMinus - - CardStackPlus - - CaretDown - - CaretLeft - - CaretRight - - CaretSort - - CaretUp - - ChatBubble - - Check - - CheckCircled - - Checkbox - - ChevronDown - - ChevronLeft - - ChevronRight - - ChevronUp - - Circle - - CircleBackslash - - Clipboard - - ClipboardCopy - - Clock - - Code - - CodeSandboxLogo - - ColorWheel - - ColumnSpacing - - Columns - - Commit - - Component1 - - Component2 - - ComponentBoolean - - ComponentInstance - - ComponentNone - - ComponentPlaceholder - - Container - - Cookie - - Copy - - CornerBottomLeft - - CornerBottomRight - - CornerTopLeft - - CornerTopRight - - Corners - - CountdownTimer - - CounterClockwiseClock - - Crop - - Cross1 - - Cross2 - - CrossCircled - - Crosshair1 - - Crosshair2 - - CrumpledPaper - - Cube - - CursorArrow - - CursorText - - Dash - - Dashboard - - Desktop - - Dimensions - - Disc - - DiscordLogo - - DividerHorizontal - - DividerVertical - - Dot - - DotFilled - - DotsHorizontal - - DotsVertical - - DoubleArrowDown - - DoubleArrowLeft - - DoubleArrowRight - - DoubleArrowUp - - Download - - DragHandleDots1 - - DragHandleDots2 - - DragHandleHorizontal - - DragHandleVertical - - DrawingPin - - DrawingPinFilled - - DropdownMenu - - Enter - - EnterFullScreen - - EnvelopeClosed - - EnvelopeOpen - - Eraser - - ExclamationTriangle - - Exit - - ExitFullScreen - - ExternalLink - - EyeClosed - - EyeNone - - EyeOpen - - Face - - FigmaLogo - - File - - FileMinus - - FilePlus - - FileText - - FontBold - - FontFamily - - FontItalic - - FontRoman - - FontSize - - FontStyle - - Frame - - FramerLogo - - Gear - - GitHubLogo - - Globe - - Grid - - Group - - Half1 - - Half2 - - HamburgerMenu - - Hand - - Heading - - Heart - - HeartFilled - - Height - - HobbyKnife - - Home - - IconJarLogo - - IdCard - - Image - - InfoCircled - - Input - - InstagramLogo - - Keyboard - - LapTimer - - Laptop - - Layers - - Layout - - LetterCaseCapitalize - - LetterCaseLowercase - - LetterCaseToggle - - LetterCaseUppercase - - LetterSpacing - - LightningBolt - - LineHeight - - Link1 - - Link2 - - LinkBreak1 - - LinkBreak2 - - LinkNone1 - - LinkNone2 - - LinkedInLogo - - ListBullet - - LockClosed - - LockOpen1 - - LockOpen2 - - Loop - - MagicWand - - MagnifyingGlass - - Margin - - MaskOff - - MaskOn - - Minus - - MinusCircled - - Mix - - MixerHorizontal - - MixerVertical - - Mobile - - ModulzLogo - - Moon - - Move - - NotionLogo - - Opacity - - OpenInNewWindow - - Overline - - Padding - - PaperPlane - - Pause - - Pencil1 - - Pencil2 - - Person - - PieChart - - Pilcrow - - PinBottom - - PinLeft - - PinRight - - PinTop - - Play - - Plus - - PlusCircled - - QuestionMark - - QuestionMarkCircled - - Quote - - Radiobutton - - Reader - - Reload - - Reset - - Resume - - Rocket - - RotateCounterClockwise - - RowSpacing - - Rows - - RulerHorizontal - - RulerSquare - - Scissors - - Section - - SewingPin - - SewingPinFilled - - Shadow - - ShadowInner - - ShadowNone - - ShadowOuter - - Share1 - - Share2 - - Shuffle - - Size - - SketchLogo - - Slash - - Slider - - SpaceBetweenHorizontally - - SpaceBetweenVertically - - SpaceEvenlyHorizontally - - SpaceEvenlyVertically - - SpeakerLoud - - SpeakerModerate - - SpeakerOff - - SpeakerQuiet - - Square - - Stack - - Star - - StarFilled - - StitchesLogo - - Stop - - Stopwatch - - StretchHorizontally - - StretchVertically - - Strikethrough - - Sun - - Switch - - Symbol - - Table - - Target - - Text - - TextAlignBottom - - TextAlignCenter - - TextAlignJustify - - TextAlignLeft - - TextAlignMiddle - - TextAlignRight - - TextAlignTop - - TextNone - - ThickArrowDown - - ThickArrowLeft - - ThickArrowRight - - ThickArrowUp - - Timer - - Tokens - - TrackNext - - TrackPrevious - - Transform - - TransparencyGrid - - Trash - - TriangleDown - - TriangleLeft - - TriangleRight - - TriangleUp - - TwitterLogo - - Underline - - Update - - Upload - - Value - - ValueNone - - VercelLogo - - Video - - ViewGrid - - ViewHorizontal - - ViewNone - - ViewVertical - - Width - - ZoomIn - - ZoomOut + - '' + - _blank + attributes: + type: + - Drupal\Core\Template\Attribute + - object + name: Attributes + additionalProperties: false + description: Call to action link. + mandatorySchemas: true + slots: [] + schema: + type: object + minProperties: 1 + # @todo Validate the property keys match the + # '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$' pattern in + # https://drupal.org/i/3506543. + # @see \Drupal\Core\Plugin\Component::$machineName + additionalProperties: + $ref: '#/components/schemas/Component' /xb/api/config/js_component: get: description: Collection of Code Components @@ -1464,7 +1089,6 @@ components: - id - category - default_markup - - list properties: name: type: string @@ -1512,12 +1136,6 @@ components: dynamic_prop_source_candidates: type: object description: the DynamicPropSources that match each - list: - type: string - label: Component list - description: > - The list in which this component should appear. A list is used to group components in the UI. - additionalProperties: false CodeComponent: title: Code Component type: object @@ -1669,11 +1287,11 @@ components: - uri - id examples: - Logged in user: - name: 'Garrett Bobby Ferguson, Jr' - avatar: /sites/default/files/user-pictures/garrett.jpg - uri: /user/12 - id: 12 + - Logged in user: + name: 'Garrett Bobby Ferguson, Jr' + avatar: /sites/default/files/user-pictures/garrett.jpg + uri: /user/12 + id: 12 properties: name: type: string -- GitLab From b2f48f70850068c807f4d058ad22626c7f4f0a8d Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <felix.mazeikis@acquia.com> Date: Thu, 20 Feb 2025 13:51:46 +0000 Subject: [PATCH 03/30] Add libraries to openapi yml, source plugins, component entity and controller --- openapi.yml | 177 +++++++++++------- .../ComponentSourceInterface.php | 7 + src/Controller/ApiConfigControllers.php | 21 ++- src/Entity/Component.php | 9 +- .../ComponentSource/BlockComponent.php | 8 + .../ComponentSource/JsComponent.php | 8 + .../SingleDirectoryComponent.php | 17 ++ 7 files changed, 171 insertions(+), 76 deletions(-) diff --git a/openapi.yml b/openapi.yml index 2aa4d5d3b1..da1dfadc78 100644 --- a/openapi.yml +++ b/openapi.yml @@ -159,76 +159,100 @@ paths: examples: singleAvailableComponent: value: - 'sdc_test:my-cta': - id: 'sdc_test:my-cta' - name: Call to Action - source: Module component - category: Buttons - transforms: - text: - extractMainPropertyName: - mainPropertyName: value - href: - extractMainPropertyName: - mainPropertyName: value - target: - passThrough: { } - metadata: - path: >- - core/modules/system/tests/modules/sdc_test/components/my-cta - documentation: >- - No documentation found. Add a README.md in your - component directory. - status: stable - machineName: my-cta + libraries: + # Provided by Experience Builder + #elements: + #label: Elements + # Provided by Modules + extension_components: + label: Block Components + dynamic_components: + # SDCs from the Active Theme + JavaScript Code Components + label: Active Theme Components and Code Components + primary_components: + label: Components? + components: + 'sdc_test:my-cta': + id: 'sdc_test:my-cta' name: Call to Action - group: All Components - schema: - type: object - required: - - text - properties: - text: - type: - - string - - object - title: Title - description: The title for the cta - examples: - - Press - - Submit now - href: - type: - - string - - object - title: URL - format: uri - target: - type: - - string - - object - title: Target - enum: - - '' - - _blank - attributes: - type: - - Drupal\Core\Template\Attribute - - object - name: Attributes - additionalProperties: false - description: Call to action link. - mandatorySchemas: true - slots: [] + source: Module component + category: Buttons + library: extension_components + transforms: + text: + extractMainPropertyName: + mainPropertyName: value + href: + extractMainPropertyName: + mainPropertyName: value + target: + passThrough: { } + metadata: + path: >- + core/modules/system/tests/modules/sdc_test/components/my-cta + documentation: >- + No documentation found. Add a README.md in your + component directory. + status: stable + machineName: my-cta + name: Call to Action + group: All Components + schema: + type: object + required: + - text + properties: + text: + type: + - string + - object + title: Title + description: The title for the cta + examples: + - Press + - Submit now + href: + type: + - string + - object + title: URL + format: uri + target: + type: + - string + - object + title: Target + enum: + - '' + - _blank + attributes: + type: + - Drupal\Core\Template\Attribute + - object + name: Attributes + additionalProperties: false + description: Call to action link. + mandatorySchemas: true + slots: [] schema: type: object - minProperties: 1 - # @todo Validate the property keys match the - # '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$' pattern in - # https://drupal.org/i/3506543. - # @see \Drupal\Core\Plugin\Component::$machineName - additionalProperties: - $ref: '#/components/schemas/Component' + properties: + libraries: + extension_components: + $ref: '#/components/schemas/Library' + dynamic_components: + $ref: '#/components/schemas/Library' + primary_components: + $ref: '#/components/schemas/Library' + components: + type: object + minProperties: 1 + # @todo Validate the property keys match the + # '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$' pattern in + # https://drupal.org/i/3506543. + # @see \Drupal\Core\Plugin\Component::$machineName + additionalProperties: + $ref: '#/components/schemas/Component' /xb/api/config/js_component: get: description: Collection of Code Components @@ -1089,6 +1113,7 @@ components: - id - category - default_markup + - library properties: name: type: string @@ -1099,6 +1124,18 @@ components: category: type: string description: The component category + library: + type: string + description: Library ID + enum: + # Provided by Experience Builder + - elements + # Provided by Modules + - extension_components + # Blocks + - dynamic_components + # SDCs from the Active Theme + JavaScript Code Components + - primary_components transforms: type: object description: Transform configuration @@ -1263,6 +1300,12 @@ components: type: boolean isPublished: type: boolean + Library: + title: Library + type: object + properties: + label: + type: string AutoSaveEntry: title: Auto Save Entry type: object diff --git a/src/ComponentSource/ComponentSourceInterface.php b/src/ComponentSource/ComponentSourceInterface.php index 7e72bafdf9..9e3873da96 100644 --- a/src/ComponentSource/ComponentSourceInterface.php +++ b/src/ComponentSource/ComponentSourceInterface.php @@ -205,4 +205,11 @@ interface ComponentSourceInterface extends PluginInspectionInterface, Derivative */ public function checkRequirements(): void; + /** + * Returns Library ID to be used for UI placement in FE. + * + * @return string + */ + public function getLibraryId(): string; + } diff --git a/src/Controller/ApiConfigControllers.php b/src/Controller/ApiConfigControllers.php index 93b303a43f..5e69c9b054 100644 --- a/src/Controller/ApiConfigControllers.php +++ b/src/Controller/ApiConfigControllers.php @@ -114,8 +114,27 @@ final class ApiConfigControllers extends ApiControllerBase { if ($max_age !== Cache::PERMANENT) { $normalizations_cacheability->setCacheMaxAge(max($max_age, 3600)); } + if ($xb_config_entity_type_id === 'component') { + $response = [ + 'libraries' => [ + 'extension_components' => [ + 'label' => 'Block Components', + ], + 'dynamic_components' => [ + 'label' => 'Active Theme Components and Code Components', + ], + 'primary_components' => [ + 'label' => 'Components', + ], + ], + 'components' => $normalizations, + ]; + } + else { + $response = $normalizations; + } - return (new CacheableJsonResponse($normalizations)) + return (new CacheableJsonResponse($response)) ->addCacheableDependency($query_cacheability) ->addCacheableDependency($normalizations_cacheability); } diff --git a/src/Entity/Component.php b/src/Entity/Component.php index 2ab3d8bced..10a7ab4981 100644 --- a/src/Entity/Component.php +++ b/src/Entity/Component.php @@ -206,18 +206,11 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb $component_config_entity_uuid = $this->uuid(); $build['#prefix'] = Markup::create("<!-- xb-start-$component_config_entity_uuid -->"); $build['#suffix'] = Markup::create("<!-- xb-end-$component_config_entity_uuid -->"); - - $info += [ - 'id' => $this->id(), - 'name' => (string) $this->label(), - 'category' => (string) $this->getCategory(), - 'source' => (string) $this->getComponentSource()->getPluginDefinition()['label'], - ]; - return ClientSideRepresentation::create( values: $info + [ 'id' => $this->id(), 'name' => (string) $this->label(), + 'library' => $this->getComponentSource()->getLibraryId(), 'category' => (string) $this->getCategory(), 'source' => (string) $this->getComponentSource()->getPluginDefinition()['label'], ], diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php index b1a3de55a1..ea7a9943a1 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php @@ -47,6 +47,7 @@ final class BlockComponent extends ComponentSourceBase implements ContainerFacto public const SOURCE_PLUGIN_ID = 'block'; public const EXPLICIT_INPUT_NAME = 'settings'; + public const LIBRARY_ID = 'extension_components'; /** * Constructs a new BlockComponent. @@ -358,4 +359,11 @@ final class BlockComponent extends ComponentSourceBase implements ContainerFacto // @todo Move logic from experience_builder_block_alter here in https://www.drupal.org/project/experience_builder/issues/3491032 } + /** + * @return string + */ + public function getLibraryId(): string { + return self::LIBRARY_ID; + } + } diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php index 80772366eb..8a9b2a3f6a 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php @@ -25,6 +25,7 @@ use Drupal\experience_builder\Entity\JavaScriptComponent; final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase { public const SOURCE_PLUGIN_ID = 'js'; + public const LIBRARY_ID = 'primary_components'; /** * {@inheritdoc} @@ -226,4 +227,11 @@ final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase ); } + /** + * @return string + */ + public function getLibraryId(): string { + return self::LIBRARY_ID; + } + } diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php index 0ce82f540a..471233ef28 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php @@ -32,6 +32,8 @@ use Symfony\Component\Filesystem\Path; final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxComponentSourceBase implements UrlRewriteInterface { public const SOURCE_PLUGIN_ID = 'sdc'; + public const LIBRARY_ID_THEME = 'extension_components'; + public const LIBRARY_ID_EXTENSIONS = 'primary_components'; /** * Constructs a new SingleDirectoryComponent. @@ -310,4 +312,19 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon return \base_path() . $path; } + /** + * @return string + */ + public function getLibraryId(): string { + $base_id = $this->getComponentPlugin()->getBaseId(); + if ($this->themeHandler->themeExists($base_id)) { + return self::LIBRARY_ID_THEME; + } + + if ($this->moduleHandler->moduleExists($base_id)) { + return self::LIBRARY_ID_EXTENSIONS; + } + throw new \InvalidArgumentException("Invalid component provider id '{$base_id}'"); + } + } -- GitLab From 753ae98ed88e718c1e2ef596ef1f5c047a1c539e Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <felix.mazeikis@acquia.com> Date: Thu, 20 Feb 2025 13:54:41 +0000 Subject: [PATCH 04/30] Revert dictionary changes --- dictionary.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dictionary.txt b/dictionary.txt index 579baf9f7f..c1402f512b 100644 --- a/dictionary.txt +++ b/dictionary.txt @@ -59,7 +59,3 @@ cebe inwidget uuidv Linkit -Modulz -Pilcrow -Vercel - -- GitLab From 2c789e11bdb2bf793edbeed2b8fae378fccb9aa2 Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <felix.mazeikis@acquia.com> Date: Thu, 20 Feb 2025 13:55:59 +0000 Subject: [PATCH 05/30] Remove elements library from openapi for now --- openapi.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openapi.yml b/openapi.yml index da1dfadc78..11deb4432e 100644 --- a/openapi.yml +++ b/openapi.yml @@ -160,9 +160,6 @@ paths: singleAvailableComponent: value: libraries: - # Provided by Experience Builder - #elements: - #label: Elements # Provided by Modules extension_components: label: Block Components @@ -1128,8 +1125,6 @@ components: type: string description: Library ID enum: - # Provided by Experience Builder - - elements # Provided by Modules - extension_components # Blocks -- GitLab From bc67a667b7289cda6e4f272386a991e83c5a8e13 Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <felix.mazeikis@acquia.com> Date: Thu, 20 Feb 2025 14:23:53 +0000 Subject: [PATCH 06/30] Reverting controller changes --- src/Controller/ApiConfigControllers.php | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/Controller/ApiConfigControllers.php b/src/Controller/ApiConfigControllers.php index 5e69c9b054..93b303a43f 100644 --- a/src/Controller/ApiConfigControllers.php +++ b/src/Controller/ApiConfigControllers.php @@ -114,27 +114,8 @@ final class ApiConfigControllers extends ApiControllerBase { if ($max_age !== Cache::PERMANENT) { $normalizations_cacheability->setCacheMaxAge(max($max_age, 3600)); } - if ($xb_config_entity_type_id === 'component') { - $response = [ - 'libraries' => [ - 'extension_components' => [ - 'label' => 'Block Components', - ], - 'dynamic_components' => [ - 'label' => 'Active Theme Components and Code Components', - ], - 'primary_components' => [ - 'label' => 'Components', - ], - ], - 'components' => $normalizations, - ]; - } - else { - $response = $normalizations; - } - return (new CacheableJsonResponse($response)) + return (new CacheableJsonResponse($normalizations)) ->addCacheableDependency($query_cacheability) ->addCacheableDependency($normalizations_cacheability); } -- GitLab From d307e188fc1c484243e688a2ea0c195ecf7394db Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <felix.mazeikis@acquia.com> Date: Thu, 20 Feb 2025 14:26:17 +0000 Subject: [PATCH 07/30] Undoing config list chape change --- openapi.yml | 162 ++++++++++++++++++++++------------------------------ 1 file changed, 68 insertions(+), 94 deletions(-) diff --git a/openapi.yml b/openapi.yml index 11deb4432e..9ccca22ab6 100644 --- a/openapi.yml +++ b/openapi.yml @@ -159,97 +159,77 @@ paths: examples: singleAvailableComponent: value: - libraries: - # Provided by Modules - extension_components: - label: Block Components - dynamic_components: - # SDCs from the Active Theme + JavaScript Code Components - label: Active Theme Components and Code Components - primary_components: - label: Components? - components: - 'sdc_test:my-cta': - id: 'sdc_test:my-cta' + 'sdc_test:my-cta': + id: 'sdc_test:my-cta' + name: Call to Action + source: Module component + category: Buttons + library: extension_components + transforms: + text: + extractMainPropertyName: + mainPropertyName: value + href: + extractMainPropertyName: + mainPropertyName: value + target: + passThrough: { } + metadata: + path: >- + core/modules/system/tests/modules/sdc_test/components/my-cta + documentation: >- + No documentation found. Add a README.md in your + component directory. + status: stable + machineName: my-cta name: Call to Action - source: Module component - category: Buttons - library: extension_components - transforms: - text: - extractMainPropertyName: - mainPropertyName: value - href: - extractMainPropertyName: - mainPropertyName: value - target: - passThrough: { } - metadata: - path: >- - core/modules/system/tests/modules/sdc_test/components/my-cta - documentation: >- - No documentation found. Add a README.md in your - component directory. - status: stable - machineName: my-cta - name: Call to Action - group: All Components - schema: - type: object - required: - - text - properties: - text: - type: - - string - - object - title: Title - description: The title for the cta - examples: - - Press - - Submit now - href: - type: - - string - - object - title: URL - format: uri - target: - type: - - string - - object - title: Target - enum: - - '' - - _blank - attributes: - type: - - Drupal\Core\Template\Attribute - - object - name: Attributes - additionalProperties: false - description: Call to action link. - mandatorySchemas: true - slots: [] + group: All Components + schema: + type: object + required: + - text + properties: + text: + type: + - string + - object + title: Title + description: The title for the cta + examples: + - Press + - Submit now + href: + type: + - string + - object + title: URL + format: uri + target: + type: + - string + - object + title: Target + enum: + - '' + - _blank + attributes: + type: + - Drupal\Core\Template\Attribute + - object + name: Attributes + additionalProperties: false + description: Call to action link. + mandatorySchemas: true + slots: [] schema: type: object - properties: - libraries: - extension_components: - $ref: '#/components/schemas/Library' - dynamic_components: - $ref: '#/components/schemas/Library' - primary_components: - $ref: '#/components/schemas/Library' - components: - type: object - minProperties: 1 - # @todo Validate the property keys match the - # '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$' pattern in - # https://drupal.org/i/3506543. - # @see \Drupal\Core\Plugin\Component::$machineName - additionalProperties: - $ref: '#/components/schemas/Component' + minProperties: 1 + # @todo Validate the property keys match the + # '^\\[a-zA-Z0-9_-]:[a-zA-Z0-9_-]$' pattern in + # https://drupal.org/i/3506543. + # @see \Drupal\Core\Plugin\Component::$machineName + additionalProperties: + $ref: '#/components/schemas/Component' /xb/api/config/js_component: get: description: Collection of Code Components @@ -1295,12 +1275,6 @@ components: type: boolean isPublished: type: boolean - Library: - title: Library - type: object - properties: - label: - type: string AutoSaveEntry: title: Auto Save Entry type: object -- GitLab From 17b4de029c3ffe6a7030ef2b73aa9319e63c5863 Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <felix.mazeikis@acquia.com> Date: Thu, 20 Feb 2025 15:41:07 +0000 Subject: [PATCH 08/30] Switch to using enum for library id --- .../ComponentSourceInterface.php | 5 ++-- src/Controller/LibraryIdEnum.php | 25 +++++++++++++++++++ src/Entity/Component.php | 2 +- .../ComponentSource/BlockComponent.php | 8 +++--- .../ComponentSource/JsComponent.php | 8 +++--- .../SingleDirectoryComponent.php | 11 ++++---- 6 files changed, 42 insertions(+), 17 deletions(-) create mode 100644 src/Controller/LibraryIdEnum.php diff --git a/src/ComponentSource/ComponentSourceInterface.php b/src/ComponentSource/ComponentSourceInterface.php index 9e3873da96..7b1381bc01 100644 --- a/src/ComponentSource/ComponentSourceInterface.php +++ b/src/ComponentSource/ComponentSourceInterface.php @@ -14,6 +14,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContextAwarePluginInterface; use Drupal\Core\Plugin\PluginFormInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\experience_builder\Controller\LibraryIdEnum; use Drupal\experience_builder\Entity\Component; use Drupal\experience_builder\Plugin\Field\FieldType\ComponentTreeItem; use Symfony\Component\Validator\ConstraintViolationListInterface; @@ -208,8 +209,8 @@ interface ComponentSourceInterface extends PluginInspectionInterface, Derivative /** * Returns Library ID to be used for UI placement in FE. * - * @return string + * @return \Drupal\experience_builder\Controller\LibraryIdEnum */ - public function getLibraryId(): string; + public function getLibraryId(): LibraryIdEnum; } diff --git a/src/Controller/LibraryIdEnum.php b/src/Controller/LibraryIdEnum.php new file mode 100644 index 0000000000..73d7b70007 --- /dev/null +++ b/src/Controller/LibraryIdEnum.php @@ -0,0 +1,25 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\experience_builder\Controller; + +/** + * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15988043 + * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15989118 + */ +enum LibraryIdEnum: string { + case ModuleComponents = 'extension_components'; + case BlockComponent = 'block_components'; + case ActiveThemeComponents = 'theme_components'; + case JsComponents = 'js_components'; + + public function getId(): string { + return match($this) { + self::ModuleComponents => 'extension_components', + self::BlockComponent => 'dynamic_components', + self::ActiveThemeComponents, self::JsComponents => 'primary_components', + }; + } + +} diff --git a/src/Entity/Component.php b/src/Entity/Component.php index 10a7ab4981..47032472ee 100644 --- a/src/Entity/Component.php +++ b/src/Entity/Component.php @@ -210,7 +210,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb values: $info + [ 'id' => $this->id(), 'name' => (string) $this->label(), - 'library' => $this->getComponentSource()->getLibraryId(), + 'library' => $this->getComponentSource()->getLibraryId()->getId(), 'category' => (string) $this->getCategory(), 'source' => (string) $this->getComponentSource()->getPluginDefinition()['label'], ], diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php index ea7a9943a1..6024e352ae 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php @@ -22,6 +22,7 @@ use Drupal\Core\TypedData\Plugin\DataType\BooleanData; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\experience_builder\Attribute\ComponentSource; use Drupal\experience_builder\ComponentSource\ComponentSourceBase; +use Drupal\experience_builder\Controller\LibraryIdEnum; use Drupal\experience_builder\Entity\Component; use Drupal\experience_builder\Entity\Component as ComponentEntity; use Drupal\experience_builder\MissingComponentInputsException; @@ -47,7 +48,6 @@ final class BlockComponent extends ComponentSourceBase implements ContainerFacto public const SOURCE_PLUGIN_ID = 'block'; public const EXPLICIT_INPUT_NAME = 'settings'; - public const LIBRARY_ID = 'extension_components'; /** * Constructs a new BlockComponent. @@ -360,10 +360,10 @@ final class BlockComponent extends ComponentSourceBase implements ContainerFacto } /** - * @return string + * {@inheritdoc} */ - public function getLibraryId(): string { - return self::LIBRARY_ID; + public function getLibraryId(): LibraryIdEnum { + return LibraryIdEnum::BlockComponent; } } diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php index 8a9b2a3f6a..6f488604ab 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php @@ -11,6 +11,7 @@ use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\experience_builder\Attribute\ComponentSource; use Drupal\experience_builder\ComponentDoesNotMeetRequirementsException; use Drupal\experience_builder\ComponentMetadataRequirementsChecker; +use Drupal\experience_builder\Controller\LibraryIdEnum; use Drupal\experience_builder\Entity\Component as ComponentEntity; use Drupal\experience_builder\Entity\ComponentInterface; use Drupal\experience_builder\Entity\JavaScriptComponent; @@ -25,7 +26,6 @@ use Drupal\experience_builder\Entity\JavaScriptComponent; final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase { public const SOURCE_PLUGIN_ID = 'js'; - public const LIBRARY_ID = 'primary_components'; /** * {@inheritdoc} @@ -228,10 +228,10 @@ final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase } /** - * @return string + * {@inheritdoc} */ - public function getLibraryId(): string { - return self::LIBRARY_ID; + public function getLibraryId(): LibraryIdEnum { + return LibraryIdEnum::JsComponents; } } diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php index 471233ef28..c4e8eabcb8 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php @@ -16,6 +16,7 @@ use Drupal\Core\Theme\ComponentPluginManager; use Drupal\Core\Theme\ExtensionType; use Drupal\experience_builder\Attribute\ComponentSource; use Drupal\experience_builder\ComponentSource\UrlRewriteInterface; +use Drupal\experience_builder\Controller\LibraryIdEnum; use Drupal\experience_builder\Entity\Component as ComponentEntity; use Drupal\experience_builder\Plugin\ComponentPluginManager as XbComponentPluginManager; use Drupal\experience_builder\ShapeMatcher\FieldForComponentSuggester; @@ -32,8 +33,6 @@ use Symfony\Component\Filesystem\Path; final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxComponentSourceBase implements UrlRewriteInterface { public const SOURCE_PLUGIN_ID = 'sdc'; - public const LIBRARY_ID_THEME = 'extension_components'; - public const LIBRARY_ID_EXTENSIONS = 'primary_components'; /** * Constructs a new SingleDirectoryComponent. @@ -313,16 +312,16 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon } /** - * @return string + * {@inheritdoc} */ - public function getLibraryId(): string { + public function getLibraryId(): LibraryIdEnum { $base_id = $this->getComponentPlugin()->getBaseId(); if ($this->themeHandler->themeExists($base_id)) { - return self::LIBRARY_ID_THEME; + return LibraryIdEnum::ActiveThemeComponents; } if ($this->moduleHandler->moduleExists($base_id)) { - return self::LIBRARY_ID_EXTENSIONS; + return LibraryIdEnum::ModuleComponents; } throw new \InvalidArgumentException("Invalid component provider id '{$base_id}'"); } -- GitLab From 75919ac3ff2655bc1731e6007fd3b49b425af09f Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <felix.mazeikis@acquia.com> Date: Thu, 20 Feb 2025 15:44:14 +0000 Subject: [PATCH 09/30] Minor rename --- src/Controller/LibraryIdEnum.php | 4 ++-- .../ComponentSource/SingleDirectoryComponent.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Controller/LibraryIdEnum.php b/src/Controller/LibraryIdEnum.php index 73d7b70007..2b6838b425 100644 --- a/src/Controller/LibraryIdEnum.php +++ b/src/Controller/LibraryIdEnum.php @@ -11,14 +11,14 @@ namespace Drupal\experience_builder\Controller; enum LibraryIdEnum: string { case ModuleComponents = 'extension_components'; case BlockComponent = 'block_components'; - case ActiveThemeComponents = 'theme_components'; + case ThemeComponents = 'theme_components'; case JsComponents = 'js_components'; public function getId(): string { return match($this) { self::ModuleComponents => 'extension_components', self::BlockComponent => 'dynamic_components', - self::ActiveThemeComponents, self::JsComponents => 'primary_components', + self::ThemeComponents, self::JsComponents => 'primary_components', }; } diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php index c4e8eabcb8..1e6835de41 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php @@ -317,7 +317,7 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon public function getLibraryId(): LibraryIdEnum { $base_id = $this->getComponentPlugin()->getBaseId(); if ($this->themeHandler->themeExists($base_id)) { - return LibraryIdEnum::ActiveThemeComponents; + return LibraryIdEnum::ThemeComponents; } if ($this->moduleHandler->moduleExists($base_id)) { -- GitLab From eaec83639b3d0558b314c7ab1964fd063854bb46 Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <felix.mazeikis@acquia.com> Date: Thu, 20 Feb 2025 16:59:45 +0000 Subject: [PATCH 10/30] Enum refactor --- src/Controller/LibraryIdEnum.php | 15 +++------------ src/Entity/Component.php | 2 +- .../ComponentSource/BlockComponent.php | 2 +- .../ComponentSource/JsComponent.php | 2 +- .../ComponentSource/SingleDirectoryComponent.php | 8 +++----- 5 files changed, 9 insertions(+), 20 deletions(-) diff --git a/src/Controller/LibraryIdEnum.php b/src/Controller/LibraryIdEnum.php index 2b6838b425..aa33943467 100644 --- a/src/Controller/LibraryIdEnum.php +++ b/src/Controller/LibraryIdEnum.php @@ -9,17 +9,8 @@ namespace Drupal\experience_builder\Controller; * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15989118 */ enum LibraryIdEnum: string { - case ModuleComponents = 'extension_components'; - case BlockComponent = 'block_components'; - case ThemeComponents = 'theme_components'; - case JsComponents = 'js_components'; - - public function getId(): string { - return match($this) { - self::ModuleComponents => 'extension_components', - self::BlockComponent => 'dynamic_components', - self::ThemeComponents, self::JsComponents => 'primary_components', - }; - } + case ExtensionComponents = 'extension_components'; + case DynamicComponents = 'dynamic_components'; + case PrimaryComponents = 'primary_components'; } diff --git a/src/Entity/Component.php b/src/Entity/Component.php index 47032472ee..10a7ab4981 100644 --- a/src/Entity/Component.php +++ b/src/Entity/Component.php @@ -210,7 +210,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb values: $info + [ 'id' => $this->id(), 'name' => (string) $this->label(), - 'library' => $this->getComponentSource()->getLibraryId()->getId(), + 'library' => $this->getComponentSource()->getLibraryId(), 'category' => (string) $this->getCategory(), 'source' => (string) $this->getComponentSource()->getPluginDefinition()['label'], ], diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php index 6024e352ae..e7a87ad712 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php @@ -363,7 +363,7 @@ final class BlockComponent extends ComponentSourceBase implements ContainerFacto * {@inheritdoc} */ public function getLibraryId(): LibraryIdEnum { - return LibraryIdEnum::BlockComponent; + return LibraryIdEnum::DynamicComponents; } } diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php index 6f488604ab..b7f8aadc52 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php @@ -231,7 +231,7 @@ final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase * {@inheritdoc} */ public function getLibraryId(): LibraryIdEnum { - return LibraryIdEnum::JsComponents; + return LibraryIdEnum::PrimaryComponents; } } diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php index 1e6835de41..85a17e35ea 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php @@ -316,14 +316,12 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon */ public function getLibraryId(): LibraryIdEnum { $base_id = $this->getComponentPlugin()->getBaseId(); - if ($this->themeHandler->themeExists($base_id)) { - return LibraryIdEnum::ThemeComponents; - } if ($this->moduleHandler->moduleExists($base_id)) { - return LibraryIdEnum::ModuleComponents; + return LibraryIdEnum::ExtensionComponents; } - throw new \InvalidArgumentException("Invalid component provider id '{$base_id}'"); + + return LibraryIdEnum::PrimaryComponents; } } -- GitLab From e11acc543e1b6c93119247ad17a31d7d38262620 Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <felix.mazeikis@acquia.com> Date: Thu, 20 Feb 2025 17:14:15 +0000 Subject: [PATCH 11/30] Exclude non-default theme components from endpoint --- src/Controller/ApiConfigControllers.php | 4 ++++ src/Controller/LibraryIdEnum.php | 1 + .../ComponentSource/SingleDirectoryComponent.php | 6 +++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Controller/ApiConfigControllers.php b/src/Controller/ApiConfigControllers.php index 93b303a43f..5df9811f2a 100644 --- a/src/Controller/ApiConfigControllers.php +++ b/src/Controller/ApiConfigControllers.php @@ -95,6 +95,10 @@ final class ApiConfigControllers extends ApiControllerBase { $normalizations_cacheability = new CacheableMetadata(); foreach ($config_entities as $key => &$entity) { $representation = $this->normalize($entity); + // @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15997374 + if ($xb_config_entity_type_id === 'component' && $representation->values['library'] === LibraryIdEnum::None) { + continue; + } $normalizations[$key] = $representation->values; $normalizations_cacheability->addCacheableDependency($representation); } diff --git a/src/Controller/LibraryIdEnum.php b/src/Controller/LibraryIdEnum.php index aa33943467..8b4fdb3270 100644 --- a/src/Controller/LibraryIdEnum.php +++ b/src/Controller/LibraryIdEnum.php @@ -12,5 +12,6 @@ enum LibraryIdEnum: string { case ExtensionComponents = 'extension_components'; case DynamicComponents = 'dynamic_components'; case PrimaryComponents = 'primary_components'; + case None = ''; } diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php index 85a17e35ea..006a051c28 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php @@ -321,7 +321,11 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon return LibraryIdEnum::ExtensionComponents; } - return LibraryIdEnum::PrimaryComponents; + if ($this->themeHandler->getDefault() === $base_id) { + return LibraryIdEnum::PrimaryComponents; + } + + return LibraryIdEnum::None; } } -- GitLab From a5329c48e2fa44e7053b157410afd133ff3db364 Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <felix.mazeikis@acquia.com> Date: Thu, 20 Feb 2025 17:23:10 +0000 Subject: [PATCH 12/30] Add Elements --- openapi.yml | 2 ++ src/Controller/LibraryIdEnum.php | 1 + .../ComponentSource/SingleDirectoryComponent.php | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/openapi.yml b/openapi.yml index 9ccca22ab6..7df1084fd3 100644 --- a/openapi.yml +++ b/openapi.yml @@ -1105,6 +1105,8 @@ components: type: string description: Library ID enum: + # Provided by Experience Builder + - elements # Provided by Modules - extension_components # Blocks diff --git a/src/Controller/LibraryIdEnum.php b/src/Controller/LibraryIdEnum.php index 8b4fdb3270..145bd390b4 100644 --- a/src/Controller/LibraryIdEnum.php +++ b/src/Controller/LibraryIdEnum.php @@ -9,6 +9,7 @@ namespace Drupal\experience_builder\Controller; * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15989118 */ enum LibraryIdEnum: string { + case Elements = 'elements'; case ExtensionComponents = 'extension_components'; case DynamicComponents = 'dynamic_components'; case PrimaryComponents = 'primary_components'; diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php index 006a051c28..d2b22766dc 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php @@ -318,7 +318,7 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon $base_id = $this->getComponentPlugin()->getBaseId(); if ($this->moduleHandler->moduleExists($base_id)) { - return LibraryIdEnum::ExtensionComponents; + return $base_id === 'experience_builder' ? LibraryIdEnum::Elements : LibraryIdEnum::ExtensionComponents; } if ($this->themeHandler->getDefault() === $base_id) { -- GitLab From a17e09e8e68bd5a0d315202ddd885f664b7bbbd3 Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <felix.mazeikis@acquia.com> Date: Thu, 20 Feb 2025 18:12:51 +0000 Subject: [PATCH 13/30] Rename enum --- src/ComponentSource/ComponentSourceInterface.php | 8 ++++---- src/Controller/ApiConfigControllers.php | 2 +- src/Controller/{LibraryIdEnum.php => LibraryEnum.php} | 2 +- src/Entity/Component.php | 2 +- .../ComponentSource/BlockComponent.php | 6 +++--- .../ExperienceBuilder/ComponentSource/JsComponent.php | 6 +++--- .../ComponentSource/SingleDirectoryComponent.php | 10 +++++----- 7 files changed, 18 insertions(+), 18 deletions(-) rename src/Controller/{LibraryIdEnum.php => LibraryEnum.php} (94%) diff --git a/src/ComponentSource/ComponentSourceInterface.php b/src/ComponentSource/ComponentSourceInterface.php index 7b1381bc01..d740e57fbb 100644 --- a/src/ComponentSource/ComponentSourceInterface.php +++ b/src/ComponentSource/ComponentSourceInterface.php @@ -14,7 +14,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContextAwarePluginInterface; use Drupal\Core\Plugin\PluginFormInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; -use Drupal\experience_builder\Controller\LibraryIdEnum; +use Drupal\experience_builder\Controller\LibraryEnum; use Drupal\experience_builder\Entity\Component; use Drupal\experience_builder\Plugin\Field\FieldType\ComponentTreeItem; use Symfony\Component\Validator\ConstraintViolationListInterface; @@ -207,10 +207,10 @@ interface ComponentSourceInterface extends PluginInspectionInterface, Derivative public function checkRequirements(): void; /** - * Returns Library ID to be used for UI placement in FE. + * Returns Library enum. * - * @return \Drupal\experience_builder\Controller\LibraryIdEnum + * @return \Drupal\experience_builder\Controller\LibraryEnum */ - public function getLibraryId(): LibraryIdEnum; + public function getLibrary(): LibraryEnum; } diff --git a/src/Controller/ApiConfigControllers.php b/src/Controller/ApiConfigControllers.php index 5df9811f2a..51a2ed5bfc 100644 --- a/src/Controller/ApiConfigControllers.php +++ b/src/Controller/ApiConfigControllers.php @@ -96,7 +96,7 @@ final class ApiConfigControllers extends ApiControllerBase { foreach ($config_entities as $key => &$entity) { $representation = $this->normalize($entity); // @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15997374 - if ($xb_config_entity_type_id === 'component' && $representation->values['library'] === LibraryIdEnum::None) { + if ($xb_config_entity_type_id === 'component' && $representation->values['library'] === LibraryEnum::None) { continue; } $normalizations[$key] = $representation->values; diff --git a/src/Controller/LibraryIdEnum.php b/src/Controller/LibraryEnum.php similarity index 94% rename from src/Controller/LibraryIdEnum.php rename to src/Controller/LibraryEnum.php index 145bd390b4..feaabc0675 100644 --- a/src/Controller/LibraryIdEnum.php +++ b/src/Controller/LibraryEnum.php @@ -8,7 +8,7 @@ namespace Drupal\experience_builder\Controller; * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15988043 * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15989118 */ -enum LibraryIdEnum: string { +enum LibraryEnum: string { case Elements = 'elements'; case ExtensionComponents = 'extension_components'; case DynamicComponents = 'dynamic_components'; diff --git a/src/Entity/Component.php b/src/Entity/Component.php index 10a7ab4981..13f756b381 100644 --- a/src/Entity/Component.php +++ b/src/Entity/Component.php @@ -210,7 +210,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb values: $info + [ 'id' => $this->id(), 'name' => (string) $this->label(), - 'library' => $this->getComponentSource()->getLibraryId(), + 'library' => $this->getComponentSource()->getLibrary(), 'category' => (string) $this->getCategory(), 'source' => (string) $this->getComponentSource()->getPluginDefinition()['label'], ], diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php index e7a87ad712..d3f70453e4 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php @@ -22,7 +22,7 @@ use Drupal\Core\TypedData\Plugin\DataType\BooleanData; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\experience_builder\Attribute\ComponentSource; use Drupal\experience_builder\ComponentSource\ComponentSourceBase; -use Drupal\experience_builder\Controller\LibraryIdEnum; +use Drupal\experience_builder\Controller\LibraryEnum; use Drupal\experience_builder\Entity\Component; use Drupal\experience_builder\Entity\Component as ComponentEntity; use Drupal\experience_builder\MissingComponentInputsException; @@ -362,8 +362,8 @@ final class BlockComponent extends ComponentSourceBase implements ContainerFacto /** * {@inheritdoc} */ - public function getLibraryId(): LibraryIdEnum { - return LibraryIdEnum::DynamicComponents; + public function getLibrary(): LibraryEnum { + return LibraryEnum::DynamicComponents; } } diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php index b7f8aadc52..460814d25f 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php @@ -11,7 +11,7 @@ use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\experience_builder\Attribute\ComponentSource; use Drupal\experience_builder\ComponentDoesNotMeetRequirementsException; use Drupal\experience_builder\ComponentMetadataRequirementsChecker; -use Drupal\experience_builder\Controller\LibraryIdEnum; +use Drupal\experience_builder\Controller\LibraryEnum; use Drupal\experience_builder\Entity\Component as ComponentEntity; use Drupal\experience_builder\Entity\ComponentInterface; use Drupal\experience_builder\Entity\JavaScriptComponent; @@ -230,8 +230,8 @@ final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase /** * {@inheritdoc} */ - public function getLibraryId(): LibraryIdEnum { - return LibraryIdEnum::PrimaryComponents; + public function getLibrary(): LibraryEnum { + return LibraryEnum::PrimaryComponents; } } diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php index d2b22766dc..295824e4d9 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php @@ -16,7 +16,7 @@ use Drupal\Core\Theme\ComponentPluginManager; use Drupal\Core\Theme\ExtensionType; use Drupal\experience_builder\Attribute\ComponentSource; use Drupal\experience_builder\ComponentSource\UrlRewriteInterface; -use Drupal\experience_builder\Controller\LibraryIdEnum; +use Drupal\experience_builder\Controller\LibraryEnum; use Drupal\experience_builder\Entity\Component as ComponentEntity; use Drupal\experience_builder\Plugin\ComponentPluginManager as XbComponentPluginManager; use Drupal\experience_builder\ShapeMatcher\FieldForComponentSuggester; @@ -314,18 +314,18 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon /** * {@inheritdoc} */ - public function getLibraryId(): LibraryIdEnum { + public function getLibrary(): LibraryEnum { $base_id = $this->getComponentPlugin()->getBaseId(); if ($this->moduleHandler->moduleExists($base_id)) { - return $base_id === 'experience_builder' ? LibraryIdEnum::Elements : LibraryIdEnum::ExtensionComponents; + return $base_id === 'experience_builder' ? LibraryEnum::Elements : LibraryEnum::ExtensionComponents; } if ($this->themeHandler->getDefault() === $base_id) { - return LibraryIdEnum::PrimaryComponents; + return LibraryEnum::PrimaryComponents; } - return LibraryIdEnum::None; + return LibraryEnum::None; } } -- GitLab From 7d06253991589143714ce066493d9941db726522 Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <felix.mazeikis@acquia.com> Date: Thu, 20 Feb 2025 18:15:37 +0000 Subject: [PATCH 14/30] Added additional inline docs --- src/Controller/ApiConfigControllers.php | 2 ++ .../ComponentSource/SingleDirectoryComponent.php | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/Controller/ApiConfigControllers.php b/src/Controller/ApiConfigControllers.php index 51a2ed5bfc..3ddc5161ca 100644 --- a/src/Controller/ApiConfigControllers.php +++ b/src/Controller/ApiConfigControllers.php @@ -95,6 +95,8 @@ final class ApiConfigControllers extends ApiControllerBase { $normalizations_cacheability = new CacheableMetadata(); foreach ($config_entities as $key => &$entity) { $representation = $this->normalize($entity); + // If component is provided by non-default theme it should be excluded + // from the endpoint output. // @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15997374 if ($xb_config_entity_type_id === 'component' && $representation->values['library'] === LibraryEnum::None) { continue; diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php index 295824e4d9..3d6d97617e 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php @@ -325,6 +325,9 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon return LibraryEnum::PrimaryComponents; } + // If component is provided by non-default theme it should be excluded + // from the endpoint output. + // @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15997374 return LibraryEnum::None; } -- GitLab From 7c687108cef45f7f322005e7820f18350793cdfc Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <felix.mazeikis@acquia.com> Date: Thu, 20 Feb 2025 18:17:53 +0000 Subject: [PATCH 15/30] Added additional inline docs --- src/Controller/ApiConfigControllers.php | 1 + .../ComponentSource/SingleDirectoryComponent.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Controller/ApiConfigControllers.php b/src/Controller/ApiConfigControllers.php index 3ddc5161ca..c3d618ac8f 100644 --- a/src/Controller/ApiConfigControllers.php +++ b/src/Controller/ApiConfigControllers.php @@ -97,6 +97,7 @@ final class ApiConfigControllers extends ApiControllerBase { $representation = $this->normalize($entity); // If component is provided by non-default theme it should be excluded // from the endpoint output. + // @see \Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\SingleDirectoryComponent::getLibrary() // @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15997374 if ($xb_config_entity_type_id === 'component' && $representation->values['library'] === LibraryEnum::None) { continue; diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php index 3d6d97617e..51ffbb6bf1 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php @@ -325,8 +325,8 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon return LibraryEnum::PrimaryComponents; } - // If component is provided by non-default theme it should be excluded - // from the endpoint output. + // If component is provided by non-default theme it should not be assigned + // 'primary_components' library value. // @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15997374 return LibraryEnum::None; } -- GitLab From ebaa04f48e1f00be5a22bc80e4d19d2b26606331 Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <felix.mazeikis@acquia.com> Date: Thu, 20 Feb 2025 18:22:34 +0000 Subject: [PATCH 16/30] Adding ->value --- src/Entity/Component.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entity/Component.php b/src/Entity/Component.php index 13f756b381..aae0c016b0 100644 --- a/src/Entity/Component.php +++ b/src/Entity/Component.php @@ -210,7 +210,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb values: $info + [ 'id' => $this->id(), 'name' => (string) $this->label(), - 'library' => $this->getComponentSource()->getLibrary(), + 'library' => $this->getComponentSource()->getLibrary()->value, 'category' => (string) $this->getCategory(), 'source' => (string) $this->getComponentSource()->getPluginDefinition()['label'], ], -- GitLab From 523e39540de5ad62af948254d3e04c11b17dd0e1 Mon Sep 17 00:00:00 2001 From: Wim Leers <wim.leers@acquia.com> Date: Thu, 20 Feb 2025 18:58:04 +0100 Subject: [PATCH 17/30] Add missing metadata: whether a `ComponentSource` supports implicit inputs or not. --- docs/components.md | 9 ++++++++- src/Attribute/ComponentSource.php | 1 + .../ExperienceBuilder/ComponentSource/BlockComponent.php | 5 ++++- .../ExperienceBuilder/ComponentSource/JsComponent.php | 3 ++- .../ComponentSource/SingleDirectoryComponent.php | 3 ++- 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/components.md b/docs/components.md index c90582957d..6744246213 100644 --- a/docs/components.md +++ b/docs/components.md @@ -93,7 +93,12 @@ Therefore, it only makes sense to surface _block plugins_ as XB `component`s. defined using config schema (`type: block.settings.<PLUGIN ID>`). Defaults are present as the `::defaultConfiguration()` method on the PHP plugin class. -`Block` `component`s specify the accepted explicit inputs +`Block` `component`s DO accept implicit inputs, in two ways even: +1. Logic in the block plugin can fetch data — through database queries, HTTP requests, anything. +2. Contexts. Not yet supported. (⚠️ handling contexts is still TBD in [#3485502](https://www.drupal.org/project/experience_builder/issues/3485502)) + +`Block` `component`s specify the accepted explicit inputs. They typically allow influencing the logic in the block +plugin. These explicit inputs can hence be seen as knobs and levers to adjust what the underlying block plugin does. `Block` DOES provide an input UX (`BlockPluginInterface::buildConfigurationForm()`), so its `Component Source Plugin` simply reuses that. @@ -128,6 +133,8 @@ refer to "blocks" and not "block plugins", under the hood, they actually _are_ b 3.1.1 above](#3.1.1)). See [section 3.2 `JavaScriptComponent config entity` in the `XB Config Management` doc](config-management.md#3.2) for all details. +`JS` `component`s DO NOT accept implicit inputs. + `JS` DOES NOT provide an input UX, so its `Component Source Plugin` must do so on its behalf; and does so by matching available field types against the JSON schema of its explicit inputs ("props"). For details, see the [`XB Shape Matching into Field Types` doc](shape-matching-into-field-types.md). (It shares this infrastructure with the `SDC` `Component diff --git a/src/Attribute/ComponentSource.php b/src/Attribute/ComponentSource.php index f714ac9611..0c3e176000 100644 --- a/src/Attribute/ComponentSource.php +++ b/src/Attribute/ComponentSource.php @@ -25,6 +25,7 @@ final class ComponentSource extends Plugin { public function __construct( public readonly string $id, public readonly TranslatableMarkup $label, + public readonly bool $supportsImplicitInputs, public readonly ?string $deriver = NULL, ) { } diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php index d3f70453e4..64aa781c74 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php @@ -40,7 +40,10 @@ use Symfony\Component\Validator\ConstraintViolationListInterface; */ #[ComponentSource( id: self::SOURCE_PLUGIN_ID, - label: new TranslatableMarkup('Blocks') + label: new TranslatableMarkup('Blocks'), + // While XB does not support context mappings yet, Block plugins also can + // contain logic and perform e.g. database queries that fetch data to present. + supportsImplicitInputs: TRUE, )] final class BlockComponent extends ComponentSourceBase implements ContainerFactoryPluginInterface { diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php index 460814d25f..b69993bb78 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php @@ -21,7 +21,8 @@ use Drupal\experience_builder\Entity\JavaScriptComponent; */ #[ComponentSource( id: self::SOURCE_PLUGIN_ID, - label: new TranslatableMarkup('Code Components') + label: new TranslatableMarkup('Code Components'), + supportsImplicitInputs: FALSE, )] final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase { diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php index 51ffbb6bf1..5768e35b69 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php @@ -28,7 +28,8 @@ use Symfony\Component\Filesystem\Path; */ #[ComponentSource( id: self::SOURCE_PLUGIN_ID, - label: new TranslatableMarkup('Single-Directory Components') + label: new TranslatableMarkup('Single-Directory Components'), + supportsImplicitInputs: FALSE, )] final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxComponentSourceBase implements UrlRewriteInterface { -- GitLab From 078d28cfee361ab00a37767b3d116a4938fc77bb Mon Sep 17 00:00:00 2001 From: Wim Leers <wim.leers@acquia.com> Date: Thu, 20 Feb 2025 19:46:18 +0100 Subject: [PATCH 18/30] Add missing metadata: each `Component` needs to know its provider: a theme name, module name, or NULL if neither. --- config/schema/experience_builder.schema.yml | 9 +++++++ experience_builder.module | 1 + src/Entity/Component.php | 26 ++++++++++++++++--- .../ComponentSource/JsComponent.php | 1 + .../SingleDirectoryComponent.php | 1 + .../src/Functional/PropSourceEndpointTest.php | 1 + tests/src/Kernel/Config/ComponentTest.php | 5 +++- .../Entity/JavascriptComponentStorageTest.php | 1 + 8 files changed, 41 insertions(+), 4 deletions(-) diff --git a/config/schema/experience_builder.schema.yml b/config/schema/experience_builder.schema.yml index b3c76f7716..e3352d5bc9 100644 --- a/config/schema/experience_builder.schema.yml +++ b/config/schema/experience_builder.schema.yml @@ -28,6 +28,15 @@ experience_builder.component.*: Length: # @see \Drupal\Core\Config\Entity\ConfigEntityStorage::MAX_ID_LENGTH max: 166 + provider: + type: string + label: 'Name of the module or theme providing this component, or null if provided via something else' + nullable: true + constraints: + NotBlank: + allowNull: true + ExtensionName: [] + Callback: ['\Drupal\experience_builder\Entity\Component', providerExists] source: type: string label: 'Source plugin' diff --git a/experience_builder.module b/experience_builder.module index ca9833a8d1..4e5efc170f 100644 --- a/experience_builder.module +++ b/experience_builder.module @@ -265,6 +265,7 @@ function experience_builder_block_alter(array &$definitions): void { 'label' => (string) new TranslatableMarkup('@label block', ['@label' => $definition['admin_label']]), 'category' => (string) $definition['category'], 'source' => BlockComponent::SOURCE_PLUGIN_ID, + 'provider' => $definition['provider'], 'settings' => [ 'plugin_id' => $id, // We are using strict config schema validation, so we need to provide valid default settings for each block. diff --git a/src/Entity/Component.php b/src/Entity/Component.php index aae0c016b0..ad9d274136 100644 --- a/src/Entity/Component.php +++ b/src/Entity/Component.php @@ -5,6 +5,8 @@ declare(strict_types=1); namespace Drupal\experience_builder\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection; use Drupal\Core\Render\Markup; use Drupal\Core\StringTranslation\TranslatableMarkup; @@ -46,6 +48,7 @@ use Drupal\experience_builder\ComponentSource\ComponentSourceManager; * "label", * "id", * "source", + * "provider", * "category", * "settings", * }, @@ -75,6 +78,16 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb */ protected string $source; + /** + * The provider of this component: a valid module or theme name, or NULL. + * + * NULL must be used to signal it's not provided by an extension. This is used + * for "code components" for example — which are provided by entities. + * + * @see \Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\JsComponent + */ + protected ?string $provider; + /** * The human-readable category of the component. */ @@ -168,10 +181,17 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb } /** - * {@inheritdoc} + * Works around the `ExtensionExists` constraint requiring a fixed type. + * + * @see \Drupal\Core\Extension\Plugin\Validation\Constraint\ExtensionExistsConstraintValidator + * @see https://www.drupal.org/node/3353397 */ - protected function providerExists(string $provider): bool { - return $this->moduleHandler()->moduleExists($provider) || $this->themeHandler()->themeExists($provider); + public static function providerExists(?string $provider): bool { + if (is_null($provider)) { + return TRUE; + } + $container = \Drupal::getContainer(); + return $container->get(ModuleHandlerInterface::class)->moduleExists($provider) || $container->get(ThemeHandlerInterface::class)->themeExists($provider); } /** diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php index b69993bb78..2d631e001e 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php @@ -148,6 +148,7 @@ final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase 'id' => self::SOURCE_PLUGIN_ID . '.' . $js_component->id(), 'label' => $js_component->label(), 'category' => '@todo', + 'provider' => NULL, 'source' => self::SOURCE_PLUGIN_ID, 'settings' => [ // @todo rename plugin_id in https://www.drupal.org/project/experience_builder/issues/3502982 diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php index 5768e35b69..86fe41d206 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php @@ -221,6 +221,7 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon 'label' => $component_plugin->getPluginDefinition()['name'] ?? $component_plugin->getPluginId(), 'category' => $component_plugin->getPluginDefinition()['category'], 'source' => self::SOURCE_PLUGIN_ID, + 'provider' => $component_plugin->getPluginDefinition()['provider'], 'settings' => [ 'plugin_id' => $component_plugin->getPluginId(), 'prop_field_definitions' => $props, diff --git a/tests/src/Functional/PropSourceEndpointTest.php b/tests/src/Functional/PropSourceEndpointTest.php index 49bde26665..e337ca24b2 100644 --- a/tests/src/Functional/PropSourceEndpointTest.php +++ b/tests/src/Functional/PropSourceEndpointTest.php @@ -122,6 +122,7 @@ class PropSourceEndpointTest extends FunctionalTestBase { $this->assertArrayHasKey('name', $component); $this->assertArrayHasKey('category', $component); $this->assertArrayHasKey('source', $component); + $this->assertArrayHasKey('provider', $component); $this->assertArrayHasKey('default_markup', $component); $this->assertArrayHasKey('css', $component); $this->assertArrayHasKey('js_header', $component); diff --git a/tests/src/Kernel/Config/ComponentTest.php b/tests/src/Kernel/Config/ComponentTest.php index 76b468a3db..bf241b6bc2 100644 --- a/tests/src/Kernel/Config/ComponentTest.php +++ b/tests/src/Kernel/Config/ComponentTest.php @@ -103,6 +103,7 @@ class ComponentTest extends KernelTestBase { 'sdc' => [ 'component_config_entity_id' => 'sdc.sdc_test.my-cta', 'source' => SingleDirectoryComponent::SOURCE_PLUGIN_ID, + 'provider' => 'sdc_test', 'source_internal_id' => 'sdc_test:my-cta', 'expected_config_dependencies' => [ 'module' => [ @@ -116,6 +117,7 @@ class ComponentTest extends KernelTestBase { 'js' => [ 'component_config_entity_id' => 'js.my-cta', 'source' => JsComponent::SOURCE_PLUGIN_ID, + 'provider' => NULL, 'source_internal_id' => 'my-cta', 'expected_config_dependencies' => [ 'config' => [ @@ -133,7 +135,7 @@ class ComponentTest extends KernelTestBase { /** * @dataProvider providerComponentCreation */ - public function testComponentCreation(string $component_config_entity_id, string $source, string $source_internal_id, array $expected_config_dependencies): void { + public function testComponentCreation(string $component_config_entity_id, string $source, ?string $provider, string $source_internal_id, array $expected_config_dependencies): void { if ($source === JsComponent::SOURCE_PLUGIN_ID) { $this->assertEmpty(JavaScriptComponent::loadMultiple()); @@ -168,6 +170,7 @@ class ComponentTest extends KernelTestBase { 'label' => self::LABEL, 'category' => self::LABEL, 'source' => $source, + 'provider' => $provider, 'settings' => [ 'plugin_id' => $source_internal_id, 'prop_field_definitions' => [ diff --git a/tests/src/Kernel/Entity/JavascriptComponentStorageTest.php b/tests/src/Kernel/Entity/JavascriptComponentStorageTest.php index 61600aff16..85b09283d0 100644 --- a/tests/src/Kernel/Entity/JavascriptComponentStorageTest.php +++ b/tests/src/Kernel/Entity/JavascriptComponentStorageTest.php @@ -139,6 +139,7 @@ final class JavascriptComponentStorageTest extends KernelTestBase { $component = Component::load($component_id); self::assertInstanceOf(ComponentInterface::class, $component); + self::assertNull($component->get('provider')); self::assertEquals(['title'], \array_keys($component->getSettings()['prop_field_definitions'])); // Now update the js component and confirm we update the matching component. -- GitLab From d844a34b8120ceb178f9829f3d90c4629e1021a0 Mon Sep 17 00:00:00 2001 From: Wim Leers <wim.leers@acquia.com> Date: Thu, 20 Feb 2025 20:17:52 +0100 Subject: [PATCH 19/30] Introduce `XbHttpApiEligibleConfigEntityInterface::refineListQuery()` and use it to dynamically omit `Component`s provided by installed-but-not-default themes. --- src/Controller/ApiConfigControllers.php | 8 +------- src/Entity/AssetLibrary.php | 8 ++++++++ src/Entity/Component.php | 17 +++++++++++++++++ src/Entity/JavaScriptComponent.php | 8 ++++++++ src/Entity/Pattern.php | 8 ++++++++ .../XbHttpApiEligibleConfigEntityInterface.php | 11 +++++++++++ 6 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/Controller/ApiConfigControllers.php b/src/Controller/ApiConfigControllers.php index c3d618ac8f..c64ae7a5c0 100644 --- a/src/Controller/ApiConfigControllers.php +++ b/src/Controller/ApiConfigControllers.php @@ -84,6 +84,7 @@ final class ApiConfigControllers extends ApiControllerBase { if (!$xb_config_entity_type->get('xb_visible_when_disabled')) { $query->condition('status', TRUE); } + $xb_config_entity_type->getClass()::refineListQuery($query); /** @var array<\Drupal\experience_builder\Entity\XbHttpApiEligibleConfigEntityInterface> $config_entities */ $config_entities = $storage->loadMultiple($query->execute()); @@ -95,13 +96,6 @@ final class ApiConfigControllers extends ApiControllerBase { $normalizations_cacheability = new CacheableMetadata(); foreach ($config_entities as $key => &$entity) { $representation = $this->normalize($entity); - // If component is provided by non-default theme it should be excluded - // from the endpoint output. - // @see \Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\SingleDirectoryComponent::getLibrary() - // @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15997374 - if ($xb_config_entity_type_id === 'component' && $representation->values['library'] === LibraryEnum::None) { - continue; - } $normalizations[$key] = $representation->values; $normalizations_cacheability->addCacheableDependency($representation); } diff --git a/src/Entity/AssetLibrary.php b/src/Entity/AssetLibrary.php index 7cf7104c31..155fb025b1 100644 --- a/src/Entity/AssetLibrary.php +++ b/src/Entity/AssetLibrary.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Drupal\experience_builder\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; +use Drupal\Core\Entity\Query\QueryInterface; use Drupal\experience_builder\ClientSideRepresentation; /** @@ -73,4 +74,11 @@ final class AssetLibrary extends ConfigEntityBase implements XbHttpApiEligibleCo return $data; } + /** + * {@inheritdoc} + */ + public static function refineListQuery(QueryInterface &$query): void { + // Nothing to do. + } + } diff --git a/src/Entity/Component.php b/src/Entity/Component.php index ad9d274136..c018a48838 100644 --- a/src/Entity/Component.php +++ b/src/Entity/Component.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Drupal\experience_builder\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; +use Drupal\Core\Entity\Query\QueryInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection; @@ -247,6 +248,22 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb throw new \LogicException('Not supported: read-only for the client side, mutable only on the server side.'); } + /** + * {@inheritdoc} + */ + public static function refineListQuery(QueryInterface &$query): void { + $container = \Drupal::getContainer(); + $theme_handler = $container->get(ThemeHandlerInterface::class); + $installed_themes = array_keys($theme_handler->listInfo()); + $default_theme = $theme_handler->getDefault(); + // Omit Components provided by installed-but-not-default themes. This keeps + // all other Components: + // - module-provided ones + // - default theme-provided + // - provided by something else than an extension, such as an entity. + $query->condition('provider', array_diff($installed_themes, [$default_theme]), 'NOT IN'); + } + /** * {@inheritdoc} */ diff --git a/src/Entity/JavaScriptComponent.php b/src/Entity/JavaScriptComponent.php index b23e6ad96d..1c3730ae2c 100644 --- a/src/Entity/JavaScriptComponent.php +++ b/src/Entity/JavaScriptComponent.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Drupal\experience_builder\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; +use Drupal\Core\Entity\Query\QueryInterface; use Drupal\experience_builder\ClientSideRepresentation; /** @@ -139,6 +140,13 @@ final class JavaScriptComponent extends ConfigEntityBase implements XbHttpApiEli ]; } + /** + * {@inheritdoc} + */ + public static function refineListQuery(QueryInterface &$query): void { + // Nothing to do. + } + /** * Code components are not Twig-defined but still aim to match SDC closely. * diff --git a/src/Entity/Pattern.php b/src/Entity/Pattern.php index ddb23dad5f..5d3c6d3d77 100644 --- a/src/Entity/Pattern.php +++ b/src/Entity/Pattern.php @@ -7,6 +7,7 @@ namespace Drupal\experience_builder\Entity; use Drupal\Component\Utility\Random; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Entity\Query\QueryInterface; use Drupal\experience_builder\ClientSideRepresentation; use Drupal\experience_builder\Controller\ApiConfigControllers; use Drupal\experience_builder\Controller\ClientServerConversionTrait; @@ -163,4 +164,11 @@ final class Pattern extends ConfigEntityBase implements XbHttpApiEligibleConfigE ] + $other_values; } + /** + * {@inheritdoc} + */ + public static function refineListQuery(QueryInterface &$query): void { + // Nothing to do. + } + } diff --git a/src/Entity/XbHttpApiEligibleConfigEntityInterface.php b/src/Entity/XbHttpApiEligibleConfigEntityInterface.php index 4a9710cbbc..3ed8183af8 100644 --- a/src/Entity/XbHttpApiEligibleConfigEntityInterface.php +++ b/src/Entity/XbHttpApiEligibleConfigEntityInterface.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Drupal\experience_builder\Entity; use Drupal\Core\Config\Entity\ConfigEntityInterface; +use Drupal\Core\Entity\Query\QueryInterface; use Drupal\experience_builder\ClientSideRepresentation; /** @@ -33,4 +34,14 @@ interface XbHttpApiEligibleConfigEntityInterface extends ConfigEntityInterface { */ public static function denormalizeFromClientSide(array $data): array; + /** + * Allows the config entity query that generates the listing to be refined. + * + * @param \Drupal\Core\Entity\Query\QueryInterface $query + * The config entity query to refine, passed by reference. + * + * @return void + */ + public static function refineListQuery(QueryInterface &$query): void; + } -- GitLab From 830cfc100dbf610e29bcdb10b8801a07cc12341e Mon Sep 17 00:00:00 2001 From: Wim Leers <wim.leers@acquia.com> Date: Fri, 21 Feb 2025 10:44:49 +0100 Subject: [PATCH 20/30] Update test expectations for `ComponentValidationTest` to account for the new `provider` property. --- tests/src/Kernel/Config/ComponentValidationTest.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/src/Kernel/Config/ComponentValidationTest.php b/tests/src/Kernel/Config/ComponentValidationTest.php index c802908e04..08837eeed0 100644 --- a/tests/src/Kernel/Config/ComponentValidationTest.php +++ b/tests/src/Kernel/Config/ComponentValidationTest.php @@ -49,6 +49,15 @@ class ComponentValidationTest extends ConfigEntityValidationTestBase { ], ]; + /** + * {@inheritdoc} + * + * @phpstan-ignore property.defaultValue + */ + protected static array $propertiesWithOptionalValues = [ + 'provider', + ]; + /** * {@inheritdoc} */ -- GitLab From ef0b3121ce5443db76c482c99b13a6294a5dba73 Mon Sep 17 00:00:00 2001 From: Wim Leers <wim.leers@acquia.com> Date: Fri, 21 Feb 2025 11:09:30 +0100 Subject: [PATCH 21/30] Move the library computing logic from the SDC source into a private `::computeUiLibrary()` method used only during `Component` normalization. Hence it is now consistent across all Component Sources. --- .../ComponentSourceInterface.php | 8 ---- src/Entity/Component.php | 48 ++++++++++++++++++- .../ComponentSource/BlockComponent.php | 8 ---- .../ComponentSource/JsComponent.php | 8 ---- .../SingleDirectoryComponent.php | 21 -------- .../src/Functional/PropSourceEndpointTest.php | 2 +- 6 files changed, 48 insertions(+), 47 deletions(-) diff --git a/src/ComponentSource/ComponentSourceInterface.php b/src/ComponentSource/ComponentSourceInterface.php index d740e57fbb..7e72bafdf9 100644 --- a/src/ComponentSource/ComponentSourceInterface.php +++ b/src/ComponentSource/ComponentSourceInterface.php @@ -14,7 +14,6 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContextAwarePluginInterface; use Drupal\Core\Plugin\PluginFormInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; -use Drupal\experience_builder\Controller\LibraryEnum; use Drupal\experience_builder\Entity\Component; use Drupal\experience_builder\Plugin\Field\FieldType\ComponentTreeItem; use Symfony\Component\Validator\ConstraintViolationListInterface; @@ -206,11 +205,4 @@ interface ComponentSourceInterface extends PluginInspectionInterface, Derivative */ public function checkRequirements(): void; - /** - * Returns Library enum. - * - * @return \Drupal\experience_builder\Controller\LibraryEnum - */ - public function getLibrary(): LibraryEnum; - } diff --git a/src/Entity/Component.php b/src/Entity/Component.php index c018a48838..572db7ce8f 100644 --- a/src/Entity/Component.php +++ b/src/Entity/Component.php @@ -14,6 +14,7 @@ use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\experience_builder\ClientSideRepresentation; use Drupal\experience_builder\ComponentSource\ComponentSourceInterface; use Drupal\experience_builder\ComponentSource\ComponentSourceManager; +use Drupal\experience_builder\Controller\LibraryEnum; /** * @todo Update these docs in https://drupal.org/i/3454519 to reflect changes. @@ -231,7 +232,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb values: $info + [ 'id' => $this->id(), 'name' => (string) $this->label(), - 'library' => $this->getComponentSource()->getLibrary()->value, + 'library' => $this->computeUiLibrary()->value, 'category' => (string) $this->getCategory(), 'source' => (string) $this->getComponentSource()->getPluginDefinition()['label'], ], @@ -239,6 +240,51 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb )->addCacheableDependency($this); } + /** + * Uses heuristics to compute the appropriate "library" in the XB UI. + * + * Each Component appears in a well-defined "library" in the XB UI. This is a + * set of heuristics with a particular decision tree. + * + * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15997505 + */ + private function computeUiLibrary(): LibraryEnum { + $config = \Drupal::configFactory()->loadMultiple(['core.extension', 'system.theme']); + $installed_modules = [ + 'core', + ...array_keys($config['core.extension']->get('module')) + ]; + // @see \Drupal\Core\Extension\ThemeHandler::getDefault() + $default_theme = $config['system.theme']->get('default'); + + // 1. Is the component dynamic (consumes implicit inputs/context or has + // logic)? + if ($this->getComponentSource()->getPluginDefinition()['supportsImplicitInputs']) { + return LibraryEnum::DynamicComponents; + } + + // 2. Is the component provided by a module? + if (in_array($this->provider, $installed_modules, TRUE)) { + return $this->provider === 'experience_builder' + // 2.B I the providing module XB? + ? LibraryEnum::Elements + : LibraryEnum::ExtensionComponents; + } + + // 3. Is the component provided by the default theme (or its base theme)? + if ($this->provider === $default_theme) { + return LibraryEnum::PrimaryComponents; + } + + // 4. Is the component provided by neither a theme nor a module? + if ($this->provider === NULL) { + return LibraryEnum::PrimaryComponents; + } + + return LibraryEnum::None; + } + + /** * {@inheritdoc} * diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php index 64aa781c74..a9ae5fadf8 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php @@ -22,7 +22,6 @@ use Drupal\Core\TypedData\Plugin\DataType\BooleanData; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\experience_builder\Attribute\ComponentSource; use Drupal\experience_builder\ComponentSource\ComponentSourceBase; -use Drupal\experience_builder\Controller\LibraryEnum; use Drupal\experience_builder\Entity\Component; use Drupal\experience_builder\Entity\Component as ComponentEntity; use Drupal\experience_builder\MissingComponentInputsException; @@ -362,11 +361,4 @@ final class BlockComponent extends ComponentSourceBase implements ContainerFacto // @todo Move logic from experience_builder_block_alter here in https://www.drupal.org/project/experience_builder/issues/3491032 } - /** - * {@inheritdoc} - */ - public function getLibrary(): LibraryEnum { - return LibraryEnum::DynamicComponents; - } - } diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php index 2d631e001e..19c12514ee 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/JsComponent.php @@ -11,7 +11,6 @@ use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\experience_builder\Attribute\ComponentSource; use Drupal\experience_builder\ComponentDoesNotMeetRequirementsException; use Drupal\experience_builder\ComponentMetadataRequirementsChecker; -use Drupal\experience_builder\Controller\LibraryEnum; use Drupal\experience_builder\Entity\Component as ComponentEntity; use Drupal\experience_builder\Entity\ComponentInterface; use Drupal\experience_builder\Entity\JavaScriptComponent; @@ -229,11 +228,4 @@ final class JsComponent extends GeneratedFieldExplicitInputUxComponentSourceBase ); } - /** - * {@inheritdoc} - */ - public function getLibrary(): LibraryEnum { - return LibraryEnum::PrimaryComponents; - } - } diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php index 86fe41d206..de628fd338 100644 --- a/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php +++ b/src/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponent.php @@ -16,7 +16,6 @@ use Drupal\Core\Theme\ComponentPluginManager; use Drupal\Core\Theme\ExtensionType; use Drupal\experience_builder\Attribute\ComponentSource; use Drupal\experience_builder\ComponentSource\UrlRewriteInterface; -use Drupal\experience_builder\Controller\LibraryEnum; use Drupal\experience_builder\Entity\Component as ComponentEntity; use Drupal\experience_builder\Plugin\ComponentPluginManager as XbComponentPluginManager; use Drupal\experience_builder\ShapeMatcher\FieldForComponentSuggester; @@ -313,24 +312,4 @@ final class SingleDirectoryComponent extends GeneratedFieldExplicitInputUxCompon return \base_path() . $path; } - /** - * {@inheritdoc} - */ - public function getLibrary(): LibraryEnum { - $base_id = $this->getComponentPlugin()->getBaseId(); - - if ($this->moduleHandler->moduleExists($base_id)) { - return $base_id === 'experience_builder' ? LibraryEnum::Elements : LibraryEnum::ExtensionComponents; - } - - if ($this->themeHandler->getDefault() === $base_id) { - return LibraryEnum::PrimaryComponents; - } - - // If component is provided by non-default theme it should not be assigned - // 'primary_components' library value. - // @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15997374 - return LibraryEnum::None; - } - } diff --git a/tests/src/Functional/PropSourceEndpointTest.php b/tests/src/Functional/PropSourceEndpointTest.php index e337ca24b2..3fa0727d9b 100644 --- a/tests/src/Functional/PropSourceEndpointTest.php +++ b/tests/src/Functional/PropSourceEndpointTest.php @@ -122,7 +122,7 @@ class PropSourceEndpointTest extends FunctionalTestBase { $this->assertArrayHasKey('name', $component); $this->assertArrayHasKey('category', $component); $this->assertArrayHasKey('source', $component); - $this->assertArrayHasKey('provider', $component); + $this->assertArrayHasKey('library', $component, $id); $this->assertArrayHasKey('default_markup', $component); $this->assertArrayHasKey('css', $component); $this->assertArrayHasKey('js_header', $component); -- GitLab From 357e7979e0c42ea5fd1f3f8a71ef510479ba0435 Mon Sep 17 00:00:00 2001 From: Wim Leers <wim.leers@acquia.com> Date: Fri, 21 Feb 2025 11:55:30 +0100 Subject: [PATCH 22/30] `phpcs` + `phpstan` --- src/Entity/Component.php | 3 +-- tests/src/Kernel/Config/ComponentValidationTest.php | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Entity/Component.php b/src/Entity/Component.php index 572db7ce8f..ba3603940f 100644 --- a/src/Entity/Component.php +++ b/src/Entity/Component.php @@ -252,7 +252,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb $config = \Drupal::configFactory()->loadMultiple(['core.extension', 'system.theme']); $installed_modules = [ 'core', - ...array_keys($config['core.extension']->get('module')) + ...array_keys($config['core.extension']->get('module')), ]; // @see \Drupal\Core\Extension\ThemeHandler::getDefault() $default_theme = $config['system.theme']->get('default'); @@ -284,7 +284,6 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb return LibraryEnum::None; } - /** * {@inheritdoc} * diff --git a/tests/src/Kernel/Config/ComponentValidationTest.php b/tests/src/Kernel/Config/ComponentValidationTest.php index 08837eeed0..7680c983a8 100644 --- a/tests/src/Kernel/Config/ComponentValidationTest.php +++ b/tests/src/Kernel/Config/ComponentValidationTest.php @@ -51,8 +51,6 @@ class ComponentValidationTest extends ConfigEntityValidationTestBase { /** * {@inheritdoc} - * - * @phpstan-ignore property.defaultValue */ protected static array $propertiesWithOptionalValues = [ 'provider', -- GitLab From eb1aa57cece2b40b758cf2c9ee6a675eb559246f Mon Sep 17 00:00:00 2001 From: Wim Leers <wim.leers@acquia.com> Date: Fri, 21 Feb 2025 11:23:36 +0100 Subject: [PATCH 23/30] `NOT IN` apparently excludes `NULL` values, so change to using `::orConditionGroup()`. --- src/Entity/Component.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Entity/Component.php b/src/Entity/Component.php index ba3603940f..5f86e1fc71 100644 --- a/src/Entity/Component.php +++ b/src/Entity/Component.php @@ -306,7 +306,10 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb // - module-provided ones // - default theme-provided // - provided by something else than an extension, such as an entity. - $query->condition('provider', array_diff($installed_themes, [$default_theme]), 'NOT IN'); + $or_group = $query->orConditionGroup() + ->condition('provider', operator: 'NOT IN', value: array_diff($installed_themes, [$default_theme])) + ->condition('provider', operator: 'IS NULL'); + $query->condition($or_group); } /** -- GitLab From c01e7be200dde495bc8c51933c0f46860919e6e8 Mon Sep 17 00:00:00 2001 From: Wim Leers <wim.leers@acquia.com> Date: Fri, 21 Feb 2025 10:54:31 +0100 Subject: [PATCH 24/30] Query cacheability must be refined, to match the config entity query being refined. --- src/Controller/ApiConfigControllers.php | 6 +++--- src/Entity/AssetLibrary.php | 3 ++- src/Entity/Component.php | 12 +++++++++++- src/Entity/JavaScriptComponent.php | 3 ++- src/Entity/Pattern.php | 3 ++- .../XbHttpApiEligibleConfigEntityInterface.php | 6 +++++- tests/src/Functional/PropSourceEndpointTest.php | 2 ++ 7 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/Controller/ApiConfigControllers.php b/src/Controller/ApiConfigControllers.php index c64ae7a5c0..62d02fc2ce 100644 --- a/src/Controller/ApiConfigControllers.php +++ b/src/Controller/ApiConfigControllers.php @@ -84,13 +84,13 @@ final class ApiConfigControllers extends ApiControllerBase { if (!$xb_config_entity_type->get('xb_visible_when_disabled')) { $query->condition('status', TRUE); } - $xb_config_entity_type->getClass()::refineListQuery($query); - /** @var array<\Drupal\experience_builder\Entity\XbHttpApiEligibleConfigEntityInterface> $config_entities */ - $config_entities = $storage->loadMultiple($query->execute()); $query_cacheability = (new CacheableMetadata()) ->addCacheContexts($xb_config_entity_type->getListCacheContexts()) ->addCacheTags($xb_config_entity_type->getListCacheTags()); + $xb_config_entity_type->getClass()::refineListQuery($query, $query_cacheability); + /** @var array<\Drupal\experience_builder\Entity\XbHttpApiEligibleConfigEntityInterface> $config_entities */ + $config_entities = $storage->loadMultiple($query->execute()); $normalizations = []; $normalizations_cacheability = new CacheableMetadata(); diff --git a/src/Entity/AssetLibrary.php b/src/Entity/AssetLibrary.php index 155fb025b1..b31e58e6d9 100644 --- a/src/Entity/AssetLibrary.php +++ b/src/Entity/AssetLibrary.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Drupal\experience_builder\Entity; +use Drupal\Core\Cache\RefinableCacheableDependencyInterface; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\Query\QueryInterface; use Drupal\experience_builder\ClientSideRepresentation; @@ -77,7 +78,7 @@ final class AssetLibrary extends ConfigEntityBase implements XbHttpApiEligibleCo /** * {@inheritdoc} */ - public static function refineListQuery(QueryInterface &$query): void { + public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void { // Nothing to do. } diff --git a/src/Entity/Component.php b/src/Entity/Component.php index 5f86e1fc71..4ec4183771 100644 --- a/src/Entity/Component.php +++ b/src/Entity/Component.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Drupal\experience_builder\Entity; +use Drupal\Core\Cache\RefinableCacheableDependencyInterface; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\Query\QueryInterface; use Drupal\Core\Extension\ModuleHandlerInterface; @@ -296,11 +297,12 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb /** * {@inheritdoc} */ - public static function refineListQuery(QueryInterface &$query): void { + public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void { $container = \Drupal::getContainer(); $theme_handler = $container->get(ThemeHandlerInterface::class); $installed_themes = array_keys($theme_handler->listInfo()); $default_theme = $theme_handler->getDefault(); + // Omit Components provided by installed-but-not-default themes. This keeps // all other Components: // - module-provided ones @@ -310,6 +312,14 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb ->condition('provider', operator: 'NOT IN', value: array_diff($installed_themes, [$default_theme])) ->condition('provider', operator: 'IS NULL'); $query->condition($or_group); + + // Reflect the conditions added to the query in the cacheability. + $cacheablity->addCacheTags([ + // The set of installed themes is stored in the `core.extension` config. + 'config:core.extension', + // The default theme is stored in the `system.theme` config. + 'config:system.theme', + ]); } /** diff --git a/src/Entity/JavaScriptComponent.php b/src/Entity/JavaScriptComponent.php index 1c3730ae2c..036de9c7b4 100644 --- a/src/Entity/JavaScriptComponent.php +++ b/src/Entity/JavaScriptComponent.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Drupal\experience_builder\Entity; +use Drupal\Core\Cache\RefinableCacheableDependencyInterface; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\Query\QueryInterface; use Drupal\experience_builder\ClientSideRepresentation; @@ -143,7 +144,7 @@ final class JavaScriptComponent extends ConfigEntityBase implements XbHttpApiEli /** * {@inheritdoc} */ - public static function refineListQuery(QueryInterface &$query): void { + public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void { // Nothing to do. } diff --git a/src/Entity/Pattern.php b/src/Entity/Pattern.php index 5d3c6d3d77..6e8ec55e69 100644 --- a/src/Entity/Pattern.php +++ b/src/Entity/Pattern.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Drupal\experience_builder\Entity; use Drupal\Component\Utility\Random; +use Drupal\Core\Cache\RefinableCacheableDependencyInterface; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\Query\QueryInterface; @@ -167,7 +168,7 @@ final class Pattern extends ConfigEntityBase implements XbHttpApiEligibleConfigE /** * {@inheritdoc} */ - public static function refineListQuery(QueryInterface &$query): void { + public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void { // Nothing to do. } diff --git a/src/Entity/XbHttpApiEligibleConfigEntityInterface.php b/src/Entity/XbHttpApiEligibleConfigEntityInterface.php index 3ed8183af8..e0d5d37ee5 100644 --- a/src/Entity/XbHttpApiEligibleConfigEntityInterface.php +++ b/src/Entity/XbHttpApiEligibleConfigEntityInterface.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Drupal\experience_builder\Entity; +use Drupal\Core\Cache\RefinableCacheableDependencyInterface; use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Entity\Query\QueryInterface; use Drupal\experience_builder\ClientSideRepresentation; @@ -39,9 +40,12 @@ interface XbHttpApiEligibleConfigEntityInterface extends ConfigEntityInterface { * * @param \Drupal\Core\Entity\Query\QueryInterface $query * The config entity query to refine, passed by reference. + * @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $cacheablity + * The cacheability of the given query, to be refined to match the + * refinements made to the query. * * @return void */ - public static function refineListQuery(QueryInterface &$query): void; + public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void; } diff --git a/tests/src/Functional/PropSourceEndpointTest.php b/tests/src/Functional/PropSourceEndpointTest.php index 3fa0727d9b..dffb567664 100644 --- a/tests/src/Functional/PropSourceEndpointTest.php +++ b/tests/src/Functional/PropSourceEndpointTest.php @@ -58,12 +58,14 @@ class PropSourceEndpointTest extends FunctionalTestBase { 'announcements_feed:feed', 'comment_list', 'config:component_list', + 'config:core.extension', 'config:search.settings', 'config:system.menu.account', 'config:system.menu.admin', 'config:system.menu.footer', 'config:system.menu.main', 'config:system.site', + 'config:system.theme', 'config:views.view.comments_recent', 'config:views.view.content_recent', 'config:views.view.who_s_new', -- GitLab From 5bdbe220da2fed4bc60e5b4e22f6058388953de6 Mon Sep 17 00:00:00 2001 From: Wim Leers <wim.leers@acquia.com> Date: Fri, 21 Feb 2025 12:35:02 +0100 Subject: [PATCH 25/30] =?UTF-8?q?Spelling=20=F0=9F=99=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Entity/AssetLibrary.php | 2 +- src/Entity/Component.php | 4 ++-- src/Entity/JavaScriptComponent.php | 2 +- src/Entity/Pattern.php | 2 +- src/Entity/XbHttpApiEligibleConfigEntityInterface.php | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Entity/AssetLibrary.php b/src/Entity/AssetLibrary.php index b31e58e6d9..727266ced6 100644 --- a/src/Entity/AssetLibrary.php +++ b/src/Entity/AssetLibrary.php @@ -78,7 +78,7 @@ final class AssetLibrary extends ConfigEntityBase implements XbHttpApiEligibleCo /** * {@inheritdoc} */ - public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void { + public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheability): void { // Nothing to do. } diff --git a/src/Entity/Component.php b/src/Entity/Component.php index 4ec4183771..bd81f78286 100644 --- a/src/Entity/Component.php +++ b/src/Entity/Component.php @@ -297,7 +297,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb /** * {@inheritdoc} */ - public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void { + public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheability): void { $container = \Drupal::getContainer(); $theme_handler = $container->get(ThemeHandlerInterface::class); $installed_themes = array_keys($theme_handler->listInfo()); @@ -314,7 +314,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb $query->condition($or_group); // Reflect the conditions added to the query in the cacheability. - $cacheablity->addCacheTags([ + $cacheability->addCacheTags([ // The set of installed themes is stored in the `core.extension` config. 'config:core.extension', // The default theme is stored in the `system.theme` config. diff --git a/src/Entity/JavaScriptComponent.php b/src/Entity/JavaScriptComponent.php index 036de9c7b4..898c6135a8 100644 --- a/src/Entity/JavaScriptComponent.php +++ b/src/Entity/JavaScriptComponent.php @@ -144,7 +144,7 @@ final class JavaScriptComponent extends ConfigEntityBase implements XbHttpApiEli /** * {@inheritdoc} */ - public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void { + public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheability): void { // Nothing to do. } diff --git a/src/Entity/Pattern.php b/src/Entity/Pattern.php index 6e8ec55e69..c5afbd8d1d 100644 --- a/src/Entity/Pattern.php +++ b/src/Entity/Pattern.php @@ -168,7 +168,7 @@ final class Pattern extends ConfigEntityBase implements XbHttpApiEligibleConfigE /** * {@inheritdoc} */ - public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void { + public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheability): void { // Nothing to do. } diff --git a/src/Entity/XbHttpApiEligibleConfigEntityInterface.php b/src/Entity/XbHttpApiEligibleConfigEntityInterface.php index e0d5d37ee5..369433cd00 100644 --- a/src/Entity/XbHttpApiEligibleConfigEntityInterface.php +++ b/src/Entity/XbHttpApiEligibleConfigEntityInterface.php @@ -40,12 +40,12 @@ interface XbHttpApiEligibleConfigEntityInterface extends ConfigEntityInterface { * * @param \Drupal\Core\Entity\Query\QueryInterface $query * The config entity query to refine, passed by reference. - * @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $cacheablity + * @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $cacheability * The cacheability of the given query, to be refined to match the * refinements made to the query. * * @return void */ - public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheablity): void; + public static function refineListQuery(QueryInterface &$query, RefinableCacheableDependencyInterface $cacheability): void; } -- GitLab From 9fbb29cb60cdae802030908eb433e5b8635690b2 Mon Sep 17 00:00:00 2001 From: Wim Leers <wim.leers@acquia.com> Date: Fri, 21 Feb 2025 12:58:03 +0100 Subject: [PATCH 26/30] Add test coverage for changing the default theme. --- .../src/Functional/PropSourceEndpointTest.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/src/Functional/PropSourceEndpointTest.php b/tests/src/Functional/PropSourceEndpointTest.php index dffb567664..b536f582b1 100644 --- a/tests/src/Functional/PropSourceEndpointTest.php +++ b/tests/src/Functional/PropSourceEndpointTest.php @@ -133,6 +133,10 @@ class PropSourceEndpointTest extends FunctionalTestBase { $this->assertStringStartsWith('<!-- xb-start-', $data['block.system_menu_block.main']['default_markup']); $this->assertStringContainsString('--><nav role="navigation"', $data['block.system_menu_block.main']['default_markup']); + // Stark has no SDCs. + $this->assertSame('stark', $this->config('system.theme')->get('default')); + $this->assertArrayNotHasKey('sdc.olivero.teaser', $data); + $data = array_intersect_key( $data, [ @@ -166,6 +170,30 @@ class PropSourceEndpointTest extends FunctionalTestBase { self::assertEquals($extractValue, $data['sdc.sdc_test_all_props.all-props']['transforms']['test_integer']); self::assertEquals(['link' => []], $data['sdc.sdc_test_all_props.all-props']['transforms']['test_string_format_uri']); self::assertEquals(['mediaSelection' => [], 'mainProperty' => ['name' => 'target_id']], $data['sdc.sdc_test_all_props.all-props']['transforms']['test_object_drupal_image']); + + // Olivero does have an SDC, and it's enabled, but it is omitted because the + // default theme is Stark. + $this->assertInstanceOf(Component::class, Component::load('sdc.olivero.teaser')); + $this->assertTrue(Component::load('sdc.olivero.teaser')->status()); + $this->assertSame('olivero', Component::load('sdc.olivero.teaser')->get('provider')); + + // Change the default theme from Stark to Olivero, and observe the impact on + // the list of Components returned. + \Drupal::configFactory()->getEditable('system.theme')->set('default', 'olivero')->save(); + $this->rebuildAll(); + $this->drupalGet('xb/api/config/component'); + $data = Json::decode($page->getText()); + $this->assertSession()->responseHeaderEquals('X-Drupal-Dynamic-Cache', 'MISS'); + $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'UNCACHEABLE (request policy)'); + // Olivero does have an SDC! + $this->assertSame('olivero', $this->config('system.theme')->get('default')); + $this->assertArrayHasKey('sdc.olivero.teaser', $data); + // Repeated request is again a Dynamic Page Cache hit. + $this->drupalGet('xb/api/config/component'); + $this->assertSession()->responseHeaderEquals('X-Drupal-Dynamic-Cache', 'HIT'); + $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'UNCACHEABLE (request policy)'); + + } /** -- GitLab From 00d3f751f036cc45aa96c010368d31f9aecc16c2 Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <56971-f.mazeikis@users.noreply.drupalcode.org> Date: Fri, 21 Feb 2025 12:15:45 +0000 Subject: [PATCH 27/30] Apply 3 suggestion(s) to 3 file(s) Co-authored-by: Wim Leers <44946-wimleers@users.noreply.drupalcode.org> --- src/Controller/LibraryEnum.php | 1 - src/Entity/Component.php | 2 +- tests/src/Functional/PropSourceEndpointTest.php | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Controller/LibraryEnum.php b/src/Controller/LibraryEnum.php index feaabc0675..b943af1691 100644 --- a/src/Controller/LibraryEnum.php +++ b/src/Controller/LibraryEnum.php @@ -14,5 +14,4 @@ enum LibraryEnum: string { case DynamicComponents = 'dynamic_components'; case PrimaryComponents = 'primary_components'; case None = ''; - } diff --git a/src/Entity/Component.php b/src/Entity/Component.php index bd81f78286..9ab2d4f49f 100644 --- a/src/Entity/Component.php +++ b/src/Entity/Component.php @@ -282,7 +282,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb return LibraryEnum::PrimaryComponents; } - return LibraryEnum::None; + throw new \LogicException('A Component is being normalized that belongs in no XB UI library.'); } /** diff --git a/tests/src/Functional/PropSourceEndpointTest.php b/tests/src/Functional/PropSourceEndpointTest.php index b536f582b1..1e5b07dcf0 100644 --- a/tests/src/Functional/PropSourceEndpointTest.php +++ b/tests/src/Functional/PropSourceEndpointTest.php @@ -192,8 +192,6 @@ class PropSourceEndpointTest extends FunctionalTestBase { $this->drupalGet('xb/api/config/component'); $this->assertSession()->responseHeaderEquals('X-Drupal-Dynamic-Cache', 'HIT'); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'UNCACHEABLE (request policy)'); - - } /** -- GitLab From ab9af541996a61fbe74aed0d55aba8ad7b63ccdc Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <felix.mazeikis@acquia.com> Date: Fri, 21 Feb 2025 13:11:38 +0000 Subject: [PATCH 28/30] Move LibraryEnum and remove None case --- src/Entity/Component.php | 1 - src/{Controller => Entity}/LibraryEnum.php | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) rename src/{Controller => Entity}/LibraryEnum.php (86%) diff --git a/src/Entity/Component.php b/src/Entity/Component.php index 9ab2d4f49f..1aa4a29478 100644 --- a/src/Entity/Component.php +++ b/src/Entity/Component.php @@ -15,7 +15,6 @@ use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\experience_builder\ClientSideRepresentation; use Drupal\experience_builder\ComponentSource\ComponentSourceInterface; use Drupal\experience_builder\ComponentSource\ComponentSourceManager; -use Drupal\experience_builder\Controller\LibraryEnum; /** * @todo Update these docs in https://drupal.org/i/3454519 to reflect changes. diff --git a/src/Controller/LibraryEnum.php b/src/Entity/LibraryEnum.php similarity index 86% rename from src/Controller/LibraryEnum.php rename to src/Entity/LibraryEnum.php index b943af1691..206d7e97c8 100644 --- a/src/Controller/LibraryEnum.php +++ b/src/Entity/LibraryEnum.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Drupal\experience_builder\Controller; +namespace Drupal\experience_builder\Entity; /** * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15988043 @@ -13,5 +13,4 @@ enum LibraryEnum: string { case ExtensionComponents = 'extension_components'; case DynamicComponents = 'dynamic_components'; case PrimaryComponents = 'primary_components'; - case None = ''; } -- GitLab From d5a78b9ebace315dbc835464de1eca64920f7dcc Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <56971-f.mazeikis@users.noreply.drupalcode.org> Date: Fri, 21 Feb 2025 13:17:47 +0000 Subject: [PATCH 29/30] Apply 1 suggestion(s) to 1 file(s) Co-authored-by: Wim Leers <44946-wimleers@users.noreply.drupalcode.org> --- src/Entity/Component.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entity/Component.php b/src/Entity/Component.php index 1aa4a29478..62fcf3facf 100644 --- a/src/Entity/Component.php +++ b/src/Entity/Component.php @@ -266,7 +266,7 @@ final class Component extends ConfigEntityBase implements ComponentInterface, Xb // 2. Is the component provided by a module? if (in_array($this->provider, $installed_modules, TRUE)) { return $this->provider === 'experience_builder' - // 2.B I the providing module XB? + // 2.B Is the providing module XB? ? LibraryEnum::Elements : LibraryEnum::ExtensionComponents; } -- GitLab From 2da72b7ac89e829ee9d5357c4e4651ca41387958 Mon Sep 17 00:00:00 2001 From: Feliksas Mazeikis <56971-f.mazeikis@users.noreply.drupalcode.org> Date: Fri, 21 Feb 2025 14:20:20 +0000 Subject: [PATCH 30/30] Apply 1 suggestion(s) to 1 file(s) Co-authored-by: Wim Leers <44946-wimleers@users.noreply.drupalcode.org> --- src/Entity/LibraryEnum.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Entity/LibraryEnum.php b/src/Entity/LibraryEnum.php index 206d7e97c8..2d05f9ce4d 100644 --- a/src/Entity/LibraryEnum.php +++ b/src/Entity/LibraryEnum.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace Drupal\experience_builder\Entity; /** - * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15988043 - * @see https://www.drupal.org/project/experience_builder/issues/3498419#comment-15989118 + * @internal + * @see \Drupal\experience_builder\Entity\Component::computeUiLibrary() */ enum LibraryEnum: string { case Elements = 'elements'; -- GitLab