Roadiz Versions Save

Roadiz is a polymorphic CMS based on a node system which can handle many types of services. This is v1 repository, for v2 and newer releases check https://github.com/roadiz/skeleton.

v1.7.0

2 years ago

Hit the road for Symfony full-stack

This version should be the last one using Symfony components and Pimple as a dependency injection container. Last versions pushed forward compatibility with Symfony framework by making all Roadiz components more independant from the DI container. And this release keeps on decoupling our architecture.

If you want to move to our next Roadix major version using Symfony framework, you should upgrade to v1.7 first and resolve any deprecations. Use Controller services instead of using $this->get('service') or worse $this->container['service'] syntax.

This update requires composer.json changes and performing database migrations, make sure to backup your websites before upgrading.

New Webhook component

Roadiz can now send webhook at content update to any location (we provide Gitlab CI and Netlify webhook consumers). Webhooks can be trigger manually, at content update or scoped for any node inside a given root node. This new feature relies on Symfony Messenger and RateLimiter components.

  • Added Webhooks node root to scope triggered events, add GenericJsonPostMessage

Bye bye Rozier

Backoffice theme has been removed from Roadiz main repository and it is a Composer dependency in order to allow using it into next Roadiz versions with Symfony framework. If you are using standard-edition or headless-edition make sure to require it in your project: composer require roadiz/rozier

  • Moved default backoffice services from BackendController to RozierServiceProvider
  • We moved every EventSubscriber from backoffice theme back to core because they're essential to Roadiz Core behaviour
  • Moved Rozier Explorer abstracts and interfaces symbols to RZ/Roadiz/Explorer namespace, make sure to update your project if you use/implement explorers
  • New document listing mode 95d2ee09319941bd83aa42bbb49adc47

Headless edition Docker image

Headless edition has now a dedicated Docker image to use it with no code at-all! This is handy if you do not need any custom features and a quick start:

https://github.com/roadiz/headless-edition/tree/develop#usage

Major changes

  • Added new Symfony Messenger to allow sync/async operations for webhooks, search-engine indexing
  • Added new Controller method isCsrfTokenValid(string $id, ?string $token): bool
  • Added new Controller method getSettingsBag(): Settings
  • Added new Controller method dispatchEvent($event)
  • Added new Controller method redirectToRoute($route, array $parameters = [], int $status = 302): RedirectResponse
  • We use Psr\Container\ContainerInterface every time it's possible instead of Pimple\Container to reuse Roadiz components in Symfony Framework context
  • We type-hinted lots of parameters and method return types. You may have to upgrade your project methods signatures
  • Added NodeTypeField Serializable fields and NodeType searchable field to prevent a node-source title to be indexed in Solr.
  • Node-type fields can be configured to allow or prevent JSON serialization: added NodeTypeFieldSerializationType and translation messages
  • Fix: we fixed the way documents are linked to NodesSources to avoid duplicated (back-ported on v1.6)
  • Solr indexers have been rewritten to cleanup subscribers, commands and allow async indexing (with Symfony messenger).
  • NodeUpdatedEvent and NodeStatusChangedEvent are no longer dispatch when changing node status. You must rely on workflow.node.* events.
  • Added new ManagerRegistry to replace EntityManager and allow smoother transition to Symfony framework. Classes constructor signatures may change.
  • Roadiz kernel now implements Symfony\Component\HttpKernel\KernelInterface
  • Changed createForm signature to match Symfony Framework
  • Removed contructor Container dependency for Models, inject services directly.
  • Use ObjectManager when possible instead of EntityManager
  • Typed Controller param and methods, you may have to type your own theme parameters
-    public static $priority = 0;
+    public static int $priority = 0;
-    protected static $themeName = '';
+    protected static string $themeName = '';
-    protected static $themeAuthor = '';
+    protected static string $themeAuthor = '';
-    protected static $themeCopyright = '';
+    protected static string $themeCopyright = '';
-    protected static $themeDir = '';
+    protected static string $themeDir = '';
-    protected static $backendTheme = false;
+    protected static bool $backendTheme = false;

Minor changes

  • Backoffice section definition was removed from webhook namespace to prevent any dependencies from Core to Rozier theme.
  • Added SettingTypeResolver to remove Symfony Form types definitions from Entity
  • Remove deprecated Security classes
  • Added meta as Twig Globals, fixed Install setup
  • Removed static_domain_name database setting, use staticDomainName in YAML configuration to prevent database access to initialize assets packages
  • Remove em service usage as much as possible, i.e. in TreeWidgets. ConsoleCommands
  • Moved Document related event subscribers to roadiz/document package
  • Replaced Splashbase service with Unsplash, this requires an API key (stored in Roadiz settings)
  • Added new documents:prune:orphans command to remove document from database when no file has been found on file-system (except for embeds)
  • Do not prevent ExceptionSubscriber to be executed in debug mode and JSON format responses
  • Additional database table indexes
  • Add Document::isLocal method to test if filename and folder are not empty
  • Added custom_public_scheme setting for headless projects
  • Documents: added new DisplayableInterface
  • Documents: added RandomImageFinder service to allow different random image service in your project
  • Documents: AbstractSplashbasePictureFinder is deprecated as splashbase.co is not available anymore
  • OpenID: removed settingsBag usage for main configuration, Openid provider throws UsernameNotFoundException to allow ChainUserProvider to test other user providers.
  • Markdown: update to commonmark v2
  • Added new roadiz/dts-generator to generate Typescript type definition from your node-types.

And after?

Roadiz v2 will be a symfony-pack to be added to any existing Symfony 5.3+ application. It will composed of a roadiz/core-bundle and roadiz/compat-bundle + roadiz/rozier-bundle to ensure compatibility with existing themes (such as our Backoffice theme) in the first time. Then new projects will be able to use App logic instead of themes and even API-platform. All Roadiz sub-packages: models, documents, entity-generator, markdown, nodetype-contracts, openid, random will still be used as framework agnostic package between v1.x and v2.x until v1.x is obsolete.

Our next steps are to migrate latest projects and our most important middleware themes (AbstractBlogTheme, AbstractApiTheme) to this new architecture. This will allow Roadiz to benefit from the awesome Symfony bundle community. Then in the long-term we plan to re-develop our backoffice theme with new user-interface and better features, taking advantage of headless architecture.

v1.6.24

2 years ago
  • Save all nested-trees items collapsed state between pages or components reloads.

image

