Changelog

Unreleased

  • Core Refactor The eighteen methods and two properties that kept a signature without a native type, because a class outside the repository overrides them without one, declare it now: onInit() of a plugin and install() of the system plugin return void, getResponseModel() takes two arrays and returns Response, onDisplayMain() takes string|false, string|false and array and returns ?bool, the system object declares getSettings(): array, getUrlRules(array $search): array and addUrlRule(array $values): mixed, DefaultUser declares get(string $name): mixed, set(string $name, mixed $value): mixed, getID(): mixed, getRole(): mixed, getSessionData(): array and array $requiredFields, ObjectPlugin declares ?IDataAccessObject $object, the fields declare getFilterTemplateName(): string, getInputType(): string, callHandlerMethod(array $params, string $methodPostfix): mixed and getCurrencySymbol(): string|false, IProxyRepository::insert() and IStore::getOption() return mixed, and PermissionsException sets its code 403 in a constructor with the parameters of SystemException instead of redeclaring $code; the tests/Resources/plugins/NonClassPlugin.php fixture declares strict_types; phpcs.xml.dist demotes no rule, so every finding of the coding standard, an error or a warning, fails the build; PHP does not load a plugin, package or project class that overrides one of these methods without the return type or redeclares one of the two properties without the type, and UPGRADE.md lists the members, the errors, a script that finds the overrides and adds the types, and what it reports per repository (BREAKING)
  • DGS Refactor ClassSchema declares the relations, listeners, routers, rules and external values of a schema in one section method, bindings(ISchemaBindings $bindings), whose argument returns the builder of each of the five sections, in place of the section methods relations(), listeners(), routers(), rules() and externalValues(), and takes the builders of its section methods from a new SchemaBuilders, both of Festi\Store\Schema\Builder, which clears the PHPMD coupling finding of ClassSchema (28 types, now 12), the last finding of the PHPMD ruleset in src. The other nine section methods, parent:: composition and the array build() returns do not change. build() refuses a schema class that still has a method with one of the five names with a SystemException that names the replacement; see "ClassSchema declares five sections in bindings()" in UPGRADE.md, which has a script that rewrites such classes (BREAKING)
  • DGS Refactor Store keeps the plugin it belongs to in its StoreComponents, as the public typed property $plugin, next to its model, view, proxy and rules manager, and StoreComponentsFactory creates the plugin, the initialised rules manager and the audit of a store, which clears the PHPMD coupling finding of Store (17 types, now 12): every public method of Store keeps its signature, among them getPlugin(), which still returns a reference, and setPlugin(), and the same store comes out with the same plugin, listeners, permission check and messages; the protected property $plugin of Store is gone, so a store class of a project reads and sets $this->components->plugin (or calls getPlugin()) instead of $this->plugin, and the protected method getRulesManager() is gone, so it reads $this->components->rulesManager and, to use a rules manager of its own, sets it in onInit() before parent::onInit(); a class that still uses or declares $plugin or getRulesManager() is stopped with an exception that names the replacement; StoreComponentsFactory::createRulesManager() takes the store, StoreIdentResolver::isSchemaClass() is public and StoreModel has getErrorMessage() and getPermissionSection(); see UPGRADE.md, which has a script that finds and rewrites the affected lines (BREAKING)
  • DGS Refactor AbstractAction builds its url through a new ActionUrlBuilder and handles an exception through a new ActionExceptionHandler, and InsertAction and RemoveAction report how the transaction of a record ended through a new TransactionOutcomeNotifier, all of core\store\action\helper, which clears the PHPMD coupling findings of AbstractAction (17 types, now 12) and InsertAction (15, now 12): getUrl(), doHandleException() and apply() keep their signatures, and the same urls, errors and events come out in the same order; the notifier takes over InsertAction::onCompleteTransaction(), addParentTransactionListeners() and $actionRecord, which no release had; AbstractAction::getStore() declares the return type Store, the class it always returned, instead of IStore, so an action class of a project that overrides getStore() with the return type IStore must declare Store; see UPGRADE.md (BREAKING)
  • DGS Refactor Store keeps the page and the order of its list (rowsPerPage, currentPage, totalRows, orderByFieldName and orderByDirection) in a StorePagination, held in the protected property $pagination, and its model, view, proxy and rules manager in a StoreComponents, held in the protected property $components, both of Festi\Store, instead of nine protected properties, which clears its PHPMD TooManyFields finding (17 fields, now 10); a new StoreComponentsFactory creates the four components, which takes the coupling of Store from 18 to 17 (that finding stays); a store class reads and sets the page and the order through $this->pagination and reads the components through getModel(), getView(), getProxy() and getRulesManager() or the public typed properties $model, $view, $proxy and $rulesManager of $this->components; every public method of Store, PluginStore and IStore keeps its signature and its result, getModel(), getView() and getProxy() still return a reference, and a clone still changes its page or replaces a component without the store it was made from; a store class that still uses or declares one of the nine names is stopped with a SystemException that names the replacement, on PHP 8.1, 8.2 and 8.3, where PHP alone would keep the write in a new property nothing reads; UPGRADE.md has the migration (BREAKING)
  • DGS Refactor StoreModel keeps the thirteen parts of a schema (fields, actions, relations, grouped, filters, sections, externalValues, routers, search, listeners, highlights, aggregations and rules) in a StoreSchemaParts of Festi\Store\Model, held in the protected property $parts, instead of thirteen protected array properties, which clears its PHPMD TooManyFields finding (20 fields, now 8): a model class reads a part with array access, $this->parts[StoreSchemaParts::FIELDS], which gives a reference, so that $this->parts[StoreSchemaParts::FIELDS][$name] = $field changes the part, and replaces it with $this->parts[StoreSchemaParts::FIELDS] = $fields; every public method of the model keeps its signature and the ones that return a reference still do, and each XML, array and class schema loads into the same model; a project model class that still reads, writes, tests or unsets $this->fields or another of the thirteen names, or declares such a property, is stopped with a SystemException that names the replacement, where PHP would have kept the value in an item of the ArrayObject without a message; for these checks the model no longer answers for properties as an ArrayObject with ARRAY_AS_PROPS does, so $store, $primaryKey and $_name are ordinary properties and no longer items of the model, and code outside a model that reads $model->primaryKey gets a SystemException where it got the key; UPGRADE.md has the migration (BREAKING)
  • Core Refactor The constructors of SystemException and FieldException take an ExceptionContext of Festi\Core\Exception, which holds the data or the label and the source, instead of the separate $data and $source parameters, which clears their two PHPMD ParameterCount findings: SystemException takes message, code, context, display message and previous exception, FieldException takes message, selector, code, context and previous exception and always shows its message unless setDisplayMessage() sets another one; calls with a message and a code, the named arguments previous and (for SystemException) displayMessage, the constructors of ApiException, StoreActionException and NotFoundException, and every getter are unchanged, an old call with a third positional argument (a fourth for FieldException) or with the named argument data or source fails with a TypeError or an Error and never runs with a changed meaning, NotFoundException now chains its $previous as the previous exception instead of storing it as the label, and UPGRADE.md has a script that finds the calls, the rule and an example for each old shape (BREAKING)
  • DGS Refactor Four protected proxy methods take value objects of Festi\Store\Proxy instead of five or six positional parameters, which clears their five PHPMD ParameterCount findings: updateForeignTableValues() and getDataForUpdateForeignTableValues() of the SQL proxies (and the MssqlProxy override) take a ForeignTableValues with the main table, the foreign table, the values of the foreign row and the current main row, updateForeignTableValues() still receives the values of the main row by reference after it and getDataForUpdateForeignTableValues() no longer takes them because the framework never read them, so the two methods no longer take the two value arrays in opposite orders; OpenApiProxy::loadRemoteListValues() and getListValuesEndpointUrl() take a ListValuesQuery with the columns, conditions, order, page and all-columns flag; the same values give the same queries, request url, result and exceptions, a project proxy that overrides one with the old parameters fails at class load and an old call fails with a TypeError, never with a changed meaning, and UPGRADE.md has the rule and an example for each method (BREAKING)
  • Core Refactor ImageUtils::setImageResize(), setImageResizeMagic() and createWatermark(), Curl::getUrl() and ApiUtils::send() take value objects of Festi\Core\Utils instead of five to seven positional parameters, which clears their five PHPMD ParameterCount findings: ImageSize, ImageOutput, Watermark and WatermarkColor for the images, CurlProxy for the proxy of a request, and ApiRequest for the data, method and headers of an API call, whose decoded body and response headers come back in an ApiResponse instead of a fifth by-reference parameter; the same values give the same file, cURL options, result and exceptions, an old call fails with a TypeError and never runs with a changed meaning, and UPGRADE.md has the rule and an example for each method (BREAKING)
  • Documentation Feature UPGRADE.md tells a developer or a coding agent what to change in a project for this release: a procedure with a class-load check that reports every project class PHP refuses to load, a table from error message to section, and one section per breaking change with the same four parts (what changed, who is affected, how to find it, what to do); AGENTS.md and CLAUDE.md ask every change that breaks project code to add its section in the same MR
  • Core Bug Fix FestiEvent::getType() returns the type of an event that was created with an integer type as a string again, as it did before src/event declared strict_types; it threw a TypeError, which broke every dispatch of festi-framework-async, whose event types are integers
  • Core Refactor ValuesObjectMapper declares strict_types and keeps handing the values to the setters of a project value object the way it did before: a setter such as setID(int $id) still receives the string id of a database row as an int, because the setter is called through the unqualified call_user_func(), which calls it in coercive mode (a fully qualified \call_user_func() or a use function import would make the call strict, and the new ValuesObjectMapperTest fails then), and the result of a filter callback is cast to bool
  • Core Bug Fix A plugin method reached through Core::call(), an event listener, a hook, a handler named in a store schema, a request rule and a log interceptor are called in coercive mode again, through the new Festi\Core\ProjectCallback: a route method that declares int $id receives 5 for the URL value '5', as it did before the framework files declared strict_types, instead of a TypeError; a value PHP cannot convert, such as a non-numeric string for int, is still refused
  • DGS Refactor SqlProxy builds the WHERE parts that come from the schema filters and the parent relation through a new SchemaFilterConditions, and collects and orders the joins of the routes through a new RouterJoins, which clears its PHPMD class complexity finding; getQueryWhere() and getQueryJoins() keep their signatures and return the same SQL
  • Core Refactor Entity reads a request value in the type of its field through a new RequestValueReader, which clears its PHPMD class complexity finding; getPreparedData(), getExtendData() and the constants are the same, and a subclass that redeclares a field type constant is still read by its own value
  • DGS Refactor StoreForm builds the config of a form through a new StoreFormConfigBuilder and its items and sections through a new StoreFormItems, which clears its PHPMD coupling finding; the constructor, fetch() and doPrefill() keep their signatures, and the form gets the same template variables and fires the same events in the same order
  • DGS Refactor XmlStoreModel reads the attributes and the text of schema elements through a new XmlAttributeReader, which clears its PHPMD class complexity finding; load() and the model it builds are the same as before
  • DGS Refactor InsertAction builds the values it saves through a new RequestValuesPreparer, which clears its PHPMD class complexity finding; getPreparedValues() keeps its signature and is called at the same point, and the same events are fired in the same order
  • Core Refactor Two locale classes each hand one private responsibility to a new class, clearing two PHPMD findings: MoDictionaryLocale keeps the header of a .mo file in a MoFileHeader value object instead of six fields, and PluralFormEvaluator reads its tokens through a PluralTokenStream; no public method changes, and every plural rule and .mo file gives the same result as before
  • DGS Refactor Three action classes that sat at a PHPMD limit each hand one private responsibility to a helper: RelationAction builds its redirect url through RelationRedirectUrlBuilder, ListAction renders its fast insert form through FastInsertForm, and CsvImportAction builds its upload form through CsvImportForm; no public or protected method changes, and each still runs where it ran before
  • DGS Refactor SqlForeignKeyProxy::loadForeignValues(), loadForeignAssigns() and loadForeignKeyValues() each move one block into a private method, which clears their three PHPMD NPathComplexity findings; the signatures, the SQL and the order of the queries are the same as before
  • Static Analysis Refactor Seven PHPMD findings in private methods are cleared without changing a public or protected signature: the XML store model loader passes an XmlAttributeQuery instead of six or seven parameters, SqlForeignKeyProxy passes ForeignAssignColumns, ImageUtils passes WatermarkText to its watermark drawing step, SqlProxy returns the router joins instead of filling a second by-reference array, and Core builds its default options from five named methods; the options, joins and SQL are the same as before
  • Static Analysis Refactor The remaining tests no longer pass an array literal as a call argument and write every array with several elements one element per line, so Festi.Functions.NoArrayArgument and Festi.Arrays.MultiLineArrayElements are errors on the whole tree again; each test asserts the same values as before
  • Static Analysis Refactor tests/Bundle/Store no longer passes an array literal as a call argument and writes every array with several elements one element per line; each test asserts the same values as before
  • Static Analysis Refactor src no longer passes an array literal as a call argument and writes every array with several elements one element per line (Festi.Functions.NoArrayArgument and Festi.Arrays.MultiLineArrayElements); the literal is built in a local variable before the call, with the same values and the same order; no behaviour changes
  • Static Analysis Refactor Every public method in src has a docblock that describes each parameter and the returned value (Logger, Core traits, events, locale, plugins and utilities in this change), so Festi.Commenting.PublicMethodDocBlock is an error on the whole tree again; no signature or behaviour changes
  • DGS Refactor Every public method left in src/store (the store traits, StoreAudit, the rules, the models and the schema builders) has a docblock that describes each parameter and the returned value; no signature or behaviour changes
  • DGS Refactor Every public method of src/store/view and of the IStore interface has a docblock that describes each parameter and the returned value; no signature or behaviour changes
  • DGS Refactor Every public method of src/store/event has a docblock that describes each parameter and the returned value; the listener interfaces say when the Store or an action calls them; no signature or behaviour changes
  • DGS Refactor Every public method of src/store/proxy has a docblock that describes each parameter and the returned value; no signature or behaviour changes
  • DGS Refactor Every public method of src/store/action has a docblock that describes each parameter and the returned value; no signature or behaviour changes
  • DGS Refactor Every public method of src/store/field has a docblock that describes each parameter and the returned value; no signature or behaviour changes
  • Core Feature A system plugin can tell which url rules a url matches without handling a request: ApiPlugin implements Festi\Core\Plugin\IUrlRuleFinder, whose findUrlRules($url, $areaIdent) returns the matching rules of the area as UrlRuleMatch objects in the order bindRequest() compares them, so the first one is the rule a request is routed by; bindRequest() takes its rule from the same Festi\Core\Plugin\UrlRuleMatcher, and its result is unchanged. The interface is separate from ISystemPlugin, so an existing system plugin keeps loading
  • Static Analysis Refactor With src and tests typed, phpcs.xml.dist demotes only what is left of the coding-standard 1.3.0 type rules: the native type of the members a class outside the repository overrides without one, and strict_types in two files; a parameter, return or property with no type at all, and an array that does not say what it holds, are errors on the whole tree again
  • Core Refactor src/util, src/exception, src/response, src/event and src/locale declare strict_types and native parameter, return and property types, with array shapes documented: a project class that overrides one of their methods, or redeclares one of their properties, must declare compatible types, and a caller must pass the declared types; this includes the option accessors of OptionsTrait (getOption(): mixed and getOptionByRef(): mixed, used by Core, AbstractPlugin, Store and AbstractAction), Permissions::doValidate(): void, and the event dispatcher, whose event type is a string (BREAKING)
  • Core Bug Fix Calls that relied on PHP coercing a scalar are cast explicitly, so they keep their result under strict_types: image sizes and watermark coordinates in ImageUtils, the date and client language helpers in FestiUtils, the display message and code of SystemException, and the .mo reader
  • Core Bug Fix Curl::getUrl() declares what it returns and accepts: false for an HTTP error, and a JSON string or null as the post data; SystemException::setDisplayMessage() accepts the false its own constructor passes
  • DGS Refactor Store, its traits, PluginStore, the store models, the rules, the store views and presenters, the store events and the schema builders declare strict_types and native parameter, return and property types, with array shapes documented: a project store or model that overrides one of their methods, or redeclares one of their properties, must declare compatible types; removeByPrimaryKey(), removeChildByPrimaryKey() and updateByPrimaryKey() take an int or string key, so a null key is refused before any child row is removed (BREAKING)
  • DGS Bug Fix Store code that relied on PHP coercing a scalar is cast explicitly, so it keeps its result under strict_types: the page size, page index and total count read from the request and the session, the order field and direction, the audit value names, the columns attribute of a form section, and the primary key a form config returns
  • DGS Refactor The field classes under src/store/field declare strict_types and native parameter, return and property types, with array shapes documented: a project field that overrides one of their methods must declare compatible types (getFormat(): string and getPreparedValueForEdit(): string among them; getFilterTemplateName(), getInputType(), HandlerField::callHandlerMethod() and PriceField::getCurrencySymbol() keep their signature for now, because known project fields override them without a type), and a model built from an array or a class must give string attributes as strings (BREAKING)
  • DGS Bug Fix Field code that relied on PHP coercing a scalar is cast explicitly, so it keeps its result under strict_types: a numeric string formatted as a price, a percent or a number, an int cell value escaped, trimmed or encrypted, the id Many2manyField passes to the proxy, the image sizes FileField reads from the resize attribute, and the timestamp TimestampField formats
  • DGS Refactor The store proxies under src/store/proxy declare strict_types and native parameter, return and property types, with array shapes documented: a project proxy that overrides one of their methods must declare compatible types, and updateByPrimaryKey() takes a string, int or bool key on the interface and every implementation (BREAKING)
  • DGS Bug Fix Proxy calls that relied on PHP coercing a scalar are cast explicitly, so they keep their result under strict_types: a scalar filter value read from the session (a list or an object there is refused with a StoreException that names the filter, as it was refused before, instead of filtering by the word Array), a primary key passed to the child stores, the row count PgsqlProxy reads from a result row, and the int that removeByPrimaryKey() returns
  • DGS Refactor The store actions under src/store/action declare strict_types and native parameter, return and property types, with array shapes documented: a project action that overrides one of their methods, or redeclares one of their properties, must declare compatible types (EditAction::load() and loadRow() return static), and a listener that receives the saved record, the list rows or the foreign key values by reference must leave an array there (BREAKING)
  • DGS Bug Fix Action code that relied on PHP coercing a scalar is cast explicitly, so it keeps its result under strict_types: the ids ForeignKeyLoadAction splits into a list, the file column and primary key DownloadAction reads, the cell values the grid editor and the list pass to the cell renderer, the current url and tree caption RelationAction works with, and a blank line in an imported CSV
  • DGS Bug Fix An action reports a database error whose code is a non-numeric SQLSTATE, such as 42S02, as its error: AbstractAction::doHandleException() turns any exception code into an int and the previous exception keeps the original, where the handler itself used to fail with a TypeError
  • Core Refactor Core, Entity, Response, DefaultUser, Logger, the Core traits, the plugin base classes and the system object declare strict_types and native parameter, return and property types, with array shapes documented: a project class that overrides one of their methods, or redeclares one of their properties, must declare compatible types (Response is the one projects usually extend), and a caller must pass the declared types; members that a known project or shared plugin overrides without a type keep their signature for now, among them DefaultUser::get(), getID(), getRole(), getSessionData(), set() and $requiredFields, and AbstractPlugin::onInit() (BREAKING)
  • Core Bug Fix Calls that relied on PHP coercing a scalar are cast explicitly, so they keep their result under strict_types: numeric array keys used as field names in Entity::getPreparedData() and getExtendData(), a scalar request value checked against a regexp or decoded as JSON, and the storage path prefix built from an id
  • Core Bug Fix ApiPlugin answers an exception with an HTTP status only when its code is one: an integer from 400 to 599 is used as the status, a code below 400 leaves the status as it is, and anything else, such as the SQLSTATE string of a PDOException or a code above 599, answers 500 instead of failing inside the exception handler
  • Tests Refactor Every file under tests/ declares strict_types and types its parameters, returns and properties, with the array shapes documented, as festi-team/coding-standard 1.3.0 asks; ids read back from the database are typed int|string, because the drivers return them as strings
  • Tests Refactor Every public and protected helper of FestiTestCase and TestUtils declares its parameter and return types: a project test class that overrides one must declare compatible types, the onTest*StoreAction() hooks and doTestStore() are void, except onTestInsertStoreAction(), which returns the Response, the two mock builders return a PHPUnit MockObject, and an argument of another type is refused in any file, strict or not (BREAKING)
  • Static Analysis Refactor festi-team/coding-standard is raised to ~1.3.0; its new type rules (parameter, return and property types, and declare(strict_types=1)) carry a backlog and report as warnings in phpcs.xml.dist until it is burned down, while every line a branch changes is still judged at full strength
  • Static Analysis Bug Fix SystemObject::isInstalled() quotes the table name it looks for through the connection instead of concatenating it into the query, which the standard's SQL injection plugin now reports
  • Core Refactor Response::send(), ArrayStoreModel::loadFromArray(), the list action's parent-relation redirect and TestUtils::installDatabase() are split into smaller steps so they stay within the standard's complexity limits; behaviour is unchanged
  • DGS Feature The default DGS URL prefix that a relation without a url redirects under is configurable through the dgs_default_url_prefix Core option (Core::OPTION_DGS_DEFAULT_URL_PREFIX, inherited by every store as Store::OPTION_DGS_DEFAULT_URL_PREFIX), instead of the hardcoded /festi/; the value is normalised to one leading and one trailing slash, an empty value means no page is a DGS page, and the default stays /festi/
  • DGS Bug Fix A list cell's containerCss is built once in ListAction::onStorageRowValue(), from the schema's containerCss and the field's row class (m-readonly), and no longer carries a trailing space, or a lone space when the field has neither; a project that overrides onStorageRowValue() without calling the parent now decides the readonly class too
  • DGS Bug Fix RemoveAction rolls the removal back whenever the action reports an error, also in custom-update mode (a listener set isUpdated): an error set by a listener or by the store's onRemoveHandler() used to be swallowed there and the removal committed. A custom update without an error, such as a soft delete after an integrity error, still commits
  • DGS Refactor The framework no longer logs DGS action failures: a failing Store::EVENT_ROLLBACK listener used to go to FestiUtils::doHandleCoreException() (the store log); now every exception an action handles, including transaction-event listener failures, is reported through Festi\Store\Event\ActionErrorEvent::EVENT_ON_ACTION_ERROR, and a project that wants those entries in its log must subscribe to it, e.g. from Core::EVENT_ON_CREATE_STORE (BREAKING)
  • DGS Feature ActionErrorEvent::EVENT_ON_ACTION_ERROR fires whenever an action handles an exception (AbstractAction::doHandleException()), with the action and the exception; a listener that throws is ignored so it never replaces the error it reports
  • DGS Feature Store::EVENT_COMMIT fires when the transaction that saved or removed a record is really committed, including a parent transaction's commit; a failing listener is handled like one of EVENT_AFTER_UPDATE: it becomes the action error and EVENT_AFTER_UPDATE listeners do not run, while the record stays saved
  • DGS Feature Store::EVENT_ROLLBACK fires when the transaction that saved or removed a record is really rolled back, whoever rolls it back; a failing listener never replaces the error that caused the rollback
  • DGS Refactor An action that owns its transaction fires Store::EVENT_COMMIT or Store::EVENT_ROLLBACK right after its own commit() or rollback() (TransactionOutcomeNotifier::notify()); one inside a parent transaction registers listeners on the connection and fires them when the parent ends (TransactionOutcomeNotifier::notifyWhenParentTransactionEnds(), using addCommitListener()/addRollbackListener() from festi-framework-database); the record travels as a Festi\Store\Event\ActionRecord value object; the rollback methods move out of AbstractAction, which brings it back under Sonar's method limit
  • DGS Bug Fix FileField and MultiFileField keep uploads consistent with the save: files written by a save that is rolled back are removed, a file an upload replaced is restored, and the backup is deleted on commit (Store\Field\Upload\TransactionalFiles; needs festi-framework-database with ITransactionListeners)
  • DGS Documentation The autocomplete branch of ForeignKeyLoadAction documents the application/json response and the flat results list of key and value pairs it answers with, and the response type it sets is covered by a test
  • DGS Refactor IStoreField::getElementName() returns a Festi\Store\Field\ElementName instead of a string, so the name a field publishes to the UI and the name a request carries back are read through one type that owns the encoding both ways; it is Stringable, so echo, concatenation, interpolation, sprintf, implode, preg_match and loose comparison keep working, while an array subscript raises a TypeError, a strict comparison against a string is false, json_encode writes an object and http_build_query drops the value - call getValue() at those sites (BREAKING)
  • DGS Bug Fix A field declared without a name is filed by an array store model under the position it publishes as its element name, instead of under the next free integer, so its autocomplete no longer loads the values of a different field or fails to find one at all
  • DGS Bug Fix A field or column whose own name begins with two underscores, such as the __ctx context token, is no longer mistaken for a positional element name by the foreign key load action or by the audit trail, which matched anywhere in the key and turned __ctx into the position 0
  • DGS Feature List rows are styled through Festi\Store\Event\ListRowStyleEvent: the row starts from its record class and the highlight classes its schema declares, and store listeners or a store implementing IListRowStyleListener add or remove classes for a record; the list and the grid editor share one IListRowStyleResolver, ListRowStyleResolver by default and replaceable per action with setListRowStyleResolver(), so rowCss keeps its value
  • DGS Refactor Action name to class resolution moves out of Store into Festi\Store\Action\StoreActionResolver, which answers null when no class backs an action name, so the display action trait and the list web presenter stop catching StoreException to ask the same question
  • DGS Refactor MssqlProxy assembles its row-count query with the core\dao\Query builder from the database package instead of concatenating SQL, so the dialect the connection speaks writes the statement
  • DGS Bug Fix PgsqlProxy selects its row count as __total_rows instead of total, so a store over a table that has a column of that name lists the column's own value rather than the number of rows on the page; the count still reaches the pager unchanged
  • DGS Refactor The row-count alias is a single SqlProxy::TOTAL_FIELD, and MssqlProxy names its row number from a constant too, so every proxy marks its internal columns the same __-prefixed way instead of TotalRecords and repeated literals
  • Plugin Refactor ISystemPlugin no longer declares setActiveMenu and getActiveMenu; a menu is a capability declared by Festi\Theme\UI\IMenuStructureProvider, which JimboPlugin already implements, so callers reaching these through an ISystemPlugin type hint should guard with instanceof IMenuStructureProvider first (BREAKING)
  • DGS Feature Session and global scope values can be used in row templates - action link, js and html, field autocomplete, composite format and the file path attributes - via S%key% and G%key%, read from $_SESSION and $GLOBALS the same way valuesWhere reads them; they are substituted after the row values so grid data cannot introduce a scope lookup the template does not already carry
  • Core Bug Fix Entity::fillString returns an empty string for a null template instead of raising a TypeError against its string return type
  • DGS Bug Fix Parent/child relation position is carried per browsing context in a __ctx token instead of a single shared session slot, so two browser tabs on the same table no longer overwrite each other's parent; every child/parent redirect url now carries that param, so code asserting on whole redirect urls must compare path and params separately
  • DGS Refactor ForeignKeyLoadAction returns JSON for dependent field loading instead of rendering a script template that the framework does not ship (BREAKING)
  • Static Analysis Refactor Coding standard and Phan baseline are consumed from the shared festi-team/coding-standard package; the vendored .phpcs directory and the duplicated Phan stubs and plugins are removed
  • Static Analysis Feature Code style is gated twice in CI: the whole tree against phpcs.xml.dist, and changed lines against the undemoted standard
  • Code Style Refactor Array syntax converted to the short form and wrapped boolean operators moved to trailing position across the tree
  • DGS Refactor Field cell trimming split into named steps and covered by tests
  • Plugin Refactor Data access objects are built through an injectable resolver
  • Api Refactor ApiPlugin settings and url rules split into composed traits
  • Core Feature Cache event bus added for project supplied cache backends
  • Api Enhancement Settings and url rules are served from the project cache when a cache listener is registered
  • Core Feature License files added to the project
  • Documentation Enhancement Documentation format guidelines section added
  • Plugin Enhancement Jimbo plugin method support updated
  • DateTime Bug Fix hasTime method now properly returns boolean true for time option
  • DateTime Refactor Datetime handling refactored with improved filter value processing
  • DateTime Enhancement Datetime range filter tests with inclusive end time support
  • Event Feature Event listeners can now be removed with improved documentation
  • Dependencies Feature ext-intl added as required dependency for internationalization
  • Docker Bug Fix Docker configuration and documentation corrected
  • Static Analysis Bug Fix Static analysis issues resolved
  • Static Analysis Bug Fix Phan static analysis warnings resolved

