Improved category listing and link hiding

This commit is contained in:
PMKuipers 2024-02-04 23:18:11 +01:00
parent 29f11982ff
commit 8aed72af70
12 changed files with 114 additions and 93 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/primary-nav-tools",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.hide_primary=function(hrefs){("string"==typeof hrefs||hrefs instanceof String)&&(hrefs=[hrefs]);if("object"==typeof hrefs&&Array.isArray(hrefs)){let element=document.createElement("style");document.head.appendChild(element);let sheet=element.sheet;for(const ix in hrefs){const href=hrefs[ix];console.info(`Hiding ${href}`);let style=`\n li > a[href*="${href}"] {\n display: none;\n }\n `;sheet.insertRule(style,sheet.cssRules.length),style=`\n #usernavigation a[href*="${href}"] {\n display: none;\n }\n `,sheet.insertRule(style,sheet.cssRules.length)}console.info(element),console.info(sheet)}}})); define("local_treestudyplan/primary-nav-tools",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.hide_primary=function(hrefs){("string"==typeof hrefs||hrefs instanceof String)&&(hrefs=[hrefs]);if("object"==typeof hrefs&&Array.isArray(hrefs)){let css="";for(const ix in hrefs){const href=hrefs[ix];console.info(`Hiding ${href}`),css+=`\n li > a[href*="${href}"] {\n display: none !important; \n }\n `,css+=`\n #usernavigation a[href*="${href}"] {\n display: none !important;\n }\n `}const element=document.createElement("style");element.setAttribute("type","text/css"),"textContent"in element?element.textContent=css:element.styleSheet.cssText=css,document.head.appendChild(element),console.info(element)}}}));
//# sourceMappingURL=primary-nav-tools.min.js.map //# sourceMappingURL=primary-nav-tools.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"primary-nav-tools.min.js","sources":["../src/primary-nav-tools.js"],"sourcesContent":["/*eslint-env es6*/\n/*eslint no-console: \"off\"*/\n\n/**\n * Hide a primary navigation item by href\n * @param {string|Array} hrefs The link that should be hidden\n */\nexport function hide_primary(hrefs) {\n if(typeof hrefs === 'string' || hrefs instanceof String){\n hrefs = [hrefs];\n }\n\n if(typeof hrefs === 'object' && Array.isArray(hrefs)){\n let element = document.createElement('style');\n document.head.appendChild(element);\n let sheet = element.sheet;\n for(const ix in hrefs){\n const href = hrefs[ix];\n console.info(`Hiding ${href}`);\n let style = `\n li > a[href*=\"${href}\"] {\n display: none;\n }\n `;\n sheet.insertRule(style, sheet.cssRules.length);\n style = `\n #usernavigation a[href*=\"${href}\"] {\n display: none;\n }\n `;\n sheet.insertRule(style, sheet.cssRules.length);\n }\n console.info(element);\n console.info(sheet);\n }\n}\n"],"names":["hrefs","String","Array","isArray","element","document","createElement","head","appendChild","sheet","ix","href","console","info","style","insertRule","cssRules","length"],"mappings":"sKAO6BA,QACL,iBAAVA,OAAsBA,iBAAiBC,UAC7CD,MAAQ,CAACA,WAGO,iBAAVA,OAAsBE,MAAMC,QAAQH,OAAO,KAC7CI,QAAUC,SAASC,cAAc,SACrCD,SAASE,KAAKC,YAAYJ,aACtBK,MAAQL,QAAQK,UAChB,MAAMC,MAAMV,MAAM,OACZW,KAAOX,MAAMU,IACnBE,QAAQC,KAAM,UAASF,YACnBG,MAAS,mCACOH,gFAIpBF,MAAMM,WAAWD,MAAOL,MAAMO,SAASC,QACvCH,MAAS,8CACsBH,gFAI/BF,MAAMM,WAAWD,MAAOL,MAAMO,SAASC,QAE3CL,QAAQC,KAAKT,SACbQ,QAAQC,KAAKJ"} {"version":3,"file":"primary-nav-tools.min.js","sources":["../src/primary-nav-tools.js"],"sourcesContent":["/*eslint-env es6*/\n/*eslint no-console: \"off\"*/\n\n/**\n * Hide a primary navigation item by href\n * @param {string|Array} hrefs The link that should be hidden\n */\nexport function hide_primary(hrefs) {\n if(typeof hrefs === 'string' || hrefs instanceof String){\n hrefs = [hrefs];\n }\n\n if(typeof hrefs === 'object' && Array.isArray(hrefs)){\n let css = '' ;\n for(const ix in hrefs){\n const href = hrefs[ix];\n console.info(`Hiding ${href}`);\n css += `\n li > a[href*=\"${href}\"] {\n display: none !important; \n }\n `;\n css += `\n #usernavigation a[href*=\"${href}\"] {\n display: none !important;\n }\n `;\n }\n\n\n const element = document.createElement('style');\n element.setAttribute('type', 'text/css');\n\n if ('textContent' in element) {\n element.textContent = css;\n } else {\n element.styleSheet.cssText = css;\n }\n\n document.head.appendChild(element);\n\n console.info(element);\n }\n}\n"],"names":["hrefs","String","Array","isArray","css","ix","href","console","info","element","document","createElement","setAttribute","textContent","styleSheet","cssText","head","appendChild"],"mappings":"sKAO6BA,QACL,iBAAVA,OAAsBA,iBAAiBC,UAC7CD,MAAQ,CAACA,WAGO,iBAAVA,OAAsBE,MAAMC,QAAQH,OAAO,KAC7CI,IAAM,OACN,MAAMC,MAAML,MAAM,OACZM,KAAON,MAAMK,IACnBE,QAAQC,KAAM,UAASF,QACvBF,KAAQ,mCACYE,4FAIpBF,KAAQ,8CACuBE,iGAO7BG,QAAUC,SAASC,cAAc,SACvCF,QAAQG,aAAa,OAAQ,YAEzB,gBAAiBH,QACjBA,QAAQI,YAAcT,IAEtBK,QAAQK,WAAWC,QAAUX,IAGjCM,SAASM,KAAKC,YAAYR,SAE1BF,QAAQC,KAAKC"}

