Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue/1512-and-1504 #1516

Closed
wants to merge 9 commits into from
6 changes: 5 additions & 1 deletion fuel/app/classes/materia/api/v1.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions fuel/app/classes/materia/perm/manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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]);

}

/**
Expand Down Expand Up @@ -351,10 +351,10 @@ static public function remove_users_from_roles_system_only(Array $user_ids = [],
->execute();
}
}

return $success;
}


/*
********************** User to Object Rights ***************************************
*/
Expand Down
15 changes: 11 additions & 4 deletions src/components/attempts-slider.jsx
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)
}
Expand Down Expand Up @@ -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 (
<span key={stopId}
className={spanClass}
className={`${parentState.lastActive === stopId ? 'active' : ''}`}
onClick={stopClickHandler}>
{display}
</span>
Expand Down
6 changes: 5 additions & 1 deletion src/components/hooks/useSupportUpdateWidget.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,7 +24,7 @@ export default function useSupportUpdateWidget() {
},
onError: (err, newWidget, context) => {
queryClient.setQueryData('widgets', context.previousValue)

variables.errorFunc()
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/components/hooks/useUpdateWidget.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions src/components/my-widgets-collaborate-dialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,12 @@ const MyWidgetsCollaborateDialog = ({onClose, inst, myPerms, otherUserPerms, set
return <div key={userId}></div>
}

if (user.id == inst.user_id) {
user.is_owner = true;
} else {
user.is_owner = false;
}

return <CollaborateUserRow
key={user.id}
user={user}
Expand Down
17 changes: 13 additions & 4 deletions src/components/my-widgets-collaborate-dialog.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
position: relative;
text-align: left;
display: block;

font-weight: bold;
}

Expand Down Expand Up @@ -72,10 +72,10 @@
width: 447px;
padding-bottom: 5px;
overflow: auto;

background-color: #ffffff;
border: #bfbfbf 1px solid;

text-align: left;

.collab-search-match {
Expand All @@ -101,7 +101,7 @@
margin: 5px 0 0 5px;
font-size: 14px;
text-align: left;

font-family: 'Lucida Grande', sans-serif;
}
}
Expand Down Expand Up @@ -295,6 +295,15 @@
font-size: 11px;
color: gray;
}

