Skip to content

3.5.1

Latest

Choose a tag to compare

@joeworkman joeworkman released this 03 Sep 06:02
3.5.1
b985378

Upgrade notes

  • Regenerate any UPC-E or Codabar barcodes. Both were being encoded incorrectly, and the fix below means existing ones are wrong wherever they have been printed or saved. UPC-E now takes the 6-digit payload; the 7- and 8-digit forms are still accepted and normalised, so no template needs changing. Codabar data must no longer contain A–D — the encoder supplies the start/stop characters itself
  • tcms push --collections has changed meaning. It used to move objects from five allowlisted collections; it now moves collection settings, which is what the name should always have meant. Scripts calling push --collections=builder-pages expecting objects will silently push settings instead — use --pages for those objects
  • tcms push --collection-meta is removed. Its job is now --collections. This one fails loudly as an unknown option
  • cms.barcode.upce() output: the rendered data-value attribute now carries the 6-digit payload actually encoded rather than the input string, when a longer form was passed
  • Everyone is signed out once when you upgrade. Session files move from the server's shared session directory into tcms-data/.system/sessions (see below), and sessions in the old location are not carried over. Anyone who used "keep me signed in" is restored automatically rather than bounced to the login form
  • Behind a CDN that is not Cloudflare, set trustProxyHeaders. Total CMS now believes CF-Connecting-IP and X-Forwarded-For only when the request came from a private address or a Cloudflare edge. Cloudflare and a reverse proxy on the same server both keep working untouched; any other CDN in front of the site needs $settings['trustProxyHeaders'] = 'always' — and its origin firewalled first, or that setting is an open door. Settings → Server Info reports what your install is doing
  • CSV exports may gain columns. A collection whose objects still hold properties its schema no longer declares now exports those properties instead of dropping them. Existing columns keep their positions — the new ones are appended at the end

Security

  • Client IP is no longer taken on trust. CF-Connecting-IP and X-Forwarded-For are set by whatever sits in front of the server — but a visitor can send them too, and seven places read them unconditionally. On a site reachable directly from the internet that let a caller hand itself a new identity on every request, walking straight past the per-IP rate limit on POST /api/action/mailer, the OAuth token endpoint, the MCP endpoint and the automation webhook throttle. One resolver now makes that decision for all of them, and trustProxyHeaders decides when the headers are believed. Cloudflare's published ranges ship with each release and are refreshed at build time, so Cloudflare sites need no configuration
  • Session files are no longer stored in the server's shared session directory. PHP's default location is one directory for every site on the machine, so a neighbouring site's session cleanup could read or delete Total CMS session files. They now live in tcms-data/.system/sessions, which the shipped Apache and nginx rules already deny, with 0700 permissions

Added

  • Seed content to production: tcms push --objects=blog sends a collection's objects to your production server, skipping anything already there. Any collection can be seeded, not just the five that sync could already move — so a new feature's schema, collection and starter content can all travel together. --objects=blog:welcome,about narrows to specific objects; --overwrite (with --force) lets the local copy win instead
  • Seed Objects in the Sync Manager: pick the collections whose objects should be seeded on push. All-or-nothing per collection, and the Sync Manager can only add — overwriting stays on the CLI where it has to be asked for explicitly
  • Feature-named sync flags: --pages, --dataviews, --mailer, --mcp-prompts and --automations, each accepting an optional id list (--pages=home,about). They replace the old --collections, which named a gated set of collection ids and required knowing that Site Builder pages live in a collection called builder-pages
  • MCP: read-only Site Builder template toolslist_templates and get_template, so an agent editing builder-pages can see which page.data.* keys a template actually consumes instead of guessing. Admin-scoped, and deliberately read-only: writing Twig reaches the whole cms.* surface, which is a larger authority grant than writing content