7.2.0 (Oct 29, 2025)

  • Store Enhancement Reference support for stores (ED-6652)
  • Field Feature Field option support for enhanced customization
  • Many2Many Bug Fix Many2manyField logic and type hinting corrected
  • Many2Many Enhancement Many2manyField performance and query optimization improved
  • Plugin Feature Plugin namespace scanning with new naming convention
  • API Bug Fix OpenAPI Proxy issues resolved
  • Core Bug Fix Core::OPTION_DEFS_PATH configuration logic corrected
  • Store Bug Fix Total count calculation in store list action corrected
  • Theme Bug Fix Theme Package dependency issues resolved
  • Store Enhancement Proxy can now be passed to store as a class
  • Plugin Enhancement OptionsTrait support added to Plugin class
  • Tests Refactor Stage tests migrated to Theme package
  • Tests Refactor Theme tests migrated to Theme package
  • Stage Refactor Stage functionality moved to Theme package
  • Static Analysis Bug Fix Sonar code quality issues resolved
  • Template Feature getTemplateFilePath method added to ITemplate interface

7.1.0 (Jun 10, 2025)

  • Dependencies Update All dependency package versions updated
  • Security Bug Fix XSS vulnerability in text field patched
  • Tests Bug Fix Locale handling in DGS tests corrected
  • Core Bug Fix convertToSnakeCase method corrected
  • Plugin Feature Execute attribute for plugin actions added
  • UI Feature Custom captions for confirm dialog actions (ED-5992)
  • UI Feature Confirm dialog added to Group Action (ED-5992)
  • Documentation Enhancement Actions documentation translated and reorganized
  • Tests Enhancement Comprehensive XSS security tests added

