WP Manifestindependent plugin directory

Install

Download release.zip From the 2.4.1 release on GitHub, Aug 21, 2025

Upload it under Plugins → Add New Plugin → Upload Plugin, then activate.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/helsingborg-stad/api-alarm-manager/releases/download/2.4.1/release.zip

On Packagist as helsingborg-stad/api-alarm-manager:

composer require helsingborg-stad/api-alarm-manager
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Creates WordPress Rest API endpoints for contal alarms and fire danger levels.

Alarms

Alarms are imported from a remote FTP/SFTP server. The alarms are then stored in the WordPress database and can be fetched from the WordPress Rest API. After the alarms have been imported, they are moved to an archive folder on the remote server. For the alarms to be archived on the remote server, the const API_ALARM_MANAGER_ARCHIVE_ALARMS_ON_REMOTE must be set to true. Otherwise the alarm files on the remote server will note be altered.

Fire danger levels

Fire danger levels are posts that are created in WordPress. The posts are then fetched from the WordPress Rest API.

Constants

define('API_ALARM_MANAGER_ARCHIVE_ALARMS_ON_REMOTE', true); - REQUIRED to archive alarms on remote server.

Devcontainer

This project uses a devcontainer for development. This means that you can use VS Code to develop the project. To use the devcontainer, you need to install the Remote - Containers extension for VS Code. When you have installed the extension, you can open the project in a container by clicking the green button in the bottom left corner of VS Code and select "Remote-Containers: Reopen in Container".

Devcontainer ftp/sftp services

To make it easier to test this plugins functionality on your local machine you can use the ftp/sftp services that are installed in the devcontainer. The services and their credentials are listed in the ./devcontainer/docker-compose.yml file.

Use the following details to set up a connection to either ftp or sftp from the WordPress admin panel:

SFTP

  • server: sftp
  • username: demo
  • password: demo
  • FTP/SFTP folder: /files
  • FTP/SFTP archive folder: /archive

FTP

  • server: ftp
  • username: demo
  • password: demo
  • FTP/SFTP folder: /files
  • FTP/SFTP archive folder: /archive

Devcontainer ftp/sftp test files

A number of files for testing locally are available in the ./devcontainer/remoteFiles. To prepare or reset test files run the shell script ./.vscode/tasks/reset-dummy-files from the terminal:

sh ./vscode/tasks/reset-dummy-files.sh

PHP Tests

Municipio uses PHPUnit for unit testing. For mocking and stubbing we use WP_Mock. This means that you can use WP_Mock, Mockery(since this is a wrapper for WP_Mock) and PHPUnit_MockObject for mocking and stubbing.

PHPUnit Tests file structure

All tests are stored in the tests/phpunit/tests folder. The file structure should mirror the file structure of the theme. The file name should be the same as the file you want to test. For example, if you want to test the file src/Controller/Base.php you should create the file tests/phpunit/tests/Controller/Base.php. To avoid having too large test files, you can instead create a folder with the same name as the file you want to test and put the test files inside. Please note that for separating files by which class function you are testing, you should name the file e.g. Base.functionName.php.

Running PHPUnit tests

Run composer test in the terminal.

Running PHPUnit tests with code coverage

Run composer test:coverage in the terminal. This will generate a code coverage report in the tests/phpunit/.coverage folder.

Releases

14 releases. Each count is every asset in that release; expand a row for the breakdown.

Tag
Published
Assets
Downloads
2.4.1 latest
Aug 21, 2025 1 year ago
release.zip
4
May 9, 2025 1 year ago
release.zip
2
May 1, 2025 1 year ago
release.zip
0
Apr 28, 2025 1 year ago
release.zip
0
Apr 1, 2025 1 year ago
release.zip
5
Apr 1, 2025 1 year ago
release.zip
3
Mar 18, 2025 1 year ago
tag only
—
May 22, 2024 2 years ago
release.zip
1
May 21, 2024 2 years ago
release.zip
1
Dec 20, 2023 2 years ago
tag only
—
Dec 18, 2023 2 years ago
tag only
—
Dec 14, 2023 2 years ago
tag only
—
Dec 14, 2023 2 years ago
tag only
—
Dec 12, 2023 2 years ago
tag only
—
D+ grade

Security

Api Alarm Manager 2.4.0 · audited by WP Registry