Changed

  • A mixed push now sends two requests. Seeded objects go to the skip-existing import endpoint while everything else keeps its upsert semantics, so push --schemas=faq --objects=faq deploys the schema and seeds its rows without either behaving like the other. Both routes already existed in 3.5.0, so a seed push works against a production server that has not upgraded yet
  • tecnickcom/tc-lib-barcode moves to ^2.14, which brings real input validation to the barcode encoders. Values that used to render an incorrect barcode now raise an error instead. In a Twig template that error renders as an HTML comment rather than breaking the page — so a bad barcode value degrades to a missing barcode, with the reason visible in View Source
  • Binaries still never travel. Image, file, gallery and depot fields are omitted from every sync payload and the receiving side keeps what it already had, so a seeded post cannot blank a production image. The image, gallery, file and depot collections cannot be seeded at all — the binary is the object there, and seeding one would land a record pointing at a file the target does not have

Fixed

  • UPC-E barcodes encoded the wrong product. upce() accepted 7–8 digits, but UPC-E's payload is 6, and the encoder never decompressed the longer forms — it zero-padded the input and appended a check digit. 04252614 encoded UPC-A 0000042526148 instead of the correct 0042100005264, scanning cleanly as a product that does not exist. All input forms now reduce to the same payload and the same barcode

  • Codabar barcodes did not scan. A, B, C and D are reserved as Codabar's start/stop characters and the encoder adds them itself, so passing A1234B encoded literally as AA1234BA. Such values are now rejected with an explanation rather than silently producing an unreadable barcode

  • "Keep me signed in" survives an update. Persistent-login tokens were stored inside the application directory, which a zip update replaces wholesale — so every update signed out everyone who had ticked the box. They now live in tcms-data/.system/persistent_tokens alongside logs, and existing tokens are carried across on the first run after upgrading

  • Sessions no longer expire early on shared hosting. PHP's shared session directory is cleaned using whichever site triggers the cleanup, commonly on a 24-minute lifetime, so a neighbouring site's cleanup deleted Total CMS sessions regardless of the 24 hours Total CMS configures. With sessions in their own directory that can no longer happen. If the directory cannot be written, Total CMS falls back to PHP's default rather than leaving nobody able to log in

  • A single unreadable object no longer aborts a whole export. One object whose stored data did not match its schema — usually after the schema was edited — threw out of exportAllObjects(), so the export produced nothing instead of the remaining objects. It now skips and logs, matching the JSON and CSV exports, and the export job records which object ids it skipped

  • CSV export no longer silently drops data. Columns are built from the schema, so properties still stored on an object but removed from the schema had no column and vanished — while a JSON export of the same collection kept them. A CSV taken as a backup after a schema edit was quietly incomplete

  • Booleans inside cards survive a CSV round trip. A card's false exported as an empty cell and true as 1, while a top-level boolean column in the same file said true or false. Card booleans now use the same spelling and are restored as booleans on import, using the card's own sub-schema so a text field containing the word "true" stays text

  • A partial auth configuration no longer breaks every request. Twenty places read auth['enable'] directly. On installs that turn PHP warnings into exceptions, a settings array without that key made the authentication middleware throw on every request — a 500 rather than a login page. Missing now means enabled, as the shipped default always intended

  • A throwing extension route registrar can no longer take down the site. An extension whose route callback threw took the exception straight out of the boot sequence, producing an empty HTTP 500 on every route — including /admin, so the operator could not reach the page to disable it. Route registrars now run through the existing extension guard: a thrower loses only its own routes, is logged with attribution, and surfaces as an error in the admin

  • Large collections could render an empty listing. Collections over 500 objects build their index by streaming it straight to disk, and the build returned nothing because the content is deliberately never held in memory. Anything that triggered that build and used its result — a fresh install, a cleared index, a repair, the rebuild endpoint — got zero objects back, so the page rendered empty and the rebuild reported an empty index. The next request was already correct, because the file itself was written, but nothing reported the empty one

  • "This month" excluded the first of the month. The window opened at the 1st at the current clock time rather than midnight, so a record dated the 1st — stored as midnight, therefore earlier — fell outside it. A post dated the 1st was missing from every "this month" filter for the whole month, not just on the 1st. "This week" was unaffected