7.0.10 (Aug 25, 2023)

  • Theme Update Theme library updated to latest version
  • PHP 8.1 Refactor Deprecated strftime function replaced for PHP 8.1 compatibility
  • PHP 8.1 Refactor Deprecated CheckboxField methods replaced for PHP 8.1
  • PHP 8.1 Refactor Deprecated FILTER_SANITIZE_STRING replaced for PHP 8.1
  • Field Feature Hidden attribute support for fields
  • Store Enhancement Default charset added to xmlStoreModel
  • Documentation Enhancement Field documentation translated to English
  • Documentation Enhancement Event system documentation translated to English
  • Documentation Enhancement Plugin documentation translated to English
  • Tests Enhancement Comprehensive unit test structure added
  • Tests Enhancement Unit test configuration updated and translated to English
  • Store Bug Fix Table name resolution from aliases corrected
  • Store Bug Fix Framework installer corrected
  • Many2Many Bug Fix Many2many field with setUpdated event support (HYD-483)

7.0.9 (Dec 6, 2022)

  • Core Maintenance Version maintenance release

7.0.8 (Nov 14, 2022)

  • Audit Feature Audit trail with previous value storage (EX-457)
  • CSV Feature isShowHiddenFields attribute for CSV export (EX-479)
  • CSV Enhancement Column loading logic for CSV operations improved
  • ForeignKey Feature allowEmpty option for AJAX child loading (EX-480)