Collapsed items won't expand again when you:

  • reload page
  • add new node

v1.6.22

2 years ago

Renamed userProvider service to UserProviderInterface::class in order to use ChainUserProvider.

If you theme uses or override userProvider service, make sure to rename it to UserProviderInterface::class. Or to append new UserProvider to ChainUserProvider through userProviders array service.

v1.2.35

3 years ago

New indexes

CREATE INDEX IDX_1D3D05FC9987F3907B00651C ON nodes (node_name, status);
CREATE INDEX IDX_1D3D05FC7AB0E8597B00651C ON nodes (visible, status);
CREATE INDEX IDX_1D3D05FC7AB0E8597B00651C3445EB91 ON nodes (visible, status, parent_node_id);
CREATE INDEX node_status_parent ON nodes (status, parent_node_id);
CREATE INDEX IDX_1D3D05FC7AB0E8593445EB91 ON nodes (visible, parent_node_id);
CREATE INDEX node_visible_parent_position ON nodes (visible, parent_node_id, position);
ALTER TABLE nodes RENAME INDEX nodetype_id TO node_nodetype_status_parent;
CREATE INDEX tag_parent_visible_position ON tags (parent_tag_id, visible, position);
CREATE INDEX ns_translation_publishedat ON nodes_sources (translation_id, published_at);

v1.6.13

3 years ago

This hotfix requires a migration: bin/roadiz migrations:migrate


  • Added custom_public_scheme setting to replace node URL in backoffice with a different domain.
  • Added mediaDuration field in Document. Require migration
  • Fixed Vimeo embed finder with OEmbed instead of APIv2
  • Use OEmbed width, height and duration to populate document metadata
  • Document entity is now castable to string using its filename or name or embed id

v1.6.0

3 years ago

After many months and long projects, here is Roadiz v1.6 which embodies 3 goals for our team:

  • ✅ A short-term goal: PHP 8 compatibility
  • ✅ a mid-term goal: migrate our development process to headless architecture
  • ⚙ a long-term goal, still in progress: keeping on splitting Roadiz logic into independent components to ease up a future transition to Symfony 5 and… v2

But for today, let's list our numerous changes, we need Le table of content…

Major changes

Roadiz requires php 7.4 minimum

Upgraded dependencies to allow PHP 8.x

  • Moved gelf monolog handler to packages suggestions
  • Upgraded to Monolog 2.x

Migration to lcobucci/jwt 4.0.x

  • Rewrote all OpenId authentication listeners and JwtAccountToken

Migration to monolog 2.x

  • Removed RavenHandler

Migration to Twig 3.*

  • spaceless tag has been deleted and must be replaced with apply spaceless
  • localizeddate filter has been deleted and must be replaced with format_datetime
  • localizednumber filter has been deleted and must be replaced with format_number
  • localizedcurrency filter has been deleted and must be replaced with format_currency
  • truncate filter has been deleted and must be replaced with u.truncate
  • wordwrap filter has been deleted and must be replaced with u.wordwrap
  • centralTruncate filter must be renamed to central_truncate
  • cache tag has been removed

New Markdown node-type field options

allow_h2: true
allow_h3: true
allow_h4: true
allow_h5: true
allow_h6: true
allow_bold: true
allow_italic: true
allow_blockquote: true
allow_image: false
allow_list: true
allow_nbsp: true
allow_return: true
allow_link: true
allow_hr: true
allow_preview: true

DotEnv configuration

Roadiz can handle .env variables inside your app/conf/config.yml. We are using the same syntax as Symfony in order to get rid of config.yml shared volume on production and allow Roadiz to be used as a Kubernetes Pod.

Here is an example of Roadiz configuration using dot-env:

appNamespace: my_website
timezone: Europe/Paris
doctrine:
    driver: pdo_mysql
    host: '%env(string:MYSQL_HOST)%'
    user: '%env(string:MYSQL_USER)%'
    password: '%env(string:MYSQL_PASSWORD)%'
    dbname: '%env(string:MYSQL_USER)%'
    charset: utf8mb4
    default_table_options:
        charset: utf8mb4
        collate: utf8mb4_unicode_ci
inheritance:
    type: single_table
themes:
    -
        classname: \Themes\MyTheme\MyThemeApp
        hostname: '*'
        routePrefix: ''
cacheDriver:
    type: apcu
    host: null
    port: null
security:
    secret: '%env(string:APP_SECURITY_SECRET)%'
    session_name: my_website_token
    session_cookie_secure: '%env(bool:APP_SECURITY_COOKIE_SECURE)%'
mailer:
    type: '%env(string:MAILER_TYPE)%'
    host: '%env(string:MAILER_HOST)%'
    port: '%env(int:MAILER_PORT)%'
    encryption: '%env(default::MAILER_ENCRYPTION)%'
    username: '%env(string:MAILER_USER)%'
    password: '%env(string:MAILER_PASSWORD)%'
assetsProcessing:
    driver: gd
    defaultQuality: '%env(int:APP_ASSETS_DEFAULT_QUALITY)%'
    maxPixelSize: '%env(int:APP_ASSETS_MAX_PIXEL_SIZE)%'
    jpegoptimPath: /usr/bin/jpegoptim
    pngquantPath: /usr/bin/pngquant

Doctrine migration

This is the last time we dump a manual SQL migration in our changelog… promised!

  • Rename groups table to usergroups to comply with MySQL 8 reserved words.
ALTER TABLE user_log_entries CHANGE object_class object_class VARCHAR(191) NOT NULL, CHANGE username username VARCHAR(191) DEFAULT NULL;
RENAME TABLE `groups` TO `usergroups`;
ALTER TABLE groups_roles DROP FOREIGN KEY FK_E79D4963FE54D947;
ALTER TABLE groups_roles ADD CONSTRAINT FK_E79D4963FE54D947 FOREIGN KEY (group_id) REFERENCES usergroups (id);
ALTER TABLE users_groups DROP FOREIGN KEY FK_FF8AB7E0FE54D947;
ALTER TABLE users_groups ADD CONSTRAINT FK_FF8AB7E0FE54D947 FOREIGN KEY (group_id) REFERENCES usergroups (id);
ALTER TABLE usergroups RENAME INDEX uniq_f06d39705e237e06 TO UNIQ_98972EB45E237E06;

…Because we finally added Doctrine Migrations tool for next Roadiz and middleware updates. You'll be able to perform application upgrades with:

