e107 v2.3.9
Caution
v2.3.9 is a maintenance release for sites on v2.3.8 or earlier.
Upgrade from any 2.x at or below v2.3.8. If your site tracks the master branch, you are already past v2.3.9, so installing it would be a downgrade. v2.4.x is planned to be the next forward step.
Note
There are no security fixes in this release. If your site is still on v2.3.7 or earlier, the release you need urgently is v2.3.8, which closed an unauthenticated SQL injection and two further advisories. v2.3.9 carries all of that forward and adds bug fixes on top.
Highlights
- Regression fixed: You can install e107 into any database your server will accept. v2.3.8 turned away database names containing a dot or a space, and said something unrelated when it did, which left some people unable to install at all. (#5819)
- Regression fixed: Your dashboard will tell you about updates again. Debug code left switched on had silenced the update notice for everyone, whatever version they were running. (#5839)
- No more broken placeholder images. e107 borrowed them from an outside service that has since shut down. It now draws its own, on your own server. (#5581)
- Members-only pages no longer send visitors round in circles. On some sites a guest was bounced between the login page and the members-only page until the browser gave up. (#5335)
- Database backups finish on modern PHP. A single empty file inside an archive used to stop the whole backup part-way through. (#5208)
- Media Manager search finds what you are looking for. Searching for "orchidea" now finds "Orchidea", in every alphabet, not just in English. (#5791)
- Adding an FAQ works again. The form was saving its answers into the wrong columns, or refusing to save at all. (#5839)
For Administrators
Fixed
- Installer: valid database names were rejected. v2.3.8 allow-listed the database name to
A-Za-z0-9_-. MySQL and MariaDB permit periods, spaces, dollar signs and Unicode in database names, verified on MariaDB 10.11 and MySQL 8.0, so an existing database named, for example,e107_2.3.8could not be used at all. The restriction protected nothing, because whoever fills in that form already holds the database credentials, and the name only ever reaches SQL inside backticks. It is now escaped there instead. The table prefix keeps itsA-Za-z0-9_rule, because the prefix really is dropped into queries unquoted. (#5819) - Installer: the wrong error message on two failure paths. A rejected database name and a database server below the minimum version both showed "A database name or prefix beginning with some digits followed by 'e' or 'E' is not acceptable", which described neither problem. Each failure now states the rule it actually broke, and a too-old MySQL server is named as such. (#5819)
- The core-update notice never appeared on the dashboard. Two leftover debug lines pinned the cached result to a fixed value and returned early, so the notice was dead for everyone. It now checks for an update when the cache is cold, stores the answer for an hour, and renders the notice. The separate scheduled update e-mail is unchanged, and the dashboard deliberately does not send it. (#5839)
- Broken placeholder images across a fresh install.
via.placeholder.comhas gone; the domain no longer completes a TLS handshake.thumb.phpused to redirect missing-image requests there, the hero plugin's three sample slides embedded its URLs directly, and the media handler had a special case for the domain name.thumb.phpnow serves a small SVG placeholder it generates itself, with cache headers, in about two microseconds; the hero slides ship as local SVG files; and image detection now ignores the query string, so existing database content still pointing at the dead service is classified correctly instead of by domain name. (#5581) - "Too many redirects" on members-only pages. The members-only redirect always sent visitors to
login.php, ignoring acustomlogin.phpif the site had one, and the login page could only render in its bare iframe form. Worse, with registration disabled and no social login, the stock login page turns a guest away and bounces them back to the page they came from, looping until the browser aborts. e107 now redirects to whichever login page the site is configured to use, and falls back to the members-only notice in exactly that dead-end case, so the loop cannot form. (#5335) - Backup failed on PHP 8.2+ when an archive held a zero-byte file. Reading zero bytes from a file is an error on modern PHP, and the backup routine did it unconditionally while compressing in memory, ending the backup with a fatal error. Zero-length files are now written as empty entries. (#5208)
- Adding an FAQ inserted the wrong data, or nothing at all. The insert supplied eight values positionally for a ten-column table. On a server in strict mode that is MySQL error 1136 and the FAQ is not saved; otherwise the values shift left and the display order ends up stored as the author's IP address. The insert now names every column, matching the rest of the plugin. (#5839)
- Duplicated entries in the administration menu. When two entries in a sorted category shared the same display name, the menu builder re-added every matching entry once per match, so two entries sharing a name produced four rows, three produced nine, and so on. Each entry now renders exactly once. (#5786)
- Media Manager search missed results that clearly matched. The image, icon, audio, video and general media pickers filtered their results with a case-sensitive comparison, so searching for "orchidea" did not find "Orchidea" even though the database query had already matched it. Searching is now case-insensitive, and it folds case correctly for accented Latin, Cyrillic, Greek and Turkish titles too, not only for A to Z. (#5791)
- Wrong text on the upload page. Two core language files each defined the same constant with different wording, so the upload page showed whichever one happened to load first. The stray duplicate is gone and the upload page has its own string. (#5730)
Changed
- The gallery's prettyPhoto share buttons are gone. The default template put a
http://twitter.com/sharelink and a Facebook "like" frame into every gallery overlay. The insecure addresses were blocked as mixed content on any HTTPS site, and the Facebook frame never received the right address anyway, so what remained was broken markup on every image. The default is now empty. A theme that defines its own share template still overrides it and is unaffected. (#5651) - Security reporting instructions live in one place. The
SECURITY.mdfile at the top of the repository still pointed reporters at an e-mail address that does not reliably deliver. GitHub serves the policy from.github/SECURITY.md, which asks for private reports through GitHub Security Advisories, so the stale copy has been removed. To report a vulnerability in e107, use Security Advisories. (#5822)
For Developers
Added
- The reworked
e107-testsharness, backported frommaster.release/v2.3.xcan now run its own PHP matrix locally, including the 5.6 and 7.0 legs whose end-of-life Debian bases previously could not build a container at all. State moves out of per-worktreedocker/.envs/files into Docker labels and per-environment named vendor volumes, solistis global,gcreaps orphans, and the repository'scomposer.lockis never rewritten (composer install, with a loud per-environmentcomposer updatefallback when the interpreter cannot satisfy the lock). Adapted for this branch: no Selenium service,DEFAULT_PHP=8.2, acceptance browseshttp://localhost/from inside the web container, and Codeception stays at 4.2.2. Validated on 5.6, 7.0, 7.4, 8.1 and 8.2. (#5837, #5836) - Offline coverage for the library manager.
testCdnUrlContainsVersionasserts each CDN version appears in its resolved URL, andtestCdnFileReportsHardcodedVersionexercises every entry's ownversion_argumentspattern against real bytes vendored undertests/_data/library_headers/.testRefreshCdnFixturesregenerates those fixtures from the live CDNs and is the one Internet-touching path, skipped unlessE107_REFRESH_LIBRARY_FIXTURESis set. (#4560)
Changed
- Installer name handling: escape, do not restrict.
check_name()'s non-strict mode (the database name) no longer imposes a character-set rule; strict mode (the table prefix) keepsA-Za-z0-9_, because the prefix is interpolated unquoted into every query. A newquoteDbName()doubles backticks and wraps the name, and the threeCREATE/ALTER/DROP DATABASEsites use it.e_db_pdo::database()ande_db_mysql::database()double backticks in their quoted interpolations;mysqli_select_db()needs nothing. The legacy quote and semicolon guard stays, because a semicolon cannot be represented in the PDO DSN.SqlInjectionFixesTestpins the new contract, including a backtick-doubling breakout guard. New stringsLANINS_148,LANINS_149andLANINS_150replace the misusedLANINS_105. (#5819) - Themes can opt out of the bare login page.
login.phpforcede_IFRAMEunconditionally. It now defers to thelogincore template, so setting$LOGIN_TEMPLATE['page']['noiframe'] = truein a theme override renders the login page with the full site chrome. The default is unchanged. The members-only redirect target also respectse_LOGINnow rather than hardcodinglogin.php. (#5335) LAN_418inlan_upload.phpis nowLAN_UL_041.LAN_407andLAN_418were each defined with different values in two core language packs, so load order decided which text won. The unusedLAN_407blurb has been dropped fromlan_signup.php(the signup e-mail composes fromLAN_EMAIL_04/05/06), andlan_upload.php's "Absolute maximum file size: " is renamed toLAN_UL_041, withupload.phprepointed.lan_search.phpkeeps sole ownership ofLAN_418. Translators and language-pack maintainers: addLAN_UL_041to yourlan_upload.php; the oldLAN_418definition there is no longer read. (#5730)sc_cb_avatar()honours$parm. The chatbox avatar shortcode hardcodedcrop => 'C'and rebuilt its own options array, discarding anycrop,worha template passed in, which pinned chatbox avatars to the legacythumb.phpquery-string URL while sibling avatar shortcodes forwarded$parmand reached SEF addresses.$parmis now merged over the defaults.'C'remains the default crop, so existing rendering is unchanged, but a template can passcrop=falseto escape to a SEF-eligible URL wherethumb.phpis broken on the host. (#5657)e_library_managertakes an injectable file handler. It defaults toe107::getFile(), anddetectPath()andgetVersion()read through it, so a test can stub every remote read._before()injects a handler that throws on any fetch, so an accidental network read now fails loudly rather than quietly downloading.testDetectionVersionConsistencycompares each CDN pin against its bundled local sibling offline, which catches drift between the shipped copy and the hard-coded version, andtestCoreLibraryPresencehas dropped its remote URL probe. Thecdn.jquery.oncepin is corrected: it claimed 2.1.2 while the bundled copy ships 2.2.3, and the legacy jsDelivr path it used does not serve 2.2.3 at all, so it is repointed at the npm path. (#4560)e_parse::toAvatar()fetches through thee107::getFile()singleton. Its base64 data-URI branch fetched a real avatar over the network at test time, so any DNS or network hiccup silently fell through to the plain remote<img>and failed the assertion, which looked order-dependent under the shuffled suite. The fetch is now injectable and the test stubs it. (#5841)- Releases are built and published from GitHub Actions.
build-release.ymlis replaced byrelease.yml. The build step is unchanged in substance, but the workflow now finishes the job instead of stopping at a workflow artifact: it uploads the archives andcore_image.pharto the GitHub Release, then uploads the nine archives to SourceForge over SFTP with a pinned host key and sets the per-platform default downloads. A guard asserts the full tag history was fetched before the core integrity image is built, and a build whose tag does not match the version it produced refuses to upload. (#5831)
Fixed
e_navigation::admin()cross-join. The sorted branch built a list of every entry's display text and then, for each text, re-appended every entry matching it, emitting K entries K times each for a same-named group. The text list is now deduplicated before the cross-join. (#5786)pclzipreading zero bytes.fread($h, 0)throws aValueErroron PHP 8.2+, so an archive containing a zero-byte file fatally errored during in-memory compression. The read is guarded, and the file is still written as a zero-byte entry. (#5208)- Multibyte-safe media search.
stripos()folds case for ASCII only, so a title differing in case on a non-ASCII letter could be dropped by the PHP post-filter even though the case-insensitive database collation had matched it. The five title filters now use the parser'sustrpos()andustrtolower(), which usembstringwhen it is present and fall back to the byte functions when it is not, so there is no new hard dependency. (#5791)
Acknowledgments
Thanks to
- @Jimmi08 for reporting the members-only redirect loop (#5335), the duplicated administration menu entries (#5786) and the PHP 8.2 backup failure (#5208); to
- @kreossino for the case-sensitive Media Manager search (discussion #5791) and the broken gallery share template (discussion #5651); to
- @Kanonimpresor for the installation failure that surfaced the database-name regression (discussion #5819); to
- @tgtje for the language-constant collision report (#5730); and to
- @sindizzy, @tgtje and @Taffman, whose thread in #5581 traced the broken images on fresh installs to the shut-down placeholder service.
Full changelog: v2.3.8...v2.3.9