getParsedBody(); /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page if (!$authorizer->checkAccess($currentUser, 'create_group')) { throw new ForbiddenException(); } /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */ $ms = $this->ci->alerts; // Load the request schema $schema = new RequestSchema('schema://requests/group/create.yaml'); // Whitelist and set parameter defaults $transformer = new RequestDataTransformer($schema); $data = $transformer->transform($params); $error = FALSE; // Validate request data $validator = new ServerSideValidator($schema, $this->ci->translator); if (!$validator->validate($data)) { $ms->addValidationErrors($validator); $error = TRUE; } /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Check if name or slug already exists if ($classMapper->staticMethod('group', 'where', 'name', $data['name'])->first()) { $ms->addMessageTranslated('danger', 'GROUP.NAME.IN_USE', $data); $error = TRUE; } if ($classMapper->staticMethod('group', 'where', 'slug', $data['slug'])->first()) { $ms->addMessageTranslated('danger', 'GROUP.SLUG.IN_USE', $data); $error = TRUE; } if ($error) { return $response->withStatus(400); } /** @var UserFrosting\Config\Config $config */ $config = $this->ci->config; // All checks passed! log events/activities and create group // Begin transaction - DB will be rolled back if an exception occurs Capsule::transaction(function () use ($classMapper, $data, $ms, $config, $currentUser) { // Create the group $group = $classMapper->createInstance('group', $data); // Store new group to database $group->save(); // Create activity record $this->ci->userActivityLogger->info("User {$currentUser->user_name} created group {$group->name}.", [ 'type' => 'group_create', 'user_id' => $currentUser->id ]); $ms->addMessageTranslated('success', 'GROUP.CREATION_SUCCESSFUL', $data); }); return $response->withStatus(200); } /** * Processes the request to delete an existing group. * * Deletes the specified group. * Before doing so, checks that: * 1. The user has permission to delete this group; * 2. The group is not currently set as the default for new users; * 3. The group is empty (does not have any users); * 4. The submitted data is valid. * This route requires authentication (and should generally be limited to admins or the root user). * Request type: DELETE */ public function delete($request, $response, $args) { $group = $this->getGroupFromParams($args); // If the group doesn't exist, return 404 if (!$group) { throw new NotFoundException($request, $response); } /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page if (!$authorizer->checkAccess($currentUser, 'delete_group', [ 'group' => $group ])) { throw new ForbiddenException(); } /** @var UserFrosting\Config\Config $config */ $config = $this->ci->config; // Check that we are not deleting the default group // Need to use loose comparison for now, because some DBs return `id` as a string if ($group->slug == $config['site.registration.user_defaults.group']) { $e = new BadRequestException(); $e->addUserMessage('GROUP.DELETE_DEFAULT', $group->toArray()); throw $e; } /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Check if there are any users in this group $countGroupUsers = $classMapper->staticMethod('user', 'where', 'group_id', $group->id)->count(); if ($countGroupUsers > 0) { $e = new BadRequestException(); $e->addUserMessage('GROUP.NOT_EMPTY', $group->toArray()); throw $e; } $groupName = $group->name; // Begin transaction - DB will be rolled back if an exception occurs Capsule::transaction(function () use ($group, $groupName, $currentUser) { $group->delete(); unset($group); // Create activity record $this->ci->userActivityLogger->info("User {$currentUser->user_name} deleted group {$groupName}.", [ 'type' => 'group_delete', 'user_id' => $currentUser->id ]); }); /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */ $ms = $this->ci->alerts; $ms->addMessageTranslated('success', 'GROUP.DELETION_SUCCESSFUL', [ 'name' => $groupName ]); return $response->withStatus(200); } /** * Returns info for a single group. * * This page requires authentication. * Request type: GET */ public function getInfo($request, $response, $args) { /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ $authorizer = $this->ci->authorizer; /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page if (!$authorizer->checkAccess($currentUser, 'uri_groups')) { throw new ForbiddenException(); } $slug = $args['slug']; /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; $group = $classMapper->staticMethod('group', 'where', 'slug', $slug)->first(); // If the group doesn't exist, return 404 if (!$group) { throw new NotFoundException($request, $response); } // Get group $result = $group->toArray(); // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content. // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating). return $response->withJson($result, 200, JSON_PRETTY_PRINT); } /** * Returns a list of Groups * * Generates a list of groups, optionally paginated, sorted and/or filtered. * This page requires authentication. * Request type: GET */ public function getList($request, $response, $args) { // GET parameters $params = $request->getQueryParams(); /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page if (!$authorizer->checkAccess($currentUser, 'uri_groups')) { throw new ForbiddenException(); } /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; $sprunje = $classMapper->createInstance('group_sprunje', $classMapper, $params); // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content. // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating). return $sprunje->toResponse($response); } public function getModalConfirmDelete($request, $response, $args) { // GET parameters $params = $request->getQueryParams(); $group = $this->getGroupFromParams($params); // If the group no longer exists, forward to main group listing page if (!$group) { throw new NotFoundException($request, $response); } /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page if (!$authorizer->checkAccess($currentUser, 'delete_group', [ 'group' => $group ])) { throw new ForbiddenException(); } /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Check if there are any users in this group $countGroupUsers = $classMapper->staticMethod('user', 'where', 'group_id', $group->id)->count(); if ($countGroupUsers > 0) { $e = new BadRequestException(); $e->addUserMessage('GROUP.NOT_EMPTY', $group->toArray()); throw $e; } return $this->ci->view->render($response, 'modals/confirm-delete-group.html.twig', [ 'group' => $group, 'form' => [ 'action' => "api/groups/g/{$group->slug}", ] ]); } /** * Renders the modal form for creating a new group. * * This does NOT render a complete page. Instead, it renders the HTML for the modal, which can be embedded in other pages. * This page requires authentication. * Request type: GET */ public function getModalCreate($request, $response, $args) { // GET parameters $params = $request->getQueryParams(); /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ $currentUser = $this->ci->currentUser; /** @var UserFrosting\I18n\MessageTranslator $translator */ $translator = $this->ci->translator; // Access-controlled page if (!$authorizer->checkAccess($currentUser, 'create_group')) { throw new ForbiddenException(); } /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Create a dummy group to prepopulate fields $group = $classMapper->createInstance('group', []); $group->icon = 'fa fa-user'; $fieldNames = ['name', 'slug', 'icon', 'description']; $fields = [ 'hidden' => [], 'disabled' => [] ]; // Load validation rules $schema = new RequestSchema('schema://requests/group/create.yaml'); $validator = new JqueryValidationAdapter($schema, $this->ci->translator); return $this->ci->view->render($response, 'modals/group.html.twig', [ 'group' => $group, 'form' => [ 'action' => 'api/groups', 'method' => 'POST', 'fields' => $fields, 'submit_text' => $translator->translate('CREATE') ], 'page' => [ 'validators' => $validator->rules('json', FALSE) ] ]); } /** * Renders the modal form for editing an existing group. * * This does NOT render a complete page. Instead, it renders the HTML for the modal, which can be embedded in other pages. * This page requires authentication. * Request type: GET */ public function getModalEdit($request, $response, $args) { // GET parameters $params = $request->getQueryParams(); $group = $this->getGroupFromParams($params); // If the group doesn't exist, return 404 if (!$group) { throw new NotFoundException($request, $response); } /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ $currentUser = $this->ci->currentUser; /** @var UserFrosting\I18n\MessageTranslator $translator */ $translator = $this->ci->translator; // Access-controlled resource - check that currentUser has permission to edit basic fields "name", "slug", "icon", "description" for this group $fieldNames = ['name', 'slug', 'icon', 'description']; if (!$authorizer->checkAccess($currentUser, 'update_group_field', [ 'group' => $group, 'fields' => $fieldNames ])) { throw new ForbiddenException(); } // Generate form $fields = [ 'hidden' => [], 'disabled' => [] ]; // Load validation rules $schema = new RequestSchema('schema://requests/group/edit-info.yaml'); $validator = new JqueryValidationAdapter($schema, $translator); return $this->ci->view->render($response, 'modals/group.html.twig', [ 'group' => $group, 'form' => [ 'action' => "api/groups/g/{$group->slug}", 'method' => 'PUT', 'fields' => $fields, 'submit_text' => $translator->translate('UPDATE') ], 'page' => [ 'validators' => $validator->rules('json', FALSE) ] ]); } public function getUsers($request, $response, $args) { $group = $this->getGroupFromParams($args); // If the group no longer exists, forward to main group listing page if (!$group) { throw new NotFoundException($request, $response); } // GET parameters $params = $request->getQueryParams(); /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page if (!$authorizer->checkAccess($currentUser, 'view_group_field', [ 'group' => $group, 'property' => 'users' ])) { throw new ForbiddenException(); } /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; $sprunje = $classMapper->createInstance('user_sprunje', $classMapper, $params); $sprunje->extendQuery(function ($query) use ($group) { return $query->where('group_id', $group->id); }); // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content. // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating). return $sprunje->toResponse($response); } /** * Renders a page displaying a group's information, in read-only mode. * * This checks that the currently logged-in user has permission to view the requested group's info. * It checks each field individually, showing only those that you have permission to view. * This will also try to show buttons for deleting, and editing the group. * This page requires authentication. * Request type: GET */ public function pageInfo($request, $response, $args) { $group = $this->getGroupFromParams($args); // If the group no longer exists, forward to main group listing page if (!$group) { $redirectPage = $this->ci->router->pathFor('uri_groups'); return $response->withRedirect($redirectPage, 404); } /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page if (!$authorizer->checkAccess($currentUser, 'uri_group', [ 'group' => $group ])) { throw new ForbiddenException(); } // Determine fields that currentUser is authorized to view $fieldNames = ['name', 'slug', 'icon', 'description']; // Generate form $fields = [ 'hidden' => [] ]; foreach ($fieldNames as $field) { if (!$authorizer->checkAccess($currentUser, 'view_group_field', [ 'group' => $group, 'property' => $field ])) { $fields['hidden'][] = $field; } } // Determine buttons to display $editButtons = [ 'hidden' => [] ]; if (!$authorizer->checkAccess($currentUser, 'update_group_field', [ 'group' => $group, 'fields' => ['name', 'slug', 'icon', 'description'] ])) { $editButtons['hidden'][] = 'edit'; } if (!$authorizer->checkAccess($currentUser, 'delete_group', [ 'group' => $group ])) { $editButtons['hidden'][] = 'delete'; } return $this->ci->view->render($response, 'pages/group.html.twig', [ 'group' => $group, 'fields' => $fields, 'tools' => $editButtons ]); } /** * Renders the group listing page. * * This page renders a table of groups, with dropdown menus for admin actions for each group. * Actions typically include: edit group, delete group. * This page requires authentication. * Request type: GET */ public function pageList($request, $response, $args) { /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled page if (!$authorizer->checkAccess($currentUser, 'uri_groups')) { throw new ForbiddenException(); } return $this->ci->view->render($response, 'pages/groups.html.twig'); } /** * Processes the request to update an existing group's details. * * Processes the request from the group update form, checking that: * 1. The group name/slug are not already in use; * 2. The user has the necessary permissions to update the posted field(s); * 3. The submitted data is valid. * This route requires authentication (and should generally be limited to admins or the root user). * Request type: PUT * @see getModalGroupEdit */ public function updateInfo($request, $response, $args) { // Get the group based on slug in URL $group = $this->getGroupFromParams($args); if (!$group) { throw new NotFoundException($request, $response); } /** @var UserFrosting\Config\Config $config */ $config = $this->ci->config; // Get PUT parameters: (name, slug, icon, description) $params = $request->getParsedBody(); /** @var UserFrosting\Sprinkle\Core\MessageStream $ms */ $ms = $this->ci->alerts; // Load the request schema $schema = new RequestSchema('schema://requests/group/edit-info.yaml'); // Whitelist and set parameter defaults $transformer = new RequestDataTransformer($schema); $data = $transformer->transform($params); $error = FALSE; // Validate request data $validator = new ServerSideValidator($schema, $this->ci->translator); if (!$validator->validate($data)) { $ms->addValidationErrors($validator); $error = TRUE; } // Determine targeted fields $fieldNames = []; foreach ($data as $name => $value) { $fieldNames[] = $name; } /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */ $authorizer = $this->ci->authorizer; /** @var UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ $currentUser = $this->ci->currentUser; // Access-controlled resource - check that currentUser has permission to edit submitted fields for this group if (!$authorizer->checkAccess($currentUser, 'update_group_field', [ 'group' => $group, 'fields' => array_values(array_unique($fieldNames)) ])) { throw new ForbiddenException(); } /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Check if name or slug already exists if ( isset($data['name']) && $data['name'] != $group->name && $classMapper->staticMethod('group', 'where', 'name', $data['name'])->first() ) { $ms->addMessageTranslated('danger', 'GROUP.NAME.IN_USE', $data); $error = TRUE; } if ( isset($data['slug']) && $data['slug'] != $group->slug && $classMapper->staticMethod('group', 'where', 'slug', $data['slug'])->first() ) { $ms->addMessageTranslated('danger', 'GROUP.SLUG.IN_USE', $data); $error = TRUE; } if ($error) { return $response->withStatus(400); } // Begin transaction - DB will be rolled back if an exception occurs Capsule::transaction(function () use ($data, $group, $currentUser) { // Update the group and generate success messages foreach ($data as $name => $value) { if ($value != $group->$name) { $group->$name = $value; } } $group->save(); // Create activity record $this->ci->userActivityLogger->info("User {$currentUser->user_name} updated details for group {$group->name}.", [ 'type' => 'group_update_info', 'user_id' => $currentUser->id ]); }); $ms->addMessageTranslated('success', 'GROUP.UPDATE', [ 'name' => $group->name ]); return $response->withStatus(200); } protected function getGroupFromParams($params) { // Load the request schema $schema = new RequestSchema('schema://requests/group/get-by-slug.yaml'); // Whitelist and set parameter defaults $transformer = new RequestDataTransformer($schema); $data = $transformer->transform($params); // Validate, and throw exception on validation errors. $validator = new ServerSideValidator($schema, $this->ci->translator); if (!$validator->validate($data)) { $e = new BadRequestException(); foreach ($validator->errors() as $idx => $field) { foreach ($field as $eidx => $error) { $e->addUserMessage($error); } } throw $e; } /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ $classMapper = $this->ci->classMapper; // Get the group $group = $classMapper->staticMethod('group', 'where', 'slug', $data['slug']) ->first(); return $group; } }