diff --git a/fuel/app/classes/materia/api/v1.php b/fuel/app/classes/materia/api/v1.php
index b173b7be3..ea536f8a2 100644
--- a/fuel/app/classes/materia/api/v1.php
+++ b/fuel/app/classes/materia/api/v1.php
@@ -237,6 +237,10 @@ static public function widget_instance_update($inst_id=null, $name=null, $qset=n
// student made widgets are locked forever
if ($inst->is_student_made)
{
+ if ($guest_access === false)
+ {
+ return new Msg('Student-made widgets must stay in guest access mode.', 'Student Made', 'error', false);
+ }
$attempts = -1;
$guest_access = true;
}
@@ -346,7 +350,7 @@ static public function widget_instance_update($inst_id=null, $name=null, $qset=n
$access = Perm_Manager::get_all_users_explicit_perms($inst_id, Perm::INSTANCE)['widget_user_perms'];
foreach ($access as $user_id => $user_perms)
{
- if (Perm_Manager::is_student($user_id))
+ if (Perm_Manager::is_student($user_id) && $user_id != $inst->user_id)
{
\Model_Notification::send_item_notification(\Model_user::find_current_id(), $user_id, Perm::INSTANCE, $inst_id, 'disabled', null);
Perm_Manager::clear_user_object_perms($inst_id, Perm::INSTANCE, $user_id);
diff --git a/fuel/app/classes/materia/perm/manager.php b/fuel/app/classes/materia/perm/manager.php
index bf3e703da..043f9aae1 100644
--- a/fuel/app/classes/materia/perm/manager.php
+++ b/fuel/app/classes/materia/perm/manager.php
@@ -38,7 +38,7 @@ static public function is_super_user()
// The session caching has been removed due to issues related to the cache when the role is added or revoked
// Ideally we can still find a way to cache this and make it more performant!!
return (\Fuel::$is_cli === true && ! \Fuel::$is_test) || self::does_user_have_role([\Materia\Perm_Role::SU]);
-
+
}
/**
@@ -351,10 +351,10 @@ static public function remove_users_from_roles_system_only(Array $user_ids = [],
->execute();
}
}
+
return $success;
}
-
/*
********************** User to Object Rights ***************************************
*/
diff --git a/src/components/attempts-slider.jsx b/src/components/attempts-slider.jsx
index 1fb7a0e37..d862035b5 100644
--- a/src/components/attempts-slider.jsx
+++ b/src/components/attempts-slider.jsx
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react'
import './my-widgets-settings-dialog.scss'
-const AttemptsSlider = ({inst, parentState, setParentState}) => {
+const AttemptsSlider = ({inst, is_student, parentState, setParentState, currentAttemptsVal}) => {
const [rawSliderVal, setRawSliderVal] = useState(parseInt(parentState.sliderVal))
const [sliderStopped, setSliderStopped] = useState(false)
@@ -16,12 +16,19 @@ const AttemptsSlider = ({inst, parentState, setParentState}) => {
const sliderStop = e => {
setSliderStopped(true)
}
-
+
// now that the slider value isn't actively changing, round the raw value to the nearest stop
// pass that rounded value up to the parent component
useEffect(() => {
if (sliderStopped && parentState.formData.changes.access != 'guest') {
const sliderInfo = getSliderInfo(rawSliderVal)
+ // students cannot change attempts to anything other than
+ // the original number of attempts or unlimited
+ if (is_student && sliderInfo.val != currentAttemptsVal && sliderInfo.val != '100') {
+ setSliderStopped(false)
+ setRawSliderVal(parseInt(parentState.sliderVal))
+ return
+ }
setParentState({...parentState, sliderVal: sliderInfo.val, lastActive: sliderInfo.last})
setSliderStopped(false)
}
@@ -66,16 +73,16 @@ const AttemptsSlider = ({inst, parentState, setParentState}) => {
const updateSliderNum = (val, index) => {
// Attempts always unlimited when guest access is true
if (parentState.formData.changes.access === 'guest') return
+ if (is_student && val != currentAttemptsVal && val != '100') return
setParentState({...parentState, sliderVal: val.toString(), lastActive: index})
}
const generateStopSpan = (stopId, sliderPosition, display) => {
- const spanClass = parentState.lastActive === stopId ? 'active' : ''
const stopClickHandler = () => updateSliderNum(sliderPosition, stopId)
return (
{display}
diff --git a/src/components/hooks/useSupportUpdateWidget.jsx b/src/components/hooks/useSupportUpdateWidget.jsx
index 5359721d3..a4f20e044 100644
--- a/src/components/hooks/useSupportUpdateWidget.jsx
+++ b/src/components/hooks/useSupportUpdateWidget.jsx
@@ -9,6 +9,10 @@ export default function useSupportUpdateWidget() {
apiUpdateWidget,
{
onSuccess: (data, variables) => {
+ if (!data || (data.type == 'error'))
+ {
+ return variables.errorFunc(data.msg)
+ }
variables.successFunc()
// Refresh widgets
@@ -20,7 +24,7 @@ export default function useSupportUpdateWidget() {
},
onError: (err, newWidget, context) => {
queryClient.setQueryData('widgets', context.previousValue)
-
+
variables.errorFunc()
}
}
diff --git a/src/components/hooks/useUpdateWidget.jsx b/src/components/hooks/useUpdateWidget.jsx
index 39321e331..14ad14656 100644
--- a/src/components/hooks/useUpdateWidget.jsx
+++ b/src/components/hooks/useUpdateWidget.jsx
@@ -19,7 +19,6 @@ export default function useUpdateWidget() {
return { widgetList }
},
onSuccess: (updatedInst, variables) => {
-
// update successful - insert new values into our local copy of widgetList
for (const page of widgetList?.pages) {
for (const inst of page?.pagination) {
@@ -33,7 +32,7 @@ export default function useUpdateWidget() {
}
}
}
-
+
// update query cache for widgets. This does NOT invalidate the cache, forcing a re-fetch!!
queryClient.setQueryData('widgets', widgetList)
diff --git a/src/components/my-widgets-collaborate-dialog.jsx b/src/components/my-widgets-collaborate-dialog.jsx
index ea0c0d28d..909eca400 100644
--- a/src/components/my-widgets-collaborate-dialog.jsx
+++ b/src/components/my-widgets-collaborate-dialog.jsx
@@ -262,6 +262,12 @@ const MyWidgetsCollaborateDialog = ({onClose, inst, myPerms, otherUserPerms, set
return
}
+ if (user.id == inst.user_id) {
+ user.is_owner = true;
+ } else {
+ user.is_owner = false;
+ }
+
return
-
+
{`${user.first} ${user.last}`}
diff --git a/src/components/my-widgets-scores.jsx b/src/components/my-widgets-scores.jsx
index c13e6429b..50d56119d 100644
--- a/src/components/my-widgets-scores.jsx
+++ b/src/components/my-widgets-scores.jsx
@@ -7,7 +7,7 @@ import LoadingIcon from './loading-icon'
import NoScoreContent from'./no-score-content'
import './my-widgets-scores.scss'
-const MyWidgetsScores = ({inst, beardMode}) => {
+const MyWidgetsScores = ({inst, beardMode, is_student}) => {
const [state, setState] = useState({
isShowingAll: false,
hasScores: false,
@@ -16,7 +16,7 @@ const MyWidgetsScores = ({inst, beardMode}) => {
const { data: currScores, isFetched } = useQuery({
queryKey: ['score-summary', inst.id],
queryFn: () => apiGetScoreSummary(inst.id),
- enabled: !!inst && !!inst.id,
+ enabled: !!inst && !!inst.id && !is_student,
staleTime: Infinity,
placeholderData: []
})
@@ -61,7 +61,9 @@ const MyWidgetsScores = ({inst, beardMode}) => {
const handleShowOlderClick = () => setState({...state, isShowingAll: !state.isShowingAll})
let contentRender =
- if (isFetched) {
+ if (is_student) {
+ contentRender = Students cannot view scores.
+ } else if (isFetched) {
contentRender =
if (state.hasScores || containsStorage()) {
const semesterElements = displayedSemesters.map(semester => (
@@ -99,8 +101,8 @@ const MyWidgetsScores = ({inst, beardMode}) => {
Student Activity
+ className={`aux_button ${inst.is_draft ? 'disabled' : ''}`}
+ onClick={openExport}>
Export Options
diff --git a/src/components/my-widgets-scores.scss b/src/components/my-widgets-scores.scss
index 1dd65f970..2e30b0ee9 100644
--- a/src/components/my-widgets-scores.scss
+++ b/src/components/my-widgets-scores.scss
@@ -10,6 +10,12 @@
overflow-y: auto;
padding-bottom: 1em;
+ &.limited_because_student {
+ filter: blur(3px);
+ user-select: none;
+ pointer-events: none;
+ }
+
#export_scores_button.disabled,
#export_scores_button.disabled:hover {
background: #d4d4d4;
diff --git a/src/components/my-widgets-selected-instance.jsx b/src/components/my-widgets-selected-instance.jsx
index b4d8b96f1..df74306ae 100644
--- a/src/components/my-widgets-selected-instance.jsx
+++ b/src/components/my-widgets-selected-instance.jsx
@@ -499,7 +499,7 @@ const MyWidgetSelectedInstance = ({
{ warningDialogRender }
{ settingsDialogRender }
{ lockedDialogRender }
-
+
)
}
diff --git a/src/components/my-widgets-settings-dialog.jsx b/src/components/my-widgets-settings-dialog.jsx
index db5fe6733..e295b629b 100644
--- a/src/components/my-widgets-settings-dialog.jsx
+++ b/src/components/my-widgets-settings-dialog.jsx
@@ -95,6 +95,7 @@ const MyWidgetsSettingsDialog = ({ onClose, inst, currentUser, otherUserPerms, o
}
}
})
+ inst.is_embedded = true
// Used for initialization
useEffect(() => {
@@ -225,7 +226,7 @@ const MyWidgetsSettingsDialog = ({ onClose, inst, currentUser, otherUserPerms, o
const changes = state.formData.changes
const openClose = validateFormData(changes.dates, changes.times, changes.periods)
const errInfo = getErrorInfo(openClose[2]) // Creates an error message if needed
- const errMsg = errInfo.msg
+ let errMsg = errInfo.msg
const errors = errInfo.errors
let form = {
inst_id: inst.id,
@@ -244,6 +245,10 @@ const MyWidgetsSettingsDialog = ({ onClose, inst, currentUser, otherUserPerms, o
form.attempts = -1
}
+ if (currentUser.is_student && form.attempts != inst.attempts && form.attempts != -1 ) {
+ errMsg = "Cannot set attempts to " + form.attempts + ". Students can keep the current number of attempts or set the attempt limits to Unlimited."
+ }
+
// Submits the form if there are no errors
if (errMsg.length === 0) {
let args = [
@@ -431,13 +436,20 @@ const MyWidgetsSettingsDialog = ({ onClose, inst, currentUser, otherUserPerms, o
}
let studentLimitWarningRender = null
- if ( currentUser.is_student) {
+ if ( currentUser.is_student && currentUser.id != inst.user_id ) {
studentLimitWarningRender = (
You are viewing a limited version of this page due to your current role as a student.
Students do not have permission to change certain settings like attempt limits or access levels.
)
+ } else if (currentUser.is_student && currentUser.id == inst.user_id) {
+ studentLimitWarningRender = (
+
+ You are viewing a limited version of this page due to your current role as a student.
+ Owners who are students may only change access to Guest Mode and increase attempt limits to Unlimited.
+
+ )
}
const handlePeriodSelectFormDataChange = data => setState({...state, formData: data})
@@ -472,6 +484,16 @@ const MyWidgetsSettingsDialog = ({ onClose, inst, currentUser, otherUserPerms, o
)
}
+ // Viewing access settings
+ let canViewNormal = !inst.is_student_made
+ let canViewEmbedded = inst.is_embedded && !inst.is_student_made
+ let canViewGuest = !currentUser.is_student || currentUser.id == inst.user_id
+ || inst.is_student_made
+ // Editing access settings
+ let canEditNormal = canViewNormal && !currentUser.is_student
+ let canEditGuest = !currentUser.is_student || currentUser.id == inst.user_id
+ let canEditEmbedded = canViewEmbedded && !currentUser.is_student
+
return (
@@ -481,21 +503,21 @@ const MyWidgetsSettingsDialog = ({ onClose, inst, currentUser, otherUserPerms, o
{ studentLimitWarningRender }
-
+
Attempts
-
+
{ periodSelectElements }
Access
-
- {currentUser.is_student && !inst.is_student_made ? Access settings are currently disabled because of your student status. : ''}
-
+
-
+
accessChange('guest')} />
Guest Mode
@@ -523,14 +545,15 @@ const MyWidgetsSettingsDialog = ({ onClose, inst, currentUser, otherUserPerms, o
+ className={`embed-only ${canViewEmbedded ? ' show' : ''} ${!canEditEmbedded ? ' limited-because-student disabled' : ''}`} aria-hidden={!canViewEmbedded}>
{accessChange('embed')}}
/>
- Embedded Only
+ Embedded Only
This widget will not be playable outside of the classes
it is embedded within.
diff --git a/src/components/my-widgets-settings-dialog.scss b/src/components/my-widgets-settings-dialog.scss
index 419709eef..3e2241d9d 100644
--- a/src/components/my-widgets-settings-dialog.scss
+++ b/src/components/my-widgets-settings-dialog.scss
@@ -2,7 +2,6 @@
display: flex;
flex-direction: column;
width: 680px;
- max-height: 675px;
padding: 10px 10px 0 10px;
.top-bar {
@@ -199,6 +198,10 @@
&.active {
color: rgba(0, 0, 0, 1);
}
+ &.disabled {
+ color: rgba(0,0,0,0.5);
+ cursor: default;
+ }
}
// Entire bar is 67% so 2.68% per 1 attempt
@@ -276,15 +279,13 @@
}
}
- &.limited-because-student {
+ .limited-because-student {
- li.normal.show, li.guest-mode {
- filter: blur(3px);
- user-select: none;
- pointer-events: none;
- }
+ filter: blur(3px);
+ user-select: none;
+ pointer-events: none;
- li.studentWarningListItem {
+ &.studentWarningListItem {
margin: 0 0 15px 0;
}
}
@@ -301,7 +302,7 @@
font-size: 13px;
}
- .normal {
+ .normal, .embed-only {
display: none;
&.show {
@@ -317,17 +318,14 @@
margin-left: 5px;
}
- .guest-mode {
- &.disabled {
- label {
- color: #b5b3b3;
- }
+ .disabled {
+ label {
+ color: #b5b3b3;
}
}
}
#embedded-only {
- display: none;
&.show {
display: block;
diff --git a/src/components/support-selected-instance.jsx b/src/components/support-selected-instance.jsx
index ae062a837..4d2e6ea9b 100644
--- a/src/components/support-selected-instance.jsx
+++ b/src/components/support-selected-instance.jsx
@@ -189,8 +189,8 @@ const SupportSelectedInstance = ({inst, currentUser, embed = false}) => {
setSuccessText('Success!')
setErrorText('')
},
- errorFunc: () => {
- setErrorText('Error: Update Unsuccessful')
+ errorFunc: (msg) => {
+ setErrorText('Error: ' + msg)
setSuccessText('')
}
})
diff --git a/src/components/widget-admin-list-card.jsx b/src/components/widget-admin-list-card.jsx
index ac3c8dd34..a1a9dfc1c 100644
--- a/src/components/widget-admin-list-card.jsx
+++ b/src/components/widget-admin-list-card.jsx
@@ -37,7 +37,7 @@ const WidgetListCard = ({widget = null}) => {
event.persist()
setState(prevState => ({...prevState, widget: {...prevState.widget, meta_data: {...prevState.widget.meta_data, demo: event.target.value}}}))
}
-
+
const handleAboutChange = event => {
event.persist()
setState(prevState => ({...prevState, widget: {...prevState.widget, meta_data: {...prevState.widget.meta_data, about: event.target.value}}}))
@@ -81,7 +81,7 @@ const WidgetListCard = ({widget = null}) => {
excerpt: state.widget.meta_data.excerpt,
demo: state.widget.meta_data.demo,
}
-
+
apiUpdateWidgetAdmin(update).then(response => {
let errorMessage = []
let success = false
@@ -136,7 +136,7 @@ const WidgetListCard = ({widget = null}) => {
{state.widget.name}
- { ! state.widget.expanded ? <>> :
+ { ! state.widget.expanded ? <>> :
{ widgetErrorsRender }
{ widgetSuccessRender }
@@ -201,7 +201,7 @@ const WidgetListCard = ({widget = null}) => {
Demo:
-
diff --git a/src/util/api.js b/src/util/api.js
index 1008d1b4d..6740f5f8d 100644
--- a/src/util/api.js
+++ b/src/util/api.js
@@ -361,7 +361,10 @@ export const apiCanEditWidgets = arrayOfWidgetIds => {
*/
export const apiUpdateWidget = ({ args }) => {
return fetch('/api/json/widget_instance_update', fetchOptions({ body: `data=${formatFetchBody(args)}` }))
- .then(res => res.json())
+ .then(resp => {
+ if (resp.status === 204 || resp.status === 502) return []
+ return resp.json()
+ })
.then(widget => widget)
}