3

I writing plugin in my profile page and i want to create upload via 'Browse' button and 'Dir' field that will upload and return image url.And i don't want to use media-upload.

I used to read Set Featured Image Front Frontend Form? and I don't understand code Help me solve this problem?

6

There are several parts.

You need to add an enctype to the profile form.

function edit_form_type_wpse_98375() {
    echo ' enctype="multipart/form-data"';
}
add_action('user_edit_form_tag','edit_form_type_wpse_98375');

Then add a field to the form.

function user_fields_wpse_98375($profileuser) {
  $_profile_photo = get_user_meta($profileuser->data->ID,'_profile_photo',true);

  echo '<h3>'.__('Additional User Data',THEME_TEXTDOMAIN).'</h3>';
    echo '<tr class="show-admin-bar">';
      echo '<th scope="row">'.__('Profile Photo', THEME_TEXTDOMAIN).'</th>';
      echo '<td'.$tspan.'>';
        echo '<fieldset>';
          echo '<legend class="screen-reader-text"><span>'.__('Profile Photo', THEME_TEXTDOMAIN).'</span></legend>';
          echo '<label for="profile_photo">';
            echo '<input name="profile_photo" type="file" id="profile_photo" value="" />';
          echo '</label><br />';
        echo '</fieldset>';
      echo '</td>';
    echo '</tr>';
  echo '</table>';
}
add_action('show_user_profile', 'user_fields_wpse_98375');
add_action('edit_user_profile', 'user_fields_wpse_98375');

And then save the data.

function save_user_custom($id=false) {
  global $_FILES,$_POST;
  if (false === $id) return false;

  // save image
  if (isset($_FILES)) {
    if (isset($_FILES['profile_photo'])){
      if (0 === $_FILES['profile_photo']['error']) {
        // This is where you save the file
        // Maybe use wp_handle_upload
        // Or use the Filesystem API
        // not sure what you want to do
      }
    }
    unset($up);
  }
}
add_action('personal_options_update','save_user_custom');
add_action('edit_user_profile_update','save_user_custom');

wp_handle_upload is probably the simplest. From the Codex:

if ( ! function_exists( 'wp_handle_upload' ) ) 
    require_once( ABSPATH . 'wp-admin/includes/file.php' );
$uploadedfile = $_FILES['file'];
$upload_overrides = array( 'test_form' => false );
$movefile = wp_handle_upload( $uploadedfile, $upload_overrides );
if ( $movefile ) {
    echo "File is valid, and was successfully uploaded.\n";
    var_dump( $movefile);
} else {
    echo "Possible file upload attack!\n";
}

The file input type works (mostly) like any other form input. If you give it a name like my_uploads['profile_photo'] instead of just profile_photo you will get an array. This will be reflected in the $_FILES variable when you process the form submission. You can add as many file inputs as you want by building that array, or even just by giving them different names. As far as dynamically adding file inputs, that is pretty simple Javascript.

Reference

http://codex.wordpress.org/Function_Reference/wp_handle_upload
http://codex.wordpress.org/Filesystem_API

4

I guess you are not very expirenced with PHP and files uploads. So I start with some basics and end with a simple class.

If you do not have already read the basics about file uploads with PHP, do it now. Nobody will explained it here for you, it is off topic.

Let start with the basic HTML form

if( empty( $_FILES ) ) {
global $pagenow;
$url = admin_url( $pagenow );
?>
<!-- The data encoding type, enctype, MUST be specified as below -->
<form enctype="multipart/form-data" action="<?php echo $url; ?>" method="POST">
    <!-- MAX_FILE_SIZE must precede the file input field -->
    <input type="hidden" name="MAX_FILE_SIZE" value="30000" />
    <!-- Name of input element determines name in $_FILES array -->
    Send this file: <input name="userfile" type="file" />
    <input type="submit" value="Send File" />
</form>
<?php
} else {
    $imageupload = new File_Upload();
    $attachment_id = $imageupload->create_attachment();
    var_dump( $attachment_id );
}

That is quiet simple. If the superglobal array $_FILES is empty, this means no file was uploaded, display the upload form. If it is not empty, process the upload. I use admin_url() and the $pagenow var to create the action url. If you use the upload form on the frontend, you have to use home_url() or something similiar.

