daniel
Daniel Rotter
Core developer and support guru. Passionate traveler and soccer player.
@danrot90

Sulu 2.0.0-alpha6 released

After a weeks have passed since our last alpha release and here comes the next one. This blog post will explain the newly added features of our sixth alpha of Sulu 2.0.

TeaserSelection

The TeaserSelection content type has around for quite a while (to be exact since Sulu 1.4) and we also published a blog post back then describing its features. Quickly explained, it allows to assign different kinds of content (e.g. articles from the SuluArticleBundle) to a single location and use all their data encapsulated behind a standardized interface. This makes building stuff like a slider very comfortable! This feature has now also been ported to Sulu 2.0.

Nesting properties in form by name

This is a very interesting feature. Our form component allows every field to only return exactly one value. The problem is that if you have e.g. two text fields, which you want to be grouped in the resulting JSON object, then you always had to build your own field type. This is a bit cumbersome and means that you have to write JavaScript, something that we want to avoid as much as possible. Therefore we have decided to introduce a new feature for handling this use case.

This resulted in an implementation that uses the name of the property as a JSON pointer. This standard describes a way to access values on a JSON object using a pointer, which is nothing more than the path to the property joined together by slashes.

The following code shows a small example of such a pointer.

<?xml version="1.0" ?>
<form xmlns="http://schemas.sulu.io/template/template"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/form-1.0.xsd"
>
    <key>pointer_example</key>

    <properties>
        <property name="title" type="text_line" />
        <property name="sub/value" type="text_line" />
        <property name="sub/title" type="text_line" />
    </properties>
</form>
Nesting properties in form by name

The form rendered by the XML above will return a JSON object like the following, if it is filled with some data.

{
    "title": "Title",
    "sub": {
        "value": "Value",
        "title": "Title"
    }
}

We needed this feature in order to fix another issue in the pages. If you've had a closer look at the network requests when switchting between the excerpt and SEO tab in the page form, then you've recognized that two request were made: One loaded the entire page and another one loaded only the SEO resp. excerpt data. This caused some inconsistencies which have now been solved by moving the data into a separate property of the request loading the entire page.

And as a nice side effect of resolving these inconsistencies, we have now only a single request for both of these tabs.

FormOverlayList

In the old UI we had lists for analytics keys and custom URLs which opened entities in an overlay instead of redirecting to a new URL and showing a form using the entire window. These two entities now work the same way as before, so we've decided to extract that functionality in a separate component. This feature is also very useful when you have sub entities, because you can open a form in an overlay, if you are already in another form.

Defining such a FormOverlayList is possible by defining a route like this:

<?php
class CustomUrlAdmin extends Admin
{
    private $routeBuilderFactory;

    public function __construct(
        RouteBuilderFactoryInterface $routeBuilderFactory
    ) {
        $this->routeBuilderFactory = $routeBuilderFactory;
    }

    public function getRoutes(): array
    {
        $listToolbarActions = [
            'sulu_admin.add',
            'sulu_admin.delete',
        ];

        return [
            $this->routeBuilderFactory
                ->createFormOverlayListRouteBuilder('sulu_custom_url.custom_urls_list', '/custom-urls')
                ->setResourceKey('custom_urls')
                ->setListKey('custom_urls')
                ->addListAdapters(['table_light'])
                ->addRouterAttributesToListStore(['webspace'])
                ->addRouterAttributesToFormStore(['webspace'])
                ->disableSearching()
                ->setFormKey('custom_url_details')
                ->setTabTitle('sulu_custom_url.custom_urls')
                ->addToolbarActions($listToolbarActions)
                ->setTabOrder(1024)
                ->setParent(PageAdmin::WEBSPACE_TABS_ROUTE) 
                ->addRerenderAttribute('webspace')
                ->getRoute(),
        ];
    }
}
FormOverlayList

As you can see this example has been taken from our Custom URLs, which have also been added in this release. There are three important method calls to note here. The resourceKey in the "setResourceKey" call defines what data we are trying to show in a list resp. in a form. And to show the correct list resp. form we need to set the keys in the "setListKey" and "setFormKey".

Custom URLs

Custom URLs allow to add shorter URLs to pages, eventually lying deep in the tree. This feature made use of the previously described FormOverlayLists and can now be found in a dedicated tab of the webspace section (it has been moved from the webspace navigation of the old UI as this does not exist anymore).

Analytics

The analytics section is very similar to the CustomURLs. They also use a FormOverlayList and are in a tab next to the CustomURLs, the only thing differing between them from a UI perspective is that they use different metadata.

The functionality is exactly the same as in the 1.x series: They allow to put predefined analytics services like Google Analystics or Matomo on every single page handle by Sulu. In addition to that you can also define custom analytic scripts, which are JavaScript codes that can be placed on different places of the website (start of head, end of head, start of body, end of body).

Default Snippets

This is the last tab of the webspace section and allows to define a default for different snippet areas which then can be loaded using a twig extension. This makes it easy to e.g. define a sidebar that should appear on every page. The administration part has been developed as a custom view because the behavior of it is different from existing views. This is also a nice example to show that adding a custom view is a pretty straightforward process.

History URLs

The overlay showing the old URLs of a page was placed right next to the URL field of the page form. We have placed it at the exact same spot in the new UI and reused the same component in the overlay form for CustomURLs as well.

Support systems without GLOB_BRACE

We've had some issues when running Sulu on AlpineLinux because in certain situations the GLOB_BRACE constant is not available in PHP. The GlobResource class from Symfony thankfully handles a fallback mechanism to a different method. Thank you Tom for contributing this bugfix!

Plugins for CKEditor

The CKEditor was missing some features compared to the old UI. These have now been implemented as plugins in CKEditor5 and allow linking to external URLs as well as to internal pages and media assets using some dedicated buttons. The latter option can also be extended by other bundles to allow internal links to other entities as well. In the future e.g. the ArticleBundle will also make use of this extension point.

Renaming content types

We are always working on making our system more consistent. A very important part of that are the names under which our content types are registered. There is a long-running issue describing how we want to name these content types and in this release we have finally adapted the names of the internal_links and the snippet content type. Since we are about to rename all content types referencing other entities to end with "selection", we renamed the previously mentioned content types to page_selection resp. snippet_selection

Summary

Apart from the bigger features I have mentioned, plenty small bug fixes and smaller features haven been implemented into this new version. These include a search field in the overlay for selecting other entities (e.g. in the snippet_selection content type), fixing the generation of URLs for ghost pages, the possibility to unpublish pages, the taxonomy tab for snippets and a new field type for selecting a single account.

This list is by far not complete, so if you are interestedin more information I would suggest you check out our release notes on GitHub. And as always, we would be happy to hear your feedback!

Smaller features

  • Search field in ListOverlay
  • Generate URLs for ghost pages
  • Unpublish page
  • Snippet Taxonomies
  • Single Account Selection