Jump to content

Handle a file upload from an InputField module


Aaron Ahearne
 Share

Recommended Posts

Hi guys,

I have a requirement for a new input field for Form Builder. Essentially I just need to have some radio buttons which use an image as their label.

I have managed to create the module, have it install automatically and create a new form with my custom input type but I am having trouble handling the image which is being uploaded as the label.

The value for imageLabel is always an empty array. I have looked all around the forums and I Google but I cant find the answer close to what Im looking for. Do I handle the file upload manually and extract/add it to my imageField during POST and GET? I've tried multiple different methods and been through most of the API too but can't figure out the correct way to do this.

<?php

class InputfieldImageRadios extends InputfieldRadios
{
  const CONFIGNAME = "ImageRadios";

  public static function getModuleInfo()
  {
    return array(
      'title' => __('Radio Buttons with Image Label', __FILE__),
      'summary' => __('Radio buttons for selection of a single item with an extra image label', __FILE__),
      'version' => 1,
      'singular' => true,
      'autoload' => true,
      'requires' => array('InputfieldRadios', "FormBuilder")
    );
  }

  public function init()
  {
    parent::init();
  }

  public function ___getConfigInputFields()
  {
    $inputFields = parent::___getConfigInputfields();

    $imageField = wire('modules')->get('InputfieldFile');
    $imageField->label = $this->_('Image Label');
    $imageField->description = $this->_('If you want the label to be displayed as an image.');
    $imageField->extensions = 'jpg jpeg png gif';
    $imageField->maxFiles = 1;
    $imageField->maxFileSize = 200000;
    $imageField->overwrite = false;
    $imageField->destinationPath = wire("config")->paths->files . "radioLabels/";
    $imageField->name = "imageLabel";
    $imageField->type = 'file';
    $imageField->value = array();

    $inputFields->add($imageField);
    return $inputFields;
  }

  public function set($key, $value) {
    if($key == 'imageLabel') {
      $test = 1;
    }
    return parent::set($key, $value);
  }

  /**
   * Install the module append to Form Builder config
   *
   */
  public function ___install()
  {
    $formBuilderClass = "FormBuilder";
    $this->verifyDependencies([$formBuilderClass]);
    $formBuilderConfig = wire("modules")->getModuleConfigData($formBuilderClass);

    if (in_array(self::CONFIGNAME, $formBuilderConfig['inputfieldClasses'])) {
      return;
    }

    $formBuilderConfig['inputfieldClasses'][] = self::CONFIGNAME;
    wire("modules")->saveModuleConfigData($formBuilderClass, $formBuilderConfig);

  }

  protected function verifyDependencies($classes)
  {
    foreach ($classes as $class) {
      if (!wire('modules')->isInstalled($class)) {
        $this->error(self::CONFIGNAME . " dependency not installed: $class");
      }
    }
  }

  /**
   * Uninstall the module and remove from Form Builder config
   *
   */
  public function ___uninstall()
  {
    $formBuilderClass = "FormBuilder";
    $this->verifyDependencies([$formBuilderClass]);
    $formBuilderConfig = wire("modules")->getModuleConfigData($formBuilderClass);

    foreach ($formBuilderConfig['inputfieldClasses'] as $key => $value) {
      if ($value == self::CONFIGNAME) {
        unset($formBuilderConfig['inputfieldClasses'][$key]);
      }
    }

    wire("modules")->saveModuleConfigData($formBuilderClass, $formBuilderConfig);
  }
}
Link to comment
Share on other sites

You'd need to do it manually e.g. as part of the module or a tad more sophisticated. The InputfieldFile does actually handle only the "input" stuff, e.g. everything till you hit submit on the form (or ajax). Everything after that is handled by FieldtypeFile, which saves the file for a specific page. That's the reason why FormBuilder does have it's own File field, as there's neigher a FieldtypeFile to be used nor a page to save images to.