After sending the file with HTML, you have to process the uploaded files. This will be done in the File_Upload class.

class File_Upload
{
    /**
     * Index key from upload form
     * @var string
     */
    public $index_key = '';

    /**
     * Copy of superglobal array $_FILES
     * @var array
     */
    public $files = array();

    /**
     * Constructor
     * Setup files array and guess index key
     */
    public function __construct(){

        if ( isset( $_FILES ) && ! empty( $_FILES ) ) {
            $this->files = $_FILES;
            $this->guess_index_key();
        }

    }

    /**
     * Set/overwrites the index key
     * Converts $name with type casting (string)
     *
     * @param   string  $name   Name of the index key
     * @return  string  ::name  Name of the stored index key
     */
    public function set_field_name_for_file( $name = '' ) {
        $this->index_key = ( ! empty( $name ) ) ? (string) $name : '';
        return $this->index_key;
    }

    /**
     * Converts uploaded file into WordPress attachment
     *
     * @return  boolean     Whether if the attachment was created (true) or not (false)
     */
    public function create_attachment(){

        // move the uploaded file from temp folder and create basic data
        $imagedata = $this->handle_uploaded_file();

        // if moving fails, stop here
        /*
         * For Production
         * Set and return an error object with WP_Error()
         */
        if ( empty( $imagedata ) )
            return false;

        /*
         * For Production
         * Check if $imagedata contains the expected (and needed)
         * values. Every method could fail and return malicious data!!
         */
        $filename = $imagedata['filename'];

        // create the attachment data array
        $attachment = array(
                'guid'           => $imagedata['url'] . '/' . $filename,
                'post_mime_type' => $imagedata['type'],
                'post_title'     => preg_replace('/\.[^.]+$/', '', $filename ),
                'post_content'   => '',
                'post_status'    => 'inherit'
        );

        // insert attachment (posttype attachment)
        $attach_id = wp_insert_attachment( $attachment, $filename );

        // you must first include the image.php file
        // for the function wp_generate_attachment_metadata() to work
        require_once( ABSPATH . 'wp-admin/includes/image.php' );

        /*
         * For Production
         * Check $attach_data, wp_generate_attachment_metadata() could fail
         * Check if wp_update_attachment_metadata() fails (returns false),
         * return an error object with WP_Error()
         */
        $attach_data = wp_generate_attachment_metadata( $attach_id, $filename );
        wp_update_attachment_metadata( $attach_id, $attach_data );

        return $attach_id;

    }

    /**
     * Handles the upload
     *
     * @return  array   $return_data    Array with informations about the uploaded file
     */
    protected function handle_uploaded_file() {

        // get the basic data
        $return_data = wp_upload_dir();

        // get temporary filepath and filename from $_FILES ($this->files)
        $tmp_file = ( isset( $this->files[$this->index_key] ) && ! empty( $this->files[$this->index_key] ) ) ?
            (string) $this->files[$this->index_key]['tmp_name'] : '';

        $tmp_name = ( isset( $this->files[$this->index_key] ) && ! empty( $this->files[$this->index_key] ) ) ?
            (string) $this->files[$this->index_key]['name'] : '';

        // stop if something went wrong
        if ( empty( $tmp_file ) )
            return false;

        // set filepath
        $filepath = $return_data['filepath'] = $return_data['path'] . '/' . basename( $tmp_name );

        // move uploaded file from temp dir to upload dir
        move_uploaded_file( $tmp_file , $filepath );

        // set filename
        $filename = $return_data['filename'] = basename( $filepath );

        // set filetype
        /*
         * For Production
         * You should really, really check the file extension and filetype on 
         * EVERY upload. If you do not, it is possible to upload EVERY kind of 
         * file including malicious code.
         */
        $type = wp_check_filetype( $filename, null );
        $return_data['file_ext'] = ( isset( $type['ext'] ) && ! empty( $type['ext'] ) ) ?
        $type['ext'] : '';

        $return_data['type'] = ( isset( $type['type'] ) && ! empty( $type['type'] ) ) ?
        $type['type'] : '';

        // return the results
        return $return_data;

    }

