Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
0202a8f
[feature] Autocomplete for EntityFilter
Nov 10, 2025
dafa4c1
Actions extensions
javiereguiluz Oct 10, 2025
1b51457
feature #7171 Actions extensions (javiereguiluz)
javiereguiluz Nov 11, 2025
e1d9a1a
Prepare 4.27.2 release
javiereguiluz Nov 11, 2025
18e7f5f
Bump development version
javiereguiluz Nov 11, 2025
a637449
feature #7226 [feature] Autocomplete for EntityFilter (a.dmitryuk)
javiereguiluz Nov 11, 2025
3c5b25a
Minor tweaks
javiereguiluz Nov 11, 2025
344bd17
Fixed detail icon color
ahazebroucq Nov 11, 2025
51d40d6
Fix some more deprecations related to Doctrine
fracsi Nov 12, 2025
c7ad0a2
Added autocomplete connect event to ALL tomselect connect
Nov 12, 2025
349f0ae
bug #7227 [BUG] Fixed detail icon color (ahazebroucq)
javiereguiluz Nov 13, 2025
3c6c735
Ensure UUID mapping is checked before binary conversion in autocomplete
johndodev Nov 13, 2025
56b0b2f
bug #7233 Ensure UUID mapping is checked before binary conversion in …
javiereguiluz Nov 13, 2025
8c04f35
bug #7228 Fix some more deprecations related to Doctrine (fracsi)
javiereguiluz Nov 13, 2025
2ad0932
Fix colors not applied on button disable state
Nov 13, 2025
0195db6
bug #7231 Fix colors not applied on button disable state (Julien Rich…
javiereguiluz Nov 13, 2025
caa64f5
Rebuild web assets
javiereguiluz Nov 13, 2025
426009f
Prepare 4.27.3 release
javiereguiluz Nov 13, 2025
79422ec
Bump development version
javiereguiluz Nov 13, 2025
2aa6220
remove workaround, DoctrineFixturesBundle is compatible with Symfony 8
xabbuh Nov 14, 2025
aa897df
use the IdentityTranslator instead of a custom TranslatorInterface mock
xabbuh Nov 14, 2025
291cc59
use Composer 2.8 to install dependencies
xabbuh Nov 14, 2025
3bac38f
minor #7237 use the IdentityTranslator instead of a custom Translator…
javiereguiluz Nov 14, 2025
1910fea
minor #7238 use Composer 2.8 to install dependencies (xabbuh)
javiereguiluz Nov 14, 2025
a0ed9ac
minor #7236 remove workaround, DoctrineFixturesBundle is compatible w…
javiereguiluz Nov 14, 2025
9e3cfe2
check if already a defaultColumns exist before overwritting customize…
Nov 15, 2025
f383ed9
FIX: Handle potential null for excludedPropertyNames
Snowbaha Nov 17, 2025
f19258d
Enhance content header help with pointer cursor
gremo Nov 17, 2025
1709464
Hide empty button labels when using button icons
gremo Nov 17, 2025
3d10b21
Improve form action rendering in datagrid by adjusting display and ma…
gremo Nov 17, 2025
6bfbea9
Fix translation domain for flash messages
gremo Nov 17, 2025
75924d3
bug #7245 Enhance content header help with pointer cursor (gremo)
javiereguiluz Nov 19, 2025
cae471e
bug #7251 Fix translation domain for flash messages (gremo)
javiereguiluz Nov 19, 2025
d2dda9e
Add a comment
javiereguiluz Nov 19, 2025
a1cc14b
bug #7241 FIX: Handle potential null for excludedPropertyNames (Snowb…
javiereguiluz Nov 19, 2025
71990c0
Fix Italian translations for boolean values and correct typos in acti…
gremo Nov 19, 2025
88fce87
minor #7253 Fix Italian translations for boolean values and correct t…
javiereguiluz Nov 20, 2025
bc78315
minor #7247 Hide empty button labels when using button icons (gremo)
javiereguiluz Nov 20, 2025
a3d5f3d
minor #7250 Improve form action rendering in datagrid by adjusting di…
javiereguiluz Nov 20, 2025
e373893
bug #7240 setDefaultColumns didn't work on ChoiceField if CustomOptio…
javiereguiluz Nov 20, 2025
b97fc35
Prepare 4.27.4 release
javiereguiluz Nov 20, 2025
6d94c62
Rebuild web assets
javiereguiluz Nov 20, 2025
0685b86
Bump development version
javiereguiluz Nov 20, 2025
15ebf14
feature #7230 Added autocomplete connect event to ALL tomselect conne…
javiereguiluz Nov 24, 2025
91f09e3
Bump actions/checkout from 5 to 6
dependabot[bot] Nov 24, 2025
fd6a8e5
minor #7256 Bump actions/checkout from 5 to 6 (dependabot[bot])
javiereguiluz Nov 25, 2025
2e4af42
Fix tests
javiereguiluz Nov 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 8 additions & 17 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
phpstan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6

- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
Expand Down Expand Up @@ -82,7 +82,7 @@ jobs:
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.stability == 'dev' }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6

- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
Expand All @@ -91,6 +91,7 @@ jobs:
coverage: none
extensions: mbstring, intl, pdo, pdo_sqlite, sqlite3
ini-values: date.timezone=UTC
tools: composer:2.8

- name: symfony/flex is required to install the correct symfony version
if: ${{ matrix.symfony_version }}
Expand All @@ -106,16 +107,6 @@ jobs:
if: ${{ matrix.symfony_version }}
run: composer config extra.symfony.require "${{ matrix.symfony_version }}.*"

# Remove this step when https://github.com/doctrine/DoctrineFixturesBundle/pull/542 is merged and released
- name: "Temporary: alias some packages to make Symfony 8 packages installable"
if: ${{ matrix.symfony_version == '8.0.x' }}
run: |
composer require --no-update "symfony/config:7.4.x-dev as 8.0.x-dev"
composer require --no-update "symfony/console:7.4.x-dev as 8.0.x-dev"
composer require --no-update "symfony/dependency-injection:7.4.x-dev as 8.0.x-dev"
composer require --no-update "symfony/doctrine-bridge:7.4.x-dev as 8.0.x-dev"
composer require --no-update "symfony/http-kernel:7.4.x-dev as 8.0.x-dev"

- name: Install dependencies
run: |
composer update ${{ matrix.composer_args }};
Expand Down Expand Up @@ -143,7 +134,7 @@ jobs:
php-linter:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6

# see https://github.com/OskarStark/php-cs-fixer-ga
- name: PHP-CS-Fixer
Expand All @@ -154,7 +145,7 @@ jobs:
doc-linter:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6

- name: DOCtor-RST
uses: docker://oskarstark/doctor-rst
Expand All @@ -166,7 +157,7 @@ jobs:
css-linter:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6

- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
Expand All @@ -185,7 +176,7 @@ jobs:
twig-linter:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6

- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
Expand All @@ -204,7 +195,7 @@ jobs:
js-linter:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- run: npm i -g corepack && corepack enable
- uses: actions/setup-node@v6
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/integration-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
timeout-minutes: 25

steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand Down
5 changes: 4 additions & 1 deletion assets/css/easyadmin-theme/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -508,9 +508,12 @@ a.user-menu-wrapper .user-details:hover {
line-height: var(--font-size-lg);
}

.content-header-help {
cursor: pointer;
}

.content-header-help i {
color: var(--text-muted);
cursor: pointer;
font-size: 21px;
}

Expand Down
7 changes: 7 additions & 0 deletions assets/css/easyadmin-theme/buttons.css
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@
/* Disabled state */
.btn.disabled,
.btn:disabled {
background: var(--button-active-bg, var(--button-bg));
border-color: var(--button-active-border-color, var(--button-border-color));
color: var(--button-active-color, var(--button-color));
box-shadow: none;
cursor: not-allowed;
opacity: var(--button-disabled-opacity);
Expand Down Expand Up @@ -252,6 +255,10 @@ fieldset:disabled a.btn {
margin-inline-start: 0;
}

.btn > .btn-icon + .btn-label:empty {
display: none;
}

.btn-sm:not(:has(.btn-label)) {
padding: var(--button-padding-y-sm);
}
Expand Down
5 changes: 5 additions & 0 deletions assets/css/easyadmin-theme/datagrids.css
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ table.datagrid:not(.datagrid-empty) tr:not(.empty-row) td.actions.actions-as-dro
.datagrid td.actions {
text-align: right;
}
.datagrid td.actions:not(.actions-as-dropdown) form {
display: inline;
margin-inline-start: 10px;
margin-inline-end: 10px;
}
.datagrid td.actions a:not(.dropdown-item) {
font-size: var(--font-size-sm);
font-weight: 500;
Expand Down
2 changes: 1 addition & 1 deletion assets/icons/internal/detail.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 22 additions & 2 deletions assets/js/autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,17 @@ export default class Autocomplete {
},
});