With that being said, have a look at Ryan's reply here about how to handle file uploads even without the Fieldtype: https://processwire.com/talk/topic/11314-inputfieldfile-in-forms-its-just-not-working/?hl=file#entry106503

Link to comment
Share on other sites

Great thanks, thats all I needed to know. 

I kept getting the error: Call to a member function add() on a non-object (line 373 of /html/wire/modules/Inputfield/InputfieldFile/InputfieldFile.module). I suppose thats because it is expecting an entry in the image table, but it hasnt created one as it has no page to reference. That makes a bit more sense now.

Now that Im using the correct Input File module it should be more straight forward. I'll try to set up some hooks to perform after it processes the file and I'll post my solution here once its done.

Link to comment
Share on other sites

Just to prevent misunderstandings. You're already using the "correct" InputfieldFile as the FormBuilder custom one is even more specific. Also the add() method didn't fail because of a missing table, but just because of the missing page. The page itself doesn't necessarily need an file field, as Ryan also stated, but mostly it's id is needed to determine the folder to put the files into.

  • Like 1
Link to comment
Share on other sites

Nothing I have tried seems to be working. I cannot use hooks as it will hook every time InputFieldFile is run... unless there is a way to set the hook only when my module is run initialized (setting them in init caused them to run regardless of whether the module was initialized)

I just cannot seem to get a value into InputFieldFile. It is always blank. 

wire('input')->post

does not seem to contain any files at all. Even though a file is uploaded to the server.

wire('input')->post = {WireInputData} [2]
 stripSlashes = false
 data = {array} [16]
  field_name = "aaa"
  field_type = "ImageRadios"
  field_label = "aaa"
  description = ""
  notes = ""
  collapsed = "0"
  showIf = ""
  columnWidth = "100%"
  requiredIf = ""
  defaultValue = ""
  options = "Option 1\r\nOption 2\r\nOption 3"
  optionColumns = "0"
  _form_id = "4"
  _field_name = "aaa"
  _submit_save_field = "Save"
  TOKEN1788870938X1447321212 = "NmaT7RliGuwEUVaGABrADx/YBdZTiKVf"
Link to comment
Share on other sites

You can use hooks by creating a custom property in the inputfield object and in the hook just check for that property. You're writing you cannot set a value so I would conclude that the upload does already work. Am I right that way? For setting the value to the inputfield make sure the value is a page pagefiles object or at least an array of pagefile objects.

Link to comment
Share on other sites

I tried manually setting the value of InputFieldFile using a beforeHook on ___processInputAddFile and although it was showing as an array of pagefiles, I was still receiving errors saying that it was attempting to call first() a non-object.

Strangely it is showing literally Array[Pagefiles], rather than an instance of WireArray or just a root of page files. Obviously a normal array does not have a first() function, but by creating it manually it should definitely have access to the first() function defined in WireArray.

$this->value = new Pagefiles(wire("pages")->get("/offers"));

This is not the actual name of my page btw, as Im using the FormBuilder page, so Im just using this page to force something into the db.

Link to comment
Share on other sites

To wrap up this subject, I wanted to post some of the code required to handle the file upload. Please let me know if you see any glaring mistakes.

