Custom Module not saving and receiving data

Hello Xibo Community,
i created a custom module to make a API request. When I add the module and want to edit the required form fields it doesnt save them. Also the added module (which works fine with hardcoded values) can’t be edited. It doesn’t save the data. I duplicates the module in the module overfiew.
here is my code:

<?php

class CAB extends Module
{
    public function __construct(database $db, user $user, $mediaid = '', $layoutid = '', $regionid = '', $lkid = '') {
        // The Module Type must be set - this should be a unique text string of no more than 50 characters.
        // It is used to uniquely identify the module globally.
        $this->type = 'cab';

        // This is the code schema version, it should be 1 for a new module and should be incremented each time the 
        // module data structure changes.
        // It is used to install / update your module and to put updated modules down to the display clients.
        $this->codeSchemaVersion = 1;
        
        // Must call the parent class
        parent::__construct($db, $user, $mediaid, $layoutid, $regionid, $lkid);
    }

    public function InstallFiles() {
        $media = new Media();
        $media->addModuleFile('modules/preview/vendor/jquery-1.11.1.min.js');
        $media->addModuleFile('modules/preview/xibo-layout-scaler.js');
    }

    /**
     * Install or Update this module
     */
    public function InstallOrUpdate() {
        // This function should update the `module` table with information about your module.
        // The current version of the module in the database can be obtained in $this->schemaVersion
        // The current version of this code can be obtained in $this->codeSchemaVersion
        
        // $settings will be made available to all instances of your module in $this->settings. These are global settings to your module, 
        // not instance specific (i.e. not settings specific to the layout you are adding the module to).
        // $settings will be collected from the Administration -> Modules CMS page.
        // 
        // Layout specific settings should be managed with $this->SetOption in your add / edit forms.

        $name =             'Name';
        $description =      'Description';
        $imageUri =         'forms/library.gif';
        $previewEnabled =   1;
        $assignable =       1;
        
        if ($this->schemaVersion <= 1) {
            // Install
            $this->InstallModule($name, $description, $imageUri, $previewEnabled, $assignable, $settings);
        }
        else {
            // Update
            // Call "$this->UpdateModule($name, $description, $imageUri, $previewEnabled, $assignable, $settings)" with the updated items
        }

        // Check we are all installed
        $this->InstallFiles();

        // After calling either Install or Update your code schema version will match the database schema version and this method will not be called
        // again. This means that if you want to change those fields in an update to your module, you will need to increment your codeSchemaVersion.
    }

    /**
     * Form for updating the module settings
     */
    public function ModuleSettingsForm() {
        // Output any form fields (formatted via a Theme file)
        // These are appended to the bottom of the "Edit" form in Module Administration

        $formFields[] = FormManager::AddText('apiKey', __('API Key'), $this->GetSetting('apiKey'), 
            __('Enter your API Key from Forecast IO.'), 'a', 'required');

        return $formFields;
    }

    /**
     * Process any module settings
     */
    public function ModuleSettings() {
        // Process any module settings you asked for.
        $apiKey = Kit::GetParam('apiKey', _POST, _STRING, '');

        if ($apiKey == '')
            $this->ThrowError(__('Missing API Key'));

        $this->settings['apiKey'] = $apiKey;

        // Return an array of the processed settings.
        return $this->settings;
    }

    /*
    public function loadTemplates()
    {
        // Scan the folder for template files
        foreach (glob('modules/theme/module/*.template.json') as $template) {
            // Read the contents, json_decode and add to the array
            $this->settings['templates'][] = json_decode(file_get_contents($template), true);
        }

        Debug::Audit(count($this->settings['templates']));
    }
    */
    
    /**
     * Return the Add Form as HTML
     * @return
     */
    public function AddForm()
    {
        // var_dump($this); die();
        $this->response = new ResponseManager();
        // This is the logged in user and can be used to assess permissions
        $user =& $this->user;

        // All modules will have:
        //  $this->layoutid
        //  $this->regionid

        // You also have access to $settings, which is the array of settings you configured for your module.
        
        // The CMS provides the region width and height in case they are needed
        $rWidth     = Kit::GetParam('rWidth', _REQUEST, _STRING);
        $rHeight    = Kit::GetParam('rHeight', _REQUEST, _STRING);

        // All forms should set some meta data about the form.
        // Usually, you would want this meta data to remain the same.
        Theme::Set('form_id', 'ModuleForm');
        Theme::Set('form_action', 'index.php?p=module&mod=' . $this->type . '&q=Exec&method=AddMedia');
        Theme::Set('form_meta', '<input type="hidden" name="layoutid" value="' . $this->layoutid . '"><input type="hidden" id="iRegionId" name="regionid" value="' . $this->regionid . '"><input type="hidden" name="showRegionOptions" value="' . $this->showRegionOptions . '" />');
    
        // Any values for the form fields should be added to the theme here.
        // Tabs

        $tabs = array();
        $tabs[] = FormManager::AddTab('general', __('General'));
        $tabs[] = FormManager::AddTab('advanced', __('Coordinates'));

        Theme::Set('form_tabs', $tabs);

        $formFields['general'][] = FormManager::AddText('name', __('Name'), NULL,
            __('Name this module (optional)'), 'n');

        $formFields['general'][] = FormManager::AddNumber('duration', __('Duration'), NULL, 
            __('The duration in seconds this item should be displayed.'), 'd', 'required');

        $formFields['advanced'][] = FormManager::AddText('latitude', __('Latitude'), NULL,
            __('Specify the Latitude of this module'), 'la');

        $formFields['advanced'][] = FormManager::AddText('longitude', __('Longitude'), NULL,
            __('Specify the Longitude of this module'), 'lo');

        $formFields['advanced'][] = FormManager::AddText('radius', __('Radius'), NULL,
            __('Specify the Search radius of this module'), 'r');

        $formFields['advanced'][] = FormManager::AddCombo('providernetwork', __('Providernetwork'), $this->GetOption('providernetwork'),
            $this->providernetworks(), 
            'id', 
            'value', 
            __('Select your desired network.'), 'ne');

        // Modules should be rendered using the theme engine.
        Theme::Set('form_fields_general', $formFields['general']);
        Theme::Set('form_fields_advanced', $formFields['advanced']);
        $this->response->html = Theme::RenderReturn('form_render');

        // Any JavaScript call backs should be set (you can use text_callback to set up a text editor should you need one)
        $this->response->callBack = 'testFormSetup';

        $this->response->dialogTitle = __('MOdule nAME');
        
        // You can have a bigger form
        //$this->response->dialogClass = 'modal-big';

        // The response object outputs the required JSON object to the browser
        // which is then processed by the CMS JavaScript library (xibo-cms.js).
        $this->response->AddButton(__('Cancel'), 'XiboDialogClose()');
        $this->response->AddButton(__('Save'), '$("#ModuleForm").submit()');

        // The response must be returned.
        return $this->response;
    }

