PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.10.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.10.1
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 5 months ago Sync.php 2 months ago
Sync.php
343 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 } else {
161 if ( (int) $idsite !== $matomo_id_site ) {
162 // 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
163 $idsite = $matomo_id_site;
164 $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' );
165 Site::map_matomo_site_id( $blog_id, $idsite );
166 }
167 }
168 } else {
169 if ( count( $matomo_id_sites ) > 1 ) {
170 // 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
171 $this->logger->log( 'There is a problem in your configuration. Please contact support at wordpress@matomo.org' );
172 return false;
173 }
174 }
175 }
176
177 if ( empty( $blog_name ) ) {
178 $blog_name = esc_html__( 'Default', 'matomo' );
179 } else {
180 $blog_name = substr( $blog_name, 0, self::MAX_LENGTH_SITE_NAME );
181 }
182
183 $track_ecommerce = (int) $this->settings->get_global_option( 'track_ecommerce' );
184 $site_currency = $this->settings->get_global_option( Settings::SITE_CURRENCY );
185 $detected_timezone = $this->detect_timezone();
186
187 $data_provider = StaticContainer::get( CurrencyDataProvider::class );
188 $valid_currencies = $data_provider->getCurrencyList();
189 if ( ! array_key_exists( $site_currency, $valid_currencies ) ) {
190 $site_currency = 'USD';
191 }
192
193 if ( ! empty( $idsite ) ) {
194 $this->logger->log( 'Matomo site is known for blog (' . $idsite . ')... will update' );
195
196 $site = $sites_manager_model->getSiteFromId( $idsite );
197 if ( ! empty( $site ) ) {
198 // if site has changed then we have to update it
199 if ( $site['name'] !== $blog_name
200 || $site['main_url'] !== $blog_url
201 // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
202 || $site['ecommerce'] != $track_ecommerce
203 || $site['currency'] !== $site_currency
204 || $site['timezone'] !== $detected_timezone ) {
205
206 /** @var WP_Site $site */
207 $params = [
208 'name' => $blog_name,
209 'main_url' => $blog_url,
210 'ecommerce' => $track_ecommerce,
211 'currency' => $site_currency,
212 'timezone' => $detected_timezone,
213 ];
214 $sites_manager_model->updateSite( $params, $idsite );
215
216 do_action( 'matomo_site_synced', $idsite, $blog_id );
217
218 // no actual setting changed but we make sure the tracking code will be updated after an update
219 $this->settings->apply_tracking_related_changes( [] );
220 }
221
222 $this->config_sync->sync_config_for_current_site();
223
224 return true;
225 }
226 }
227
228 $this->logger->log( 'Matomo site is not known for blog... will create site' );
229
230 /** @var WP_Site $site */
231 $idsite = null;
232
233 $this->set_enable_sites_admin( 1 );
234
235 Access::doAsSuperUser(
236 function () use ( $blog_name, $blog_url, $detected_timezone, $track_ecommerce, &$idsite, $site_currency ) {
237 SitesManager\API::unsetInstance();
238 // we need to unset the instance to make sure it fetches the
239 // up to date dependencies eg current plugin manager etc
240
241 $idsite = SitesManager\API::getInstance()->addSite(
242 $blog_name,
243 [ $blog_url ],
244 $track_ecommerce,
245 $site_search = null,
246 $search_keyword_parameters = null,
247 $search_category_parameters = null,
248 $excluded_ips = null,
249 $excluded_query_parameters = null,
250 $detected_timezone,
251 $site_currency
252 );
253 }
254 );
255 $this->set_enable_sites_admin( 0 );
256
257 $this->logger->log( 'Matomo created site with ID ' . $idsite . ' for blog' );
258
259 if ( ! is_numeric( $idsite ) || 0 === $idsite || '0' === $idsite ) {
260 $this->logger->log( sprintf( 'Creating the website failed: %s', wp_json_encode( $blog_id ) ) );
261
262 return false;
263 }
264
265 Site::map_matomo_site_id( $blog_id, $idsite );
266
267 $this->config_sync->sync_config_for_current_site();
268
269 do_action( 'matomo_site_synced', $idsite, $blog_id );
270
271 return true;
272 }
273
274 private function set_enable_sites_admin( $enabled ) {
275 $general = Config::getInstance()->General;
276 $general['enable_sites_admin'] = (int) $enabled;
277 Config::getInstance()->General = $general;
278 }
279
280 private function detect_timezone() {
281 $timezone = get_option( 'timezone_string' );
282
283 if ( $timezone && $this->check_and_try_to_set_default_timezone( $timezone ) ) {
284 return $timezone;
285 }
286
287 // older WordPress
288 $utc_offset = (int) get_option( 'gmt_offset', 0 );
289
290 if ( 0 === $utc_offset ) {
291 return 'UTC';
292 }
293
294 $utc_offset_in_seconds = $utc_offset * 3600;
295 $timezone = timezone_name_from_abbr( '', $utc_offset_in_seconds );
296
297 if ( $timezone && $this->check_and_try_to_set_default_timezone( $timezone ) ) {
298 return $timezone;
299 }
300
301 $dst = (bool) gmdate( 'I' );
302 foreach ( timezone_abbreviations_list() as $abbr ) {
303 foreach ( $abbr as $city ) {
304 if ( $dst === (bool) $city['dst']
305 && $city['timezone_id']
306 && (int) $city['offset'] === $utc_offset_in_seconds ) {
307 return $city['timezone_id'];
308 }
309 }
310 }
311
312 if ( is_numeric( $utc_offset ) ) {
313 if ( $utc_offset > 0 ) {
314 $timezone = 'UTC+' . $utc_offset;
315 } else {
316 $timezone = 'UTC' . $utc_offset;
317 }
318
319 if ( $this->check_and_try_to_set_default_timezone( $timezone ) ) {
320 return $timezone;
321 }
322 }
323
324 return 'UTC';
325 }
326
327 private function check_and_try_to_set_default_timezone( $timezone ) {
328 try {
329 Access::doAsSuperUser(
330 function () use ( $timezone ) {
331 // make sure we're loading the latest instance with all up to date dependencies... mainly needed for tests
332 SitesManager\API::unsetInstance();
333 SitesManager\API::getInstance()->setDefaultTimezone( $timezone );
334 }
335 );
336 } catch ( Exception $e ) {
337 return false;
338 }
339
340 return true;
341 }
342 }
343