<?php /** * Abstraction of WordPress roles and capabilities and how they apply to Loco. * * - Currently only one capability exists, proving full access "loco_admin" * - Any user with super admin privileges automatically inherits this permission * - A single custom role is added called "translator" */ class Loco_data_Permissions { /** * Loco capabilities applicable to roles * @var array */ private static $caps = ['loco_admin']; /** * Polyfill for wp_roles which requires WP >= 4.3 * @return WP_Roles */ private static function wp_roles(){ global $wp_roles; if( ! isset($wp_roles) ){ get_role('ping'); } return $wp_roles; } /** * Set up default roles and capabilities * @return WP_Roles */ public static function init(){ $roles = self::wp_roles(); $apply = []; // ensure translator role exists and is not locked out $role = $roles->get_role('translator'); if( $role instanceof WP_Role ){ $role->has_cap('read') || $role->add_cap('read'); } // else absence of translator role indicates first run // by default we'll initially allow full access to anyone that can manage_options else { $apply['translator'] = $roles->add_role( 'translator', 'Translator', ['read'=>true] ); foreach( $roles->role_objects as $id => $role ){ if( $role->has_cap('manage_options') ){ $apply[$id] = $role; } } } // fix broken permissions whereby super admin cannot access Loco at all. // this could happen if another plugin added the translator role beforehand. if( ! isset($apply['administrator']) && ! is_multisite() ){ $apply['administrator'] = $roles->get_role('administrator'); } foreach( $apply as $role ){ if( $role instanceof WP_Role ){ foreach( self::$caps as $cap ){ $role->has_cap($cap) || $role->add_cap($cap); } } } return $roles; } /** * Construct instance, ensuring default roles and capabilities exist */ public function __construct(){ self::init(); } /** * @return WP_Role[] */ public function getRoles(){ $roles = self::wp_roles(); return $roles->role_objects; } /** * Check if role is protected such that user cannot lock themselves out when modifying settings * @param WP_Role WordPress role object to check * @return bool */ public function isProtectedRole( WP_Role $role ){ // if current user has this role and is not the super user, prevent lock-out $user = wp_get_current_user(); if( $user instanceof WP_User && ! is_super_admin($user->ID) && $user->has_cap('manage_options') ){ return in_array( $role->name, $user->roles, true ); } // admin users of single site install must never be denied access // note that there is no such thing as a network admin role, but network admins have all permissions return is_multisite() ? false : $role->has_cap('delete_users'); } /** * Completely remove all Loco permissions, as if uninstalling * @return Loco_data_Permissions */ public function remove(){ /* @var $role WP_Role */ foreach( $this->getRoles() as $role ){ foreach( self::$caps as $cap ){ $role->has_cap($cap) && $role->remove_cap($cap); } } // we'll only remove our custom role if it has no capabilities other than admin access // this avoids breaking other plugins that use it, or added it before Loco was installed. if( $role = get_role('translator') ){ if( ! $role->capabilities || ['read'] === array_keys($role->capabilities) ){ remove_role('translator'); } } return $this; } /** * Reset to default: roles include no Loco capabilities unless they have super admin privileges * @return WP_Role[] */ public function reset(){ $roles = $this->getRoles(); /* @var $role WP_Role */ foreach( $roles as $role ){ // always provide access to site admins on first run $grant = $this->isProtectedRole($role); foreach( self::$caps as $cap ){ if( $grant ){ $role->has_cap($cap) || $role->add_cap($cap); } else { $role->has_cap($cap) && $role->remove_cap($cap); } } } return $roles; } /** * Get translated WordPress role name * @param string * @return string */ public function getRoleName( $id ){ if( 'translator' === $id ){ $label = _x( 'Translator', 'User role', 'loco-translate' ); } else { $names = self::wp_roles()->role_names; $label = isset($names[$id]) ? translate_user_role( $names[$id] ) : $id; } return $label; } /** * Populate permission settings from posted checkboxes * @param string[] * @return self */ public function populate( array $caps ){ // drop all permissions before adding (cos checkboxes) $roles = $this->reset(); foreach( $caps as $id => $checked ){ if( isset($roles[$id]) ){ $role = $roles[$id]; /* @var $role WP_Role */ foreach( self::$caps as $cap ){ if( ! empty($checked[$cap]) ){ $role->has_cap($cap) || $role->add_cap($cap); } } } } return $this; } }