    /**
     * Add Media to the Database
     * @return
     */
    public function AddMedia()
    {
        $this->response = new ResponseManager();
        // Same member variables as the Form call, except with POST variables for your form fields.
        $layoutid   = $this->layoutid;
        $regionid   = $this->regionid;
        $mediaid    = $this->mediaid;

        // You are required to set a media id, which should be unique.
        $this->mediaid  = md5(uniqid());

        // You must also provide a duration (all media items must provide this field)
        $this->duration = Kit::GetParam('duration', _POST, _INT, 0, false);

        // You should validate all form input using the Kit::GetParam helper classes
        Kit::GetParam('duration', _POST, _INT, 0, false);
        
        // You should also validate that fields are set to your liking
        /*
        *   if ($text == '')
        *   {
        *       $this->response->SetError('Please enter some text');
        *       $this->response->keepOpen = true;
        *       return $this->response;
        *   }
        */

        // You can store any additional options for your module using the SetOption method

        $this->SetOption('latitude', Kit::GetParam('latitude', _POST, _DOUBLE));
        $this->SetOption('longitude', Kit::GetParam('longitude', _POST, _DOUBLE));
        $this->SetOption('radius', Kit::GetParam('radius', _POST, _INT));
        $this->SetOption('providernetwork', Kit::GetParam('providernetwork', _POST, _INT));

        // You may also store raw XML/HTML using SetRaw. You should provide a containing node (in this example: <text>)
        $this->SetRaw('<text><![CDATA[' . $text . ']]></text>');

        // Should have built the media object entirely by this time
        // This saves the Media Object to the Region
        $this->UpdateRegion();

        // Usually you will want to load the region options form again once you have added your module.
        // In some cases you will want to load the edit form for that module
        $this->response->loadForm = true;
        $this->response->loadFormUri = "index.php?p=timeline&layoutid=$this->layoutid&regionid=$this->regionid&q=RegionOptions";
        //$this->response->loadFormUri = "index.php?p=module&mod=$this->type&q=Exec&method=EditForm&layoutid=$this->layoutid&regionid=$regionid&mediaid=$this->mediaid";

        return $this->response;
    }

    /**
     * Return the Edit Form as HTML
     * @return
     */
    public function EditForm()
    {
        $this->response = new ResponseManager();
        // Edit forms are the same as add forms, except you will have the $this->mediaid member variable available for use.

        if (!$this->auth->edit)
        {
            $this->response->SetError('You do not have permission to edit this assignment.');
            $this->response->keepOpen = false;
            return $this->response;
        }

        // All forms should set some meta data about the form.
        // Usually, you would want this meta data to remain the same.
        Theme::Set('form_id', 'ModuleForm');
        Theme::Set('form_action', 'index.php?p=module&mod=' . $this->type . '&q=Exec&method=AddMedia');
        Theme::Set('form_meta', '<input type="hidden" name="layoutid" value="' . $this->layoutid . '"><input type="hidden" id="iRegionId" name="regionid" value="' . $this->regionid . '"><input type="hidden" name="showRegionOptions" value="' . $this->showRegionOptions . '" />');

        $tabs = array();
        $tabs[] = FormManager::AddTab('general', __('General'));
        $tabs[] = FormManager::AddTab('advanced', __('Coordinates'));

        Theme::Set('form_tabs', $tabs);

        $formFields['general'][] = FormManager::AddText('name', __('Name'), NULL,
            __('Name this module (otional)'), 'n');

        $formFields['general'][] = FormManager::AddNumber('duration', __('Duration'), NULL, 
            __('The duration in seconds this item should be displayed.'), 'd', 'required');

        $formFields['advanced'][] = FormManager::AddText('latitude', __('Latitude'), NULL,
            __('Specify the Latitude of this module'), 'la');

        $formFields['advanced'][] = FormManager::AddText('longitude', __('Longitude'), NULL,
            __('Specify the Longitude of this module'), 'lo');

        $formFields['advanced'][] = FormManager::AddText('radius', __('Radius'), NULL,
            __('Specify the Search radius of this module'), 'r');

        $formFields['advanced'][] = FormManager::AddCombo('providernetwork', __('Providernetwork'), $this->GetOption('providernetwork'),
            $this->providernetworks(), 
            'id', 
            'value', 
            __('Select your desired network.'), 'ne');

        // Dependencies (some fields should be shown / hidden)
        // $this->SetFieldDependencies();

        // Modules should be rendered using the theme engine.
        Theme::Set('form_fields_general', $formFields['general']);
        Theme::Set('form_fields_advanced', $formFields['advanced']);
        $this->response->html = Theme::RenderReturn('form_render');

        // Any JavaScript call backs should be set (you can use text_callback to set up a text editor should you need one)
        $this->response->callBack = 'flinksterFormSetup';

        $this->response->dialogTitle = __('CallABike');
        
        // You can have a bigger form
        //$this->response->dialogClass = 'modal-big';

        // The response object outputs the required JSON object to the browser
        // which is then processed by the CMS JavaScript library (xibo-cms.js).
        $this->response->AddButton(__('Cancel'), 'XiboDialogClose()');
        $this->response->AddButton(__('Save'), '$("#ModuleForm").submit()');

        // The response must be returned.
        return $this->response;
    }

