Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
c775537
use GlobalSettingsProvider override to store INI config data in the d…
diosmosis Jun 30, 2026
83f6f70
switch to using db option value as backup config in case file config …
diosmosis Jul 1, 2026
52cb2a0
fix unit tests
diosmosis Jul 1, 2026
2d9e4b4
undo config sync removal
diosmosis Jul 4, 2026
e9c5efc
make sure Settings notices when the current blog changes and reloads …
diosmosis Jul 5, 2026
0965464
delay ini file config write if environment has not been created yet +…
diosmosis Jul 5, 2026
d007fdd
fixing some tests
diosmosis Jul 5, 2026
b2a3ffb
update expected screenshots
diosmosis Jul 6, 2026
42d4b33
do not log config backup changes
diosmosis Jul 6, 2026
fcabf0a
try fixing random e2e test failure
diosmosis Jul 6, 2026
f80fceb
enable tracker debug logging to debug test failure
diosmosis Jul 6, 2026
1630d2a
make sure new TrackingSpamPrevention feature to block headless browse…
diosmosis Jul 6, 2026
4410dd1
do not save secrets to backup + avoid race conditions with backup saving
diosmosis Jul 7, 2026
e142243
make sure entire settings state is reloaded so pending changes are fo…
diosmosis Jul 7, 2026
5501624
prevent race conditions with core's non-atomic config.ini.php write b…
diosmosis Jul 7, 2026
73ac604
keep Plugins out of the db backup since it is built at runtime using …
diosmosis Jul 7, 2026
94ac3f9
two small bug fixes
diosmosis Jul 7, 2026
5517864
delegate suggestion hook registering until after init
diosmosis Jul 7, 2026
9168b9f
fixing a test
diosmosis Jul 7, 2026
ad20897
do not store other database sections in backup, but keep db settings …
diosmosis Jul 8, 2026
67853e0
use filemtime to detect interrupted writes and restore from the DB ba…
diosmosis Jul 8, 2026
a140ae2
remove code related to Settings::NETWORK_CONFIG_OPTION since the code…
diosmosis Jul 8, 2026
9e1301d
disable the persist backup path for tracker requests
diosmosis Jul 8, 2026
65750a4
to avoid silent privacy compliance violations, keep the salt in the b…
diosmosis Jul 8, 2026
dfac469
only use a substring of the auth salt when computing the salt encrypt…
diosmosis Jul 8, 2026
7237e1a
if a salt is regenerated, display a message to super admins informing…
diosmosis Jul 8, 2026
236268a
two more restore tests
diosmosis Jul 8, 2026
b62e548
add quick log when a blog is switched over before settings are saved …
diosmosis Jul 8, 2026
eb74513
couple minor fixes
diosmosis Jul 8, 2026
3cb34e0
handle the case where the config.ini.php file is not missing, but is …
diosmosis Jul 9, 2026
ade4779
change the eof marker to double as a readme for why its needed and wh…
diosmosis Jul 9, 2026
e3337f1
do not flag a blog as having the salt regenerated, if the blog was ne…
diosmosis Jul 9, 2026
f267d5c
do not use an EnvironmentManipulator but full Environment subclass to…
diosmosis Jul 9, 2026
f38bd03
allow users to disable the feature entirely via a const
diosmosis Jul 9, 2026
0c7f4ac
upgrade minimum woocommerce tested against version
diosmosis Jul 9, 2026
93d11b6
handle case with unparseable config.ini.php with no backup existing t…
diosmosis Jul 10, 2026
a0a1056
fix some concurrency related edge cases
diosmosis Jul 10, 2026
efab1a9
a couple more edge case fixes
diosmosis Jul 11, 2026
5c6e450
another concurrency edge case fix around non-atomic write in core
diosmosis Jul 11, 2026
c77c377
one more edge case fix
diosmosis Jul 11, 2026
c1a25c1
another edge case fix for when corrupt config is due to concurrent write
diosmosis Jul 11, 2026
6a62fa9
another edge case fix around the Config.beforeSave handler
diosmosis Jul 11, 2026
08e1cbb
keep tmp config file unreadable and remove stale files before restore
diosmosis Jul 11, 2026
c7a2609
make sure ini config data in GlobalSettingsProvider is handled the sa…
diosmosis Jul 12, 2026
155a075
refactor, use single method to check for MATOMO_DISABLE_CONFIG_BACKUP…
diosmosis Jul 12, 2026
de16ae6
remove unneeded condition
diosmosis Jul 12, 2026
a7465ab
undo unneeded change
diosmosis Jul 12, 2026
2b3fe02
move config backup disabled method to Settings so it can be used befo…
diosmosis Jul 12, 2026
253c358
one small refactor
diosmosis Jul 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 12 additions & 43 deletions app/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,6 @@ function matomo_log_message_no_display($message)
}
}


