vkemeter / opinion
Collects feedback from editors including a screenshot and environment details and delivers it by email
Requires
- php: ^8.4
- typo3/cms-adminpanel: ^14.3
- typo3/cms-backend: ^14.3
- typo3/cms-core: ^14.3
- typo3/cms-frontend: ^14.3
Requires (Dev)
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-21 17:52:49 UTC
README
TYPO3 extension opinion
The full documentation is in Documentation/ (reStructuredText, rendered with the TYPO3 documentation tools).
Collects feedback from editors: a description, a category and severity, an optional screenshot of the current browser tab and details about browser and TYPO3. Every submission is stored as a record and delivered by email.
The dialog opens from two places:
- Frontend: entry "Feedback" in the admin panel; a click opens the dialog. The page, site, language and workspace are attached.
- Backend: speech-bubble button in the toolbar. The current module, page and module URL are attached.
Both are only shown to users who may send feedback (see below).
Before sending, the screenshot can be marked up in a full-screen editor: frame an area with a rectangle, point at something with an arrow, or black out personal data. The markings are burnt into the image; only the marked-up image is sent.
Admins find all feedback in the module Admin Tools → Feedback: a list, newest first, and a detail view with message, screenshot and all collected details.
Requirements
- TYPO3 v14.3, PHP 8.4 or 8.5
- HTTPS for screenshots (
getDisplayMedia()needs a secure context)
Installation
composer require vkemeter/opinion vendor/bin/typo3 extension:setup
Without Composer, install it from the TYPO3 Extension Repository. Changes are listed in the changelog.
extension:setup creates the table tx_opinion_feedback and the file storage
"Opinion screenshots" (see below).
Configuration
Admin Tools → Settings → Extension Configuration → opinion:
| Setting | Default | Meaning |
|---|---|---|
recipients |
– | Comma-separated email addresses. Invalid ones are ignored. |
subjectPrefix |
[Opinion] |
Prefix of the email subject |
retentionDays |
180 |
Age after which opinion:cleanup deletes feedback |
redeliverMaxAttempts |
5 |
Attempts after which opinion:redeliver gives up, 0 = no limit |
rateLimit |
10 |
Feedback per backend user and hour, 0 = no limit |
enabled |
1 |
0 hides the triggers and rejects submissions |
Without valid recipients the feedback is still stored, its delivery status is "failed".
Collected details
The tab "Collected details" of the extension configuration has a switch for every detail, grouped into browser, server and page/module (e.g. IP address, screen size, time zone, images on the page). All are on by default. A switched-off detail is not collected by the browser and is removed on the server, even if a request sends it. The sender (name, username, email) is always kept.
deniedUrlParameters in the same tab lists query parameters that are removed
from the page and module URL (default
ADMCMD_*,logintype,user,pass,redirect_url,tx_felogin_login; a trailing *
matches a prefix). The backend request token is always removed.
The sender address is $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'];
the sending backend user becomes reply-to.
Who may send feedback
Admins always. Other backend users need this user or group TSconfig:
options.opinion.enable = 1
Sending feedback also needs a valid email address in the user account, so the team can answer. Users without one see the button, but get a note with a link to their user settings instead of the form; the endpoint rejects their submissions as well.
For the frontend button the admin panel itself must be shown:
- the site set
vkemeter/opinion("Opinion: editor feedback") added to the site, which setsconfig.admPanel = 1(site settingopinion.adminPanel.enable, default on); without it, addconfig.admPanel = 1to the TypoScript yourself - user TSconfig with at least one enabled admin panel module, e.g.
admPanel.enable.opinion = 1(admins getadmPanel.enable.all = 1from the core). Site sets cannot provide user TSconfig, so this stays in the user or group records.
The "Feedback" tab only appears for users who may send feedback.
Deliver again
Failed or pending feedback (e.g. the mail server was down) is delivered again by
vendor/bin/typo3 opinion:redeliver # older than 10 minutes
vendor/bin/typo3 opinion:redeliver --min-age=0
vendor/bin/typo3 opinion:redeliver --dry-run
It skips records with redeliverMaxAttempts attempts and can run as a
Scheduler task. In the admin module, the detail view of an undelivered
feedback has a button "Deliver again" for a single record.
Cleanup
vendor/bin/typo3 opinion:cleanup # older than retentionDays
vendor/bin/typo3 opinion:cleanup --days=30
vendor/bin/typo3 opinion:cleanup --dry-run
To run it regularly, add a Scheduler task "Execute console commands" with the
command opinion:cleanup.
Data and privacy
- Records live on the root level (
pid = 0), read-only, and are shown in the admin module "Feedback" only, not in the List module. - Screenshots are stored in a local, non-public FAL storage below
var/opinion/, outside the web root. TYPO3 serves them through thedumpFileURL only; a listener answers403unless the request carries an admin session. - Blacked-out areas are burnt into the screenshot in the browser; the original capture never leaves the page.
- The browser sends a fixed set of environment details (URL, title, user agent,
language, time zone, screen and viewport size, pixel ratio, color scheme,
reduced motion, online state). The server drops every other key. Cookies,
localStorageandsessionStorageare never read. - Feedback from the admin panel also contains the details of its tab "Info → General": page type, cache state and reasons, uncached elements, parse time, document size, simulated frontend user and the images on the page. Images and document size are only known when the page was rendered without cache, like in the admin panel itself. Cookies, server variables and request parameters of that tab are not taken.
- The IP address is stored anonymised.
opinion:cleanupremoves records, references and screenshot files.
Deployment
var/opinion/ must survive deployments (shared directory). The storage is
configured relative to the public path (../var/opinion/), so it stays valid
when the release directory changes. The path is inside the project root, no
BE.lockRootPath entry is needed.
Integration for further entry points
Build the dialog configuration on the server and pass it to the JavaScript module:
$configuration = $dialogConfigurationBuilder->build($backendUser, Source::Frontend, ['pageUid' => 12]);
import { openOpinionDialog } from '@vkemeter/opinion/opinion-dialog.js'; openOpinionDialog(configuration);
Load EXT:opinion/Resources/Public/Css/opinion-dialog.css as well. Check
AccessChecker::hasAccess() before rendering a trigger.
Extension points:
ContextProviderInterfacevalidates the context an entry point sends along. Context of a source without a provider is dropped.DeliveryChannelInterfaceadds a delivery channel, e.g. a ticket system. All implementations are collected automatically.ConfigurationProviderInterfacereplaces the source of the configuration.
Endpoint
POST to the backend AJAX route opinion_submit, multipart/form-data:
message, category (bug|content|idea|other), severity
(low|normal|high), source (frontend|backend), clientInfo (JSON),
context (JSON), screenshot (PNG/JPEG, at most 10 MB).
| Status | Meaning |
|---|---|
200 |
Stored; delivered tells whether all channels succeeded |
401 |
No backend session (answered by the core) |
403 |
No access or extension disabled |
422 |
Invalid input, errors holds a message per field |
503 |
Screenshot storage unavailable |
Development
make help lists all tasks: make install, make test, make check,
make fix, make docs, make ci (everything the CI runs) and more. The
variables PHP, DB and CONTAINER select PHP version, database and
container tool, e.g. make test-functional DB=mariadb. The targets wrap the
commands below.
The PHP suites run in ghcr.io/typo3/core-testing-php84 containers (docker or
podman), no local PHP or Composer needed. Dependencies are installed into
.Build/.
Build/Scripts/runTests.sh -s composerUpdate # install dependencies Build/Scripts/runTests.sh -s unit Build/Scripts/runTests.sh -s functional # SQLite Build/Scripts/runTests.sh -s functional -d mariadb # also mysql, postgres Build/Scripts/runTests.sh -s phpstan Build/Scripts/runTests.sh -s cgl -n # php-cs-fixer dry run Build/Scripts/runTests.sh -s lint Build/Scripts/runTests.sh -h # all options
The browser test runs the dialog against a mocked endpoint in headless
Chromium, including the screenshot flow. Playwright comes from Build/:
npm --prefix Build ci
npx --prefix Build playwright install chromium
node Tests/Browser/dialog.test.mjs
npm --prefix Build run lint:js # ESLint, Build/eslint.config.mjs
GitHub Actions (.github/workflows/ci.yml) runs the same commands on every push
to main and on pull requests: lint, CGL and PHPStan; ESLint; unit and functional tests
on PHP 8.4 with SQLite, MariaDB, MySQL and PostgreSQL and on PHP 8.5 with SQLite;
the browser test; a test rendering of the documentation.
Documentation
docker run --rm --pull always -v $(pwd):/project -it \
ghcr.io/typo3-documentation/render-guides:latest --config=Documentation
Open Documentation-GENERATED-temp/Index.html.