Taking a Picture / File Upload from Moodle App in custom plugin

Taking a Picture / File Upload from Moodle App in custom plugin

by Martin Herold -
Number of replies: 12

Hi everyone, 

I'm developing a moodle plugin where we want to have students add entries including a text, date and a picture. It's working on web but we wanted to bring it into the mobile App. I'm not getting file uploads to work here, what do I have to do? I can't find anything in the documentation regarding file uploads from the app. 

I have a mustache template including a file input:

<ion-item>
  <ion-label id="image" color="primary" stacked>Bild hinzufügen</ion-label>
  <ion-input [(ngModel)]="CONTENT_OTHERDATA.file" type="file" accept="image/*" ></ion-input>
</ion-item>

 But the file is not send to the webservice. In the screenshot you can see, its just some kind of path that gets send to the api. 

Can you help?

Best
Martin


Attachment moodle_mobile_newscreen.png
Average of ratings: Useful (1)
In reply to Martin Herold

Re: Taking a Picture / File Upload from Moodle App in custom plugin

by Dani Palou -
Picture of Core developers Picture of Moodle HQ Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

Hi Martin!

AFAIK there is no site plugin supporting this at the moment, so there's no plugin example you can look at.

First of all, uploading files in Moodle is usually not as easy as just sending a file input. The workflow done in other places of the app is:

  • Using the <core-attachments> component, we let the user select files to be uploaded.
  • Once the user submits the form, we upload those files to Moodle using 
    CoreFileUploaderProvider.uploadOrReuploadFiles. This function uploads the files to Moodle using the "/webservice/upload.php" endpoint, and it returns some data of the area where the files were uploaded. We're interested in the returned itemid.
  • The app then sends that itemid to the WebService that saves the data (e.g. in the case of creating a glossary entry, we use the WebService mod_glossary_add_entry). The WebService moves the files from the temporary draft area to the final area.

You will need to use Javascript to achieve this workflow. The core-attachments component needs to receive a [files] input with an array where to store the files (and, if you want to display files already attached, you should put them in this array too). The component will automatically update this array when the user selects or deletes files.

Once the user clicks the submit button you will need to run a custom Javascript function. This function will have to use CoreFileUploaderProvider.uploadOrReuploadFiles to upload the files and receive the itemid. Then you will need to put that itemid with the rest of the user input data and send it to your WebService.

If you click here you'll see how it's done in the app for glossary. Please notice the app code is more complex because it supports offline, I guess your plugin will only support online so the code will be simpler.

Cheers,

Dani

Average of ratings: Useful (1)
In reply to Dani Palou

Re: Taking a Picture / File Upload from Moodle App in custom plugin

by Martin Herold -
Hi Dani,
thank you for the infos on this.
I cannot find documentation for the core-attachments component and the CoreFileUploaderProvider.uploadOrReuploadFiles function in the official moodle documentation. Is this already documented?
In the example you sent me, this looks like an Ionic Module. Is it also possible in a plugin using PHP code, templates and Ionic markup? So by just adding JavaScript to the template? I don’t know how to do the typescript imports there.
Best
Martin
In reply to Martin Herold

Re: Taking a Picture / File Upload from Moodle App in custom plugin

by Dani Palou -
Picture of Core developers Picture of Moodle HQ Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
Hi Martin,

unfortunately we don't have time to document all the components and services of the app, so you will have to check the JSDoc in the code. We could find a tool to generate some documentation using the JSDoc, but IMO it's almost the same as looking the source code:


About Javascript, you can return JS code along with your template (in the PHP code) and the app will run it with a certain context that includes most of the app services. If you click here you can see a simple example of returning javascript. I recommend putting the JS code in a separate file and then return it in the PHP using file_get_contents, you can see an example in here. In that JS code you can do for example:

this.CoreFileUploaderProvider

and you will have access to the service instance and all of its methods.

Cheers,
Dani
Average of ratings: Useful (2)
In reply to Dani Palou

Re: Taking a Picture / File Upload from Moodle App in custom plugin

by Naomi Quirke -
Hi Dani,
I can't work out from what you've written above whether it is even possible to use the core-attachments component etc from normal JavaScript functions as in https://docs.moodle.org/dev/Moodle_App_Plugins_Development_Guide#JS_functions_visible_in_the_templates, or whether we have to write another component to be able to access CoreFileEntry files.
Just need to know the level of expertise required here.
In reply to Naomi Quirke

Re: Taking a Picture / File Upload from Moodle App in custom plugin

by Marcus Green -
Picture of Core developers Picture of Particularly helpful Moodlers Picture of Plugin developers Picture of Testers
You mention Taking a picture in the subject line, would the Mobile code that allows the scanning of bar/QR code give some clues as to how to take pictures?
In reply to Naomi Quirke

Re: Taking a Picture / File Upload from Moodle App in custom plugin

by Dani Palou -
Picture of Core developers Picture of Moodle HQ Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers
Hi Naomi,

using the in your template shouldn't be hard, you just need to initialize an array of files (just an empty array if there are no files) and supply it to the component. You don't need to create your own component.

The trickier part will be uploading those files, you will need to create your own Javascript function to upload the files and call your WebService. Please remember your Javascript code has access to most of the app's services.

If you aren't familiar with Angular and the app's code it won't be trivial to implement this.

Cheers,
Dani
Average of ratings: Useful (1)
In reply to Martin Herold

Re: Taking a Picture / File Upload from Moodle App in custom plugin

by Naomi Quirke -
Martin, were you able to work out how to do this "in a plugin using PHP code, templates and Ionic markup? So by just adding JavaScript to the template? "
In reply to Naomi Quirke

Re: Taking a Picture / File Upload from Moodle App in custom plugin

