Multiple upload alone (for Drupal 6.x) allows to upload a single archive (zip) file and save the contained files into Drupal as node attachments (standard Upload).

When I wrote the first draft of it I haven't found any module for drupal 6.x to do this task but using proprietary plugins where single files were uploaded one after another. This wasn't the solution I was looking for since I have to give this module to unprivileged users to upload many photos and documents (from 30 to 70+ attachments per node) at once without caring of plugins and browser issues.

This is the refactored version of the module I'm using:
http://drupal.org/sandbox/chirale/1192546

Happy coding.

CommentFileSizeAuthor
#2 preview.jpg16.33 KBchirale

Comments

andyf’s picture

Status: Needs review » Needs work

Hi,

I've taken a look through your module. First of all I installed it, and it worked as advertised! I have a few comments below.

  • I think it would be neater to offer this functionality directly on the node edit form (use a hook_form_alter() to modify the upload element), rather than as a new local task. Is there a particular difficulty in that approach?
  • If you do use a new local task, I think convention expects the task name to be a short command, eg. upload zip
  • You don't use Doxygen style headers to describe functions. Some custom functions don't describe what they do, or their preconditions (eg. _multiuploadalone_writefile() references arg(1) as the nid so can only be called from a node page). Also instead of cfr. you can use @see.
  • I don't think you should have the version number in the .info file.
  • hook_menu
    • The access callback checks for edit access, but not for upload access. An unauthorized user can attach files to nodes in this way.
    • If the page is just a form, it's typical to use a page callback of drupal_get_form() and pass the name of the form as an argument.
    • You could move pretty much all of the module into a separate file (to lower memory use when on shared hosts) by specifying the new file name in the 'file' key of the menu item.
  • In multiuploadmodule.module on line 81 you start renaming potentially executable files, to help prevent exploits. It seems to me (I'm no expert) that as you've already munged the filename, you should just ensure that the extension (if there is on) matches one of the members of the $extensions array.
  • $tmp_filepath on line 200 seems unnecessary, as do _multiuploadalone_upload_page_init() and _multiuploadalone_upload_content (but maybe they're for something you're planning on adding?).
  • I see you've already tried to use a PHP zip library - I think that would be a nice thing to add if you can.
  • The node edit form
    • I personally find the function of the multiupload button a bit counter-intuitive - it actually attempts to save the node, which is an action that I really want to know when I trigger.
    • If you have JS enabled you will end up with an unpublished node, if not you might not. The JS behavior can be mirrored server-side by displaying a confirmation form if the user submits the form via the multiupload button and they have the node set to published.
    • There's no warning about the JS unpublishing, and I'd be a bit surprised as a user if I specifically set the node to published, and then when I saved it was unpublished.
    • In the JS you use $(document).ready(function(); it's preferred to use Drupal behaviors.
    • The JS for changing the button color can be achieved via CSS I believe.
chirale’s picture

StatusFileSize
new16.33 KB

Thanks for your comments, AndyF! In the last commit:

  • Module file slim down to 66 lines of code: I've created an inc file loaded only when needed as you suggest
  • Local task removed in favor of form_alter on node edit
  • Now $file->list from default upload setting is used
  • No more javascript (you're too polite to say that, but I know it was a crock)
  • Upload access control added on form alter (removed previous access control: since one can see the node edit form, the edit access control is implicit, right?)
  • Bogus php mode added (future use)
  • Some doxygen headers added
  • Some code clean up

I've tested both for node edit (update) and creation (insert) and seems to work fine.

Some unsolved/future issues:

  • Uploading zip file on the upload element and then do a Preview cause file to disappear. It could be counter-intuitive.
  • About file munge, I've adapted the code from upload.module to mimic core file upload and it seems to do his work well. Is a reworking necessary? (It's primarily a question to myself, I think that D6 require too much work for this task, or I've not understand well the upload mechanism)
  • Use PHP to uncompress zip. How to do that smoothly preserving all files within directories (junk paths, unzip -j)?

Feel free to review the new version. Thanks!

chirale’s picture

Status: Needs work » Needs review
andyf’s picture

Status: Needs review » Needs work

Hi chirale,

Sorry for the delay. I've taken a look through the latest, and the code and functionality are much nicer to my mind at least! I've put some notes below, but most of these are really quite minor I think.

    multiuploadalone.module

  1. l17: you can omit the final argument, as it's assumed to be the same as the module name if not given. Also, this include could be made optional (only if there's something in the upload element). Not sure that'll actually make a noticeable difference tho :)
  2. l19: you forgot to call t(). I also wonder if it might not be an idea to record this using watchdog(). Oh wait, looking closer it seems that it's not possible for this to ever error currently!!
  3. l42: there's another missed t().
  4. l43: the upload element's set as required. I don't understand why this is, and it doesn't seem to work for me anyway! (The element's marked as required, but I can still save without a file.) Maybe I'm missing something :)
  5. l46: I think it would be cooler to pull in the same limits that upload.module uses. The only way I can see to do that is using _upload_file_limits().

    multiuploadalone.inc

  6. l33: I think it's better to use drupal_install_mkdir(). (The name does make you think it's only for install time, but the code seems generic.)
  7. l28-41: I find it very confusing to have an argument called $mode, give it a default value, then overwrite it (l33), then do a switch on it as if it hadn't been overwritten (l34), then repeat the code from the switch (l50-51) because the switch didn't do what you'd first expect!
  8. l36: I'd indent this to make it easier to understand.
  9. l51: It's a good idea to do a function_exists('exec') and respond gracefully if not, as exec() can be disabled for security reasons. You can also check for this in hook_requirements(), but I don't think that should replace the inline check, as someone might enable the module on a host with support and then migrate it to a different one.
  10. General

  11. I think the way the multiupload element works now is much more nicely integrated and fine for a live project. As a future direction I think it would be neater still if you could integrate with the existing upload element. You could add a checkbox to select if it's to be extracted or not (in case someone wanted to upload a zip and keep it that way).
  12. In _multiuploadalone_upload I personally find it weird to return TRUE on error, but maybe that's just me :)
  13. There's still one or two places that aren't fully complying with the coding standards. I'm not sure how important it's considered to cross all of these Ts, but I'll point it out just for completeness!
    1. Some of the functions and hooks don't have Doxygen style headers.
    2. There's a few lines that go over 80 characters and aren't declarations/definitions.
  14. It might be an idea to mention that files starting with a dot will be ignored (maybe).
  15. Given you're thinking of maybe expanding to support other archive types, perhaps multiuploadalone_upload_zip_validate() could be renamed to something a bit more generic? Following on from that, and just as a suggestion, if you know now that you might be adding support for other archive types, you can code with that in mind: currently where you have the extension zip and mime type application/zip hardcoded, they could be replaced by a function call that returns an array. This might make it easier to modify later.

