ridgedale

PW3 - Custom Upload Method for Data Archive

Recommended Posts

Reference: PW 3.0.62 and uikit3 based site using the Regular-Master profile.

I was wondering if it is possible to create an upload method that is not directly associated with any particular Processwire page so that a custom folder can be used from within the admin frontend to upload for example newsletters so they are stored in one hierarchical location to allow the latest edition to be automatically displayed using a single code call.

What I'd like to be able to do is provide an upload method that allows a user to create year folders and upload newsletters to the relevant folders, making sure the uploaded files are named as required.

Any input would be greatly appreciated.

Share this post


Link to post
Share on other sites

Hi,

As far as your webserver has write permission on a given folder outside the web directory, you can write your logic in a module which handle your upload thing - everything is possible inside ProcessWire.

1 hour ago, ridgedale said:

What I'd like to be able to do is provide an upload method that allows a user to create year folders and upload newsletters to the relevant folders, making sure the uploaded files are named as required.

I personnaly would go with pages. Make a parent page "newsletters-upload" in your page tree then create a child page for each "year", then create a child page inside "year" for each uploaded newsletter. Each newsletter page have a fieldtype-secure-file field which give you the possibility to store your uploaded files outside the webfolder. 

In the process module, the author could create/select a "year" page (folder) and upload file by drag&drop in the admin.  You can also pull files from an FTP server where authors create their folder tree, then do the logic in the module.

 

This is just an idea from what I understand, more details needed  😁

  • Like 2

Share this post


Link to post
Share on other sites
2 hours ago, ridgedale said:

What I'd like to be able to do is provide an upload method that allows a user to create year folders and upload newsletters to the relevant folders, making sure the uploaded files are named as required.

PW does not really use the concept of folders. I agree with @flydev - you should use pages in place of folders.

I've used a repeater for doing something quite similar to what I think you are describing. Create a page that will be the central place where all files are uploaded. Add a repeater to that page's template, containing an integer field for year and a files field to hold the uploads. Users will upload their files to the relevant year item. Using API code you can get the most recent year (i.e. the highest integer) and the most recently added file in that year (the last file in the field).

For automatic upload renaming there is @adrian's Custom Upload Names module.

  • Like 3

Share this post


Link to post
Share on other sites
Posted (edited)

I am doing something very similar to this and am just using pages.

I use two templates:  'newsletters_parent' and 'newsletters_child'.

The 'newsletters_parent' is the container and onyl has a title, but its settings on the "Family" tab are:

  • Can have children: YES
  • Can be used for new pages: ONE
  • Allowed template(s) for children: newsletter_child
  • Name format for children: Y/m/d H:i:s  (the page creation date/time -- this will get overwritten at a later stage)
  • Sort settings for children:  pub_date (a field that will be added to the newsletter_child page)
  • Reverse sort direction: CHECKED (yes)

The 'newsletters_child' template has the following fields:

  • title (default and required by all templates)
  • pub_date (a datepicker field  set to YYYY-MM-DD  -- the publication date might not necessarily be the same as the date the file was uploaded!)
  • pdf_file (file-upload field, single file only, and also set to only allow PDF files)

The settings ("Family" tab) for the child template are:

  • Can have children: NO
  • Can be used for new pages: YES
  • Allowed template(s) for parents: newsletter_parent
  • Show in the add-page shortcut menu: YES

To make the process of adding a new newsletter as easy as possible for users -- all they have to do is select the file to upload and the pick the pub date -- I use a couple of hooks hook in the site's ready.php file:

The first hook adds dummy text to the page title field on page creation so that the user doesn't have to enter anything (if it is left blank, it will throw an error) -- the proper title, along with the page name will be created from the pub_date field when the page is saved.

The second hook also creates the actual page title and the sets page name based on the supplied 'pub_date'. [NOTE: the newsletters only get published once a month, so we only use the Year/Month in page names/titles.]

/**
 * 1. Set a dummy title on page save at creation, to prevent the required field showing an error if it is blank.
 *    (Requires the parent template option 'Name format for children' to be set)
 * 2. Modify newsletter title and page name from pub_date before saving.
 */
$pages->addHookAfter('saveReady', function(HookEvent $event) {
	$page = $event->arguments(0);

	// Only on newsletter_child templates where the title is blank (on page creation)
	if ($page->template->name == 'newsletter_child' && $page->title == '') {
		$page->title = 'Newsletter'; // a dummy title, will be replaced at a later stage
	}

	// Only on newsletter_child templates with a pub_date
	if ($page->template->name == 'newsletter_child' && $page->pub_date) {
		$newName = wire('sanitizer')->pageName('newsletter-' . date('Y-M', $page->pub_date), true);
		$newTitle = date('F Y', $page->pub_date);
		if ($page->title != $newTitle) {
			$page->title = $newTitle;
		}
		if ($page->name != $newName) {
			$page->name = $newName;
		}
	}
});

 

Thiis then makes it easy to search the system for whatever you want:

