aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/courseware/public.php14
-rw-r--r--app/views/courseware/public/index.php2
-rw-r--r--lib/classes/JsonApi/RouteMap.php1
-rw-r--r--lib/classes/JsonApi/Routes/Courseware/DescendantsOfPublicStructuralElementsIndex.php54
-rw-r--r--lib/classes/JsonApi/Schemas/Courseware/PublicLink.php6
-rw-r--r--resources/vue/courseware-public-app.js34
-rw-r--r--resources/vue/store/courseware/courseware-public.module.js74
-rw-r--r--resources/vue/store/courseware/public-structure.module.js51
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 });
+ }
},
};