HTH

Andy

chirale’s picture

Status: Needs work » Needs review

multiuploadalone.module

5) By defaults upload_element module has a fallback to default upload limits if file_validate_size is not overridden. I comment it but limits displayed on description are ignored. Apparently it is an Upload Element issue. Anyway, file limits are still used for single files contained within the archive.

multiupload.inc

6) I try to use that function, but I have to include install.inc and I cannot get to work smoothly like mkdir. Added a Todo on comments about this issue.
7) It was simply a variable name error. Corrected.
9) That logic is on multiuploadalone_requirements() on multiuploadalone.install. There, both exec and unzip command are checked (I've added few new lines). I've added phase runtime along install phase to alert the administrator on error. I've also added a check on user level that use the same function to avoid control statements duplications. Now the form element is not rendered if requirements are not met, and a message for the administrator is returned on Status Report page.

General

11) Now it returns TRUE on success, FALSE on error.
13) Comment added on upload description.
14) Renamed to multiuploadalone_upload_archive_validate().

Any other issue of this list has been fixed.

andyf’s picture

Status: Needs review » Needs work

Hi,

Sorry for the delay in getting back to you.

I've put some notes below, and indicated the issues I think are blockers by asterisks*.

I try to use that function, but I have to include install.inc

Ah sorry I didn't realise that: I think you definitely don't want to use it - it's not worth including a 22KB file just for that imho.

multiuploadalone.module

  1. * The example of using #file_validators from upload_element's README.txt is this:
    '#file_validators' => array(
      'file_validate_size' => array(16384),
      'file_validate_extensions' => array('txt gif patch diff jpg jpeg'),
    ),

    So on line 63 I think you should have 'file_validate_extensions' => array(implode(' ', $settings['allowed_extensions'])),. Similarly, $allowed_extensions on line 124 should be implode(', ', $settings['allowed_extensions']) (or something similar). Currently it only displays the first element of the array.

  2. *

    I've added phase runtime along install phase to alert the administrator on error.

    That's great. However I don't think it's very nice to manually call hook_requirements() (line 39) on the node edit form:

    • You're calling exec() on every nodeform build, which is not good from a performance point of view (it wouldn't scale well) as I understand.
    • The docs say the hook is for the status page, so developers might expect to be able to add more heavy code to that function, without realising it's actually also called on every nodeform build.
    • You're including the install file, which might grow (eg. often hook_schema() is large).

    Personally I think it's best to keep hook_requirements absolutely the same, and just use a simple function_exists('exec') for the nodeform check.

  3. I think it's worth wrapping lines 19-22 in if (user_access('upload files')) as that way you don't unnecessarily load the include file for users who won't need it (lower memory usage).