7.0.7 (Sep 21, 2022)

  • Core Maintenance Version maintenance release

7.0.6 (Sep 7, 2022)

  • Core Maintenance Version maintenance release

7.0.5 (Aug 25, 2022)

  • Plugin Bug Fix Plugins path resolution issue corrected

7.0.4 (Aug 1, 2022)

  • Core Enhancement General improvements and bug fixes (EX-345)

7.0.3 (Jul 11, 2022)

  • Core Maintenance Version maintenance release

7.0.2 (Jul 6, 2022)

  • Plugin Bug Fix Plugin name resolution corrected (EX-378)
  • Static Analysis Enhancement Phan static analysis issues resolved

7.0.1 (Jun 27, 2022)

  • Core Bug Fix CURL functionality restored
  • Event Bug Fix Event handling mechanism corrected
  • Template Refactor Template namespace changed for better organization
  • Plugin Feature Plugin instantiation by class name (TestPlugin::class)
  • Config Feature Database configuration override via environment variables
  • Documentation Enhancement Documentation for test environment variables added

7.0.0 (Apr 23, 2022)

  • PHP Breaking Minimum PHP version requirement increased to 7.1
  • Architecture Breaking Store fields and actions refactored from AbstractField to interface-based architecture
  • ORM Update ORM library updated to latest version
  • ForeignKey Feature showSearchInput attribute for ForeignKeyField inline search
  • DateTime Feature Timezone attributes with automatic value conversion
  • Field Feature Custom field support with isCustom method
  • Response Feature Response type for opening content in new browser tabs
  • CSV Feature CSV export support for encrypted fields
  • Static Analysis Feature PHPStan integrated for static code analysis
  • Many2Many Enhancement Many-to-Many field handling in SqlProxy improved
  • Template Enhancement Variable passing to field templates improved
  • Refactor String constants replaced with method calls for maintainability
  • PHP 8.1 Bug Fix PHP 8.1 deprecation warnings resolved
  • Store Bug Fix IStore interface implementation corrected
  • Stage Bug Fix Stage builder with improved type hinting