// All newsletter files (no limit applied)
$allNews = $pages->find('template=newsletter_child, sort=pub_date'); // ascending order (earliest first)
$allNews = $pages->find('template=newsletter_child, sort=-pub_date'); // descending order (most recent first)

// All newsletters from a particular year (e.g., 2017):
$yearStart = strtotime('2017-01-01 00:00:00');
$yearEnd = strtotime('2018-01-01 00:00:00');
$yearNews = $pages->find("template=newsletter_child, pub_date>={$yearStart}, pub_date<{$yearEnd}, sort=-pub_date");

// The most recent newsletter:
$mostRecent = $pages->findOne('template=newsletter_child, sort=-pub_date');
 
// Accessing the file itself (using $mostRecent)
if ($mostRecent->pdf_file) {
	$file = $mostRecent->pdf_file;
	$downloadLink = "<a href='{$file->url}'>Download the newsletter: {$mostRecent->title}</a> ({$file->filesizeStr})";
}
  

 

That's all there is, really.  There is lots you can do with the system.

 

Edited by LMD
Realised I had two hooks, when I only needed one!
  • Like 6

Share this post


Link to post
Share on other sites

Thank you for all your feedback, flydev, Robin S and LMD.

I should have clarified that I have already setup a Parent page for the newsletters and the Children pages to each hold 10-years' worth of monthly newsletters - the newsletters go back to 1975 (the ones I've scanned and have yet to process) and possibly well beyond. See the screen grabs attached.

The thinking behind a single folder hierarchy for the newsletter store is that, firstly, it would allow straightforward portability should the archive ever need to be moved, and, secondly, to facilitate the automatic display of the current newsletter based upon today's yyyy-mm date, provided it has been uploaded, or otherwise display the previous month's newsletter.

The problem I foresee with storing the files as media attached to the Pages is that the paths to the files will not be predictable making the automatic displaying of the latest newsletter in the sidebar less than straightforward, if not impossible, and the newsletters will end up being scattered among many different folders meaning the portability of the archive would be less than ideal.

Any further thoughts or suggestions would be welcome. Thanks again.

ParentPage.jpg

ArchivePeriod-Child-Page.jpg

Share this post


Link to post
Share on other sites

@ridgedale, the essence of your scenario is that you have some newsletters, and for each newsletter you have:

1. A file
2. Some metadata about that file (its month and its year)

You want to associate the metadata with the file. Using a file naming scheme and a folder structure is one way to associate the metadata with the files, but it's not the only way, nor is it the "ProcessWire way".

The ProcessWire way is to use pages and/or the metadata fields that are built into File fields.

If you have a lot of metadata to associate with a file then the most powerful approach is the "one page per file" approach, which is what @LMD is suggesting. This way you can use all the available PW fields in your file page to store different kinds of metadata about each file.

But the downside to this approach is that the UI/workflow can be a bit less than ideal for clients, especially if they have a lot of files that they want to upload at once and populate metadata for. That's because, generally speaking, they will have to edit each file page individually (although there can be solutions to this such as Lister Pro inline editing, Batch Child Editor, and automatically creating a page for each file similar to what is done in AutoImagePages).

But you only have a little bit of metadata per file so I don't think you need to go this sophisticated. Instead I suggest you use a repeater for the years, and file tags for the months. See these screenshots...

2018-03-22_182925.png.e111ad104c27877a2d7609bd74ccaeaa.png2018-03-22_182955.png.fda7a4b1da7054834f1a22da6e61fdfc.png

2018-03-22_184541.thumb.png.9ff762f2829592d6a5bf2ca1df06d49f.png

Then you can get the latest newsletter like this...

$newsletters_page = $pages->get("template=newsletters");
$latest_year = $newsletters_page->newsletters->find("newsletter_files.count>0, sort=-year")->first();
$latest_newsletter = $latest_year->newsletter_files->sort("-tags")->first();
$month = substr($latest_newsletter->tags, 3);
echo "<a href='$latest_newsletter->url'>$month $latest_year->year</a>";

 

13 hours ago, ridgedale said:

I should have clarified that I have already setup a Parent page for the newsletters and the Children pages to each hold 10-years' worth of monthly newsletters

The pages that visitors browse the newsletters through can be completely decoupled from the storage of the newsletter files. You just get the relevant repeater items by year according to the page being viewed, e.g.

$years = $newsletters_page->newsletters->find("year>=1980, year<1990, sort=year");

You could even do it without actual pages for each decade, and use URL segments to get any range of years.

  • Like 6

Share this post


Link to post
Share on other sites

Hi Robin S,

Your suggestion looks like it should fit the bill. Is it possible to reverse sort the repeater fields so the most recent year appears at the top of the list?

Thanks again for all your input.

Share this post


Link to post
Share on other sites
5 hours ago, ridgedale said:

Is it possible to reverse sort the repeater fields

from @Robin S