bin/roadiz migrations:migrate --allow-no-migration

Only static updates will be migrated with this tool, node-types and node-type fields won't generate any Doctrine Migration because if you create your node-type schema on a production environment this will lead into unsynchronized migrations table (unless you can commit your file changes from your production server which is non-sense in a Docker environment). We prefer to keep using .json exports and import. But for any other static databases changes, we invite you to generate migration diffs (filtering ns_ tables):

bin/roadiz migrations:diff --allow-empty-diff --filter-expression="#^(?\!ns_)[^\\s]*\$#"

By default, migration files will be generated in app/migrations folder unless you specify a namespace. Each registered theme can have its own Migrations/ folder, check at your doctrine migration status to see active namespaces:

bin/roadiz migrations:status

After upgrading Roadiz, Doctrine may complain that one migration is skipped, this is the initialization migration. You can mark it as resolved by adding it to migration table:

# [error] Migration RZ\Roadiz\Migrations\Version20201203004857 skipped during Execution. Reason: "Database has been initialized before Doctrine Migration tool."
# Manually add init migration
bin/roadiz migrations:version --add "RZ\Roadiz\Migrations\Version20201203004857"

Themes are now optional

Roadiz can now route nodes-sources without any theme registered until you setup all your services, routes, … directly into a custom service-provider registered in your AppKernel.

New request-time preview mode

?_preview=1 will trigger preview any time you need. No more need to use preview.php entry-point. This may ease up previewing APIs and non-HTML responses.

Of course, security is still enforce if Preview mode is enabled.

BC break: all Roadiz services that depended on Kernel::isPreview() now depend on PreviewResolverInterface which allows late-binding and abstracting preview mode logic.

New Roadiz flavor: Headless-edition

  • https://github.com/roadiz/headless-edition is a light-weight edition without any theme and already configured with roadiz/abstract-api-theme for creating a JAM stack and use whatever frontend framework.
  • All business logic can be added in src folder, no need to register theme(s), just register your service provider into AppKernel. Do you see it? We are slowly going to a more standard Symfony app architecture.
  • Rezo Zero' TreeWalker is now preconfigured with AutoChildrenNodeSourceWalker which reads your node-types configuration to build a data-graph against your children node-types fields.

Abstract Api Theme

roadiz/abstract-api-theme becomes default Roadiz public endpoint to expose your CMS content as JSON. This a read-only REST API engine which will automatically expose your node-types and will filter them. It uses JSON-LD like syntax and Hydra schema for collections and outputs simple JSON entity for detailled requests. Rezo Zero' TreeWalker is also serialized into detailled requests to be able to build an entire website page using only one API request.
See detailled documentation

Minor changes

SVG are not rendered as object

Not-inlined SVG documents are now rendered as <img> for a better compatibility with CSS and object-fit properties.

<img src="/files/folder/file.svg" />

Interfaces and contracts

We are trying to split Roadiz logic into non-dependent modules. In order to acheive this, we need more independent interfaces to be able to couple loosely components, especially for Entities relationships. So Roadiz requires now:

Frontend controllers use TranslationInterface

  • Many methods switch from RZ\Roadiz\Core\Entities\Translation to RZ\Roadiz\Core\AbstractEntities\TranslationInterface.
  • All your theme controllers indexAction signatures must comply with TranslationInterface
  • Be prepared to keep on this way on next Roadiz releases, we aim at using interfaces whenever possible to decouple CMS entities from your themes.

Refactored form types and validators dependencies

All Roadiz form types were written using $options as a dependency injector 🤦. We now use constructor to inject services and dependencies into AbstractType classes thanks to RZ\Roadiz\CMS\Forms\Extension\ContainerFormExtension.

Same refactoring for every ConstraintValidators thanks to ContainerConstraintValidatorFactory and Psr11.


This update is introducing some BC breaks with existing setups. But we shall keep on this way to allow Roadiz logic to be split into independent components. Roadiz back-office will still be rendered with Twig environment but we expect headless-edition to become the main flavor for creating new frontend projects. We will keep maintaining Twig theming but Roadiz' BaseTheme repository won't be updated frequently (on the JS/CSS side) as our team will focus on headless architecture. Headless-edition will allow more developers and teams to use Roadiz with their favorite front-end framework such as Gatsby or NuxtJS for building API-driven experiences. Even using Netlify-like platforms and static-generated pages.

We hope that this new release will bring Roadiz closer to frontend developers and open up our CMS to many workflows while keeping Roadiz core philosophy: a content-centric approach to building websites.

See you for next release… until this time, give your feedback on our dedicated forum post.

v1.5.6

3 years ago
  • Improved Solr indexing to compile texts and tags into localized *_txt_{locale} dynamic fields

Reindex your website content to see changes. This should improve localized searches on Page contents and Tags.

v1.5.0

3 years ago

2020 summer release is here

This release is focused on performances and OpenID authentication. After many big websites, we needed to look further into Doctrine and the way we store your nodes content and how we can improve that. Especially when you have to deal with more than 30 node-types, your MySQL server is crying because of too long queries (too many INNER JOIN). So we let developers choose Doctrine inheritance mapping strategy according to each website needs.

In the same time, we keep on developing our themes and new sidekick libraries in order to use Roadiz as a headless CMS in the next releases.

OpenID and JWT based authentication

Roadiz is now supporting OpenID authentication scheme for backoffice users as well as custom authentication for your website visitors. We chose OpenId because it is based on OAuth2 and JWT and use auto-discovery which make it really easy to configure. You can them choose to attach a Role to all external accounts, or implement your own JwtRoleStrategy to decide how to authorize your SSO users based on their JWT claims.

Single-table inheritance VS Class table https://github.com/roadiz/roadiz/issues/366

Due to many performance limitations using Doctrine class (or joined) table inheritance (too many INNER JOIN when lots of node-types, in fact, one additional inner join for each new node-type), we added the choice, at first install, to switch to Doctrine single-table inheritance. This means that all node-types data will be stored in a unique table with all fields as columns. There are several drawbacks with this mode:

  • You cannot create two different node-type fields with the name if they have a different types (because it will be the same column in your DB table);
  • You cannot add indexed fields (you’ll lose some performance when querying over node-type fields) But in exchange you’ll get much better performances when browsing through nodes and you won’t be limited in node-type count.

So if you need less than 20 node-types but complex ones and indexed fields, juste stay using joined table inheritance type. If you need to create lots of node-types but with very little fields or with the same fields over and over (excerpt, content, …), go for the single table inheritance type.

