PHPackages                             itk-dev/tidy-feedback - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. itk-dev/tidy-feedback

ActiveDrupal-module

itk-dev/tidy-feedback
=====================

Tidy feedback

1.0.0-RC3(10mo ago)0717[7 issues](https://github.com/itk-dev/tidy-feedback/issues)MITJavaScript

Since Jul 10Pushed 2mo agoCompare

[ Source](https://github.com/itk-dev/tidy-feedback)[ Packagist](https://packagist.org/packages/itk-dev/tidy-feedback)[ RSS](/packages/itk-dev-tidy-feedback/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (3)Dependencies (10)Versions (9)Used By (0)

Tidy feedback
=============

[](#tidy-feedback)

This is a [Drupal module](https://www.drupal.org/docs/user_guide/en/understanding-modules.html) *and* a [Symfony bundle](https://symfony.com/doc/current/bundles.html) for collecting user feedback.

Installation
------------

[](#installation)

```
composer require itk-dev/tidy-feedback
```

Important

You may have to add `--with-all-dependencies` to the `composer require` command to make everything fall into place.

### Drupal

[](#drupal)

```
drush pm:install tidy_feedback
```

### Symfony

[](#symfony)

Create `config/routes/tidy_feedback.yaml` (or copy [`resources/config/routes/tidy_feedback.yaml`](resources/config/routes/tidy_feedback.yaml)):

```
#config/routes/tidy_feedback.yaml
tidy_feedback:
  resource: "@TidyFeedbackBundle/config/routes.php"
  prefix: /tidy-feedback
```

Note

You can use any path as `prefix`, but for consistency with the Drupal version of Tidy feedback you should use `/tidy-feedback`.

If [Symfony Flex](https://symfony.com/doc/current/setup/flex.html) hasn't already done so, you must enable the TidyFeedbackBundle bundle:

```
// config/bundles.php
return [
    …,
    ItkDev\TidyFeedbackBundle\TidyFeedbackBundle::class => ['all' => true],
];
```

Configuration
-------------

[](#configuration)

We need a [Doctrine database URL](https://www.doctrine-project.org/projects/doctrine-dbal/en/4.2/reference/configuration.html#connecting-using-a-url)in the environment variable `TIDY_FEEDBACK_DATABASE_URL`, e.g.

```
# .env
# See https://www.doctrine-project.org/projects/doctrine-dbal/en/4.2/reference/configuration.html#connecting-using-a-url for details.
TIDY_FEEDBACK_DATABASE_URL=pdo-sqlite://localhost//app/tidy-feedback.sqlite
```

As an alternative for Drupal you can set `TIDY_FEEDBACK_DATABASE_URL` in `settings.local.php`:

```
# web/sites/default/settings.local.php
putenv('TIDY_FEEDBACK_DATABASE_URL=pdo-sqlite://localhost//app/tidy-feedback.sqlite');
```

See [All configuration options](#all-configuration-options) for details and more options.

Create Tidy feedback database
-----------------------------

[](#create-tidy-feedback-database)

In a Drupal project, run

```
drush tidy-feedback:doctrine:schema-update
```

In Symfony projects, run

```
bin/console tidy-feedback:doctrine:schema-update
```

After installation and configuration, open `/tidy-feedback/test` on your site and enjoy!

How the widget works
--------------------

[](#how-the-widget-works)

The feedback widget is automatically injected into every HTML page via a kernel response listener (`onKernelResponse`). No template changes are needed — the widget HTML is appended before the closing `` tag.

The widget runs inside a [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM), which isolates its styles from the host page. The user-facing workflow is:

1. A "Feedback" button appears on the right side of the page
2. Clicking it opens a form with a draggable region overlay for highlighting a part of the page
3. The user fills in email and description fields
4. On submit, a screenshot is captured automatically using [snapdom](https://github.com/zumerlab/snapdom)
5. The screenshot, form data, and page context (URL, viewport size, etc.) are sent to the server

 ```
flowchart TD
    A[Page loads] --> B[Kernel listener injects widget before closing body tag]
    B --> C[Start button appears]
    C --> D{User clicks + or presses Shift+C}
    D --> E[Select mode: crosshair cursor highlights elements on hover]
    E --> F[User clicks an element]
    F --> G[Region overlay positioned with 20px padding]
    G --> H[Form shown, description field focused]
    H --> I[User submits via button or Ctrl/Cmd+Enter]
    I --> J[Screenshot captured via snapdom]
    J --> K[POST JSON to server]
    K --> L[Item entity created, 201 response]
    L --> M[Success message, form reset, count badge refreshed]
```

      Loading Query string parameters
-----------------------

[](#query-string-parameters)

You can use query string parameters to control the widget:

- `tidy-feedback-show=form` — automatically open the feedback form when the page loads
- `tidy-feedback[email]=...` — pre-fill (and lock) the email field
- `tidy-feedback[description]=...` — pre-fill the description field

Example:

```
/my-page?tidy-feedback-show=form&tidy-feedback[email]=user@example.com

```

Disabling the widget
--------------------

[](#disabling-the-widget)

The widget can be disabled entirely or on specific pages:

- **`TIDY_FEEDBACK_DISABLE=true`** — disables the widget on all pages
- **`TIDY_FEEDBACK_DISABLE_PATTERN`** — a regex matched against the request path. Pages matching the pattern will not show the widget. Default: `@^/tidy-feedback$@` (hides the widget on the feedback list page itself)

Examples:

```
# Disable on all admin pages
TIDY_FEEDBACK_DISABLE_PATTERN=@^/(admin|tidy-feedback)@

# Don't disable on any pages (empty pattern never matches)
TIDY_FEEDBACK_DISABLE_PATTERN=@^$@
```

Viewing feedback
----------------

[](#viewing-feedback)

All feedback items are listed at `/tidy-feedback`. Individual items can be viewed at `/tidy-feedback/{id}`, and screenshots at `/tidy-feedback/{id}/image`.

To get JSON output, append `?_format=json` to any of these URLs.

### Access control

[](#access-control)

By default, the feedback list and detail pages are publicly accessible. To restrict access with basic authentication, set `TIDY_FEEDBACK_USERS` to a JSON object mapping usernames to passwords:

```
TIDY_FEEDBACK_USERS={"admin": "s3cret", "reviewer": "p4ssw0rd"}
```

API endpoints
-------------

[](#api-endpoints)

All paths are relative to the Tidy feedback prefix (default `/tidy-feedback`).

MethodPathDescriptionAuth requiredResponse`GET``/`List all feedback itemsYes (if users configured)HTML or JSON`POST``/`Create a new feedback itemNoJSON (201)`GET``/check`Get feedback count for a URLNoJSON`GET``/{id}`Show a single feedback itemYes (if users configured)HTML or JSON`GET``/{id}/image`Get the screenshot imageYes (if users configured)Binary image`GET``/asset/{asset}`Serve a compiled assetNoBinary fileAppend `?_format=json` to `GET /` or `GET /{id}` to force JSON output.

### POST /

[](#post-)

Create a new feedback item by sending a JSON body:

FieldTypeDescription`created_by``string` (optional)Email address of the submitter`email``string` (optional)Email address (stored in data)`description``string`Feedback description`image``string`Screenshot as a data URI`context``object`Page context (URL, viewport, selected element, etc.)Example 201 response:

```
{
  "data": {
    "id": 42,
    "createdAt": "2025-07-10T12:00:00+00:00",
    "createdBy": "user@example.com",
    "data": { "..." }
  }
}
```

### GET /check

[](#get-check)

Returns the feedback count and a summary of items for a given URL.

ParameterTypeDescription`url``string` (query)The page URL to checkExample response:

```
{
  "data": {
    "count": 3,
    "items": [
      {
        "description": "Button is misaligned",
        "url": "/tidy-feedback/42",
        "selectedElement": "div.main > button.submit"
      }
    ]
  }
}
```

### Data schema

[](#data-schema)

The `data` JSON column on each Item entity stores the full feedback payload:

```
{
  "email": "user@example.com",
  "description": "The heading overlaps the sidebar",
  "image": "data:image/svg+xml,...",
  "context": {
    "url": "https://example.com/page",
    "viewport": { "width": 1920, "height": 1080 },
    "selectedElement": "div.content > h1",
    "region": { "top": 100, "left": 50, "width": 400, "height": 200 }
  },
  "created_by": "user@example.com"
}
```

All configuration options
-------------------------

[](#all-configuration-options)

Only `TIDY_FEEDBACK_DATABASE_URL` is required.

NameDefault valueExample`TIDY_FEEDBACK_CACHE_DIR``/tmp/tidy-feedback``TIDY_FEEDBACK_DATABASE_URL``pdo-sqlite://localhost//app/tidy-feedback.sqlite``TIDY_FEEDBACK_DEBUG``false``TIDY_FEEDBACK_DEFAULT_LOCALE``da``TIDY_FEEDBACK_DEV_MODE``false``TIDY_FEEDBACK_DISABLE``false``TIDY_FEEDBACK_DISABLE_PATTERN``@^/tidy-feedback$@``@^$@` (don't disable on all pages)`TIDY_FEEDBACK_USERS``{}``{"admin": "password"}`Boolean options must be set to `true`, e.g. `TIDY_FEEDBACK_DISABLE=true`, to be true. Otherwise they're false.

The pattern in `TIDY_FEEDBACK_DISABLE_PATTERN` will be matched against the *path* of the request URL only, i.e. the query string will *not* be included.

Development
-----------

[](#development)

```
task
```

### Architecture

[](#architecture)

Tidy feedback is designed to work as both a Drupal module and a Symfony bundle from a single codebase. The key design patterns are:

- **Shared trait**: The controller logic lives in `TidyFeedbackControllerTrait` (`src/Controller/`). Both the Drupal and Symfony controllers consume this trait, avoiding code duplication.
- **Separate entity manager**: The module creates its own Doctrine `EntityManager` configured via `TIDY_FEEDBACK_DATABASE_URL`, keeping feedback data isolated from the host application's database.
- **Kernel listener**: `TidyFeedbackHelper` implements `EventSubscriberInterface` and listens on `kernel.response` to inject the widget HTML before the closing `` tag.
- **Shadow DOM isolation**: The widget renders inside a Shadow DOM so its styles never leak into (or are affected by) the host page.
- **Watered-down Twig**: The helper instantiates a minimal Twig environment with only a `trans` filter and a `path`function, independent of the host framework's template engine.

### composer.json

[](#composerjson)

In order to make this behave as both a Drupal module and a Synfony bundle, we use some tricks in [`composer.json`](./composer.json):

```
{
    // We use "type": "drupal-module" to make Drupal move the module into the
    // proper location (web/modules/contrib).
    // Symfony recommend using "type": "drupal-module" (cf. https://symfony.com/doc/current/bundles/best_practices.html#installation),
    // but Symfony and Flex don't seem to really care about this.
    "type": "drupal-module",
    "require": {
        // In order to not pull much of Symfony into a Drupal project or (worse)
        // much of Drupal into a Symfony project, we require only the bare
        // minimum to make this module/bundle work.
        "doctrine/dbal": "^3 || ^4",
        "doctrine/orm": "^2.8 || ^3",
        // These are required by Drupal 9 later
        "symfony/cache": "^6 || ^7",
        "symfony/http-foundation": "^6 || ^7",
        "symfony/http-kernel": "^6 || ^7",
        "symfony/mime": "^6 || ^7",
        "symfony/routing": "^6 || ^7",
        "twig/twig": "^3"
    },
    "autoload": {
        "psr-4": {
            // The Symfony bundle namespace.
            "ItkDev\\TidyFeedbackBundle\\": "symfony/src/",
            // The shared code namespace.
            "ItkDev\\TidyFeedback\\": "src/"
        }
    },
    // …
}
```

### Twig

[](#twig)

We use a watered-down instance of Twig with only a `trans` filter and a `path` function.

### Development app

[](#development-app)

For development, you can start a Symfony app with Tidy feedback installed:

```
task app:start
```

Run `task app:stop` to stop the app.

See [Query string parameters](#query-string-parameters) for how to auto-open the form and pre-fill fields during testing.

For easy testing, you can use [Bookmarklet Creator](https://mrcoles.com/bookmarklet/) to convert the code

```
const url = new URL(document.location);
url.searchParams.set('tidy-feedback-show', 'form');
url.searchParams.set('tidy-feedback[email]', 'test@example.com');
url.searchParams.set('tidy-feedback[description]', 'This is cool!');
document.location = url
```

into a bookmarklet.

###  Health Score

32

—

LowBetter than 72% of packages

Maintenance73

Regular maintenance activity

Popularity13

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity28

Early-stage or recently created project

 Bus Factor1

Top contributor holds 61.1% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

3

Last Release

312d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/33924554?v=4)[ITK Development](/maintainers/itk-dev)[@itk-dev](https://github.com/itk-dev)

---

Top Contributors

[![yepzdk](https://avatars.githubusercontent.com/u/332915?v=4)](https://github.com/yepzdk "yepzdk (80 commits)")[![rimi-itk](https://avatars.githubusercontent.com/u/11267554?v=4)](https://github.com/rimi-itk "rimi-itk (51 commits)")

###  Code Quality

Static AnalysisRector

Code StylePHP CS Fixer

### Embed Badge

![Health badge](/badges/itk-dev-tidy-feedback/health.svg)

```
[![Health](https://phpackages.com/badges/itk-dev-tidy-feedback/health.svg)](https://phpackages.com/packages/itk-dev-tidy-feedback)
```

###  Alternatives

[easycorp/easyadmin-bundle

Admin generator for Symfony applications

4.3k16.7M310](/packages/easycorp-easyadmin-bundle)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.4k5.6M651](/packages/sylius-sylius)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.3M152](/packages/sulu-sulu)[shopware/platform

The Shopware e-commerce core

3.3k1.5M3](/packages/shopware-platform)[prestashop/prestashop

PrestaShop is an Open Source e-commerce platform, committed to providing the best shopping cart experience for both merchants and customers.

9.0k15.4k](/packages/prestashop-prestashop)[ec-cube/ec-cube

EC-CUBE EC open platform.

78527.0k1](/packages/ec-cube-ec-cube)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