I added several hooks to modify the data going into and coming out of ProcessInput for InputfieldFile, that way I managed to grab the file name and store it as part of my form input config in the forms table. Then, when retrieving the data, I had the final part of the file path stored so I could access the image. Unfortunately this does mean that I will need to implement the delete and description functionality of the file uploader myself.

  public function init() {
    parent::init();
    $this->set("image", '');
    $this->addHookBefore("InputfieldFile::processInput", $this, "beforeProcessInput");
    $this->addHookAfter("InputfieldFile::processInput", $this, "afterProcessInput");
    $this->addHookAfter("InputfieldFile::processInputAddFile", $this, "afterProcessInputAddFile");
  }
  /**
   * Modify the value property of the InputFieldFile. This sets the form admin page as the value so as to
   * bypass an error when saving a new image.
   * @param HookEvent $event
   */
  public function beforeProcessInput(HookEvent $event) {
    if (!$this->isImageLabelEvent($event)) {
      return;
    }
    $event->object->removeAttr("value"); // sanitise the value field
    $event->object->set("value", new Pagefiles($this->wire('page')));
  }
  /**
   * Save the form image to its own directory and save the image path as part of the input field config
   * @param HookEvent $event
   */
  public function afterProcessInput(HookEvent $event) {
    if (!$this->isImageLabelEvent($event)) {
      return;
    }

    $uploadedFiles = $event->object->getWireUpload()->getCompletedFilenames();

    if (empty($uploadedFiles)) {
      return;
    }

    $formId = wire("input")->get("id");
    $form = wire("forms")->get($formId);
    $imageLabelFieldName = wire("input")->post("field_name");
    $imageRadioField = $form->get("children")[$imageLabelFieldName];
    $imageFolder = self::DIR_PREFIX . $formId;

    if ($imageRadioField) {
      if (!file_exists($imageFolder)) {
        mkdir($imageFolder);
      }
      $imageRadioField->set("image", $imageFolder . "/" . $uploadedFiles[0]);
      $form->save();
    }
  }
  /**
   * Delete the current image once a new one is uploaded
   * @param HookEvent $event
   */
  public function afterProcessInputAddFile(HookEvent $event) {
    if (!$this->isImageLabelEvent($event)) {
      return;
    }

    $uploadedFiles = $event->object->getWireUpload()->getCompletedFilenames();

    if (empty($uploadedFiles)) {
      return;
    }

    $imagePath = $this->getCurrentImagePath();

    if ($imagePath) {
      unlink($imagePath);
    }
  }
 
 /**
   * Create the file input to allow users to override the default series image
   * @return mixed
   */
  private function createInputFieldFile() {
    $formId = wire("input")->get("id");
    $inputFieldFile = wire('modules')->get('InputfieldFile');
    $inputFieldFile->label = $this->_('Image Label');
    $inputFieldFile->description = $this->_('Override the series default image.');
    $inputFieldFile->extensions = 'jpg jpeg png gif';
    $inputFieldFile->maxFiles = 1;
    $inputFieldFile->maxFileSize = 200000;
    $inputFieldFile->overwrite = false;
    $inputFieldFile->destinationPath = $this->getFormImageFilePath($formId);
    $inputFieldFile->attr('name', self::IMAGE_LABEL_NAME);
    $inputFieldFile->type = "file";

    if ($this->getCurrentImagePath()) {
      $pageFiles = new Pagefiles($this->wire('page'));
      $pageFile = new Pagefile($pageFiles, $this->getCurrentImagePath());
      $pageFiles->add($pageFile);

      $inputFieldFile->set('value', $pageFiles);
    }

    return $inputFieldFile;
  }
Link to comment
Share on other sites

  • 11 months later...

Hello, 

 

(sorry for my english! :) )

I created a custom file field input (InputfieldFile) on my custom XML importer process module, but when I select a file my field do not show information about the selected file before upload.

InputfieldFile code:

$file = $this->modules->get("InputfieldFile");
$file->name = 'upload_xml_file';
$file->id = 'field_upload_xml_file';
$file->inputfieldClass = 'InputfieldFile';
$file->label = $this->_('Upload XML File');
$file->extensions = 'xml natfis';
$file->destinationPath = $this->destinationPath;
$file->maxFiles = 1;
$file->required = 1;
$file->type = "file";
$file->icon = 'fa-upload';
$file->attr('onchange', 'return validateFileExtension(this)'); // validate filename
$fieldset->add($file);

Actually (custom inputfieldFile):

custom-inputfieldfile-no-information.png

 

What I want (field file created on fields administration):

ui-inputfieldfile-with-information.png

 

Thank you very much!

Link to comment
Share on other sites

  • 2 weeks later...

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...