protonsystems / content-reviewer
Reusable Drupal module for plugin-based content issue scanning, admin review reporting, and deterministic fix commands.
Package info
gitlab.com/Proton.Systems/drupal/content-reviewer
Type:drupal-module
pkg:composer/protonsystems/content-reviewer
Requires
- php: ^8.3
- drupal/core: ^10 || ^11
- drupal/paragraphs: ^1.20
- protonsystems/qa: ^2.1
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Reusable Drupal module for plugin-based content issue scanning, admin review reporting, and deterministic fix commands.
Purpose
The module is intended to be reused across Drupal 10 and Drupal 11 projects by installing it as a Composer package and enabling it in the target site.
Requirements
- PHP 8.3+
- Drupal 10 or 11
- ProtonSystems
qapackage (bundles theqa_audit_coreshared submodule) - Paragraphs
content-reviewer uses the shared scanner services from the bundled
qa_audit_core submodule inside protonsystems/qa and layers UI, reports,
and deterministic fix commands on top.
Features
- Provides an admin review UI at
/admin/reports/content-reviewer - Lists enabled issue plugins through
drush content-reviewer:list - Scans issues and exports structured JSON through
drush content-reviewer:scan - Applies deterministic fixes for auto-fixable issue plugins through
drush content-reviewer:fix - Checks orphan paragraph references across every stored revision and translation of the paragraph's recorded parent, plus all SQL entity-reference fields that can point to paragraphs
- Excludes paragraphs with missing parent metadata, incoming references, or incomplete reference scans from cleanup
- Rechecks orphan candidates immediately before deletion
Built-In Issue Types
orphan_paragraphsbroken_referenceshardcoded_internal_absolute_urlslinkit_legacy_linkslinkit_broken_uuidsmissing_filesaccessibility_basicsredirect_health
| Issue type | What it reports | Scope and limits |
|---|---|---|
missing_files | File entities whose files are absent from local storage | Checks local Drupal stream wrappers. Remote and custom non-local wrappers are counted as not checked; no network request is made. |
accessibility_basics | Image fields or rich-text images without alternative text, rich-text links without a label, and link fields without an explicit title | Scans current node, paragraph, and media entities. A missing link-field title is a review signal because its formatter may show another label. It cannot decide from context whether an image is decorative or a label is meaningful. |
redirect_health | Internal redirects to missing nodes, redirect chains, and loops | Requires the Redirect module. It checks exact internal paths and direct node targets; arbitrary routes and external destinations are not HTTP-checked. |
These reports are diagnostic only. They do not edit content, delete file entities,
or change redirect records. Use drush content-reviewer:scan <issue_id> --stdout
to export each report as JSON.
The linkit_broken_uuids report includes current paragraph rows and historical
paragraph revisions. It groups repeated historical anchors by paragraph, field,
link, and UUID, and shows the matching revision IDs and the parent metadata from
each row. The admin report defaults to 50 occurrences; append ?limit=0 to
include every occurrence in the report. Missing parent metadata is a review
signal, not proof that a paragraph is unused.
The fix only scans and updates current rows by default. It synchronizes the
matching active revision row only when its stored value still matches the
current row; older revisions are read-only unless explicitly included with
--set=include_revisions=1. Preview both scopes before applying a fix,
especially when revisions are included:
drush content-reviewer:fix linkit_broken_uuids --dry-run
drush content-reviewer:fix linkit_broken_uuids --dry-run --set=include_revisions=1
To apply a reviewed fix to historical revisions, explicitly pass
--dry-run=0 --set=include_revisions=1.
The linkit_legacy_links fix follows the same safety rules: it updates current
paragraph content and its matching active revision snapshot by default, and
does not patch older revisions unless --set=include_revisions=1 is supplied.
The hardcoded_internal_absolute_urls report identifies URLs that can be
normalized by removing the same-site host. It checks whether /node/{nid}
targets exist, but does not make HTTP requests; other destinations are marked
not checked. The fix skips missing node targets and only normalizes their URL
when the report can verify the node exists or the destination was not checked.
The broken_references report describes current node field rows and adds source
publication status. It is a diagnostic report and does not repair references.
The orphan paragraph report checks all SQL entity-reference fields that target
paragraphs in addition to each paragraph's recorded parent history. A custom
storage reference field or scan error blocks deletion of candidates that could
not be checked. The result therefore means no incoming reference was found in
the scanned storage; it is not a claim about references outside Drupal's
registered SQL entity fields.
Installation
composer require protonsystems/content-reviewer
drush en content_reviewer
Usage
Generic issue commands:
drush content-reviewer:list
drush content-reviewer:scan orphan_paragraphs --stdout
drush content-reviewer:fix orphan_paragraphs --dry-run
Useful options:
--output=<path>: write JSON output to a file--stdout: print scan results to stdout--pretty=0|1: control pretty-printed JSON output--set=key=value: pass plugin-specific options, repeat as needed--dry-run=0|1: control whether fix commands persist changes
Examples:
drush content-reviewer:scan broken_references --set=max_reference_fields=300 --stdout
drush content-reviewer:scan hardcoded_internal_absolute_urls --set=hosts=example.com,www.example.com --stdout
drush content-reviewer:fix hardcoded_internal_absolute_urls --dry-run=0 --set=hosts=example.com,www.example.com
drush content-reviewer:fix linkit_broken_uuids --dry-run=0
Legacy orphan paragraph commands:
drush content_reviewer:analyze-orphaned-paragraphs
drush content_reviewer:verify-orphans
drush content_reviewer:delete-orphaned-paragraphs
The orphan paragraph report separates current references, historical-only references, absence from the recorded parent's revisions, and records that could not be checked. The cleanup commands only consider the absence group; paragraphs with missing parent metadata remain for manual review.
Releasing A New Version On Packagist
Packagist publishes versions of protonsystems/content-reviewer from git tags.
Do not add a version field to composer.json; create and push a new release
tag instead.
For the first public release, submit the public Git repository URL to Packagist.
If you want automatic updates from GitLab, configure the Packagist integration
in GitLab under Settings > Integrations using your Packagist username and API
token.
Prepare the release commit on main:
git checkout main
git pull origin main
composer validate --no-check-publish --strict
git status
git add <files>
git commit -m "Release v1.0.0"
git push origin main
Create and push an annotated tag for the release:
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
Use semantic version tags such as v1.0.0. After the tag is pushed, Packagist
should detect the new version automatically. If it does not appear, open the
package page in Packagist and trigger a manual update.
Notes
- Keep a DB snapshot before non-dry-run cleanup commands
- Prefer internal or entity-relative replacements for same-site links instead of hardcoded production URLs
protonsystems/qamust expose a compatibleqa_audit_coresubmodule release line before external Packagist installs of this module will resolve