$this->addHookAfter('FieldtypeRepeater::wakeupValue', function($event) {
    $field = $event->arguments('field');
    if($field->name !== 'myrepeater') return; // repeater field name
    $pa = $event->return;
    $pa->sort("-created"); // or whatever sort you want
    $event->return = $pa;
});

 

Will return :

  • 2018
  • 2017
  • 2016
  • ...
  • Like 3

Share this post


Link to post
Share on other sites
9 hours ago, ridgedale said:

Is it possible to reverse sort the repeater fields so the most recent year appears at the top of the list?

Another approach besides the one @flydev mentioned:

$pages->addHookAfter('saveReady', function(HookEvent $event) {
    $page = $event->arguments(0);
    if($page->template != 'newsletters') return; // Only for specific template
    $page->newsletters->sort('-year'); // Sort the repeater how you want
    // Sequentially set the sort property of the repeater items
    $i = 0;
    foreach($page->newsletters as $item) {
        $item->sort = $i;
        $i++;
    }
});

 

  • Like 2

Share this post


Link to post
Share on other sites

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

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By ridgedale
      Reference: PW 3.0.111 and uikit3 based site using the Regular-Master profile.
      I wonder if anyone might be able to point me in the right direction. I need to restrict the superuser role to overall administrators of a group of sites, but provide role and permission administration for the administrators of the individual sites. My searches unearthed the following thread:
      However, after having already created the sitemanager role and given site administrators the user-admin permission and having then created the role-admin permission and assigned that to the sitemanager role, the users with sitemanager permissions are able to see the Roles item under the Access menu of the backend but no submenu is displayed showing the Add Role option or any of the roles that the administrator should have access to. My intention is that the individual site adminstrators should have access to assign the guest and sitemanager roles (but not edit them) and be able to create roles with privileges beneath that of sitemanager.
      Any advice would be greatly appreciated.
       
    • By ridgedale
      Reference: PW 3.0.62 and uikit3 based site using the Regular-Master profile.
      I have a table that needs some of its content to be hidden. I've tried applying the following classes and styles to <tr>, <td> and <a> elements all without success:
      class="hidden" class="uk-hidden" class="uk-invisible" style="display:none" style="visibility:none" style="visibility:collapse" <-- only applicable to rows in this case Is there any way to allow a user to hide content? Any assistance would be appreciated.
    • By ridgedale
      Reference: PW 3.0.62 and uikit3 based site using the Regular-Master profile.
      The issue is that blog posts for the particular site in question are automatically formatted as /blog/<blog-post-title>. It is evident the path format should have been configured as /blog/<year>/<month>/<blog-post-title>.
      Please could someone advise whether or not it is possible to change the path format for the blog posts of an existing active site that uses the regular profile?
      Any assitance would be greatly appreciated.
    • By ridgedale
      Reference: PW 3.0.62 and uikit3 based site using the Regular-Master profile.
      I've setup a hidden page and template (sitePreferences - with no associated template file) containing site-wide preferences/settings, however I can't get the field content to display.
      I've tried the following which either display nothing or break the site:
      <?php echo $item->siteChairperson; ?> <-- nothing gets displayed <?php echo $field->siteChairperson; ?> <-- nothing gets displayed <?php =page()->siteChairperson ?> <-- breaks site <?php echo $field = $fields->get('siteChairperson'); ?> <-- displays the text siteChairperson instead of the name. <?php =sitePreferences()->siteChairperson?> <-- breaks site <?php echo $field->get($siteChairperson); ?> <-- breaks site Any assistance would be greatly appreciated.
    • By ridgedale
      Reference: PW 3.0.62 and uikit3 based site using the Regular-Master profile.
      I wonder if anyone has come across the problem of displaying images within the <figure> element before.
      When editing a page:
      If an image is added inside <p></p> tags without any align assigned, it displays normally at the size the user has specified. See screen grab example 1.
      If an image is placed inside the <figure> element and left or right alignment applied, the size the image is displayed as in the editing view is significantly reduced. See screen grab example 2.
      If an image is placed inside the <figure> element and center alignment applied, the size the image completely disappears in the editing view with only the caption text showing. See screen grab example 3.
      The code used to display an image in <p></p> tags without aligment assigned is standard:
      <p><img alt="" src="/<path-to-image>/image.jpg" width="128" /></p> Examples of the code automatically being generated by PW3/UiKit3 from within the image editing interface are:
      <figure class="align_left"><img alt="" src="/<path-to-image>/image.jpg>" width="128" /> <figcaption>Caption text</figcaption> </figure> <figure class="align_right"><img alt="" src="/<path-to-image>/image.jpg>" width="128" /> <figcaption>Caption text</figcaption> </figure> <figure class="align_center"><img alt="" src="/<path-to-image>/image.jpg>" width="128" /> <figcaption>Caption text</figcaption> </figure> Important Note: The images and and captions actually do display correctly when the website is browsed, but from the editing perspective it is not practical.
      Any thoughts or advice would be appreciated.