    /**
     * Try to fetch the first index from $_FILES
     *
     * @return  boolean     Whether if a key was found or not
     */
    protected function guess_index_key() {

        $keys = array_keys( $_FILES );

        if ( ! empty( $keys ) ) {
            $this->index_key = $keys[0];
            return true;
        }

        return false;

    }

}

This is a basic class and not for production! File uploads are a very sensitive topic, you have to validate and sanitize all data by yourself. Otherwise it is possible that someone upload malicious code and hack your server/blog/website!

Now step by step what happend where and when.

With creating an instance of the class, the class try to create a copy of the superglobal array $_FILES. If you work with superglobal arrays, don't touch them! Maybe other parts of the code (plugins or themes) also need the data inside them. The next thing what happend in the constructor is, that we try to guess the index key. As you know, we can pass more than only one file and $_FILES can contain data for all uploaded files. If you need to process more than one file, loop over $_FILES and set the index key with set_field_name_for_file()

// get the indexes from $_FILES
$keys = array_keys( $_FILES );
$upload_processor = new File_Upload();
foreach ( $keys as $key ) {
  $upload_processor->set_field_name_for_file( $key );
  $upload_processor->create_attachment();
}

set_field_name_for_file() is also needed if you want to pic a specific field from your HTML form, otherwise the class use the first index it can find.

The next step is to handle the uploaded file. The method create_attachment() call the protected method handle_uploaded_file(), you do not have to do it manually. The handle_uploaded_file() method simply collect and setup some needed pathes and filenames.

The last step is to create an attachment. create_atatchment() will setup all needed data and create an attachment for you. The method returns the ID of the attachment, so you can access all attachment data with get_post( [id] )

$imageupload = new File_Upload();
$attachment_id = $imageupload->create_attachment();
[some other code]        
$attachment = get_post( $attachment_id );
$image_url  = $attachment->guid;

printf( '<p><img src="%s"></p>', $image_url );

Some words about error handling

I wrote some hints in the class code for production environment. A I mentioned above, file uploads are a very sensitive topic. You have to check, validate and sanitize really everything. WordPress has a build in error handling with WP_Error(). Use it! And check if the upload was success with is_wp_error().

PHP can setup some error messages why the upload was failed if it failed. But PHP do not return clear text messages, it returns error codes. A method to convert this codes into clear text could look like this:

protected function guess_upload_error( $err = 0 ) {

$errcodes = array(
    'Unknown error',
    'The uploaded file exceeds the upload_max_filesize directive in php.ini.',
    'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.',
    'The uploaded file was only partially uploaded.',
    'No file was uploaded.',
    'Missing a temporary folder.',
    'Failed to write file to disk.',
    'A PHP extension stopped the file upload. PHP does not provide a way to ascertain which extension caused the file upload to stop; examining the list of loaded extensions with phpinfo() may help.'
);

return ( isset( $errcodes[$err] ) ) ?
    $errcodes[$err] : 'Unknown error';

}

You have to check if the upload was successfull, this could look like this (in method handle_upload_file())

// stop if something went wrong
if ( empty( $tmp_file ) ) {

    $code = ( isset( $this->files[$this->index_key]['error'] ) ) ?
        $this->files[$this->index_key]['error'] : 0;

    $msg = $this->guess_upload_error( $code );

    return new WP_Error( 'uploaderror', 'Upload failed with message: ' . $msg );
}

And then you have to handle the error when calling the class

if ( empty( $_FILES ) ) {

global $pagenow;
$url = admin_url( $pagenow );
?>
<!-- HTML form-->
<?php
} else {
    $imageupload = new File_Upload();
    $attachment_id = $imageupload->create_attachment();

    if ( is_wp_error( $attachment_id ) ) {

        echo '<ol>';
        foreach ( $attachment_id->get_error_messages() as $err )
            printf( '<li>%s</li>', $err );
        echo '</ol>';

    } else {

        // do something with the created attachment
        $attachment = get_post( $attachment_id );
        $image_url  = $attachment->guid;

        printf( '<p><img src="%s"></p>', $image_url );

    }
}

Always remember: Never ever let a failed upload be untreated!

  • In basic HTML form that that above code i didn't see if before else statement – MonoGot May 5 '13 at 20:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.