High-severity findings.

1 high 2 medium
Audited release
2.4.0
Findings
3
Worst severity
high
Content hash
60fca55ba690465db43be00b…

Findings

  • high Unauthenticated and Subscriber-reachable alarm import with no capability or nonce check

    missing_auth

    When FTP/SFTP import is configured (the plugin's intended production mode), Importer::addHooks() registers two unguarded entry points for the same privileged import(): (1) any request with query argument alarmimport hooks import() on init with no is_user_logged_in, current_user_can, or nonce check, so an unauthenticated GET to /?alarmimport=1 runs the importer; (2) wp_ajax_import_alarms is reachable by every logged-in role, including Subscriber, and the admin JS posts {action: 'import_alarms'} with no nonce. import() raises max_execution_time to 600, creates wp-content/uploads/alarms at mode 0777, copies every file listed on the remote host (not just XML) into that web-accessible directory using stored FTP credentials, parses XML into published alarm and station posts, updates api-alarm-manager-last-import, and when API_ALARM_MANAGER_ARCHIVE_ALARMS_ON_REMOTE is true moves remote files into the archive folder. A one-time path also blanks post_content on all existing alarm posts. The admin UI only shows the import button to administrators, but that check is not applied to either handler. Impact is unauthenticated integrity/availability abuse of the municipal alarm pipeline (DoS against FTP and PHP, forced content create/update, possible remote XML loss, arbitrary-file drop into uploads if the FTP tree is untrusted).

    source/php/Importer.php:20-39

    Recommendation

    Require manage_options (or a dedicated cap) and a CSRF nonce on both the GET trigger and wp_ajax_import_alarms. Remove the unauthenticated ?alarmimport= init hook; if a manual trigger is needed, keep it behind admin-post.php or AJAX with check_ajax_referer and current_user_can. Restrict remote copies to .xml, store them outside the web root, and do not mkdir 0777.

  • medium Public fire-danger levels options page is writable by Contributor+ (edit_posts)

    missing_capability

    Admin\FireDangerLevels registers an ACF options page at menu slug fire-danger-levels with capability edit_posts. Contributors, Authors, and Editors can therefore change the fire_danger_levels repeater stored in options. Those values are the sole data source for the unauthenticated REST callbacks Api\FireDangerLevels::getFireDangerLevels and Api\AllDisturbances::getApiResponse (routes /wp/v2/fire-danger-levels and /all-disturbances), which city front-ends consume as official fire-ban status. FTP/API credentials on the sibling options page correctly require edit_users; fire-danger does not. A Contributor who cannot even publish posts can still poison the public emergency API.

    source/php/Admin/FireDangerLevels.php:16-29

    Recommendation

    Raise the options page capability to manage_options (or at least publish_posts / edit_others_posts) so only trusted operators can change publicly consumed fire-ban data.

  • medium Global Cache-Control: public, max-age=300 overwrites logged-in nocache headers

    insecure_config

    PostTypes\Alarms hooks rssCacheControl to send_headers without an is_feed() guard. The callback always sends Cache-Control: max-age=300, public and a matching Expires header. WP::send_headers applies wp_get_nocache_headers() for logged-in users first, then fires send_headers, so this plugin overwrites WordPress's private/no-cache policy on every front-end request that reaches WP::send_headers (archives, singles, feeds, and any other non-REST query). Independently, Api\Disturbances::setCacheHeader() removes rest_send_cors_headers and sets Cache-Control: max-age=300 on rest_pre_serve_request for the entire REST API, not only disturbance routes. Shared caches that honor public (CDN 'cache everything', Varnish, nginx proxy_cache) may store and replay authenticated or user-specific HTML and REST responses for five minutes.

    source/php/PostTypes/Alarms.php:15-16, 63-66

    Recommendation

    Only send RSS cache headers inside is_feed() (and only for alarm feeds). Do not use the public directive on authenticated responses. Scope the REST Cache-Control filter to the plugin's own GET routes and send Cache-Control: public, max-age=300 only for unauthenticated GETs of public alarm data.

WP Registry hashes the installable build and reports on that exact bytes-for-bytes copy. Embargoed findings are withheld until they are disclosed, so a clean verdict means nothing public is outstanding. WP Manifest does not audit code itself.