return new TomSelect(element, config);
element.dispatchEvent(
new CustomEvent('ea.autocomplete.pre-connect', { detail: { config, prefix: 'autocomplete' }, bubbles: true })
);

const tomSelect = new TomSelect(element, config);

element.dispatchEvent(
new CustomEvent('ea.autocomplete.connect', { detail: { tomSelect, config, prefix: 'autocomplete' }, bubbles: true })
);

return tomSelect;
}

#createAutocompleteWithRemoteData(element, autocompleteEndpointUrl) {
Expand Down Expand Up @@ -138,7 +148,17 @@ export default class Autocomplete {
},
});

return new TomSelect(element, config);
element.dispatchEvent(
new CustomEvent('ea.autocomplete.pre-connect', { detail: { config, prefix: 'autocomplete' }, bubbles: true })
);

const tomSelect = new TomSelect(element, config);

element.dispatchEvent(
new CustomEvent('ea.autocomplete.connect', { detail: { tomSelect, config, prefix: 'autocomplete' }, bubbles: true })
);

return tomSelect;
}

#stripTags(string) {
Expand Down
3 changes: 3 additions & 0 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@
->arg(3, new Reference(CrudControllerRegistry::class))
->arg(4, new Reference(EntityFactory::class))
->arg(5, service(AdminRouteGenerator::class))
->arg(6, service(ActionFactory::class))

->set(AdminUrlGenerator::class)
// I don't know if we truly need the share() method to get a new instance of the
Expand Down Expand Up @@ -307,6 +308,7 @@
->set(DateTimeFilterConfigurator::class)

->set(EntityFilterConfigurator::class)
->arg(0, new Reference(AdminUrlGenerator::class))

->set(NullFilterConfigurator::class)

Expand All @@ -319,6 +321,7 @@
->arg(1, new Reference(AuthorizationChecker::class))
->arg(2, new Reference(AdminUrlGenerator::class))
->arg(3, new Reference('security.csrf.token_manager', ContainerInterface::NULL_ON_INVALID_REFERENCE))
->arg(4, tagged_iterator(EasyAdminExtension::TAG_ACTIONS_EXTENSION))

->set(SecurityVoter::class)
->arg(0, service(AuthorizationChecker::class))
Expand Down
59 changes: 59 additions & 0 deletions doc/actions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ strings with the action names (``'index'``, ``'detail'``, ``'edit'``, etc.) you
can also use constants for these values: ``Action::INDEX``, ``Action::DETAIL``,
``Action::EDIT``, etc. (they are defined in the ``EasyCorp\Bundle\EasyAdminBundle\Config\Action`` class).

.. _actions-built-in:

Built-in Actions
----------------

Expand Down Expand Up @@ -1016,6 +1018,63 @@ This is no longer needed in modern EasyAdmin versions and is now a discouraged
practice that you should avoid in your applications. Instead, see the previous
section about :ref:`how to integrate custom Symfony controllers into EasyAdmin dashboards <actions-integrating-symfony>`.

Actions Extensions
------------------

Applications using EasyAdmin define their actions in the ``configureActions()``
method of the :doc:`CRUD controllers </crud>`. You can enable, disable, or modify
:ref:`built-in actions <actions-built-in>`, and also create your own
:ref:`custom actions <actions-custom>`.

EasyAdmin provides an additional feature to add, remove, or change actions
(built-in or custom) dynamically at runtime: **action extensions**. They allow
your application (or third-party bundles installed in it) to modify the actions
defined for your controllers.

Action extensions are PHP classes that receive the full configuration of
actions in your backend so they can add, remove, or update any of them.

For example, imagine you need a **Duplicate** action across most of your
backends. Instead of defining it repeatedly, you can create a reusable package
(such as a `Symfony bundle`_) and add the following class::

// <your-package>/src/DuplicateActionExtension.php
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext;
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Action\ActionsExtensionInterface;