View File

@ -193,7 +193,7 @@ export function init(contextid,categoryid,options) {
app.courses = response; app.courses = response;
}).catch(notification.exception); }).catch(notification.exception);
call([{ call([{
methodname: 'local_treestudyplan_list_used_categories', methodname: 'local_treestudyplan_list_available_categories',
args: { operation: 'edit', refcontext_id: contextid} args: { operation: 'edit', refcontext_id: contextid}
}])[0].then(function(response){ }])[0].then(function(response){
app.usedcontexts = response; app.usedcontexts = response;

View File

@ -100,13 +100,14 @@ export function init(contextid,categoryid) {
} }
}).catch(notification.exception); }).catch(notification.exception);
call([{ call([{
methodname: 'local_treestudyplan_list_used_categories', methodname: 'local_treestudyplan_list_available_categories',
args: { operation: 'view', refcontext_id: contextid} args: { operation: 'view', refcontext_id: contextid}
}])[0].then(function(response){ }])[0].then(function(response){
const contexts = []; const contexts = [];
for(const ix in response){ for(const ix in response){
if(response[ix].studyplancount >0){ const cat = response[ix];
contexts.push(response[ix]); if(cat.studyplancount > 0 || cat.context_id == contextid){
contexts.push(cat);
} }
} }
app.usedcontexts = contexts; app.usedcontexts = contexts;

View File

@ -11,26 +11,34 @@ export function hide_primary(hrefs) {
} }
if(typeof hrefs === 'object' && Array.isArray(hrefs)){ if(typeof hrefs === 'object' && Array.isArray(hrefs)){
let element = document.createElement('style'); let css = '' ;
document.head.appendChild(element);
let sheet = element.sheet;
for(const ix in hrefs){ for(const ix in hrefs){
const href = hrefs[ix]; const href = hrefs[ix];
console.info(`Hiding ${href}`); console.info(`Hiding ${href}`);
let style = ` css += `
li > a[href*="${href}"] { li > a[href*="${href}"] {
display: none; display: none !important;
} }
`; `;
sheet.insertRule(style, sheet.cssRules.length); css += `
style = `
#usernavigation a[href*="${href}"] { #usernavigation a[href*="${href}"] {
display: none; display: none !important;
} }
`; `;
sheet.insertRule(style, sheet.cssRules.length);
} }
const element = document.createElement('style');
element.setAttribute('type', 'text/css');
if ('textContent' in element) {
element.textContent = css;
} else {
element.styleSheet.cssText = css;
}
document.head.appendChild(element);
console.info(element); console.info(element);
console.info(sheet);
} }
} }

