diff --git a/src/Controller/AppController.php b/src/Controller/AppController.php index ada3464..b6d0e29 100644 --- a/src/Controller/AppController.php +++ b/src/Controller/AppController.php @@ -132,7 +132,12 @@ class AppController extends Controller $this->set('ajax', $this->request->is('ajax')); $this->request->getParam('prefix'); $this->set('baseurl', Configure::read('App.fullBaseUrl')); - $this->set('bsTheme', Configure::read('Cerebrate')['ui.bsTheme']); + + if (!empty($user) && !empty($user->user_settings_by_name_with_fallback['ui.bsTheme']['value'])) { + $this->set('bsTheme', $user->user_settings_by_name_with_fallback['ui.bsTheme']['value']); + } else { + $this->set('bsTheme', Configure::read('Cerebrate')['ui.bsTheme']); + } if ($this->modelClass == 'Tags.Tags') { $this->set('metaGroup', !empty($this->isAdmin) ? 'Administration' : 'Cerebrate'); diff --git a/src/Controller/Component/Navigation/UserSettings.php b/src/Controller/Component/Navigation/UserSettings.php index 84efa25..4574b13 100644 --- a/src/Controller/Component/Navigation/UserSettings.php +++ b/src/Controller/Component/Navigation/UserSettings.php @@ -13,7 +13,7 @@ class UserSettingsNavigation extends BaseNavigation if (!empty($request->getQuery('Users_id'))) { $user_id = h($request->getQuery('Users_id')); $linkData = [ - 'label' => __('View user ({0})', h($user_id)), + 'label' => __('View user [{0}]', h($user_id)), 'url' => sprintf('/users/view/%s', h($user_id)) ]; return $linkData; @@ -24,7 +24,7 @@ class UserSettingsNavigation extends BaseNavigation if (!empty($request->getQuery('Users_id'))) { $user_id = h($request->getQuery('Users_id')); $linkData = [ - 'label' => __('Edit user ({0})', h($user_id)), + 'label' => __('Edit user [{0}]', h($user_id)), 'url' => sprintf('/users/edit/%s', h($user_id)) ]; return $linkData; diff --git a/src/Controller/Component/Navigation/Users.php b/src/Controller/Component/Navigation/Users.php index ea76f15..7e228db 100644 --- a/src/Controller/Component/Navigation/Users.php +++ b/src/Controller/Component/Navigation/Users.php @@ -5,31 +5,83 @@ require_once(APP . 'Controller' . DS . 'Component' . DS . 'Navigation' . DS . 'b class UsersNavigation extends BaseNavigation { + public function addRoutes() + { + $this->bcf->addRoute('Users', 'settings', [ + 'label' => __('User settings'), + 'url' => '/users/settings/', + 'icon' => 'user-cog' + ]); + } + + public function addParents() + { + // $this->bcf->addParent('Users', 'settings', 'Users', 'view'); + } + public function addLinks() { $bcf = $this->bcf; $request = $this->request; - $this->bcf->addLink('Users', 'view', 'UserSettings', 'index', function ($config) use ($bcf, $request) { - if (!empty($this->passedData[0])) { - $user_id = $this->passedData[0]; + $passedData = $this->request->getParam('pass'); + $this->bcf->addLink('Users', 'view', 'UserSettings', 'index', function ($config) use ($bcf, $request, $passedData) { + if (!empty($passedData[0])) { + $user_id = $passedData[0]; $linkData = [ - 'label' => __('User Setting ({0})', h($user_id)), + 'label' => __('Account settings', h($user_id)), + 'url' => sprintf('/users/settings/%s', h($user_id)) + ]; + return $linkData; + } + return []; + }); + $this->bcf->addLink('Users', 'view', 'UserSettings', 'index', function ($config) use ($bcf, $request, $passedData) { + if (!empty($passedData[0])) { + $user_id = $passedData[0]; + $linkData = [ + 'label' => __('User Setting [{0}]', h($user_id)), 'url' => sprintf('/user-settings/index?Users.id=%s', h($user_id)) ]; return $linkData; } return []; }); - $this->bcf->addLink('Users', 'edit', 'UserSettings', 'index', function ($config) use ($bcf, $request) { - if (!empty($this->passedData[0])) { - $user_id = $this->passedData[0]; + $this->bcf->addLink('Users', 'edit', 'UserSettings', 'index', function ($config) use ($bcf, $request, $passedData) { + if (!empty($passedData[0])) { + $user_id = $passedData[0]; $linkData = [ - 'label' => __('User Setting ({0})', h($user_id)), + 'label' => __('Account settings', h($user_id)), + 'url' => sprintf('/users/settings/%s', h($user_id)) + ]; + return $linkData; + } + return []; + }); + $this->bcf->addLink('Users', 'edit', 'UserSettings', 'index', function ($config) use ($bcf, $request, $passedData) { + if (!empty($passedData[0])) { + $user_id = $passedData[0]; + $linkData = [ + 'label' => __('User Setting [{0}]', h($user_id)), 'url' => sprintf('/user-settings/index?Users.id=%s', h($user_id)) ]; return $linkData; } return []; }); + + $this->bcf->addLink('Users', 'settings', 'Users', 'view', function ($config) use ($bcf, $request, $passedData) { + if (!empty($passedData[0])) { + $user_id = $passedData[0]; + $linkData = [ + 'label' => __('View user', h($user_id)), + 'url' => sprintf('/users/view/%s', h($user_id)) + ]; + return $linkData; + } + return []; + }); + $this->bcf->addSelfLink('Users', 'settings', [ + 'label' => __('Account settings') + ]); } } diff --git a/src/Controller/Component/NavigationComponent.php b/src/Controller/Component/NavigationComponent.php index f1ad538..f0dd453 100644 --- a/src/Controller/Component/NavigationComponent.php +++ b/src/Controller/Component/NavigationComponent.php @@ -68,7 +68,7 @@ class NavigationComponent extends Component public function getUserBookmarks(): array { $userSettingTable = TableRegistry::getTableLocator()->get('UserSettings'); - $setting = $userSettingTable->getSettingByName($this->request->getAttribute('identity'), 'ui.sidebar.bookmarks'); + $setting = $userSettingTable->getSettingByName($this->request->getAttribute('identity'), 'ui.bookmarks'); $bookmarks = is_null($setting) ? [] : json_decode($setting->value, true); $links = array_map(function($bookmark) { @@ -316,11 +316,11 @@ class BreadcrumbFactory $this->endpoints[$sourceController][$sourceAction]['after'] = $parents; } - public function addSelfLink(string $controller, string $action) + public function addSelfLink(string $controller, string $action, array $options=[]) { - $this->addLink($controller, $action, $controller, $action, [ - 'selfLink' => true - ]); + $this->addLink($controller, $action, $controller, $action, array_merge($options, [ + 'selfLink' => true, + ])); } public function addLink(string $sourceController, string $sourceAction, string $targetController, string $targetAction, $overrides = []) diff --git a/src/Controller/UserSettingsController.php b/src/Controller/UserSettingsController.php index 99c212e..3d73707 100644 --- a/src/Controller/UserSettingsController.php +++ b/src/Controller/UserSettingsController.php @@ -117,7 +117,7 @@ class UserSettingsController extends AppController $this->render('view'); } - public function setSetting($settingsName) + public function setSetting($settingsName = false) { if (!$this->request->is('get')) { $setting = $this->UserSettings->getSettingByName($this->ACL->getUser(), $settingsName); @@ -137,6 +137,39 @@ class UserSettingsController extends AppController $this->set('settingName', $settingsName); } + public function saveSetting() + { + if ($this->request->is('post')) { + $data = $this->ParamHandler->harvestParams([ + 'name', + 'value' + ]); + $setting = $this->UserSettings->getSettingByName($this->ACL->getUser(), $data['name']); + if (is_null($setting)) { // setting not found, create it + $result = $this->UserSettings->createSetting($this->ACL->getUser(), $data['name'], $data['value']); + } else { + $result = $this->UserSettings->editSetting($this->ACL->getUser(), $data['name'], $data['value']); + } + $success = !empty($result); + $message = $success ? __('Setting saved') : __('Could not save setting'); + $this->CRUD->setResponseForController('setSetting', $success, $message, $result); + $responsePayload = $this->CRUD->getResponsePayload(); + if (!empty($responsePayload)) { + return $responsePayload; + } + } + } + + public function getBookmarks($forSidebar=false) + { + $bookmarks = $this->UserSettings->getSettingByName($this->ACL->getUser(), $this->UserSettings->BOOKMARK_SETTING_NAME); + $bookmarks = json_decode($bookmarks['value'], true); + $this->set('user_id', $this->ACL->getUser()->id); + $this->set('bookmarks', $bookmarks); + $this->set('forSidebar', $forSidebar); + $this->render('/element/UserSettings/saved-bookmarks'); + } + public function saveBookmark() { if (!$this->request->is('get')) { @@ -152,4 +185,19 @@ class UserSettingsController extends AppController $this->set('user_id', $this->ACL->getUser()->id); } + public function deleteBookmark() + { + if (!$this->request->is('get')) { + $result = $this->UserSettings->deleteBookmark($this->ACL->getUser(), $this->request->getData()); + $success = !empty($result); + $message = $success ? __('Bookmark deleted') : __('Could not delete bookmark'); + $this->CRUD->setResponseForController('deleteBookmark', $success, $message, $result); + $responsePayload = $this->CRUD->getResponsePayload(); + if (!empty($responsePayload)) { + return $responsePayload; + } + } + $this->set('user_id', $this->ACL->getUser()->id); + } + } \ No newline at end of file diff --git a/src/Controller/UsersController.php b/src/Controller/UsersController.php index ec17d6b..2205f08 100644 --- a/src/Controller/UsersController.php +++ b/src/Controller/UsersController.php @@ -151,6 +151,11 @@ class UsersController extends AppController public function settings() { $this->set('user', $this->ACL->getUser()); + $all = $this->Users->UserSettings->getSettingsFromProviderForUser($this->ACL->getUser()['id'], true); + $this->set('settingsProvider', $all['settingsProvider']); + $this->set('settings', $all['settings']); + $this->set('settingsFlattened', $all['settingsFlattened']); + $this->set('notices', $all['notices']); } public function register() diff --git a/src/Model/Entity/User.php b/src/Model/Entity/User.php index 04809e3..0e685f5 100644 --- a/src/Model/Entity/User.php +++ b/src/Model/Entity/User.php @@ -6,11 +6,14 @@ use App\Model\Entity\AppModel; use Cake\ORM\Entity; use Authentication\PasswordHasher\DefaultPasswordHasher; +require_once(APP . 'Model' . DS . 'Table' . DS . 'SettingProviders' . DS . 'UserSettingsProvider.php'); +use App\Settings\SettingsProvider\UserSettingsProvider; + class User extends AppModel { protected $_hidden = ['password', 'confirm_password']; - protected $_virtual = ['user_settings_by_name']; + protected $_virtual = ['user_settings_by_name', 'user_settings_by_name_with_fallback']; protected function _getUserSettingsByName() { @@ -23,6 +26,22 @@ class User extends AppModel return $settingsByName; } + protected function _getUserSettingsByNameWithFallback() + { + if (!isset($this->SettingsProvider)) { + $this->SettingsProvider = new UserSettingsProvider(); + } + $settingsByNameWithFallback = []; + if (!empty($this->user_settings)) { + foreach ($this->user_settings as $i => $setting) { + $settingsByNameWithFallback[$setting->name] = $setting->value; + } + } + $settingsProvider = $this->SettingsProvider->getSettingsConfiguration($settingsByNameWithFallback); + $settingsFlattened = $this->SettingsProvider->flattenSettingsConfiguration($settingsProvider); + return $settingsFlattened; + } + protected function _setPassword(string $password) : ?string { if (strlen($password) > 0) { diff --git a/src/Model/Table/SettingProviders/UserSettingsProvider.php b/src/Model/Table/SettingProviders/UserSettingsProvider.php new file mode 100644 index 0000000..baeca50 --- /dev/null +++ b/src/Model/Table/SettingProviders/UserSettingsProvider.php @@ -0,0 +1,50 @@ + [ + __('User Interface') => [ + 'ui.bsTheme' => [ + 'description' => 'The Bootstrap theme to use for the application', + 'default' => 'default', + 'name' => 'UI Theme', + 'options' => (function () { + $instanceTable = TableRegistry::getTableLocator()->get('Instance'); + $themes = $instanceTable->getAvailableThemes(); + return array_combine($themes, $themes); + })(), + 'severity' => 'info', + 'type' => 'select' + ], + 'ui.sidebar.expanded' => [ + 'name' => __('Sidebar expanded'), + 'type' => 'boolean', + 'description' => __('Should the left navigation sidebar expanded and locked.'), + 'default' => false, + 'severity' => 'info', + ], + 'ui.sidebar.include_bookmarks' => [ + 'name' => __('Include bookmarks in the sidebar'), + 'type' => 'boolean', + 'description' => __('Should bookmarks links included in the sidebar.'), + 'default' => false, + 'severity' => 'info', + ], + ] + ], + __('Account Security') => [ + ] + ]; + } +} \ No newline at end of file diff --git a/src/Model/Table/UserSettingsTable.php b/src/Model/Table/UserSettingsTable.php index 1e34b17..4c7e708 100644 --- a/src/Model/Table/UserSettingsTable.php +++ b/src/Model/Table/UserSettingsTable.php @@ -6,8 +6,13 @@ use App\Model\Table\AppTable; use Cake\ORM\Table; use Cake\Validation\Validator; +require_once(APP . 'Model' . DS . 'Table' . DS . 'SettingProviders' . DS . 'UserSettingsProvider.php'); +use App\Settings\SettingsProvider\UserSettingsProvider; + class UserSettingsTable extends AppTable { + protected $BOOKMARK_SETTING_NAME = 'ui.bookmarks'; + public function initialize(array $config): void { parent::initialize($config); @@ -16,6 +21,8 @@ class UserSettingsTable extends AppTable 'Users' ); $this->setDisplayField('name'); + + $this->SettingsProvider = new UserSettingsProvider(); } public function validationDefault(Validator $validator): Validator @@ -27,6 +34,35 @@ class UserSettingsTable extends AppTable return $validator; } + public function getSettingsFromProviderForUser($user_id, $full = false): array + { + $settingsTmp = $this->getSettingsForUser($user_id)->toArray(); + $settings = []; + foreach ($settingsTmp as $setting) { + $settings[$setting->name] = $setting->value; + } + if (empty($full)) { + return $settings; + } else { + $settingsProvider = $this->SettingsProvider->getSettingsConfiguration($settings); + $settingsFlattened = $this->SettingsProvider->flattenSettingsConfiguration($settingsProvider); + $notices = $this->SettingsProvider->getNoticesFromSettingsConfiguration($settingsProvider, $settings); + return [ + 'settings' => $settings, + 'settingsProvider' => $settingsProvider, + 'settingsFlattened' => $settingsFlattened, + 'notices' => $notices, + ]; + } + } + + public function getSettingsForUser($user_id) + { + return $this->find()->where([ + 'user_id' => $user_id, + ])->all(); + } + public function getSettingByName($user, $name) { return $this->find()->where([ @@ -60,8 +96,7 @@ class UserSettingsTable extends AppTable public function saveBookmark($user, $data) { - $bookmarkSettingName = 'ui.sidebar.bookmarks'; - $setting = $this->getSettingByName($user, $bookmarkSettingName); + $setting = $this->getSettingByName($user, $this->BOOKMARK_SETTING_NAME); $bookmarkData = [ 'label' => $data['bookmark_label'], 'name' => $data['bookmark_name'], @@ -69,12 +104,34 @@ class UserSettingsTable extends AppTable ]; if (is_null($setting)) { // setting not found, create it $bookmarksData = json_encode([$bookmarkData]); - $result = $this->createSetting($user, $bookmarkSettingName, $bookmarksData); + $result = $this->createSetting($user, $this->BOOKMARK_SETTING_NAME, $bookmarksData); } else { $bookmarksData = json_decode($setting->value); $bookmarksData[] = $bookmarkData; $bookmarksData = json_encode($bookmarksData); - $result = $this->editSetting($user, $bookmarkSettingName, $bookmarksData); + $result = $this->editSetting($user, $this->BOOKMARK_SETTING_NAME, $bookmarksData); + } + return $result; + } + + public function deleteBookmark($user, $data) + { + $setting = $this->getSettingByName($user, $this->BOOKMARK_SETTING_NAME); + $bookmarkData = [ + 'name' => $data['bookmark_name'], + 'url' => $data['bookmark_url'], + ]; + if (is_null($setting)) { // Can't delete something that doesn't exist + return null; + } else { + $bookmarksData = json_decode($setting->value, true); + foreach ($bookmarksData as $i => $savedBookmark) { + if ($savedBookmark['name'] == $bookmarkData['name'] && $savedBookmark['url'] == $bookmarkData['url']) { + unset($bookmarksData[$i]); + } + } + $bookmarksData = json_encode($bookmarksData); + $result = $this->editSetting($user, $this->BOOKMARK_SETTING_NAME, $bookmarksData); } return $result; } diff --git a/src/View/Helper/BootstrapHelper.php b/src/View/Helper/BootstrapHelper.php index 08093de..bc1000e 100644 --- a/src/View/Helper/BootstrapHelper.php +++ b/src/View/Helper/BootstrapHelper.php @@ -652,21 +652,21 @@ class BoostrapTable extends BootstrapGeneric { $key = $field; } $cellValue = Hash::get($row, $key); - $html .= $this->genCell($cellValue, $field, $row); + $html .= $this->genCell($cellValue, $field, $row, $i); } } else { // indexed array foreach ($row as $cellValue) { - $html .= $this->genCell($cellValue, $field, $row); + $html .= $this->genCell($cellValue, $field, $row, $i); } } $html .= $this->closeNode('tr'); return $html; } - private function genCell($value, $field=[], $row=[]) + private function genCell($value, $field=[], $row=[], $i=0) { if (isset($field['formatter'])) { - $cellContent = $field['formatter']($value, $row); + $cellContent = $field['formatter']($value, $row, $i); } else if (isset($field['element'])) { $cellContent = $this->btHelper->getView()->element($field['element'], [ 'data' => [$value], diff --git a/templates/Instance/home.php b/templates/Instance/home.php index d576185..5d507e7 100644 --- a/templates/Instance/home.php +++ b/templates/Instance/home.php @@ -1,30 +1,57 @@ text($md); +$bookmarks = !empty($loggedUser->user_settings_by_name['ui.bookmarks']['value']) ? json_decode($loggedUser->user_settings_by_name['ui.bookmarks']['value'], true) : [] ?> -
= __('No bookmarks') ?>
+ +