Editor Upload File Exists Confirm Overwrite Prompt

Editor Upload File Exists Confirm Overwrite Prompt

maliwikmaliwik Posts: 55Questions: 5Answers: 1

Is there a way to check if a submitted filename exists before actually uploading and moving the file over?

I want to have someone either drag a file into the upload box or select one, and before it uploads it, check the filename to make sure it doesn't exist. If it does exist, I'd like to show a JavaScript alert box and ask the user if they want to overwrite the file. If they don't, empty the field. This ideally all has to happen when they're selecting a file to upload instead of the form submission.

This question has an accepted answers - jump to answer

Answers

  • allanallan Posts: 63,468Questions: 1Answers: 10,466 Site admin
    Answer ✓

    The preUpload event could possibly be used for this. Have it check if the file already exists and then return false if it does. You'd then need to set some kind of flag that will tell the server it is okay to overwrite the file.

    Editor 1.7 is going to introduce better validation for the file upload which will make this kind of thing much easier.

    Having said that, it might be easier to use a unique identifier for the file name, like the examples do (for this reason). Otherwise you might overwrite a completely different file which just happens to have a common name.

    Allan

  • maliwikmaliwik Posts: 55Questions: 5Answers: 1

    So with the following:

    $( editorUserReports.field( 'tbl_User_Reports.User_Report_Filename' ).input() ).on( 'upload.editor', function (e, val) {
      [How would I pull the filename in here if the user canceled the upload overwrite?]
    });
    
  • allanallan Posts: 63,468Questions: 1Answers: 10,466 Site admin

    I'm not sure to be honest - what is the goal there? Do you want them to use the other file that already exists on the server, even although it might not be the same file?

    Allan

  • maliwikmaliwik Posts: 55Questions: 5Answers: 1

    Basically, when someone selects a file to upload, I want to prompt the user with an alertbox that says "Filename exists. Do you want to overwrite the file already on the server?" and if the user clicks "No", then cancel the upload (I guess it'd prevent it from moving the temporary file over to the directory I'm checking). If the user clicks "Yes", then move the temporary file over and overwrite the existing file. If they overwrite the file, update the input with the filename, if not, leave the input untouched.

    I'm using the example that displays the thumbnail of the uploaded image before someone actually submits the form, so I'm assuming I could implement the logic within the preUpload on that part as opposed to the submitting of the form preUpload.

  • allanallan Posts: 63,468Questions: 1Answers: 10,466 Site admin

    The checking of the file name being a duplicate and showing a validation error is easy - preUpload on the server-side or a file upload validator can be used for that.

    What is more difficult is the confirmation message since Editor's upload wasn't designed for that. It assumes that each uploaded file should be stored uniquely. For example, consider the case where I have a document called "Screenshot.png" that I want to upload - I get a message stating that there is already a file called "Screenshot.png" - do I want to overwrite it? I'd probably click yes, because what do I care about someone else's file that happens to use the same name as mine? But then, what if someone else overwrites mine?

    For that reason the Editor demos all make use of the database's row id which contains the file's meta data - it is unique and thus there is no overwriting issue.

    Allan

  • maliwikmaliwik Posts: 55Questions: 5Answers: 1

    I see. You make an excellent point.

    How then might I automatically name the file based on the contents of another field and retain the integrity of the name throughout the process?

    We have a table with the following information:

    • Report Title
    • Report Description
    • Report Category
    • Report Filename

    Ideally the Report Filename should automatically use the Report Title plus whatever extension the uploaded file has. I know how to pass the Report title onto the PHP backend via ajaxData and retrieving it with $_POST. I have the filename successfully renamed to the Report Title when the thumbnail image is created (before the form is actually submitted), but the problem I'm running into is if the Report Title field is changed *after* the upload, and then the form is submitted. The file that's already present in the directory still uses the old title that was in the Report Title field when the image was originally uploaded and thumbnail displayed.

    Here's my PHP code for the upload field:

    Field::inst( 'tbl_User_Reports.User_Report_Filename' )
        ->setFormatter( 'Format::nullEmpty' )
        ->upload( Upload::inst(function ( $file, $id ) {
            $userDir = generateReportDirectory($_POST['User_ID'], $_POST['User_ReportDirSalt']);
            $userDirFull = SITE_ROOT . '/user/reports/' . $userDir;
            if(!file_exists($userDirFull)){
                mkdir($userDirFull);
            }
    
            $extension = pathinfo($file['name'], PATHINFO_EXTENSION);
            $newFilename = $_POST['User_Report_Title'] . '.' . $extension;
            $fileLocation = $userDirFull . '/' . $newFilename;
            $fileRelativeLocation = WEBSITE_REL_ROOT . '/user/reports/' . $userDir . '/' . $newFilename;
    
            move_uploaded_file( $file['tmp_name'], $fileLocation);
            return $fileRelativeLocation;
        })
        ->validator( function ( $file ) {
            return $file['size'] >= 50000000 ?
                "Files must be smaller than 50 MB" :
                null;
            })
        ),
    

    And here's my file field in the Editor JavaScript declaration:

    {
        "label": "File:",
        "name": "tbl_User_Reports.User_Report_Filename",
        "type": 'upload',
        "display": function ( file_name ) {
            if (file_name != null){
                if (file_name.match(/\.(gif|jpg|jpeg|tiff|png)$/i)){
                    return '<img src="' + file_name + '"  />';
                }else if (file_name.match(/\.*$/i)){
                    return '<i class="fa fa-file-o">&nbsp;&nbsp;</i>Document';
                }
            }
        },
        "clearText": "Clear File",
        "noImageText": 'No File',
        "ajaxData": function ( fd ) {
            fd.append( 'User_Report_Title', editorManageUserReports.field( 'tbl_User_Reports.User_Report_Title' ).val() );
        }
    },
    

    I assume there's probably a super easy solution to this like you've seem to been able to help me with, with few exceptions (I said in a post a few weeks ago that I'm extremely rusty on my JS and DataTables knowledge since the last version I was proficient in was waaaay back with 1.7.5 haha)

  • allanallan Posts: 63,468Questions: 1Answers: 10,466 Site admin

    How then might I automatically name the file based on the contents of another field and retain the integrity of the name throughout the process?

    Two options:

    1) Use the table row id in path - e.g.:

    >upload( Upload::inst( $_SERVER['DOCUMENT_ROOT'].'/uploads/__ID__/__NAME__' ) )
    

    That means you have a single file in each directory, but it should work.

    2) Use a PHP script to download the files, naming them as you need from the database information. It would read the file contents and then echo them back out.

    Allan

  • maliwikmaliwik Posts: 55Questions: 5Answers: 1
    edited October 2017

    So the primary key for our reports is numeric - what if I were to switch the primary key to a composite primary key consisting of the Report title and the User ID associated with the report? How would the file be named then?

  • maliwikmaliwik Posts: 55Questions: 5Answers: 1
    edited October 2017

    When I do the following:

    ->upload( Upload::inst( SITE_ROOT . '/user/reports/' . $_POST['User_ReportDir'] . '/__ID__/__NAME__' )
    

    It doesn't use the ID of the row (this is logged to the console):

    Image field has changed value (host dir hidden)/user/reports/(user report dir hidden)//test.txt
    

    I assume it's because the directory isn't being made and it can't create the file in a directory that doesn't exist.

  • maliwikmaliwik Posts: 55Questions: 5Answers: 1

    Okay, I see where the confusion is.

    When editing a report, I don't actually want anything changed except the filename field, and don't need an extra table to store information about the files themselves since its inconsequential. From what I've been experimenting with, I think your suggestion about the ID actually pulls the newly created ID from such a table, not from the current table being edited. So what ideally would happen is this:

    On create of a report:
    - Regular fields from the report such as Title and Description are put into the Reports table
    - The Report Filename in the Reports table needs to be unique. I was hoping that I could use the title (can't be duplicates for each user) of the newly created report itself as an identifier, such as "report title.ext". So somehow the file that was uploaded would be renamed to the report title.
    - Sometimes a report might just be an image, so that's why the thumbnail is generated when they first select a file - so if they stick with that file and submit the report form, that particular file would need to be renamed and the Report Filename updated accordingly

    On edit of report:
    - Really the only thing that needs to happen is the Report Filename column should be updated accordingly to match the newly uploaded file on submit, and any other fields that may have changed.

    I'm not sure if all of that successfully explains the model of the system (I'm not very good at describing these things accurately).

  • maliwikmaliwik Posts: 55Questions: 5Answers: 1

    I figured it out!

    Here's the PHP code for the initial field declaration:

    Field::inst( 'tbl_User_Reports.User_Report_Filename' )
        ->setFormatter( 'Format::nullEmpty' )
        ->upload( Upload::inst( function ( $file, $id ) use ( $db ) {
            $userDirId = generateReportDirectory($_POST['User_ID'], $_POST['User_ReportDirSalt']);
            $userDir = '/user/reports/' . $userDirId . '/';
            $userDirSystem = SITE_ROOT . $userDir . '/';
            $userDirWebsite = WEBSITE_REL_ROOT . $userDir;
    
            $userTempDirSystem = $userDirSystem . 'temp/';
            $userTempDirWebsite = $userDirWebsite . 'temp/';
    
            if(!file_exists($userDirSystem)) mkdir($userDirSystem);
            if(!file_exists($userTempDirSystem)) mkdir($userTempDirSystem);
    
            $extension = '.' . pathinfo($file['name'], PATHINFO_EXTENSION);
            $filenameTemp = end(explode('/', $file['tmp_name']));
            $fileLocationSystem = $userTempDirSystem . $filenameTemp . $extension;
            $fileLocationWebsite = $userTempDirWebsite . $filenameTemp . $extension;
    
            move_uploaded_file( $file['tmp_name'], $fileLocationSystem );
    
            return $filenameTemp . $extension;
        })
    

    What this does is moves the temporary uploaded file to a temporary directory within the user's directory and uses the temporary file's name as the filename in the database (temporarily heh).

    The following php code is in the writeCreate and writeEdit events:

    // Create record write routine
    ->on( 'writeCreate', function ( $editor, $id, $values ) {
    
        $userDirId =  $_POST['User_ReportDir'];
        $userDir = '/user/reports/' . $userDirId . '/';
        $userDirSystem = SITE_ROOT . $userDir . '/';
    
        $userTempDirSystem = $userDirSystem . 'temp/';
        $fileTempLocationSystem = $userTempDirSystem . $values['tbl_User_Reports']['User_Report_Filename'];
    
        if(!file_exists($userDirSystem)) mkdir($userDirSystem);
        if(!file_exists($userTempDirSystem)) mkdir($userTempDirSystem);
    
        $filenameTemp = $values['tbl_User_Reports']['User_Report_Filename'];
        $extension = '.' . pathinfo($filenameTemp, PATHINFO_EXTENSION);
        $filename = $values['tbl_User_Reports']['User_Report_Title'] . $extension;
    
        $fileLocationSystem = $userDirSystem . $filename;
    
        $editor
            ->db()
            ->update( 'tbl_User_Reports', array(
                                'User_Report_Filename'   => $filename),
                                array( 'User_Report_ID' => $id ));
    
        rename( $fileTempLocationSystem, $fileLocationSystem );
    
    }) // end writeCreate (Create record write routine)
    
    
    // edit record write routine
    ->on( 'writeEdit', function ( $editor, $id, $values ) {
    
    
        $userDirId = generateReportDirectory($_POST['User_ID'], $_POST['User_ReportDirSalt']);
        $userDir = '/user/reports/' . $userDirId . '/';
        $userDirSystem = SITE_ROOT . $userDir . '/';
        
        $userTempDirSystem = $userDirSystem . 'temp/';
        $fileTempLocationSystem = $userTempDirSystem . $values['tbl_User_Reports']['User_Report_Filename'];
        
        $filenameTemp = $values['tbl_User_Reports']['User_Report_Filename'];
        $extension = '.' . pathinfo($filenameTemp, PATHINFO_EXTENSION);
        $filename = $values['tbl_User_Reports']['User_Report_Title'] . $extension;
        
        $fileLocationSystem = $userDirSystem . $filename;
        
        $editor
            ->db()
            ->update( 'tbl_User_Reports', array(
                                'User_Report_Filename'   => $filename),
                                array( 'User_Report_ID' => $id ));
    
        rename( $fileTempLocationSystem, $fileLocationSystem );
    
    }) // end writeEdit (Edit record write routine)
    

    These further events finish the moving of the uploaded file to the user's directory from their temp directory as its proper name (the report title), and updates the database with the new title after submission. This way there are no inconsistencies with the title being changed somehow after they select a file and before submission of the form.

    Looks like everything is suitable for what we need except for garbage collection (which shouldn't be too difficult).

    Thanks for your help Allan!

  • allanallan Posts: 63,468Questions: 1Answers: 10,466 Site admin

    Thanks for posting back - I'm sorry I wasn't able to get back to you earlier about this.

    That's a really impressive solution you've come up with - nice one.

    Allan

This discussion has been closed.