if ( matomo_is_app_request() ) {
// pretend we are in the admin... potentially avoiding caching etc
$GLOBALS['hook_suffix'] = '';
Expand Down Expand Up @@ -222,45 +221,15 @@ function matomo_log_message_no_display($message)
}
}

$GLOBALS['MATOMO_MODIFY_CONFIG_SETTINGS'] = function ($settings) {
$plugins = $settings['Plugins'];
if (is_array($settings['Plugins'])) {
$pluginsToRemove = array('Marketplace', 'MultiSites', 'TwoFactorAuth', 'Widgetize', 'Feedback', 'ExamplePlugin', 'ExampleAPI', 'MobileAppMeasurable', 'CustomPiwikJs');
foreach ($pluginsToRemove as $pluginToRemove) {
// Marketplace => this is instead done in wordpress
// MultiSites => doesn't really make sense since we have only one website per installation
// TwoFactorAuth => not needed as login is being handled by WordPress
// widgetize for now we don't want to allow widgetizing as it is based on the token_auth authentication
// Monolog => we use our own logger
// ProfessionalServices => we advertise in the WP plugin itself instead
// feedback => we want to hide things like Need help in the admin etc
// MobileAppMeasurable => for WP mobile apps are not a thing
// custom variables we don't want to enable as we will deprecate them in Matomo 4 anyway => used to be disabled but we need to make sure the columns get installed otherwise matomo has issues... need to wait to matomo 4 to remove it
$pos = array_search($pluginToRemove, $plugins['Plugins']);
if ($pos !== false) {
array_splice($plugins['Plugins'], $pos, 1);
}
}
if (matomo_has_tag_manager()) {
$plugins['Plugins'][] = 'TagManager';
}
$mustEnable = ['BulkTracking', 'CustomJsTracker'];
foreach ($mustEnable as $enable) {
if (!in_array($enable, $plugins['Plugins'])) {
$plugins['Plugins'][] = $enable;
}
}
}
if (!empty($GLOBALS['MATOMO_PLUGINS_ENABLED'])) {
foreach ($GLOBALS['MATOMO_PLUGINS_ENABLED'] as $plugin) {
if (!in_array($plugin, $plugins['Plugins'])) {
$plugins['Plugins'][] = $plugin;
}
}
}
if (!empty($GLOBALS['MATOMO_MARKETPLACE_PLUGINS'])) {
matomo_filter_incompatible_plugins($plugins['Plugins']);
}
$settings['Plugins'] = $plugins;
return $settings;
};
// use a WordPress specific Matomo Environment subclass so the WordPress GlobalSettingsProvider is
// used. core Matomo entry points are patched to instantiate WordPressEnvironment (see
// patches/prefixed/wordpress-environment.diff).
//
// at this point the composer autoloader is not included yet, so make sure the classes these
// entry points reference are loaded before they run.
if (!defined('PIWIK_INCLUDE_PATH')) {
define('PIWIK_INCLUDE_PATH', PIWIK_DOCUMENT_ROOT);
}
require_once PIWIK_DOCUMENT_ROOT . '/vendor/autoload.php';
require_once __DIR__ . '/../plugins/WordPress/Overrides/GlobalSettingsProvider.php';
require_once __DIR__ . '/../plugins/WordPress/WordPressEnvironment.php';
2 changes: 1 addition & 1 deletion app/core/CliMulti/RequestCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ private function recreateContainerWithWebEnvironment()
{
StaticContainer::clearContainer();
Log::unsetInstance();
$this->environment = new Environment(null);
$this->environment = new \Piwik\Plugins\WordPress\WordPressEnvironment(null);
$this->environment->init();
}
private function resetDatabase()
Expand Down
2 changes: 1 addition & 1 deletion app/core/Console.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ protected function initEnvironment(OutputInterface $output)
{
try {
if ($this->environment === null) {
$this->environment = new Environment('cli');
$this->environment = new \Piwik\Plugins\WordPress\WordPressEnvironment('cli');
$this->environment->init();
}
$config = \Piwik\Config::getInstance();
Expand Down
2 changes: 1 addition & 1 deletion app/core/dispatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
\define('PIWIK_ENABLE_DISPATCH', \true);
}
if (\PIWIK_ENABLE_DISPATCH) {
$environment = new \Piwik\Application\Environment(null);
$environment = new \Piwik\Plugins\WordPress\WordPressEnvironment(null);
$environment->init();
$controller = FrontController::getInstance();
try {
Expand Down
2 changes: 1 addition & 1 deletion app/js/tracker.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class Validator {
public function validate() {}
}
$validator = new Validator();
$environment = new \Piwik\Application\Environment(null, array(
$environment = new \Piwik\Plugins\WordPress\WordPressEnvironment(null, array(
'Piwik\Application\Kernel\EnvironmentValidator' => $validator
));
$environment->init();
Expand Down
2 changes: 1 addition & 1 deletion app/piwik.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
require_once \PIWIK_INCLUDE_PATH . '/core/Cookie.php';
require_once \PIWIK_INCLUDE_PATH . '/core/API/CORSHandler.php';
SettingsServer::setIsTrackerApiRequest();
$environment = new \Piwik\Application\Environment('tracker');
$environment = new \Piwik\Plugins\WordPress\WordPressEnvironment('tracker');
try {
$environment->init();
} catch (\Piwik\Exception\NotYetInstalledException $e) {
Expand Down
5 changes: 2 additions & 3 deletions classes/WpMatomo/API.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public function register_routes() {
$this->register_route( 'CoreAdminHome', 'invalidateArchivedReports' );
$this->register_route( 'CoreAdminHome', 'runScheduledTasks' );
$this->register_route( 'CoreAdminHome', 'runCronArchiving' );
$this->register_route( 'CorePluginsAdmin', 'setSystemSettings' );
$this->register_route( 'Dashboard', 'getDashboards' );
$this->register_route( 'ImageGraph', 'get' );
$this->register_route( 'VisitsSummary', 'getVisits' );
Expand Down Expand Up @@ -155,9 +156,6 @@ public function to_snake_case( $method ) {
return implode( '_', $snake_case );
}

/**
* @api
*/
public function register_route( $api_module, $api_method ) {
$methods = [
'get' => 'GET',
Expand All @@ -170,6 +168,7 @@ public function register_route( $api_module, $api_method ) {
'run' => 'POST',
'send' => 'POST',
'publish' => 'POST',
'set' => 'POST',
'delete' => 'DELETE',
'remove' => 'DELETE',
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ public function register_hooks() {

add_action( 'admin_enqueue_scripts', [ $this, 'load_scripts' ] );

add_action( 'init', [ $this, 'register_suggestion_hooks' ] );
}

public function register_suggestion_hooks() {
foreach ( $this->get_suggestions() as $suggestion ) {
$suggestion->register_hooks();
}
Expand Down
24 changes: 22 additions & 2 deletions classes/WpMatomo/Admin/SystemReport.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@ class SystemReport implements MatomoPageContent {
const TROUBLESHOOT_UPDATE_GEOIP_DB = 'matomo_troubleshooting_action_update_geoipdb';
const TROUBLESHOOT_CLEAR_LOGS = 'matomo_troubleshooting_action_clear_logs';
const TROUBLESHOOT_RUN_UPDATER = 'matomo_troubleshooting_action_run_updater';
const REGENERATE_TRACKING_CODE = 'matomo_troubleshooting_action_regen_tracking_code';
const RUN_SCHEDULED_TASK = 'matomo_troubleshooting_action_run_task';

const TROUBLESHOOT_DISMISS_SALT_REGENERATED = 'matomo_troubleshooting_action_dismiss_salt_regenerated';
const REGENERATE_TRACKING_CODE = 'matomo_troubleshooting_action_regen_tracking_code';
const RUN_SCHEDULED_TASK = 'matomo_troubleshooting_action_run_task';

private $not_compatible_plugins = [
'minify-html-markup',
Expand Down Expand Up @@ -196,6 +198,10 @@ private function execute_troubleshoot_if_needed() {
$this->logger->clear_logged_exceptions();
}

if ( ! empty( $_POST[ self::TROUBLESHOOT_DISMISS_SALT_REGENERATED ] ) ) {
$this->settings->set_time_salt_was_regenerated( 0 );
}

if ( ! $this->settings->is_network_enabled() || ! is_network_admin() ) {
if ( ! empty( $_POST[ self::TROUBLESHOOT_SYNC_USERS ] ) ) {
$sync = new UserSync();
Expand Down Expand Up @@ -366,6 +372,7 @@ public function show() {
$matomo_has_exception_logs = [];
$matomo_has_warning_and_no_errors = false;
$matomo_scheduled_tasks = [];
$matomo_salt_was_regenerated = $this->settings->get_time_salt_was_regenerated() > 0;

if ( empty( $matomo_active_tab ) ) { // system report
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting
Expand Down Expand Up @@ -585,6 +592,19 @@ private function get_matomo_info() {
$path_config_file = $paths->get_config_ini_path();
$rows = $this->check_file_exists_and_writable( $rows, $path_config_file, 'Config', true );

$salt_regenerated_time = $this->settings->get_time_salt_was_regenerated();
if ( $salt_regenerated_time > 0 ) {
$rows[] = [
'name' => esc_html__( 'Matomo salt was regenerated during a config file recovery', 'matomo' ),
'value' => $this->convert_time_to_date( $salt_regenerated_time, true, true ),
'is_warning' => true,
'comment' => esc_html__(
'The Matomo config file (config.ini.php) went missing or was corrupted, and was automatically recovered from the backup kept in the WordPress database. The original "salt" setting could not be recovered, however, so a new one was generated. This has the following consequences for your install: visitors who opted out of tracking through the Matomo opt-out feature are being tracked again, because their opt-out cookie is no longer recognized — consider informing your users to renew their opt-out, since being able to refuse tracking may be legally required depending on your jurisdiction (eg. GDPR/ePrivacy). Additionally, visits that were ongoing at the time of the recovery may appear split in two in your reports.',
'matomo'
),
];
}

$path_tracker_file = $paths->get_matomo_js_upload_path();
$rows = $this->check_file_exists_and_writable( $rows, $path_tracker_file, 'JS Tracker', false );

Expand Down
8 changes: 8 additions & 0 deletions classes/WpMatomo/Admin/views/systemreport.php
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,14 @@ class='button-primary'
<br/><br/>
<?php } ?>

<?php if ( ! empty( $matomo_salt_was_regenerated ) ) { ?>
<input name="<?php echo esc_attr( SystemReport::TROUBLESHOOT_DISMISS_SALT_REGENERATED ); ?>" type="submit"
class='button-primary'
title="<?php esc_attr_e( 'Removes the system report warning about the Matomo salt having been regenerated during a config file recovery.', 'matomo' ); ?>"
value="<?php esc_html_e( 'Dismiss salt regeneration warning', 'matomo' ); ?>">
<br/><br/>
<?php } ?>

<input name="<?php echo esc_attr( SystemReport::TROUBLESHOOT_UPDATE_GEOIP_DB ); ?>" type="submit"
class='button-primary'
title="<?php esc_attr_e( 'Updates the geolocation database which is used to detect the location (city/region/country) of visitors. This task is performed automatically. If the geolocation DB is not loaded or updated, you may need to trigger it manually to find the error which is causing it.', 'matomo' ); ?>"
Expand Down
4 changes: 2 additions & 2 deletions classes/WpMatomo/Bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace WpMatomo;

use Piwik\Application\Environment;
use Piwik\Plugins\WordPress\WordPressEnvironment;
use Piwik\Cache;
use Piwik\Container\StaticContainer;
use Piwik\FrontController;
Expand Down Expand Up @@ -104,7 +104,7 @@ public static function bootstrap_environment() {

include_once 'Db/WordPress.php';

$environment = new Environment( null, self::$extra_di_definitions );
$environment = new WordPressEnvironment( null, self::$extra_di_definitions );
$environment->init();

self::$environment_bootstrapped = true;
Expand Down
43 changes: 31 additions & 12 deletions classes/WpMatomo/Installer.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Piwik\Plugin\API as PluginApi;
use Piwik\Plugin\Manager;
use Piwik\Plugins\SitesManager\Model;
use Piwik\Plugins\WordPress\Overrides\GlobalSettingsProvider;
use Piwik\SettingsPiwik;
use Piwik\Singleton;
use WpMatomo\Site\Sync;
Expand Down Expand Up @@ -114,7 +115,10 @@ public function install() {

Bootstrap::bootstrap_environment();

if ( ! SettingsPiwik::isMatomoInstalled() || ! $this->looks_like_it_is_installed() ) {
if (
! SettingsPiwik::isMatomoInstalled()
|| ! $this->looks_like_it_is_installed()
) {
throw new NotYetInstalledException( 'Not yet installed' );
}

Expand Down Expand Up @@ -148,7 +152,7 @@ public function install() {
if ( ! $this->is_environment_set_up() ) {
try {
$this->logger->log( 'Matomo will now init the environment' );
$environment = new \Piwik\Application\Environment( null, Bootstrap::get_extra_di_definitions() );
$environment = new \Piwik\Plugins\WordPress\WordPressEnvironment( null, Bootstrap::get_extra_di_definitions() );
$environment->init();
} catch ( Exception $e ) {
$this->logger->log( 'Ignoring error environment init' );
Expand Down Expand Up @@ -299,18 +303,25 @@ private function create_db() {
return $db_infos;
}

private function create_config( $db_info ) {
$this->logger->log( 'Matomo is now creating the config' );
public static function get_trusted_host_from_wp_url() {
$home_url = home_url();
$domain = wp_parse_url( $home_url, PHP_URL_HOST );
if ( $domain ) {
$port = wp_parse_url( $home_url, PHP_URL_PORT );
if ( $port ) {
$domain .= ':' . $port;
}
} else {
$domain = $home_url;

$domain = wp_parse_url( $home_url, PHP_URL_HOST );
if ( ! $domain ) {
return $home_url;
}

$port = wp_parse_url( $home_url, PHP_URL_PORT );
if ( $port ) {
$domain .= ':' . $port;
}

return $domain;
}

private function create_config( $db_info ) {
$this->logger->log( 'Matomo is now creating the config' );
$domain = self::get_trusted_host_from_wp_url();
$general = [
'trusted_hosts' => [ $domain ],
'salt' => Common::generateUniqId(),
Expand All @@ -333,6 +344,14 @@ private function create_config( $db_info ) {
$config->database = array_merge( $db_default, $db_info );
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$config->General = array_merge( $general_default, $general );

// add the end-of-file marker used to detect interrupted writes to config.ini.php; it must
// exist in every config file (see GlobalSettingsProvider). skip it when the config backup
// feature is disabled, so the file is not modified for admins managing it themselves.
if ( ! Settings::is_config_backup_disabled() ) {
GlobalSettingsProvider::addEndOfFileMarkerSectionTo( $config );
}

$config->forceSave();

$mode = 0664;
Expand Down
33 changes: 30 additions & 3 deletions classes/WpMatomo/ScheduledTasks.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Piwik\Plugins\GeoIp2\LocationProvider\GeoIp2;
use Piwik\Plugins\GeoIp2\LocationProvider\GeoIp2\Php;
use Piwik\Plugins\UserCountry\LocationProvider;
use Piwik\Plugins\WordPress\Overrides\GlobalSettingsProvider;
use WpMatomo\Admin\Admin;
use WpMatomo\Site\Sync as SiteSync;
use WpMatomo\User\Sync as UserSync;
Expand All @@ -33,6 +34,7 @@ class ScheduledTasks extends Feature {
const EVENT_ARCHIVE = 'matomo_scheduled_archive';
const EVENT_GEOIP = 'matomo_scheduled_geoipdb';
const EVENT_UPDATE = 'matomo_update_core';
const EVENT_CONFIG_MARKER = 'matomo_scheduled_config_marker';

const KEY_BEFORE_CRON = 'before-cron-';
const KEY_AFTER_CRON = 'after-cron-';
Expand Down Expand Up @@ -145,21 +147,26 @@ public function set_last_time_after_cron( $event_name, $time ) {

public function get_all_events() {
$events = [
self::EVENT_SYNC => [
self::EVENT_SYNC => [
'name' => 'Sync users & sites',
'interval' => 'daily',
'method' => 'sync',
],
self::EVENT_ARCHIVE => [
self::EVENT_ARCHIVE => [
'name' => 'Archive',
'interval' => 'hourly',
'method' => 'archive',
],
self::EVENT_GEOIP => [
self::EVENT_GEOIP => [
'name' => 'Update GeoIP DB',
'interval' => 'matomo_monthly',
'method' => 'update_geo_ip2_db',
],
self::EVENT_CONFIG_MARKER => [
'name' => 'Add config end of file marker',
'interval' => 'daily',
'method' => 'add_config_end_of_file_marker',
],
];
if ( $this->settings->should_disable_addhandler() ) {
$events[ self::EVENT_DISABLE_ADDHANDLER ] = [
Expand Down Expand Up @@ -211,6 +218,26 @@ public function disable_add_handler( $force_undo = false ) {
}
}

/**
* Adds the eof file marker used by the config backup system (see GlobalSettingsProvider).
*/
public function add_config_end_of_file_marker() {
if ( Settings::is_config_backup_disabled() ) {
return; // the marker only exists for the config backup feature
}

$this->remove_task_errors( [ 'config_backup' ] );

try {
Bootstrap::do_bootstrap();

$updater = new Updater( $this->settings );
$updater->add_config_end_of_file_marker_if_needed();
} catch ( Exception $e ) {
$this->on_task_fail( 'config_backup', $e, 'An error occurred when adding the end-of-file marker to config.ini.php.' );
}
}

private function check_try_update() {
try {
$installer = new Installer( $this->settings );
Expand Down
Loading
Loading