final class DuplicateActionExtension implements ActionsExtension
{
// return true in this method to enable the extension for
// the current backend request
public function supports(AdminContext $context): bool
{
// enable the extension only on some pages
return $context->getCrud()->getCurrentPage() === Crud::PAGE_DETAIL;

// enable it on all except some entities
$entityFqcn = $context->getCrud()->getEntityFqcn();
return null !== $entityFqcn && !\in_array(entityFqcn, ['...'], true);

// or use any other admin context data to make the decision
}

public function extend(Actions $actions, AdminContext $context): void
{
$duplicate = Action::new('duplicate', 'Duplicate', 'fa fa-clone')
->linkToCrudAction('duplicate')
->asSuccessAction();

$actions->add(Crud::PAGE_DETAIL, $duplicate);

// you can add single actions, groups of actions, etc.
// you can also remove or update existing actions
}
}

.. _`FontAwesome`: https://fontawesome.com/
.. _`Symfony base controller class`: https://symfony.com/doc/current/controller.html#the-base-controller-class-services
.. _`Symfony controllers`: https://symfony.com/doc/current/controller.html
.. _`Symfony bundle`: https://symfony.com/doc/current/bundles.html
1 change: 1 addition & 0 deletions doc/filters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ These are the built-in filters provided by EasyAdmin:
* ``EntityFilter``: applied to fields with Doctrine associations (all kinds
supported). It's rendered as a ``<select>`` list with the condition (equal/not
equal/etc.) and another ``<select>`` list to choose the comparison value.
You can call `->autocomplete()` to load values dynamically via Ajax requests.
* ``NullFilter``: it's not applied by default to any field. It's useful to
filter results depending on the "null" or "not null" value of a property.
It's rendered as two radio buttons for the null and not null options.
Expand Down
2 changes: 1 addition & 1 deletion public/app.acfa0952.css → public/app.54fc226c.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion public/entrypoints.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"entrypoints": {
"app": {
"css": [
"/app.acfa0952.css"
"/app.54fc226c.css"
],
"js": [
"/app.92065ad6.js"
Expand Down
2 changes: 1 addition & 1 deletion public/manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"app.css": "app.acfa0952.css",
"app.css": "app.54fc226c.css",
"app.js": "app.92065ad6.js",
"form.js": "form.875c88d4.js",
"page-layout.js": "page-layout.6e9fe55d.js",
Expand Down
18 changes: 18 additions & 0 deletions src/Contracts/Action/ActionsExtensionInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace EasyCorp\Bundle\EasyAdminBundle\Contracts\Action;

use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext;

/**
* Contract for services that modify EasyAdmin actions at runtime.
*
* @author Javier Eguiluz <[email protected]>
*/
interface ActionsExtensionInterface
{
public function supports(AdminContext $context): bool;

public function extend(Actions $actions, AdminContext $context): void;
}
5 changes: 5 additions & 0 deletions src/DependencyInjection/EasyAdminExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace EasyCorp\Bundle\EasyAdminBundle\DependencyInjection;

use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminRoute;
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Action\ActionsExtensionInterface;
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Controller\CrudControllerInterface;
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Controller\DashboardControllerInterface;
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldConfiguratorInterface;
Expand All @@ -24,6 +25,7 @@ class EasyAdminExtension extends Extension implements PrependExtensionInterface
public const TAG_ADMIN_ROUTE_CONTROLLER = 'ea.admin_route_controller';
public const TAG_FIELD_CONFIGURATOR = 'ea.field_configurator';
public const TAG_FILTER_CONFIGURATOR = 'ea.filter_configurator';
public const TAG_ACTIONS_EXTENSION = 'ea.actions_extension';

public function load(array $configs, ContainerBuilder $container): void
{
Expand All @@ -45,6 +47,9 @@ static function (Definition $definition, AdminRoute $attribute, \ReflectionClass
$container->registerForAutoconfiguration(FilterConfiguratorInterface::class)
->addTag(self::TAG_FILTER_CONFIGURATOR);

$container->registerForAutoconfiguration(ActionsExtensionInterface::class)
->addTag(self::TAG_ACTIONS_EXTENSION);

$loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../../config'));
$loader->load('services.php');
}
Expand Down
2 changes: 1 addition & 1 deletion src/EasyAdminBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*/
class EasyAdminBundle extends Bundle
{
public const VERSION = '4.27.2-DEV';
public const VERSION = '4.27.5-DEV';

public function build(ContainerBuilder $container): void
{
Expand Down
Loading