diff options
| -rw-r--r-- | app/controllers/courseware/public.php | 14 | ||||
| -rw-r--r-- | app/views/courseware/public/index.php | 2 | ||||
| -rw-r--r-- | lib/classes/JsonApi/RouteMap.php | 1 | ||||
| -rw-r--r-- | lib/classes/JsonApi/Routes/Courseware/DescendantsOfPublicStructuralElementsIndex.php | 54 | ||||
| -rw-r--r-- | lib/classes/JsonApi/Schemas/Courseware/PublicLink.php | 6 | ||||
| -rw-r--r-- | resources/vue/courseware-public-app.js | 34 | ||||
| -rw-r--r-- | resources/vue/store/courseware/courseware-public.module.js | 74 | ||||
| -rw-r--r-- | resources/vue/store/courseware/public-structure.module.js | 51 |
8 files changed, 194 insertions, 42 deletions
diff --git a/app/controllers/courseware/public.php b/app/controllers/courseware/public.php index e5f7aec..a1d3077 100644 --- a/app/controllers/courseware/public.php +++ b/app/controllers/courseware/public.php @@ -22,11 +22,23 @@ class Courseware_PublicController extends StudipController $publicLink = PublicLink::find($this->link_id); $this->invalid = $publicLink === null; if (!$this->invalid) { - $this->block_types = Courseware\BlockTypes\BlockType::getBlockTypes(); + $blockTypes = Courseware\BlockTypes\BlockType::getBlockTypes(); + $this->block_types = json_encode( array_map([$this, 'mapType'], $blockTypes)); + $containerTypes = Courseware\ContainerTypes\ContainerType::getContainerTypes(); + $this->container_types = json_encode( array_map([$this, 'mapType'], $containerTypes)); $this->expired = $publicLink->isExpired(); $this->link_pass = $publicLink->password; $this->entry_element_id = $publicLink->structural_element_id; } } } + + private function mapType(string $typeClass): array + { + return [ + 'type' => $typeClass::getType(), + 'title' => $typeClass::getTitle(), + 'is-activated' => $typeClass::isActivated(), + ]; + } } diff --git a/app/views/courseware/public/index.php b/app/views/courseware/public/index.php index 15e0810..cae565d 100644 --- a/app/views/courseware/public/index.php +++ b/app/views/courseware/public/index.php @@ -5,6 +5,8 @@ link-pass="<?= htmlReady($link_pass) ?>" entry-type="public" entry-element-id="<?= htmlReady($entry_element_id) ?>" + block-types="<?= htmlReady($block_types) ?>" + container-types="<?= htmlReady($container_types) ?>" > </div> <? endif; ?> diff --git a/lib/classes/JsonApi/RouteMap.php b/lib/classes/JsonApi/RouteMap.php index d85b12d..176c1ea 100644 --- a/lib/classes/JsonApi/RouteMap.php +++ b/lib/classes/JsonApi/RouteMap.php @@ -164,6 +164,7 @@ class RouteMap if (\PluginManager::getInstance()->getPlugin(\CoursewareModule::class)) { $group->get('/public/courseware/{link_id}/courseware-structural-elements/{id}', Routes\Courseware\PublicStructuralElementsShow::class); + $group->get('/public/courseware/{link_id}/courseware-structural-elements/{id}/descendants', Routes\Courseware\DescendantsOfPublicStructuralElementsIndex::class); $group->get('/public/courseware/{link_id}/courseware-structural-elements', Routes\Courseware\PublicStructuralElementsIndex::class); } diff --git a/lib/classes/JsonApi/Routes/Courseware/DescendantsOfPublicStructuralElementsIndex.php b/lib/classes/JsonApi/Routes/Courseware/DescendantsOfPublicStructuralElementsIndex.php new file mode 100644 index 0000000..e01aa4a --- /dev/null +++ b/lib/classes/JsonApi/Routes/Courseware/DescendantsOfPublicStructuralElementsIndex.php @@ -0,0 +1,54 @@ +<?php + +namespace JsonApi\Routes\Courseware; + +use Courseware\StructuralElement; +use Courseware\PublicLink; +use JsonApi\Errors\AuthorizationFailedException; +use JsonApi\Errors\RecordNotFoundException; +use JsonApi\JsonApiController; +use Neomerx\JsonApi\Contracts\Http\ResponsesInterface; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; + +/** + * Displays all descendants of a structural element. + */ +class DescendantsOfPublicStructuralElementsIndex extends JsonApiController +{ + protected $allowedPagingParameters = ['offset', 'limit']; + + protected $allowedIncludePaths = ['containers', 'parent']; + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function __invoke(Request $request, Response $response, $args) + { + /** @var ?StructuralElement $resource */ + $resource = StructuralElement::find($args['id']); + $publicLink = PublicLink::find($args['link_id']); + + if (!$publicLink) { + throw new RecordNotFoundException(); + } + if (!$resource) { + throw new RecordNotFoundException(); + } + + if (!$publicLink->canVisitElement($resource)) { + throw new AuthorizationFailedException(); + } + + $descendants = $resource->findDescendants(); + + [$offset, $limit] = $this->getOffsetAndLimit(); + $page = array_slice($descendants, $offset, $limit); + $total = count($descendants); + + return $this->getPaginatedContentResponse( + $page, + $total + ); + } +} diff --git a/lib/classes/JsonApi/Schemas/Courseware/PublicLink.php b/lib/classes/JsonApi/Schemas/Courseware/PublicLink.php index 13d14e0..e9174b4 100644 --- a/lib/classes/JsonApi/Schemas/Courseware/PublicLink.php +++ b/lib/classes/JsonApi/Schemas/Courseware/PublicLink.php @@ -40,12 +40,12 @@ class PublicLink extends SchemaProvider { $relationships = []; - $relationships[self::REL_STRUCTURAL_ELEMENT] = $resource['structural_element_id'] + $relationships[self::REL_STRUCTURAL_ELEMENT] = $resource->structural_element ? [ self::RELATIONSHIP_LINKS => [ - Link::RELATED => $this->createLinkToResource($resource['structural_element']), + Link::RELATED => $this->createLinkToResource($resource->structural_element), ], - self::RELATIONSHIP_DATA => $resource['structural_element'], + self::RELATIONSHIP_DATA => $resource->structural_element, ] : [self::RELATIONSHIP_DATA => null]; diff --git a/resources/vue/courseware-public-app.js b/resources/vue/courseware-public-app.js index 5bd9a96..f85db8e 100644 --- a/resources/vue/courseware-public-app.js +++ b/resources/vue/courseware-public-app.js @@ -4,14 +4,26 @@ import PublicCoursewareStructuralElement from './components/courseware/structura import CoursewarePublicStructureModule from './store/courseware/public-structure.module'; import PluginManager from './components/courseware/plugin-manager.js'; import { createRouter, createWebHashHistory } from 'vue-router'; -import { h } from "vue"; +import axios from 'axios'; +import { h } from 'vue'; const mountApp = (STUDIP, createApp, store, element) => { + const getHttpClient = () => + axios.create({ + baseURL: STUDIP.URLHelper.getURL(`jsonapi.php/v1`, {}, true), + headers: { + 'Content-Type': 'application/vnd.api+json', + }, + }); + + const httpClient = getHttpClient(); let elem_id = null; let link_id = null; let link_pass = null; let entry_type = null; + let block_types = []; + let container_types = []; let elem = document.getElementById(element.substring(1)); if (elem !== undefined) { @@ -31,20 +43,27 @@ const mountApp = (STUDIP, createApp, store, element) => { if (elem.attributes['link-pass'] !== undefined) { link_pass = elem.attributes['link-pass'].value; } + + if (elem.attributes['block-types'] !== undefined) { + block_types = JSON.parse(elem.attributes['block-types'].value); + } + + if (elem.attributes['container-types'] !== undefined) { + container_types = JSON.parse(elem.attributes['container-types'].value); + } } } - let base = new URL( - STUDIP.URLHelper.getURL('dispatch.php/courseware/public', { link: link_id }, true) - ); + let base = new URL(STUDIP.URLHelper.getURL('dispatch.php/courseware/public', { link: link_id }, true)); store.registerModule('courseware-public', CoursewarePublicModule); store.registerModule('courseware-structure', CoursewarePublicStructureModule); store.dispatch('setContext', { id: link_id, type: entry_type, - rootId: elem_id + rootId: elem_id, }); + store.dispatch('setHttpClient', httpClient); if (link_pass) { store.dispatch('setPassword', link_pass); @@ -52,6 +71,9 @@ const mountApp = (STUDIP, createApp, store, element) => { store.dispatch('setIsAuthenticated', true); } + store.dispatch('setBlockTypes', block_types); + store.dispatch('setContainerTypes', container_types); + const pluginManager = new PluginManager(); store.dispatch('setPluginManager', pluginManager); STUDIP.eventBus.emit('courseware:init-plugin-manager', pluginManager); @@ -87,6 +109,6 @@ const mountApp = (STUDIP, createApp, store, element) => { app.mount(element); return app; -} +}; export default mountApp; diff --git a/resources/vue/store/courseware/courseware-public.module.js b/resources/vue/store/courseware/courseware-public.module.js index 403cfe4..ea1aac7 100644 --- a/resources/vue/store/courseware/courseware-public.module.js +++ b/resources/vue/store/courseware/courseware-public.module.js @@ -1,9 +1,12 @@ const getDefaultState = () => { return { blockAdder: {}, + blockTypes: [], + containerTypes: [], containerAdder: false, context: null, courseware: {}, + httpClient: null, isAuthenticated: false, password: null, pluginManager: null, @@ -21,6 +24,14 @@ const getters = { return state.blockAdder; }, + blockTypes(state) { + return state.blockTypes; + }, + + containerTypes(state) { + return state.containerTypes; + }, + containerAdder(state) { return state.containerAdder; }, @@ -33,6 +44,10 @@ const getters = { return state.courseware; }, + httpClient(state) { + return state.httpClient; + }, + isAuthenticated(state) { return state.isAuthenticated; }, @@ -57,6 +72,10 @@ const getters = { return state.userId; }, + userIsTeacher() { + return false; + }, + viewMode(state) { return state.viewMode; }, @@ -66,6 +85,13 @@ export const state = { ...initialState }; export const actions = { // setters + setBlockTypes({ commit }, blockTypes) { + commit('setBlockTypes', blockTypes); + }, + setContainerTypes({ commit }, containerTypes) { + commit('setContainerTypes', containerTypes); + }, + coursewareContainerAdder(context, adder) { context.commit('setContainerAdder', adder); }, @@ -94,18 +120,32 @@ export const actions = { commit('setPassword', password); }, + setHttpClient({ commit }, httpClient) { + commit('setHttpClient', httpClient); + }, + // other actions - loadStructuralElement({ dispatch }, structuralElementId) { - const options = { - include: - 'containers,containers.blocks', - }; - - return dispatch( - 'courseware-structural-elements/loadById', - { id: structuralElementId, options }, - { root: true } + async loadStructuralElement({ dispatch, rootGetters }, structuralElementId) { + const context = rootGetters['context']; + const httpClient = rootGetters['httpClient']; + + let response = await httpClient.get( + `public/courseware/${context.id}/courseware-structural-elements/${structuralElementId}`, + { + params: { + include: 'containers,containers.blocks', + }, + } ); + + const element = response.data.data; + const includedObjects = response.data.included ?? []; + dispatch('courseware-structural-elements/storeRecord', element, { root: true }); + for (const includedObject of includedObjects) { + dispatch(`${includedObject.type}/storeRecord`, includedObject, { root: true }); + } + + return element; }, validatePassword({ getters, dispatch }, password) { @@ -116,7 +156,7 @@ export const actions = { } return false; - } + }, }; export const mutations = { @@ -125,6 +165,14 @@ export const mutations = { state.courseware = data; }, + setBlockTypes(state, blockTypes) { + state.blockTypes = blockTypes; + }, + + setContainerTypes(state, containerTypes) { + state.containerTypes = containerTypes; + }, + setContainerAdder(state, containerAdder) { state.containerAdder = containerAdder; }, @@ -152,6 +200,10 @@ export const mutations = { setViewMode(state, data) { state.viewMode = data; }, + + setHttpClient(state, httpClient) { + state.httpClient = httpClient; + }, }; export default { diff --git a/resources/vue/store/courseware/public-structure.module.js b/resources/vue/store/courseware/public-structure.module.js index fba2384..197a8bc 100644 --- a/resources/vue/store/courseware/public-structure.module.js +++ b/resources/vue/store/courseware/public-structure.module.js @@ -30,7 +30,7 @@ export const mutations = { }; const actions = { - build({commit, rootGetters }) { + build({ commit, rootGetters }) { const context = rootGetters['context']; const structuralElements = rootGetters['courseware-structural-elements/all']; const children = structuralElements.reduce((memo, element) => { @@ -57,29 +57,38 @@ const actions = { }, async load({ dispatch, rootGetters }) { const context = rootGetters['context']; + const httpClient = rootGetters['httpClient']; - await dispatch('courseware-structural-elements/loadById', { - id: context.rootId, - options: { - include: 'containers,containers.blocks', - }, - }, { root: true }); - const root = rootGetters['courseware-structural-elements/byId']({id: context.rootId}); - await dispatch('loadDescendants', { root }); - }, - loadDescendants({ dispatch }, { root }) { - const parent = { id: root.id, type: root.type }; - const relationship = 'descendants'; - const options = { - 'page[offset]': 0, - 'page[limit]': 10000, - }; + let response = await httpClient.get( + `public/courseware/${context.id}/courseware-structural-elements/${context.rootId}`, + { + params: { + include: 'containers,containers.blocks', + }, + } + ); + + const rootElement = response.data.data; + const includedObjects = response.data.included || []; + dispatch('courseware-structural-elements/storeRecord', rootElement, { root: true }); + for (const includedObject of includedObjects) { + dispatch(`${includedObject.type}/storeRecord`, includedObject, { root: true }); + } - return dispatch( - 'courseware-structural-elements/loadRelated', - { parent, relationship, options }, - { root: true } + response = await httpClient.get( + `public/courseware/${context.id}/courseware-structural-elements/${context.rootId}/descendants`, + { + params: { + 'page[offset]': 0, + 'page[limit]': 10000, + }, + } ); + + const descendants = response.data.data; + for (const descendant of descendants) { + dispatch('courseware-structural-elements/storeRecord', descendant, { root: true }); + } }, }; |