    /**
     * Edit Media in the Database
     * @return
     */
    public function EditMedia()
    {
        $this->response = new ResponseManager();
        
        // Edit calls are the same as add calls, except you will to check the user has permissions to do the edit
        if (!$this->auth->edit)
        {
            $this->response->SetError('You do not have permission to edit this assignment.');
            $this->response->keepOpen = false;
            return $this->response;
        }

        // You can store any additional options for your module using the SetOption method

        $this->SetOption('latitude', Kit::GetParam('latitude', _POST, _DOUBLE));
        $this->SetOption('longitude', Kit::GetParam('longitude', _POST, _DOUBLE));
        $this->SetOption('radius', Kit::GetParam('radius', _POST, _INT));
        $this->SetOption('providernetwork', Kit::GetParam('providernetwork', _POST, _INT));

        // You may also store raw XML/HTML using SetRaw. You should provide a containing node (in this example: <text>)
        $this->SetRaw('<text><![CDATA[' . $text . ']]></text>');

        // Should have built the media object entirely by this time
        // This saves the Media Object to the Region
        $this->UpdateRegion();

        // Usually you will want to load the region options form again once you have added your module.
        // In some cases you will want to load the edit form for that module
        if ($this->showRegionOptions) {
            $this->response->callBack = 'refreshPreview("' . $this->regionid . '")';
            $this->response->loadForm = true;
            $this->response->loadFormUri = "index.php?p=timeline&layoutid=$this->layoutid&regionid=$this->regionid&q=RegionOptions";
        }

        // Usually you will want to load the region options form again once you have added your module.
        // In some cases you will want to load the edit form for that module
        $this->response->loadForm = true;
        $this->response->loadFormUri = "index.php?p=timeline&layoutid=$this->layoutid&regionid=$this->regionid&q=RegionOptions";
        //$this->response->loadFormUri = "index.php?p=module&mod=$this->type&q=Exec&method=EditForm&layoutid=$this->layoutid&regionid=$regionid&mediaid=$this->mediaid";

        return $this->response;
    }

    /**
     * GetResource
     *     Return the rendered resource to be used by the client (or a preview)
     *     for displaying this content.
     * @param integer $displayId If this comes from a real client, this will be the display id.
     */
    public function GetResource($displayId = 0)
    {
        // Behave exactly like the client.

        // A template is provided which contains a number of different libraries that might
        // be useful (jQuery, etc).
        // You can provide your own template, or just output the HTML directly in this method. It is up to you.
        //$template = file_get_contents('modules/preview/HtmlTemplateSimple.html');
        // Behave exactly like the client.

        $isPreview = (Kit::GetParam('preview', _REQUEST, _WORD, 'false') == 'true');

        // Load in the template
        $template = file_get_contents('modules/preview/HtmlTemplateForCAB.html');

        // Replace the View Port Width?
        if (isset($_GET['preview']))
            $template = str_replace('[[ViewPortWidth]]', $this->width, $template);

        // Get the text out of RAW
        $rawXml = new DOMDocument();
        $rawXml->loadXML($this->GetRaw());

        // HERE GOES API HANDLING

        // Return that content.
        return $template;
    }

    public function HoverPreview()
    {
        // Default Hover window contains a thumbnail, media type and duration
        $output = parent::HoverPreview();

        // You can add anything you like to this, or completely replace it

        return $output;
    }
    
    public function IsValid() {
        // Using the information you have in your module calculate whether it is valid or not.
        // 0 = Invalid
        // 1 = Valid
        // 2 = Unknown
        return 1;
    }

    private function providernetworks()
    {
    return array(
            array('id' => '1', 'value' => 'network1'),
            array('id' => '2', 'value' => 'network2'),
            array('id' => '3', 'value' => 'network3'),
        );
    }

    private function parseLibraryReferences($isPreview, $content)
    {
        $parsedContent = $content;
        $matches = '';
        preg_match_all('/\[.*?\]/', $content, $matches);

        foreach ($matches[0] as $sub) {
            // Parse out the mediaId
            $mediaId = str_replace(']', '', str_replace('[', '', $sub));

            // Only proceed if the content is actually an ID
            if (!is_numeric($mediaId))
                continue;

            // Check that this mediaId exists and get some information about it
            $entry = Media::Entries(null, array('mediaId' => $mediaId));

            if (count($entry) <= 0)
                continue;

            // We have a valid mediaId to substitute
            $replace = ($isPreview) ? 'index.php?p=module&mod=image&q=Exec&method=GetResource&mediaid=' . $entry[0]->mediaId : $entry[0]->storedAs;

            // Substitute the replacement we have found (it might be '')
            $parsedContent = str_replace($sub, $replace, $parsedContent);
        }

        return $parsedContent;
    }
    
}
?>

I hope you can help me with this problem.

What exactly is it you want to achieve please? It’s modified forecastio.module.php in 1.7 series CMS, correct?

It is based of the module template and the forecast module. It is meant to make a API request and output the data. All this works fine but if i try to edit the module settings (e.g. I need the geographic location for my request) it doesn’t save my settings neither it displays settings i made before.

perhaps @dan will have some idea, he should get back to you once he will have time.

Thanks anyways. I hope he can help me.

Sorry but without some logs its very difficult to say what might be happening.

It sounds almost like there is a mismatch between the module db record created (type column), the module file name and the class name - perhaps it can’t instantiate the module correctly or something?

Can you attempt to get some logs?

Hi, I got a little step closer. I managed to prefill all form fields. It seems like if I try to save it doesn’t update the DB but rather create a new entry. I will post logs asap.

So here is a log when I try to edit the name of my module.

Seems like the variable ‘name’ is not defined?

What is on line 253 of your file?

Can you confirm that this transaction is on the modules page, when you use the edit form?

Thats correct!
Line 253 is empty but I will post a short excerpt of my file around line 253:

        // You can store any additional options for your module using the SetOption method
        $this->SetOption('name', Kit::GetParam('name', _POST, _STRING));
        $this->SetOption('latitude', Kit::GetParam('latitude', _POST, _DOUBLE));
        $this->SetOption('longitude', Kit::GetParam('longitude', _POST, _DOUBLE));
        $this->SetOption('radius', Kit::GetParam('radius', _POST, _INT));
        $this->SetOption('providernetwork', Kit::GetParam('providernetwork', _POST, _INT));

        // You may also store raw XML/HTML using SetRaw. You should provide a containing node (in this example: <text>)
        $this->SetRaw('<text><![CDATA[' . $text . ']]></text>');

        // Should have built the media object entirely by this time
        // This saves the Media Object to the Region
        $this->UpdateRegion();

        // Usually you will want to load the region options form again once you have added your module.
        // In some cases you will want to load the edit form for that module
        $this->response->loadForm = true;
        $this->response->loadFormUri = "index.php?p=timeline&layoutid=$this->layoutid&regionid=$this->regionid&q=RegionOptions";
        //$this->response->loadFormUri = "index.php?p=module&mod=$this->type&q=Exec&method=EditForm&layoutid=$this->layoutid&regionid=$regionid&mediaid=$this->mediaid";

        return $this->response;

Line 253 is below this statement:

       $this->SetRaw('<text><![CDATA[' . $text . ']]></text>');