View File

@ -54,8 +54,11 @@ class courseservice extends \external_api {
* @param int $userid Id of the user * @param int $userid Id of the user
* @return array of core_course_category * @return array of core_course_category
*/ */
public static function user_tops($userid) { public static function user_tops($userid=null,$capability='moodle/category:viewcourselist') {
global $DB; global $DB, $USER;
if ($userid == null) {
$userid = $USER->id;
}
$tops = []; $tops = [];
/* /*
@ -300,35 +303,22 @@ class courseservice extends \external_api {
} }
/** /**
* [Description for categories_by_capability] * List all user visible categories the current user has a given capability for.
* @param mixed $capability * @param mixed $capability
* @param core_course_category|null $parent * @param core_course_category|null $parent
* @return array * @return array
*/ */
public static function categories_by_capability($capability, core_course_category $parent = null) { public static function categories_by_capability($capability) {
global $USER;
// List the categories in which the user has a specific capability. // List the categories in which the user has a specific capability.
$list = []; $list = [];
// Initialize parent if needed. $parents = self::user_tops($USER->id,$capability);
if ($parent == null) { array_merge($list,$parents);
$parent = \core_course_category::user_top();
if (has_capability($capability, $parent->get_context())) {
$list[] = $parent;
}
}
$children = $parent->get_children(); foreach ($parents as $parent) {
foreach ($children as $child) {
// Check if we should add this category.
if (has_capability($capability, $child->get_context())) {
$list[] = $child;
// For optimization purposes, we include all its children now, since they will have inherited the permission. // For optimization purposes, we include all its children now, since they will have inherited the permission.
// #PREMATURE_OPTIMIZATION ???. // #PREMATURE_OPTIMIZATION ???.
$list = array_merge($list, self::recursive_child_categories($child)); $list = array_merge($list, self::recursive_child_categories($parent));
} else {
if ($child->get_children_count() > 0) {
$list = array_merge($list, self::categories_by_capability($capability, $child));
}
}
} }
return $list; return $list;
@ -352,9 +342,9 @@ class courseservice extends \external_api {
} }
/** /**
* Return value description for list_used_categories function * Return value description for list_available_categories function
*/ */
public static function list_used_categories_parameters() : \external_function_parameters { public static function list_available_categories_parameters() : \external_function_parameters {
return new \external_function_parameters( [ return new \external_function_parameters( [
"operation" => new \external_value(PARAM_TEXT, 'type of operation ["view"|"edit"]', VALUE_DEFAULT), "operation" => new \external_value(PARAM_TEXT, 'type of operation ["view"|"edit"]', VALUE_DEFAULT),
"refcontext_id" => new \external_value(PARAM_INT, 'id of reference context', VALUE_DEFAULT), "refcontext_id" => new \external_value(PARAM_INT, 'id of reference context', VALUE_DEFAULT),
@ -362,9 +352,9 @@ class courseservice extends \external_api {
} }
/** /**
* Parameter description for list_used_categories function * Parameter description for list_available_categories function
*/ */
public static function list_used_categories_returns() : \external_description { public static function list_available_categories_returns() : \external_description {
return new \external_multiple_structure(static::map_category_structure(true)); return new \external_multiple_structure(static::map_category_structure(true));
} }
@ -374,43 +364,66 @@ class courseservice extends \external_api {
* @param int $refctxid Reference context id * @param int $refctxid Reference context id
* @return array * @return array
*/ */
public static function list_used_categories($operation = 'edit', $refctxid = 0) { public static function list_available_categories($operation = 'edit', $refctxid = 0) {
global $DB; global $DB;
if ($operation == "edit") { if ($operation == "edit") {
$capability = self::CAP_EDIT; $capability = self::CAP_EDIT;
} else { // Operation == "view" || default. } else { // Operation == "view" || default.
$capability = self::CAP_VIEW; $capability = self::CAP_VIEW;
} }
$contextcounts = [];
// Get the context ids of all categories the user has access to view and wich have the given permission.
$contextids = []; $contextids = [];
$tops = self::user_tops(null,$capability);
foreach ($tops as $cat) {
$ctx = \context_coursecat::instance($cat->id);
$contextids[] = $ctx->id;
}
// Now get an overview of the number of study plans in a given context.
$contextcounts = [];
$insertctxs = [];
$rs = $DB->get_recordset_sql("SELECT DISTINCT context_id, COUNT(*) as num FROM {local_treestudyplan} $rs = $DB->get_recordset_sql("SELECT DISTINCT context_id, COUNT(*) as num FROM {local_treestudyplan}
GROUP BY context_id"); GROUP BY context_id");
foreach ($rs as $r) { foreach ($rs as $r) {
// Build the counts.
$contextcounts[$r->context_id] = $r->num; $contextcounts[$r->context_id] = $r->num;
$contextids[] = $r->context_id; // Add any of the categories containing studyplans to the list.
$ctx = \context::instance_by_id($r->context_id);
if (has_capability($capability,$ctx) && !in_array($r->context_id,$contextids)) {
$insertctxs[] = $ctx;
} }
// Add system context to list if needed.
if (!in_array(1,$contextids)) {
array_unshift($contextids,1);
} }
$rs->close(); $rs->close();
$cats = []; $cats = [];
// If the reference context id is not in the list, push it there
// If the reference context id is not in the list, push it there if the user has proper permissions in that context
if ($refctxid > 1 && !in_array($refctxid, $contextids)) { if ($refctxid > 1 && !in_array($refctxid, $contextids)) {
try { try {
// Get the context.
$refctx = \context::instance_by_id($refctxid); $refctx = \context::instance_by_id($refctxid);
$refpath = $refctx->get_parent_context_ids(true); // Double check permissions.
if (has_capability($capability,$refctx)) {
$insertctxs[] = $refctx;
}
} catch(\dml_missing_record_exception $x) {
// ignore context
}
}
foreach ($insertctxs as $ictx) {
// Place this context and all relevant direct parents in the correct spots.
$ipath = $ictx->get_parent_context_ids(true);
$found = false; $found = false;
foreach ($refpath as $i => $pid) { foreach ($ipath as $i => $pid) {
$idx = array_search($pid,$contextids); $idx = array_search($pid,$contextids);
if($idx !== false) { if($idx !== false) {
$contextids = array_merge( $contextids = array_merge(
array_slice($contextids, 0, $idx+1), array_slice($contextids, 0, $idx+1),
array_reverse(array_slice($refpath,0,$i)), array_reverse(array_slice($ipath,0,$i)),
array_slice($contextids, $idx+1, count($contextids) - 1) array_slice($contextids, $idx+1, count($contextids) - 1)
) ; ) ;
@ -419,25 +432,24 @@ class courseservice extends \external_api {
} }
} }
if(!$found) { if(!$found) {
array_unshift($contextids,$refctxid); array_unshift($contextids,$ictx->id);
}
} catch(\dml_missing_record_exception $x) {
// ignore context
} }
} }
// we only have to check the contexts having a study plan for access permissions
// Now translate this to the list of categories.
foreach ($contextids as $ctxid ) { foreach ($contextids as $ctxid ) {
try { try {
$ctx = \context::instance_by_id($ctxid); $ctx = \context::instance_by_id($ctxid);
if (has_capability($capability, $ctx)) {
if ($ctx->contextlevel == CONTEXT_SYSTEM) { if ($ctx->contextlevel == CONTEXT_SYSTEM) {
$cat = \core_course_category::top(); $cat = \core_course_category::top();
} else if ($ctx->contextlevel == CONTEXT_COURSECAT) { } else if ($ctx->contextlevel == CONTEXT_COURSECAT) {
$cat = \core_course_category::get($ctx->instanceid,\MUST_EXIST,true); $cat = \core_course_category::get($ctx->instanceid,\MUST_EXIST,false);
} }
$cats[] = $cat; $cats[] = $cat;
// In edit mode, also include direct children of the currently selected context.
if ($operation == "edit" && $ctxid == $refctxid) { if ($operation == "edit" && $ctxid == $refctxid) {
// Include direct children for navigation purposes // Include direct children for navigation purposes.
foreach ($cat->get_children() as $ccat) { foreach ($cat->get_children() as $ccat) {
$ccatctx = \context_coursecat::instance($ccat->id); $ccatctx = \context_coursecat::instance($ccat->id);
if (!in_array($ccatctx->id,$contextids)) { if (!in_array($ccatctx->id,$contextids)) {
@ -445,12 +457,12 @@ class courseservice extends \external_api {
} }
} }
} }
}
} catch (\dml_missing_record_exception $x) { } catch (\dml_missing_record_exception $x) {
// ignore context // ignore context
} }
} }
// And finally build the proper models, including studyplan count in the category context.
$list = []; $list = [];
foreach ($cats as $cat) { foreach ($cats as $cat) {
$count = 0; $count = 0;

View File

@ -484,9 +484,9 @@ $functions = [
'ajax' => true, 'ajax' => true,
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_list_used_categories' => [ // Web service function name. 'local_treestudyplan_list_available_categories' => [ // Web service function name.
'classname' => '\local_treestudyplan\courseservice', // Class containing the external function. 'classname' => '\local_treestudyplan\courseservice', // Class containing the external function.
'methodname' => 'list_used_categories', // External function name. 'methodname' => 'list_available_categories', // External function name.
'description' => 'Get categories hosting a studyplan', 'description' => 'Get categories hosting a studyplan',
'type' => 'read', // Database rights of the web service function (read, write). 'type' => 'read', // Database rights of the web service function (read, write).
'capabilities' => 'local/treestudyplan:editstudyplan', 'capabilities' => 'local/treestudyplan:editstudyplan',

View File

@ -22,7 +22,7 @@
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
$plugin->component = 'local_treestudyplan'; // Recommended since 2.0.2 (MDL-26035). Required since 3.0 (MDL-48494). $plugin->component = 'local_treestudyplan'; // Recommended since 2.0.2 (MDL-26035). Required since 3.0 (MDL-48494).
$plugin->version = 2024020204; // YYYYMMDDHH (year, month, day, iteration). $plugin->version = 2024020400; // YYYYMMDDHH (year, month, day, iteration).
$plugin->requires = 2021051700; // YYYYMMDDHH (This is the release version for Moodle 3.11). $plugin->requires = 2021051700; // YYYYMMDDHH (This is the release version for Moodle 3.11).
$plugin->release = "1.1.0"; $plugin->release = "1.1.0";