I will soon lock down the b2evolution.net site. It will become read only and neither you nor me will be able to post new content to it.
Original post blogged on b2evolution.
The fourth feature release of Drupal 11 is another performance breakthrough. Using only a third of the database and cache lookups compared to Drupal 11.0 and 10.6 for the same requests. It also comes with 15-25% better compression of JS and CSS, much faster translation file handling, a new native command line interface, improved password hashing and a lot more.

With Drupal 11.3, we announced that it was the biggest performance improvement of the decade. Drupal 11.4 is arguably the biggest performance improvement of the decade again!
Drupal 11.4 reduces database queries by half compared to 11.3 across a wide range of requests due to optimizations in how entity fields are loaded.
Now, on a completely cold cache, Drupal 11.4 will execute just over 1/3rd of the database and cache lookups compared to Drupal 11.0 or 10.6, representing hundreds of milliseconds saved.
As well as entity loading, entity listing queries have also been significantly improved via reducing the number of table joins, leading to fewer slow queries. This should particularly benefit sites using JSON:API.
To reduce the cost of rendering menus and improve render cache hit rates, menu blocks now have a configuration option to not generate CSS classes for ancestor menu links.
We have made recipe-based site installation twice as fast. This significantly improves the UX of installing Drupal CMS and other site recipes. Installing individual recipes is also markedly faster.
Importing translations during the installer or during site operation is now much faster. On a test site with 66 projects and 38 languages, checking for translation updates was 87% faster on Drupal 11.4 compared to 11.3.
The APIs handling translation files and import have undergone an extensive modernization effort. All .inc files and several important APIs in locale.module have been deprecated and updated to OOP with special attention paid to performance and organization.
Drupal now supports Brotli compression for aggregated CSS and JavaScript files in addition to the existing gzip compression. Brotli typically provides 15-25% better compression ratios than gzip, resulting in faster page loads for browsers that support it. The feature relies on the PHP Brotli extension: ext-brotli.
The drupal/core-recommended package no longer pins minor versions for dependencies like Guzzle, Twig, and Symfony Polyfills. In the past, stricter version rules and Composer 2.9's blocking behaviour forced sites to wait for a new Drupal release to get important security fixes. Now, you can install these security fixes immediately. Since the updated dependencies at that time may not have been tested with Drupal core yet, site owners should ensure adequate quality assurance occurs before deploying to production.
A new extensible ./vendor/bin/dr command line interface was added. While Drupal already includes a CLI script with hardcoded commands, it is not extensible. This new interface was built by a team which included the maintainers of the Drush utility. Drush has been a mainstay for people using Drupal with the command line. Now a transitional period starts as Drush is gradually replaced with the core dr CLI over time. Learn how to make your existing Drush commands compatible.
The default installation, the Standard profile, is now leaner. It no longer includes the Article and Page content types, and commenting is disabled by default. Further core startup simplifications are planned for upcoming releases.
The Navigation module is now enabled in the standard administrative interface. The legacy Toolbar module remains available but is scheduled for removal in Drupal 12.
A new overview page has been added under the "Manage display" tab for content entity bundles. Previously, this tab led to the form editing the default view mode. Now, it lists all display modes for the bundle with their label and description and allows one to toggle the enabled/disabled status. The listing makes it easier to integrate tools such as Drupal Canvas.
Text formats using CKEditor can now be configured to include the FullScreen plugin. This plugin lets users expand the editor to the whole browser viewport, giving more space to comfortably edit content in a distraction-free environment.
The password hashing algorithm is now configurable. The new argon2id option provides much stronger hashing compared to the old bcrypt method. Drupal 12 will default to argon2id, but your site can already start to adopt it. If you update the setting, users' passwords will be rehashed on their next login.
You can now use attributes on your controllers to specify the routes the controller is used for. Any class in a module's Controller namespace (for example, Drupal\example\Controller) that have the Symfony\Component\Routing\Attribute\Route attribute will be picked up as route definitions. Even multiple routes can be defined on one class. This supplements the existing .routing.yml based declarations.
It is now possible to use the Drupal\Core\Entity\Attribute\Bundle attribute to define bundle classes, when in need of specific logic for an entity subtype. This previously required an entity_type_info or entity_type_info_alter implementation.
All .theme and .theme-settings.php files in core have moved to PHP classes. Support for .theme files is still planned to be retained in Drupal 12 to ease the transition, but will be removed in Drupal 13.
Most .module files have been converted too: 32 modules are fully converted to PHP classes, with 11 modules remaining (4 of which are deprecated for removal in Drupal 12).
A team of 26 key contributors worked on 57 issues since January 2026 to get here, making Drupal's code more consistent. Also thanks to the dozens of users that worked on the many decades old issues that this initiative built upon.
Drupal now integrates the Symfony Runtime component to separate bootstrapping logic from request handling. This provides a clear separation of concerns between preparing the environment (runtime) and handling a request, which will also later enable better integration with FrankenPHP.
A new trait for kernel tests, HttpKernelUiHelperTrait, allows kernel tests to make HTTP requests to the test site and make assertions against the returned content. This has the potential for many browser tests to be converted to kernel tests, which are much faster to run because unlike browser tests, they don't fully set up a test site by running the Drupal installer.
The Gin administrative theme has been added to Drupal core as the "Default Admin" experimental theme. The theme includes a new dark mode option.
While it is not yet actually the default admin theme, when it becomes stable it will replace Claro as the look of Drupal's backend. We encourage module maintainers to test their module's UIs and provide feedback!