multiupload.inc

  1. * I think you want to delete l53-54 (currently the command's executed twice).
  2. * l59: you've commented out the rmdir - to me that's necessary. Is there a particular problem you were having with it? As it is, I think you'll end up with loads of directories in the tmp dir.
  3. The following's a bit vague to me.

    Some files could be renamed or ignored on upload

  4. If you enable the module via Drush, you get the requirements message repeated twice. I presume that Drush first calls the install check and then the runtime check. You could use static variable to keep track of whether it's done a check to avoid this. (Obviously this is really really minor!)

HTH

chirale’s picture

No problem, I was abroad and offline so I read your suggestions in the last few days.

multiuploadalone.module

  • 1) Done.
  • 2) Right. Done.
  • 3) Done.

    multiupload.inc

    • 1) Fixed.
    • 2) Done. Zip will be deleted automatically, extracted files will be moved and then only the containing directory should remains. If directory has one or more files, directory will be (temporarily) preserved.
    • 3) Rewritten.
    • 4) Repeat disabled using the third argument on drupal_set_message().

    And more:

    • Now forbidden files for non-administrators users are skipped gracefully and a message is returned.
    • Added PHP5 on installation requirements (now for scandir, but in the future for use PHP to uncompress archives).

    Thank you for your suggestions.

    Your turn.

  • chirale’s picture

    Status: Needs work » Needs review
    andyf’s picture

    Status: Needs review » Reviewed & tested by the community

    Looks good to me.

    klausi’s picture

    Status: Reviewed & tested by the community » Needs work

    * Git release branch is missing, see http://drupal.org/node/1015226
    * README.txt is missing
    * Remove the translations folder, translations are done on localize.drupal.org
    * Remove empty install/uninstall functions
    * "// Insert files within archive in {upload} table after save (new node) or update (existing node)" comment lines should not exceed 80 characters and should end with a "."
    * "// Load" load what? be more specific in a comment

    chirale’s picture

    Status: Needs work » Needs review

    Thank you klausi, I've created the 6.x-1.x branch and fix the other issues you listed. I've checked the module with Coder and should be fine now.

    Are any other action required to comply to project standards?

    klausi’s picture

    Status: Needs review » Needs work
    • remove LICENSE.txt, this will be added by drupal.org packaging automatically
    • multiuploadalone_nodeapi(): do not use "/** ... */" style comments in function bodies, use "//" there.
    • Also in multiuploadalone_form_alter() and in other places.
    • "exec($cmd, $output, $return_vars);": executing shell stuff is never a good idea ... why can't you use http://www.php.net/manual/en/book.zip.php ?
    • "$file->destination = file_destination(file_create_path($dest .'/'. $file->filename), $replace);": code style, there should be a space before and after ".". Also elsewhere.
    chirale’s picture

    Status: Needs work » Needs review

    PHP Zip extension support is planned: look at the comments within code to check the issues I've had. Anyway, the command line alternative should remains as fallback where PHP Zip extension is not available.

    Comments and string concatenation should be ok now.

    Are any other action required? Thank you.

    klausi’s picture

    Status: Needs review » Reviewed & tested by the community
    • multiuploadalone_requirements(): "foreach ($execoutput as $info) {": you don't do anything in that loop, remove it.
    • "/** create file object **/": replace the comment style with "//"
    • "A blank line forms a paragraph. There should be no trailing white-space": I think this is a left over from the documentation template?

    Thos things are quite minor, otherwise RTBC for me. Now while we are waiting for the access grants to create full projects would you be so kind to do a review of the other applications pending? Just pick one from this list: http://drupal.org/project/issues/projectapplications?status=8

    chirale’s picture

    $info is used to store version information from $execoutput when it's not empty. Fixed other lines and added doxygen header for a parameter on _multiuploadalone_file2db().

    Thank you for reviewing this module, AndyF and klausi.

    greggles’s picture

    Status: Reviewed & tested by the community » Fixed

    Two smaller issues:

    Please take a moment to make your project page follow tips for a great project page.

    This is not great for translatability because it requires the version to be on the end:

    35 'value' => $t('Unzip command available. Version information: ') . check_plain($info),
    

    better is

    35 'value' => $t('Unzip command available. Version information: @version', array('@version' => $info),
    

    The @ substitution includes a call to check_plain.

    Thanks for your contribution, chirale! Welcome to the community of project contributors on drupal.org.

    I've granted you the git vetted user role which will let you promote this to a full project and also create new projects as either sandbox or "full" projects depending on which you feel is best.

    Thanks, also, for your patience with the review process. Anyone is welcome to participate in the review process. Please consider reviewing other projects that are pending review. I encourage you to learn more about that process and join the group of reviewers.

    As you continue to work on your module, keep in minde: Commit messages - providing history and credit and Release naming conventions.

    Status: Fixed » Closed (fixed)

    Automatically closed -- issue fixed for 2 weeks with no activity.