diff --git a/lib/amd/build/fetch.min.js b/lib/amd/build/fetch.min.js new file mode 100644 index 00000000000..e90b107327f --- /dev/null +++ b/lib/amd/build/fetch.min.js @@ -0,0 +1,10 @@ +define("core/fetch",["exports","core/config","./pending"],(function(_exports,_config,_pending){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * The core/fetch module allows you to make web service requests to the Moodle API. + * + * @module core/fetch + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.request=_exports.performPut=_exports.performPost=_exports.performHead=_exports.performGet=_exports.performDelete=void 0,_config=_interopRequireDefault(_config),_pending=_interopRequireDefault(_pending);const normaliseComponent=component=>component.replace(/^core_/,""),getRequest=(component,endpoint,_ref)=>{let{params:params={},body:body=null,method:method="GET"}=_ref;const url=new URL("".concat(_config.default.apibase,"rest/v2/").concat(component,"/").concat(endpoint)),options={method:method,headers:{Accept:"application/json","Content-Type":"application/json"}};return Object.entries(params).forEach((_ref2=>{let[key,value]=_ref2;url.searchParams.append(key,value)})),body&&(body instanceof FormData?options.body=body:options.body=body instanceof Object?JSON.stringify(body):body),new Request(url,options)},request=async function(component,action){let{params:params={},body:body=null,method:method="GET"}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const pending=new _pending.default("Requesting ".concat(component,"/").concat(action," with ").concat(method)),result=await fetch(getRequest(normaliseComponent(component),action,{params:params,method:method,body:body}));if(pending.resolve(),result.ok)return result.json();throw new Error(result.statusText)};_exports.request=request;_exports.performGet=function(component,action){let{params:params={}}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return request(component,action,{params:params,method:"GET"})};_exports.performHead=function(component,action){let{params:params={}}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return request(component,action,{params:params,method:"HEAD"})};_exports.performPost=function(component,action){let{body:body}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return request(component,action,{body:body,method:"POST"})};_exports.performPut=function(component,action){let{body:body}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return request(component,action,{body:body,method:"POST"})};_exports.performDelete=function(component,action){let{params:params={},body:body=null}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return request(component,action,{body:body,params:params,method:"DELETE"})}})); + +//# sourceMappingURL=fetch.min.js.map \ No newline at end of file diff --git a/lib/amd/build/fetch.min.js.map b/lib/amd/build/fetch.min.js.map new file mode 100644 index 00000000000..9c770a19164 --- /dev/null +++ b/lib/amd/build/fetch.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"fetch.min.js","sources":["../src/fetch.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * The core/fetch module allows you to make web service requests to the Moodle API.\n *\n * @module core/fetch\n * @copyright 2023 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Cfg from 'core/config';\nimport PendingPromise from './pending';\n\n/**\n * Normalise the component name to remove the core_ prefix.\n *\n * @param {string} component\n * @returns {string}\n */\nconst normaliseComponent = (component) => component.replace(/^core_/, '');\n\n/**\n * Get the Request object for a given API request.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} endpoint The endpoint within the componet to call\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @param {string|Object|FormData} [params.body = null] The HTTP method to use\n * @param {string} [params.method = \"GET\"] The HTTP method to use\n * @returns {Request}\n */\nconst getRequest = (\n component,\n endpoint,\n {\n params = {},\n body = null,\n method = 'GET',\n }\n) => {\n const url = new URL(`${Cfg.apibase}rest/v2/${component}/${endpoint}`);\n const options = {\n method,\n headers: {\n 'Accept': 'application/json',\n 'Content-Type': 'application/json',\n },\n };\n\n Object.entries(params).forEach(([key, value]) => {\n url.searchParams.append(key, value);\n });\n\n if (body) {\n if (body instanceof FormData) {\n options.body = body;\n } else if (body instanceof Object) {\n options.body = JSON.stringify(body);\n } else {\n options.body = body;\n }\n }\n\n return new Request(url, options);\n};\n\n/**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @param {string|Object|FormData} [params.body = null] The HTTP method to use\n * @param {string} [params.method = \"GET\"] The HTTP method to use\n * @returns {Promise}\n */\nconst request = async(\n component,\n action,\n {\n params = {},\n body = null,\n method = 'GET',\n } = {},\n) => {\n const pending = new PendingPromise(`Requesting ${component}/${action} with ${method}`);\n const result = await fetch(\n getRequest(\n normaliseComponent(component),\n action,\n {params, method, body},\n ),\n );\n\n pending.resolve();\n\n if (result.ok) {\n return result.json();\n }\n\n throw new Error(result.statusText);\n};\n\n/**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @returns {Promise}\n */\nconst performGet = (\n component,\n action,\n {\n params = {},\n } = {},\n) => request(\n component,\n action,\n {params, method: 'GET'},\n);\n\n/**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @returns {Promise}\n */\nconst performHead = (\n component,\n action,\n {\n params = {},\n } = {},\n) => request(\n component,\n action,\n {params, method: 'HEAD'},\n);\n\n/**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {string|Object|FormData} params.body The HTTP method to use\n * @returns {Promise}\n */\nconst performPost = (\n component,\n action,\n {\n body,\n } = {},\n) => request(\n component,\n action,\n {body, method: 'POST'},\n);\n\n/**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {string|Object|FormData} params.body The HTTP method to use\n * @returns {Promise}\n */\nconst performPut = (\n component,\n action,\n {\n body,\n } = {},\n) => request(\n component,\n action,\n {body, method: 'POST'},\n);\n\n/**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @param {string|Object|FormData} [params.body = null] The HTTP method to use\n * @returns {Promise}\n */\nconst performDelete = (\n component,\n action,\n {\n params = {},\n body = null,\n } = {},\n) => request(\n component,\n action,\n {\n body,\n params,\n method: 'DELETE',\n },\n);\n\nexport {\n request,\n performGet,\n performHead,\n performPost,\n performPut,\n performDelete,\n};\n"],"names":["normaliseComponent","component","replace","getRequest","endpoint","params","body","method","url","URL","Cfg","apibase","options","headers","Object","entries","forEach","_ref2","key","value","searchParams","append","FormData","JSON","stringify","Request","request","async","action","pending","PendingPromise","result","fetch","resolve","ok","json","Error","statusText"],"mappings":";;;;;;;sRAgCMA,mBAAsBC,WAAcA,UAAUC,QAAQ,SAAU,IAahEC,WAAa,CACfF,UACAG,qBACAC,OACIA,OAAS,GADbC,KAEIA,KAAO,KAFXC,OAGIA,OAAS,kBAGPC,IAAM,IAAIC,cAAOC,gBAAIC,2BAAkBV,sBAAaG,WACpDQ,QAAU,CACZL,OAAAA,OACAM,QAAS,QACK,kCACM,4BAIxBC,OAAOC,QAAQV,QAAQW,SAAQC,YAAEC,IAAKC,aAClCX,IAAIY,aAAaC,OAAOH,IAAKC,UAG7Bb,OACIA,gBAAgBgB,SAChBV,QAAQN,KAAOA,KAEfM,QAAQN,KADDA,gBAAgBQ,OACRS,KAAKC,UAAUlB,MAEfA,MAIhB,IAAImB,QAAQjB,IAAKI,UActBc,QAAUC,eACZ1B,UACA2B,YACAvB,OACIA,OAAS,GADbC,KAEIA,KAAO,KAFXC,OAGIA,OAAS,8DACT,SAEEsB,QAAU,IAAIC,sCAA6B7B,sBAAa2B,wBAAerB,SACvEwB,aAAeC,MACjB7B,WACIH,mBAAmBC,WACnB2B,OACA,CAACvB,OAAAA,OAAQE,OAAAA,OAAQD,KAAAA,WAIzBuB,QAAQI,UAEJF,OAAOG,UACAH,OAAOI,aAGZ,IAAIC,MAAML,OAAOM,0DAYR,SACfpC,UACA2B,YACAvB,OACIA,OAAS,2DACT,UACHqB,QACDzB,UACA2B,OACA,CAACvB,OAAAA,OAAQE,OAAQ,8BAYD,SAChBN,UACA2B,YACAvB,OACIA,OAAS,2DACT,UACHqB,QACDzB,UACA2B,OACA,CAACvB,OAAAA,OAAQE,OAAQ,+BAYD,SAChBN,UACA2B,YACAtB,KACIA,6DACA,UACHoB,QACDzB,UACA2B,OACA,CAACtB,KAAAA,KAAMC,OAAQ,8BAYA,SACfN,UACA2B,YACAtB,KACIA,6DACA,UACHoB,QACDzB,UACA2B,OACA,CAACtB,KAAAA,KAAMC,OAAQ,iCAaG,SAClBN,UACA2B,YACAvB,OACIA,OAAS,GADbC,KAEIA,KAAO,6DACP,UACHoB,QACDzB,UACA2B,OACA,CACItB,KAAAA,KACAD,OAAAA,OACAE,OAAQ"} \ No newline at end of file diff --git a/lib/amd/build/utils.min.js b/lib/amd/build/utils.min.js index df869fc7fea..4f5052e9b6f 100644 --- a/lib/amd/build/utils.min.js +++ b/lib/amd/build/utils.min.js @@ -1,3 +1,3 @@ -define("core/utils",["exports","core/pending"],(function(_exports,_pending){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.throttle=_exports.getNormalisedComponent=_exports.debounce=void 0,_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};_exports.throttle=(func,wait)=>{let onCooldown=!1,runAgain=null;const run=function(){for(var _len=arguments.length,args=new Array(_len),_key=0;_key<_len;_key++)args[_key]=arguments[_key];runAgain=null!==runAgain,onCooldown||(func.apply(this,args),onCooldown=!0,setTimeout((()=>{const recurse=runAgain;onCooldown=!1,runAgain=null,recurse&&run(args)}),wait))};return run};const debounceMap=new Map;_exports.debounce=function(func,wait){let{pending:pending=!1,cancel:cancel=!1}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},timeout=null;const returnedFunction=function(){for(var _len2=arguments.length,args=new Array(_len2),_key2=0;_key2<_len2;_key2++)args[_key2]=arguments[_key2];pending&&!debounceMap.has(returnedFunction)&&debounceMap.set(returnedFunction,new _pending.default("core/utils:debounce")),clearTimeout(timeout),timeout=setTimeout((async()=>{const pendingPromise=debounceMap.get(returnedFunction);debounceMap.delete(returnedFunction),await func.apply(undefined,args),null==pendingPromise||pendingPromise.resolve()}),wait)};return cancel&&(returnedFunction.cancel=()=>{const pendingPromise=debounceMap.get(returnedFunction);null==pendingPromise||pendingPromise.resolve(),clearTimeout(timeout)}),returnedFunction};_exports.getNormalisedComponent=component=>component&&"moodle"!==component&&"core"!==component?component:"core"})); +define("core/utils",["exports","core/pending","jquery"],(function(_exports,_pending,_jquery){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.wrapPromiseInWhenable=_exports.throttle=_exports.getNormalisedComponent=_exports.debounce=void 0,_pending=_interopRequireDefault(_pending),_jquery=_interopRequireDefault(_jquery);function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}_exports.throttle=(func,wait)=>{let onCooldown=!1,runAgain=null;const run=function(){for(var _len=arguments.length,args=new Array(_len),_key=0;_key<_len;_key++)args[_key]=arguments[_key];runAgain=null!==runAgain,onCooldown||(func.apply(this,args),onCooldown=!0,setTimeout((()=>{const recurse=runAgain;onCooldown=!1,runAgain=null,recurse&&run(args)}),wait))};return run};const debounceMap=new Map;_exports.debounce=function(func,wait){let{pending:pending=!1,cancel:cancel=!1}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},timeout=null;const returnedFunction=function(){for(var _len2=arguments.length,args=new Array(_len2),_key2=0;_key2<_len2;_key2++)args[_key2]=arguments[_key2];pending&&!debounceMap.has(returnedFunction)&&debounceMap.set(returnedFunction,new _pending.default("core/utils:debounce")),clearTimeout(timeout),timeout=setTimeout((async()=>{const pendingPromise=debounceMap.get(returnedFunction);debounceMap.delete(returnedFunction),await func.apply(undefined,args),null==pendingPromise||pendingPromise.resolve()}),wait)};return cancel&&(returnedFunction.cancel=()=>{const pendingPromise=debounceMap.get(returnedFunction);null==pendingPromise||pendingPromise.resolve(),clearTimeout(timeout)}),returnedFunction};_exports.getNormalisedComponent=component=>component&&"moodle"!==component&&"core"!==component?component:"core";_exports.wrapPromiseInWhenable=promise=>_jquery.default.when(promise)})); //# sourceMappingURL=utils.min.js.map \ No newline at end of file diff --git a/lib/amd/build/utils.min.js.map b/lib/amd/build/utils.min.js.map index 60a9850f2ef..f36fce1ae49 100644 --- a/lib/amd/build/utils.min.js.map +++ b/lib/amd/build/utils.min.js.map @@ -1 +1 @@ -{"version":3,"file":"utils.min.js","sources":["../src/utils.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Utility functions.\n *\n * @module core/utils\n * @copyright 2019 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Pending from 'core/pending';\n\n /**\n * Create a wrapper function to throttle the execution of the given\n *\n * function to at most once every specified period.\n *\n * If the function is attempted to be executed while it's in cooldown\n * (during the wait period) then it'll immediately execute again as\n * soon as the cooldown is over.\n *\n * @method\n * @param {Function} func The function to throttle\n * @param {Number} wait The number of milliseconds to wait between executions\n * @return {Function}\n */\nexport const throttle = (func, wait) => {\n let onCooldown = false;\n let runAgain = null;\n const run = function(...args) {\n if (runAgain === null) {\n // This is the first time the function has been called.\n runAgain = false;\n } else {\n // This function has been called a second time during the wait period\n // so re-run it once the wait period is over.\n runAgain = true;\n }\n\n if (onCooldown) {\n // Function has already run for this wait period.\n return;\n }\n\n func.apply(this, args);\n onCooldown = true;\n\n setTimeout(() => {\n const recurse = runAgain;\n onCooldown = false;\n runAgain = null;\n\n if (recurse) {\n run(args);\n }\n }, wait);\n };\n\n return run;\n};\n\n/**\n * @property {Map} debounceMap A map of functions to their debounced pending promises.\n */\nconst debounceMap = new Map();\n\n/**\n * Create a wrapper function to debounce the execution of the given\n * function. Each attempt to execute the function will reset the cooldown\n * period.\n *\n * @method\n * @param {Function} func The function to debounce\n * @param {Number} wait The number of milliseconds to wait after the final attempt to execute\n * @param {Object} [options]\n * @param {boolean} [options.pending=false] Whether to wrap the debounced method in a pending promise\n * @param {boolean} [options.cancel=false] Whether to add a cancel method to the debounced function\n * @return {Function}\n */\nexport const debounce = (\n func,\n wait,\n {\n pending = false,\n cancel = false,\n } = {},\n) => {\n let timeout = null;\n\n const returnedFunction = (...args) => {\n if (pending && !debounceMap.has(returnedFunction)) {\n debounceMap.set(returnedFunction, new Pending('core/utils:debounce'));\n }\n clearTimeout(timeout);\n timeout = setTimeout(async () => {\n // Get the current pending promise and immediately empty it.\n // This is important to allow the function to be debounced again as soon as possible.\n // We do not resolve it until later - but that's fine because the promise is appropriately scoped.\n const pendingPromise = debounceMap.get(returnedFunction);\n debounceMap.delete(returnedFunction);\n\n // Allow the debounced function to return a Promise.\n // This ensures that Behat will not continue until the function has finished executing.\n await func.apply(this, args);\n\n // Resolve the pending promise if it exists.\n pendingPromise?.resolve();\n }, wait);\n };\n\n if (cancel) {\n returnedFunction.cancel = () => {\n const pendingPromise = debounceMap.get(returnedFunction);\n pendingPromise?.resolve();\n clearTimeout(timeout);\n };\n }\n\n return returnedFunction;\n};\n\n/**\n * Normalise the provided component such that '', 'moodle', and 'core' are treated consistently.\n *\n * @param {String} component\n * @returns {String}\n */\nexport const getNormalisedComponent = (component) => {\n if (component) {\n if (component !== 'moodle' && component !== 'core') {\n return component;\n }\n }\n\n return 'core';\n};\n"],"names":["func","wait","onCooldown","runAgain","run","args","apply","this","setTimeout","recurse","debounceMap","Map","pending","cancel","timeout","returnedFunction","has","set","Pending","clearTimeout","async","pendingPromise","get","delete","resolve","component"],"mappings":"mSAuCwB,CAACA,KAAMC,YACvBC,YAAa,EACbC,SAAW,WACTC,IAAM,yCAAYC,6CAAAA,2BAGhBF,SAFa,OAAbA,SASAD,aAKJF,KAAKM,MAAMC,KAAMF,MACjBH,YAAa,EAEbM,YAAW,WACDC,QAAUN,SAChBD,YAAa,EACbC,SAAW,KAEPM,SACAL,IAAIC,QAETJ,eAGAG,WAMLM,YAAc,IAAIC,sBAeA,SACpBX,KACAC,UACAW,QACIA,SAAU,EADdC,OAEIA,QAAS,0DACT,GAEAC,QAAU,WAERC,iBAAmB,0CAAIV,kDAAAA,6BACrBO,UAAYF,YAAYM,IAAID,mBAC5BL,YAAYO,IAAIF,iBAAkB,IAAIG,iBAAQ,wBAElDC,aAAaL,SACbA,QAAUN,YAAWY,gBAIXC,eAAiBX,YAAYY,IAAIP,kBACvCL,YAAYa,OAAOR,wBAIbf,KAAKM,gBAAYD,MAGvBgB,MAAAA,gBAAAA,eAAgBG,YACjBvB,cAGHY,SACAE,iBAAiBF,OAAS,WAChBQ,eAAiBX,YAAYY,IAAIP,kBACvCM,MAAAA,gBAAAA,eAAgBG,UAChBL,aAAaL,WAIdC,kDAS4BU,WAC/BA,WACkB,WAAdA,WAAwC,SAAdA,UACnBA,UAIR"} \ No newline at end of file +{"version":3,"file":"utils.min.js","sources":["../src/utils.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Utility functions.\n *\n * @module core/utils\n * @copyright 2019 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Pending from 'core/pending';\nimport jQuery from 'jquery';\n\n /**\n * Create a wrapper function to throttle the execution of the given\n *\n * function to at most once every specified period.\n *\n * If the function is attempted to be executed while it's in cooldown\n * (during the wait period) then it'll immediately execute again as\n * soon as the cooldown is over.\n *\n * @method\n * @param {Function} func The function to throttle\n * @param {Number} wait The number of milliseconds to wait between executions\n * @return {Function}\n */\nexport const throttle = (func, wait) => {\n let onCooldown = false;\n let runAgain = null;\n const run = function(...args) {\n if (runAgain === null) {\n // This is the first time the function has been called.\n runAgain = false;\n } else {\n // This function has been called a second time during the wait period\n // so re-run it once the wait period is over.\n runAgain = true;\n }\n\n if (onCooldown) {\n // Function has already run for this wait period.\n return;\n }\n\n func.apply(this, args);\n onCooldown = true;\n\n setTimeout(() => {\n const recurse = runAgain;\n onCooldown = false;\n runAgain = null;\n\n if (recurse) {\n run(args);\n }\n }, wait);\n };\n\n return run;\n};\n\n/**\n * @property {Map} debounceMap A map of functions to their debounced pending promises.\n */\nconst debounceMap = new Map();\n\n/**\n * Create a wrapper function to debounce the execution of the given\n * function. Each attempt to execute the function will reset the cooldown\n * period.\n *\n * @method\n * @param {Function} func The function to debounce\n * @param {Number} wait The number of milliseconds to wait after the final attempt to execute\n * @param {Object} [options]\n * @param {boolean} [options.pending=false] Whether to wrap the debounced method in a pending promise\n * @param {boolean} [options.cancel=false] Whether to add a cancel method to the debounced function\n * @return {Function}\n */\nexport const debounce = (\n func,\n wait,\n {\n pending = false,\n cancel = false,\n } = {},\n) => {\n let timeout = null;\n\n const returnedFunction = (...args) => {\n if (pending && !debounceMap.has(returnedFunction)) {\n debounceMap.set(returnedFunction, new Pending('core/utils:debounce'));\n }\n clearTimeout(timeout);\n timeout = setTimeout(async () => {\n // Get the current pending promise and immediately empty it.\n // This is important to allow the function to be debounced again as soon as possible.\n // We do not resolve it until later - but that's fine because the promise is appropriately scoped.\n const pendingPromise = debounceMap.get(returnedFunction);\n debounceMap.delete(returnedFunction);\n\n // Allow the debounced function to return a Promise.\n // This ensures that Behat will not continue until the function has finished executing.\n await func.apply(this, args);\n\n // Resolve the pending promise if it exists.\n pendingPromise?.resolve();\n }, wait);\n };\n\n if (cancel) {\n returnedFunction.cancel = () => {\n const pendingPromise = debounceMap.get(returnedFunction);\n pendingPromise?.resolve();\n clearTimeout(timeout);\n };\n }\n\n return returnedFunction;\n};\n\n/**\n * Normalise the provided component such that '', 'moodle', and 'core' are treated consistently.\n *\n * @param {String} component\n * @returns {String}\n */\nexport const getNormalisedComponent = (component) => {\n if (component) {\n if (component !== 'moodle' && component !== 'core') {\n return component;\n }\n }\n\n return 'core';\n};\n\n/**\n * Wrap a Native Promise in a jQuery Whenable for b/c.\n *\n * @param {*} promise\n * @returns {jQuery}\n */\nexport const wrapPromiseInWhenable = (promise) => jQuery.when(promise);\n"],"names":["func","wait","onCooldown","runAgain","run","args","apply","this","setTimeout","recurse","debounceMap","Map","pending","cancel","timeout","returnedFunction","has","set","Pending","clearTimeout","async","pendingPromise","get","delete","resolve","component","promise","jQuery","when"],"mappings":"qbAwCwB,CAACA,KAAMC,YACvBC,YAAa,EACbC,SAAW,WACTC,IAAM,yCAAYC,6CAAAA,2BAGhBF,SAFa,OAAbA,SASAD,aAKJF,KAAKM,MAAMC,KAAMF,MACjBH,YAAa,EAEbM,YAAW,WACDC,QAAUN,SAChBD,YAAa,EACbC,SAAW,KAEPM,SACAL,IAAIC,QAETJ,eAGAG,WAMLM,YAAc,IAAIC,sBAeA,SACpBX,KACAC,UACAW,QACIA,SAAU,EADdC,OAEIA,QAAS,0DACT,GAEAC,QAAU,WAERC,iBAAmB,0CAAIV,kDAAAA,6BACrBO,UAAYF,YAAYM,IAAID,mBAC5BL,YAAYO,IAAIF,iBAAkB,IAAIG,iBAAQ,wBAElDC,aAAaL,SACbA,QAAUN,YAAWY,gBAIXC,eAAiBX,YAAYY,IAAIP,kBACvCL,YAAYa,OAAOR,wBAIbf,KAAKM,gBAAYD,MAGvBgB,MAAAA,gBAAAA,eAAgBG,YACjBvB,cAGHY,SACAE,iBAAiBF,OAAS,WAChBQ,eAAiBX,YAAYY,IAAIP,kBACvCM,MAAAA,gBAAAA,eAAgBG,UAChBL,aAAaL,WAIdC,kDAS4BU,WAC/BA,WACkB,WAAdA,WAAwC,SAAdA,UACnBA,UAIR,sCAS2BC,SAAYC,gBAAOC,KAAKF"} \ No newline at end of file diff --git a/lib/amd/src/fetch.js b/lib/amd/src/fetch.js new file mode 100644 index 00000000000..f73d0b8d372 --- /dev/null +++ b/lib/amd/src/fetch.js @@ -0,0 +1,237 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * The core/fetch module allows you to make web service requests to the Moodle API. + * + * @module core/fetch + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Cfg from 'core/config'; +import PendingPromise from './pending'; + +/** + * Normalise the component name to remove the core_ prefix. + * + * @param {string} component + * @returns {string} + */ +const normaliseComponent = (component) => component.replace(/^core_/, ''); + +/** + * Get the Request object for a given API request. + * + * @param {string} component The frankenstyle component name + * @param {string} endpoint The endpoint within the componet to call + * @param {object} params + * @param {object} [params.params = {}] The parameters to pass to the API + * @param {string|Object|FormData} [params.body = null] The HTTP method to use + * @param {string} [params.method = "GET"] The HTTP method to use + * @returns {Request} + */ +const getRequest = ( + component, + endpoint, + { + params = {}, + body = null, + method = 'GET', + } +) => { + const url = new URL(`${Cfg.apibase}rest/v2/${component}/${endpoint}`); + const options = { + method, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + }; + + Object.entries(params).forEach(([key, value]) => { + url.searchParams.append(key, value); + }); + + if (body) { + if (body instanceof FormData) { + options.body = body; + } else if (body instanceof Object) { + options.body = JSON.stringify(body); + } else { + options.body = body; + } + } + + return new Request(url, options); +}; + +/** + * Make a request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {object} [params.params = {}] The parameters to pass to the API + * @param {string|Object|FormData} [params.body = null] The HTTP method to use + * @param {string} [params.method = "GET"] The HTTP method to use + * @returns {Promise} + */ +const request = async( + component, + action, + { + params = {}, + body = null, + method = 'GET', + } = {}, +) => { + const pending = new PendingPromise(`Requesting ${component}/${action} with ${method}`); + const result = await fetch( + getRequest( + normaliseComponent(component), + action, + {params, method, body}, + ), + ); + + pending.resolve(); + + if (result.ok) { + return result.json(); + } + + throw new Error(result.statusText); +}; + +/** + * Make a request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {object} [params.params = {}] The parameters to pass to the API + * @returns {Promise} + */ +const performGet = ( + component, + action, + { + params = {}, + } = {}, +) => request( + component, + action, + {params, method: 'GET'}, +); + +/** + * Make a request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {object} [params.params = {}] The parameters to pass to the API + * @returns {Promise} + */ +const performHead = ( + component, + action, + { + params = {}, + } = {}, +) => request( + component, + action, + {params, method: 'HEAD'}, +); + +/** + * Make a request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {string|Object|FormData} params.body The HTTP method to use + * @returns {Promise} + */ +const performPost = ( + component, + action, + { + body, + } = {}, +) => request( + component, + action, + {body, method: 'POST'}, +); + +/** + * Make a request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {string|Object|FormData} params.body The HTTP method to use + * @returns {Promise} + */ +const performPut = ( + component, + action, + { + body, + } = {}, +) => request( + component, + action, + {body, method: 'POST'}, +); + +/** + * Make a request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {object} [params.params = {}] The parameters to pass to the API + * @param {string|Object|FormData} [params.body = null] The HTTP method to use + * @returns {Promise} + */ +const performDelete = ( + component, + action, + { + params = {}, + body = null, + } = {}, +) => request( + component, + action, + { + body, + params, + method: 'DELETE', + }, +); + +export { + request, + performGet, + performHead, + performPost, + performPut, + performDelete, +}; diff --git a/lib/amd/src/utils.js b/lib/amd/src/utils.js index 0a59eab25d8..6a7bef0f65a 100644 --- a/lib/amd/src/utils.js +++ b/lib/amd/src/utils.js @@ -22,6 +22,7 @@ */ import Pending from 'core/pending'; +import jQuery from 'jquery'; /** * Create a wrapper function to throttle the execution of the given @@ -147,3 +148,11 @@ export const getNormalisedComponent = (component) => { return 'core'; }; + +/** + * Wrap a Native Promise in a jQuery Whenable for b/c. + * + * @param {*} promise + * @returns {jQuery} + */ +export const wrapPromiseInWhenable = (promise) => jQuery.when(promise); diff --git a/lib/classes/output/requirements/page_requirements_manager.php b/lib/classes/output/requirements/page_requirements_manager.php index 039d68a0ef2..d58e75995c8 100644 --- a/lib/classes/output/requirements/page_requirements_manager.php +++ b/lib/classes/output/requirements/page_requirements_manager.php @@ -298,7 +298,7 @@ class page_requirements_manager { * @return array List of safe config values that are available to javascript. */ public function get_config_for_javascript(moodle_page $page, renderer_base $renderer) { - global $CFG; + global $CFG, $USER; if (empty($this->M_cfg)) { $iconsystem = \core\output\icon_system::instance(); @@ -336,6 +336,7 @@ class page_requirements_manager { 'langrev' => get_string_manager()->get_revision(), 'templaterev' => $this->get_templaterev(), 'siteId' => (int) SITEID, + 'userId' => (int) $USER->id, ]; if ($CFG->debugdeveloper) { $this->M_cfg['developerdebug'] = true; diff --git a/user/amd/build/repository.min.js b/user/amd/build/repository.min.js index 19b649ff48b..edbf4e9a78e 100644 --- a/user/amd/build/repository.min.js +++ b/user/amd/build/repository.min.js @@ -1,3 +1,10 @@ -define("core_user/repository",["exports","core/ajax"],(function(_exports,_ajax){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.unenrolUser=_exports.submitUserEnrolmentForm=_exports.setUserPreferences=_exports.setUserPreference=_exports.sendMessagesToUsers=_exports.getUserPreferences=_exports.getUserPreference=_exports.createNotesForUsers=void 0;_exports.getUserPreference=function(name){let userid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return getUserPreferences(name,userid).then((response=>response.preferences[0].value))};const getUserPreferences=function(){let name=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,userid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return(0,_ajax.call)([{methodname:"core_user_get_user_preferences",args:{name:name,userid:userid}}])[0]};_exports.getUserPreferences=getUserPreferences;_exports.setUserPreference=function(name){let value=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,userid=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0;return setUserPreferences([{name:name,value:value,userid:userid}])};const setUserPreferences=preferences=>(0,_ajax.call)([{methodname:"core_user_set_user_preferences",args:{preferences:preferences}}])[0];_exports.setUserPreferences=setUserPreferences;_exports.unenrolUser=userEnrolmentId=>(0,_ajax.call)([{methodname:"core_enrol_unenrol_user_enrolment",args:{ueid:userEnrolmentId}}])[0];_exports.submitUserEnrolmentForm=formdata=>(0,_ajax.call)([{methodname:"core_enrol_submit_user_enrolment_form",args:{formdata:formdata}}])[0];_exports.createNotesForUsers=notes=>(0,_ajax.call)([{methodname:"core_notes_create_notes",args:{notes:notes}}])[0];_exports.sendMessagesToUsers=messages=>(0,_ajax.call)([{methodname:"core_message_send_instant_messages",args:{messages:messages}}])[0]})); +define("core_user/repository",["exports","core/config","core/ajax","core/fetch"],(function(_exports,_config,_ajax,_fetch){var obj; +/** + * Module to handle AJAX interactions. + * + * @module core_user/repository + * @copyright 2020 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.unenrolUser=_exports.submitUserEnrolmentForm=_exports.setUserPreferences=_exports.setUserPreference=_exports.sendMessagesToUsers=_exports.getUserPreferences=_exports.getUserPreference=_exports.createNotesForUsers=void 0,_config=(obj=_config)&&obj.__esModule?obj:{default:obj};const checkUserId=userid=>{if(0!==Number(userid)&&Number(userid)!==_config.default.userId)throw new Error("Invalid user ID: ".concat(userid,". It is only possible to manage preferences for the current user."))},addLegacySavedProperty=(response,preferences)=>{const debugLogger={get:(target,prop,receiver)=>"then"===prop?null:"saved"===prop?(window.console.warn("The saved property is deprecated. Please use the response object directly."),preferences.filter((preference=>target.hasOwnProperty(preference.name))).map((preference=>({name:preference.name,userid:_config.default.userid})))):Reflect.get(target,prop,receiver)};return Promise.resolve(new Proxy(response,debugLogger))};_exports.getUserPreference=function(name){let userid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return getUserPreferences(name,userid).then((response=>response[name]))};const getUserPreferences=function(){let name=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,userid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;checkUserId(userid);const endpoint=["current","preferences"];return name&&endpoint.push(name),(0,_fetch.performGet)("core_user",endpoint.join("/"))};_exports.getUserPreferences=getUserPreferences;_exports.setUserPreference=function(name){let value=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,userid=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0;return checkUserId(userid),(0,_fetch.performPost)("core_user","current/preferences/".concat(name),{body:{value:value}}).then((response=>addLegacySavedProperty(response,[{name:name}])))};_exports.setUserPreferences=preferences=>(preferences.forEach((preference=>checkUserId(preference.userid))),(0,_fetch.performPost)("core_user","current/preferences",{body:{preferences:Object.fromEntries(preferences.map((preference=>[preference.name,preference.value])))}}).then((response=>addLegacySavedProperty(response,preferences))));_exports.unenrolUser=userEnrolmentId=>(0,_ajax.call)([{methodname:"core_enrol_unenrol_user_enrolment",args:{ueid:userEnrolmentId}}])[0];_exports.submitUserEnrolmentForm=formdata=>(0,_ajax.call)([{methodname:"core_enrol_submit_user_enrolment_form",args:{formdata:formdata}}])[0];_exports.createNotesForUsers=notes=>(0,_ajax.call)([{methodname:"core_notes_create_notes",args:{notes:notes}}])[0];_exports.sendMessagesToUsers=messages=>(0,_ajax.call)([{methodname:"core_message_send_instant_messages",args:{messages:messages}}])[0]})); //# sourceMappingURL=repository.min.js.map \ No newline at end of file diff --git a/user/amd/build/repository.min.js.map b/user/amd/build/repository.min.js.map index b59633bfe53..731768594ea 100644 --- a/user/amd/build/repository.min.js.map +++ b/user/amd/build/repository.min.js.map @@ -1 +1 @@ -{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module to handle AJAX interactions.\n *\n * @module core_user/repository\n * @copyright 2020 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\n\n/**\n * Get single user preference\n *\n * @param {String} name Name of the preference\n * @param {Number} userid User ID (defaults to current user)\n * @return {Promise}\n */\nexport const getUserPreference = (name, userid = 0) => {\n return getUserPreferences(name, userid)\n .then(response => response.preferences[0].value);\n};\n\n/**\n * Get multiple user preferences\n *\n * @param {String|null} name Name of the preference (omit if you want to retrieve all)\n * @param {Number} userid User ID (defaults to current user)\n * @return {Promise}\n */\nexport const getUserPreferences = (name = null, userid = 0) => {\n return fetchMany([{\n methodname: 'core_user_get_user_preferences',\n args: {name, userid}\n }])[0];\n};\n\n/**\n * Set single user preference\n *\n * @param {String} name Name of the preference\n * @param {String|null} value Value of the preference (omit if you want to remove the current value)\n * @param {Number} userid User ID (defaults to current user)\n * @return {Promise}\n */\nexport const setUserPreference = (name, value = null, userid = 0) => {\n return setUserPreferences([{name, value, userid}]);\n};\n\n/**\n * Set multiple user preferences\n *\n * @param {Object[]} preferences Array of preferences containing name/value/userid attributes\n * @return {Promise}\n */\nexport const setUserPreferences = (preferences) => {\n return fetchMany([{\n methodname: 'core_user_set_user_preferences',\n args: {preferences}\n }])[0];\n};\n\n/**\n * Unenrol the user with the specified user enrolmentid ID.\n *\n * @param {Number} userEnrolmentId\n * @return {Promise}\n */\nexport const unenrolUser = userEnrolmentId => {\n return fetchMany([{\n methodname: 'core_enrol_unenrol_user_enrolment',\n args: {\n ueid: userEnrolmentId,\n },\n }])[0];\n};\n\n/**\n * Submit the user enrolment form with the specified form data.\n *\n * @param {String} formdata\n * @return {Promise}\n */\nexport const submitUserEnrolmentForm = formdata => {\n return fetchMany([{\n methodname: 'core_enrol_submit_user_enrolment_form',\n args: {\n formdata,\n },\n }])[0];\n};\n\nexport const createNotesForUsers = notes => {\n return fetchMany([{\n methodname: 'core_notes_create_notes',\n args: {\n notes\n }\n }])[0];\n};\n\nexport const sendMessagesToUsers = messages => {\n return fetchMany([{\n methodname: 'core_message_send_instant_messages',\n args: {messages}\n }])[0];\n};\n"],"names":["name","userid","getUserPreferences","then","response","preferences","value","methodname","args","setUserPreferences","userEnrolmentId","ueid","formdata","notes","messages"],"mappings":"wYAgCiC,SAACA,UAAMC,8DAAS,SACtCC,mBAAmBF,KAAMC,QAC3BE,MAAKC,UAAYA,SAASC,YAAY,GAAGC,eAUrCJ,mBAAqB,eAACF,4DAAO,KAAMC,8DAAS,SAC9C,cAAU,CAAC,CACdM,WAAY,iCACZC,KAAM,CAACR,KAAAA,KAAMC,OAAAA,WACb,8EAWyB,SAACD,UAAMM,6DAAQ,KAAML,8DAAS,SACpDQ,mBAAmB,CAAC,CAACT,KAAAA,KAAMM,MAAAA,MAAOL,OAAAA,iBAShCQ,mBAAsBJ,cACxB,cAAU,CAAC,CACdE,WAAY,iCACZC,KAAM,CAACH,YAAAA,gBACP,uEASmBK,kBAChB,cAAU,CAAC,CACdH,WAAY,oCACZC,KAAM,CACFG,KAAMD,oBAEV,oCAS+BE,WAC5B,cAAU,CAAC,CACdL,WAAY,wCACZC,KAAM,CACFI,SAAAA,aAEJ,gCAG2BC,QACxB,cAAU,CAAC,CACdN,WAAY,0BACZC,KAAM,CACFK,MAAAA,UAEJ,gCAG2BC,WACxB,cAAU,CAAC,CACdP,WAAY,qCACZC,KAAM,CAACM,SAAAA,aACP"} \ No newline at end of file +{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module to handle AJAX interactions.\n *\n * @module core_user/repository\n * @copyright 2020 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Config from 'core/config';\nimport {call as fetchMany} from 'core/ajax';\nimport {performGet, performPost} from 'core/fetch';\n\nconst checkUserId = (userid) => {\n if (Number(userid) === 0) {\n return;\n }\n if (Number(userid) === Config.userId) {\n return;\n }\n throw new Error(\n `Invalid user ID: ${userid}. It is only possible to manage preferences for the current user.`,\n );\n};\n\n/**\n * Turn the response object into a Proxy object that will log a warning if the saved property is accessed.\n *\n * @param {Object} response\n * @param {Object} preferences The preferences that might be in the response\n * @return {Promise}\n */\nconst addLegacySavedProperty = (response, preferences) => {\n const debugLogger = {\n get(target, prop, receiver) {\n if (prop === 'then') {\n // To proxy a Promise we have to return null when the then key is requested.\n return null;\n }\n if (prop === 'saved') {\n window.console.warn(\n 'The saved property is deprecated. Please use the response object directly.',\n );\n\n return preferences\n .filter((preference) => target.hasOwnProperty(preference.name))\n .map((preference) => ({\n name: preference.name,\n userid: Config.userid,\n }));\n }\n return Reflect.get(target, prop, receiver);\n },\n };\n\n return Promise.resolve(new Proxy(response, debugLogger));\n};\n\n/**\n * Get single user preference\n *\n * @param {String} name Name of the preference\n * @param {Number} userid User ID (defaults to current user)\n * @return {Promise}\n */\nexport const getUserPreference = (name, userid = 0) => getUserPreferences(name, userid)\n .then((response) => response[name]);\n\n/**\n * Get multiple user preferences\n *\n * @param {String|null} name Name of the preference (omit if you want to retrieve all)\n * @param {Number} userid User ID (defaults to current user)\n * @return {Promise>}\n */\nexport const getUserPreferences = (name = null, userid = 0) => {\n checkUserId(userid);\n const endpoint = ['current', 'preferences'];\n\n if (name) {\n endpoint.push(name);\n }\n\n return performGet('core_user', endpoint.join('/'));\n};\n\n/**\n * Set single user preference\n *\n * @param {String} name Name of the preference\n * @param {String|null} value Value of the preference (omit if you want to remove the current value)\n * @param {Number} userid User ID (defaults to current user)\n * @return {Promise}\n */\nexport const setUserPreference = (name, value = null, userid = 0) => {\n checkUserId(userid);\n return performPost(\n 'core_user',\n `current/preferences/${name}`,\n {\n body: {value},\n },\n )\n // Return the result of the fetch call, and also add in the legacy saved property.\n .then((response) => addLegacySavedProperty(response, [{name}]));\n};\n\n/**\n * Set multiple user preferences\n *\n * @param {Object[]} preferences Array of preferences containing name/value/userid attributes\n * @return {Promise}\n */\nexport const setUserPreferences = (preferences) => {\n preferences.forEach((preference) => checkUserId(preference.userid));\n return performPost(\n 'core_user',\n 'current/preferences',\n {\n body: {\n preferences: Object.fromEntries (preferences.map((preference) => ([preference.name, preference.value]))),\n },\n },\n )\n // Return the result of the fetch call, and also add in the legacy saved property.\n .then((response) => addLegacySavedProperty(response, preferences));\n};\n\n/**\n * Unenrol the user with the specified user enrolmentid ID.\n *\n * @param {Number} userEnrolmentId\n * @return {Promise}\n */\nexport const unenrolUser = userEnrolmentId => {\n return fetchMany([{\n methodname: 'core_enrol_unenrol_user_enrolment',\n args: {\n ueid: userEnrolmentId,\n },\n }])[0];\n};\n\n/**\n * Submit the user enrolment form with the specified form data.\n *\n * @param {String} formdata\n * @return {Promise}\n */\nexport const submitUserEnrolmentForm = formdata => {\n return fetchMany([{\n methodname: 'core_enrol_submit_user_enrolment_form',\n args: {\n formdata,\n },\n }])[0];\n};\n\nexport const createNotesForUsers = notes => {\n return fetchMany([{\n methodname: 'core_notes_create_notes',\n args: {\n notes\n }\n }])[0];\n};\n\nexport const sendMessagesToUsers = messages => {\n return fetchMany([{\n methodname: 'core_message_send_instant_messages',\n args: {messages}\n }])[0];\n};\n"],"names":["checkUserId","userid","Number","Config","userId","Error","addLegacySavedProperty","response","preferences","debugLogger","get","target","prop","receiver","window","console","warn","filter","preference","hasOwnProperty","name","map","Reflect","Promise","resolve","Proxy","getUserPreferences","then","endpoint","push","join","value","body","forEach","Object","fromEntries","userEnrolmentId","methodname","args","ueid","formdata","notes","messages"],"mappings":";;;;;;;gWA2BMA,YAAeC,YACM,IAAnBC,OAAOD,SAGPC,OAAOD,UAAYE,gBAAOC,aAGxB,IAAIC,iCACcJ,8EAWtBK,uBAAyB,CAACC,SAAUC,qBAChCC,YAAc,CAChBC,IAAG,CAACC,OAAQC,KAAMC,WACD,SAATD,KAEO,KAEE,UAATA,MACAE,OAAOC,QAAQC,KACX,8EAGGR,YACFS,QAAQC,YAAeP,OAAOQ,eAAeD,WAAWE,QACxDC,KAAKH,cACFE,KAAMF,WAAWE,KACjBnB,OAAQE,gBAAOF,YAGpBqB,QAAQZ,IAAIC,OAAQC,KAAMC,kBAIlCU,QAAQC,QAAQ,IAAIC,MAAMlB,SAAUE,0CAUd,SAACW,UAAMnB,8DAAS,SAAMyB,mBAAmBN,KAAMnB,QAC3E0B,MAAMpB,UAAaA,SAASa,eASpBM,mBAAqB,eAACN,4DAAO,KAAMnB,8DAAS,EACrDD,YAAYC,cACN2B,SAAW,CAAC,UAAW,sBAEzBR,MACAQ,SAASC,KAAKT,OAGX,qBAAW,YAAaQ,SAASE,KAAK,iFAWhB,SAACV,UAAMW,6DAAQ,KAAM9B,8DAAS,SAC3DD,YAAYC,SACL,sBACH,0CACuBmB,MACvB,CACIY,KAAM,CAACD,MAAAA,SAIdJ,MAAMpB,UAAaD,uBAAuBC,SAAU,CAAC,CAACa,KAAAA,uCASxBZ,cAC/BA,YAAYyB,SAASf,YAAelB,YAAYkB,WAAWjB,WACpD,sBACH,YACA,sBACA,CACI+B,KAAM,CACFxB,YAAa0B,OAAOC,YAAa3B,YAAYa,KAAKH,YAAgB,CAACA,WAAWE,KAAMF,WAAWa,aAK1GJ,MAAMpB,UAAaD,uBAAuBC,SAAUC,qCAS9B4B,kBAChB,cAAU,CAAC,CACdC,WAAY,oCACZC,KAAM,CACFC,KAAMH,oBAEV,oCAS+BI,WAC5B,cAAU,CAAC,CACdH,WAAY,wCACZC,KAAM,CACFE,SAAAA,aAEJ,gCAG2BC,QACxB,cAAU,CAAC,CACdJ,WAAY,0BACZC,KAAM,CACFG,MAAAA,UAEJ,gCAG2BC,WACxB,cAAU,CAAC,CACdL,WAAY,qCACZC,KAAM,CAACI,SAAAA,aACP"} \ No newline at end of file diff --git a/user/amd/src/repository.js b/user/amd/src/repository.js index f6c078539a1..1b8b592e72d 100644 --- a/user/amd/src/repository.js +++ b/user/amd/src/repository.js @@ -21,7 +21,54 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +import Config from 'core/config'; import {call as fetchMany} from 'core/ajax'; +import {performGet, performPost} from 'core/fetch'; + +const checkUserId = (userid) => { + if (Number(userid) === 0) { + return; + } + if (Number(userid) === Config.userId) { + return; + } + throw new Error( + `Invalid user ID: ${userid}. It is only possible to manage preferences for the current user.`, + ); +}; + +/** + * Turn the response object into a Proxy object that will log a warning if the saved property is accessed. + * + * @param {Object} response + * @param {Object} preferences The preferences that might be in the response + * @return {Promise} + */ +const addLegacySavedProperty = (response, preferences) => { + const debugLogger = { + get(target, prop, receiver) { + if (prop === 'then') { + // To proxy a Promise we have to return null when the then key is requested. + return null; + } + if (prop === 'saved') { + window.console.warn( + 'The saved property is deprecated. Please use the response object directly.', + ); + + return preferences + .filter((preference) => target.hasOwnProperty(preference.name)) + .map((preference) => ({ + name: preference.name, + userid: Config.userid, + })); + } + return Reflect.get(target, prop, receiver); + }, + }; + + return Promise.resolve(new Proxy(response, debugLogger)); +}; /** * Get single user preference @@ -30,23 +77,25 @@ import {call as fetchMany} from 'core/ajax'; * @param {Number} userid User ID (defaults to current user) * @return {Promise} */ -export const getUserPreference = (name, userid = 0) => { - return getUserPreferences(name, userid) - .then(response => response.preferences[0].value); -}; +export const getUserPreference = (name, userid = 0) => getUserPreferences(name, userid) + .then((response) => response[name]); /** * Get multiple user preferences * * @param {String|null} name Name of the preference (omit if you want to retrieve all) * @param {Number} userid User ID (defaults to current user) - * @return {Promise} + * @return {Promise>} */ export const getUserPreferences = (name = null, userid = 0) => { - return fetchMany([{ - methodname: 'core_user_get_user_preferences', - args: {name, userid} - }])[0]; + checkUserId(userid); + const endpoint = ['current', 'preferences']; + + if (name) { + endpoint.push(name); + } + + return performGet('core_user', endpoint.join('/')); }; /** @@ -58,7 +107,16 @@ export const getUserPreferences = (name = null, userid = 0) => { * @return {Promise} */ export const setUserPreference = (name, value = null, userid = 0) => { - return setUserPreferences([{name, value, userid}]); + checkUserId(userid); + return performPost( + 'core_user', + `current/preferences/${name}`, + { + body: {value}, + }, + ) + // Return the result of the fetch call, and also add in the legacy saved property. + .then((response) => addLegacySavedProperty(response, [{name}])); }; /** @@ -68,10 +126,18 @@ export const setUserPreference = (name, value = null, userid = 0) => { * @return {Promise} */ export const setUserPreferences = (preferences) => { - return fetchMany([{ - methodname: 'core_user_set_user_preferences', - args: {preferences} - }])[0]; + preferences.forEach((preference) => checkUserId(preference.userid)); + return performPost( + 'core_user', + 'current/preferences', + { + body: { + preferences: Object.fromEntries (preferences.map((preference) => ([preference.name, preference.value]))), + }, + }, + ) + // Return the result of the fetch call, and also add in the legacy saved property. + .then((response) => addLegacySavedProperty(response, preferences)); }; /**