mirror of
https://github.com/mediacms-io/mediacms.git
synced 2026-05-07 12:53:53 -04:00
all
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
|||||||
VERSION = "8.0.1e"
|
VERSION = "8.0.1f"
|
||||||
|
|||||||
@@ -67,6 +67,10 @@ urlpatterns = [
|
|||||||
name="api_get_encoding",
|
name="api_get_encoding",
|
||||||
),
|
),
|
||||||
re_path(r"^api/v1/search$", views.MediaSearch.as_view()),
|
re_path(r"^api/v1/search$", views.MediaSearch.as_view()),
|
||||||
|
re_path(
|
||||||
|
rf"^api/v1/media/{friendly_token}/share$",
|
||||||
|
views.MediaShare.as_view(),
|
||||||
|
),
|
||||||
re_path(
|
re_path(
|
||||||
rf"^api/v1/media/{friendly_token}/actions$",
|
rf"^api/v1/media/{friendly_token}/actions$",
|
||||||
views.MediaActions.as_view(),
|
views.MediaActions.as_view(),
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from .media import MediaBulkUserActions # noqa: F401
|
|||||||
from .media import MediaDetail # noqa: F401
|
from .media import MediaDetail # noqa: F401
|
||||||
from .media import MediaList # noqa: F401
|
from .media import MediaList # noqa: F401
|
||||||
from .media import MediaSearch # noqa: F401
|
from .media import MediaSearch # noqa: F401
|
||||||
|
from .media import MediaShare # noqa: F401
|
||||||
from .pages import about # noqa: F401
|
from .pages import about # noqa: F401
|
||||||
from .pages import add_subtitle # noqa: F401
|
from .pages import add_subtitle # noqa: F401
|
||||||
from .pages import approval_required # noqa: F401
|
from .pages import approval_required # noqa: F401
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ from django.conf import settings
|
|||||||
from django.contrib.postgres.search import SearchQuery
|
from django.contrib.postgres.search import SearchQuery
|
||||||
from django.db.models import Count, F, Prefetch, Q, prefetch_related_objects
|
from django.db.models import Count, F, Prefetch, Q, prefetch_related_objects
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from drf_yasg import openapi
|
from drf_yasg import openapi
|
||||||
from drf_yasg.utils import swagger_auto_schema
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
from rest_framework import permissions, status
|
from rest_framework import permissions, status
|
||||||
@@ -1233,3 +1235,29 @@ class MediaSearch(APIView):
|
|||||||
page = paginator.paginate_queryset(media, request)
|
page = paginator.paginate_queryset(media, request)
|
||||||
serializer = MediaSearchSerializer(page, many=True, context={"request": request})
|
serializer = MediaSearchSerializer(page, many=True, context={"request": request})
|
||||||
return paginator.get_paginated_response(serializer.data)
|
return paginator.get_paginated_response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(csrf_exempt, name='dispatch')
|
||||||
|
class MediaShare(APIView):
|
||||||
|
"""Mark a media item as shared when the owner embeds it via the LTI plugin."""
|
||||||
|
|
||||||
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
def post(self, request, friendly_token):
|
||||||
|
media = get_object_or_404(Media, friendly_token=friendly_token)
|
||||||
|
if media.user != request.user:
|
||||||
|
return Response(status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
MediaPermission.objects.get_or_create(
|
||||||
|
media=media,
|
||||||
|
user=request.user,
|
||||||
|
defaults={'owner_user': request.user, 'permission': 'owner'},
|
||||||
|
)
|
||||||
|
|
||||||
|
courseid = request.data.get('courseid')
|
||||||
|
if courseid:
|
||||||
|
category = Category.objects.filter(lti_context_id=str(courseid), is_rbac_category=True).first()
|
||||||
|
if category:
|
||||||
|
EmbedMediaCourse.objects.get_or_create(media=media, category=category)
|
||||||
|
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|||||||
@@ -269,21 +269,7 @@ class text_filter extends \core_filters\text_filter {
|
|||||||
|
|
||||||
$view_url = new moodle_url('/filter/mediacms/my_media.php', $view_params);
|
$view_url = new moodle_url('/filter/mediacms/my_media.php', $view_params);
|
||||||
|
|
||||||
$launch_url = new moodle_url('/filter/mediacms/launch.php', $view_params);
|
return html_writer::tag('a', $text_matches[1], [
|
||||||
|
|
||||||
// Hidden iframe fires the LTI launch silently on every page load.
|
|
||||||
// When the media owner (teacher) loads the page, EmbedMediaLTIView's
|
|
||||||
// auto-share logic runs, marking the media as shared — same as for
|
|
||||||
// regular embedded iframes. Visits by non-owners are harmless.
|
|
||||||
$hidden_iframe = html_writer::tag('iframe', '', [
|
|
||||||
'src' => $launch_url->out(false),
|
|
||||||
'style' => 'display:none;width:0;height:0;border:0;',
|
|
||||||
'title' => '',
|
|
||||||
'tabindex' => '-1',
|
|
||||||
'aria-hidden' => 'true',
|
|
||||||
]);
|
|
||||||
|
|
||||||
return $hidden_iframe . html_writer::tag('a', $text_matches[1], [
|
|
||||||
'href' => $view_url->out(false),
|
'href' => $view_url->out(false),
|
||||||
'target' => '_blank',
|
'target' => '_blank',
|
||||||
'rel' => 'noopener noreferrer',
|
'rel' => 'noopener noreferrer',
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Selectors from './selectors';
|
|||||||
import { getLti, getData } from './options';
|
import { getLti, getData } from './options';
|
||||||
|
|
||||||
const PREFS_KEY = 'tiny_mediacms_embed_prefs';
|
const PREFS_KEY = 'tiny_mediacms_embed_prefs';
|
||||||
const PREFS_FIELDS = ['showTitle', 'linkTitle', 'showUserAvatar', 'width', 'height'];
|
const PREFS_FIELDS = ['showTitle', 'linkTitle', 'showUserAvatar', 'width', 'height', 'textLinkOnly'];
|
||||||
|
|
||||||
export default class IframeEmbed {
|
export default class IframeEmbed {
|
||||||
editor = null;
|
editor = null;
|
||||||
@@ -210,6 +210,32 @@ export default class IframeEmbed {
|
|||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signalShare(values) {
|
||||||
|
const parsed = this.parseInput(values.url);
|
||||||
|
if (!parsed || parsed.isGeneric || !parsed.videoId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const editorData = getData(this.editor);
|
||||||
|
const baseUrl = parsed.isLtiLaunch
|
||||||
|
? (editorData?.mediacmsBaseUrl || '')
|
||||||
|
: parsed.baseUrl;
|
||||||
|
|
||||||
|
if (!baseUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ltiConfig = getLti(this.editor);
|
||||||
|
const courseId = ltiConfig?.courseId || 0;
|
||||||
|
|
||||||
|
fetch(`${baseUrl}/api/v1/media/${parsed.videoId}/share`, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({courseid: courseId}),
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
savePrefs(values) {
|
savePrefs(values) {
|
||||||
try {
|
try {
|
||||||
const prefs = {};
|
const prefs = {};
|
||||||
@@ -251,7 +277,7 @@ export default class IframeEmbed {
|
|||||||
showTitle: getDefault('showTitle'),
|
showTitle: getDefault('showTitle'),
|
||||||
linkTitle: getDefault('linkTitle'),
|
linkTitle: getDefault('linkTitle'),
|
||||||
showUserAvatar: getDefault('showUserAvatar'),
|
showUserAvatar: getDefault('showUserAvatar'),
|
||||||
textLinkOnly: data.textLinkOnly || false,
|
textLinkOnly: getDefault('textLinkOnly', false),
|
||||||
startAtEnabled: data.startAtEnabled || false,
|
startAtEnabled: data.startAtEnabled || false,
|
||||||
startAt: data.startAt || '0:00',
|
startAt: data.startAt || '0:00',
|
||||||
width,
|
width,
|
||||||
@@ -531,6 +557,7 @@ export default class IframeEmbed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.savePrefs(values);
|
this.savePrefs(values);
|
||||||
|
this.signalShare(values);
|
||||||
const html = await this.generateIframeHtml(values);
|
const html = await this.generateIframeHtml(values);
|
||||||
if (html) {
|
if (html) {
|
||||||
if (this.isUpdating && this.selectedIframe) {
|
if (this.isUpdating && this.selectedIframe) {
|
||||||
|
|||||||
+1
-15
@@ -30,7 +30,7 @@ from pylti1p3.exception import LtiException
|
|||||||
from pylti1p3.message_launch import MessageLaunch
|
from pylti1p3.message_launch import MessageLaunch
|
||||||
from pylti1p3.oidc_login import OIDCLogin
|
from pylti1p3.oidc_login import OIDCLogin
|
||||||
|
|
||||||
from files.models import Category, EmbedMediaCourse, Media, MediaPermission
|
from files.models import Media, MediaPermission
|
||||||
from rbac.models import RBACMembership
|
from rbac.models import RBACMembership
|
||||||
|
|
||||||
from .adapters import DjangoRequest, DjangoSessionService, DjangoToolConfig
|
from .adapters import DjangoRequest, DjangoSessionService, DjangoToolConfig
|
||||||
@@ -728,20 +728,6 @@ class EmbedMediaLTIView(View):
|
|||||||
context_id = lti_session.get('context_id')
|
context_id = lti_session.get('context_id')
|
||||||
platform_id = lti_session.get('platform_id')
|
platform_id = lti_session.get('platform_id')
|
||||||
|
|
||||||
# Auto-share: when the media owner loads their own embed via LTI,
|
|
||||||
# mark it as shared and link it to the course. This fires on the
|
|
||||||
# teacher's first page view after saving (Moodle redirects there automatically).
|
|
||||||
if media.user == request.user:
|
|
||||||
MediaPermission.objects.get_or_create(
|
|
||||||
media=media,
|
|
||||||
user=request.user,
|
|
||||||
defaults={'owner_user': request.user, 'permission': 'owner'},
|
|
||||||
)
|
|
||||||
if context_id:
|
|
||||||
category = Category.objects.filter(lti_context_id=context_id, is_rbac_category=True).first()
|
|
||||||
if category:
|
|
||||||
EmbedMediaCourse.objects.get_or_create(media=media, category=category)
|
|
||||||
|
|
||||||
if media.is_shared and context_id and platform_id:
|
if media.is_shared and context_id and platform_id:
|
||||||
try:
|
try:
|
||||||
resource_link = (
|
resource_link = (
|
||||||
|
|||||||
Reference in New Issue
Block a user