PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.1.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.1.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 2 years ago Commands 2 years ago Db 2 years ago Ecommerce 2 years ago Report 2 years ago Site 2 years ago TrackingCode 2 years ago Updater 4 years ago User 2 years ago Workarounds 2 years ago WpStatistics 2 years ago views 4 years ago API.php 2 years ago Access.php 4 years ago AjaxTracker.php 2 years ago Annotations.php 4 years ago Bootstrap.php 2 years ago Capabilities.php 4 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 4 years ago RedirectOnActivation.php 4 years ago Referral.php 4 years ago Roles.php 4 years ago ScheduledTasks.php 2 years ago Settings.php 2 years ago Site.php 3 years ago TrackingCode.php 4 years ago Uninstaller.php 2 years ago Updater.php 2 years ago User.php 4 years ago
Updater.php
290 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() {
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 \Piwik\Access::doAsSuperUser(
149 function () {
150 self::update_components();
151 self::update_components();
152 }
153 );
154
155 $upload_dir = $paths->get_upload_base_dir();
156
157 $wp_filesystem = $paths->get_file_system();
158 if ( $paths->is_upload_dir_writable() ) {
159 $wp_filesystem->put_contents( $upload_dir . '/index.php', '//hello' );
160 $wp_filesystem->put_contents( $upload_dir . '/index.html', '//hello' );
161 $wp_filesystem->put_contents( $upload_dir . '/index.htm', '//hello' );
162 $wp_filesystem->put_contents(
163 $upload_dir . '/.htaccess',
164 '<Files ~ "(\.mmdb)$">
165 ' . ServerFilesGenerator::getDenyHtaccessContent() . '
166 </Files>
167 <Files ~ "(\.js)$">
168 ' . ServerFilesGenerator::getAllowHtaccessContent() . '
169 </Files>'
170 );
171 }
172 $config_dir = $paths->get_config_ini_path();
173 if ( $paths->is_upload_dir_writable() ) {
174 $wp_filesystem->put_contents( $config_dir . '/index.php', '//hello' );
175 $wp_filesystem->put_contents( $config_dir . '/index.html', '//hello' );
176 $wp_filesystem->put_contents( $config_dir . '/index.htm', '//hello' );
177 }
178
179 if ( $this->settings->should_disable_addhandler() ) {
180 wp_schedule_single_event( time() + 10, ScheduledTasks::EVENT_DISABLE_ADDHANDLER );
181 }
182 }
183
184 public function is_upgrade_in_progress() {
185 if ( ! self::load_upgrader() ) {
186 return 'no upgrader';
187 }
188
189 if ( self::lock() ) {
190 // we can get the lock meaning no update is in progress
191 self::unlock();
192
193 return false;
194 }
195
196 return true;
197 }
198
199 private static function load_upgrader() {
200 if ( ! class_exists( '\WP_Upgrader', false ) ) {
201 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
202 @include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
203 }
204
205 return class_exists( '\WP_Upgrader', false );
206 }
207
208 public static function lock() {
209 // prevent the upgrade from being started several times at once
210 // we lock for 4 minutes. In case of major Matomo upgrades the upgrade may take much longer but it should be
211 // safe in this case to run the upgrade several times
212 // important: we always need to use the same timeout otherwise if something did use `create_lock(2)` then
213 // even though another job locked it for 4 minutes, the other job that locks it only for 2 seconds would release
214 // the lock basically since WP does not remember the initialy set release timeout
215 return self::load_upgrader() && WP_Upgrader::create_lock( self::LOCK_NAME, 60 * 4 );
216 }
217
218 public static function unlock() {
219 return self::load_upgrader() && WP_Upgrader::release_lock( self::LOCK_NAME );
220 }
221
222 private static function update_components() {
223 $updater = new \Piwik\Updater();
224 $components_with_update_file = $updater->getComponentUpdates();
225
226 if ( empty( $components_with_update_file ) ) {
227 return false;
228 }
229
230 if ( ! self::lock() ) {
231 throw new UpdateInProgressException();
232 }
233
234 try {
235 SettingsServer::setMaxExecutionTime( 0 );
236
237 if ( function_exists( 'ignore_user_abort' ) ) {
238 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
239 @ignore_user_abort( true );
240 }
241
242 self::ensure_log_tables_have_correct_row_format();
243 $result = $updater->updateComponents( $components_with_update_file );
244 } catch ( Exception $e ) {
245 self::unlock();
246 throw $e;
247 }
248 self::unlock();
249
250 if ( ! empty( $result['errors'] ) ) {
251 throw new Exception( 'Error while updating components: ' . implode( ', ', $result['errors'] ) );
252 }
253
254 \Piwik\Updater::recordComponentSuccessfullyUpdated( 'core', Version::VERSION );
255 Filesystem::deleteAllCacheOnUpdate();
256 }
257
258 private static function ensure_log_tables_have_correct_row_format() {
259 try {
260 // we only care about log tables which can have dimension columns added/removed.
261 // other tables won't have this problem.
262 $core_log_tables = [
263 'log_visit',
264 'log_link_visit_action',
265 'log_conversion',
266 'log_conversion_item',
267 ];
268
269 // get table status of all log_ tables
270 $prefix = Config::getInstance()->database['tables_prefix'];
271 $table_info = \Piwik\Db::fetchAll( 'SHOW TABLE STATUS LIKE ?', [ "{$prefix}log_%" ] );
272 $table_info = array_column( $table_info, null, 'Name' );
273
274 // for every CORE log table that has Compact, switch to Dynamic
275 foreach ( $core_log_tables as $table_name ) {
276 $prefixed_name = Common::prefixTable( $table_name );
277 if ( isset( $table_info[ $prefixed_name ] )
278 && 'Compact' === $table_info[ $prefixed_name ]['Row_format']
279 ) {
280 Db::exec( "ALTER TABLE `$prefixed_name` ROW_FORMAT=Dynamic" );
281 }
282 }
283 } catch ( \Exception $ex ) {
284 // log and ignore
285 $logger = new Logger();
286 $logger->log( 'Failure when trying to ensure log table ROW_FORMATs are Dynamic: ' . $ex->getMessage() . "\n" . $ex->getTraceAsString(), Logger::LEVEL_DEBUG );
287 }
288 }
289 }
290