PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.8.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.8.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 / Updater.php
matomo / classes / WpMatomo Last commit date
Admin 2 months ago Commands 2 years ago Db 1 year ago Ecommerce 3 months ago Report 2 months ago Site 2 months ago TrackingCode 4 months ago Updater 4 years ago User 2 months ago Workarounds 2 years ago WpStatistics 5 months ago views 4 years ago AIBotTracking.php 4 months ago API.php 2 months ago Access.php 4 years ago AjaxTracker.php 4 months ago Annotations.php 2 months ago Bootstrap.php 10 months ago Capabilities.php 2 months ago Compatibility.php 2 months ago Email.php 2 years ago ErrorNotice.php 2 months ago Feature.php 2 months ago Installer.php 5 months ago Logger.php 1 year ago OptOut.php 2 months ago Paths.php 5 months ago PluginActionLinks.php 2 months ago PluginAdminOverrides.php 2 months ago PluginInit.php 2 months ago PrivacyBadge.php 4 years ago RedirectOnActivation.php 2 months ago Referral.php 2 months ago Roles.php 2 months ago ScheduledTasks.php 2 months ago Settings.php 4 months ago Site.php 3 years ago TrackingCode.php 2 months ago Uninstaller.php 5 months ago Updater.php 10 months ago User.php 4 years ago
Updater.php
327 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\Cache;
14 use Piwik\Common;
15 use Piwik\Config;
16 use Piwik\Db;
17 use Piwik\Filesystem;
18 use Piwik\Option;
19 use Piwik\Plugins\Installation\ServerFilesGenerator;
20 use Piwik\SettingsServer;
21 use Piwik\Version;
22 use WP_Upgrader;
23 use WpMatomo\TrackingCode\GeneratorOptions;
24 use WpMatomo\TrackingCode\TrackingCodeGenerator;
25 use WpMatomo\Updater\UpdateInProgressException;
26
27 if ( ! defined( 'ABSPATH' ) ) {
28 exit; // if accessed directly
29 }
30
31 class Updater {
32 const LOCK_NAME = 'matomo_updater';
33
34 /**
35 * @var Settings
36 */
37 private $settings;
38
39 /**
40 * @var Logger
41 */
42 private $logger;
43
44 public function __construct( Settings $settings ) {
45 $this->settings = $settings;
46 $this->logger = new Logger();
47 }
48
49 public function load_plugin_functions() {
50 if ( ! function_exists( 'get_plugin_data' ) ) {
51 require_once ABSPATH . '/wp-admin/includes/plugin.php';
52 }
53
54 return function_exists( 'get_plugin_data' );
55 }
56
57 public function get_plugins_requiring_update() {
58 if ( ! $this->load_plugin_functions() ) {
59 return [];
60 }
61
62 $keys = [];
63 $plugin_files = $GLOBALS['MATOMO_PLUGIN_FILES'];
64 if ( ! in_array( MATOMO_ANALYTICS_FILE, $plugin_files, true ) ) {
65 array_unshift( $plugin_files, MATOMO_ANALYTICS_FILE );
66 // making sure this plugin is in the list so when itself gets updated
67 // it will execute the core updates
68 }
69
70 foreach ( $plugin_files as $plugin_file ) {
71 $plugin_data = get_plugin_data( $plugin_file, $markup = false, $translate = false );
72
73 $key = Settings::OPTION_PREFIX . 'plugin-version-' . basename( str_ireplace( '.php', '', $plugin_file ) );
74 $installed_ver = get_option( $key );
75 if ( ! $installed_ver || $installed_ver !== $plugin_data['Version'] ) {
76 if ( ! Installer::is_intalled() ) {
77 return [];
78 }
79 $keys[ $key ] = $plugin_data['Version'];
80 }
81 }
82
83 return $keys;
84 }
85
86 public function update_if_needed() {
87 $plugins_requiring_update = $this->get_plugins_requiring_update();
88 if ( ! empty( $plugins_requiring_update ) ) {
89 try {
90 $this->update();
91 } catch ( UpdateInProgressException $e ) {
92 $this->logger->log( 'Matomo update is already in progress' );
93
94 return; // we also don't execute any further update as they should be executed in another process
95 } catch ( Exception $e ) {
96 $this->logger->log_exception( 'plugin_update', $e );
97 return;
98 }
99
100 // we're scheduling another update in case there are some dimensions to be updated or anything
101 // we do not do this in the "update" method as otherwise we might be calling this recursively...
102 // it is possible that because the plugins need to be reloaded etc that those updates are not executed right
103 // away but need an actual reload and cache clearance etc
104 wp_schedule_single_event( time() + 15, ScheduledTasks::EVENT_UPDATE );
105
106 // we make sure to delete cache even if no component was updated eg there may be translation updates etc
107 // and caches need to be invalidated
108 Filesystem::deleteAllCacheOnUpdate();
109
110 $tracking_code_generator = new TrackingCodeGenerator( $this->settings, new GeneratorOptions( $this->settings ) );
111 $tracking_code_generator->update_tracking_code( true );
112 }
113
114 foreach ( $plugins_requiring_update as $key => $plugin_version ) {
115 update_option( $key, $plugin_version );
116 }
117
118 return array_keys( $plugins_requiring_update );
119 }
120
121 public function update( $update_from_version = null ) {
122 Bootstrap::do_bootstrap();
123
124 if ( $this->load_plugin_functions() ) {
125 $plugin_data = get_plugin_data( MATOMO_ANALYTICS_FILE, $markup = false, $translate = false );
126
127 $history = $this->settings->get_global_option( 'version_history' );
128 if ( empty( $history ) || ! is_array( $history ) ) {
129 $history = [];
130 }
131
132 if ( ! empty( $plugin_data['Version'] )
133 && ! in_array( $plugin_data['Version'], $history, true ) ) {
134 // this allows us to see which versions of matomo the user was using before this update so we better understand
135 // which version maybe regressed something
136 array_unshift( $history, $plugin_data['Version'] );
137 $history = array_slice( $history, 0, 5 ); // lets keep only the last 5 versions
138 $this->settings->set_global_option( 'version_history', $history );
139 }
140 }
141
142 $this->settings->set_global_option( 'core_version', Version::VERSION );
143 $this->settings->save();
144
145 $paths = new Paths();
146 $paths->clear_cache_dir();
147
148 Option::clearCache();
149 try {
150 Cache::flushAll();
151 } catch ( \Exception $ex ) {
152 if ( ! Installer::is_file_not_exists_failure( $ex ) ) { // ignore errors that involve a directory not existing
153 throw $ex;
154 }
155 }
156
157 $current_version = Option::get( 'version_core' );
158
159 try {
160 if ( ! empty( $update_from_version ) ) {
161 $update_from_version = trim( $update_from_version );
162
163 if ( ! preg_match( '/^\d+\.\d+.\d+/', $update_from_version ) ) {
164 throw new \Exception( __( 'Invalid version. Please specify a full version identifier like "5.0.0".', 'matomo' ) );
165 }
166
167 if ( version_compare( $update_from_version, Version::VERSION, '>' ) ) {
168 throw new \Exception( __( 'Invalid version. The given version is greater than the current Matomo version.', 'matomo' ) );
169 }
170
171 Option::set( 'version_core', $update_from_version );
172
173 $installed_plugins = Config::getInstance()->PluginsInstalled['PluginsInstalled'];
174 foreach ( $installed_plugins as $plugin ) {
175 Option::set( 'version_' . $plugin, $update_from_version );
176 }
177 }
178
179 \Piwik\Access::doAsSuperUser(
180 function () {
181 self::update_components();
182 self::update_components();
183 }
184 );
185 } catch ( \Exception $ex ) {
186 Option::set( 'version_core', $current_version );
187 throw $ex;
188 }
189
190 $upload_dir = $paths->get_upload_base_dir();
191
192 $wp_filesystem = $paths->get_file_system();
193 if ( $paths->is_upload_dir_writable() ) {
194 $wp_filesystem->put_contents( $upload_dir . '/index.php', '//hello' );
195 $wp_filesystem->put_contents( $upload_dir . '/index.html', '//hello' );
196 $wp_filesystem->put_contents( $upload_dir . '/index.htm', '//hello' );
197 $wp_filesystem->put_contents(
198 $upload_dir . '/.htaccess',
199 '<Files ~ "(\.mmdb)$">
200 ' . ServerFilesGenerator::getDenyHtaccessContent() . '
201 </Files>
202 <Files ~ "(\.js)$">
203 ' . ServerFilesGenerator::getAllowHtaccessContent() . '
204 </Files>'
205 );
206 }
207 $config_dir = $paths->get_config_ini_path();
208 if ( $paths->is_upload_dir_writable() ) {
209 $wp_filesystem->put_contents( $config_dir . '/index.php', '//hello' );
210 $wp_filesystem->put_contents( $config_dir . '/index.html', '//hello' );
211 $wp_filesystem->put_contents( $config_dir . '/index.htm', '//hello' );
212 }
213
214 if ( $this->settings->should_disable_addhandler() ) {
215 wp_schedule_single_event( time() + 10, ScheduledTasks::EVENT_DISABLE_ADDHANDLER );
216 }
217
218 do_action( 'matomo_update' );
219 }
220
221 public function is_upgrade_in_progress() {
222 if ( ! self::load_upgrader() ) {
223 return 'no upgrader';
224 }
225
226 if ( self::lock() ) {
227 // we can get the lock meaning no update is in progress
228 self::unlock();
229
230 return false;
231 }
232
233 return true;
234 }
235
236 private static function load_upgrader() {
237 if ( ! class_exists( '\WP_Upgrader', false ) ) {
238 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
239 @include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
240 }
241
242 return class_exists( '\WP_Upgrader', false );
243 }
244
245 public static function lock() {
246 // prevent the upgrade from being started several times at once
247 // we lock for 4 minutes. In case of major Matomo upgrades the upgrade may take much longer but it should be
248 // safe in this case to run the upgrade several times
249 // important: we always need to use the same timeout otherwise if something did use `create_lock(2)` then
250 // even though another job locked it for 4 minutes, the other job that locks it only for 2 seconds would release
251 // the lock basically since WP does not remember the initialy set release timeout
252 return self::load_upgrader() && WP_Upgrader::create_lock( self::LOCK_NAME, 60 * 4 );
253 }
254
255 public static function unlock() {
256 return self::load_upgrader() && WP_Upgrader::release_lock( self::LOCK_NAME );
257 }
258
259 private static function update_components() {
260 $updater = new \Piwik\Updater();
261 $components_with_update_file = $updater->getComponentUpdates();
262
263 if ( empty( $components_with_update_file ) ) {
264 return false;
265 }
266
267 if ( ! self::lock() ) {
268 throw new UpdateInProgressException();
269 }
270
271 try {
272 SettingsServer::setMaxExecutionTime( 0 );
273
274 if ( function_exists( 'ignore_user_abort' ) ) {
275 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
276 @ignore_user_abort( true );
277 }
278
279 self::ensure_log_tables_have_correct_row_format();
280 $result = $updater->updateComponents( $components_with_update_file );
281 } catch ( Exception $e ) {
282 self::unlock();
283 throw $e;
284 }
285 self::unlock();
286
287 if ( ! empty( $result['errors'] ) ) {
288 throw new Exception( 'Error while updating components: ' . implode( ', ', $result['errors'] ) );
289 }
290
291 \Piwik\Updater::recordComponentSuccessfullyUpdated( 'core', Version::VERSION );
292 Filesystem::deleteAllCacheOnUpdate();
293 }
294
295 private static function ensure_log_tables_have_correct_row_format() {
296 try {
297 // we only care about log tables which can have dimension columns added/removed.
298 // other tables won't have this problem.
299 $core_log_tables = [
300 'log_visit',
301 'log_link_visit_action',
302 'log_conversion',
303 'log_conversion_item',
304 ];
305
306 // get table status of all log_ tables
307 $prefix = Config::getInstance()->database['tables_prefix'];
308 $table_info = \Piwik\Db::fetchAll( 'SHOW TABLE STATUS LIKE ?', [ "{$prefix}log_%" ] );
309 $table_info = array_column( $table_info, null, 'Name' );
310
311 // for every CORE log table that has Compact, switch to Dynamic
312 foreach ( $core_log_tables as $table_name ) {
313 $prefixed_name = Common::prefixTable( $table_name );
314 if ( isset( $table_info[ $prefixed_name ] )
315 && 'Compact' === $table_info[ $prefixed_name ]['Row_format']
316 ) {
317 Db::exec( "ALTER TABLE `$prefixed_name` ROW_FORMAT=Dynamic" );
318 }
319 }
320 } catch ( \Exception $ex ) {
321 // log and ignore
322 $logger = new Logger();
323 $logger->log( 'Failure when trying to ensure log table ROW_FORMATs are Dynamic: ' . $ex->getMessage() . "\n" . $ex->getTraceAsString(), Logger::LEVEL_DEBUG );
324 }
325 }
326 }
327