$this->SetRaw('<text><![CDATA[' . $text . ']]></text>');

Have you set the $text variable anywhere? Because the error says you haven’t.

That excerpt of code should be running when you edit your Module on a layout - nothing to do with the Modules page as you had previously stated.

It really is very difficult indeed to help unless you are very clear with your description of what you have done/are doing.

Ok I try to make it more understandable for you. If I go to the layout and add a new region and then selecting my module, entering my data into the form fields. Everything works fine until now. If I now edit the module in the layout, the data gets somehow stored but it duplicates the module. Screenshot:

I haven’t set the $text variable, but commenting all statements doesn’t change the result. I still doesn’t save correctly.

The only error in my logs I get is: Undefined index: link in line 3 in my qrcode-generating file i included.

So I compared my source code to other modules and didn’t find ANY difference where the data is saved.
Take a look:

public function EditMedia()
    {
        $this->response = new ResponseManager();

        // Edit calls are the same as add calls, except you will to check the user has permissions to do the edit
        if (!$this->auth->edit)
        {
            $this->response->SetError('You do not have permission to edit this assignment.');
            $this->response->keepOpen = false;
            return $this->response;
        }

        // You can store any additional options for your module using the SetOption method
        $name = Kit::GetParam('name', _POST, _STRING);

        $this->duration = Kit::GetParam('duration', _POST, _INT, 0, false);
        $this->SetOption('updateInterval', Kit::GetParam('updateInterval', _POST, _INT, 60));

        $this->SetOption('name', $name);
        $this->SetOption('latitude', Kit::GetParam('latitude', _POST, _DOUBLE));
        $this->SetOption('longitude', Kit::GetParam('longitude', _POST, _DOUBLE));
        $this->SetOption('radius', Kit::GetParam('radius', _POST, _INT));
        $this->SetOption('providernetwork', Kit::GetParam('providernetwork', _POST, _INT));

        // You may also store raw XML/HTML using SetRaw. You should provide a containing node (in this example: <text>)
        // $this->SetRaw('<text><![CDATA[' . $text . ']]></text>');

        // Should have built the media object entirely by this time
        // This saves the Media Object to the Region
        $this->UpdateRegion();

        // Usually you will want to load the region options form again once you have added your module.
        // In some cases you will want to load the edit form for that module
        if ($this->showRegionOptions) {
            $this->response->callBack = 'refreshPreview("' . $this->regionid . '")';
            $this->response->loadForm = true;
            $this->response->loadFormUri = "index.php?p=timeline&layoutid=$this->layoutid&regionid=$this->regionid&q=RegionOptions";
        }
        //$this->response->loadFormUri = "index.php?p=module&mod=$this->type&q=Exec&method=EditForm&layoutid=$this->layoutid&regionid=$regionid&mediaid=$this->mediaid";

        return $this->response;
    }

This code snippet is from my custom module. Now here is a snippet from the clock module:

public function EditMedia()
    {
        $this->response = new ResponseManager();
        
        // Edit calls are the same as add calls, except you will to check the user has permissions to do the edit
        if (!$this->auth->edit)
        {
            $this->response->SetError('You do not have permission to edit this assignment.');
            $this->response->keepOpen = false;
            return $this->response;
        }

        // You must also provide a duration (all media items must provide this field)
        $this->duration = Kit::GetParam('duration', _POST, _INT, 0, false);
        $this->SetOption('theme', Kit::GetParam('themeid', _POST, _INT, 0));
        $this->SetOption('clockTypeId', Kit::GetParam('clockTypeId', _POST, _INT, 1));
        $this->SetOption('offset', Kit::GetParam('offset', _POST, _INT, 0));
        $this->SetRaw('<format><![CDATA[' . Kit::GetParam('ta_text', _POST, _HTMLSTRING) . ']]></format>');

        // Should have built the media object entirely by this time
        // This saves the Media Object to the Region
        $this->UpdateRegion();

        // Usually you will want to load the region options form again once you have added your module.
        // In some cases you will want to load the edit form for that module
        if ($this->showRegionOptions) {
            $this->response->callBack = 'refreshPreview("' . $this->regionid . '")';
            $this->response->loadForm = true;
            $this->response->loadFormUri = "index.php?p=timeline&layoutid=$this->layoutid&regionid=$this->regionid&q=RegionOptions";
        }
        
        return $this->response;
    }

They are exactly the same besides some minor differences.

I have no idea i’m afraid - looking at little excerpts of code is rather like looking through a letter box, trying to work out why the fridge isn’t working in the adjacent room.

I guess it might be something to do with the module not loading correctly in the constructor. If you can provide the entire file then I can try it locally (this is something I will be able to do at some point, not immediately).

As a side note, is there any reason why you wouldn’t be using 1.8 as a basis for your module?

Hi, I really appreciate your support. I can provide the whole source code of course. We are using Xibo company-wide and we currently use 1.7.

Here is the source code:
(Have to split it up because if the character limit. Sorry for that)

