PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.14.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.14.2
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 / ScheduledTasks.php
matomo / classes / WpMatomo Last commit date
Admin 3 years ago Commands 4 years ago Db 4 years ago Ecommerce 3 years ago Report 4 years ago Site 3 years ago TrackingCode 4 years ago Updater 4 years ago User 3 years ago WpStatistics 4 years ago views 4 years ago API.php 4 years ago Access.php 4 years ago AjaxTracker.php 5 years ago Annotations.php 4 years ago Bootstrap.php 4 years ago Capabilities.php 4 years ago Compatibility.php 4 years ago Email.php 4 years ago Installer.php 4 years ago Logger.php 4 years ago OptOut.php 4 years ago Paths.php 4 years ago PrivacyBadge.php 4 years ago RedirectOnActivation.php 4 years ago Referral.php 4 years ago Roles.php 4 years ago ScheduledTasks.php 4 years ago Settings.php 4 years ago Site.php 3 years ago TrackingCode.php 4 years ago Uninstaller.php 4 years ago Updater.php 4 years ago User.php 4 years ago
ScheduledTasks.php
376 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;
11
12 use Exception;
13 use Piwik\CronArchive;
14 use Piwik\Filesystem;
15 use Piwik\Option;
16 use Piwik\Plugin\Manager;
17 use Piwik\Plugins\GeoIp2\GeoIP2AutoUpdater;
18 use Piwik\Plugins\GeoIp2\LocationProvider\GeoIp2;
19 use Piwik\Plugins\GeoIp2\LocationProvider\GeoIp2\Php;
20 use Piwik\Plugins\UserCountry\LocationProvider;
21 use WpMatomo\Site\Sync as SiteSync;
22 use WpMatomo\User\Sync as UserSync;
23 use WpMatomo\Paths;
24
25 if ( ! defined( 'ABSPATH' ) ) {
26 exit; // if accessed directly
27 }
28
29 class ScheduledTasks {
30 const EVENT_SYNC = 'matomo_scheduled_sync';
31 const EVENT_DISABLE_ADDHANDLER = 'matomo_scheduled_disable_addhandler';
32 const EVENT_ARCHIVE = 'matomo_scheduled_archive';
33 const EVENT_GEOIP = 'matomo_scheduled_geoipdb';
34 const EVENT_UPDATE = 'matomo_update_core';
35
36 const KEY_BEFORE_CRON = 'before-cron-';
37 const KEY_AFTER_CRON = 'after-cron-';
38
39 /**
40 * @var Settings
41 */
42 private $settings;
43
44 /**
45 * @var Logger
46 */
47 private $logger;
48
49 public function __construct( Settings $settings ) {
50 $this->settings = $settings;
51 $this->logger = new Logger();
52 }
53
54 public function add_weekly_schedule( $schedules ) {
55 $schedules['matomo_monthly'] = [
56 'interval' => 60 * 60 * 24 * 30,
57 'display' => __( 'Monthly', 'matomo' ),
58 ];
59
60 return $schedules;
61 }
62
63 public function schedule() {
64 add_action( self::EVENT_UPDATE, [ $this, 'perform_update' ] );
65 add_filter( 'cron_schedules', [ $this, 'add_weekly_schedule' ] );
66
67 $self = $this;
68 $event_priority = 10;
69
70 $installer = new Installer( $this->settings );
71 $looks_installed = $installer->looks_like_it_is_installed(); // we only schedule events when Matomo looks installed but we still listen to the actions in case the app triggers a one time update.
72
73 foreach ( $this->get_all_events() as $event_name => $event_config ) {
74 if ( $looks_installed && ! wp_next_scheduled( $event_name ) ) {
75 wp_schedule_event( time(), $event_config['interval'], $event_name );
76 }
77
78 // logging last execution start time
79 add_action(
80 $event_name,
81 function () use ( $self, $event_name ) {
82 $self->set_last_time_before_cron( $event_name, time() );
83 },
84 $event_priority / 2,
85 $accepted_args = 0
86 );
87
88 // actual event
89 add_action( $event_name, [ $this, $event_config['method'] ], $event_priority, $accepted_args = 0 );
90
91 // logging last execution end time
92 add_action(
93 $event_name,
94 function () use ( $self, $event_name ) {
95 $self->set_last_time_after_cron( $event_name, time() );
96 },
97 $event_priority * 2,
98 $accepted_args = 0
99 );
100 }
101
102 register_deactivation_hook( MATOMO_ANALYTICS_FILE, [ $this, 'uninstall' ] );
103 }
104
105 public function get_last_time_before_cron( $event_name ) {
106 return get_option( Settings::OPTION_PREFIX . self::KEY_BEFORE_CRON . $event_name );
107 }
108
109 public function set_last_time_before_cron( $event_name, $time ) {
110 // we use settings prefix so data automatically gets removed when uninstalling
111 update_option( Settings::OPTION_PREFIX . self::KEY_BEFORE_CRON . $event_name, $time );
112 }
113
114 public function get_last_time_after_cron( $event_name ) {
115 return get_option( Settings::OPTION_PREFIX . self::KEY_AFTER_CRON . $event_name );
116 }
117
118 public function set_last_time_after_cron( $event_name, $time ) {
119 // we use settings prefix so data automatically gets removed when uninstalling
120 update_option( Settings::OPTION_PREFIX . self::KEY_AFTER_CRON . $event_name, $time );
121 }
122
123 public function get_all_events() {
124 $events = [
125 self::EVENT_SYNC => [
126 'name' => 'Sync users & sites',
127 'interval' => 'daily',
128 'method' => 'sync',
129 ],
130 self::EVENT_ARCHIVE => [
131 'name' => 'Archive',
132 'interval' => 'hourly',
133 'method' => 'archive',
134 ],
135 self::EVENT_GEOIP => [
136 'name' => 'Update GeoIP DB',
137 'interval' => 'matomo_monthly',
138 'method' => 'update_geo_ip2_db',
139 ],
140 ];
141 if ( $this->settings->should_disable_addhandler() ) {
142 $events[ self::EVENT_DISABLE_ADDHANDLER ] = [
143 'name' => 'Disable AddHandler',
144 'interval' => 'hourly',
145 'method' => 'disable_add_handler',
146 ];
147 }
148
149 return $events;
150 }
151
152 public function disable_add_handler( $force_undo = false ) {
153 $disable_addhandler = $this->settings->should_disable_addhandler();
154 if ( $disable_addhandler ) {
155 $this->logger->log( 'Scheduled tasks disabling addhandler' );
156 try {
157 Bootstrap::do_bootstrap();
158
159 $files = Filesystem::globr( dirname( MATOMO_ANALYTICS_FILE ), '.htaccess' );
160 foreach ( $files as $file ) {
161 if ( is_readable( $file ) ) {
162 // we don't need to access remote files
163 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
164 $content = file_get_contents( $file );
165 $search = 'AddHandler';
166 $replace = '# AddHandler';
167 if ( $force_undo ) {
168 $search = '# AddHandler';
169 $replace = 'AddHandler';
170 }
171 if ( strpos( $content, $search ) !== false && ( $force_undo || strpos( $content, $replace ) === false ) ) {
172 if ( is_writeable( $file ) ) {
173 $content = str_replace( $search, $replace, $content );
174 $paths = new Paths();
175 $wp_filesystem = $paths->get_file_system();
176 $wp_filesystem->put_contents( $file, $content );
177 } else {
178 $this->logger->log( 'Cannot update file as not writable ' . $file );
179 }
180 }
181 }
182 }
183 } catch ( Exception $e ) {
184 $this->logger->log_exception( 'disable_addhandler', $e );
185 throw $e;
186 }
187 }
188 }
189
190 private function check_try_update() {
191 try {
192 $installer = new Installer( $this->settings );
193 if ( $installer->looks_like_it_is_installed() ) {
194 $updater = new Updater( $this->settings );
195 $updater->update_if_needed();
196 }
197 } catch ( Exception $e ) {
198 // we don't want to rethrow exception otherwise some other blogs might never sync
199 $this->logger->log_exception( 'check_try_update', $e );
200 }
201 }
202
203 public function perform_update() {
204 $this->logger->log( 'Scheduled tasks perform update' );
205
206 try {
207 $updater = new Updater( $this->settings );
208 $updater->update();
209 } catch ( Exception $e ) {
210 $this->logger->log_exception( 'cron_update', $e );
211 throw $e;
212 }
213 }
214
215 public function update_geo_ip2_db() {
216 $this->logger->log( 'Scheduled tasks update geoip database' );
217 try {
218 Bootstrap::do_bootstrap();
219
220 $maxmind_license = $this->settings->get_global_option( 'maxmind_license_key' );
221 if ( empty( $maxmind_license ) ) {
222 $db_url = GeoIp2::getDbIpLiteUrl();
223 $asn_url = GeoIp2::getDbIpLiteUrl( 'asn' );
224 } else {
225 $db_url = 'https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&suffix=tar.gz&license_key=' . $maxmind_license;
226 $asn_url = 'https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-ASN&suffix=tar.gz&license_key=' . $maxmind_license;
227 }
228
229 Option::set( GeoIP2AutoUpdater::LOC_URL_OPTION_NAME, $db_url );
230
231 if ( Manager::getInstance()->isPluginActivated( 'Provider' ) ) {
232 Option::set( GeoIP2AutoUpdater::ISP_URL_OPTION_NAME, $asn_url );
233 } else {
234 Option::delete( GeoIP2AutoUpdater::ISP_URL_OPTION_NAME );
235 }
236
237 $updater = new GeoIP2AutoUpdater();
238 $updater->update();
239 if ( LocationProvider::getCurrentProviderId() !== Php::ID && LocationProvider::getProviderById( Php::ID ) ) {
240 LocationProvider::setCurrentProvider( Php::ID );
241 }
242 } catch ( Exception $e ) {
243 $this->logger->log_exception( 'update_geoip2', $e );
244 throw $e;
245 }
246 }
247
248 public function sync() {
249 $this->check_try_update();
250
251 $this->logger->log( 'Scheduled tasks sync all sites and users' );
252
253 try {
254 // we update the matomo url if needed/when possible. eg an update may be needed when site_url changes
255 $installer = new Installer( $this->settings );
256 if ( $installer->looks_like_it_is_installed() ) {
257 Bootstrap::do_bootstrap();
258 $installer->set_matomo_url();
259 }
260 } catch ( Exception $e ) {
261 $this->logger->log_exception( 'matomo_url_sync', $e );
262 }
263
264 try {
265 $site = new SiteSync( $this->settings );
266 $site->sync_all();
267 $user = new UserSync();
268 $user->sync_all();
269 } catch ( Exception $e ) {
270 $this->logger->log_exception( 'cron_sync', $e );
271 throw $e;
272 }
273 }
274
275 public function archive( $force = false, $throw_exception = true ) {
276 $this->check_try_update();
277
278 if ( defined( 'MATOMO_DISABLE_WP_ARCHIVING' ) && MATOMO_DISABLE_WP_ARCHIVING ) {
279 return;
280 }
281
282 // we don't want any error triggered when a user vistis the website
283 // that's because cron might be triggered during a regular request from a regular user (unless WP CRON is disabled and triggered manually)
284 $should_rethrow_exception = is_admin() || ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) || ( defined( 'MATOMO_PHPUNIT_TEST' ) && MATOMO_PHPUNIT_TEST );
285
286 $this->logger->log( 'Scheduled tasks archive data' );
287
288 try {
289 Bootstrap::do_bootstrap();
290 } catch ( Exception $e ) {
291 $this->logger->log_exception( 'archive_bootstrap', $e );
292 if ( $should_rethrow_exception || $force ) {
293 // we want to trigger an exception if it was forced from the UI
294 throw $e;
295 }
296 }
297
298 $archiver = new CronArchive();
299 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
300 $archiver->concurrentRequestsPerWebsite = 1;
301 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
302 $archiver->maxConcurrentArchivers = 1;
303
304 if ( $force ) {
305 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
306 $archiver->shouldArchiveAllSites = true;
307 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
308 $archiver->disableScheduledTasks = true;
309 // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
310 if ( ! defined( 'PIWIK_ARCHIVE_NO_TRUNCATE' ) ) {
311 define( 'PIWIK_ARCHIVE_NO_TRUNCATE', true );
312 }
313 }
314
315 if ( is_multisite() ) {
316 if ( is_network_admin() ) {
317 return; // nothing to archive
318 } else {
319 $blog_id = get_current_blog_id();
320 $idsite = Site::get_matomo_site_id( $blog_id );
321 if ( ! empty( $idsite ) ) {
322 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
323 $archiver->shouldArchiveSpecifiedSites = [ $idsite ];
324 } else {
325 // there is no site mapped to it so there's no point in archiving it
326 return;
327 }
328 }
329 }
330
331 try {
332 $archiver->main();
333
334 $archive_errors = $archiver->getErrors();
335 } catch ( Exception $e ) {
336 $this->logger->log_exception( 'archive_main', $e );
337 $archive_errors = $archiver->getErrors();
338
339 if ( ! empty( $archive_errors ) ) {
340 $message = '';
341 foreach ( $archiver->getErrors() as $error ) {
342 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
343 $message .= var_export( $error, 1 ) . ' ';
344 }
345 $message = new Exception( trim( $message ) );
346 $this->logger->log_exception( 'archive_errors', $message );
347 }
348
349 if ( $throw_exception ) {
350 if ( $should_rethrow_exception ) {
351 throw $e;
352 }
353 // we otherwise only log the error but don't throw an exception
354 } else {
355 $archive_errors[] = $e->getMessage();
356 }
357 }
358
359 return $archive_errors;
360 }
361
362 public function uninstall() {
363 $this->logger->log( 'Scheduled tasks uninstall all events' );
364
365 foreach ( $this->get_all_events() as $event_name => $config ) {
366 $timestamp = wp_next_scheduled( $event_name );
367 if ( $timestamp ) {
368 wp_unschedule_event( $timestamp, $event_name );
369 }
370 }
371 foreach ( $this->get_all_events() as $event_name => $config ) {
372 wp_clear_scheduled_hook( $event_name );
373 }
374 }
375 }
376