PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.12.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.12.1
5.12.1 5.12.0 5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / classes / WpMatomo / Site / Sync.php
matomo / classes / WpMatomo / Site Last commit date
Sync 1 week ago Sync.php 1 week ago
Sync.php
339 lines
1 <?php
2 /**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 * @package matomo
8 */
9
10 namespace WpMatomo\Site;
11
12 use Exception;
13 use Piwik\Access;
14 use Piwik\Config;
15 use Piwik\Container\StaticContainer;
16 use Piwik\Intl\Data\Provider\CurrencyDataProvider;
17 use Piwik\Plugins\SitesManager;
18 use Piwik\Plugins\SitesManager\Model;
19 use WP_Site;
20 use WpMatomo\Bootstrap;
21 use WpMatomo\Feature;
22 use WpMatomo\Installer;
23 use WpMatomo\Logger;
24 use WpMatomo\Settings;
25 use WpMatomo\Site;
26 use WpMatomo\Site\Sync\SyncConfig;
27
28 if ( ! defined( 'ABSPATH' ) ) {
29 exit; // if accessed directly
30 }
31
32 /**
33 * Properties coming from matomo
34 * phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
35 */
36 class Sync extends Feature {
37 const MAX_LENGTH_SITE_NAME = 90;
38
39 /**
40 * @var Logger
41 */
42 private $logger;
43
44 /**
45 * @var Settings
46 */
47 private $settings;
48
49 /**
50 * @var SyncConfig
51 */
52 private $config_sync;
53
54 public function __construct( Settings $settings ) {
55 $this->logger = new Logger();
56 $this->settings = $settings;
57 $this->config_sync = new SyncConfig( $settings );
58 }
59
60 public function is_active() {
61 return is_admin();
62 }
63
64 public function register_hooks() {
65 add_action( 'update_option_blogname', [ $this, 'sync_current_site_ignore_error' ] );
66 add_action( 'update_option_home', [ $this, 'sync_current_site_ignore_error' ] );
67 add_action( 'update_option_siteurl', [ $this, 'sync_current_site_ignore_error' ] );
68 add_action( 'update_option_timezone_string', [ $this, 'sync_current_site_ignore_error' ] );
69 add_action( 'matomo_setting_change_track_ecommerce', [ $this, 'sync_current_site_ignore_error' ] );
70 add_action( 'matomo_setting_change_site_currency', [ $this, 'sync_current_site_ignore_error' ] );
71 }
72
73 public function sync_current_site_ignore_error() {
74 if ( ! is_plugin_active( 'matomo/matomo.php' ) ) {
75 // @see https://github.com/matomo-org/matomo-for-wordpress/issues/577
76 return;
77 }
78 try {
79 $this->sync_current_site();
80 } catch ( Exception $e ) {
81 $this->logger->log( 'Ignoring site sync error: ' . $e->getMessage() );
82 $this->logger->log_exception( 'sync_site_ignore', $e );
83 }
84 }
85
86 public function sync_all() {
87 $succeed_all = true;
88
89 Bootstrap::do_bootstrap();
90
91 if ( is_multisite() && function_exists( 'get_sites' ) ) {
92 foreach ( get_sites() as $site ) {
93 if ( 1 === (int) $site->deleted ) {
94 continue;
95 }
96
97 /** @var WP_Site $site */
98 switch_to_blog( $site->blog_id );
99 try {
100 $installer = new Installer( $this->settings );
101 if ( ! $installer->looks_like_it_is_installed() ) {
102 $this->logger->log( sprintf( 'Matomo was not installed yet for blog: %s installing now.', $site->blog_id ) );
103
104 // prevents error that it wouldn't fully install matomo for a different site as it would think it already did install it etc.
105 // and would otherwise think plugins are already activated etc
106 Bootstrap::set_not_bootstrapped();
107 $config = Config::getInstance();
108 $installed = $config->PluginsInstalled;
109 $installed['PluginsInstalled'] = [];
110 $config->PluginsInstalled = $installed;
111
112 if ( $installer->can_be_installed() ) {
113 $installer->install();
114 } else {
115 continue;
116 }
117 }
118
119 $success = $this->sync_site( $site->blog_id, $site->blogname, $site->siteurl );
120 } catch ( Exception $e ) {
121 $success = false;
122 // we don't want to rethrow exception otherwise some other blogs might never sync
123 $this->logger->log( 'Matomo error syncing site: ' . $e->getMessage() );
124 }
125
126 $succeed_all = $succeed_all && $success;
127 restore_current_blog();
128 }
129 } else {
130 $success = $this->sync_current_site();
131 $succeed_all = $succeed_all && $success;
132 }
133
134 return $succeed_all;
135 }
136
137 public function sync_current_site() {
138 return $this->sync_site( get_current_blog_id(), get_bloginfo( 'name' ), get_bloginfo( 'url' ) );
139 }
140
141 public function sync_site( $blog_id, $blog_name, $blog_url ) {
142 Bootstrap::do_bootstrap();
143 $this->logger->log( 'Matomo is now syncing blogId ' . $blog_id );
144
145 $idsite = Site::get_matomo_site_id( $blog_id );
146
147 $sites_manager_model = new Model();
148
149 if ( ! is_multisite() ) {
150 // we should have here only one record in the matomo_site table then...
151 $matomo_id_sites = $sites_manager_model->getSitesId();
152 if ( count( $matomo_id_sites ) === 1 ) {
153 $matomo_id_site = (int) $matomo_id_sites[0];
154 if ( empty( $idsite ) ) {
155 // we have one record in the matomo_site table but the mapping does not exist. Force usage of the ID found.
156 $idsite = $matomo_id_site;
157 $this->logger->log( "Can't find the id site in the mapping, but there is already an existing site. Use its ID " . $idsite . ' for blog' );
158 wp_cache_flush();
159 add_site_option( Site::SITE_MAPPING_PREFIX . $blog_id, $idsite );
160 } elseif ( (int) $idsite !== $matomo_id_site ) {
161 // the mapped id in the WP config is different from the id in the matomo_site table: we force usage of the matomo table site ID
162 $idsite = $matomo_id_site;
163 $this->logger->log( 'The id site in the mapping is different from the id site in the matomo table. Force usage of Matomo ID ' . $idsite . ' for blog' );
164 Site::map_matomo_site_id( $blog_id, $idsite );
165 }
166 } elseif ( count( $matomo_id_sites ) > 1 ) {
167 // there are more than one record: we'll have to identify which one has data and which one must be removed from the matomo_site table
168 $this->logger->log( 'There is a problem in your configuration. Please contact support at wordpress@matomo.org' );
169 return false;
170 }
171 }
172
173 if ( empty( $blog_name ) ) {
174 $blog_name = esc_html__( 'Default', 'matomo' );
175 } else {
176 $blog_name = substr( $blog_name, 0, self::MAX_LENGTH_SITE_NAME );
177 }
178
179 $track_ecommerce = (int) $this->settings->get_global_option( 'track_ecommerce' );
180 $site_currency = $this->settings->get_global_option( Settings::SITE_CURRENCY );
181 $detected_timezone = $this->detect_timezone();
182
183 $data_provider = StaticContainer::get( CurrencyDataProvider::class );
184 $valid_currencies = $data_provider->getCurrencyList();
185 if ( ! array_key_exists( $site_currency, $valid_currencies ) ) {
186 $site_currency = 'USD';
187 }
188
189 if ( ! empty( $idsite ) ) {
190 $this->logger->log( 'Matomo site is known for blog (' . $idsite . ')... will update' );
191
192 $site = $sites_manager_model->getSiteFromId( $idsite );
193 if ( ! empty( $site ) ) {
194 // if site has changed then we have to update it
195 if ( $site['name'] !== $blog_name
196 || $site['main_url'] !== $blog_url
197 // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual
198 || $site['ecommerce'] != $track_ecommerce
199 || $site['currency'] !== $site_currency
200 || $site['timezone'] !== $detected_timezone ) {
201
202 /** @var WP_Site $site */
203 $params = [
204 'name' => $blog_name,
205 'main_url' => $blog_url,
206 'ecommerce' => $track_ecommerce,
207 'currency' => $site_currency,
208 'timezone' => $detected_timezone,
209 ];
210 $sites_manager_model->updateSite( $params, $idsite );
211
212 do_action( 'matomo_site_synced', $idsite, $blog_id );
213
214 // no actual setting changed but we make sure the tracking code will be updated after an update
215 $this->settings->apply_tracking_related_changes( [] );
216 }
217
218 $this->config_sync->sync_config_for_current_site();
219
220 return true;
221 }
222 }
223
224 $this->logger->log( 'Matomo site is not known for blog... will create site' );
225
226 /** @var WP_Site $site */
227 $idsite = null;
228
229 $this->set_enable_sites_admin( 1 );
230
231 Access::doAsSuperUser(
232 function () use ( $blog_name, $blog_url, $detected_timezone, $track_ecommerce, &$idsite, $site_currency ) {
233 SitesManager\API::unsetInstance();
234 // we need to unset the instance to make sure it fetches the
235 // up to date dependencies eg current plugin manager etc
236
237 $idsite = SitesManager\API::getInstance()->addSite(
238 $blog_name,
239 [ $blog_url ],
240 $track_ecommerce,
241 $site_search = null,
242 $search_keyword_parameters = null,
243 $search_category_parameters = null,
244 $excluded_ips = null,
245 $excluded_query_parameters = null,
246 $detected_timezone,
247 $site_currency
248 );
249 }
250 );
251 $this->set_enable_sites_admin( 0 );
252
253 $this->logger->log( 'Matomo created site with ID ' . $idsite . ' for blog' );
254
255 if ( ! is_numeric( $idsite ) || 0 === $idsite || '0' === $idsite ) {
256 $this->logger->log( sprintf( 'Creating the website failed: %s', wp_json_encode( $blog_id ) ) );
257
258 return false;
259 }
260
261 Site::map_matomo_site_id( $blog_id, $idsite );
262
263 $this->config_sync->sync_config_for_current_site();
264
265 do_action( 'matomo_site_synced', $idsite, $blog_id );
266
267 return true;
268 }
269
270 private function set_enable_sites_admin( $enabled ) {
271 $general = Config::getInstance()->General;
272 $general['enable_sites_admin'] = (int) $enabled;
273 Config::getInstance()->General = $general;
274 }
275
276 private function detect_timezone() {
277 $timezone = get_option( 'timezone_string' );
278
279 if ( $timezone && $this->check_and_try_to_set_default_timezone( $timezone ) ) {
280 return $timezone;
281 }
282
283 // older WordPress
284 $utc_offset = (int) get_option( 'gmt_offset', 0 );
285
286 if ( 0 === $utc_offset ) {
287 return 'UTC';
288 }
289
290 $utc_offset_in_seconds = $utc_offset * 3600;
291 $timezone = timezone_name_from_abbr( '', $utc_offset_in_seconds );
292
293 if ( $timezone && $this->check_and_try_to_set_default_timezone( $timezone ) ) {
294 return $timezone;
295 }
296
297 $dst = (bool) gmdate( 'I' );
298 foreach ( timezone_abbreviations_list() as $abbr ) {
299 foreach ( $abbr as $city ) {
300 if ( $dst === (bool) $city['dst']
301 && $city['timezone_id']
302 && (int) $city['offset'] === $utc_offset_in_seconds ) {
303 return $city['timezone_id'];
304 }
305 }
306 }
307
308 if ( is_numeric( $utc_offset ) ) {
309 if ( $utc_offset > 0 ) {
310 $timezone = 'UTC+' . $utc_offset;
311 } else {
312 $timezone = 'UTC' . $utc_offset;
313 }
314
315 if ( $this->check_and_try_to_set_default_timezone( $timezone ) ) {
316 return $timezone;
317 }
318 }
319
320 return 'UTC';
321 }
322
323 private function check_and_try_to_set_default_timezone( $timezone ) {
324 try {
325 Access::doAsSuperUser(
326 function () use ( $timezone ) {
327 // make sure we're loading the latest instance with all up to date dependencies... mainly needed for tests
328 SitesManager\API::unsetInstance();
329 SitesManager\API::getInstance()->setDefaultTimezone( $timezone );
330 }
331 );
332 } catch ( Exception $e ) {
333 return false;
334 }
335
336 return true;
337 }
338 }
339