Sadly, there is no migration tool to switch an existing website to single-table inheritance because we would have to move every data to a single table and lose some during the process.

This strategy is very efficient for querying across all types in the hierarchy or for specific types. No table joins are required, only a WHERE clause listing the type identifiers. In particular, relationships involving types that employ this mapping strategy are very performing.

This strategy inherently requires multiple JOIN operations to perform just about any query which can have a negative impact on performance, especially with large tables and/or large hierarchies.

Other major changes

  • Podcast RSS download. Roadiz can download all episode from a single podcast RSS feed (may take time if episode files are large).
  • Changed URL generator usage to comply with next Symfony UrlGeneratorInterface signature: you cannot generate URL from an object anymore. https://github.com/roadiz/roadiz/issues/365

Twig path and url methods still use the same signature to keep the ease of use: {{ path(nodeSource) }} is internally interpreted as:

$this->get('urlGenerator')->generate(
    RouteObjectInterface::OBJECT_BASED_ROUTE_NAME,
    array_merge($parameters, [RouteObjectInterface::ROUTE_OBJECT => $nodeSource]),
    Router::ABSOLUTE_PATH
);
  • MySQL 5.7 or MariaDB 10.2.3 are now required: As we migrated to MySQL JSON native type for Doctrine array and json types. This makes these fields searchable using the MySQL 5.7 JSON_ functions
  • You can use JSON_CONTAINS verb to build Doctrine simple queries, for example to filter out nodes using a multiple choice field (without needing any relation table and heavy SQL query :
$this->get('nodeSourceApi')->getBy([
    'node.nodeType' => $this->get('nodeTypesBag')->get(NSBlogPost::class),
    'places' => ['JSON_CONTAINS', 'Paris']
])
  • Improved User serialization for checking equality
  • All node-type fields are now written in camelCase in generated PHP classes. If you use Doctrine filters on these fields, you must convert them from snake_case to camelCase.
  • Removed all newsletter related features, definitively
ALTER TABLE newsletter_subscriber DROP FOREIGN KEY FK_401562C322DB1917;
ALTER TABLE newsletter_subscriber DROP FOREIGN KEY FK_401562C37808B1AD;
DROP TABLE newsletter_subscriber;
DROP TABLE newsletters;
DROP TABLE subscribers;
DROP INDEX IDX_409B1BCC845B4F32 ON node_types;
ALTER TABLE node_types DROP newsletter_type;
  • Support non-interactive theme:migrate command to be able to run it at any Docker image startup. Here is an exemple of ./docker/php74-nginx-alpine/before_launch.sh script that you could add in your project Docker image:
# Fix volume permissions
/bin/chown -R www-data:www-data /var/www/html/files;
/bin/chown -R www-data:www-data /var/www/html/web;
/bin/chown -R www-data:www-data /var/www/html/app;

# This line ensure that your project data structure is always up to date
/usr/bin/sudo -u www-data bin/roadiz themes:migrate -n Awesome

Then you will be able to setup automatic docker container restart with tools like Watchtower

  • Added documents:prune:unused command to delete unused documents from CLI (when more than 200 documents can be too long to prune from web-server)
  • Added nodes:empty-trash command to empty deleted nodes from CLI (if it takes too long time to run it from web-server)
  • Improved Custom-forms exported answers document, added document upload

Better Roadiz sidekicks

Combine TreeWalker and AbstractApiTheme and transform Roadiz into a headless CMS.

AbstractApiTheme now supports listing and detail views for each node-types. TreeWalker is a standalone PHP library made to create data graphs from Data type definitions, in other terms, you could create an entire JSON page+blocks graph just by tell him how to fetch children nodes for each node-types. For the moment we are using it to build website navigation (to loop over pages without getting blocks), breadcrumb (just rewind your navigation TreeWalker) and to build pages by looping on inner blocks.

Minor changes

  • Roadiz Role does not extend deprecated Symfony Role anymore
  • Simplify Node route matcher to use only one SQL query for nodeName and UrlAlias lookup.
  • Added missing annotations and metadata cache clearers (when using JMS Serializer)
  • Added new ROLE_ACCESS_LOGS role to give unit access to CMS logs without needing an administrator account
  • Moved cssMainColor and loginImagePage outside of /rz-admin for front-proxy caching
  • Implements EquatableInterface for Roadiz User and OpenIdAccount for better control over authentication and token validation
  • Backoffice main-tree UI are loaded in background to speed up first load.
  • Added username to Roadiz log, in order to track SSO backend users history too

Migration

bin/roadiz orm:schema-tool:update --dump-sql --force should give you to following migration:

ALTER TABLE newsletter_subscriber DROP FOREIGN KEY FK_401562C322DB1917;
ALTER TABLE newsletter_subscriber DROP FOREIGN KEY FK_401562C37808B1AD;
CREATE TABLE custom_form_answers_documents (customformfieldattribute_id INT NOT NULL, document_id INT NOT NULL, INDEX IDX_E979F877C84CA2FC (customformfieldattribute_id), INDEX IDX_E979F877C33F7837 (document_id), PRIMARY KEY(customformfieldattribute_id, document_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB;
ALTER TABLE custom_form_answers_documents ADD CONSTRAINT FK_E979F877C84CA2FC FOREIGN KEY (customformfieldattribute_id) REFERENCES custom_form_field_attributes (id) ON DELETE CASCADE;
ALTER TABLE custom_form_answers_documents ADD CONSTRAINT FK_E979F877C33F7837 FOREIGN KEY (document_id) REFERENCES documents (id) ON DELETE CASCADE;
DROP TABLE newsletter_subscriber;
DROP TABLE newsletters;
DROP TABLE subscribers;
ALTER TABLE documents DROP FOREIGN KEY FK_A2B0728826CBD5A5;
ALTER TABLE documents ADD CONSTRAINT FK_A2B0728826CBD5A5 FOREIGN KEY (raw_document) REFERENCES documents (id) ON DELETE SET NULL;
CREATE INDEX IDX_C6B7DA87A58FA4853F824FD6 ON translations (available, override_locale);
ALTER TABLE log ADD username VARCHAR(255) DEFAULT NULL;
CREATE INDEX IDX_8F3F68C5F85E0677 ON log (username);
CREATE INDEX IDX_7C7DED6D4AD26064 ON nodes_sources (discr);
CREATE INDEX IDX_7C7DED6D4AD260649CAA2B25 ON nodes_sources (discr, translation_id);
CREATE INDEX IDX_7C7DED6DE0D4FDE14AD260649CAA2B25 ON nodes_sources (published_at, discr, translation_id);
DROP INDEX IDX_409B1BCC845B4F32 ON node_types;
ALTER TABLE node_types DROP newsletter_type;

v1.4.0

3 years ago

v1.4.0 is out, after a long stay-at-home period

TL;DR: Roadiz v1.4 is huge 🍾 and will breaks some little things.

Make sure to upgrade to v1.3.x first and resolve every Roadiz deprecation notices before trying to upgrade to v1.4. If you are using our AbstractBlogTheme or AbstractApiTheme, you will need to upgraded them too.

For more detailled questions and feedback, you can use Roadiz dedicated forum: https://ask.roadiz.io/

Security

Needs Doctrine migration

  • User creation does not send random password by email anymore. It blocks the new user until he or she chooses a new password. An email is at least sent to invite them to choose a new password. This invitation is now valid for 15 minutes. After that delay this user will have to use Forgot password feature.
    • This new feature is built on LoginRequest and LoginReset so that you can customize it for your public users if you are using AbstractUserTheme
  • Add login attempts throttling policy using username and / or ClientIp if username does not exists
    • After 3 failed attemps, IP or username is blocked for 3 minutes, after any failed attempt
    • After 6 failed attemps, IP or username is blocked for 15 minutes, after any failed attempt
    • After 9 failed attemps, IP or username is blocked for 30 minutes, after any failed attempt
  • If as login is attempted on existing username, blocking is applyied on username and ClientIp not to block the legitimate user against the attacker
  • Added console commands to clear login attempts:
    • login-attempts:clean
    • login-attempts:purge
  • Added new LoggerFactory to separate log files and channels and easily create custom loggers for your themes matters.
  • Populate more information on authentication exceptions and to make it available from back-office dashboard history.
  • Renamed RZ\Roadiz\Core\Authentification namespace to RZ\Roadiz\Core\Authentication
  • Made AuthenticationProviderManager configuration extensible into a new authenticationProviderList service.

URL Generation

  • Made NodesSources path aggregation customizable and optimized, RZ\Roadiz\Core\Routing\NodesSourcesPathAggregator will reduce Doctrine object instanciation and will use partial array response to fetch all node-source ancestors.
  • asset and absolute_url Twig function are now available to use theme AssetPackages (this has been back-ported into Roadiz v1.3). This means no more ugly string concatenation like below: {% set previewImageUrl = head.absoluteResourcesUrl ~ 'img/share.png' %} becomes {% set previewImageUrl = absolute_url(asset('img/share.png', 'DefaultTheme')) %}
  • New Display locale when using url-aliases setting to force displaying translation locale in URL even if your node-source has an url-alias configured.

Serialization

  • All node-sources fields are now camel-cased: make sure to swap case of your queries criteria if you need to filter your content using these fields. For example a hide_title field will become hideTitle.
  • New getNodeTypeName() public method in your node-sources classes to prevent using nodeSource.node.nodeType.name and triggering DB relations if your entity is not fully hydrated. Make sure to run bin/roadiz generate:nsentities once before using it. This method will be serialized with name: @type.
  • Added more detailled node-sources serialization groups
    • nodes_sources_base for non specific node-sources data (title, publication date, SEO fields)
    • nodes_sources_default for every node-type fields with no group name
    • nodes_sources_$canonicalNodeTypeFieldName will be added so that you can enable or disable node-source fields based on their node-type field group name

Contact form manager

  • Switch contact form response to JSON if Accept header is set to application/json, no more need to fake a XmlHttpRequest when using window.fetch to send your form in JS.

Sessions

  • Added Symfony\Bridge\Twig\AppVariable to handle session, request and user
    • session object is deprecated: use app.session
    • session.user is deprecated: use app.user
  • Session messages are no longer available, you must use new app object the same way you would use it in Symfony:
{% for label, messages in app.flashes(['warning', 'error']) %}
    {% for message in messages %}
        <p class="alert alert-{{ label }}">
            {{- message -}}
        </p>
    {% endfor %}
{% endfor %}

Backoffice

Node-source edition form and tree widget have been improved to hide and display children nodes based on your node-type configuration.

  • Children Node Widget now displays only node according to your node-type selection for this field. This goes with new feature below to support cases where you want to create pages nodes inside pages.
  • A new node-type parameter hidingNonReachableNodes allows node-tree widget to display reachable children node without the non-reachable ones. This is really useful when your tree has pages in pages but your pages also have content blocks, so you need to see your pages tree without being polluted by content blocks.
  • Main node / tag / folder tree widgets are not included in backoffice template rendering. This avoids numerous Doctrine queries to build tree graph on plain HTML request.
  • Better node search page with exact text pattern criteria, if you are look for a given node-name for example.

Markdown documentation generator

Roadiz can now export a documentation boilerplate for all your node-types. It will generate Markdown files for each node-type with a _sidebar.md file ready to be compiled with Docsify tool.

Attributes

Needs Doctrine migration

  • Attributes can be grouped in… AttributeGroup
  • Attributes can have documents now
  • Node’ attribute values can be reordered in the back-office (this has been back-ported in Roadiz v1.3 too)

Documents and fonts

Needs Doctrine migration

  • Documents can now have one or more thumbnails documents. This is useful to illustrate non-image documents such as PDFs, native videos, archives…
    • |display Twig filter will automatically fallback on thumbnail if original document is not displayable.
  • Usage tab now supports Tag and Attribute documents, so unused documents view is now taking these usages into account.
  • Support Deezer OEmbed, but without any thumbnails.
  • Removed deprecated fonts files from Font creation form (only keep .woff and .woff2 files) and added font-display: swap; into generated font-face file.
  • Upgraded image manipulation library Intervention Request to v3.0.0 with improved performances

Solr

  • Moved all Node-source indexing logic to an event-subscriber, even base indexation data so you can override it from your themes. FYI: event is RZ\Roadiz\Core\Events\NodesSources\NodesSourcesIndexingEvent
  • Added SolrSearchResults data transfer object to store Solr results and meta. Before we had to query once for the results and once for result count :shushing_face:
    • SolrSearchResults implements \Iterator to make nearly no breaking changes in your Controllers or Twig templates. This object handles your search result hydratation too, so we will be able to override it if you need to hydrate more than NodesSources or Document objects.
    • AbstractSearchHandler::search and AbstractSearchHandler::searchWithHighlight methods must now return a SolrSearchResults. You should check at your custom Solr search handlers in your themes.
  • Use regex to check if Solr query is single word
  • Solarium deprecations have been addressed
  • Improved Back-office search by grouping node-source results by node
  • Node-type fields can be flagged in order to exclude them from Solr indexation. This is useful for technical string fields such as layout selector or external API IRI that could be indexed into Solr and pollute search results.
  • Made Solr highlighting fragment size configurable via setHighlightingFragmentSize

Commands

  • Refactored all theme-related commands moving generation and path resolution logic away from commands to RZ\Roadiz\Utils\Theme\ThemeGenerator
  • Simplified theme commands: they only need theme name and not its class path anymore
  • Added command to delete all documents from a folder: documents:clear-folder

Translations

  • Added new Simplified Chinese backoffice translations. Big up to JerryS

Standard Edition

Roadiz Standard Edition is the repository for creating new projects with Roadiz. With v1.4, we added a complete Docker development environment using Traefik labels. If you are working on Linux, MacOs or Windows this will offer the same experience and the same workspace for all your developers.

Docker images

  • Our dedicated production Docker images are now usable in development with just a few modifications that will be built when you’ll start a new Standard Edition project.
  • For Linux users, mysql and solr images will be built to use the same unix User ID as your current session. This will prevent shared volume files to be locked for your host machine.
  • crontab is now running inside your Roadiz Docker image, so you can configure maintenance or custom CLI tasks to be performed without configuring cron on your production server. Just edit crontab.txt

Other cool stuff on our abstract themes…

ApiTheme

AbstractApiTheme is our middleware theme to expose your Roadiz contents as REST Json API with a simple API-Key+Referrer security. Since Roadiz v1.4 and AbstractApiTheme v2.0 all your node-types will be automatically exposed into /api/1.0/my-node-type and /api/1.0/my-node-type/{id} Hydra JSON responses.

AbstractApiTheme will become more important for Roadiz ecosystem in the future. As we want Roadiz to become a Content repository instead of a classic CMS. Exposing its content as API will make them available for frontend frameworks as well as Server-side rendering services. Maybe, one day we won’t need Twig anymore… but for now we love it too much 😋 to give it away.

BlogTheme

AbstractBlogTheme has been upgraded to take advantage of all Roadiz v1.4 features and with more configuration options.

Migration

Here is a migration dump for this version.
bin/roadiz orm:schema-tool:upgrade --dump-sql --force should make the changes below:

CREATE TABLE attributes_documents (id INT AUTO_INCREMENT NOT NULL, attribute_id INT DEFAULT NULL, document_id INT DEFAULT NULL, position DOUBLE PRECISION NOT NULL, INDEX IDX_67CCC9E0B6E62EFA (attribute_id), INDEX IDX_67CCC9E0C33F7837 (document_id), INDEX IDX_67CCC9E0462CE4F5 (position), INDEX IDX_67CCC9E0B6E62EFA462CE4F5 (attribute_id, position), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB;
CREATE TABLE attribute_group_translations (id INT AUTO_INCREMENT NOT NULL, attribute_group_id INT DEFAULT NULL, translation_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, INDEX IDX_5C704A6862D643B7 (attribute_group_id), INDEX IDX_5C704A689CAA2B25 (translation_id), INDEX IDX_5C704A685E237E06 (name), UNIQUE INDEX UNIQ_5C704A6862D643B79CAA2B25 (attribute_group_id, translation_id), UNIQUE INDEX UNIQ_5C704A685E237E069CAA2B25 (name, translation_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB;
CREATE TABLE login_attempts (id INT AUTO_INCREMENT NOT NULL, ip_address VARCHAR(50) DEFAULT NULL, date DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', blocks_login_until DATETIME DEFAULT NULL, username VARCHAR(255) NOT NULL, attempt_count INT DEFAULT NULL, INDEX IDX_9163C7FBF85E0677 (username), INDEX IDX_9163C7FBEFF8A4EEF85E0677 (blocks_login_until, username), INDEX IDX_9163C7FBEFF8A4EEF85E067722FFD58C (blocks_login_until, username, ip_address), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB;
CREATE TABLE attribute_groups (id INT AUTO_INCREMENT NOT NULL, canonical_name VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_D28C172A674D812 (canonical_name), INDEX IDX_D28C172A674D812 (canonical_name), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB;
ALTER TABLE attributes_documents ADD CONSTRAINT FK_67CCC9E0B6E62EFA FOREIGN KEY (attribute_id) REFERENCES attributes (id) ON DELETE CASCADE;
ALTER TABLE attributes_documents ADD CONSTRAINT FK_67CCC9E0C33F7837 FOREIGN KEY (document_id) REFERENCES documents (id) ON DELETE CASCADE;
ALTER TABLE attribute_group_translations ADD CONSTRAINT FK_5C704A6862D643B7 FOREIGN KEY (attribute_group_id) REFERENCES attribute_groups (id) ON DELETE CASCADE;
ALTER TABLE attribute_group_translations ADD CONSTRAINT FK_5C704A689CAA2B25 FOREIGN KEY (translation_id) REFERENCES translations (id) ON DELETE CASCADE;
ALTER TABLE node_type_fields ADD exclude_from_search TINYINT(1) DEFAULT '0' NOT NULL;
CREATE INDEX IDX_6FBC9426F5C1A0D77AB0E859 ON tags (parent_tag_id, visible);
ALTER TABLE log ADD channel VARCHAR(255) DEFAULT NULL, ADD additional_data JSON DEFAULT NULL;
CREATE INDEX IDX_8F3F68C5A2F98E47 ON log (channel);
CREATE INDEX IDX_7C7DED6D460D9FD79CAA2B25E0D4FDE1 ON nodes_sources (node_id, translation_id, published_at);
CREATE INDEX IDX_7C7DED6D2B36786BE0D4FDE1 ON nodes_sources (title, published_at);
CREATE INDEX IDX_7C7DED6D2B36786BE0D4FDE19CAA2B25 ON nodes_sources (title, published_at, translation_id);
ALTER TABLE node_types ADD hiding_non_reachable_nodes TINYINT(1) DEFAULT '0' NOT NULL;
CREATE INDEX IDX_409B1BCC5A3C14C7 ON node_types (hiding_non_reachable_nodes);
CREATE INDEX IDX_6E886F1F22010F1462CE4F5 ON tags_translations_documents (tag_translation_id, position);
CREATE INDEX IDX_1D3D05FC9987F3907B00651C ON nodes (node_name, status);
CREATE INDEX IDX_1D3D05FC7AB0E8597B00651C ON nodes (visible, status);
CREATE INDEX IDX_1D3D05FC7AB0E8597B00651C3445EB91 ON nodes (visible, status, parent_node_id);
DROP INDEX IDX_184662BC462CE4F5 ON attribute_values;
CREATE INDEX IDX_184662BCB6E62EFA460D9FD7 ON attribute_values (attribute_id, node_id);
CREATE INDEX IDX_C6B7DA87A58FA485609A56D9 ON translations (available, default_translation);
CREATE INDEX IDX_C6B7DA87A58FA4854180C698 ON translations (available, locale);
ALTER TABLE attributes ADD group_id INT DEFAULT NULL, ADD color VARCHAR(7) DEFAULT NULL;
ALTER TABLE attributes ADD CONSTRAINT FK_319B9E70FE54D947 FOREIGN KEY (group_id) REFERENCES attribute_groups (id) ON DELETE SET NULL;
CREATE INDEX IDX_319B9E7094CD8C0D ON attributes (searchable);
CREATE INDEX IDX_319B9E70FE54D947 ON attributes (group_id);
ALTER TABLE attribute_value_translations DROP FOREIGN KEY FK_1293849BFF82614D;
DROP INDEX IDX_1293849BFF82614D ON attribute_value_translations;
ALTER TABLE attribute_value_translations CHANGE attributevalue_id attribute_value INT DEFAULT NULL;
ALTER TABLE attribute_value_translations ADD CONSTRAINT FK_1293849BFE4FBB82 FOREIGN KEY (attribute_value) REFERENCES attribute_values (id) ON DELETE CASCADE;
CREATE INDEX IDX_1293849BFE4FBB82 ON attribute_value_translations (attribute_value);
CREATE INDEX IDX_1293849B9CAA2B25FE4FBB82 ON attribute_value_translations (translation_id, attribute_value);
CREATE INDEX IDX_1CD104F7AA2D6147705282 ON nodes_sources_documents (ns_id, node_type_field_id);
CREATE INDEX IDX_1CD104F7AA2D6147705282462CE4F5 ON nodes_sources_documents (ns_id, node_type_field_id, position);
ALTER TABLE documents ADD original INT DEFAULT NULL;
ALTER TABLE documents ADD CONSTRAINT FK_A2B072882F727085 FOREIGN KEY (original) REFERENCES documents (id) ON DELETE SET NULL;
CREATE INDEX IDX_A2B072882F727085 ON documents (original);
CREATE INDEX IDX_A2B072881AB3DB55D206C1D1 ON documents (raw, private);
CREATE INDEX IDX_761F9A91FC7ADECE47705282 ON nodes_to_nodes (node_a_id, node_type_field_id);
CREATE INDEX IDX_761F9A91FC7ADECE47705282462CE4F5 ON nodes_to_nodes (node_a_id, node_type_field_id, position);
CREATE INDEX IDX_761F9A91EECF712047705282 ON nodes_to_nodes (node_b_id, node_type_field_id);
CREATE INDEX IDX_761F9A91EECF712047705282462CE4F5 ON nodes_to_nodes (node_b_id, node_type_field_id, position);
CREATE INDEX IDX_7C7DED6DE0D4FDE19CAA2B25 ON nodes_sources (published_at, translation_id);
CREATE INDEX IDX_1D3D05FC7AB0E8593445EB91 ON nodes (visible, parent_node_id);

🥔

v1.3.0

4 years ago

Happy New Year everybody! 🍾

We begin 2020 with an awesome Roadiz update. v1.3 will bring many big new features such as versioning, encrypted settings and Symfony 4.4 LTS.

Lots of new features mean some changes to apply in your themes, check the following changelog to upgrade smoothly. Our forum is still available to discuss and share on your upgrade experience.

Migration to Symfony LTS 4.4.* version

  • Many deprecation removals
    • choices_as_values form option was removed, check your Form types
    • New syntax for EventDispatcher and Event system
    • $form->isValid() must be called after $form->isSubmitted()
    • Role class deprecation
    • (Doctrine) Removed deprecated Doctrine\Common\Persistence namespace
    • (Doctrine) Removed deprecated merge() method
  • Update for intervention-request lib
  • More strict_types everywhere: pay more attention at your type especially when using native PHP function with wrong argument types (ie. null instead of string with explode() method).
  • Single event classes and FQCN are used to address EventDispatcher::dispatch deprecation when using multiple arguments. See documentation for updating your events.

Markdown: get rid of Parsedown in favor of thephpleague/commonmark

Due to PHP 7.4 and the lack of update from erusev/parsedown-extra maintainer we decided to switch to Commonmark library with a custom made extension for Markdown footnotes support.

In order to anticipate any library change in the future we split the Markdown feature in a dedicated component: roadiz/markdown. This service provides a MarkdownInterface with text, textExtra and line methods, it’s up to you to wire any Markdown converter to these 3 methods.

Parsedown maintainers have updated parsedown-extra lib since we switched to commonmark. But, guess what, we made roadiz/markdown library extensible so you can revert using parsedown instead of commonmark without changing any code in Roadiz or your theme.

Redirections

  • Nullable redirectUri and timestampable fields to control redirection by their date
  • NodesSources redirections can be managed in their own section, in Node SEO page. This enables dynamic redirections with which query is static but redirected URL is generated at call-time against node-source URL.
ALTER TABLE redirections ADD created_at DATETIME NOT NULL, ADD updated_at DATETIME NOT NULL, CHANGE redirectUri redirectUri VARCHAR(255) DEFAULT NULL;
CREATE INDEX IDX_38F5ECE48B8E8428 ON redirections (created_at);
CREATE INDEX IDX_38F5ECE443625D9F ON redirections (updated_at);

Encrypted settings

Roadiz now takes advantage of rezozero/crypto and paragonie/halite libraries to perform modern cryptographic operations (this requires lib-sodium which should be available since PHP 7.2).

image

image

ALTER TABLE settings ADD encrypted TINYINT(1) DEFAULT '0' NOT NULL;
  • New console command to generate a private key : bin/roadiz generate:private-key
  • New configuration param: security.private_key_path with default value: conf/default.key

Be careful to copy and back-up your private key when deploying your website data or your setting values will be lost for ever… ever. If you enabled encryption on a setting while having no private key, data will be saved as a clear string into your database.

Versioning

We added node-source content versioning on scalar fields (not on relations and children nodes) to be able to view and revert to previous changes. Each node-source has its own history, so reverting changes on a parent node won’t affect its children.

A new DB table is required for holding versioning data:

CREATE TABLE user_log_entries (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, action VARCHAR(8) NOT NULL, logged_at DATETIME NOT NULL, object_id VARCHAR(64) DEFAULT NULL, object_class VARCHAR(255) NOT NULL, version INT NOT NULL, data LONGTEXT DEFAULT NULL COMMENT '(DC2Type:array)', username VARCHAR(255) DEFAULT NULL, INDEX IDX_BC2E42C7A76ED395 (user_id), INDEX log_class_lookup_idx (object_class), INDEX log_date_lookup_idx (logged_at), INDEX log_user_lookup_idx (username), INDEX log_version_lookup_idx (object_id, object_class, version), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB ROW_FORMAT = DYNAMIC;
ALTER TABLE user_log_entries ADD CONSTRAINT FK_BC2E42C7A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL;
  • bin/roadiz generate:nsentities to add Versioned flag on your node-sources fields.
  • Add new ROLE_ACCESS_VERSIONS to allowed back-office users to manage versions.

AdvancedDocumentInterface

Roadiz now stores document file size and image average color for any processable formats.

ALTER TABLE documents ADD filesize INT DEFAULT NULL;
ALTER TABLE documents ADD average_color VARCHAR(7) DEFAULT NULL;

Backoffice

  • Tag contents edition form is now submitted async… it will enhance UX when dealing with lots of tags before our team get the time to refactorize the whole back-office UI, especially trees.
  • We hid newsletter feature until we can maintain it and… use it. Tell us if you are using this newsletter composing section and is it still relevant for you? Instead of using Mailchimp or any other dedicated solution.

Solr

$solarium = new SolariumNodeSource(
    $nodeSource,
    $this->solr,
    $this->dispatcher,
    $this->handlerFactory,
    $this->logger,
    $this->markdown
);

can be replaced by:

/** @var SolariumNodeSource $solarium */
$solarium = $solariumFactory->createWithNodesSources($nodeSource);

Better docker for development configuration examples

You’ll find docker-compose.yml examples in our standard edition and ready-to-build development docker images based on our production-ready Roadiz stack to develop with docker and get production-ish performances (on Linux and Windows, macOS still sucks with docker shared volumes).

Export

  • Stack types are now exported with your nodes when using .rzt format from back-office.

Fonts

  • We use absolutes paths instead of absolute URL in the font-face CSS file generated by Roadiz

Misc

  • We now use phpstan to run static analyse with Travis CI for a better maintenance.
  • We activated Symfony Debug tool in dev.php mode for cooler error pages. This means that 404 errors will not trigger your theme custom page since your are not in prod mode.
  • Roadiz now supports latest sentry/sentry Monolog handler, make sure to require these librairies before using it in your configuration file: composer require sentry/sentry php-http/curl-client guzzlehttp/psr7
  • Fixed login screen layout when errors occur, remove error message when requesting new password

Migration recap

ALTER TABLE redirections ADD created_at DATETIME NOT NULL, ADD updated_at DATETIME NOT NULL, CHANGE redirectUri redirectUri VARCHAR(255) DEFAULT NULL;
CREATE INDEX IDX_38F5ECE48B8E8428 ON redirections (created_at);
CREATE INDEX IDX_38F5ECE443625D9F ON redirections (updated_at);
ALTER TABLE settings ADD encrypted TINYINT(1) DEFAULT '0' NOT NULL;
ALTER TABLE documents ADD filesize INT DEFAULT NULL;
ALTER TABLE documents ADD average_color VARCHAR(7) DEFAULT NULL;
CREATE TABLE user_log_entries (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, action VARCHAR(8) NOT NULL, logged_at DATETIME NOT NULL, object_id VARCHAR(64) DEFAULT NULL, object_class VARCHAR(255) NOT NULL, version INT NOT NULL, data LONGTEXT DEFAULT NULL COMMENT '(DC2Type:array)', username VARCHAR(255) DEFAULT NULL, INDEX IDX_BC2E42C7A76ED395 (user_id), INDEX log_class_lookup_idx (object_class), INDEX log_date_lookup_idx (logged_at), INDEX log_user_lookup_idx (username), INDEX log_version_lookup_idx (object_id, object_class, version), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB ROW_FORMAT = DYNAMIC;
ALTER TABLE user_log_entries ADD CONSTRAINT FK_BC2E42C7A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL;

ALTER TABLE fonts CHANGE created_at created_at DATETIME DEFAULT NULL, CHANGE updated_at updated_at DATETIME DEFAULT NULL;
ALTER TABLE tags CHANGE created_at created_at DATETIME DEFAULT NULL, CHANGE updated_at updated_at DATETIME DEFAULT NULL;
ALTER TABLE subscribers CHANGE created_at created_at DATETIME DEFAULT NULL, CHANGE updated_at updated_at DATETIME DEFAULT NULL;
ALTER TABLE folders CHANGE created_at created_at DATETIME DEFAULT NULL, CHANGE updated_at updated_at DATETIME DEFAULT NULL;
ALTER TABLE translations CHANGE created_at created_at DATETIME DEFAULT NULL, CHANGE updated_at updated_at DATETIME DEFAULT NULL;
ALTER TABLE users CHANGE created_at created_at DATETIME DEFAULT NULL, CHANGE updated_at updated_at DATETIME DEFAULT NULL;
ALTER TABLE newsletters CHANGE created_at created_at DATETIME DEFAULT NULL, CHANGE updated_at updated_at DATETIME DEFAULT NULL;
ALTER TABLE custom_forms CHANGE created_at created_at DATETIME DEFAULT NULL, CHANGE updated_at updated_at DATETIME DEFAULT NULL;
ALTER TABLE nodes CHANGE created_at created_at DATETIME DEFAULT NULL, CHANGE updated_at updated_at DATETIME DEFAULT NULL;
ALTER TABLE documents CHANGE created_at created_at DATETIME DEFAULT NULL, CHANGE updated_at updated_at DATETIME DEFAULT NULL;