LinkWatch
LinkWatch WordPress plugin (not in the WP Plugins Directory yet)
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/lennonka/linkwatch/archive/refs/heads/master.zipFind links in published WordPress posts and pages, check their HTTP status, and see which content needs attention.
LinkWatch adds Tools → LinkWatch and an administrator dashboard widget. Discovery and HTTP checking are separate actions: viewing results never triggers outgoing checks.
Requirements
- WordPress 7.1 or later.
- PHP 8.5 or later, with DOM/XML support (
DOMDocumentandDOMXPath) and the extensions required by WordPress and the installed Composer dependencies. - A database supported by WordPress, with permission to create and alter the plugin's tables.
- Composer 2 on the development/build machine. Composer is unnecessary on the production server when you deploy a package containing
vendor/. - Outgoing HTTP/HTTPS access, working DNS, and a valid TLS certificate trust store for link checking.
- JavaScript enabled in the administrator's browser for scan/check controls and progress reporting.
The command-line PHP and the PHP serving WordPress must both meet the requirements. Composer checks dependency requirements; the plugin's PHP 8.5 minimum and DOM requirement must also be satisfied.
Prepare for production
Build an installable ZIP
A GitHub source archive or checkout does not include Composer dependencies. Build the package before uploading it to WordPress.
From a release checkout of this repository:
composer validate
composer install --no-dev --prefer-dist --optimize-autoloader --no-interaction
composer check-platform-reqs --no-dev
Use composer install to reproduce the versions in composer.lock. The optimized autoloader and platform check are documented in the Composer CLI reference. Do not bypass platform checks to build for an incompatible runtime.
On a Unix-like build machine with mktemp, cp, and zip, run these commands from the repository root:
package_dir=$(mktemp -d /tmp/linkwatch-release.XXXXXX)
mkdir "$package_dir/linkwatch"
cp linkwatch.php composer.json composer.lock README.md readme.txt AGENTS.md "$package_dir/linkwatch/"
cp -R src assets vendor "$package_dir/linkwatch/"
(cd "$package_dir" && zip -qr linkwatch.zip linkwatch)
The installable ZIP is at $package_dir/linkwatch.zip. Its top-level directory is linkwatch/, containing the plugin entry file, src/, assets/, and vendor/. No JavaScript or CSS compilation is required.
Install and verify
- Back up the site's database and existing plugin files before an upgrade.
- In WordPress, open Plugins → Add New Plugin → Upload Plugin, upload the prepared ZIP, and activate it. For an existing installation, replace it through the update flow without deleting the plugin first.
- As an administrator, open Tools → LinkWatch and click Scan content.
- Click Check links, then inspect Watch Pages, Watch Posts, and the dashboard widget.
- Save the desired Settings values and verify that pagination and the default Watch tab behave as expected.
You can also deploy the prepared linkwatch/ directory directly into wp-content/plugins/. Include vendor/ in every deployment. Schema upgrades run during normal plugin startup, so an update does not require reactivation.
Hosting considerations
Scanning reads content in batches of 100, but the full scan still runs in one REST request. Checking runs sequentially in another request, with a 10-second inactivity timeout and a 15-second maximum duration per HTTP request. DNS validation also takes time outside those HTTP limits.
There are no background jobs, scheduled checks, or resumable operations. Ensure PHP, the web server, and any proxy allow enough time for your site's collection. Avoid simultaneous scans or checks from different browser tabs or administrators. If a request fails, results may be partially updated; correct the cause and rerun the action.
The administrator-only REST endpoints are /linkwatch/v1/scan, /linkwatch/v1/check, and /linkwatch/v1/check-progress. The progress bar polls the progress endpoint once per second. It needs another available PHP worker while the check request runs. A single-worker development server may show progress only after the check finishes. The bar reports completed stored-link rows, not elapsed time.
Using LinkWatch
- Scan content discovers links in published posts and pages, synchronizes occurrences, and removes stale sources and unused URLs. Every successful full scan clears all previous check results and timestamps.
- Check links checks all stored links and saves their results. It is enabled only when a successful scan is fresh and at least one link exists.
- Watch Pages / Watch Posts show URLs, source edit links, occurrence counts, status, details, and check times. Times use the viewing administrator's browser timezone.
Repeated links within the same content remain separate stored occurrences and contribute to the occurrence count. Pagination counts rows in the Source Page/Post column: one row represents a URL and a source. A URL with several sources can continue onto another page. Source rows belonging to the same URL share their background and hover highlight.
The dashboard groups occurrence counts by status for Pages and Posts separately. It displays the newest stored check time once in the footer.
Settings
| Setting | Default | Accepted values |
|---|---|---|
| Content scan expires after | 60 minutes | 1–10080 minutes |
| Default Watch tab | Watch Pages | Enable to open Watch Posts first |
| Watch table page size | 20 rows | 1–100 source rows per page |
The expiry controls whether checking is allowed; it does not schedule a scan. Leaving Settings with unsaved changes triggers the browser's confirmation dialog. Saving or reverting the changes suppresses the warning.
Results and limitations
| Status | Meaning |
|---|---|
| Not checked | No stored check result, including after a scan |
| OK | HTTP 2xx |
| Redirect | HTTP 3xx; the redirect is not followed |
| Broken | HTTP 4xx |
| Server error | HTTP 5xx |
| Unknown | Another HTTP status |
| Unreachable | DNS, connection, timeout, or other transport failure |
| Invalid | Malformed or incomplete URL |
| Blocked | Destination rejected by URL/network validation |
Redirect targets are displayed as plain text. Link checking validates HTTP/HTTPS URLs, credentials, ports, localhost, and resolved private/reserved destinations. Local development URLs may therefore be reported as Blocked. These checks do not establish that a destination is trustworthy or that its content is correct.
Discovery parses stored HTML, without rendering shortcodes or dynamic blocks. Only published posts and pages are scanned; custom post types are excluded. Fragment identifiers are removed from tracked URLs. Link extraction excludes empty, fragment-only, email, telephone, and JavaScript links, and requires a URL accepted by PHP's URL validation.
Troubleshooting
- Activation cannot find
vendor/autoload.php: rebuild or redeploy with Composer dependencies included. - Check links is disabled: run a scan, confirm it found links, and check the scan-expiry setting. Hover over the disabled button for the reason.
- REST actions fail: verify administrator permissions, reload for a fresh nonce, and check whether a security plugin or proxy blocks the plugin's REST routes.
- Checks fail or time out: inspect the stored error, outgoing access, DNS, TLS configuration, and server request limits. A successful HTTP status alone does not guarantee usable page content.
- The progress bar does not update: check PHP worker availability and the
/check-progressrequest in browser developer tools.
Data and removal
The plugin stores links and occurrences in <prefix>linkwatch_links and <prefix>linkwatch_occurrences, using the table prefix of the WordPress instance. Settings and schema metadata use WordPress options. Progress uses short-lived, per-administrator transients.
Deactivation preserves stored data. Uninstalling/deleting the plugin removes its custom tables, schema metadata, and settings. Back up the database if you need to retain results.
Development
Set up a local installation
Prepare a local WordPress 7.1+ instance running PHP 8.5+ with DOM support. Clone the repository into its plugins directory:
cd /path/to/wordpress/wp-content/plugins
git clone https://github.com/Lennonka/linkwatch.git linkwatch
cd linkwatch
composer install
composer validate
composer check-platform-reqs
Activate LinkWatch through WordPress, or use WP-CLI from the WordPress root:
wp plugin activate linkwatch
Node.js is needed only to run JavaScript syntax checks. The assets are plain JavaScript and CSS; there is no npm installation or frontend build step. Do not edit generated files in vendor/.
Code layout
| Area | Files |
|---|---|
| Bootstrap and WordPress hooks | linkwatch.php, src/Plugin.php |
| HTML extraction and URL handling | src/LinkExtractor.php, src/LinkUrl.php |
| Discovery and checks | src/Scanner.php, src/Monitor.php, src/LinkChecker.php |
| Persistence and schema | src/Database/ |
| Options | src/Settings.php |
| Admin interface | assets/ |
Composer maps WPLinkWatch\ to src/ via PSR-4. Use constructor injection and keep database operations in repositories. The plugin uses Symfony components without a Symfony kernel. See AGENTS.md for detailed repository conventions.
Validation
Run from the repository root:
composer validate
php -l linkwatch.php
for file in src/*.php src/Database/*.php; do
php -l "$file" || exit 1
done
node --check assets/admin.js
node --check assets/local-time.js
git diff --check
After changing dependencies, run composer install and include the corresponding lockfile changes. After changing autoload mappings, run composer dump-autoload.
There is currently no automated test suite or CI configuration. Syntax checks do not replace WordPress integration checks. In a disposable installation, verify:
- Published posts/pages are discovered, repeated occurrences are counted, and unpublished/deleted sources are removed.
- Scanning clears check results; checking repopulates them only while the scan is fresh.
- Watch tables preserve source links, row counts, pagination, empty states, and group highlighting.
- All Settings values save correctly, invalid values are rejected, and unsaved-change warnings behave correctly.
- The dashboard reports the correct counts and browser-local timestamps.
- REST requests reject missing permissions/nonces; progress is isolated by administrator and updates after saved results.
For HTTP behavior changes, use public endpoints you control for success, redirects, client/server errors, connection failures, and slow responses. Also verify malformed URLs, unsupported schemes, disallowed ports, localhost, and private/reserved destinations. Redirects must remain unfollowed. Use a separate injected-client harness when local fixtures are intentionally blocked.
Release versions
The current plugin version is 1.0.0; the custom database schema version is 1.1.0. Keep the plugin header and Plugin::VERSION synchronized so asset cache versions follow the release.
Increment the plugin version for each packaged/deployed release using semantic versioning. Increment the schema version only for database changes or required migrations, and verify both fresh installation and upgrade from the previous schema. Presentation and option-only changes do not require a schema bump.
License
LinkWatch is licensed under the GNU General Public License, version 2 or any later version (GPLv2 or later). Read the latest GPL license (currently GPLv3).
Bundled third-party dependencies retain their own licenses and copyright notices in vendor/. Preserve those notices when distributing the plugin.