&.user-match-owner:after {
content: 'Owner';
position: absolute;
top: -12px;
left: 0;
font-size: 11px;
color: gray;
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/my-widgets-collaborate-user-row.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ const CollaborateUserRow = ({user, perms, myPerms, isCurrentUser, onChange, read
<div className='about'>
<img className='avatar' src={user.avatar} />

<span className={`name ${user.is_student ? 'user-match-student' : ''}`}>
<span className={`name ${user.is_owner ? 'user-match-owner' : user.is_student ? 'user-match-student' : ''}`}>
{`${user.first} ${user.last}`}
</span>
</div>
Expand Down
12 changes: 7 additions & 5 deletions src/components/my-widgets-scores.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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: []
})
Expand Down Expand Up @@ -61,7 +61,9 @@ const MyWidgetsScores = ({inst, beardMode}) => {
const handleShowOlderClick = () => setState({...state, isShowingAll: !state.isShowingAll})

let contentRender = <LoadingIcon />
if (isFetched) {
if (is_student) {
contentRender = <p>Students cannot view scores.</p>
} else if (isFetched) {
contentRender = <NoScoreContent scorable={parseInt(inst.widget.is_scorable)} isDraft={inst.is_draft} beardMode={beardMode} />
if (state.hasScores || containsStorage()) {
const semesterElements = displayedSemesters.map(semester => (
Expand Down Expand Up @@ -99,8 +101,8 @@ const MyWidgetsScores = ({inst, beardMode}) => {
<div className='scores'>
<h2>Student Activity</h2>
<span id='export_scores_button'
className={`aux_button ${inst.is_draft ? 'disabled' : ''}`}
onClick={openExport}>
className={`aux_button ${inst.is_draft ? 'disabled' : ''}`}
onClick={openExport}>
<span className='arrow_down'></span>
Export Options
</span>
Expand Down
6 changes: 6 additions & 0 deletions src/components/my-widgets-scores.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/components/my-widgets-selected-instance.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ const MyWidgetSelectedInstance = ({
{ warningDialogRender }
{ settingsDialogRender }
{ lockedDialogRender }
<MyWidgetsScores inst={inst} setInvalidLogin={setInvalidLogin} beardMode={beardMode}/>
<MyWidgetsScores inst={inst} setInvalidLogin={setInvalidLogin} beardMode={beardMode} is_student={currentUser.is_student}/>
</section>
)
}
Expand Down
47 changes: 35 additions & 12 deletions src/components/my-widgets-settings-dialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const MyWidgetsSettingsDialog = ({ onClose, inst, currentUser, otherUserPerms, o
}
}
})
inst.is_embedded = true

// Used for initialization
useEffect(() => {
Expand Down Expand Up @@ -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,
Expand All @@ -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 = [
Expand Down Expand Up @@ -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 = (
<p className='student-role-notice'>
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.
</p>
)
} else if (currentUser.is_student && currentUser.id == inst.user_id) {
studentLimitWarningRender = (
<p className='student-role-notice'>
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.
</p>
)
}

const handlePeriodSelectFormDataChange = data => setState({...state, formData: data})
Expand Down Expand Up @@ -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 (
<Modal onClose={onClose} ignoreClose={state.showWarning}>
<div className='settings-modal'>
Expand All @@ -481,21 +503,21 @@ const MyWidgetsSettingsDialog = ({ onClose, inst, currentUser, otherUserPerms, o
</div>
{ studentLimitWarningRender }
<ul className='attemptsPopup'>
<li className={`attempt-content ${currentUser.is_student ? 'hide' : ''}`}>
<li className={`attempt-content ${currentUser.is_student && currentUser.id != inst.user_id ? 'hide' : ''}`}>
<h3>Attempts</h3>
<AttemptsSlider key='slider-key' inst={inst} parentState={state} setParentState={setState}/>
<AttemptsSlider key='slider-key' inst={inst} parentState={state} setParentState={setState} is_student={currentUser.is_student} currentAttemptsVal={attemptsToValue(inst.attempts)}/>
</li>
<ul className='to-from'>
{ periodSelectElements }
<li className='access'>
<h3>Access</h3>
<ul className={`access-options ${inst.is_embedded ? 'embedded' : ''} ${currentUser.is_student && !inst.is_student_made ? 'limited-because-student' : ''}`}>
{currentUser.is_student && !inst.is_student_made ? <li className='studentWarningListItem student-role-notice'>Access settings are currently disabled because of your student status.</li> : ''}
<li className={`normal ${inst.is_student_made ? '' : 'show'}`}>
<ul className={`access-options ${inst.is_embedded ? 'embedded' : ''}`}>
{currentUser.is_student && !inst.is_student_made ? <li className='studentWarningListItem student-role-notice'>Access settings are currently limited because of your student status.</li> : ''}
<li className={`normal ${!canViewNormal ? '' : 'show'} ${!canEditNormal ? ' limited-because-student' : ''}`} aria-hidden={!canViewNormal}>
<input type='radio'
id='normal-radio'
value='normal'
disabled={currentUser.is_student}
disabled={!canEditNormal}
checked={state.formData.changes.access === 'normal'}
onChange={() => accessChange('normal')} />
<label htmlFor='normal-radio'>Normal</label>
Expand All @@ -505,11 +527,11 @@ const MyWidgetsSettingsDialog = ({ onClose, inst, currentUser, otherUserPerms, o
The widget can be distributed via URL, embed code, or as an assignment in your LMS.
</div>
</li>
<li className={`guest-mode ${inst.is_student_made ? 'disabled' : ''}`}>
<li className={`guest-mode ${!canEditGuest ? 'disabled' : ''} ${!canViewGuest ? ' limited-because-student ' : ''} `} aria-hidden={!canViewGuest}>
<input type='radio'
id='guest-radio'
value='guest'
disabled={currentUser.is_student && !inst.is_student_made}
disabled={!canEditGuest}
checked={state.formData.changes.access === 'guest'}
onChange={() => accessChange('guest')} />
<label htmlFor='guest-radio'>Guest Mode</label>
Expand All @@ -523,14 +545,15 @@ const MyWidgetsSettingsDialog = ({ onClose, inst, currentUser, otherUserPerms, o
</div>
</li>
<li id='embedded-only'
className={`embed-only ${inst.is_embedded ? 'show' : ''}`}>
className={`embed-only ${canViewEmbedded ? ' show' : ''} ${!canEditEmbedded ? ' limited-because-student disabled' : ''}`} aria-hidden={!canViewEmbedded}>
<input type='radio'
id='embed-radio'
value='embed'
disabled={!canEditEmbedded}
checked={state.formData.changes.access === 'embed'}
onChange={() => {accessChange('embed')}}
/>
<label>Embedded Only</label>
<label htmlFor='embed-radio'>Embedded Only</label>
<div className='input-desc'>
This widget will not be playable outside of the classes
it is embedded within.
Expand Down
Loading
Loading