Problem Description
Hi all,I just want to share this since I used the form knowledge to achieve it.Apparently it's a real struggle to use the build this in a custom plugin.So here it is FULLY working and tested.local/videoupload/templates/mobile_main.mustache<ion-list><ion-item lines="none"><ion-label><h2>Videoupload</h2><p>Select 1 video and send it.</p></ion-label></ion-item><core-attachments[files]="attachments"[maxSize]="CONTENT_OTHERDATA.maxsize"[maxSubmissions]="1"[component]="'local_videoupload'"[acceptedTypes]="CONTENT_OTHERDATA.filetypes"[allowOffline]="false"></core-attachments><ion-buttonexpand="block"color="primary"(click)="sendVideo()"name="local_videoupload_send">Send</ion-button></ion-list>local/videoupload/db/mobile.php<?phpdefined('MOODLE_INTERNAL') || die();$addons = ['local_videoupload' => ['handlers' => ['videoupload_menu' => ['delegate' => 'CoreMainMenuDelegate','method' => 'mobile_view','displaydata' => ['title' => 'pluginname','icon' => 'fa-video',],'priority' => 800,],],'lang' => [['pluginname', 'local_videoupload'],['send', 'local_videoupload'],['novideo', 'local_videoupload'],['uploadok', 'local_videoupload'],['uploadfail', 'local_videoupload'],],],];local/videoupload/classes/external.php<?phpnamespace local_videoupload;use external_api;use external_function_parameters;use external_single_structure;use external_value;defined('MOODLE_INTERNAL') || die();require_once($CFG->libdir . '/externallib.php');class external extends external_api {public static function save_parameters() {return new external_function_parameters(['draftitemid' => new external_value(PARAM_INT, 'Draft itemid from mobile upload'),]);}public static function save($draftitemid) {global $USER, $CFG;$params = self::validate_parameters(self::save_parameters(),['draftitemid' => $draftitemid]);$usercontext = \context_user::instance($USER->id);self::validate_context($usercontext);// Ensure file API functions are available.require_once($CFG->libdir . '/filelib.php');$finalitemid = time();\file_save_draft_area_files($params['draftitemid'],$usercontext->id,'local_videoupload','video',$finalitemid,['subdirs' => 0, 'maxfiles' => 1]);return ['status' => true,'itemid' => $finalitemid,];}public static function save_returns() {return new external_single_structure(['status' => new external_value(PARAM_BOOL, 'Success'),'itemid' => new external_value(PARAM_INT, 'Stored itemid in plugin filearea'),]);}}ocal/videoupload/mobile/js/template_javascript.js/*** Moodle App site plugin JS.* Important: core-attachments expects to mutate a top-level array bound via [files].* So we use this.attachments (NOT CONTENT_OTHERDATA.attachments).*/// The array that <core-attachments> will update.this.attachments = [];this.sendVideo = async function () {const attachments = this.attachments || [];if (!attachments.length) {this.CoreDomUtilsProvider.showToast(this.TranslateService.instant('local_videoupload.novideo'),true,4000);return false;}const modal = await this.CoreDomUtilsProvider.showModalLoading('core.sending', true);try {const uploadResult = await this.CoreFileUploaderProvider.uploadOrReuploadFiles(attachments);// Handle versions that return itemid as a number/string directly.let draftItemId = null;if (typeof uploadResult === 'number') {draftItemId = uploadResult;} else if (typeof uploadResult === 'string' && uploadResult.trim() !== '' && !isNaN(parseInt(uploadResult, 10))) {draftItemId = parseInt(uploadResult, 10);} else {draftItemId =(uploadResult && uploadResult.itemid) ||(uploadResult && uploadResult.draftitemid) ||(uploadResult && uploadResult.draftid) ||(uploadResult && uploadResult.data && uploadResult.data.itemid) ||(uploadResult && uploadResult.result && uploadResult.result.itemid) ||(uploadResult && uploadResult.filesresult && uploadResult.filesresult.itemid) ||(uploadResult && uploadResult[0] && (uploadResult[0].itemid || uploadResult[0].draftitemid));if (!draftItemId && attachments[0]) {draftItemId =attachments[0].itemid ||attachments[0].draftitemid ||attachments[0].draftid;}}if (!draftItemId) {throw new Error('Upload finished but draft itemid was not returned.');}const site = await this.CoreSitesProvider.getSite();const result = await site.write('local_videoupload_save', { draftitemid: draftItemId });if (result && result.status) {this.CoreDomUtilsProvider.showToast(this.TranslateService.instant('local_videoupload.uploadok'),true,6000);} else {this.CoreDomUtilsProvider.showToast(this.TranslateService.instant('local_videoupload.uploadfail'),true,6000);}return result;} catch (error) {this.CoreDomUtilsProvider.showErrorModalDefault(error, 'local_videoupload.uploadfail');return false;} finally {modal.dismiss();try { this.CoreFileUploaderProvider.clearTmpFiles(attachments); } catch (e) {}this.attachments = [];}};local/videoupload/classes/output/mobile.php<?phpnamespace local_videoupload\output;defined('MOODLE_INTERNAL') || die();class mobile {public static function mobile_view($args) {global $CFG, $OUTPUT;$maxbytes = isset($CFG->maxbytes) ? (int) $CFG->maxbytes : 0;// IMPORTANT: Keep otherdata flat + scalar strings (most compatible).$otherdata = ['maxsize' => (string) $maxbytes,'filetypes' => '.mp4,.mov,.m4v,.webm','attachments' => '', // empty string, JS will turn it into []];return ['templates' => [['id' => 'main','html' => $OUTPUT->render_from_template('local_videoupload/mobile_main', []),],],'javascript' => file_get_contents($CFG->dirroot. '/local/videoupload/mobile/js/template_javascript.js'),'otherdata' => $otherdata,];}}local/videoupload/db/services.php<?phpdefined('MOODLE_INTERNAL') || die();$functions = ['local_videoupload_save' => ['classname' => 'local_videoupload\external','methodname' => 'save','description' => 'Finalize a draft video upload.','type' => 'write','services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],],];local/videoupload/version.php<?phpdefined('MOODLE_INTERNAL') || die();$plugin->component = 'local_videoupload';$plugin->version = 2025122906;$plugin->requires = 2022041900; // Moodle 4.0+ (adjust if needed).
AI-Generated Solution
Powered by LMSouq AI · GPT-4.1-mini
Analyzing problem and generating solution…
Was this solution helpful?