6.0.0 (Jan 23, 2022)

  • Locale Bug Fix Locale tool functionality restored

5.9 (Nov 19, 2019)

  • Feature Enhancement General feature improvements and enhancements

5.4 (Sep 8, 2018)

  • Filter Bug Fix Table name resolution for field filters corrected
  • Filter Enhancement Filter code performance optimized

rc3.0 (Mar 14, 2017)

  • Field Enhancement Percent field functionality improved
  • CSV Feature CSV import and export actions
  • Demo Feature Demo content installation system
  • Filter Feature Multiple filter support

rc2.4 (Mar 29, 2016)

  • Theme Feature Theme Event system support

rc2.3 (Mar 29, 2016)

  • Theme Feature Theme Event system support

rc2.2 (Mar 15, 2016)

  • Core Bug Fix PHP warning issues resolved

rc2.1 (Feb 26, 2016)

  • Router Bug Fix Router functionality corrected
  • Validation Feature Frontend form validation

rc2.0 (Dec 8, 2015)

  • Entity Bug Fix Null casting in Entity string validation
  • Database Enhancement Database library improvements
  • CSV Feature CSV Export action

rc1.2 (Aug 21, 2015)

  • PostgreSQL Feature Full PostgreSQL database support
  • Field Feature Float Field for decimal number handling

rc1.1 (Dec 7, 2015)

  • Field Feature Encryption support for fields
  • Field Enhancement General field enhancements

rc1.0 (Jan 27, 2015)

  • Core Feature Initial release candidate version
  • DGS Feature DataGridStore (DGS) architecture established
  • Plugin Feature Plugin system foundation
  • Documentation Enhancement Initial documentation

release-0.3.0-alpha (Jun 28, 2014)

  • Architecture Refactor SQL proxy removed from submodules
  • Field Feature SerializeField for complex data structure handling
  • Field Feature AutocompleteField and MultiAutocompleteField
  • Field Feature Price Field for currency handling
  • Template Feature DateTime field templates

release-0.2.1-alpha (Jan 4, 2014)

  • Theme Feature Theme system support
  • Template Enhancement Template engine improvements

release-0.1.1-alpha (Sep 3, 2013)

  • Core Bug Fix Global variable usage corrected
  • Core Feature Initial alpha release