document upload path

document upload path

crush123crush123 Posts: 417Questions: 126Answers: 18
edited August 2015 in Editor

I am trying to implement a document upload using the new editor plugin.

In my example, there is no joined table, only a basic document table with which i want to upload a document to my documents folder, and store the web path or the filename only, (not the full system path) to the table.

so my editor instance looks like this

Field::inst( 'tbldocuments.DocumentPath' )
          ->upload( Upload::inst( $_SERVER['DOCUMENT_ROOT'].'/documents/__NAME__' )
         ),

which puts eg document1.pdf in the documents folder, and this stores full path in the database table.
This is working fine, but not quite what I want to achieve

How do I save the /documents/document1.pdf path in my table instead of the full path please ?

I can truncate the path when i render the field value in my datatable and the editor, but i would prefer just to store the filename or folder/filename

This question has accepted answers - jump to:

Answers

  • allanallan Posts: 63,498Questions: 1Answers: 10,471 Site admin

    You would use a custom upload action to do this.

    For example:

    Field::inst( 'tbldocuments.DocumentPath' )
        ->upload(
            Upload::inst( function ( $file, $id ) {
                move_uploaded_file( $file['tmp_name'], $_SERVER['DOCUMENT_ROOT'].'/documents/NAME' );
                return '/documents/NAME';
            } )
    

    Obviously you would want to make NAME a variable based on the file name (although if you upload the same file twice you could get a conflict so you may wish to include some other unique information in there).

    Allan

  • crush123crush123 Posts: 417Questions: 126Answers: 18

    Hmm,

    I changed my code as follows, with an id just to try it, but the editor form stays open, no errors

        Field::inst( 'tbldocuments.DocumentPath' )
        ->validator( 'Validate::notEmpty')
        ->upload(
            Upload::inst( function ( $file, $id ) {
                move_uploaded_file( $file['tmp_name'], $_SERVER['DOCUMENT_ROOT'].'/documents/'.$id );
                return $id;
            } )
        ),
    

    can you see what i am doing wrong ?

    http://test3.forthwebsolutions.com/admin/documents/plugins/documents_results.php

  • allanallan Posts: 63,498Questions: 1Answers: 10,471 Site admin

    When you upload a file the form will stay open until you submit it.

    Does it stay open when you click the submit button? If so, what is the Ajax return from the server?

    Also, since you haven't used Upload->db() $id will be null. You noted above that you didn't want to use the database.

    Allan

  • crush123crush123 Posts: 417Questions: 126Answers: 18
    edited April 2015

    the form stays open when you click the submit button, (but only if you have chosen a document using the upload), otherwise it submits fine.
    In fact I don't think submit does anything in this instance

    you should see this effect using the link above.

    I do want to use the database, but I am storing the document name in the document table itself, not using a join.

    My initial code works

    Field::inst( 'tbldocuments.DocumentPath' )
          ->validator( 'Validate::notEmpty')
          ->upload( Upload::inst( $_SERVER['DOCUMENT_ROOT'].'/documents/__NAME__' )
          ->allowedExtensions( array( 'pdf', 'doc', 'docx' ), "Please upload a document file (.pdf, .doc, .docx)" )
         ),
    

    except this stores the full path in my table, not the folder/filename, which is what i want

    UPDATE

    Just noticed, that this IS throwing an error

    Fatal error: Call to undefined method DataTables\Editor\Upload::_action() ... on line 352

  • allanallan Posts: 63,498Questions: 1Answers: 10,471 Site admin

    Fatal error: Call to undefined method DataTables\Editor\Upload::_action() ... on line 352

    Doh. Good old PHP :-)

    On line 352:

    return $this->_action( $upload, $id );

    needs to become:

                $action = $this->_action;
                return $action( $upload, $id );
    

    Hopefully that will help. Thanks for letting me know about that.

    I do want to use the database

    Yes, but not in the sense of the db() method of the Upload class. You aren't storing information about the file in the db on upload, but rather just the file name on edit. There is on row in the database that is unique to just that file.

    Allan

  • crush123crush123 Posts: 417Questions: 126Answers: 18
    edited April 2015

    Fix to PHP is working

    ;-)

    Have now got a bit further, but how do I replace NAME in the example with the name of the original uploaded file ? I can;t get the syntax right

    Field::inst( 'tbldocuments.DocumentPath' )
    ->upload(
        Upload::inst( function ( $file, $id ) {
            move_uploaded_file( $file['tmp_name'], $_SERVER['DOCUMENT_ROOT'].'/documents/NAME' );
            return '/documents/NAME';
        } )
    

    i have played with the db method, which stores the file name ok, but as it is the same table, it inserts a new row into my table

  • allanallan Posts: 63,498Questions: 1Answers: 10,471 Site admin
    Answer ✓

    The $files parameter passed in, as noted in the documentation, is the entry from $_FILES for the uploaded file. The PHP documentation here describes the options in the array.

    So you would use $file['name'] to get the file name.

    Allan

  • crush123crush123 Posts: 417Questions: 126Answers: 18
    edited April 2015

    Thanks Allan, I didn't see that.

    Beautiful - this is an excellent add-on to editor

    Only 1 more question on this thread.

    File Deletion - is there anything built in to delete the file from the server when the row is deleted from the database ? When i remove a row, the file is still present

  • allanallan Posts: 63,498Questions: 1Answers: 10,471 Site admin
    Answer ✓

    Currently no. What you need to do is hook into the remove action (i.e. check for $_POST['action'] === 'remove') and then delete the file.

    I haven't decided how to handle this yet in the upload class - likely some kind of callback will be needed as i don't want to have the class deleting files on the file system (incase there is a misconfiguration during initial setup - that would be bad...).

    Allan

  • crush123crush123 Posts: 417Questions: 126Answers: 18

    that's cool, just didn't want to reinvent the wheel

    cheers for all the support

  • allanallan Posts: 63,498Questions: 1Answers: 10,471 Site admin

    Thanks for letting me know about the issue you found! Great to hear that the new feature is working well for you so far.

    Allan

  • san_101san_101 Posts: 2Questions: 0Answers: 0
    edited May 2015

    I'm using the upload with custom function but somehow getting following error:

    <b>Catchable fatal error</b>: Object of class Closure could not be converted to string in <b>/www/Editor/Upload.php</b> on line <b>476</b><br />

    looks like following code is giving this error. Works fine when it is not a custom function

    
       private function _path ( $name, $id )
       {
          $extn = pathinfo( $name, PATHINFO_EXTENSION );
    
          $to = $this->_action;
          $to = str_replace( "__NAME__", $name, $to   );
          $to = str_replace( "__ID__",   $id,   $to   );
          $to = str_replace( "__EXTN__", $extn, $to );
    
          return $to;
       }
    

    Here is my code:

    DOES NOT WORK=>

    
    Field::inst( 'staff.image' )
        ->upload(
            Upload::inst( function ( $file, $id ) {
                move_uploaded_file( $file['tmp_name'], '/uploads/'.$id );
                return $id;
            } )
                ->db( 'image', 'id', array(
                    'fileName' => Upload::DB_FILE_NAME,
                    'fileSize' => Upload::DB_FILE_SIZE
                ) )
        )
    

    WORKS=>

    
    Field::inst( 'staff.image' )
        ->upload(
            Upload::inst($_SERVER['DOCUMENT_ROOT'].'/uploads/__ID__.__EXTN__')
                ->db( 'image', 'id', array(
                    'fileName' => Upload::DB_FILE_NAME,
                    'fileSize' => Upload::DB_FILE_SIZE
                ) )
        )
    
  • allanallan Posts: 63,498Questions: 1Answers: 10,471 Site admin

    @ san_101 - The error sounds you are using v1.4.1. The bug has been fixed in 1.4.2.

    It would appear that your trial ended back in February. How have you been getting on with Editor?

    Allan

  • san_101san_101 Posts: 2Questions: 0Answers: 0

    Allan - Thanks for the update. Yes looks like I'm on 1.4.1. Been really impressed and purchased single license for now. Thanks for the support.

  • fabioberettafabioberetta Posts: 74Questions: 23Answers: 4

    Hi Allan,

    I have the same error that san_101 but I am on version 1.5.0.

    How can I fix it?

    ty
    f

  • allanallan Posts: 63,498Questions: 1Answers: 10,471 Site admin

    Can you link to the page so I can take a look and see what is going wrong please? If you could post your PHP code as well.

    Thanks,
    Allan

  • fabioberettafabioberetta Posts: 74Questions: 23Answers: 4

    Hello Allan,

    this is the PHP. I will try to have the web site available on a server (now it is on a MAMP on my MAC).

    // Alias Editor classes so they are easy to use
    use
        DataTables\Editor,
        DataTables\Editor\Field,
        DataTables\Editor\Format,
        DataTables\Editor\Join,
        DataTables\Editor\Upload,
        DataTables\Editor\Validate;
    
    // Build our Editor instance and process the data coming from _POST
    Editor::inst( $db, 'child' )
        ->fields(
            Field::inst( 'child.id' ),
            Field::inst( 'child.first_name' ),
            Field::inst( 'child.last_name' ),
            Field::inst( 'child.birth_date' ),
            Field::inst( 'child.sex' ),
            Field::inst( 'child.photo_id' )
                    ->upload( 
                            Upload::inst( $_SERVER['DOCUMENT_ROOT'].'/LutinRouge_admin/__school_files/'.str_pad(strval($_SESSION['account_id']), 7, "0", STR_PAD_LEFT).'/CHILD.__ID__.PO.__EXTN__' ) 
                                ->db( 'file', 'id', array(
                                            'fileName'    => Upload::DB_FILE_NAME,
                                            'fileSize'    => Upload::DB_FILE_SIZE,
                                            'webPath'    => Upload::DB_WEB_PATH,
                                            'fullPath' => Upload::DB_SYSTEM_PATH,
                                            'fileExtension' => Upload::DB_EXTN,
                                            'entity' => 'CHILD'
                                ) )
                    ),
            Field::inst( 'child.section_id' )
                    ->options( 'section', 'id', 'name' ),
            Field::inst( 'section.name' ),
            Field::inst( 'section.color' ),
            Field::inst( 'child.account_id' )
                ->set( Field::SET_CREATE )
                ->setValue( $_SESSION['account_id'] ))
        ->where( 'child.account_id', $_SESSION['account_id'] )
        ->leftJoin( 'section', 'section.id', '=', 'child.section_id' )
        ->process( $_POST )
        ->json();
    

    ty
    f

  • allanallan Posts: 63,498Questions: 1Answers: 10,471 Site admin

    Thanks for the code. The error that san_101 is / was seeing is due to the use of a closure function for the upload action. The function is moving the file to where it needs to be in the file system. That is fine, but the database settings in that configuration use the path, which it can't know since it was a function that set the path and not a simple string.

    In your own case you are using a string as the action, so the issue isn't going to be quite the same. I'm not actually sure what would cause the issue you are seeing - I don't see anything wrong with the above code. A link to the page might help in debugging it.

    Regards,
    Allan

  • fabioberettafabioberetta Posts: 74Questions: 23Answers: 4

    Hi Allan,

    sorry the code above is the working version (that is not using the Custom Upload closure).

    I think I know where the problem is coming.

    This is the code NOT working.

    Editor::inst( $db, 'child' )
        ->fields(
            Field::inst( 'child.id' ),
            Field::inst( 'child.first_name' ),
            Field::inst( 'child.last_name' ),
            Field::inst( 'child.birth_date' ),
            Field::inst( 'child.sex' ),
            Field::inst( 'child.photo_id' )
                    ->upload( 
                            Upload::inst( function ( $file, $id ) {
                                //var $_path = $_SERVER['DOCUMENT_ROOT'].'/LutinRouge_admin/__school_files/'.str_pad(strval($_SESSION['account_id']), 7, "0", STR_PAD_LEFT);
                                $filename = $_SERVER['DOCUMENT_ROOT'].'/LutinRouge_admin/__school_files/pippo.jpg';
                                echo    json_encode($filename);
                                die;
                                move_uploaded_file( $file['tmp_name'], $filename );
                                return $id;
                            } ) 
                                ->validator( function ( $file ) {
                                    if ( $file['size'] >= 100000 ) {
                                        return "Files must be smaller than 100Kb";
                                    }
                                        return null;
                                } )
                                ->db( 'file', 'id', array(
                                            'fileName'    => Upload::DB_FILE_NAME,
                                            'fileSize'    => Upload::DB_FILE_SIZE,
                                            'webPath'    => Upload::DB_WEB_PATH,
                                            'fullPath' => Upload::DB_SYSTEM_PATH,
                                            'fileExtension' => Upload::DB_EXTN,
                                            'entity' => 'CHILD'
                                ) )
                    )
                    ->setFormatter( 'Format::nullEmpty' ),
            Field::inst( 'child.section_id' )
                    ->options( 'section', 'id', 'name' ),
            Field::inst( 'section.name' ),
            Field::inst( 'section.color' ),
            Field::inst( 'child.account_id' )
                ->set( Field::SET_CREATE )
                ->setValue( $_SESSION['account_id'] ))
        ->where( 'child.account_id', $_SESSION['account_id'] )
        ->leftJoin( 'section', 'section.id', '=', 'child.section_id' )
        ->process( $_POST )
        ->json();
    

    I checked your Upload.php and I realised that you call the _path() function when you fill the database.

    This is your code:

            // Update the newly inserted row with the path information. We have to
            // use a second statement here as we don't know in advance what the
            // database schema is and don't want to prescribe that certain triggers
            // etc be created. It makes it a bit less efficient but much more
            // compatible
            if ( count( $pathFields ) ) {
            
                $path = $this->_path( $upload['name'], $id );
                $webPath = str_replace($_SERVER['DOCUMENT_ROOT'], '', $path);
                $q = $db
                    ->query( 'update' )
                    ->table( $this->_dbTable )
                    ->where( $this->_dbPKey, $id );
    
                foreach ( $pathFields as $column => $type ) {
                    $q->set( $column, $type === self::DB_WEB_PATH ? $webPath : $path );
                }
    
                $q->exec();
            }
    

    If you are using a custom upload closure the _path() method will of courde generate an error.

        private function _path ( $name, $id )
        {
            $extn = pathinfo( $name, PATHINFO_EXTENSION );
            
            $to = $this->_action;
            $to = str_replace( "__NAME__", $name, $to   );
            $to = str_replace( "__ID__",   $id,   $to   );
            $to = str_replace( "__EXTN__", $extn, $to );
    
            return $to;
        }
    }
    

    I do not really know how to fix it. Your thoughts?

    ty
    f

  • fabioberettafabioberetta Posts: 74Questions: 23Answers: 4

    Hi Allan,

    after a comprehensive review of the code of Upload.php I realized that what I intended to do can't actually be done.

    I wanted to upluad 1 photo, store it as original into a directory specific to each account with a name based on the id of the file table.
    I wanted also to create a thumbnail and store it in the same directory.

    Saving the path of both files based on id is impossible with your library unless you modify it with the possiility to use closures for the DB_FULLPATH and DB_WEBPATH firing them AFTER the creation of the table row and allowing $id as parameter.

    Since I liked your library I have reworked the code on "client side" but it is far from being "elegant".

    ty
    f

  • MarianHMarianH Posts: 1Questions: 0Answers: 0

    My solution to make thumbnail after upload an image:

    PHP code:

    function make_thumb($src, $dest, $desired_width) {
        if (!file_exists($dest)) {
            $source_image = imagecreatefromjpeg($src);
            $width = imagesx($source_image);
            $height = imagesy($source_image);
            $desired_height = floor($height * ($desired_width / $width));
            $virtual_image = imagecreatetruecolor($desired_width, $desired_height);
            imagecopyresampled($virtual_image, $source_image, 0, 0, 0, 0, $desired_width, $desired_height, $width, $height);
            imagejpeg($virtual_image, $dest);
        }
    }
        
    function thumbnails($db, $values) {
            $files = array();
            for ($i=0 ; $i<=$values["image-many-count"]-1 ; $i++ ) {
                $result = $db->sql('SELECT system_path FROM image WHERE id='.$values["image"][$i]["id"].';' );
                while ($row = $result->fetch()) {
                    $files[] = $row['system_path'];
                }
            }
    
            foreach ($files as &$file) {
                $path_parts = pathinfo($file);
                make_thumb($file, $path_parts['dirname']."/thumbnails/".$path_parts['basename'], 75);
            }
    }
    
    Editor::inst($db, 'foto')
            ->fields(
                Field::inst('foto.id'),
                Field::inst('foto.cislo_pu')
                    ->set(Field::SET_CREATE)
                    ->setValue($cislo_pu)
            )
            ->join(
                    Mjoin::inst('image')
                        ->link('foto.id', 'foto_image.foto_id' )
                        ->link('image.id', 'foto_image.image_id' )
                        ->fields(
                            Field::inst('id')
                                ->upload(Upload::inst($_SERVER['DOCUMENT_ROOT'].'/webAPS/uploads/'.$cislo_pu.'/__NAME__')
                                ->db('image', 'id', array(
                                        'web_path' => Upload::DB_WEB_PATH,
                                        'system_path' => Upload::DB_SYSTEM_PATH
                                    ))
                                )   
                        )
                )
            ->on('postCreate', function ($editor, $id, $values, $row) use ($db){
                thumbnails($db, $values);  
            })
            ->on('postEdit', function ($editor, $id, $values, $row) use ($db){
                thumbnails($db, $values);  
            })
        ->process($_POST)
        ->json();
    

    Could it be?
    Marian

  • allanallan Posts: 63,498Questions: 1Answers: 10,471 Site admin

    Hi,

    What you want is actually perfectly possible, but you need to write the path information to the database yourself. Using the Upload->db() method to write path information won't work, as I explained above, because the action being performed is a closure function and the Upload class doesn't know what it does or where it puts the files!

    In the closure function you have the $id parameter, which is the primary key value for the file's new row in the database. You would use that to simply write an UPDATE statement with the path information you need to store.

    Allan

  • fabioberettafabioberetta Posts: 74Questions: 23Answers: 4

    Hi Allan,

    thanks for the comment. I understood that I cannot use the Upload->db() because the id is unknown when the line is created and the closure fired.

    Nevethehless I am trying to stick to your existing methods that are robust and regularly updated and well improved.

    Do you have a short example or snippet that I could use to send this UPDATE sql statement? I would like to avoid to develop a specific PHP service only for that.

    ty
    f

  • allanallan Posts: 63,498Questions: 1Answers: 10,471 Site admin

    You can use Upload->db() no problem - you just can't use the web path or system path statements in it.

    For an example, you could use the Db->update() method in your closure function to update the database row that Upload->db() has created (if you use it - otherwise you'll need to create a new row and then return that id):

    $db->update( 'image', [ "web_path" => ... ], [ "id" => $id ] );
    

    Allan

  • c_bindingc_binding Posts: 3Questions: 0Answers: 0
    edited October 2015

    Upload::inst( function ( $file, $id ) {
    move_uploaded_file( $file['tmp_name'], '/uploads/'.$id );
    return $id;
    } )

    fails with
    [02-Oct-2015 11:07:55 Europe/Paris] PHP Catchable fatal error: Object of class Closure could not be converted to string in D:\wamp\www\bus_hours\Editor-PHP-1.5.0\php\Editor\Upload.php on line 621

    [02-Oct-2015 11:07:55 Europe/Paris] PHP Stack trace:

    [02-Oct-2015 11:07:55 Europe/Paris] PHP 1. {main}() D:\wamp\www\bus_hours\php\wav_files.php:0

    [02-Oct-2015 11:07:55 Europe/Paris] PHP 2. DataTables\Editor->process() D:\wamp\www\bus_hours\php\wav_files.php:98

    [02-Oct-2015 11:07:55 Europe/Paris] PHP 3. DataTables\Editor->_upload() D:\wamp\www\bus_hours\Editor-PHP-1.5.0\php\Editor\Editor.php:558

    [02-Oct-2015 11:07:55 Europe/Paris] PHP 4. DataTables\Editor\Upload->exec() D:\wamp\www\bus_hours\Editor-PHP-1.5.0\php\Editor\Editor.php:981

    [02-Oct-2015 11:07:55 Europe/Paris] PHP 5. DataTables\Editor\Upload->_dbExec() D:\wamp\www\bus_hours\Editor-PHP-1.5.0\php\Editor\Upload.php:379

    [02-Oct-2015 11:07:55 Europe/Paris] PHP 6. DataTables\Editor\Upload->_path() D:\wamp\www\bus_hours\Editor-PHP-1.5.0\php\Editor\Upload.php:591

    [02-Oct-2015 11:07:55 Europe/Paris] PHP 7. str_replace() D:\wamp\www\bus_hours\Editor-PHP-1.5.0\php\Editor\Upload.php:621

  • c_bindingc_binding Posts: 3Questions: 0Answers: 0

    as was mentioned above, the _path() function is called in some cases without guarding it against string vs. closure (function). if $this->_action is a function (closure), it fails in str_replace.

    The offending call to _path is in _dbExec

  • allanallan Posts: 63,498Questions: 1Answers: 10,471 Site admin

    You can't use Upload::DB_WEB_PATH or Upload::DB_SYSTEM_PATH if you provide a custom closure method. The reason for this is that it is up to the closure where it puts the file on the file system, and the Upload class doesn't know where that is, so it can't write it to the database.

    Editor 1.5.2 will include a more understandable warning about this.

    Allan

This discussion has been closed.