PluginProbe
Groups – Memberships and Access Control / 4.7.0
Groups – Memberships and Access Control v4.7.0
4.7.1 4.7.0 4.6.0 4.5.0 4.4.0 4.3.0 trunk 1.0.0-beta-1 1.0.0-beta-2 1.0.0-beta-3 1.0.0-beta-3b 1.0.0-beta-3c 1.0.0-beta-3d 1.1.4 1.1.5 1.10.0 1.10.1 1.10.2 1.10.3 1.11.0 1.11.1 1.11.2 1.11.3 1.12.0 1.13.0 All 131 releases
← All changes | lib/core/class-groups-controller.php +500 -109 1.10.04.7.0 View file →
@@ -22,41 +22,77 @@
22 22 if ( !defined( 'ABSPATH' ) ) {
23 23 exit;
24 24 }
25 25
26 +// phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange
27 +
26 28 /**
27 29 * Plugin controller
28 30 */
29 31 class Groups_Controller {
30 -
32 +
31 33 /**
34 + * Version 2.0.0 number
35 + *
36 + * @var string
37 + */
38 + const GROUPS_200 = '2.0.0';
39 +
40 + /**
41 + * Holds mofile when determined.
42 + *
43 + * @since 3.10.0
44 + *
45 + * @var string
46 + */
47 + private static $mofile = null;
48 +
49 + /**
32 50 * Cache-safe switching in case any multi-site hiccups might occur.
51 + *
33 52 * Clears the cache after switching to the given blog to avoid using
34 53 * another blog's cached values.
35 - * See wp_cache_reset() in wp-includes/cache.php
54 + *
55 + * Some implementations don't have wp_cache_switch_to_blog() nor the deprecated
56 + * wp_cache_reset(), e.g. WP Engine's object-cache.php which has wp_cache_flush().
57 + * Cache plugins are responsible for acting accordingly when switch_to_blog() or
58 + * restore_current_blog() is invoked. In 4.0.0 we dropped any additional calls
59 + * other than those to switch_to_blog() and restore_current_blog().
60 + *
61 + * @since 2.10.0 giving preference to call wp_cache_init() since it will reset the blog_id
62 + * @since 2.10.0 removed alternative call to wp_cache_reset()
63 + * @since 4.0.0 multisite guard added; not calling functions already called within switch_to_blog; cache plugins should act appropriately
64 + *
65 + * @see wp_cache_switch_to_blog()
66 + * @see wp_cache_init()
67 + * @see wp_cache_flush()
36 68 * @see wp_cache_reset()
69 + * @see self::restore_current_blog()
70 + *
37 71 * @link http://core.trac.wordpress.org/ticket/14941
38 72 *
39 73 * @param int $blog_id
40 74 */
41 75 public static function switch_to_blog( $blog_id ) {
42 - switch_to_blog( $blog_id );
43 - if ( function_exists( 'wp_cache_switch_to_blog' ) ) {
44 - wp_cache_switch_to_blog( $blog_id ); // introduced in WP 3.5.0
45 - } else {
46 - wp_cache_reset(); // deprecated in WP 3.5.0
76 + if ( is_multisite() ) {
77 + switch_to_blog( $blog_id );
47 78 }
48 79 }
49 -
80 +
50 81 /**
51 82 * Switch back to previous blog.
83 + *
84 + * @see self::switch_to_blog()
52 85 */
53 86 public static function restore_current_blog() {
54 - restore_current_blog();
87 + if ( is_multisite() ) {
88 + restore_current_blog();
89 + }
55 90 }
56 -
91 +
57 92 /**
58 93 * Boot the plugin.
94 + *
59 95 * @see Groups_Registered::wpmu_new_blog()
60 96 */
61 97 public static function boot() {
62 98 register_activation_hook( GROUPS_FILE, array( __CLASS__, 'activate' ) );
@@ -61,18 +97,21 @@
61 97 public static function boot() {
62 98 register_activation_hook( GROUPS_FILE, array( __CLASS__, 'activate' ) );
63 99 register_deactivation_hook( GROUPS_FILE, array( __CLASS__, 'deactivate' ) );
64 100 add_action( 'init', array( __CLASS__, 'init' ) );
65 -
101 + add_filter( 'load_textdomain_mofile', array( __CLASS__, 'load_textdomain_mofile' ), 10, 2 ); // as of 3.10.0 this filter is removed within its function while processing, any changes made here should be reflected there, too
102 + add_filter( 'load_script_translation_file', array( __CLASS__, 'load_script_translation_file' ), 10, 3 ); // @since 3.10.0
66 103 // priority 9 because it needs to be called before Groups_Registered's
67 104 // wpmu_new_blog kicks in
68 105 add_action( 'wpmu_new_blog', array( __CLASS__, 'wpmu_new_blog' ), 9, 2 );
69 106 add_action( 'delete_blog', array( __CLASS__, 'delete_blog' ), 10, 2 );
107 + add_action( 'wp_loaded', array( __CLASS__, 'wp_loaded' ) ); // @since 2.16.0
108 + add_action( 'admin_enqueue_scripts', array( __CLASS__, 'admin_enqueue_scripts' ) ); // @since 2.16.0
70 109 }
71 -
110 +
72 111 /**
73 112 * Run activation for a newly created blog in a multisite environment.
74 - *
113 + *
75 114 * @param int $blog_id
76 115 */
77 116 public static function wpmu_new_blog( $blog_id, $user_id ) {
78 117 if ( is_multisite() ) {
@@ -83,13 +122,13 @@
83 122 self::restore_current_blog();
84 123 }
85 124 }
86 125 }
87 -
126 +
88 127 /**
89 128 * Run deactivation for a blog that is about to be deleted in a multisite
90 129 * environment.
91 - *
130 + *
92 131 * @param int $blog_id
93 132 */
94 133 public static function delete_blog( $blog_id, $drop = false ) {
95 134 if ( is_multisite() ) {
@@ -100,41 +139,200 @@
100 139 self::restore_current_blog();
101 140 }
102 141 }
103 142 }
104 -
143 +
105 144 /**
106 145 * Initialize.
107 146 * Loads the plugin's translations.
147 + * Invokes version check.
108 148 */
109 149 public static function init() {
110 - load_plugin_textdomain( GROUPS_PLUGIN_DOMAIN, null, 'groups/languages' );
150 +
151 + unload_textdomain( 'groups' );
152 +
153 + // Load our current translations first ...
154 + $mofile = self::get_mofile();
155 + load_textdomain( 'groups', $mofile );
156 +
157 + // ... otherwise load_plugin_textdomain will simply get those in WP's languages
158 + // and we won't have our up-to-date translations.
159 + //load_plugin_textdomain( 'groups', null, 'groups/languages' );
111 160 self::version_check();
161 +
162 + // load the notice class
163 + if ( is_admin() ) {
164 + if ( Groups_User::current_user_can( 'activate_plugins' ) ) { // important: after init hook
165 + require_once GROUPS_ADMIN_LIB . '/class-groups-admin-notice.php';
166 + }
167 + }
112 168 }
113 -
169 +
114 170 /**
171 + * Register the menu style.
172 + *
173 + * @since 2.16.0
174 + */
175 + public static function wp_loaded() {
176 + global $groups_version;
177 + if ( !wp_style_is( 'groups-menu', 'registered' ) ) {
178 + wp_register_style( 'groups-menu', GROUPS_PLUGIN_URL . 'css/groups-menu.css', array(), $groups_version );
179 + }
180 + }
181 +
182 + /**
183 + * Enqueue the menu style.
184 + *
185 + * @since 2.16.0
186 + */
187 + public static function admin_enqueue_scripts() {
188 + wp_enqueue_style( 'groups-menu' );
189 + }
190 +
191 + /**
192 + * Builds the mofile string for our own translations.
193 + *
194 + * @return string mofile
195 + */
196 + private static function get_mofile() {
197 +
198 + // @since 3.10.0 determine the file only once and avoid redundant work
199 + if ( self::$mofile !== null ) {
200 + return self::$mofile;
201 + }
202 +
203 + $locale = get_locale();
204 + if ( function_exists( 'get_user_locale' ) ) {
205 + $locale = get_user_locale();
206 + }
207 + $locale = apply_filters( 'plugin_locale', $locale, 'groups' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
208 + $mofile = GROUPS_CORE_DIR . '/languages/groups-' . $locale . '.mo';
209 + // @since 3.3.0 load language-generic translation if available
210 + if ( !file_exists( $mofile ) ) {
211 + $parts = explode( '_', $locale );
212 + $language = $parts[0];
213 + $country = isset( $parts[1] ) ? $parts[1] : '';
214 + $form = isset( $parts[2] ) ? $parts[2] : '';
215 + switch ( $country ) {
216 + case 'CH':
217 + switch ( $form ) {
218 + case 'informal':
219 + $form = '';
220 + break;
221 + case '':
222 + $form = 'formal';
223 + break;
224 + }
225 + break;
226 + }
227 + $the_mofile = null;
228 + if ( $form !== '' ) {
229 + $the_mofile = GROUPS_CORE_DIR . '/languages/groups' . '-' . $language . '_' . $form . '.mo';
230 + if ( !file_exists( $the_mofile ) ) {
231 + $the_mofile = null;
232 + }
233 + }
234 + if ( $the_mofile === null ) {
235 + $the_mofile = GROUPS_CORE_DIR . '/languages/groups' . '-' . $language . '.mo';
236 + if ( !file_exists( $the_mofile ) ) {
237 + $the_mofile = null;
238 + }
239 + }
240 + if ( $the_mofile !== null ) {
241 + $mofile = $the_mofile;
242 + }
243 + }
244 +
245 + self::$mofile = $mofile;
246 +
247 + return $mofile;
248 + }
249 +
250 + /**
251 + * Makes sure that our own translation file is loaded first.
252 + *
253 + * @param string $mofile
254 + * @param string $domain
255 + *
256 + * @return string mofile
257 + */
258 + public static function load_textdomain_mofile( $mofile, $domain ) {
259 + if ( $domain === 'groups' ) {
260 + // @since 3.10.0 remove the filter while processing and avoid potential infinite recursion during processing
261 + remove_filter( 'load_textdomain_mofile', array( __CLASS__, 'load_textdomain_mofile' ), 10 );
262 + $own_mofile = self::get_mofile();
263 + if ( $own_mofile != $mofile ) {
264 + if ( !is_textdomain_loaded( $domain ) ) {
265 + if ( is_readable( $own_mofile ) ) {
266 + $mofile = $own_mofile;
267 + }
268 + }
269 + }
270 + add_filter( 'load_textdomain_mofile', array( __CLASS__, 'load_textdomain_mofile' ), 10, 2 ); // see self::boot() where this filter is originally added
271 + }
272 + return $mofile;
273 + }
274 +
275 + /**
276 + * Load our script translations.
277 + *
278 + * @since 3.10.0
279 + *
280 + * @param string $file
281 + * @param string $handle
282 + * @param string $domain
283 + *
284 + * @return string
285 + */
286 + public static function load_script_translation_file( $file, $handle, $domain ) {
287 + if ( $domain === 'groups' ) {
288 + $mofile = self::get_mofile();
289 + if ( $mofile !== null ) {
290 + $base = basename( $mofile, '.mo' );
291 + $path = GROUPS_CORE_DIR . '/languages/js/' . $base . '.json';
292 + if ( file_exists( $path ) ) {
293 + $file = $path;
294 + }
295 + }
296 + }
297 + return $file;
298 + }
299 +
300 + /**
115 301 * Plugin activation.
302 + *
116 303 * @param boolean $network_wide
117 304 */
118 305 public static function activate( $network_wide = false ) {
119 - if ( is_multisite() && $network_wide ) {
120 - $blog_ids = Groups_Utility::get_blogs();
121 - foreach ( $blog_ids as $blog_id ) {
122 - self::switch_to_blog( $blog_id );
306 + $sem_id = self::sem_get( self::get_sem_key() );
307 + if ( ( $sem_id === false ) || self::sem_acquire( $sem_id ) ) {
308 + if ( is_multisite() && $network_wide ) {
309 + $blog_ids = Groups_Utility::get_blogs();
310 + foreach ( $blog_ids as $blog_id ) {
311 + self::switch_to_blog( $blog_id );
312 + self::setup();
313 + self::restore_current_blog();
314 + }
315 + } else {
123 316 self::setup();
124 - self::restore_current_blog();
317 + // @since 3.3.1 Check here already if it is a single activation of Groups and only then set the transient
318 + if ( self::is_single_activate() ) {
319 + set_transient( 'groups_plugin_activated', true, 60 );
320 + }
125 321 }
126 - } else {
127 - self::setup();
322 + if ( $sem_id !== false ) {
323 + self::sem_release( $sem_id );
324 + self::sem_remove( $sem_id );
325 + }
128 326 }
129 327 }
130 -
328 +
131 329 /**
132 330 * Plugin activation work.
133 331 */
134 332 private static function setup() {
135 333 global $wpdb, $wp_roles;
136 -
334 +
137 335 // create WP capabilities
138 336 Groups_Controller::set_default_capabilities();
139 337
140 338 $charset_collate = '';
@@ -144,12 +342,13 @@
144 342 if ( ! empty( $wpdb->collate ) ) {
145 343 $charset_collate .= " COLLATE $wpdb->collate";
146 344 }
147 345
148 - // create tables
346 + // create tables
149 347 $group_table = _groups_get_tablename( 'group' );
348 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
150 349 if ( $wpdb->get_var( "SHOW TABLES LIKE '$group_table'" ) != $group_table ) {
151 - $queries[] = "CREATE TABLE $group_table (
350 + $queries[] = "CREATE TABLE IF NOT EXISTS $group_table (
152 351 group_id BIGINT(20) UNSIGNED NOT NULL auto_increment,
153 352 parent_id BIGINT(20) DEFAULT NULL,
154 353 creator_id BIGINT(20) DEFAULT NULL,
155 354 datetime DATETIME DEFAULT NULL,
@@ -159,10 +358,11 @@
159 358 UNIQUE INDEX group_n (name)
160 359 ) $charset_collate;";
161 360 }
162 361 $capability_table = _groups_get_tablename( 'capability' );
362 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
163 363 if ( $wpdb->get_var( "SHOW TABLES LIKE '$capability_table'" ) != $capability_table ) {
164 - $queries[] = "CREATE TABLE $capability_table (
364 + $queries[] = "CREATE TABLE IF NOT EXISTS $capability_table (
165 365 capability_id BIGINT(20) UNSIGNED NOT NULL auto_increment,
166 366 capability VARCHAR(255) NOT NULL,
167 367 class VARCHAR(255) DEFAULT NULL,
168 368 object VARCHAR(255) DEFAULT NULL,
@@ -173,10 +373,11 @@
173 373 INDEX capability_kco (capability(20),class(20),object(20))
174 374 ) $charset_collate;";
175 375 }
176 376 $user_group_table = _groups_get_tablename( 'user_group' );
377 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
177 378 if ( $wpdb->get_var( "SHOW TABLES LIKE '$user_group_table'" ) != $user_group_table ) {
178 - $queries[] = "CREATE TABLE $user_group_table (
379 + $queries[] = "CREATE TABLE IF NOT EXISTS $user_group_table (
179 380 user_id bigint(20) unsigned NOT NULL,
180 381 group_id bigint(20) unsigned NOT NULL,
181 382 PRIMARY KEY (user_id, group_id),
182 383 INDEX user_group_gu (group_id,user_id)
@@ -182,11 +383,12 @@
182 383 INDEX user_group_gu (group_id,user_id)
183 384 ) $charset_collate;";
184 385 }
185 386 $user_capability_table = _groups_get_tablename( 'user_capability' );
387 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
186 388 if ( $wpdb->get_var( "SHOW TABLES LIKE '$user_capability_table'" ) != $user_capability_table ) {
187 - $queries[] = "CREATE TABLE $user_capability_table (
188 - user_id bigint(20) unsigned NOT NULL,
389 + $queries[] = "CREATE TABLE IF NOT EXISTS $user_capability_table (
390 + user_id bigint(20) unsigned NOT NULL,
189 391 capability_id bigint(20) unsigned NOT NULL,
190 392 PRIMARY KEY (user_id, capability_id),
191 393 INDEX user_capability_cu (capability_id,user_id)
192 394 ) $charset_collate;";
@@ -191,10 +393,11 @@
191 393 INDEX user_capability_cu (capability_id,user_id)
192 394 ) $charset_collate;";
193 395 }
194 396 $group_capability_table = _groups_get_tablename( 'group_capability' );
397 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
195 398 if ( $wpdb->get_var( "SHOW TABLES LIKE '$group_capability_table'" ) != $group_capability_table ) {
196 - $queries[] = "CREATE TABLE $group_capability_table (
399 + $queries[] = "CREATE TABLE IF NOT EXISTS $group_capability_table (
197 400 group_id bigint(20) unsigned NOT NULL,
198 401 capability_id bigint(20) unsigned NOT NULL,
199 402 PRIMARY KEY (group_id, capability_id),
200 403 INDEX group_capability_cg (capability_id,group_id)
@@ -200,10 +403,16 @@
200 403 INDEX group_capability_cg (capability_id,group_id)
201 404 ) $charset_collate;";
202 405 }
203 406 if ( !empty( $queries ) ) {
204 - require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
205 - dbDelta( $queries );
407 + // For the record ... (and https://core.trac.wordpress.org/ticket/12773 should not be closed)
408 + // dbDelta() fails to handle queries "CREATE TABLE IF NOT EXISTS ..."
409 + // (a regex results in "IF" used as array index holding only last query to create table).
410 + //require_once ABSPATH . 'wp-admin/includes/upgrade.php';
411 + //dbDelta( $queries );
412 + foreach ( $queries as $query ) {
413 + $wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
414 + }
206 415 }
207 416 // needs to be called to create its capabilities
208 417 Groups_Post_Access::activate();
209 418 // same thing to created groups for registered users
@@ -211,108 +420,149 @@
211 420 // add WordPress capabilities
212 421 Groups_WordPress::activate();
213 422 // ... end of plugin activation work.
214 423 }
215 -
424 +
216 425 /**
217 426 * Checks current version and triggers update if needed.
218 427 */
219 428 public static function version_check() {
220 429 global $groups_version, $groups_admin_messages;
221 - $previous_version = get_option( 'groups_plugin_version', null );
430 + $previous_version = get_option( 'groups_plugin_version', '' );
222 431 $groups_version = GROUPS_CORE_VERSION;
223 - if ( version_compare( $previous_version, $groups_version ) < 0 ) {
224 - if ( self::update( $previous_version ) ) {
225 - update_option( 'groups_plugin_version', $groups_version );
226 - } else {
227 - $groups_admin_messages[] = '<div class="error">Updating Groups plugin core <em>failed</em>.</div>';
432 + // auto-enable legacy support on upgrade from Groups previous to 2.0.0
433 + if ( $previous_version ) {
434 + if ( version_compare( $previous_version, self::GROUPS_200 ) < 0 ) {
435 + if ( Groups_Options::get_option( GROUPS_LEGACY_ENABLE ) === null ) {
436 + Groups_Options::update_option( GROUPS_LEGACY_ENABLE, true );
437 + }
438 + set_transient( 'groups_plugin_updated_legacy', true, 60 );
228 439 }
229 440 }
441 +
442 + // disable legacy support on new installations
443 + if ( Groups_Options::get_option( GROUPS_LEGACY_ENABLE ) === null ) {
444 + Groups_Options::update_option( GROUPS_LEGACY_ENABLE, false );
445 + }
446 +
447 + // run update procedure if newer version is installed
448 + if ( $previous_version ) {
449 + if ( version_compare( $previous_version, $groups_version ) < 0 ) {
450 + if ( self::update( $previous_version ) ) {
451 + if ( update_option( 'groups_plugin_version', $groups_version ) ) {
452 + set_transient( 'groups_plugin_updated', true, 60 );
453 + }
454 + } else {
455 + $groups_admin_messages[] = '<div class="error">Updating Groups plugin core <em>failed</em>.</div>';
456 + }
457 + }
458 + }
230 459 }
231 -
460 +
232 461 /**
233 462 * Update maintenance.
234 463 */
235 464 public static function update( $previous_version ) {
465 +
236 466 global $wpdb, $groups_admin_messages;
467 +
237 468 $result = true;
238 - $queries = array();
239 - switch ( $previous_version ) {
240 - case '1.0.0' :
241 - $capability_table = _groups_get_tablename( 'capability' );
242 - if ( $wpdb->get_var( "SHOW TABLES LIKE '$capability_table'" ) == $capability_table ) {
243 - // increase column sizes
244 - $queries[] = "ALTER TABLE $capability_table MODIFY capability VARCHAR(255) UNIQUE NOT NULL;";
245 - $queries[] = "ALTER TABLE $capability_table MODIFY class VARCHAR(255) DEFAULT NULL;";
246 - $queries[] = "ALTER TABLE $capability_table MODIFY object VARCHAR(255) DEFAULT NULL;";
247 - // correct capabilities
248 - $queries[] = "UPDATE $capability_table SET capability='delete_published_pages' WHERE capability='delete_published_pag';";
249 - $queries[] = "UPDATE $capability_table SET capability='delete_published_posts' WHERE capability='delete_published_pos';";
250 - // fix hideously big index
251 - $queries[] = "ALTER TABLE $capability_table DROP INDEX capability_kco;";
252 - $queries[] = "ALTER TABLE $capability_table ADD INDEX capability_kco (capability(20),class(20),object(20));";
253 - }
254 - break;
255 - case '1.0.0-beta-3d' :
256 - $capability_table = _groups_get_tablename( 'capability' );
257 - if ( $wpdb->get_var( "SHOW TABLES LIKE '$capability_table'" ) == $capability_table ) {
258 - // increase column sizes
259 - $queries[] = "ALTER TABLE $capability_table MODIFY capability VARCHAR(255) UNIQUE NOT NULL;";
260 - $queries[] = "ALTER TABLE $capability_table MODIFY class VARCHAR(255) DEFAULT NULL;";
261 - $queries[] = "ALTER TABLE $capability_table MODIFY object VARCHAR(255) DEFAULT NULL;";
262 - // correct capabilities
263 - $queries[] = "UPDATE $capability_table SET capability='delete_published_pages' WHERE capability='delete_published_pag';";
264 - $queries[] = "UPDATE $capability_table SET capability='delete_published_posts' WHERE capability='delete_published_pos';";
265 - }
266 - break;
267 - default :
268 - if ( !empty( $previous_version ) ) {
269 - if ( version_compare( $previous_version, '1.1.6' ) < 0 ) {
270 - Groups_Options::update_option( Groups_Post_Access::READ_POST_CAPABILITIES, array( Groups_Post_Access::READ_POST_CAPABILITY ) );
271 - $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->postmeta SET meta_value = %s WHERE meta_key = %s", Groups_Post_Access::READ_POST_CAPABILITY, Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ_POST_CAPABILITY ) );
469 +
470 + $sem_id = self::sem_get( self::get_sem_key() );
471 + if ( ( $sem_id === false ) || self::sem_acquire( $sem_id ) ) {
472 + $queries = array();
473 + switch ( $previous_version ) {
474 + case '1.0.0' :
475 + $capability_table = _groups_get_tablename( 'capability' );
476 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
477 + if ( $wpdb->get_var( "SHOW TABLES LIKE '$capability_table'" ) == $capability_table ) {
478 + // increase column sizes
479 + $queries[] = "ALTER TABLE $capability_table MODIFY capability VARCHAR(255) UNIQUE NOT NULL;";
480 + $queries[] = "ALTER TABLE $capability_table MODIFY class VARCHAR(255) DEFAULT NULL;";
481 + $queries[] = "ALTER TABLE $capability_table MODIFY object VARCHAR(255) DEFAULT NULL;";
482 + // correct capabilities
483 + $queries[] = "UPDATE $capability_table SET capability='delete_published_pages' WHERE capability='delete_published_pag';";
484 + $queries[] = "UPDATE $capability_table SET capability='delete_published_posts' WHERE capability='delete_published_pos';";
485 + // fix hideously big index
486 + $queries[] = "ALTER TABLE $capability_table DROP INDEX capability_kco;";
487 + $queries[] = "ALTER TABLE $capability_table ADD INDEX capability_kco (capability(20),class(20),object(20));";
272 488 }
273 - if ( version_compare( $previous_version, '1.5.1' ) < 0 ) {
274 - $capability_table = _groups_get_tablename( 'capability' );
275 - $queries[] = "ALTER TABLE $capability_table DROP INDEX capability, ADD UNIQUE INDEX capability(capability(100));";
489 + break;
490 + case '1.0.0-beta-3d' :
491 + $capability_table = _groups_get_tablename( 'capability' );
492 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
493 + if ( $wpdb->get_var( "SHOW TABLES LIKE '$capability_table'" ) == $capability_table ) {
494 + // increase column sizes
495 + $queries[] = "ALTER TABLE $capability_table MODIFY capability VARCHAR(255) UNIQUE NOT NULL;";
496 + $queries[] = "ALTER TABLE $capability_table MODIFY class VARCHAR(255) DEFAULT NULL;";
497 + $queries[] = "ALTER TABLE $capability_table MODIFY object VARCHAR(255) DEFAULT NULL;";
498 + // correct capabilities
499 + $queries[] = "UPDATE $capability_table SET capability='delete_published_pages' WHERE capability='delete_published_pag';";
500 + $queries[] = "UPDATE $capability_table SET capability='delete_published_posts' WHERE capability='delete_published_pos';";
276 501 }
502 + break;
503 + default :
504 + if ( !empty( $previous_version ) ) {
505 + if ( version_compare( $previous_version, '1.1.6' ) < 0 ) {
506 + Groups_Options::update_option( Groups_Post_Access::READ_POST_CAPABILITIES, array( Groups_Post_Access::READ_POST_CAPABILITY ) );
507 + $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->postmeta SET meta_value = %s WHERE meta_key = %s", Groups_Post_Access::READ_POST_CAPABILITY, Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ_POST_CAPABILITY ) );
508 + }
509 + if ( version_compare( $previous_version, '1.5.1' ) < 0 ) {
510 + $capability_table = _groups_get_tablename( 'capability' );
511 + $queries[] = "ALTER TABLE $capability_table DROP INDEX capability, ADD UNIQUE INDEX capability(capability(100));";
512 + }
513 + }
514 + } // switch
515 + if ( !empty( $previous_version ) && version_compare( $previous_version, '2.0.0' ) < 0 ) {
516 + self::set_default_capabilities();
517 + Groups_WordPress::refresh_capabilities();
518 + }
519 + foreach ( $queries as $query ) {
520 + if ( $wpdb->query( $query ) === false ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
521 + $result = false;
277 522 }
278 - } // switch
279 - foreach ( $queries as $query ) {
280 - if ( $wpdb->query( $query ) === false ) {
281 - $result = false;
282 523 }
283 524 }
284 525 return $result;
285 526 }
286 -
527 +
287 528 /**
288 529 * Drop tables and clear data if the plugin is deactivated.
289 530 * This will happen only if the user chooses to delete data upon deactivation.
531 + *
290 532 * @param boolean $network_wide
291 533 */
292 534 public static function deactivate( $network_wide = false ) {
293 - if ( is_multisite() && $network_wide ) {
294 - if ( Groups_Options::get_option( 'groups_network_delete_data', false ) ) {
295 - $blog_ids = Groups_Utility::get_blogs();
296 - foreach ( $blog_ids as $blog_id ) {
297 - self::switch_to_blog( $blog_id );
298 - self::cleanup( true );
299 - self::restore_current_blog();
535 + $sem_id = self::sem_get( self::get_sem_key() );
536 + if ( ( $sem_id === false ) || self::sem_acquire( $sem_id ) ) {
537 + if ( is_multisite() && $network_wide ) {
538 + if ( Groups_Options::get_option( 'groups_network_delete_data', false ) ) {
539 + $blog_ids = Groups_Utility::get_blogs();
540 + foreach ( $blog_ids as $blog_id ) {
541 + self::switch_to_blog( $blog_id );
542 + self::cleanup( true );
543 + self::restore_current_blog();
544 + }
300 545 }
546 + } else {
547 + self::cleanup();
301 548 }
302 - } else {
303 - self::cleanup();
549 + if ( $sem_id !== false ) {
550 + self::sem_release( $sem_id );
551 + self::sem_remove( $sem_id );
552 + }
304 553 }
305 554 }
306 -
555 +
307 556 /**
308 557 * Plugin deactivation cleanup.
309 - * @param $drop overrides the groups_delete_data option, default is false
558 + *
559 + * @param $drop boolean overrides the groups_delete_data option, default is false
310 560 */
311 561 private static function cleanup( $drop = false ) {
312 -
562 +
313 563 global $wpdb, $wp_roles;
314 -
564 +
315 565 $delete_data = Groups_Options::get_option( 'groups_delete_data', false );
316 566 if ( $delete_data || $drop ) {
317 567 foreach ( $wp_roles->role_objects as $role ) {
318 568 $role->remove_cap( GROUPS_ACCESS_GROUPS );
@@ -317,22 +567,63 @@
317 567 foreach ( $wp_roles->role_objects as $role ) {
318 568 $role->remove_cap( GROUPS_ACCESS_GROUPS );
319 569 $role->remove_cap( GROUPS_ADMINISTER_GROUPS );
320 570 $role->remove_cap( GROUPS_ADMINISTER_OPTIONS );
571 + $role->remove_cap( GROUPS_RESTRICT_ACCESS );
321 572 }
322 - $wpdb->query('DROP TABLE IF EXISTS ' . _groups_get_tablename( 'group' ) );
323 - $wpdb->query('DROP TABLE IF EXISTS ' . _groups_get_tablename( 'capability' ) );
324 - $wpdb->query('DROP TABLE IF EXISTS ' . _groups_get_tablename( 'user_group' ) );
325 - $wpdb->query('DROP TABLE IF EXISTS ' . _groups_get_tablename( 'user_capability' ) );
326 - $wpdb->query('DROP TABLE IF EXISTS ' . _groups_get_tablename( 'group_capability' ) );
573 + $wpdb->query( 'DROP TABLE IF EXISTS ' . _groups_get_tablename( 'group' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
574 + $wpdb->query( 'DROP TABLE IF EXISTS ' . _groups_get_tablename( 'capability' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
575 + $wpdb->query( 'DROP TABLE IF EXISTS ' . _groups_get_tablename( 'user_group' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
576 + $wpdb->query( 'DROP TABLE IF EXISTS ' . _groups_get_tablename( 'user_capability' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
577 + $wpdb->query( 'DROP TABLE IF EXISTS ' . _groups_get_tablename( 'group_capability' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
327 578 Groups_Options::flush_options();
328 - delete_option( GROUPS_ADMINISTRATOR_ACCESS_OVERRIDE );
579 + if ( class_exists( 'Groups_Admin_Notice' ) ) {
580 + delete_metadata( 'user', null, Groups_Admin_Notice::HIDE_REVIEW_NOTICE, null, true );
581 + delete_site_option( Groups_Admin_Notice::INIT_TIME );
582 + }
583 + delete_option( GROUPS_ADMINISTRATOR_ACCESS_OVERRIDE ); // keep this to delete the deprecated option @since 2.1.1
329 584 delete_option( 'groups_plugin_version' );
330 585 delete_option( 'groups_delete_data' );
331 586 }
332 587 }
333 -
588 +
334 589 /**
590 + * Whether this is an individual activation of the plugin.
591 + *
592 + * @since 3.3.1
593 + *
594 + * @return boolean
595 + */
596 + private static function is_single_activate() {
597 + $is = false;
598 + $groups_basename = plugin_basename( GROUPS_FILE );
599 + $action = groups_sanitize_request( 'action' );
600 + if ( is_string( $action ) ) {
601 + switch ( $action ) {
602 + case 'activate':
603 + // Single plugin activation of Groups:
604 + $slug = groups_sanitize_request( 'plugin' );
605 + if ( $slug === $groups_basename ) {
606 + $is = true;
607 + }
608 + break;
609 + case 'activate-selected':
610 + // Bulk plugin activation of Groups but it is the only plugin being activated:
611 + $slugs = groups_sanitize_request( 'checked' );
612 + if ( is_array( $slugs ) && count( $slugs ) === 1 ) {
613 + $slug = array_pop( $slugs );
614 + if ( $slug === $groups_basename ) {
615 + $is = true;
616 + break;
617 + }
618 + }
619 + break;
620 + }
621 + }
622 + return $is;
623 + }
624 +
625 + /**
335 626 * Determines the default capabilities for the administrator role.
336 627 * In lack of an administrator role, these capabilities are assigned
337 628 * to any role that can manage_options.
338 629 * This is also used to assure a minimum set of capabilities is
@@ -339,9 +630,9 @@
339 630 * assigned to an appropriate role, so that it's not possible
340 631 * to lock yourself out (although deactivating and then activating
341 632 * the plugin would have the same effect but with the danger of
342 633 * deleting all plugin data).
343 - * @param boolean $activate defaults to true, when this function is called upon plugin activation
634 + *
344 635 * @access private
345 636 */
346 637 public static function set_default_capabilities() {
347 638 global $wp_roles;
@@ -350,8 +641,9 @@
350 641 if ( $administrator_role = $wp_roles->get_role( 'administrator' ) ) {
351 642 $administrator_role->add_cap( GROUPS_ACCESS_GROUPS );
352 643 $administrator_role->add_cap( GROUPS_ADMINISTER_GROUPS );
353 644 $administrator_role->add_cap( GROUPS_ADMINISTER_OPTIONS );
645 + $administrator_role->add_cap( GROUPS_RESTRICT_ACCESS );
354 646 } else {
355 647 foreach ( $wp_roles->role_objects as $role ) {
356 648 if ($role->has_cap( 'manage_options' ) ) {
357 649 $role->add_cap( GROUPS_ACCESS_GROUPS );
@@ -356,13 +648,14 @@
356 648 if ($role->has_cap( 'manage_options' ) ) {
357 649 $role->add_cap( GROUPS_ACCESS_GROUPS );
358 650 $role->add_cap( GROUPS_ADMINISTER_GROUPS );
359 651 $role->add_cap( GROUPS_ADMINISTER_OPTIONS );
652 + $role->add_cap( GROUPS_RESTRICT_ACCESS );
360 653 }
361 654 }
362 655 }
363 656 }
364 -
657 +
365 658 /**
366 659 * There must be at least one role with the minimum set of capabilities
367 660 * to access and manage the Groups plugin's options.
368 661 * If this condition is not met, the minimum set of capabilities is
@@ -371,9 +664,9 @@
371 664 public static function assure_capabilities() {
372 665 global $wp_roles;
373 666 $complies = false;
374 667 $roles = $wp_roles->role_objects;
375 - foreach( $roles as $role ) {
668 + foreach ( $roles as $role ) {
376 669 if ( $role->has_cap( GROUPS_ACCESS_GROUPS ) && ( $role->has_cap( GROUPS_ADMINISTER_OPTIONS ) ) ) {
377 670 $complies = true;
378 671 break;
379 672 }
@@ -382,6 +675,104 @@
382 675 self::set_default_capabilities();
383 676 }
384 677 }
385 678
679 + /**
680 + * Guarded sem_get() wrapper.
681 + *
682 + * @see sem_get()
683 + *
684 + * @param int $key
685 + * @param number $max_acquire
686 + * @param number $perm
687 + * @param number $auto_release
688 + *
689 + * @return boolean|resource
690 + */
691 + private static function sem_get( $key, $max_acquire = 1, $perm = 0666, $auto_release = 1 ) {
692 + $result = false;
693 + if ( function_exists( 'sem_get' ) ) {
694 + $result = sem_get( $key, $max_acquire, $perm, $auto_release );
695 + }
696 + return $result;
697 + }
698 +
699 + /**
700 + * Guarded sem_acquire() wrapper.
701 + *
702 + * To maintain backwards-compatibility with servers running PHP < 5.6 where
703 + * the second parameter to sem_acquire() is not supported, we use sem_remove()
704 + * and have any calls waiting on sem_acquire() fail silently (achieving that
705 + * the activation, update or deactivation routines are not run for those
706 + * processes that have been waiting and which would have duplicated execution
707 + * unnecessarily).
708 + *
709 + * @see sem_acquire()
710 + *
711 + * @param resource $sem_identifier
712 + * @param boolean $nowait (only taken into account and effective on PHP >= 5.6.1)
713 + *
714 + * @return boolean
715 + */
716 + private static function sem_acquire( $sem_identifier, $nowait = false ) {
717 + $result = false;
718 + if ( function_exists( 'sem_acquire' ) ) {
719 + if ( version_compare( phpversion(), '5.6.1' ) >= 0 ) {
720 + $result = @sem_acquire( $sem_identifier, $nowait );
721 + } else {
722 + $result = @sem_acquire( $sem_identifier );
723 + }
724 + }
725 + return $result;
726 + }
727 +
728 + /**
729 + * Guarded sem_release() wrapper.
730 + *
731 + * @see sem_release()
732 + *
733 + * @param resource $sem_identifier
734 + *
735 + * @return boolean
736 + */
737 + private static function sem_release( $sem_identifier ) {
738 + $result = false;
739 + if ( function_exists( 'sem_release' ) ) {
740 + $result = @sem_release( $sem_identifier );
741 + }
742 + return $result;
743 + }
744 +
745 + /**
746 + * Guarded sem_remove() wrapper.
747 + *
748 + * @see sem_remove()
749 + *
750 + * @param resource $sem_identifier
751 + *
752 + * @return boolean
753 + */
754 + private static function sem_remove( $sem_identifier ) {
755 + $result = false;
756 + if ( function_exists( 'sem_remove' ) ) {
757 + $result = @sem_remove( $sem_identifier );
758 + }
759 + return $result;
760 + }
761 +
762 + /**
763 + * Produces a file-based key for use with sem_get().
764 + *
765 + * @return number
766 + */
767 + private static function get_sem_key() {
768 + $key = -1;
769 + if ( function_exists( 'ftok' ) ) {
770 + $key = ftok( __FILE__, 'g' );
771 + }
772 + if ( $key == -1 ) {
773 + $key = fileinode( __FILE__ );
774 + }
775 + return $key;
776 + }
386 777 }
387 778 Groups_Controller::boot();