build_modules($modules_data); } /** * Returns the module via magic getter. * * @since 2.0.0 * * @param string $name The module name. * @return \WP_Ultimo\Limitations\Limit */ public function __get($name) { $module = wu_get_isset($this->modules, $name, false); if (false === $module) { $repo = self::repository(); $class_name = wu_get_isset($repo, $name, false); if (class_exists($class_name)) { $module = new $class_name([]); $this->modules[ $name ] = $module; return $module; } } return $module; } /** * Prepare to serialization. * * @see requires php 7.3 * @since 2.0.0 * @return array */ public function __serialize() { // phpcs:ignore return $this->to_array(); } /** * Handles un-serialization. * * @since 2.0.0 * * @see requires php 7.3 * @param array $modules_data Array of modules data. * @return void */ public function __unserialize($modules_data) { // phpcs:ignore $this->build_modules($modules_data); } /** * Builds the module list based on the module data. * * @since 2.0.0 * * @param array $modules_data Array of modules data. * @return self */ public function build_modules($modules_data) { foreach ($modules_data as $type => $data) { $module = self::build($data, $type); if ($module) { $this->modules[ $type ] = $module; } } return $this; } /** * Build a module, based on the data. * * @since 2.0.0 * * @param string|array $data The module data. * @param string $module_name The module_name. * @return false|\WP_Ultimo\Limitations\Limit */ public static function build($data, $module_name) { $class = wu_get_isset(self::repository(), $module_name); if (class_exists($class)) { if (is_string($data)) { $data = json_decode($data, true); } return new $class($data); } return false; } /** * Checks if a limitation model exists in this limitations. * * @since 2.0.0 * * @param string $module The module name. * @return bool */ public function exists($module) { return wu_get_isset($this->modules, $module, false); } /** * Checks if we have any limitation modules setup at all. * * @since 2.0.0 * @return boolean */ public function has_limitations() { $has_limitations = false; foreach ($this->modules as $module) { if ($module->is_enabled()) { return true; } } return $has_limitations; } /** * Checks if a particular module is enabled. * * @since 2.0.0 * * @param string $module_name Module name. * @return boolean */ public function is_module_enabled($module_name) { $module = $this->{$module_name}; return $module ? $module->is_enabled() : false; } /** * Merges limitations from other entities. * * This is what we use to combine different limitations from * different sources. For example: we override the limitations * of site with restrictions coming from the membership, * products, etc. * * @since 2.0.0 * * @param array|bool $override A limitation array or a boolean to override the values from first to last limitation. * @param array ...$limitations Limitation arrays. * @return self */ public function merge($override = false, ...$limitations) { if ( ! is_bool($override)) { $limitations[] = $override; $override = false; } $results = $this->to_array(); foreach ($limitations as $limitation) { if (is_a($limitation, self::class)) { // @phpstan-ignore-line $limitation = $limitation->to_array(); } if ( ! is_array($limitation)) { continue; } $this->merge_recursive($results, $limitation, ! $override); } return new self($results); } /** * Merges a limitation array * * @since 2.0.20 * * @param array $array1 The arrays original. * @param array $array2 The array to be merged in. * @param bool $should_sum If we should add up numeric values instead of replacing the original. * @return void */ protected function merge_recursive(array &$array1, array &$array2, $should_sum = true) { $current_id = $this->current_merge_id; $force_enabled_list = [ 'plugins', 'themes', ]; $force_enabled = in_array($current_id, $force_enabled_list, true); if ($force_enabled && (! wu_get_isset($array1, 'enabled', true) || ! wu_get_isset($array2, 'enabled', true))) { $array1['enabled'] = true; $array2['enabled'] = true; } if ( ! wu_get_isset($array1, 'enabled', true)) { $array1 = [ 'enabled' => false, ]; } if ( ! wu_get_isset($array2, 'enabled', true) && $should_sum) { return; } foreach ($array2 as $key => &$value) { /** * Here we need to work with arrays and some limits * as themes and plugins have stdClass values. */ $value = is_object($value) ? get_object_vars($value) : $value; if (isset($array1[ $key ])) { $array1[ $key ] = is_object($array1[ $key ]) ? get_object_vars($array1[ $key ]) : $array1[ $key ]; } if (is_array($value) && isset($array1[ $key ]) && is_array($array1[ $key ])) { $array1_id = wu_get_isset($array1[ $key ], 'id', $current_id); $this->current_merge_id = wu_get_isset($value, 'id', $array1_id); $this->merge_recursive($array1[ $key ], $value, $should_sum); $this->current_merge_id = $current_id; } else { $original_value = wu_get_isset($array1, $key); // If the value is 0 or '' it can be a unlimited value $is_unlimited = (is_numeric($value) || '' === $value) && (int) $value === 0; if ($should_sum && ('' === $original_value || 0 === $original_value)) { /** * We use values 0 or '' as unlimited in our limits */ continue; } elseif (isset($array1[ $key ]) && is_numeric($array1[ $key ]) && is_numeric($value) && $should_sum && ! $is_unlimited) { $array1[ $key ] = ((int) $array1[ $key ]) + $value; } elseif ('visibility' === $key && isset($array1[ $key ]) && $should_sum) { $key_priority = [ 'hidden' => 0, 'visible' => 1, ]; $array1[ $key ] = $key_priority[ $value ] > $key_priority[ $array1[ $key ] ] ? $value : $array1[ $key ]; } elseif ('behavior' === $key && isset($array1[ $key ]) && $should_sum) { $key_priority_list = [ 'plugins' => [ 'default' => 10, 'force_inactive_locked' => 20, 'force_inactive' => 30, 'force_active_locked' => 40, 'force_active' => 50, ], 'site' => [ 'not_available' => 10, 'available' => 20, 'pre_selected' => 30, ], 'themes' => [ 'not_available' => 10, 'available' => 20, 'force_active' => 30, ], ]; $key_priority = apply_filters("wu_limitation_{$current_id}_priority", $key_priority_list[ $current_id ]); $array1[ $key ] = $key_priority[ $value ] > $key_priority[ $array1[ $key ] ] ? $value : $array1[ $key ]; } else { // Avoid change true values $array1[ $key ] = true !== $original_value || ! $should_sum ? $value : true; $array1[ $key ] = true !== $original_value || ! $should_sum ? $value : true; } } } } /** * Converts the limitations list to an array. * * @since 2.0.0 */ public function to_array(): array { return array_map(fn($module) => method_exists($module, 'to_array') ? $module->to_array() : (array) $module, $this->modules); } /** * Static method to return limitations in very early stages of the WordPress lifecycle. * * @since 2.0.0 * * @param string $slug Slug of the model. * @param int $id ID of the model. * @return \WP_Ultimo\Objects\Limitations */ public static function early_get_limitations($slug, $id) { $wu_prefix = 'wu_'; /* * Reset the slug and prefixes * for the native tables of blogs. */ if ('site' === $slug) { $slug = 'blog'; $wu_prefix = ''; } $cache = static::$limitations_cache; $key = sprintf('%s-%s', $slug, $id); if (isset($cache[ $key ])) { return $cache[ $key ]; } global $wpdb; $limitations = []; $table_name = "{$wpdb->base_prefix}{$wu_prefix}{$slug}meta"; $sql = $wpdb->prepare("SELECT meta_value FROM {$table_name} WHERE meta_key = 'wu_limitations' AND {$wu_prefix}{$slug}_id = %d LIMIT 1", $id); // phpcs:ignore $results = $wpdb->get_var($sql); // phpcs:ignore if ( ! empty($results)) { $limitations = unserialize($results); } /* * Caches the results. */ static::$limitations_cache[ $key ] = $limitations; return $limitations; } /** * Delete limitations. * * @since 2.0.0 * * @param string $slug The slug of the model. * @param int $id The id of the meta id. * @return void */ public static function remove_limitations($slug, $id): void { global $wpdb; $wu_prefix = 'wu_'; /* * Site apis are already available, * so no need to use low-level sql calls. */ if ('site' === $slug) { $wu_prefix = ''; $slug = 'blog'; } $table_name = "{$wpdb->base_prefix}{$wu_prefix}{$slug}meta"; $sql = $wpdb->prepare("DELETE FROM {$table_name} WHERE meta_key = 'wu_limitations' AND {$wu_prefix}{$slug}_id = %d LIMIT 1", $id); // phpcs:ignore $wpdb->get_var($sql); // phpcs:ignore } /** * Returns an empty permission set, with modules. * * @since 2.0.0 * @return self */ public static function get_empty() { $limitations = new self(); foreach (array_keys(self::repository()) as $module_name) { $limitations->{$module_name}; } return $limitations; } /** * Repository of the limitation modules. * * @see wu_register_limit_module() * * @since 2.0.0 * @return array */ public static function repository() { $classes = [ 'post_types' => \WP_Ultimo\Limitations\Limit_Post_Types::class, 'plugins' => \WP_Ultimo\Limitations\Limit_Plugins::class, 'sites' => \WP_Ultimo\Limitations\Limit_Sites::class, 'themes' => \WP_Ultimo\Limitations\Limit_Themes::class, 'visits' => \WP_Ultimo\Limitations\Limit_Visits::class, 'disk_space' => \WP_Ultimo\Limitations\Limit_Disk_Space::class, 'users' => \WP_Ultimo\Limitations\Limit_Users::class, 'site_templates' => \WP_Ultimo\Limitations\Limit_Site_Templates::class, 'domain_mapping' => \WP_Ultimo\Limitations\Limit_Domain_Mapping::class, 'customer_user_role' => \WP_Ultimo\Limitations\Limit_Customer_User_Role::class, ]; return apply_filters('wu_limit_classes', $classes); } }