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