PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.0.7
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.0.7
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 4 years ago Sync.php 2 years ago
Sync.php
338 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\Installer;
22 use WpMatomo\Logger;
23 use WpMatomo\Settings;
24 use WpMatomo\Site;
25 use WpMatomo\Site\Sync\SyncConfig;
26
27 if ( ! defined( 'ABSPATH' ) ) {
28 exit; // if accessed directly
29 }
30
31 /**
32 * Properties coming from matomo
33 * phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
34 */
35 class Sync {
36 const MAX_LENGTH_SITE_NAME = 90;
37
38 /**
39 * @var Logger
40 */
41 private $logger;
42
43 /**
44 * @var Settings
45 */
46 private $settings;
47
48 /**
49 * @var SyncConfig
50 */
51 private $config_sync;
52
53 public function __construct( Settings $settings ) {
54 $this->logger = new Logger();
55 $this->settings = $settings;
56 $this->config_sync = new SyncConfig( $settings );
57 }
58
59 public function register_hooks() {
60 add_action( 'update_option_blogname', [ $this, 'sync_current_site_ignore_error' ] );
61 add_action( 'update_option_home', [ $this, 'sync_current_site_ignore_error' ] );
62 add_action( 'update_option_siteurl', [ $this, 'sync_current_site_ignore_error' ] );
63 add_action( 'update_option_timezone_string', [ $this, 'sync_current_site_ignore_error' ] );
64 add_action( 'matomo_setting_change_track_ecommerce', [ $this, 'sync_current_site_ignore_error' ] );
65 add_action( 'matomo_setting_change_site_currency', [ $this, 'sync_current_site_ignore_error' ] );
66 }
67
68 public function sync_current_site_ignore_error() {
69 if ( ! is_plugin_active( 'matomo/matomo.php' ) ) {
70 // @see https://github.com/matomo-org/matomo-for-wordpress/issues/577
71 return;
72 }
73 try {
74 $this->sync_current_site();
75 } catch ( Exception $e ) {
76 $this->logger->log( 'Ignoring site sync error: ' . $e->getMessage() );
77 $this->logger->log_exception( 'sync_site_ignore', $e );
78 }
79 }
80
81 public function sync_all() {
82 $succeed_all = true;
83
84 Bootstrap::do_bootstrap();
85
86 if ( is_multisite() && function_exists( 'get_sites' ) ) {
87 foreach ( get_sites() as $site ) {
88 if ( 1 === (int) $site->deleted ) {
89 continue;
90 }
91
92 /** @var WP_Site $site */
93 switch_to_blog( $site->blog_id );
94 try {
95 $installer = new Installer( $this->settings );
96 if ( ! $installer->looks_like_it_is_installed() ) {
97 $this->logger->log( sprintf( 'Matomo was not installed yet for blog: %s installing now.', $site->blog_id ) );
98
99 // prevents error that it wouldn't fully install matomo for a different site as it would think it already did install it etc.
100 // and would otherwise think plugins are already activated etc
101 Bootstrap::set_not_bootstrapped();
102 $config = Config::getInstance();
103 $installed = $config->PluginsInstalled;
104 $installed['PluginsInstalled'] = [];
105 $config->PluginsInstalled = $installed;
106
107 if ( $installer->can_be_installed() ) {
108 $installer->install();
109 } else {
110 continue;
111 }
112 }
113
114 $success = $this->sync_site( $site->blog_id, $site->blogname, $site->siteurl );
115 } catch ( Exception $e ) {
116 $success = false;
117 // we don't want to rethrow exception otherwise some other blogs might never sync
118 $this->logger->log( 'Matomo error syncing site: ' . $e->getMessage() );
119 }
120
121 $succeed_all = $succeed_all && $success;
122 restore_current_blog();
123 }
124 } else {
125 $success = $this->sync_current_site();
126 $succeed_all = $succeed_all && $success;
127 }
128
129 return $succeed_all;
130 }
131
132 public function sync_current_site() {
133 return $this->sync_site( get_current_blog_id(), get_bloginfo( 'name' ), get_bloginfo( 'url' ) );
134 }
135
136 public function sync_site( $blog_id, $blog_name, $blog_url ) {
137 Bootstrap::do_bootstrap();
138 $this->logger->log( 'Matomo is now syncing blogId ' . $blog_id );
139
140 $idsite = Site::get_matomo_site_id( $blog_id );
141
142 $sites_manager_model = new Model();
143
144 if ( ! is_multisite() ) {
145 // we should have here only one record in the matomo_site table then...
146 $matomo_id_sites = $sites_manager_model->getSitesId();
147 if ( count( $matomo_id_sites ) === 1 ) {
148 $matomo_id_site = (int) $matomo_id_sites[0];
149 if ( empty( $idsite ) ) {
150 // we have one record in the matomo_site table but the mapping does not exist. Force usage of the ID found.
151 $idsite = $matomo_id_site;
152 $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' );
153 wp_cache_flush();
154 add_site_option( Site::SITE_MAPPING_PREFIX . $blog_id, $idsite );
155 } else {
156 if ( (int) $idsite !== $matomo_id_site ) {
157 // 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
158 $idsite = $matomo_id_site;
159 $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' );
160 Site::map_matomo_site_id( $blog_id, $idsite );
161 }
162 }
163 } else {
164 if ( count( $matomo_id_sites ) > 1 ) {
165 // 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
166 $this->logger->log( 'There is a problem in your configuration. Please contact support at wordpress@matomo.org' );
167 return false;
168 }
169 }
170 }
171
172 if ( empty( $blog_name ) ) {
173 $blog_name = esc_html__( 'Default', 'matomo' );
174 } else {
175 $blog_name = substr( $blog_name, 0, self::MAX_LENGTH_SITE_NAME );
176 }
177
178 $track_ecommerce = (int) $this->settings->get_global_option( 'track_ecommerce' );
179 $site_currency = $this->settings->get_global_option( Settings::SITE_CURRENCY );
180 $detected_timezone = $this->detect_timezone();
181
182 $data_provider = StaticContainer::get( CurrencyDataProvider::class );
183 $valid_currencies = $data_provider->getCurrencyList();
184 if ( ! array_key_exists( $site_currency, $valid_currencies ) ) {
185 $site_currency = 'USD';
186 }
187
188 if ( ! empty( $idsite ) ) {
189 $this->logger->log( 'Matomo site is known for blog (' . $idsite . ')... will update' );
190
191 $site = $sites_manager_model->getSiteFromId( $idsite );
192 if ( ! empty( $site ) ) {
193 // if site has changed then we have to update it
194 if ( $site['name'] !== $blog_name
195 || $site['main_url'] !== $blog_url
196 // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
197 || $site['ecommerce'] != $track_ecommerce
198 || $site['currency'] !== $site_currency
199 || $site['timezone'] !== $detected_timezone ) {
200
201 /** @var WP_Site $site */
202 $params = [
203 'name' => $blog_name,
204 'main_url' => $blog_url,
205 'ecommerce' => $track_ecommerce,
206 'currency' => $site_currency,
207 'timezone' => $detected_timezone,
208 ];
209 $sites_manager_model->updateSite( $params, $idsite );
210
211 do_action( 'matomo_site_synced', $idsite, $blog_id );
212
213 // no actual setting changed but we make sure the tracking code will be updated after an update
214 $this->settings->apply_tracking_related_changes( [] );
215 }
216
217 $this->config_sync->sync_config_for_current_site();
218
219 return true;
220 }
221 }
222
223 $this->logger->log( 'Matomo site is not known for blog... will create site' );
224
225 /** @var WP_Site $site */
226 $idsite = null;
227
228 $this->set_enable_sites_admin( 1 );
229
230 Access::doAsSuperUser(
231 function () use ( $blog_name, $blog_url, $detected_timezone, $track_ecommerce, &$idsite, $site_currency ) {
232 SitesManager\API::unsetInstance();
233 // we need to unset the instance to make sure it fetches the
234 // up to date dependencies eg current plugin manager etc
235
236 $idsite = SitesManager\API::getInstance()->addSite(
237 $blog_name,
238 [ $blog_url ],
239 $track_ecommerce,
240 $site_search = null,
241 $search_keyword_parameters = null,
242 $search_category_parameters = null,
243 $excluded_ips = null,
244 $excluded_query_parameters = null,
245 $detected_timezone,
246 $site_currency
247 );
248 }
249 );
250 $this->set_enable_sites_admin( 0 );
251
252 $this->logger->log( 'Matomo created site with ID ' . $idsite . ' for blog' );
253
254 if ( ! is_numeric( $idsite ) || 0 === $idsite || '0' === $idsite ) {
255 $this->logger->log( sprintf( 'Creating the website failed: %s', wp_json_encode( $blog_id ) ) );
256
257 return false;
258 }
259
260 Site::map_matomo_site_id( $blog_id, $idsite );
261
262 $this->config_sync->sync_config_for_current_site();
263
264 do_action( 'matomo_site_synced', $idsite, $blog_id );
265
266 return true;
267 }
268
269 private function set_enable_sites_admin( $enabled ) {
270 $general = Config::getInstance()->General;
271 $general['enable_sites_admin'] = (int) $enabled;
272 Config::getInstance()->General = $general;
273 }
274
275 private function detect_timezone() {
276 $timezone = get_option( 'timezone_string' );
277
278 if ( $timezone && $this->check_and_try_to_set_default_timezone( $timezone ) ) {
279 return $timezone;
280 }
281
282 // older WordPress
283 $utc_offset = (int) get_option( 'gmt_offset', 0 );
284
285 if ( 0 === $utc_offset ) {
286 return 'UTC';
287 }
288
289 $utc_offset_in_seconds = $utc_offset * 3600;
290 $timezone = timezone_name_from_abbr( '', $utc_offset_in_seconds );
291
292 if ( $timezone && $this->check_and_try_to_set_default_timezone( $timezone ) ) {
293 return $timezone;
294 }
295
296 $dst = (bool) gmdate( 'I' );
297 foreach ( timezone_abbreviations_list() as $abbr ) {
298 foreach ( $abbr as $city ) {
299 if ( $dst === (bool) $city['dst']
300 && $city['timezone_id']
301 && (int) $city['offset'] === $utc_offset_in_seconds ) {
302 return $city['timezone_id'];
303 }
304 }
305 }
306
307 if ( is_numeric( $utc_offset ) ) {
308 if ( $utc_offset > 0 ) {
309 $timezone = 'UTC+' . $utc_offset;
310 } else {
311 $timezone = 'UTC' . $utc_offset;
312 }
313
314 if ( $this->check_and_try_to_set_default_timezone( $timezone ) ) {
315 return $timezone;
316 }
317 }
318
319 return 'UTC';
320 }
321
322 private function check_and_try_to_set_default_timezone( $timezone ) {
323 try {
324 Access::doAsSuperUser(
325 function () use ( $timezone ) {
326 // make sure we're loading the latest instance with all up to date dependencies... mainly needed for tests
327 SitesManager\API::unsetInstance();
328 SitesManager\API::getInstance()->setDefaultTimezone( $timezone );
329 }
330 );
331 } catch ( Exception $e ) {
332 return false;
333 }
334
335 return true;
336 }
337 }
338