PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 1.3.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v1.3.2
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 / User / Sync.php
matomo / classes / WpMatomo / User Last commit date
Sync.php 5 years ago
Sync.php
324 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\User;
11
12 use Piwik\Access;
13 use Piwik\Access\Role\Admin;
14 use Piwik\Access\Role\View;
15 use Piwik\Access\Role\Write;
16 use Piwik\Auth\Password;
17 use Piwik\Common;
18 use Piwik\Date;
19 use Piwik\Plugin;
20 use Piwik\Plugins\LanguagesManager\API;
21 use Piwik\Plugins\UsersManager\Model;
22 use Piwik\Plugins\UsersManager;
23 use WpMatomo\Bootstrap;
24 use WpMatomo\Capabilities;
25 use WpMatomo\Logger;
26 use WpMatomo\ScheduledTasks;
27 use WpMatomo\Site;
28 use WpMatomo\User;
29
30 if ( ! defined( 'ABSPATH' ) ) {
31 exit; // if accessed directly
32 }
33
34 class Sync {
35 /**
36 * actually allowed is 100 characters...
37 * but we do -5 to have some room to append `wp_`.$login.XYZ if needed
38 */
39 const MAX_USER_NAME_LENGTH = 95;
40
41 /**
42 * @var Logger
43 */
44 private $logger;
45
46 public function __construct() {
47 $this->logger = new Logger();
48 }
49
50 public function register_hooks() {
51 add_action( 'add_user_role', array( $this, 'sync_current_users' ), $prio = 10, $args = 0 );
52 add_action( 'remove_user_role', array( $this, 'sync_current_users' ), $prio = 10, $args = 0 );
53 add_action( 'add_user_to_blog', array( $this, 'sync_current_users' ), $prio = 10, $args = 0 );
54 add_action( 'remove_user_from_blog', array( $this, 'sync_current_users' ), $prio = 10, $args = 0 );
55 add_action( 'user_register', array( $this, 'sync_current_users' ), $prio = 10, $args = 0 );
56 add_action( 'profile_update', array( $this, 'sync_maybe_background' ), $prio = 10, $args = 0 );
57 }
58
59 public function sync_maybe_background()
60 {
61 global $pagenow;
62 if ( is_admin() && $pagenow == 'users.php' ) {
63 // eg for profile update we don't want to sync directly see #365 as it could cause issues with other plugins
64 // if they eg alter `get_users` option
65 wp_schedule_single_event(time() + 5, ScheduledTasks::EVENT_SYNC);
66 } else {
67 $this->sync_current_users();
68 }
69 }
70
71 public function sync_all() {
72 if ( function_exists( 'is_multisite' ) && is_multisite() ) {
73 foreach ( get_sites() as $site ) {
74 switch_to_blog( $site->blog_id );
75
76 $idsite = Site::get_matomo_site_id( $site->blog_id );
77
78 try {
79 if ( $idsite ) {
80 $users = $this->get_users( array('blog_id' => $site->blog_id ) );
81 $this->sync_users( $users, $idsite );
82 }
83 } catch ( \Exception $e ) {
84 // we don't want to rethrow exception otherwise some other blogs might never sync
85 $this->logger->log_exception( 'user_sync ', $e );
86 }
87
88 restore_current_blog();
89 }
90 } else {
91 $this->sync_current_users();
92 }
93 }
94
95 private function get_users($options = array())
96 {
97 /** @var \WP_User[] $users */
98 $users = get_users( $options );
99
100 $current_user = wp_get_current_user();
101 if (!empty($current_user) && !empty($current_user->user_login)) {
102 // refs https://github.com/matomo-org/wp-matomo/issues/365
103 // some other plugins may under circumstances overwrite the get_users query and not return all users
104 // as a result we would delete some users in the matomo users table. this way we make sure at least the current
105 // user will be added and not deleted even if the list of users is not complete
106 $found = false;
107 foreach ($users as $user) {
108 if ($user->user_login === $current_user->user_login) {
109 $found = true;
110 break;
111 }
112 }
113 if (!$found) {
114 $users[] = $current_user;
115 }
116 }
117
118 if (is_multisite()) {
119 $super_admins = get_super_admins();
120 if (!empty($super_admins)) {
121 foreach ($super_admins as $super_admin) {
122 $found = false;
123 foreach ($users as $user) {
124 if ($user->user_login === $super_admin) {
125 $found = true;
126 break;
127 }
128 }
129 if (!$found) {
130 $user = get_user_by('login', $super_admin);
131 if (!empty($user)) {
132 $users[] = $user;
133 }
134 }
135 }
136 }
137 }
138 return $users;
139 }
140
141 public function sync_current_users() {
142 $idsite = Site::get_matomo_site_id( get_current_blog_id() );
143 if ( $idsite ) {
144 $users = $this->get_users();
145 $this->sync_users( $users, $idsite );
146 }
147 }
148
149 /**
150 * Sync all users. Make sure to always pass all sites that exist within a given site... you cannot just sync an individual
151 * user... we would delete all other users
152 *
153 * @param \WP_User[] $users
154 * @param $idsite
155 */
156 protected function sync_users( $users, $idsite ) {
157 Bootstrap::do_bootstrap();
158
159 $this->logger->log( 'Matomo will now sync ' . count( $users ) . ' users' );
160
161 $super_users = array();
162 $logins_with_some_view_access = array( 'anonmyous' ); // may or may not exist... we don't want to delete this user though
163 $user_model = new Model();
164
165 // need to make sure we recreate new instance later with latest dependencies in case they changed
166 API::unsetInstance();
167
168 foreach ( $users as $user ) {
169 $user_id = $user->ID;
170
171 // todo if we used transactions we could commit it after a possibly new access has been added
172 // to prevent UI preventing randomly saying no access between deleting and adding access
173
174 $mapped_matomo_login = User::get_matomo_user_login( $user_id );
175
176 $matomo_login = null;
177
178 if ( user_can( $user, Capabilities::KEY_SUPERUSER ) ) {
179 $matomo_login = $this->ensure_user_exists( $user );
180 $super_users[ $matomo_login ] = $user;
181 $logins_with_some_view_access[] = $matomo_login;
182 } elseif ( user_can( $user, Capabilities::KEY_ADMIN ) ) {
183 $matomo_login = $this->ensure_user_exists( $user );
184 $user_model->deleteUserAccess( $mapped_matomo_login, array( $idsite ) );
185 $user_model->addUserAccess( $matomo_login, Admin::ID, array( $idsite ) );
186 $user_model->setSuperUserAccess( $matomo_login, false );
187 $logins_with_some_view_access[] = $matomo_login;
188 } elseif ( user_can( $user, Capabilities::KEY_WRITE ) ) {
189 $matomo_login = $this->ensure_user_exists( $user );
190 $user_model->deleteUserAccess( $mapped_matomo_login, array( $idsite ) );
191 $user_model->addUserAccess( $matomo_login, Write::ID, array( $idsite ) );
192 $user_model->setSuperUserAccess( $matomo_login, false );
193 $logins_with_some_view_access[] = $matomo_login;
194 } elseif ( user_can( $user, Capabilities::KEY_VIEW ) ) {
195 $matomo_login = $this->ensure_user_exists( $user );
196 $user_model->deleteUserAccess( $mapped_matomo_login, array( $idsite ) );
197 $user_model->addUserAccess( $matomo_login, View::ID, array( $idsite ) );
198 $user_model->setSuperUserAccess( $matomo_login, false );
199 $logins_with_some_view_access[] = $matomo_login;
200 } elseif ($mapped_matomo_login) {
201 $user_model->deleteUserAccess( $mapped_matomo_login, array( $idsite ) );
202 }
203
204 if ( $matomo_login ) {
205 $locale = get_user_locale( $user->ID );
206 $locale_dash = Common::mb_strtolower(str_replace('_', '-', $locale));
207 $parts = [];
208 if ($locale && in_array($locale_dash, ['zh-cn', 'zh-tw', 'pt-br', 'es-ar'], true)) {
209 $parts = [$locale_dash];
210 } elseif (!empty($locale) && is_string($locale)) {
211 $parts = explode( '_', $locale );
212 }
213
214 if ( ! empty( $parts[0] ) ) {
215 $lang = $parts[0];
216 if ( Plugin\Manager::getInstance()->isPluginActivated( 'LanguagesManager' )
217 && Plugin\Manager::getInstance()->isPluginInstalled( 'LanguagesManager' )
218 && API::getInstance()->isLanguageAvailable( $lang ) ) {
219 $user_lang_model = new \Piwik\Plugins\LanguagesManager\Model();
220 $user_lang_model->setLanguageForUser( $matomo_login, $lang );
221 }
222 }
223 }
224
225 if ($idsite != 1) {
226 // only needed if the actual site is not the default site... makes sure when they click in Matomo
227 // UI on "Dashboard" that the correct site is being opened by default
228 // eg if the linked site is actually idSite=2.
229 Access::doAsSuperUser(
230 function () use ( $matomo_login, &$idsite ) {
231 try {
232 UsersManager\API::unsetInstance();
233 // we need to unset the instance to make sure it fetches the
234 // up to date dependencies eg current plugin manager etc
235
236 UsersManager\API::getInstance()->setUserPreference(
237 $matomo_login,
238 UsersManager\API::PREFERENCE_DEFAULT_REPORT,
239 $idsite
240 );
241 } catch (\Exception $e) {
242 // ignore any error for now
243 }
244
245 }
246 );
247 }
248 }
249
250 foreach ( $super_users as $matomo_login => $user ) {
251 $user_model->setSuperUserAccess( $matomo_login, true );
252 }
253
254 $logins_with_some_view_access = array_unique( $logins_with_some_view_access );
255 $all_users = $user_model->getUsers( array() );
256 foreach ( $all_users as $all_user ) {
257 if ( ! in_array( $all_user['login'], $logins_with_some_view_access, true )
258 && ! empty( $all_user['login'] ) ) {
259 $user_model->deleteUserOnly( $all_user['login'] );
260 $user_model->deleteUserOptions( $all_user['login'] );
261 $user_model->deleteUserAccess( $all_user['login'] );
262 }
263 }
264 }
265
266 /**
267 * @param \WP_User $wp_user
268 */
269 protected function ensure_user_exists( $wp_user ) {
270 $user_model = new Model();
271 $user_id = $wp_user->ID;
272 $login = $wp_user->user_login;
273
274 $matomo_user_login = User::get_matomo_user_login( $user_id );
275 $user_in_matomo = null;
276
277 if ( $matomo_user_login ) {
278 $user_in_matomo = $user_model->getUser( $matomo_user_login );
279 } else {
280 // wp usernames may include whitespace etc
281 $login = preg_replace('/[^A-Za-zÄäÖöÜüß0-9_.@+-]+/D', '_', $login);
282 $login = substr( $login, 0, self::MAX_USER_NAME_LENGTH );
283
284 if ( ! $user_model->getUser( $login ) ) {
285 // username is available...
286 $matomo_user_login = $login;
287 } else {
288 // this username seems taken... lets create another one
289
290 $index = 0;
291 do {
292 if ( ! $index ) {
293 $matomo_user_login = 'wp_' . $login;
294 } else {
295 $matomo_user_login = 'wp_' . $login . $index;
296 }
297
298 $index ++;
299 } while ( $user_model->getUser( $matomo_user_login ) );
300 }
301 }
302
303 if ( ! $matomo_user_login || empty( $user_in_matomo ) ) {
304 $this->logger->log( 'Matomo is now creating a user forUserId ' . $user_id . ' with matomo login ' . $matomo_user_login );
305
306 $now = Date::now()->getDatetime();
307 $password = new Password();
308 // we generate some random password since log in using matomo won't be happening anyway
309 $password = $password->hash( $login . $now . Common::getRandomString( 200 ) . microtime( true ) . Common::generateUniqId() );
310
311 UsersManager\API::unsetInstance(); // make sure latest instance is loaded with all current dependencies... mainly needed for tests
312 $token = UsersManager\API::getInstance()->createTokenAuth( $login );
313 $user_model->addUser( $matomo_user_login, $password, $wp_user->user_email, $login, $token, $now );
314
315 User::map_matomo_user_login( $user_id, $matomo_user_login );
316 } elseif ( $user_in_matomo['email'] !== $wp_user->user_email ) {
317 $this->logger->log( 'Matomo is now updating the email for wpUserID ' . $user_id . ' matomo login ' . $matomo_user_login );
318 $user_model->updateUserFields( $matomo_user_login, array( 'email' => $wp_user->user_email ) );
319 }
320
321 return $matomo_user_login;
322 }
323 }
324