看板初始化提交

This commit is contained in:
zephyr
2026-06-01 21:23:12 -07:00
commit 54a842f4ab
2104 changed files with 241695 additions and 0 deletions
+93
View File
@@ -0,0 +1,93 @@
<?php
namespace Kanboard\Core\User\Avatar;
/**
* Avatar Manager
*
* @package avatar
* @author Frederic Guillot
*/
class AvatarManager
{
/**
* Providers
*
* @access private
* @var AvatarProviderInterface[]
*/
private $providers = array();
/**
* Register a new Avatar provider
*
* @access public
* @param AvatarProviderInterface $provider
* @return $this
*/
public function register(AvatarProviderInterface $provider)
{
$this->providers[] = $provider;
return $this;
}
/**
* Render avatar HTML element
*
* @access public
* @param string $user_id
* @param string $username
* @param string $name
* @param string $email
* @param string $avatar_path
* @param int $size
* @return string
*/
public function render($user_id, $username, $name, $email, $avatar_path, $size)
{
$user = array(
'id' => $user_id,
'username' => $username,
'name' => $name,
'email' => $email,
'avatar_path' => $avatar_path,
);
krsort($this->providers);
foreach ($this->providers as $provider) {
if ($provider->isActive($user)) {
return $provider->render($user, $size);
}
}
return '';
}
/**
* Render default provider for unknown users (first provider registered)
*
* @access public
* @param integer $size
* @return string
*/
public function renderDefault($size)
{
if (count($this->providers) > 0) {
ksort($this->providers);
$provider = current($this->providers);
$user = array(
'id' => 0,
'username' => '',
'name' => '?',
'email' => '',
'avatar_path' => '',
);
return $provider->render($user, $size);
}
return '';
}
}
@@ -0,0 +1,30 @@
<?php
namespace Kanboard\Core\User\Avatar;
/**
* Avatar Provider Interface
*
* @package user
* @author Frederic Guillot
*/
interface AvatarProviderInterface
{
/**
* Render avatar html
*
* @access public
* @param array $user
* @param int $size
*/
public function render(array $user, $size);
/**
* Determine if the provider is active
*
* @access public
* @param array $user
* @return boolean
*/
public function isActive(array $user);
}
+65
View File
@@ -0,0 +1,65 @@
<?php
namespace Kanboard\Core\User;
use Kanboard\Core\Base;
/**
* Group Synchronization
*
* @package user
* @author Frederic Guillot
*/
class GroupSync extends Base
{
/**
* Synchronize group membership
*
* @access public
* @param integer $userId
* @param string[] $externalGroupIds
*/
public function synchronize($userId, array $externalGroupIds)
{
$userGroups = $this->groupMemberModel->getGroups($userId);
$this->addGroups($userId, $userGroups, $externalGroupIds);
$this->removeGroups($userId, $userGroups, $externalGroupIds);
}
/**
* Add missing groups to the user
*
* @access protected
* @param integer $userId
* @param array $userGroups
* @param string[] $externalGroupIds
*/
protected function addGroups($userId, array $userGroups, array $externalGroupIds)
{
$userGroupIds = array_column($userGroups, 'external_id', 'external_id');
$externalGroups = $this->groupModel->getByExternalIds($externalGroupIds);
foreach ($externalGroups as $externalGroup) {
if (! isset($userGroupIds[$externalGroup['external_id']])) {
$this->groupMemberModel->addUser($externalGroup['id'], $userId);
}
}
}
/**
* Remove groups from the user
*
* @access protected
* @param integer $userId
* @param array $userGroups
* @param string[] $externalGroupIds
*/
protected function removeGroups($userId, array $userGroups, array $externalGroupIds)
{
foreach ($userGroups as $userGroup) {
if (! empty($userGroup['external_id']) && ! in_array($userGroup['external_id'], $externalGroupIds)) {
$this->groupMemberModel->removeUser($userGroup['id'], $userId);
}
}
}
}
@@ -0,0 +1,21 @@
<?php
namespace Kanboard\Core\User;
/**
* User Backend Provider Interface
*
* @package Kanboard\Core\User
* @author Frederic Guillot
*/
interface UserBackendProviderInterface
{
/**
* Find a user from a search query
*
* @access public
* @param string $input
* @return UserProviderInterface[]
*/
public function find($input);
}
+71
View File
@@ -0,0 +1,71 @@
<?php
namespace Kanboard\Core\User;
/**
* User Manager
*
* @package Kanboard\Core\User
* @author Frederic Guillot
*/
class UserManager
{
/**
* List of backend providers
*
* @access protected
* @var array
*/
protected $providers = array();
/**
* Register a new group backend provider
*
* @access public
* @param UserBackendProviderInterface $provider
* @return $this
*/
public function register(UserBackendProviderInterface $provider)
{
$this->providers[] = $provider;
return $this;
}
/**
* Find a group from a search query
*
* @access public
* @param string $input
* @return UserProviderInterface[]
*/
public function find($input)
{
$groups = array();
foreach ($this->providers as $provider) {
$groups = array_merge($groups, $provider->find($input));
}
return $this->removeDuplicates($groups);
}
/**
* Remove duplicated users
*
* @access protected
* @param array $users
* @return UserProviderInterface[]
*/
protected function removeDuplicates(array $users)
{
$result = array();
foreach ($users as $user) {
if (! isset($result[$user->getUsername()])) {
$result[$user->getUsername()] = $user;
}
}
return array_values($result);
}
}
+68
View File
@@ -0,0 +1,68 @@
<?php
namespace Kanboard\Core\User;
use Kanboard\Core\Base;
use Kanboard\Event\UserProfileSyncEvent;
/**
* User Profile
*
* @package user
* @author Frederic Guillot
*/
class UserProfile extends Base
{
const EVENT_USER_PROFILE_AFTER_SYNC = 'user_profile.after.sync';
/**
* Assign provider data to the local user
*
* @access public
* @param integer $userId
* @param UserProviderInterface $user
* @return boolean
*/
public function assign($userId, UserProviderInterface $user)
{
$profile = $this->userModel->getById($userId);
$values = UserProperty::filterProperties($profile, UserProperty::getProperties($user));
$values['id'] = $userId;
if ($this->userModel->update($values)) {
$profile = array_merge($profile, $values);
$this->userSession->initialize($profile);
return true;
}
return false;
}
/**
* Synchronize user properties with the local database and create the user session
*
* @access public
* @param UserProviderInterface $user
* @return boolean
*/
public function initialize(UserProviderInterface $user)
{
if ($user->getInternalId()) {
$profile = $this->userModel->getById($user->getInternalId());
} elseif ($user->getExternalIdColumn() && $user->getExternalId()) {
$profile = $this->userSync->synchronize($user); // The profile can be null when external user creation is disabled
if (LDAP_GROUP_SYNC && ! empty($profile)) {
$this->groupSync->synchronize($profile['id'], $user->getExternalGroupIds());
}
}
if (! empty($profile) && $profile['is_active'] == 1) {
$this->userSession->initialize($profile);
$this->dispatcher->dispatch(new UserProfileSyncEvent($profile, $user), self::EVENT_USER_PROFILE_AFTER_SYNC);
return true;
}
return false;
}
}
+74
View File
@@ -0,0 +1,74 @@
<?php
namespace Kanboard\Core\User;
/**
* User Property
*
* @package user
* @author Frederic Guillot
*/
class UserProperty
{
/**
* Get filtered user properties from user provider
*
* @static
* @access public
* @param UserProviderInterface $user
* @return array
*/
public static function getProperties(UserProviderInterface $user)
{
$properties = array(
'username' => $user->getUsername(),
'name' => $user->getName(),
'email' => $user->getEmail(),
'role' => $user->getRole(),
$user->getExternalIdColumn() => $user->getExternalId(),
);
$properties = array_merge($properties, $user->getExtraAttributes());
return array_filter($properties, array(__NAMESPACE__.'\UserProperty', 'isNotEmptyValue'));
}
/**
* Filter user properties compared to existing user profile
*
* @static
* @access public
* @param array $profile
* @param array $properties
* @return array
*/
public static function filterProperties(array $profile, array $properties)
{
$excludedProperties = explode_csv_field(EXTERNAL_AUTH_EXCLUDE_FIELDS);
$values = array();
foreach ($properties as $property => $value) {
if (self::isNotEmptyValue($value) &&
! in_array($property, $excludedProperties) &&
array_key_exists($property, $profile) &&
$value !== $profile[$property]) {
$values[$property] = $value;
}
}
return $values;
}
/**
* Check if a value is not empty
*
* @static
* @access public
* @param string $value
* @return boolean
*/
public static function isNotEmptyValue($value)
{
return $value !== null && $value !== '';
}
}
+103
View File
@@ -0,0 +1,103 @@
<?php
namespace Kanboard\Core\User;
/**
* User Provider Interface
*
* @package user
* @author Frederic Guillot
*/
interface UserProviderInterface
{
/**
* Return true to allow automatic user creation
*
* @access public
* @return boolean
*/
public function isUserCreationAllowed();
/**
* Get external id column name
*
* Example: google_id, github_id, gitlab_id...
*
* @access public
* @return string
*/
public function getExternalIdColumn();
/**
* Get internal id
*
* If a value is returned the user properties won't be updated in the local database
*
* @access public
* @return integer
*/
public function getInternalId();
/**
* Get external id
*
* @access public
* @return string
*/
public function getExternalId();
/**
* Get user role
*
* Return an empty string to not override role stored in the database
*
* @access public
* @return string
*/
public function getRole();
/**
* Get username
*
* @access public
* @return string
*/
public function getUsername();
/**
* Get user full name
*
* @access public
* @return string
*/
public function getName();
/**
* Get user email
*
* @access public
* @return string
*/
public function getEmail();
/**
* Get external group ids
*
* A synchronization is done at login time,
* the user will be member of those groups if they exists in the database
*
* @access public
* @return string[]
*/
public function getExternalGroupIds();
/**
* Get extra user attributes
*
* Example: is_ldap_user, disable_login_form, notifications_enabled...
*
* @access public
* @return array
*/
public function getExtraAttributes();
}
+300
View File
@@ -0,0 +1,300 @@
<?php
namespace Kanboard\Core\User;
use Kanboard\Core\Base;
use Kanboard\Core\Security\Role;
/**
* User Session
*
* @package user
* @author Frederic Guillot
*/
class UserSession extends Base
{
/**
* Refresh current session if necessary
*
* @access public
* @param integer $user_id
*/
public function refresh($user_id)
{
if ($this->getId() == $user_id) {
$this->initialize($this->userModel->getById($user_id));
}
}
/**
* Update user session
*
* @access public
* @param array $user
*/
public function initialize(array $user)
{
foreach (array('password', 'is_admin', 'is_project_admin', 'twofactor_secret') as $column) {
if (isset($user[$column])) {
unset($user[$column]);
}
}
$user['id'] = (int) $user['id'];
$user['is_ldap_user'] = isset($user['is_ldap_user']) ? (bool) $user['is_ldap_user'] : false;
$user['twofactor_activated'] = isset($user['twofactor_activated']) ? (bool) $user['twofactor_activated'] : false;
if (session_status() === PHP_SESSION_ACTIVE) {
// Note: Do not delete the old session to avoid possible race condition and a PHP warning.
session_regenerate_id(false);
}
session_set('user', $user);
session_set('postAuthenticationValidated', false);
}
/**
* Get user properties
*
* @access public
* @return array
*/
public function getAll()
{
return session_get('user');
}
/**
* Get user application role
*
* @access public
* @return string
*/
public function getRole()
{
if (! $this->isLogged()) {
return '';
}
return session_get('user')['role'];
}
/**
* Return true if the user has validated the 2FA key
*
* @access public
* @return bool
*/
public function isPostAuthenticationValidated()
{
return session_is_true('postAuthenticationValidated');
}
/**
* Validate 2FA for the current session
*
* @access public
*/
public function setPostAuthenticationAsValidated()
{
session_set('postAuthenticationValidated', true);
}
/**
* Return true if the user has 2FA enabled
*
* @access public
* @return bool
*/
public function hasPostAuthentication()
{
if (! $this->isLogged()) {
return false;
}
return session_get('user')['twofactor_activated'] === true;
}
/**
* Disable 2FA for the current session
*
* @access public
*/
public function disablePostAuthentication()
{
session_merge('user', ['twofactor_activated' => false]);
}
/**
* Return true if the logged user is admin
*
* @access public
* @return bool
*/
public function isAdmin()
{
return $this->getRole() === Role::APP_ADMIN;
}
/**
* Get the connected user id
*
* @access public
* @return integer
*/
public function getId()
{
if (! $this->isLogged()) {
return 0;
}
return session_get('user')['id'];
}
/**
* Get username
*
* @access public
* @return string
*/
public function getUsername()
{
if (! $this->isLogged()) {
return '';
}
return session_get('user')['username'];
}
/**
* Get user language
*
* @access public
* @return string
*/
public function getLanguage()
{
if (! $this->isLogged()) {
return '';
}
return session_get('user')['language'];
}
/**
* Get user timezone
*
* @access public
* @return string
*/
public function getTimezone()
{
if (! $this->isLogged()) {
return '';
}
return session_get('user')['timezone'];
}
/**
* Get user theme
*
* @access public
* @return string
*/
public function getTheme()
{
if (! $this->isLogged()) {
return 'light';
}
$user_session = session_get('user');
if (array_key_exists('theme', $user_session)) {
return $user_session['theme'];
}
return 'light';
}
/**
* Return true if subtask list toggle is active
*
* @access public
* @return string
*/
public function hasSubtaskListActivated()
{
return session_is_true('subtaskListToggle');
}
/**
* Check is the user is connected
*
* @access public
* @return bool
*/
public function isLogged()
{
return session_exists('user') && session_get('user') !== [];
}
/**
* Get project filters from the session
*
* @access public
* @param integer $projectID
* @return string
*/
public function getFilters($projectID)
{
if (! session_exists('filters:'.$projectID)) {
return session_get('user') ? session_get('user')['filter'] ?: 'status:open' : 'status:open';
}
return session_get('filters:'.$projectID);
}
/**
* Save project filters in the session
*
* @access public
* @param integer $projectID
* @param string $filters
*/
public function setFilters($projectID, $filters)
{
session_set('filters:'.$projectID, $filters);
}
/**
* Get project list order from the session
*
* @access public
* @param integer $projectID
* @return array
*/
public function getListOrder($projectID)
{
$default = ['tasks.id', 'DESC'];
if (! session_exists('listOrder:'.$projectID)) {
return $default;
}
return session_get('listOrder:'.$projectID);
}
/**
* Save project list order in the session
*
* @access public
* @param integer $projectID
* @param string $listOrder
* @param string $listDirection
*/
public function setListOrder($projectID, $listOrder, $listDirection)
{
session_set('listOrder:'.$projectID, [$listOrder, $listDirection]);
}
}
+82
View File
@@ -0,0 +1,82 @@
<?php
namespace Kanboard\Core\User;
use Kanboard\Core\Base;
use Kanboard\Notification\MailNotification;
use Kanboard\Notification\WebNotification;
/**
* User Synchronization
*
* @package user
* @author Frederic Guillot
*/
class UserSync extends Base
{
/**
* Synchronize user profile
*
* @access public
* @param UserProviderInterface $user
* @return array|null
*/
public function synchronize(UserProviderInterface $user)
{
$profile = $this->userModel->getByExternalId($user->getExternalIdColumn(), $user->getExternalId());
$properties = UserProperty::getProperties($user);
if (! empty($profile)) {
$profile = $this->updateUser($profile, $properties);
} elseif ($user->isUserCreationAllowed()) {
$profile = $this->createUser($user, $properties);
}
return $profile;
}
/**
* Update user profile
*
* @access public
* @param array $profile
* @param array $properties
* @return array
*/
private function updateUser(array $profile, array $properties)
{
$values = UserProperty::filterProperties($profile, $properties);
if (! empty($values)) {
$values['id'] = $profile['id'];
$result = $this->userModel->update($values);
return $result ? array_merge($profile, $properties) : $profile;
}
return $profile;
}
/**
* Create user
*
* @access public
* @param UserProviderInterface $user
* @param array $properties
* @return array
*/
private function createUser(UserProviderInterface $user, array $properties)
{
$userId = $this->userModel->create($properties);
if ($userId === false) {
$this->logger->error('Unable to create user profile: '.$user->getExternalId());
return array();
}
if ($this->configModel->get('notifications_enabled', 0) == 1) {
$this->userNotificationTypeModel->saveSelectedTypes($userId, [MailNotification::TYPE, WebNotification::TYPE]);
}
return $this->userModel->getById($userId);
}
}