<?php
    /*
     * Xibo - Digital Signage - http://www.xibo.org.uk
     * Copyright (C) 2006-2014 Daniel Garner
     *
     * This file is part of Xibo.
     *
     * Xibo is free software: you can redistribute it and/or modify
     * it under the terms of the GNU Affero General Public License as published by
     * the Free Software Foundation, either version 3 of the License, or
     * any later version.
     *
     * Xibo 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 Affero General Public License for more details.
     *
     * You should have received a copy of the GNU Affero General Public License
     * along with Xibo.  If not, see <http://www.gnu.org/licenses/>.
     *
     *
     *
     * This is a template module used to demonstrate how a module for Xibo can be made.
     *
     * The class name must be equal to the $this->type and the file name must be equal to modules/type.module.php
     */

    include 'modules/3rdparty/callabike.php';
    include 'modules/3rdparty/qrcode.php';

    class CAB extends Module
    {
        public function __construct(database $db, user $user, $mediaid = '', $layoutid = '', $regionid = '', $lkid = '') {
            // The Module Type must be set - this should be a unique text string of no more than 50 characters.
            // It is used to uniquely identify the module globally.
            $this->type = 'cab';

            // This is the code schema version, it should be 1 for a new module and should be incremented each time the
            // module data structure changes.
            // It is used to install / update your module and to put updated modules down to the display clients.
            $this->codeSchemaVersion = 1;

            // Must call the parent class
            parent::__construct($db, $user, $mediaid, $layoutid, $regionid, $lkid);
        }

        /**
         * Install or Update this module
         */
        public function InstallOrUpdate() {
            // This function should update the `module` table with information about your module.
            // The current version of the module in the database can be obtained in $this->schemaVersion
            // The current version of this code can be obtained in $this->codeSchemaVersion

            // $settings will be made available to all instances of your module in $this->settings. These are global settings to your module,
            // not instance specific (i.e. not settings specific to the layout you are adding the module to).
            // $settings will be collected from the Administration -> Modules CMS page.
            //
            // Layout specific settings should be managed with $this->SetOption in your add / edit forms.

            $name =             'CallABike';
            $description =      'CallABike powered by Flinkster API';
            $imageUri =         'forms/library.gif';
            $previewEnabled =   1;
            $assignable =       1;
            $this->InstallModule($name, $description, $imageUri, $previewEnabled, $assignable);

            /* if ($this->schemaVersion <= 1) {
                // Install
                $this->InstallModule($name, $description, $imageUri, $previewEnabled, $assignable, $settings);
            }
            else {
                // Update
                // Call "$this->UpdateModule($name, $description, $imageUri, $previewEnabled, $assignable, $settings)" with the updated items
            }*/

            // Check we are all installed
            $this->InstallFiles();

            /*
            public function InstallFiles() {
                $media = new Media();
                $media->addModuleFile('modules/preview/vendor/jquery-1.11.1.min.js');
                $media->addModuleFile('modules/preview/xibo-layout-scaler.js');
                $media->addModuleFile('modules/3rdparty/qrcode.php');
            }
            */

            // After calling either Install or Update your code schema version will match the database schema version and this method will not be called
            // again. This means that if you want to change those fields in an update to your module, you will need to increment your codeSchemaVersion.
        }

        /**
         * Form for updating the module settings
         */
        public function ModuleSettingsForm() {
            // Output any form fields (formatted via a Theme file)
            // These are appended to the bottom of the "Edit" form in Module Administration

            $formFields[] = FormManager::AddText('apiKey', __('API Key'), $this->GetSetting('apiKey'),
                __('Enter your API Key from Flinkster.'), 'a', 'required');

            $formFields[] = FormManager::AddText('googleKey', __('Google Maps Key'), $this->GetSetting('googleKey'),
                __('Enter your API Key from Google Maps API.'), 'gk', 'required');

            return $formFields;
        }

        /**
         * Process any module settings
         */
        public function ModuleSettings() {
            // Process any module settings you asked for.
            $apiKey = Kit::GetParam('apiKey', _POST, _STRING, '');
            $googleKey = Kit::GetParam('googleKey', _POST, _STRING, '');

            if ($apiKey == '' || $googleKey == '')
                $this->ThrowError(__('Missing API Key'));

            $this->settings['apiKey'] = $apiKey;
            $this->settings['googleKey'] = $googleKey;

            // Return an array of the processed settings.
            return $this->settings;
        }

        /*
        public function loadTemplates()
        {
            // Scan the folder for template files
            foreach (glob('modules/theme/callabike/*.template.json') as $template) {
                // Read the contents, json_decode and add to the array
                $this->settings['templates'][] = json_decode(file_get_contents($template), true);
            }

            Debug::Audit(count($this->settings['templates']));
        }
        */

        /**
         * Return the Add Form as HTML
         * @return
         */
        public function AddForm()
        {
            // var_dump($this); die();
            $this->response = new ResponseManager();
            // This is the logged in user and can be used to assess permissions
            $user =& $this->user;

            // All modules will have:
            //  $this->layoutid
            //  $this->regionid
            // $this->loadTemplates();

            // You also have access to $settings, which is the array of settings you configured for your module.

            // The CMS provides the region width and height in case they are needed
            $rWidth     = Kit::GetParam('rWidth', _REQUEST, _STRING);
            $rHeight    = Kit::GetParam('rHeight', _REQUEST, _STRING);

            // All forms should set some meta data about the form.
            // Usually, you would want this meta data to remain the same.
            Theme::Set('form_id', 'ModuleForm');
            Theme::Set('form_action', 'index.php?p=module&mod=' . $this->type . '&q=Exec&method=AddMedia');
            Theme::Set('form_meta', '<input type="hidden" name="layoutid" value="' . $this->layoutid . '"><input type="hidden" id="iRegionId" name="regionid" value="' . $this->regionid . '"><input type="hidden" name="showRegionOptions" value="' . $this->showRegionOptions . '" />');

            // Any values for the form fields should be added to the theme here.
            // Tabs

            $tabs = array();
            $tabs[] = FormManager::AddTab('general', __('General'));
            $tabs[] = FormManager::AddTab('advanced', __('Koordinaten'));

            Theme::Set('form_tabs', $tabs);

            $formFields['general'][] = FormManager::AddText('name', __('Name'), NULL,
                __('Name this CallABike module (optional)'), 'n');

            $formFields['general'][] = FormManager::AddNumber('duration', __('Duration'), 60,
                __('The duration in seconds this item should be displayed.'), 'd', 'required');

            $formFields['general'][] = FormManager::AddNumber('updateInterval', __('Update Interval (mins)'), $this->GetOption('updateInterval', 60),
                __('Please enter the update interval in minutes. This should be kept as high as possible. For example, if the data will only change once per hour this could be set to 60.'),
                'u', 'required');

            $formFields['advanced'][] = FormManager::AddText('latitude', __('Latitude'), $this->GetOption('latitude'),
                __('Specify the Latitude of this CallABike module'), 'la');

            $formFields['advanced'][] = FormManager::AddText('longitude', __('Longitude'), $this->GetOption('longitude'),
                __('Specify the Longitude of this CallABike module'), 'lo');

            $formFields['advanced'][] = FormManager::AddText('radius', __('Radius'), $this->GetOption('radius'),
                __('Specify the Search radius of this CallABike module'), 'r');

            $formFields['advanced'][] = FormManager::AddCombo('providernetwork', __('Providernetwork'), $this->GetOption('providernetwork'),
                $this->providernetworks(),
                'id',
                'value',
                __('Select your desired network.'), 'ne');

            // Modules should be rendered using the theme engine.
            Theme::Set('form_fields_general', $formFields['general']);
            Theme::Set('form_fields_advanced', $formFields['advanced']);
            $this->response->html = Theme::RenderReturn('form_render');

            // Any JavaScript call backs should be set (you can use text_callback to set up a text editor should you need one)
            $this->response->callBack = 'flinksterFormSetup';

            $this->response->dialogTitle = __('CallABike');

            // You can have a bigger form
            //$this->response->dialogClass = 'modal-big';

            // The response object outputs the required JSON object to the browser
            // which is then processed by the CMS JavaScript library (xibo-cms.js).
            $this->response->AddButton(__('Cancel'), 'XiboDialogClose()');
            $this->response->AddButton(__('Save'), '$("#ModuleForm").submit()');

            // The response must be returned.
            return $this->response;
        }

        /**
         * Add Media to the Database
         * @return
         */
        public function AddMedia()
        {
            $this->response = new ResponseManager();
            // Same member variables as the Form call, except with POST variables for your form fields.
            $layoutid   = $this->layoutid;
            $regionid   = $this->regionid;
            $mediaid    = $this->mediaid;

            $name = Kit::GetParam('name', _POST, _STRING);

            // You are required to set a media id, which should be unique.
            $this->mediaid  = md5(uniqid());

            // You must also provide a duration (all media items must provide this field)
            $this->duration = Kit::GetParam('duration', _POST, _INT, 0, false);

            // You can store any additional options for your module using the SetOption method
            $this->SetOption('name', Kit::GetParam('name', _POST, _STRING));
            $this->SetOption('latitude', Kit::GetParam('latitude', _POST, _DOUBLE));
            $this->SetOption('longitude', Kit::GetParam('longitude', _POST, _DOUBLE));
            $this->SetOption('radius', Kit::GetParam('radius', _POST, _INT));
            $this->SetOption('providernetwork', Kit::GetParam('providernetwork', _POST, _INT));
            $this->SetOption('duration', Kit::GetParam('duration', _POST, _INT));
            $this->SetOption('updateInterval', Kit::GetParam('updateInterval', _POST, _INT, 60));

            // You may also store raw XML/HTML using SetRaw. You should provide a containing node (in this example: <text>)
            // $this->SetRaw('<text><![CDATA[' . $text . ']]></text>');

            // Should have built the media object entirely by this time
            // This saves the Media Object to the Region
            $this->UpdateRegion();

            // Usually you will want to load the region options form again once you have added your module.
            // In some cases you will want to load the edit form for that module
            $this->response->loadForm = true;
            $this->response->loadFormUri = "index.php?p=timeline&layoutid=$this->layoutid&regionid=$this->regionid&q=RegionOptions";
            //$this->response->loadFormUri = "index.php?p=module&mod=$this->type&q=Exec&method=EditForm&layoutid=$this->layoutid&regionid=$regionid&mediaid=$this->mediaid";

            return $this->response;
        }

        /**
         * Return the Edit Form as HTML
         * @return
         */
        public function EditForm()
        {
            $this->response = new ResponseManager();
            // Edit forms are the same as add forms, except you will have the $this->mediaid member variable available for use.

            if (!$this->auth->edit)
            {
                $this->response->SetError('You do not have permission to edit this assignment.');
                $this->response->keepOpen = false;
                return $this->response;
            }

            // All forms should set some meta data about the form.
            // Usually, you would want this meta data to remain the same.
            Theme::Set('form_id', 'ModuleForm');
            Theme::Set('form_action', 'index.php?p=module&mod=' . $this->type . '&q=Exec&method=AddMedia');
            Theme::Set('form_meta', '<input type="hidden" name="layoutid" value="' . $this->layoutid . '"><input type="hidden" id="iRegionId" name="regionid" value="' . $this->regionid . '"><input type="hidden" name="showRegionOptions" value="' . $this->showRegionOptions . '" />');

            // $this->loadTemplates();

            $tabs = array();
            $tabs[] = FormManager::AddTab('general', __('General'));
            $tabs[] = FormManager::AddTab('advanced', __('Appearance'));

            Theme::Set('form_tabs', $tabs);

            $formFields['general'][] = FormManager::AddText('name', __('Name'), $this->GetOption('name'),
                __('Name this CallABike module (optional)'), 'n');

            $formFields['general'][] = FormManager::AddNumber('duration', __('Duration'), $this->GetOption('duration'),
                __('The duration in seconds this item should be displayed.'), 'd', 'required');

            $formFields['general'][] = FormManager::AddNumber('updateInterval', __('Update Interval (mins)'), $this->GetOption('updateInterval', 60),
                __('Please enter the update interval in minutes. This should be kept as high as possible. For example, if the data will only change once per hour this could be set to 60.'),
                'n', 'required');

            $formFields['advanced'][] = FormManager::AddNumber('latitude', __('Latitude'), $this->GetOption('latitude'),
                __('Specify the Latitude of this CallABike module'), 'la');

            $formFields['advanced'][] = FormManager::AddNumber('longitude', __('Longitude'), $this->GetOption('longitude'),
                __('Specify the Longitude of this CallABike module'), 'lo');

            $formFields['advanced'][] = FormManager::AddNumber('radius', __('Radius'), $this->GetOption('radius'),
                __('Specify the Search radius of this CallABike module'), 'r');

            $formFields['advanced'][] = FormManager::AddCombo('providernetwork', __('Providernetwork'), $this->GetOption('providernetwork'),
                $this->providernetworks(),
                'id',
                'value',
                __('Select your desired network.'), 'ne');

            // Dependencies (some fields should be shown / hidden)
            // $this->SetFieldDependencies();

            // Modules should be rendered using the theme engine.
            Theme::Set('form_fields_general', $formFields['general']);
            Theme::Set('form_fields_advanced', $formFields['advanced']);
            $this->response->html = Theme::RenderReturn('form_render');

            // Any JavaScript call backs should be set (you can use text_callback to set up a text editor should you need one)
            $this->response->callBack = 'flinksterFormSetup';

            $this->response->dialogTitle = __('CallABike');

            // You can have a bigger form
            //$this->response->dialogClass = 'modal-big';

            // The response object outputs the required JSON object to the browser
            // which is then processed by the CMS JavaScript library (xibo-cms.js).
            $this->response->AddButton(__('Cancel'), 'XiboDialogClose()');
            $this->response->AddButton(__('Save'), '$("#ModuleForm").submit()');

            // The response must be returned.
            return $this->response;
        }

        /**
         * Edit Media in the Database
         * @return
         */
        public function EditMedia()
        {
            $this->response = new ResponseManager();

            // Edit calls are the same as add calls, except you will to check the user has permissions to do the edit
            if (!$this->auth->edit)
            {
                $this->response->SetError('You do not have permission to edit this assignment.');
                $this->response->keepOpen = false;
                return $this->response;
            }

            // You can store any additional options for your module using the SetOption method
            $name = Kit::GetParam('name', _POST, _STRING);

            $this->duration = Kit::GetParam('duration', _POST, _INT, 0, false);
            $this->SetOption('updateInterval', Kit::GetParam('updateInterval', _POST, _INT, 60));

            $this->SetOption('name', $name);
            $this->SetOption('latitude', Kit::GetParam('latitude', _POST, _DOUBLE));
            $this->SetOption('longitude', Kit::GetParam('longitude', _POST, _DOUBLE));
            $this->SetOption('radius', Kit::GetParam('radius', _POST, _INT));
            $this->SetOption('providernetwork', Kit::GetParam('providernetwork', _POST, _INT));

            // You may also store raw XML/HTML using SetRaw. You should provide a containing node (in this example: <text>)
            // $this->SetRaw('<text><![CDATA[' . $text . ']]></text>');

            // Should have built the media object entirely by this time
            // This saves the Media Object to the Region
            $this->UpdateRegion();

            // Usually you will want to load the region options form again once you have added your module.
            // In some cases you will want to load the edit form for that module
            if ($this->showRegionOptions) {
                $this->response->callBack = 'refreshPreview("' . $this->regionid . '")';
                $this->response->loadForm = true;
                $this->response->loadFormUri = "index.php?p=timeline&layoutid=$this->layoutid&regionid=$this->regionid&q=RegionOptions";
            }
            //$this->response->loadFormUri = "index.php?p=module&mod=$this->type&q=Exec&method=EditForm&layoutid=$this->layoutid&regionid=$regionid&mediaid=$this->mediaid";

            return $this->response;
        }
    /**
     * GetResource
     *     Return the rendered resource to be used by the client (or a preview)
     *     for displaying this content.
     * @param integer $displayId If this comes from a real client, this will be the display id.
     */
    public function GetResource($displayId = 0)
    {
        // Behave exactly like the client.

        // A template is provided which contains a number of different libraries that might
        // be useful (jQuery, etc).
        // You can provide your own template, or just output the HTML directly in this method. It is up to you.
        //$template = file_get_contents('modules/preview/HtmlTemplateSimple.html');
        // Behave exactly like the client.

        $isPreview = (Kit::GetParam('preview', _REQUEST, _WORD, 'false') == 'true');

        // Load in the template
        $template = file_get_contents('modules/preview/HtmlTemplateForCAB.html');

        // Replace the View Port Width?
        if (isset($_GET['preview']))
            $template = str_replace('[[ViewPortWidth]]', $this->width, $template);

        // Get the text out of RAW
        $rawXml = new DOMDocument();
        $rawXml->loadXML($this->GetRaw());

        // Create the rquest url
        $request_url = 'https://api.deutschebahn.com/flinkster-api-ng/v1/bookingproposals'
            . '?'
            . 'lat=' . $this->GetOption('latitude')  . '&'
            . 'lon=' . $this->GetOption('longitude') . '&'
            . 'radius=' . $this->GetOption('radius') . '&'
            . 'providernetwork=' . $this->GetOption('providernetwork');

        // echo $request_url;
        // Work out the url (convert to ISO standart [see ISO]) ##not finished
        $url = urldecode($request_url);
        $url = (preg_match('/^' . preg_quote('http') . "/", $url)) ? $url : 'http://' . $url;

        // Create cURL object (handling API request)
        $cabCurl = new cabCurl();
        $result = $cabCurl->urlToCurl($url, $this->GetSetting('apiKey'));

        // Handling singular and prural
        function countItems($count, $network) {
            if ($network == 2) {
                if($count == 1) {
                    return 'Fahrrad';
                } else {
                    return 'Fahrräder';
                }
            } else if ($network == 1 || $network == 3) {
                if($count == 1) {
                    return 'Fahrzeug';
                } else {
                    return 'Fahrzeuge';
                }
            } else {
                return 'Dudideldy Donger Kackerino haha';
            }

        }

        // Generate body content
        $headline = '<h2>' . $result['size'] . ' ' . countItems($result['size'], $this->GetOption('providernetwork')) . ' in der Nähe.</h2>';

        function distance($lat1, $lon1, $lat2, $lon2) {

              $theta = abs($lon1 - $lon2);
              $dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) +  cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
              $dist = acos($dist);
              $dist = rad2deg($dist);
              $miles = $dist * 60 * 1.1515;
              $result = $miles * 1.609344;

              return floor(1000 * $result);
        }

        function selectClass($count) {
            if($count == 1) {
                return 'one';
            } else if($count == 2) {
                return 'two';
            } else if($count == 3) {
                return 'three';
            } else if($count == 4) {
                return 'four';
            } else if($count == 5) {
                return 'five';
            } else {
                return 'five';
            }
        }

        function groupItems($distances) {
            $group = array();
            $grpId = 0; $y = 0; $i = 0; $t = 0;

            while($i < sizeof($distances)) {
                if($distances[$i] < ($distances[$y] + 3)) {
                    $group[$grpId][$t][0] = $i;
                    $group[$grpId][$t][1] = $distances[$i];
                    $i++; $t++;
                } else {
                    $grpId++; $y = $i; $t = 0;
                }
            }
            return $group;
        }

        function findGroupCenter($group, $result) {
            $avgLat = 0; $avgLon = 0;
            $avgCenter = array();
            for($i = 0; $i < sizeof($group); $i++) {
                for($j = 0; $j < sizeof($group[$i]); $j++) {
                    $avgLat += $result['items'][$j]['position']['coordinates'][1];
                    $avgLon += $result['items'][$j]['position']['coordinates'][0];
                }
                $avgCenter[$i][0] = round($avgLat / sizeof($group[$i]), 20);
                $avgCenter[$i][1] = round($avgLon / sizeof($group[$i]), 20);
                $avgLat = 0;
                $avgLon = 0;
            }
            return $avgCenter;
        }

        $distance = array();

        // Computing the distance between the modules origin and all returned values
        for($i = 0; ($i < sizeof($result['items'])) /* && ($i < 5) */; $i++) {
            array_push($distance, distance($this->GetOption('latitude'), $this->GetOption('longitude'), $result['items'][$i]['position']['coordinates'][1], $result['items'][$i]['position']['coordinates'][0]));
        }

        array_multisort($distance);
        $grouped = groupItems($distance);
        $avgCenter = findGroupCenter($grouped, $result);

        $output = '
            <div id="map"></div>
        ';

        $groupedJS = json_encode($grouped);
        $resultJS = json_encode($result);

        // var_dump($grouped);

        $javascript = '<script>
            var map;

            var avgCenter = [];
            var grouped = [];
            var result = [];
            grouped = ' . $groupedJS . ';
            result = ' . $resultJS . ';
            var size = ' . sizeof($avgCenter) . ';

            var styleArray = [
                {
                    featureType: \'road\',
                    stylers: [
                        {color: \'#BDBDBD\'}
                    ]
                }, {
                    featureType: \'transit\',
                    stylers: [
                        {hue: \'#FE1414\'}
                    ]
                }, {
                    featureType: \'landscape\',
                    stylers: [
                        {hue: \'#EF9A9A\'}
                    ]
                }, {
                    featureType: \'poi\',
                    elementType: \'labels\',
                    stylers: [
                        {visibility: \'off\'}
                    ]
                }
            ];


            function initMap() {
                map = new google.maps.Map(document.getElementById(\'map\'), {
                    center: {lat: ' . $this->GetOption('latitude') . ', lng: ' . $this->GetOption('longitude') . '},
                    zoom: 17,
                    disableDefaultUI: true,
                    zoomControl: true,
                    styles: styleArray
                });

                for (var i = 0; i < size; i++) {
                    // Experimental
                    // var infoWindow = new google.maps.InfoWindow({
                    //     content: \'<div class="info"><img class="infoWindow" src="modules/3rdparty/qrcode.php?link=\' + result[\'items\'][grouped[i][0][0]][\'_links\'][0][\'href\'] + \'"/></div>\',
                    //     maxWidth: 120
                    // });
                    // infoWindow.setPosition({lat: result[\'items\'][grouped[i][0][0]][\'position\'][\'coordinates\'][1], lng: result[\'items\'][grouped[i][0][0]][\'position\'][\'coordinates\'][0]});
                    // infoWindow.open(map);
                    // Add the circle for this city to the map.

                    function calcOpacity(count) {
                        return (0.1 * count < 1) ? 0.1 * count : 1;
                    }
                    var opa = calcOpacity(grouped[i].length);

                    var bikeLocation = new google.maps.Circle({
                      strokeColor: \'#FF0000\',
                      strokeOpacity: 0.8,
                      strokeWeight: 2,
                      fillColor: \'#FF0000\',
                      fillOpacity: opa,
                      map: map,
                      center: {lat: result[\'items\'][grouped[i][0][0]][\'position\'][\'coordinates\'][1], lng: result[\'items\'][grouped[i][0][0]][\'position\'][\'coordinates\'][0]},
                      radius: 12
                    });
                }
            }
        </script>
        <script async defer src="https://maps.googleapis.com/maps/api/js?key=' . $this->GetSetting('googleKey') . '&callback=initMap"></script>';

        // Replace the Body Content with our generated text
        $template = str_replace('<!--[[[JAVASCRIPTCONTENT]]]-->', $javascript, $template);
        $template = str_replace('<!--[[[BODYCONTENT]]]-->', $output, $template);
        $template = str_replace('<!--[[[HEADLINE]]]-->', $headline, $template);

        // ##Tidy this shit up
        $headContent = '';
        $template = str_replace('<!--[[[HEADCONTENT]]]-->', $headContent, $template);

        // $javascript = '<script src="modules/theme/callabike/main.js"></script>';
        // $template = str_replace('<!--[[[JAVASCRIPTCONTENT]]]-->', $javascript, $template);

        // Do whatever it is you need to do to render your content.
        // Return that content.
        return $template;
    }

    public function HoverPreview()
    {
        // Default Hover window contains a thumbnail, media type and duration
        $output = parent::HoverPreview();

        // You can add anything you like to this, or completely replace it

        return $output;
    }

    public function IsValid() {
        // Using the information you have in your module calculate whether it is valid or not.
        // 0 = Invalid
        // 1 = Valid
        // 2 = Unknown
        return 1;
    }

    private function providernetworks()
    {
        return array(
                array('id' => '1', 'value' => 'Flinkster Netzwerk'),
                array('id' => '2', 'value' => 'CallABike Netzwerk'),
                array('id' => '3', 'value' => 'Car2Go Netzwerk'),
            );
    }

    private function parseLibraryReferences($isPreview, $content)
    {
        $parsedContent = $content;
        $matches = '';
        preg_match_all('/\[.*?\]/', $content, $matches);

        foreach ($matches[0] as $sub) {
            // Parse out the mediaId
            $mediaId = str_replace(']', '', str_replace('[', '', $sub));

            // Only proceed if the content is actually an ID
            if (!is_numeric($mediaId))
                continue;

            // Check that this mediaId exists and get some information about it
            $entry = Media::Entries(null, array('mediaId' => $mediaId));

            if (count($entry) <= 0)
                continue;

            // We have a valid mediaId to substitute
            $replace = ($isPreview) ? 'index.php?p=module&mod=image&q=Exec&method=GetResource&mediaid=' . $entry[0]->mediaId : $entry[0]->storedAs;

            // Substitute the replacement we have found (it might be '')
            $parsedContent = str_replace($sub, $replace, $parsedContent);
        }

        return $parsedContent;
    }

}
?>

I hope you can help me with this

You need to provide an empty settings array to “install modules” if you do not have any default settings:

$this->InstallModule($name, $description, $imageUri, $previewEnabled, $assignable, array());

Your EditForm method points to AddMedia - see

Theme::Set('form_action', 'index.php?p=module&mod=' . $this->type . '&q=Exec&method=AddMedia');

this should be

Theme::Set('form_action', 'index.php?p=module&mod=' . $this->type . '&q=Exec&method=EditMedia');

I’ve not checked beyond that point as I that will be your duplication issue

1 Like

Thanks A LOT @dan, I really appreciate your extensive support :slight_smile:
But now I get the error message:

There has been an application error.
You do not have permission to edit this assignment.

Under the permissions tab everyone is checked and my user is a super-admin.

That would suggest to me that your layoutid or regionid are not being passed in correctly and therefore the auth object is not being populated.

When the form is open, can you check the hidden input fields for those two parameters to see if they are populated, and if not trace backwards up the stack to see where they are missing?

Unfortunately I do not have time to do this for you, so you will need to do some debugging yourself - you can add Debug statements to your code to see what is happening, which might be useful?