by Martin Herold -
Hi Naomi,

I wasn't able to get this working so far. But I just put my project on GitHub. Maybe it helps someone with a similar use case. If someone can and wants to help me out with the upload via the Moodle App feel free to send me a pull request on GitHub.

https://github.com/MartinHerold/moodleplugin-trainingsnachweise

Cheers,
Martin
In reply to Martin Herold

Re: Taking a Picture / File Upload from Moodle App in custom plugin

by Naomi Quirke -
Hi Martin and everyone,
I was able to get this going in my project last year, sorry I forgot to update this with the solution.
Following is what I got working, but if there is a more efficient way, then please share back to me.

There are six parts to the solution, 5 files are affected:


1 classes\output\mobile.php

must return the 'templates', the 'javascript', and 'otherdata' with the data required for the javascript and template.

e.g.

 

        $otherdata = ['usefulstuff' => json_encode($usefulstuff)

        , 'maxsize' => $maxsize

        , 'filetypes' => '.png,.jpg,.jpeg,.pdf,.doc,.docx,.bmp,.odt', 'attachments' => ""

];

        return [

            'templates' => [

                [

                    'id' => 'main',

                    'html' => $OUTPUT->render_from_template('myplugin/template_name', $data),

                ],

            ],

            'javascript' => file_get_contents($CFG->dirroot . "myplugin_path/mobile/template_javascript.js"),

            'otherdata' => $otherdata

        ];

 

2 template part 1: the attachments selector

myplugin/template_name
This creates the functionality to select and gather the documents into the mobile app.

 

<core-attachments [files]="CONTENT_OTHERDATA.attachments"

[maxSize]="CONTENT_OTHERDATA.maxsize"

[maxSubmissions]="1"

    [component]="myplugin"

[acceptedTypes]="CONTENT_OTHERDATA.filetypes"

[allowOffline]=false>

 </core-attachments>

 

3 template part 2

myplugin/template_name
We now need to let the user agree to send these documents.

 

<ion-button (click)="updateContent(CONTENT_OTHERDATA)color="primary" expand="block" name="myplugin_upload" successMessage="{{ 'plugin.myplugin.done' | translate }}" refreshOnSuccess="true">

          Send

</ion-button>

 

4 javascript for the button

myplugin_path/mobile/template_javascript.js

  

this.updateContent = function(allinfo) {

    let attachments = allinfo.attachments;

    if ((!attachments.length) {

        return false;

    }

    return this.saveFiles(allinfo);

};

  

this.saveFiles = async function (inputData) {

    let saveOffline = false;

    let response;

    const modal = await this.CoreDomUtilsProvider.showModalLoading('core.sending', true);

    // Upload attachments.

    try {

        let attachmentsId; // number | undefined;

        attachmentsId = await this.uploadOrStoreSubmissionFiles(inputData.attachments, false);

        const params = {

            attachments: attachmentsId,

        };

        const site = await this.CoreSitesProvider.getSite();

        response = await site.write('myplugin_add_attachments', params);

        if (response.status === true) {

            this.CoreDomUtilsProvider.showToast(

                this.TranslateService.instant('plugin.myplugin.uploadsuccessful'), true, 6000);

        } else {

            this.CoreDomUtilsProvider.showToast(

                this.TranslateService.instant('plugin.myplugin.uploadfailure'), true, 6000);

        }

    } catch (error) {

        this.CoreDomUtilsProvider.showErrorModalDefault(error, 'Cannot save files');

    } finally {

        modal.dismiss();

        this.CoreFileUploaderProvider.clearTmpFiles(inputData.attachments);

        this.CONTENT_OTHERDATA.attachments = [];

    }

    return response;

};

 

 

5 DB\Services.php

 

$functions = [    

    'myplugin_add_attachments' => [

        'classname'     => 'myplugin_external',

        'methodname'    => 'add_attachments',

        'description'   => 'Upload attachments',

        'type'          => 'write',

        'services'      => [MOODLE_OFFICIAL_MOBILE_SERVICE, 'local_mobile']

    ],

];

6 Classes\External.php


public static function add_attachments

The file gets uploaded into user draft files. So to access, make 

public static function add_attachments($attachments), and include

        $fs = get_file_storage();

        $file = $fs->get_area_files($contextid, 'user', 'draft', $attachments

            , "itemid, filepath, filename", false, 0, 0, 0);

        $uploadStatus = true;

        foreach ($file as $f) {

            $filecontent = $f->get_content();

            $filename = $f->get_filename();

            $encoded = base64_encode($filecontent);

            // Do stuff with file

        }

        $fs->delete_area_files($contextid, 'user', 'draft', $attachments);

 

 

 

In reply to Naomi Quirke

Re: Taking a Picture / File Upload from Moodle App in custom plugin

by Kurt Armbruster -
Naomi,
Could this be adapted for a non-mobile plugin?

I'm working on a project where the user can upload a text file that ultimately builds the UI of a mod plugin for their students.
We're storing the in a cloud service for now, but it would be nice to get it into the moodle db, instead.
In reply to Naomi Quirke

Re: Taking a Picture / File Upload from Moodle App in custom plugin

by Neal Young -

Hello, Naomi. 

I'm trying to build an attachment selector on "mobile_add_document_to_category_view". Unfortunately, I might not see it's been showed correctly on this mobile view. I assume I can browse & select documents from local Photo albums.  

The third screenshot is my php codes when I create a function "createAttachmentsSelector".  Are my written codes correctly may I ask ?

Can you describe a bit more details about these 6 steps if you have a chance, please ?

Attachment mobile_add_document_to_category_view.mustache
Attachment moodle mobile view
Attachment Php codes