Index: lams_central/src/java/org/lamsfoundation/lams/web/qb/EditQbQuestionController.java =================================================================== diff -u -rdfa729666ab6bd11ce1440315e9bac1191fb734d -re7cbf85417c8226f95995e5adb34f7eeb386a98c --- lams_central/src/java/org/lamsfoundation/lams/web/qb/EditQbQuestionController.java (.../EditQbQuestionController.java) (revision dfa729666ab6bd11ce1440315e9bac1191fb734d) +++ lams_central/src/java/org/lamsfoundation/lams/web/qb/EditQbQuestionController.java (.../EditQbQuestionController.java) (revision e7cbf85417c8226f95995e5adb34f7eeb386a98c) @@ -125,9 +125,10 @@ } Integer userId = getUserId(); boolean editingAllowed = securityService.isAppadmin(userId, null, true) - || securityService.isSysadmin(userId, null, true) - || qbService.isQuestionInUserCollection(qbQuestion.getQuestionId(), userId) - || qbService.isQuestionInPublicCollection(qbQuestion.getQuestionId()); + || qbService.isQuestionInPublicCollection(qbQuestion.getQuestionId()) + || qbService.isQuestionInUserOwnCollection(qbQuestion.getQuestionId(), userId) + || qbService.isQuestionInUserSharedCollection(qbQuestion.getQuestionId(), userId) + || qbService.isQuestionInUserMonitoredOrganisationFolder(qbQuestion.getQuestionId(), userId); if (!editingAllowed) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "The user does not have access to given QB question editing"); Index: lams_central/src/java/org/lamsfoundation/lams/web/qb/QbCollectionController.java =================================================================== diff -u -rdfa729666ab6bd11ce1440315e9bac1191fb734d -re7cbf85417c8226f95995e5adb34f7eeb386a98c --- lams_central/src/java/org/lamsfoundation/lams/web/qb/QbCollectionController.java (.../QbCollectionController.java) (revision dfa729666ab6bd11ce1440315e9bac1191fb734d) +++ lams_central/src/java/org/lamsfoundation/lams/web/qb/QbCollectionController.java (.../QbCollectionController.java) (revision e7cbf85417c8226f95995e5adb34f7eeb386a98c) @@ -325,7 +325,7 @@ if (userId == null) { return false; } - if (securityService.isAppadmin(userId, null, true) || securityService.isSysadmin(userId, null, true)) { + if (securityService.isAppadmin(userId, "acess QB collection", true)){ return true; } Collection collections = qbService.getUserCollections(userId); Index: lams_central/src/java/org/lamsfoundation/lams/web/qb/QbStatsController.java =================================================================== diff -u -r9012873415698350686bf05ac1ddb1ee5b8fe154 -re7cbf85417c8226f95995e5adb34f7eeb386a98c --- lams_central/src/java/org/lamsfoundation/lams/web/qb/QbStatsController.java (.../QbStatsController.java) (revision 9012873415698350686bf05ac1ddb1ee5b8fe154) +++ lams_central/src/java/org/lamsfoundation/lams/web/qb/QbStatsController.java (.../QbStatsController.java) (revision e7cbf85417c8226f95995e5adb34f7eeb386a98c) @@ -37,6 +37,7 @@ import org.lamsfoundation.lams.qb.model.QbCollection; import org.lamsfoundation.lams.qb.model.QbQuestion; import org.lamsfoundation.lams.qb.service.IQbService; +import org.lamsfoundation.lams.security.ISecurityService; import org.lamsfoundation.lams.usermanagement.dto.UserDTO; import org.lamsfoundation.lams.util.Configuration; import org.lamsfoundation.lams.util.ConfigurationKeys; @@ -66,15 +67,20 @@ @Autowired private IOutcomeService outcomeService; + @Autowired + private ISecurityService securityService; + @RequestMapping("/show") public String showStats(@RequestParam long qbQuestionUid, Model model) throws Exception { QbStatsDTO stats = qbService.getQuestionStats(qbQuestionUid); model.addAttribute("stats", stats); Integer userId = getUserId(); int qbQuestionId = stats.getQuestion().getQuestionId(); - boolean managementAllowed = qbService.isQuestionInUserCollection(qbQuestionId, userId) - || qbService.isQuestionInPublicCollection(qbQuestionId); + boolean managementAllowed = securityService.isAppadmin(getUserId(), "allow QB question editing", true) + || qbService.isQuestionInPublicCollection(qbQuestionId) + || qbService.isQuestionInUserOwnCollection(qbQuestionId, userId) + || qbService.isQuestionInUserSharedCollection(qbQuestionId, userId); model.addAttribute("managementAllowed", managementAllowed); Collection existingCollections = qbService.getQuestionCollectionsByUid(qbQuestionUid); Index: lams_common/src/java/org/lamsfoundation/lams/qb/dao/IQbDAO.java =================================================================== diff -u -r8bdf2c81e6073c5bde35413e62767e4804b638d7 -re7cbf85417c8226f95995e5adb34f7eeb386a98c --- lams_common/src/java/org/lamsfoundation/lams/qb/dao/IQbDAO.java (.../IQbDAO.java) (revision 8bdf2c81e6073c5bde35413e62767e4804b638d7) +++ lams_common/src/java/org/lamsfoundation/lams/qb/dao/IQbDAO.java (.../IQbDAO.java) (revision e7cbf85417c8226f95995e5adb34f7eeb386a98c) @@ -86,10 +86,14 @@ Set getCollectionQuestionIdsExcluded(long collectionUid, Collection qbQuestionIds); - boolean isQuestionInUserCollection(int userId, int qbQuestionId); + boolean isQuestionInUserOwnCollection(int userId, int qbQuestionId); + boolean isQuestionInUserSharedCollection(int userId, int qbQuestionId); + boolean isQuestionInPublicCollection(int qbQuestionId); + boolean isQuestionInUserMonitoredOrganisationFolder(int qbQuestionId, int userId); + int mergeQuestions(long sourceQbQUestionUid, long targetQbQuestionUid); void removeAnswersByToolContentId(long toolContentId); Index: lams_common/src/java/org/lamsfoundation/lams/qb/dao/hibernate/QbDAO.java =================================================================== diff -u -r6b45c9ecb6999e184ca671297026bc6fa6faf9b5 -re7cbf85417c8226f95995e5adb34f7eeb386a98c --- lams_common/src/java/org/lamsfoundation/lams/qb/dao/hibernate/QbDAO.java (.../QbDAO.java) (revision 6b45c9ecb6999e184ca671297026bc6fa6faf9b5) +++ lams_common/src/java/org/lamsfoundation/lams/qb/dao/hibernate/QbDAO.java (.../QbDAO.java) (revision e7cbf85417c8226f95995e5adb34f7eeb386a98c) @@ -26,6 +26,7 @@ import org.lamsfoundation.lams.qb.model.QbToolQuestion; import org.lamsfoundation.lams.tool.ToolContent; import org.lamsfoundation.lams.usermanagement.Role; +import org.lamsfoundation.lams.usermanagement.WorkspaceFolder; public class QbDAO extends LAMSBaseDAO implements IQbDAO { @@ -131,14 +132,32 @@ private static final String FIND_QUESTIONS_BY_TOOL_CONTENT_ID = "SELECT tq.qbQuestion FROM QbToolQuestion AS tq " + "WHERE tq.toolContentId = :toolContentId"; - private static final String IS_QUESTION_IN_USER_COLLECTION = "SELECT DISTINCT 1 FROM lams_qb_collection_question AS cq " + private static final String IS_QUESTION_IN_USER_OWN_COLLECTION = "SELECT DISTINCT 1 FROM lams_qb_collection_question AS cq " + "JOIN lams_qb_collection AS c ON cq.collection_uid = c.uid AND " + "c.user_id = :userId AND cq.qb_question_id = :qbQuestionId"; + private static final String IS_QUESTION_IN_USER_SHARED_COLLECTION = "SELECT DISTINCT 1 FROM lams_qb_collection_question AS cq " + + "JOIN lams_qb_collection AS c ON cq.collection_uid = c.uid " + + "JOIN lams_qb_collection_organisation AS co ON co.collection_uid = c.uid " + + "JOIN lams_user_organisation AS uo USING (organisation_id) " + + "JOIN lams_user_organisation_role AS uor USING (user_organisation_id) " + + "WHERE uo.user_id = :userId AND cq.qb_question_id = :qbQuestionId AND uor.role_id = " + Role.ROLE_AUTHOR; + private static final String IS_QUESTION_IN_PUBLIC_COLLECTION = "SELECT DISTINCT 1 FROM lams_qb_collection_question AS cq " + "JOIN lams_qb_collection AS c ON cq.collection_uid = c.uid AND " + "c.user_id IS NULL AND cq.qb_question_id = :qbQuestionId"; + private static final String IS_QUESTION_IN_USER_MONITORED_ORGANISATION_FOLDER = "SELECT DISTINCT 1 FROM lams_qb_question AS q " + + "JOIN lams_qb_tool_question AS tq ON q.uid = tq.qb_question_uid AND q.question_id = :qbQuestionId " + + "JOIN lams_learning_activity AS a USING (tool_content_id) " + + "JOIN lams_learning_design AS ld USING (learning_design_id) " + + "JOIN lams_workspace_folder AS wf USING (workspace_folder_id)" + + "LEFT JOIN lams_user_organisation AS uo USING (organisation_id) " + + "LEFT JOIN lams_user_organisation_role AS uor USING (user_organisation_id) " + + "WHERE ld.removed = 0 AND (wf.user_id = :userId OR wf.lams_workspace_folder_type_id = " + + WorkspaceFolder.PUBLIC_SEQUENCES + " OR (uo.user_id = :userId AND uor.role_id = " + Role.ROLE_MONITOR + + "))"; + private static final String GENERATE_QUESTION_ID = "INSERT INTO lams_sequence_generator(lams_qb_question_question_id) VALUES (:qbQuestionId)"; private static final String MERGE_TOOL_QUESTIONS = "UPDATE QbToolQuestion SET qbQuestion.uid = :targetQbQuestionUid WHERE qbQuestion.uid = :sourceQbQuestionUid"; @@ -571,17 +590,29 @@ } @Override - public boolean isQuestionInUserCollection(int userId, int qbQuestionId) { - return getSession().createNativeQuery(IS_QUESTION_IN_USER_COLLECTION).setParameter("userId", userId) + public boolean isQuestionInUserOwnCollection(int userId, int qbQuestionId) { + return getSession().createNativeQuery(IS_QUESTION_IN_USER_OWN_COLLECTION).setParameter("userId", userId) .setParameter("qbQuestionId", qbQuestionId).uniqueResult() != null; } @Override + public boolean isQuestionInUserSharedCollection(int userId, int qbQuestionId) { + return getSession().createNativeQuery(IS_QUESTION_IN_USER_SHARED_COLLECTION).setParameter("userId", userId) + .setParameter("qbQuestionId", qbQuestionId).uniqueResult() != null; + } + + @Override public boolean isQuestionInPublicCollection(int qbQuestionId) { return getSession().createNativeQuery(IS_QUESTION_IN_PUBLIC_COLLECTION) .setParameter("qbQuestionId", qbQuestionId).uniqueResult() != null; } + @Override + public boolean isQuestionInUserMonitoredOrganisationFolder(int qbQuestionId, int userId) { + return getSession().createNativeQuery(IS_QUESTION_IN_USER_MONITORED_ORGANISATION_FOLDER) + .setParameter("userId", userId).setParameter("qbQuestionId", qbQuestionId).uniqueResult() != null; + } + private boolean isQuestionInCollection(long collectionUid, int qbQuestionId) { return !getSession().createNativeQuery(IS_QUESTION_IN_COLLECTION).setParameter("collectionUid", collectionUid) .setParameter("qbQuestionId", qbQuestionId).getResultList().isEmpty(); Index: lams_common/src/java/org/lamsfoundation/lams/qb/service/IQbService.java =================================================================== diff -u -reaf86cc891c4a0dbcc71fa7bd87f0657a250c730 -re7cbf85417c8226f95995e5adb34f7eeb386a98c --- lams_common/src/java/org/lamsfoundation/lams/qb/service/IQbService.java (.../IQbService.java) (revision eaf86cc891c4a0dbcc71fa7bd87f0657a250c730) +++ lams_common/src/java/org/lamsfoundation/lams/qb/service/IQbService.java (.../IQbService.java) (revision e7cbf85417c8226f95995e5adb34f7eeb386a98c) @@ -154,10 +154,14 @@ void releaseFromCache(Object object); - boolean isQuestionInUserCollection(int qbQuestionId, int userId); + boolean isQuestionInUserOwnCollection(int qbQuestionId, int userId); + boolean isQuestionInUserSharedCollection(int qbQuestionId, int userId); + boolean isQuestionInPublicCollection(int qbQuestionId); + boolean isQuestionInUserMonitoredOrganisationFolder(int qbQuestionId, int userId); + void insertQuestion(QbQuestion qbQuestion); void prepareQuestionForExport(QbQuestion qbQuestion); Index: lams_common/src/java/org/lamsfoundation/lams/qb/service/QbService.java =================================================================== diff -u -r8bdf2c81e6073c5bde35413e62767e4804b638d7 -re7cbf85417c8226f95995e5adb34f7eeb386a98c --- lams_common/src/java/org/lamsfoundation/lams/qb/service/QbService.java (.../QbService.java) (revision 8bdf2c81e6073c5bde35413e62767e4804b638d7) +++ lams_common/src/java/org/lamsfoundation/lams/qb/service/QbService.java (.../QbService.java) (revision e7cbf85417c8226f95995e5adb34f7eeb386a98c) @@ -385,10 +385,7 @@ @Override public List getUserOwnCollections(int userId) { - Map map = new HashMap<>(); - map.put("userId", userId); - map.put("personal", false); - List result = qbDAO.findByProperties(QbCollection.class, map); + List result = qbDAO.findByProperty(QbCollection.class, "userId", userId); Collections.sort(result, COLLECTION_NAME_COMPARATOR); return result; } @@ -638,9 +635,6 @@ public List getUserCollections(int userId) { Set collections = new LinkedHashSet<>(); - QbCollection privateCollection = getUserPrivateCollection(userId); - collections.add(privateCollection); - collections.addAll(getUserOwnCollections(userId)); QbCollection publicCollection = getPublicCollection(); @@ -675,16 +669,30 @@ } @Override - public boolean isQuestionInUserCollection(int qbQuestionId, int userId) { - return qbDAO.isQuestionInUserCollection(userId, qbQuestionId); + public boolean isQuestionInUserOwnCollection(int qbQuestionId, int userId) { + return qbDAO.isQuestionInUserOwnCollection(userId, qbQuestionId); } @Override + public boolean isQuestionInUserSharedCollection(int qbQuestionId, int userId) { + return qbDAO.isQuestionInUserSharedCollection(userId, qbQuestionId); + } + + @Override public boolean isQuestionInPublicCollection(int qbQuestionId) { return qbDAO.isQuestionInPublicCollection(qbQuestionId); } /** + * Is the question in a learning design which is in user's private folder, public folder + * or a course folder where user is a monitor. + */ + @Override + public boolean isQuestionInUserMonitoredOrganisationFolder(int qbQuestionId, int userId) { + return qbDAO.isQuestionInUserMonitoredOrganisationFolder(qbQuestionId, userId); + } + + /** * Cascades in QbToolQuestion, QbQuestion and QbOptions do not seem to work on insert. * New QbQuestions need to be saved step by step. */ Index: lams_tool_scratchie/web/pages/monitoring/studentChoices.jsp =================================================================== diff -u -r8a7a259deba12163c46440a712e41bde926fdaf8 -re7cbf85417c8226f95995e5adb34f7eeb386a98c --- lams_tool_scratchie/web/pages/monitoring/studentChoices.jsp (.../studentChoices.jsp) (revision 8a7a259deba12163c46440a712e41bde926fdaf8) +++ lams_tool_scratchie/web/pages/monitoring/studentChoices.jsp (.../studentChoices.jsp) (revision e7cbf85417c8226f95995e5adb34f7eeb386a98c) @@ -15,44 +15,16 @@ body { -webkit-overflow-scrolling: touch; } - - #questions-header-sticky { - display: none; - position: sticky; - top: 0; - z-index: 1; - background: #FFF; - } - #questions-header-sticky.stick { - display: table; - } - - /* Use strick column widths so sticky and standard header look the same. */ - #questions-header-sticky thead th:first-child, - #questions-data thead th:first-child { - min-width: 17rem; - max-width: 17rem; - width: 17rem; - } - - .question-header-cell { - min-width: 10rem; - max-width: 10rem; - width: 10rem; - } - - .question-header-summary-cell { - min-width: 7rem; - max-width: 7rem; - width: 7rem; - } - #questions-data { position: relative; background: #FFF; } + #questions-data thead { + background: #FFF; + } + #questions-data tbody th { position: -webkit-sticky; /* for Safari */ position: sticky; @@ -94,15 +66,19 @@ // Add sticky column headers to student choices table. // Standard sticky header CSS solution does not work as it is page which is being scrolled, not the table itself - let studentChoicesTable = $('#questions-data'), - studentChoicesStickyHeader = $('#questions-header-sticky'); window.onscroll = function() { - let studentChoicesTableTopOffset = studentChoicesTable.offset().top, + let studentChoicesTable = $('#questions-data'), + studentChoicesStickyHeader = $('thead', studentChoicesTable), + studentChoicesTableTopOffset = studentChoicesTable.offset().top, studentChoicesTableHeight = studentChoicesTable.height(); - studentChoicesStickyHeader.toggleClass("stick", - window.pageYOffset > studentChoicesTableTopOffset + 20 - && window.pageYOffset < studentChoicesTableTopOffset + studentChoicesTableHeight); + if (window.pageYOffset > studentChoicesTableTopOffset + 20 + && window.pageYOffset < studentChoicesTableTopOffset + studentChoicesTableHeight - 20) { + studentChoicesStickyHeader + .css('transform', 'translateY(' + (window.pageYOffset - studentChoicesTableTopOffset - 2) + 'px)'); + } else { + studentChoicesStickyHeader.css('transform', 'none'); + } } }); @@ -165,66 +141,28 @@ - - - - - - - - - - - - - - - - -
- - ${i.index + 1} - - - - - - - - - - - -   - "> - - % -
-
- -
+
- - -
+ ${i.index + 1} +   "> + %