Since Drupal 11.3 Andrei Mateescu was appointed as a provisional general core committer and is now a Content Moderation and the Workflows module maintainer too. Also Edward Wu was appointed as provisional release manager.
Various wonderful contributors also took our call for subsystem maintainership:
We also thank maintainers that stepped down in this period:
If you are looking to make the leap from Drupal user to Drupal contributor, or you want to share resources with your team as part of their professional development, there are many opportunities to deepen your Drupal skill set and give back to the community. Check out the Drupal contributor guide.
You would be more than welcome to join us at DrupalCon Rotterdam in September 2026 to attend sessions, network, and enjoy mentorship for your first contributions.
Drupal 12 will be released with the upcoming Drupal 11.5 at the beginning of December this year. Drupal 11.5 will be a Long Term Support release with version 11 support expected until the end of 2028.
[!CAUTION] v2.3.12 is a security release for sites on v2.3.11 or earlier. Upgrade from any 2.x at or below v2.3.11. If your site tracks the
masterbranch, you are already past v2.3.12, so installing it would be a downgrade. v2.4.x is planned to be the next forward step.[!IMPORTANT] Upgrade immediately. This release closes twenty-one security advisories outright and narrows a twenty-second, which needs one setting from you as well as the update.
Advisory Severity What it was GHSA-f7x7-v438-qmf4 9.9 Critical A member's post could attach a script to an ordinary image and run it for every reader, with nothing to click. GHSA-48cx-ccvq-52mc 9.9 Critical Anything a member could write in BBCode could run script in another visitor's browser. GHSA-9gr7-g6pw-5244 9.8 Critical Anyone could sign in as a social-linked account, an administrator's included, with no password. GHSA-m8v8-wc99-3h82 8.8 High A member could give themselves a second way in that a password change does not revoke. GHSA-pq2m-9gxf-64x9 8.8 High A member who knew only their own password could write any column on their own account, including the two that make it an administrator. GHSA-7v5h-vrhj-wjf5 8.1 High One read of the database forged a working login for every account on the site. GHSA-vr9h-v6xq-4m35 8.1 High The automatic ban ships switched on and never counted a wrong password. GHSA-c33m-2hph-47p4 7.4 High Comparisons in the login paths accepted values that were not the ones expected. GHSA-pf37-7c5m-mpg3 7.2 High An administrator holding only the Languages permission could put PHP of their choosing into a file every visitor loads. GHSA-22m8-h4gh-vfh3 6.8 Medium Signing in did not change the session identifier, so one known beforehand was known afterward. GHSA-pw4f-4544-2mv2 6.5 Medium A handler meant only for the command line answered anonymous web requests as the main administrator. GHSA-9qgj-v67f-r22q 6.5 Medium Anyone holding a live session could set a new password without knowing the old one. GHSA-396r-g8m8-w9xx 6.1 Medium A crafted link to the File Inspector ran script in the browser of the administrator who opened it. GHSA-3357-4wp5-w5fp 6.1 Medium A contact form open to guests handed posted values back to the page as markup. GHSA-cg88-cq8v-w6g6 5.4 Medium A page an administrator merely looked at could run database operations on their site. GHSA-8xcj-7wmw-r939 5.4 Medium A page anyone could link to deleted a member's private messages and rewrote their block list. GHSA-72mc-5q6j-2fqq 5.3 Medium One anonymous request returned every user name on the site, banned and unvalidated accounts included. GHSA-qj68-pgg7-hf3p 5.3 Medium Anyone could publish FAQ entries without an account. GHSA-f2q2-hchc-h3wx 4.3 Medium One member could vote on a star rating over and over and set the average to anything. GHSA-vhf5-8cfh-jr22 4.3 Medium Comments switched off on an item did not stop a member commenting on it. GHSA-hwmh-m57x-w5h2 3.8 Low The user manager did not apply the rules that protect administrator accounts to its batch route or to its single-row Unban and Delete controls. GHSA-w24r-4r8j-vqgc 3.7 Low A forged Hostheader was reflected into the addresses the site prints. Also needs a setting from you, see below.The two worst need only a member account. GHSA-f7x7-v438-qmf4 and GHSA-48cx-ccvq-52mc between them reach every field that accepts BBCode: forum posts, comments, profile signatures. An administrator who views that content can install plugins, so the ceiling is a full site takeover.
Several need no account at all, so any internet-facing site could have been reached through them. The three worst are the passwordless sign-in as a social-linked account (GHSA-9gr7-g6pw-5244), the unlimited password guessing (GHSA-vr9h-v6xq-4m35), and the anonymous request that boots e107 as the main administrator (GHSA-pw4f-4544-2mv2). Look through Admin » Users for accounts you do not recognize, and for accounts holding administrator rights that should not.
Two can be narrowed short of upgrading. GHSA-3357-4wp5-w5fp reaches a browser allowed to see the affected contact form. Setting Contact page visibility to Members removes the exposure for signed-out visitors, but a signed-in member or administrator can still be targeted. To narrow it further, set the page to Nobody, and set the bundled contact menu's own visibility to Nobody in Menu Manager or remove it too, since that menu does not read the contact preference. Members-only has shipped since v2.3.2, so an older site is open to guests until someone changes it. GHSA-pf37-7c5m-mpg3 needs an admin account holding the Languages permission, so withdrawing it narrows the exposure. Neither issue can be hidden from the main administrator, who passes every class and permission check.
One is not closed by upgrading. GHSA-w24r-4r8j-vqgc needs a hostname from you, not a code change: a default install has never told e107 which hostnames it answers to, so it builds its addresses from whatever
Hostheader arrives. Setsiteurlto your site's full address, or list your hostnames under Trusted Hosts in Admin » Preferences. Either one arms the boot-time host check, so name every hostname you serve on: once e107 knows of one, a request arriving on a hostname you did not name is refused outright. Until you do, and while your site still takes new member registrations, a stranger can have e107 email somebody an account activation link that points at a host they picked.[!WARNING] Some of these fixes change behavior you may be relying on. Each is deliberate, and each can stop something that worked in v2.3.11.
Do these before you upgrade.
- Check that Trusted Hosts names every hostname you serve on. The boot-time host check now runs, and a request on a hostname you missed is refused outright. An empty list and a relative
siteurlcannot lock you out, but a wrong entry in either can, and it takes the admin area with it. The way back in is the database: correcttrusted_hostsorsiteurlin theSitePrefsrow, then deleteS_Config_core.cache.phpfrom youre107_systemcache directory so the correction is read.- Export the Failed logins list if you keep it as a long-term record. The ban check now prunes that history to thirty days as it runs.
- Regenerate your cron token if you have ever sent the Test Email in Admin » Schedule Tasks. That mail carried the token, so treat it as known.
- Review the ban list for wildcard entries. An entry such as
10.77.66.*never matched anybody and starts matching after the ban files are next regenerated on v2.3.12. A wildcard whitelist row stays inert, as it was.- List your subdomains under Trusted Hosts if your site spans them. A valid security token no longer overrules a browser that says the request came from somewhere else.
- Check Admin » Preferences for the
user_trackingsetting while it is still there. On a site installed before v2.3.0 where it was never changed, it reads cookie mode, and visitors can keep the old forgeable authentication cookie for up to thirty days, since only an explicit logout expires it. The old cookie can no longer authenticate anyone after upgrading. If you also want every browser to begin a fresh session under a new name, change Cookie Name in Admin » Preferences afterward.These may behave differently afterward.
- State-changing links now need a security token in their address: logging out, rating votes, social sign-in, deleting a private message, marking a forum read, much of the admin area. If logging out stops working, a theme, menu, or template is spelling out
index.php?logoutby hand and needs updating.- On PHP 8, a plugin that extends e107's routing or controller classes can stop the site loading, every page blank or naming the plugin's class in a
Fatal error: Declaration of ...line. Rename that plugin's folder to get the site back, then send its author the one-line fix at For Developers » Changed.- *Five `LANCRON
constants are gone**, and a theme or plugin that prints one bare is a fatal error on PHP 8. If a page dies after upgrading, search your theme and plugin folders forLANCRON`; the five are named at For Developers » Changed.- The "Remember Me" checkbox is gone from the login form, and a browser still holding an old login cookie is signed out.
- Eleven BBCode tags now filter or encode whatever a post writes into them.
[table]keeps only the presentational attributes the tag was meant for and drops any others.- A news submission is stored through the same BBCode save pass as a comment or a forum post, so a tag's parameters are filtered on the way in and a submitter whose user class may post HTML keeps it, where the form used to strip every tag.
- An
[img]height, caption, orloadingvalue set in the editor survives the save, where each used to be discarded every time.- Changing your own password in Settings now asks you to confirm it with your current password.
- Wrong passwords count toward the automatic ban, and an address that exceeds the failed-login limit is banned for an hour. That hour arrives with Admin » Database » Update; until you run it, a wrong password is recorded and nobody is banned.
- The SMTP connection test now needs the main-administrator permission, and the stored SMTP password is masked on the preferences screens.
- Scheduled tasks triggered over a URL run as a guest, not as the first administrator. None of e107's own tasks cares; a third-party task that reads who is running it will behave differently.
- On Apache, direct web requests into
e107_handlersare refused where the server honors e107's.htaccessfiles. Core serves nothing from that directory directly, but a third-party public asset or endpoint placed there stops being served and must move.- A message sent through the contact form arrives as text, not as markup. A form whose senders were relied on to format their own messages loses that.
- The Sign-in plugin no longer loads the Login menu's language file, so a custom
signin_template.phpwritten against the oldLAN_LOGINMENU_*names loses its labels.After upgrading, run Admin » Database » Update. This release changes the database: the failed-login ban gains a duration, the
generictable gains two indexes, and session rows are re-keyed. Until you run it, those are pending.On PHP 5.6 or 7.x, also check "Proof that a request came from this site" in Admin » Preferences after upgrading. Saving that screen while the recommended value was unset could silently select Off and stop e107 publishing security tokens. v2.3.12 fixes the form comparison but deliberately does not change a setting already stored as Off.
For Administrators below has the detail on each.
.. entry could delete e107_system, taking the cache, the logs, your saved backups, and the ban-list files with it. (#6119)schtasks line, and a shell script. The Manage tab reports refused calls and links to Setup. (#5919)user_tracking preference is gone from Admin » Preferences and the authentication token lives only in the server-side session, so a browser still holding the old cookie is anonymous until its owner signs in again. The checkbox goes even from a site that had the setting switched off, since five of the eight places that drew it never consulted it. (GHSA-7v5h-vrhj-wjf5)ban_durations had no install default, so a failed-login ban was written with no expiry and never lifted. The counter now reads a rolling hour and failed-login bans get a one-hour duration, applied to existing sites once on upgrade and never overruling one you set afterward. The ban lands on the eleventh failure from one address inside the hour, successful sign-ins notwithstanding, and none is raised while that duration is missing or set to Indefinite. (GHSA-vr9h-v6xq-4m35)1e3 confirmed 1000, and it stored the first box, locking an administrator out of the account they thought they had set. On a site that allows email login it also stored a hash of the empty string as the email password. Its hand-written form now carries the token every other e107 form does.generic table gains two indexes on upgrade. The ban counter reads that table on every failed login, and had to read every row ever recorded to do so. The check now drops history older than thirty days as it runs. (GHSA-vr9h-v6xq-4m35)siteurl. An operator who listed their hostnames under Trusted Hosts and left siteurl relative was never checked against the list they had just written. (GHSA-w24r-4r8j-vqgc)1e3 opened a page whose password was 1000. On PHP 8, where a trailing space no longer stops PHP reading a string as a number, 123 opened it too. Both stop working, and the same tightening applies to a plaintext password held by an alt_auth source. (GHSA-c33m-2hph-47p4)e107-cli. Over the web the run is an ordinary guest request, so your trusted-host list and the ban check apply; use the site address the Setup tab shows rather than localhost or an IP. (#5919)98b4920)[table] is the one to check after upgrading. It rebuilds the tag from the attributes you wrote, keeping the presentational ones it was meant for and dropping anything else, so a table styled through an unusual attribute falls back to the default.http, https, ftp, ftps, or mailto now renders as its own words. The old guard looked for javascript: alone and a tab inside the scheme walked past it, so [link] and [url] ask the URL encoder instead; a tel: or sms: link keeps its text and stops being a link.[img], [textarea], and [stream] drop only the offending attribute; [alert] and [block] go through the class-attribute guard the other tags already used, with [alert] falling back to its default styling if its parameter holds whitespace; and [link], [email], [quote], and [flash] encode their values instead of listing them.Deprecated: Constant E_STRICT is deprecated line appeared twice at the head of every response, carrying your full server path. Scheduled tasks and command-line runs no longer bury their own output in deprecation notices. (#5951, #5952)?logout on any page has been the e107 idiom since v1, so a theme, menu, or template that spells it out by hand leaves the visitor signed in with an error on the page. Both bundled themes and every core logout link were updated.rate.php ballot and index.php?provider= both acted on a bare address. The address a provider returns to is unchanged, so nothing is re-registered with Facebook or Google.e107_admin/e107_update.php reached the update routines from a bare address, so an image tag on a page a main administrator visited flushed the system cache and re-ran every installed plugin's setup file. The page's own form still submits.10.77.66.* was stored as typed while the check compares an encoded form of the address, so it sat in the Banlist looking live and stopped nobody. Trailing whole-octet IPv4 wildcards are encoded now, on ban rows alone: a wildcard whitelist row stays inert. It takes effect the next time that screen rewrites the ban files, so open Admin » Users » Banlist and save one entry after upgrading. (#6114)LAN_SIGNIN_* strings replace them with identical text; the twelfth, the password field's label, now comes from core's LAN_PASSWORD and reads "Password" rather than "Password: ". (#5719)[textarea] and [stream] emitted each parameter as an attribute name, where no encoding protects it, so a member could write an event handler that fires for every reader with nothing to click. [img], [alert], and [block] let a parameter escape its quotes to the same end. All five now filter or encode each parameter where they render it, which is the only boundary that reaches content stored before the upgrade. (GHSA-f7x7-v438-qmf4, GHSA-48cx-ccvq-52mc)
[img] pasted id, class, style, title, loading, and the width and height overrides into the tag as given, for every caller and not only the BBCode.e_parse::isBBcode() answered false for any text carrying something shaped like , so a single tag anywhere in a submission skipped every save-time BBCode filter. It answers on the BBCode alone now. Stored rows are untouched, and the next save of such a post is filtered like any other. (#6265)[img] keeps a caption and a loading value through the save, as it now does a height, and the caption reaches the reader as it was typed rather than with its apostrophes spelled out.[link], [email], [quote], [table], and [flash] each wrote the text after the tag name, and for [email] and [table] the text between the tags as well, straight into an HTML attribute or an inline script, so a member could attach an event handler, or with [flash] inject an element outright. Ordinary content renders as before; [table] is the exception, above. (GHSA-48cx-ccvq-52mc)autologin field reached the login handler, which read provider out of it as an internal mode. That mode matches on the account's social-provider identifier, a name rather than a secret, and returns success without checking a password: that identifier posted with an empty password signed you in as that account, even with social login switched off. The identifier was member-editable, a second credential no password change revokes. (GHSA-9gr7-g6pw-5244, GHSA-m8v8-wc99-3h82)e107_session table after upgrading, because an identifier captured before it works until its row expires. (GHSA-7v5h-vrhj-wjf5)e1097e6, 641d761)508dc47, f12d76c)e107_system and overwrote the cached timestamp Admin » Mail reads, once per request, with nothing to stop it. The caller could not supply the message text, so it could not mark anyone's mail as bounced or make the site send mail. (GHSA-pw4f-4544-2mv2)Host header the visitor sent. e107 puts them in its canonical link, its OpenGraph tags and its resource URLs, and the check that should have refused an unrecognized hostname almost never ran. It now runs whenever you have told e107 a hostname of its own; if you never have, the note at the top of these release notes says what to set, because upgrading alone does not close this for you. (GHSA-w24r-4r8j-vqgc)403 Bad URL. (#5893).. entry no longer deletes e107_system. Uploading a zip whose first entry sits above the root made the unpacker resolve its destination to the parent of e107's temporary directory and remove it recursively, taking the cache, the logs, the saved backups, the import folder, and the ban-list files. Four upload failures that all reported "Couldn't detect the root folder in the zip" now name their reason. (#6119){SITELINKS} or {SITELINKS_ALT} went blank. Neither bundled theme does. (#6065){FEATUREBOX}. Its preview printed a name only the Feature Box plugin defines, which is fatal on PHP 8 when that plugin is inactive. (#5956)_blank sample plugin's navbar link points at the page it ships rather than a file it does not contain. (#5790, #6224)e_PAGE is defined after the 404 page has been rendered, so a plugin's shortcode file read a constant that did not exist yet and every unknown URL ended in a fatal on PHP 8. (#6024)?page= value, which was an uncaught error on PHP 8, so a scanner or a mistyped link took the page down. Only sites whose News pagination preference was changed are reached. (#6110)[email] and [link] survive the "Make URLs clickable" setting. The clickable pass ran before the BBCodes and handed them an anchor where they expected an address, so [link] lost the address and [email] produced a link inside a link. It runs after them now. (#5954)e107-cli or nobody. (#5944)/news.php. (#5614)Reply-To header, which many mail clients never show and which is lost when the message is forwarded. The From address stays the site's own, so the site's mail still passes SPF. (#5980)forum_icons_template.php left about two dozen IMAGE_* names undefined, which is fatal on PHP 8. Such a theme now renders the plugin's default icon where it drew nothing. (#6209)[youtube] save path for a pasted embed replaced the post with a placeholder, printed its parse into the save response, and dropped the privacy-domain setting; a pasted player now round-trips. (#6265)url_config made every address carrying a path fail on PHP 8 while the front page kept answering. Admin » Database » Update puts the default back. (#5928)open_basedir set. It probes absolute paths for a control panel and a PHP binary, most of them outside every allowed prefix on such a host. (#5991)@ was collected and printed anyway, which the atomic cache write made visible: every ordinary cache miss began reporting a failed stat. (#5992)thumb.php address to the delete and to the size check, so a checked image was cleared from the account and left on disk while every avatar counted as missing, and the height report read the width preference. (#6023)body, p, a, and a:hover, into the shared inline stylesheet the admin header emits. (#5981)e_parse::toJsString() encodes a value for a JavaScript string literal. It returns the quoted literal, its own quotes included, so interpolate the result bare. Quotes, apostrophes, angle brackets, and ampersands become hex escapes, so it is safe inside an HTML attribute carrying script too. Encoding failure returns an empty string literal. (5b27db3)e107::writeFileAtomic($file, $data, $mode = null) writes a file so a concurrent reader gets the old contents or the new, never a partial one. It writes through tempnam() in the target's own directory and rename()s into place, falling back to file_put_contents() where either step is impossible, so a true return means the file holds the data, not that the write was atomic. Overwriting resets mode and owner, and the read side is uncovered. (#5935)e_form::copyable() renders a block of text with a Copy button. It takes the text and an optional label, and emits its own script and styles once per page. New LAN_EFORM_COPY and LAN_EFORM_COPIED constants back it. (#5944)e107\Reflection\ReflectionProperty and e107\Reflection\ReflectionMethod make private members readable across the whole supported PHP range. PHP 8.1 stopped requiring setAccessible() and 8.5 deprecates the now-empty call, while 5.6 through 8.0 still require it, so these two subclasses make the call themselves only where it is needed. (#5951)eRequest $request = null is the only nullable spelling PHP 5.6 accepts and the one PHP 8.4 deprecates, so it had to go. The five that fatal are eFront::dispatch(), eDispatcher::dispatch(), eController::run(), eUrlConfig::parse(), and e_session::fetchMetadataReachesUs(). The fix is one line: delete the eRequest, eRouter, eResponse, or array hint from the override. Watch eUrlConfig::parse(), which a plugin shipping its own url/url.php overrides. (#5951, #5930)(integer) casts through setAccessible(), implicitly nullable parameters, E_STRICT, and strptime(). The suite does not reach everything the tree holds. Two are replacements rather than respellings: Latin-1 conversion goes through iconv() alone, and e_parse::cleanHtml() now substitutes ? for invalid UTF-8 where it produced U+FFFD. (#5951)e107plugin::execute_plugin_method() no longer assigns version_from onto a plugin's setup class, which should call e107::getPlugin(), and e107::__get() memoizes into a static array, so $e107->tp after e107::destruct() returns the object rather than null. Six classes now declare the properties their constructors assign, so a subclass declaring one with narrower visibility fails with an access-level error. (#5951)LOGIN_TABLE_REMEMBERME, LOGIN_TABLE_AUTOLOGIN, and LOGIN_TABLE_AUTOLOGIN_LAN go from the core login form; {LM_REMEMBERME} and {SIGNIN_REMEMBERME} go from the Login menu and Sign-in plugins. An unresolved shortcode renders empty; the language constants stay defined, since a removed constant printed bare is fatal on PHP 8. The user_tracking row is no longer seeded on a fresh install, so third-party code reading it without varset() warns there; on an upgraded site it survives reading cookie, so code branching on it takes the branch for a feature that is gone. session_set() keeps its value in the session and ignores its $expire, $path, $domain, and $secure arguments, so a value parked across visits is lost when the session ends. e_session_db::_sanitize() is now static, which is fatal for a subclass that overrides it as an instance method. Session rows are re-keyed to a digest of the identifier, which raises a pending core update on essentially every site. (GHSA-7v5h-vrhj-wjf5)userlogin::login() no longer honors 'provider' in its $autologin argument, and user_xup is out of the member-editable field list. The provider mode travels on an instance flag only userlogin::loginProvider() sets, and class2.php casts the posted value to an integer, so no mode can be selected from a request. The documented 'signup' value still works, and social signup and login write user_xup server-side without consulting the field list. (GHSA-9gr7-g6pw-5244, GHSA-m8v8-wc99-3h82)updated_data, updated_key, updated_extended, extended_key, and the private getValidationKey() in usersettings.php are gone; the change is held in the session and the form carries an opaque handle. A confirmation the server is no longer holding anything for now says so instead of reporting success, and the confirmed values are no longer passed through filter(..., 'str'), which had been double-encoding plain fields and flattening cleanHtml() output on rich-text ones. UserHandler::hasReadonlyField() has also been repaired. It answered false for any field set it could not iterate, which is the one answer its own documentation promises never to give; it now reads an array or a Traversable, refuses anything else rather than reporting no restricted field, and recognizes a list of field names as well as a keyed set. user_class remains writable, because user settings already filters it through the classes the member is allowed to edit. (GHSA-pq2m-9gxf-64x9)e_admin_controller_ui::_handleListBatch() asked only that the posted column be a declared field; it now also requires the 'batch' flag the dropdown is built from, and refuses a field declared 'data' => false. A plugin admin UI that posts a batch trigger for a field it never marked 'batch' => true now silently does nothing. (#6074)$_E107['cli'] is refused when the request arrived over HTTP. The shared guard tests the shape of the request, as cron.php already did, rather than a User-Agent header and the debug flag. The debug clause is deleted rather than corrected: nothing has read e107_config.php that early. (GHSA-pw4f-4544-2mv2)e107_handlers. Core fetches nothing from that directory over the web, so a default installation loses nothing. A third-party public asset or endpoint placed there stops being served on a host that honors e107's .htaccess files and must move elsewhere. The PHP bootstrap guard above remains the protection on servers that ignore the file. (064476a)cronScheduler's docblock states the contract: a task runs as the first administrator from the command line and as a guest over HTTP, so it must not read ADMIN, USERID, USERNAME, or USERCLASS_LIST, and must not assume e107::redirect() is a no-op. cronScheduler::run() now takes a $via argument, so a subclass overriding run() with no parameters fails to load; nothing in the tree does. (#5944)constants and one help file are gone, and about forty constants are new.**LAN_CRON_13,LAN_CRON_14,LAN_CRON_15,LAN_CRON_16, andLAN_CRON_60go, withe107_languages/English/admin/help/cron.php`. A language pack that translates the old names needs updating, and a theme or plugin that prints any of the five bare is a fatal error on PHP 8. (#5944)804197f)
^[a-z0-9_]+$, and one that does not is left in the page as written. Members reach this parser, because toEmail() turns shortcode parsing on by default.var_export().email.php narrows its plugin source parameter, as print.php already did, differing only in that it also permits a hyphen.e_jsmanager encodes every asset URL it prints and packs its registry through one method. A path or media value registered by a caller reached a or attribute unencoded, and one carrying the #|# separator the registry joins on could shift the fields of its own record. Reaching either needs a permission that already grants code execution, so this is hardening rather than an advisory. (cceda40, ac2f645)select(), count(), delete(), fields(), insert(), update(), replace(), db_UpdateArray(), db_FieldList()) return false, without throwing, and record error number -1 for a table name outside [A-Za-z0-9_]+, on both drivers, so a plugin that passed a dotted db.table or a backticked name now gets false. And users_admin_ui::beforeUpdate() filters user_class through checkAllowed(), so every route into the model applies the userclass_editclass rule, vetting a withdrawal like an assignment.e_db_pdo::close() now releases the connection, and the last result set with it. A PDOStatement holds a reference to its connection, so nulling the handle left the server connection alive whenever a query had run. Nothing fetched before close() is readable after it. The mysqli driver already behaved this way. (#5935)e_pref::save() honors its own "no messages" argument. Both error branches printed the raw mySQL error #NNNN on screen whatever the caller asked for. Around two dozen core callers pass false, so a failed preference write in the update routines, the plugin handler, Menu Manager, Search, or the URL configuration now reports to Admin > Logs. (#6018)null used to be stored, which is invisible to isset(), so a theme calling count() or in_array() on getThemePref('x') took the page down on PHP 8. A field the form posts as name[] now stores an empty array, everything else an empty string. If your theme relied on getThemePref('x', 'fallback') returning the fallback for an unticked box, it now gets ''. A theme's values also save into its own preference row. (#5995, #6036, #6133)e107-tests up and e107-tests ci-unit, so a CI failure reproduces locally with those two commands. The unit matrix no longer measures coverage nothing consumed, which was most of its wall clock: the PHP 5.6 cell falls from about sixteen minutes to two. (#5884, #6180, #6221, #5940, #6270)AdminConfirmTokenTest is gone: it matched source against a registry pinned by file and line, so unrelated edits failed it, and 0038_AdminConfirmTokenCest covers the same ground against the real forms. The entry-script sweep now runs each script in its own process and fails on a warning or a notice. themeHandlerTest stops leaving theme preferences behind, which had been failing a later case in a shuffled run, and .gitignore now covers the e107_config.php.bak the harness writes while a test site is up, which a broad git add could stage with the database password in it. (a6e54a1, #5917, #6228)?debug= set. (#5886, #5917, #6109)sitelinks::get() called count() on a missing submenu bucket in link display mode 3; e_parse::toAvatar() multiplied an empty string when the documented hd option was used with no explicit height; four forum templates read IMAGE_post2 and IMAGE_e as barewords at include time; and language::$_select_array was read before it was declared. None is reachable from core or a shipped theme. (#6073, #6060, #6086, #6085)e_db::getLastErrorNumber() returns a MySQL error number on the PDO driver. It returned PDOException::getCode(), the SQLSTATE: '23000' where the mysqli driver returns 1062. PDO is the shipped default, so every caller comparing that value against a MySQL error code failed silently on nearly every install. The Feature Box admin screen is the demonstrated casualty: its duplicate-key branch never ran. Four more failure paths now record a number as well as the text. (#5993, #6040)e_file::isValidURL() connects to the address its own policy check passed. It asked the outbound policy about the URL, then handed the URL as typed to fopen(), so the stream wrapper resolved the name again and connected wherever that answer pointed. Every other outbound path in the class already pins. The one-second budget now travels in the request context, and the status line is parsed as a code rather than searched as a substring. (#6027)ini_set() calls on the session settings cannot take effect. The harness now generates Codeception's actor classes in its own process, and the suite bootstrap turns off display_errors before e107 boots. Bringing an existing environment up with different flags either takes effect or stops and tells you to recreate it, and ci-unit checks whether the running PHP has xdebug loaded rather than trusting a label. (#5884, #5950, #5937)Thanks to
Host header reflected into the site's addresses; and, in a second batch on September 5, the File Inspector link that ran script in an administrator's session, the private messages a page could delete, the member typeahead that answered anyone who asked, the FAQ entries that needed no account, and the comments accepted where they had been switched off; to{FEATUREBOX} (#5956), the Feature Box menu that drew nothing (#5957), the PDO driver reporting a SQLSTATE (#5993), the theme manager reading another theme's language file (#5996), the member list reporting no members (#6002), the Login menu's missing plugin statistics (#6044) and its statistics rendering outside the menu (#6048), the theme options that reported "saved" either way (#6018), and the Users Online page empty since 2019 (#6108) with its hand-built profile links (#6107). Her chatbox fork also identified the emote panel's dead first click (#5613) and the form actions that break search-engine-friendly URLs (#5614, #5616); toopen_basedir warnings filling Schedule Tasks > Setup (#5991), the silenced cache diagnostics reaching the PHP Errors panel (#5992), the contact form drawing a second CAPTCHA over a plugin's own (#6014), the ban durations and failed logins that could not be saved (#6051), the missing username placeholder (#6096), and the news page killed by a junk ?page= value (#6110); toFull changelog: https://github.com/e107inc/e107/compare/v2.3.11...v2.3.12
Videos 0.18.0 is now available for Geeklog.
This release goes far beyond adding YouTube videos to a website. It turns video into a real content layer that can help a Geeklog site become more active, more engaging and easier to explore.
For a site built around a specific topic, this opens an interesting opportunity: use relevant videos to reconnect visitors with your existing content, highlight useful creators, surface remarkable resources and encourage people to stay longer on the site.
The new version is available from the Geeklog Plugins repository:
https://github.com/Geeklog-Plugins/videos
Many websites already have years of articles, forum discussions, documentation or static pages.
The challenge is often no longer creating more content. It is finding new ways to make an existing audience want to explore the site again.
Video can help.
A gardening site can surface tutorials about composting or seed saving. A technical community can recommend demonstrations and conference talks. A climbing website can highlight training, safety and equipment videos. A local history site can build a catalogue around archives, documentaries and interviews.
Videos 0.18.0 is designed for exactly this kind of use.
Instead of treating YouTube as an external destination, the plugin builds a local video experience inside Geeklog.
Videos are discovered from YouTube, but visitors browse them through your site, your navigation, your editorial selections and your local recommendations.
This makes video another gateway into the community rather than a reason to leave it.
Videos can maintain a public catalogue related to the theme of the site.
The plugin combines automated discovery with editorial control. Administrators can seed the discovery system with relevant searches and then let Videos maintain a bounded reservoir of potential content.
The public catalogue can take into account several signals, including search relevance, publication recency, local ratings, qualified views, viewing activity, priority channels and editorial selections.
This allows the catalogue to evolve over time instead of becoming a static list of embedded videos.
At the same time, the administrator remains in control.
A particularly useful video can be added permanently. An important video can be pinned. Unwanted videos can be removed or excluded from future automatic selection.
The goal is not automation for its own sake. The goal is to combine discovery with human editorial judgement.
One of the most interesting possibilities in Videos 0.18.0 is the ability to identify and promote relevant channels.
The plugin can build local rankings of remarkable videos and derive a ranking of channels from that content. Eligible channels can also receive their own local page inside the Geeklog site.
This can create a useful editorial loop.
A visitor arrives for one video, discovers another video on the same subject, explores the channel behind it, then continues through related videos and rankings without leaving the site immediately.
For niche websites, that can be particularly valuable.
A strong thematic site often does not need millions of visitors. It needs people who recognize that the site regularly surfaces useful material in their area of interest.
Videos can help create that feeling of ongoing discovery.
A video page should not necessarily be the end of a visit.
Videos 0.18.0 includes local recommendation logic designed to offer sensible next videos while respecting moderation, viewing history, unavailable content and channel diversity.
Importantly, these recommendations can work from locally cached and ranked data. The plugin does not need to call the YouTube API every time a visitor loads a page.
That makes recommendations useful not only for engagement, but also for building stronger internal navigation.
A visitor may arrive from a search engine on one video page and then move through several related resources.
That is exactly the kind of browsing behaviour that can help breathe life back into a mature website.
Videos does not have to reproduce YouTube's popularity rankings.
Geeklog can build its own signals.
The plugin supports local ratings, qualified local views, playback completion data and viewing history. These signals can contribute to rankings and recommendations.
This matters because the most popular video on YouTube is not necessarily the most useful video for your community.
A small specialist tutorial may be much more valuable to the visitors of a technical or enthusiast site than a video with millions of generic views.
Over time, local engagement gives the site a way to develop its own editorial intelligence.
Automated discovery is useful for keeping a catalogue fresh, but some resources deserve a stable place.
Videos 0.18.0 introduces a stronger permanent catalogue model.
Administrators can manually add videos, keep them permanently available, pin especially important selections, remove them later, or prevent unwanted videos from being automatically reintroduced.
This makes it possible to gradually create a curated reference library.
For example, a site could maintain:
The automated catalogue can continue changing around this stable editorial foundation.
The new version also places strong emphasis on SEO.
Public video pages can include canonical URLs, page-specific descriptions, Open Graph metadata, Twitter Card metadata and VideoObject structured data. The plugin also creates local navigation between videos, rankings, channels and catalogue pages.
This matters because every useful local video page can potentially become another entry point into the site.
A thematic Geeklog site may already rank well for articles written years ago. Video pages provide another way to cover the same subject from a different angle.
They can also strengthen internal links around a topic.
Instead of maintaining isolated articles and isolated embedded videos, the site can gradually develop small thematic ecosystems.
Videos 0.18.0 also takes an important step toward broader Geeklog interoperability.
The plugin can expose videos, catalogues, rankings and channels through generic Geeklog content identities and canonical URL resolution. It can also generate lifecycle events when meaningful editorial changes occur.
This means other plugins do not need to understand how Videos stores its data internally.
Compatible plugins can react to a video being added, a ranking changing or a channel becoming important.
For example, IndexNow can be informed that an affected public URL has changed. Other content or notification plugins can eventually use the same signals.
This is an important direction for Geeklog: plugins becoming less isolated and more capable of cooperating around content.
A dynamic catalogue only works if administrators can understand and control it.
Videos 0.18.0 reorganizes administration into four clearer areas:
Overview provides the general state of the plugin.
Actions contains editorial operations, discovery, maintenance and indexing tools.
Statistics provides information about the discovery reservoir, rankings, cache, YouTube API activity and SEO.
Moderation centralizes decisions about videos and channels.
The release also improves YouTube API diagnostics, quota visibility and failure messages.
This is particularly useful because a video catalogue should be able to run for long periods without requiring administrators to guess what the API is doing.
Videos can also contribute content outside the main catalogue.
Its optional Geeklog block can display recommended videos, top-rated videos, most-watched videos, recently active videos, random videos or notable channels.
That creates an easy way to make video discovery visible throughout an existing Geeklog site.
An article reader might notice a related video in a block. A forum visitor might discover a highly rated tutorial. A returning user might see a new selection from a priority channel.
These small points of discovery can help reconnect different parts of a site that have accumulated over many years.
This may ultimately be the most interesting role for the Videos plugin.
Many long-running community websites do not lack content.
They lack movement.
Their archives are valuable, but visitors may not immediately see what is new, interesting or worth rediscovering.
A thematic video catalogue can provide that movement.
New videos appear. Older reference videos remain available. Visitors rate and watch them. Rankings evolve. Channels emerge. Recommendations connect one resource to another.
The surrounding Geeklog site suddenly has another living content stream.
And because the catalogue remains under local editorial control, the site keeps its own identity instead of becoming just another window into YouTube.
Videos 0.18.0 supports:
The source code and current release are available here:
https://github.com/Geeklog-Plugins/videos
If your Geeklog site is built around a clear subject and you are looking for a new way to engage visitors, Videos 0.18.0 is worth experimenting with.
The idea is simple: do not add video merely because video is popular.
Use video to strengthen the subject, personality and community that already make your site worth visiting.
Posted August 29, 2026 | 9:09 am