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