Convert YAML data to CSV file and download it from the Panel

Hi,

I’m saving the data collected from a frontend form into a registrations.txt file as YAML by using the method described here (Save registrations as structure field).

The client wants to be able to check and edit the data gathered through the form, so I created a structure field in the Panel that shows the data from registrations.txt and rewrites the file if the client edits it through the Panel.

I would like the user to be able to get this file in CSV format (from the Panel), but I don’t know how to do it. The user can’t even download /content/registrations/registrations.txt because this file doesn’t get listed in the “Files” section.

Any ideas?

I’d probably create a widget that creates a new CSV file when a button is clicked and then offers it for download.

If you have never created a widgets, these examples might be a starting point: https://github.com/jenstornell/kirby-plugins/issues?q=is%3Aissue+is%3Aopen+label%3AWidget

Thank you @texnixe!

You guessed it, I have never created a widget, but I will give it a try.

Besides that, I will have to create a function to actually convert the YAML into CSV. I’m not a programmer, so it feels like my brain is overheating. Is there a premade function to do that in Kirby? If not, could you give me a clue?

Thank you in advance

Yep, creating the text file is just a few lines of code. Let’s assume you are on the current page and your field is called registrations:

<?php
$registrations = $page->registrations()->yaml();
foreach($registrations as $registration) {
  f::write(kirby()->roots()->assets(). '/test.csv',implode(';',$registration)."\n", true);
}

This would write a file called test.csv to the /assets folder.

Without actually creating a file, you can create a route that forces a download

c::set('routes', [
  [
    'pattern' => 'api/csv',
    'action' => function() {
      header::download(['mime'=>'application/csv', 'name' => 'registrations.csv']);
      $registrations = page('home')->registrations()->yaml();
      $f = fopen('php://output', 'w');
      foreach($registrations as $registration) {
          fputcsv($f, $registration, ';');
      }
      fpassthru($f);
      return false;
    }
  ]
]);

And then in your widget, you would just put a link that calls the route. And if all goes well, your user would end up with all registrations in his download folder.

Thank you @texnixe!

I’m trying to use f::write to write the file test.csv not to the /assets folder but to the content/registrations folder . That way the file will appear in the file section of the Registrations page in the Panel.

However, I don’t find the way to do that. This, for example, is not working:

f::write(kirby()->roots()->page('registrations'). '/test.csv',implode(';',$registration)."\n", true);

How should I write the path?

This should work:

f::write(kirby()->roots()->content() .  '/registrations/test.csv',implode(';',$registration)."\n", true);

Oh, of course, how dumb I am.

However, I now realize the whole thing doesn’t work as expected: each time the file test.csv gets rewritten, the new data doesn’t replace the existing one but it is added below. I don’t know why.

Apart from this, I wonder if there’s a way to detect if the file registrations.txt has been updated and if so automatically generate an updated version of test.csv. I have no idea if this is possible.

Yes, if you trigger an update event when the page is updated in your script (see below where to put that), you can then use a panel.page.update hook to react on that event.

// everything is ok, let's try to create a new registration
try {

  $p = $page->find('registrations');
  addToStructure($p, 'registrations', $data);
  kirby()->trigger('panel.page.update', $p);
  $success = 'Your registration was successful';

  $data = array();

} catch(Exception $e) {
  echo 'Your registration failed: ' . $e->getMessage();
}

As regards the duplicate content in the file, you could do one of two things:

  • read the content of the file and compare with what is already in the file, then only add what is new
  • easier: clear the file contents and then write to it

Or, you use my second suggestion with the route. No file, no worries :wink: