<?php
defined( 'ABSPATH' ) or exit;   // show a blank page if try to access this file directly

/* * ************************************* */
/* Generate and process the upload form */
/* * ************************************* */
require_once( EWZ_PLUGIN_DIR . 'classes/ewz-base.php' );
require_once( EWZ_PLUGIN_DIR . 'classes/ewz-exception.php' );
require_once( EWZ_PLUGIN_DIR . 'classes/ewz-item.php' );
require_once( EWZ_PLUGIN_DIR . 'classes/ewz-webform.php' );
require_once( EWZ_PLUGIN_DIR . 'classes/ewz-layout.php' );
require_once( EWZ_PLUGIN_DIR . 'classes/ewz-settings.php' );
require_once( EWZ_PLUGIN_DIR . 'classes/validation/ewz-upload-input.php' );
require_once( EWZ_PLUGIN_DIR . 'includes/ewz-common.php' );



/**
 * Display the upload form
 *
 * This is the function called by the shortcode
 *
 * @param   array<mixed>   $atts   attributes passed to the shortcode
 * @return  string  $output html
 */
function ewz_show_webform( $atts )
{

    wp_enqueue_script( 'ewz-upload' );

    // NB: other styles are defined as dependencies of this one in entrywizard.php
    wp_enqueue_style( 'ewz-style' );

    try{
        // need webformdata whether or not we need to process an upload
        // this checks for user logged in
        $webformdata = ewz_get_webform_data( $atts );
    } catch( Exception $e ) {
        return $e->getMessage();
    }
    if(  array_key_exists( 'failmsg', $webformdata ) ) {
        return $webformdata['failmsg'];
    }

    $errmsg = '';

    // Now we have the webform data, process any changes  coming
    // in from old browsers that dont use ajax for the upload
    if ( $_POST && isset( $_POST['identifier'] ) && ( $_POST['identifier'] == $atts['identifier'] ) ) {
        try{
            //error_log("EWZ: uploading (old form) for " . $_SERVER["REMOTE_ADDR"]);
            // had problems with more than 10 3M images
            $n = $webformdata['webform']->num_items;
            $timelimit = ini_get('max_execution_time');
            if( 15 * $n > $timelimit ){
                set_time_limit ( 15 * $n );
            }

            $input = new Ewz_Upload_Input( stripslashes_deep( $_POST ), $_FILES, $webformdata['layout'] );

            $errmsg .= ewz_process_upload(
                                           $input->get_input_data(), $webformdata['user_id'],
                                           $webformdata['webform']->webform_id
                                          );

        } catch( Exception $e ) {
            $errmsg .= $e->getMessage();
        }
    }

    $webform_id = $webformdata['webform']->webform_id;
    // Get the info for the display from the database
    // Boolean arg for get_items_for_webform when true restricts items to those of current user
    $stored_items = array();
    try{
        $stored_items = Ewz_Item::get_items_for_webform( $webform_id, Ewz_Item::GET_USER_ITEMS );
    } catch( Exception $e ) {
        $errmsg .= $e->getMessage();
    }
    $ewzG = ewz_get_layout_info( $webformdata['layout'] );  // grabs all required layout data
    $ewzG['webform_id'] = $webform_id;
    $ewzG['num_items'] = $webformdata['webform']->num_items;
    $ewzG['errmsg'] = $errmsg;
    $ewzG['thumb_dim'] =  ewz_thumbfile_dimensions();
    $ewzG['has_data'] = ( count( $stored_items ) > 0 );
    $ewzG['jsvalid'] = Ewz_Base::validate_using_javascript();     // this is set to false for testing server validation
    $ewzG['do_warn'] = Ewz_Base::warn_on_leaving();               // this is set to false for testing since selenium cant handle it

    $ewzG['cs_warn'] =  Ewz_Settings::get_ewz_option('cs_warn');
    // in a non-block theme, this was used to pass the ewzG variable to Javascript,
    // $worked=wp_localize_script( "ewz-upload", "ewzG_$webform_id",  ewz_html_esc( $ewzG ));

    // From wp_add_inline_script docs:  with new block themes such as Twenty Twenty Two, wp_localize_script/wp_add_inline_script
    // will no longer work if called too late such as in a shortcode rendering callback function.

    if(wp_is_block_theme()) {
        $localise = json_encode(ewz_html_esc($ewzG));

        add_action('wp_footer', function() use ($localise, $webform_id ){
            printf("<script type='text/javascript'>var ewzG_$webform_id = %s</script>", $localise);
        });
    } else {
        wp_localize_script( "ewz-upload", "ewzG_$webform_id",  ewz_html_esc( $ewzG ));
    }



   global $current_user;
   wp_get_current_user();

   $output = '<h2>' . esc_html( $webformdata['webform']->webform_title ) . '</h2>';
   if( Ewz_Settings::get_ewz_option('show_auto_close') && $webformdata['webform']->auto_close ){
       $output .=  '<p class="ewz_closenote">' . ewz_safe_tags( Ewz_Settings::get_ewz_option('will_close_txt') );
       $output .=  ' ' .  $webformdata['webform']->auto_close_time .  ' ' . $webformdata['webform']->auto_close_date .'</p>';
   }
   // Display of stored data for confirmation.  Only displayed if there is some stored data.
   if( count( $stored_items ) > 0 ){
       $output .= ewz_current_status(
                                       $stored_items,
                                       $webformdata['layout']->fields,
                                       $current_user->display_name,
                                       $webform_id
                                      );
    }
    // Display of upload form, hidden if there is stored data
    $output .= ewz_upload_form( $stored_items,
                                $webformdata['layout'],
                                $webformdata['webform'] );

    return $output;
}


/**
 * Return all the required information about the webform
 *
 * @param   array<mixed>  $atts   attributes the shortcode was called with
 * @return  array<mixed> containing user, webform and layout info
 */
function ewz_get_webform_data( $atts )
{
    $data = array();

    $shortcode_err = ewz_check_upload_atts( $atts );
    if ( $shortcode_err ) {
        $data['failmsg'] =  __("Error in upload form: ", 'entrywizard' ) .  $shortcode_err;
        return $data;
    }



    /* ****************** */
    /* Check user status  */
    /* ****************** */
    wp_get_current_user();
    if ( !is_user_logged_in() ) {
        $data['failmsg'] = __('Sorry, you must be logged in to see this.', 'entrywizard');
        return $data;
    }

    /* ******************* */
    /* Collect some  data  */
    /* ******************* */

    $webform = new Ewz_Webform( $atts['identifier'] );

    if( !$webform->open_for_current_user() ){
        $data['failmsg'] = '<div class="ewz-err"><h2>';
        if( Ewz_Settings::get_ewz_option('show_title_if_closed') ){
            $data['failmsg'] .=  esc_html( $webform->webform_title );
        }
        $data['failmsg'] .=    "</h2>\n";
        if( $webform->auto_open && Ewz_Settings::get_ewz_option('show_auto_open') ) {
            $data['failmsg'] .=  '<p class="ewz_closenote">' .  ewz_safe_tags( Ewz_Settings::get_ewz_option('will_open_txt') ) ;
            $data['failmsg'] .=  ' ' .  $webform->auto_open_time .  ' ' . $webform->auto_open_date .'</p>';
        } else {
            $data['failmsg'] .=    "\n<p>" . ewz_safe_tags( Ewz_Settings::get_ewz_option('upload_closed_txt') ) . "</p>";
        }
        $data['failmsg'] .= '</div>';
        return $data;
    }

    $data['user_id'] = get_current_user_id();
    $data['webform'] = $webform;
    $data['layout'] = new Ewz_Layout( $webform->layout_id, Ewz_Layout::EXCLUDE_FOLLOWUP );
    return $data;
}

/**
 * Return an html string to display the upload form
 *
 * The upload form is displayed if there is no stored data, or
 *   when  "Add, Change or Delete" is clicked
 ***************************************************************************************
 *                         Ewz_Item data format
 * -----------------------------------------------------------------------------------
 * item_id
 * user_id
 * webform_id
 * last_change
 * upload_date
 * item_files:
 *   ( "444" => (field_id => 444, thumb_url => ..., fname => ..., type=>jpg, width=>.., height=>.., orient=>...  )
 *     "544" => (field_id => 544, thumb_url => ..., fname => ..., type=>jpg),
 *      ....)
 * item_data:   ( "11"=> (field_id => 11, value => 'P'),
 *                "25"=> (field_id => 25, value => 'A'),
 *                "34"=> (field_id => 34, value => 'sssssssssssss')
 *              )
 *************************************************************************************
 * @param  array<Ewz_Item>       $stored_items     Array of Ewz_Items
 * @param  Ewz_Layout  $layout
 * @param  Ewz_Webform $webform
 * @return  string
 */
function ewz_upload_form( $stored_items, $layout, $webform )
{
    $output = '';
    $has_data = ( count( $stored_items ) > 0 );

    $fields_arr = array_values( $layout->fields );
    $webform_id = $webform->webform_id;
    $hidemodify = '';
    if ( $has_data ) {
        $hidemodify = 'style="display:none"';
        $output .= '<div class="ewz_pcentre"><button type="button" id="ewz_change_' .
            esc_attr( "$webform_id" ) .
            '" onClick="show_modify(this, ' . esc_js( "$webform_id" ) .
            ')">' . esc_html__('Add, Change or Delete', 'entrywizard') . '</button></div>';
    }

    $form_action = esc_attr( get_permalink() );
    $output .= '<div id="ewz_modify_' . esc_attr( "$webform_id" ) .'" ' . $hidemodify . '>';
    $output .= '<span id="ewz_warn_' . esc_attr( "$webform_id") .'"></span>';
    //  autocomplete="off"  required for FF, otherwise it saves old values and uses them
    //  after an upload
    $output .= '<form  autocomplete="off" id="ewz_form_' . esc_attr( "$webform_id" ) .
            '" method="POST" action="' . esc_js( $form_action ) . '" ';
    $output .= ' enctype="multipart/form-data"  encoding="multipart/form-data" >';
    $output .= '<div class="ewzform">';
    $output .= wp_nonce_field( 'ewzupload', 'ewzuploadnonce', true, false );
    $output .= "\n";
    $output .= '<input type="hidden" name="webform_id" value="' .  esc_attr( "$webform_id" ) . '">';
    $output .= '<input type="hidden" name="identifier" value="' . esc_attr( "$webform->webform_ident" ) . '">';
    $output .= '<div id="scrollablediv_'. esc_attr( "$webform_id" ) . '" class="ewz_overflow">';
    $output .= "\n";
    $append = array_map ( function($v){ return $v->append == 1; },  $fields_arr );
    $showfield = array_map ( function($v){ return (('lab' !== $v->field_type) || ('lab' == $v->field_type && $v->fdata['showlabel'] )); }, $fields_arr );

    // define these so we can always refer to the "next" field
    $append[ count($fields_arr) ] = false;
    $showfield[ count($fields_arr) ] = false;

    $fields_arr1 = array_filter ( $fields_arr, function($v ){ return $v->all_items == 1; } );
    $fields_arr2 = array_filter ( $fields_arr, function($v ){ return $v->all_items == 0; } );

/***************** All-Items Fields ******************/
    if( $fields_arr1 ){
        $output .= '<table id="ewz_all_table_' .  esc_attr( "$webform_id" ) . '" class="ewz_upload_table ewz_padded">';
        $output .= "\n";

        $output .= '<tbody>';
        $output .= '<tr>';
        foreach ( $fields_arr1 as $n => $field ) {
            // assert( $n < count($fields_arr) );
            $reqflag = $field->required ? '*' : ' ';

            if( $showfield[$n] ){
                $output .= "<th>";
                $output .= $reqflag;
                $output .= esc_html( $field->field_header );
                $output .= ':</th>';
            }
            $savedval = '';
            if( $stored_items ){
                $savedval = ewz_get_saved_value( $field, $stored_items[0] );
            }
            $output .= '<td>';

            // dont allow editing of data that has already been included in the filename
            if( $stored_items && $webform->apply_prefix && ( strpos( $webform->prefix, '[~'.$field->field_ident.']' ) !== false ) ){
                $output .=   ewz_display_webform_field( -1, $webform_id, $savedval, $field, true );
            } else {
                $output .= ewz_display_webform_field( -1, $webform_id, $savedval, $field, false );
            }
            $output .= '</td>';
            $output .= "</tr>\n";
        }
        $output .= "</tbody></table>";

        $output .= '<p class="ewz_spacer"></p>';
    }
/***************** Per-Item Fields ******************/

    if( $fields_arr2 ){
        $output .= '<table id="ewz_item_table_' .  esc_attr( "$webform_id" ) . '"  class="ewz_upload_table ewz_padded">';
        $output .= "\n";


        //  header line
        $output .= '<thead>   <tr>';
        foreach ( $fields_arr2 as $n => $field ) {
            // assert( $n < count($fields_arr) );
            $reqflag = $field->required ? '*' : ' ';

            if( $showfield[$n] ){
                if( $append[$n] ){
                    $output .= ' / <br \>';
                } else {
                    $output .= "<th>";
                }
                if( !( $append[$n] || $append[$n+1] ) ){
                    $output .= $reqflag;
                }
                $output .= esc_html( $field->field_header );
                if( !$append[$n+1] ){
                    $output .= '</th>';
                }
            }
        }
        if( $has_data ){
            $output .= '     <th class="ewz-btn"></th>';
        }
        $output .= "   </tr></thead><tbody>\n";


        // item rows
        $row = 0;
        foreach ( $stored_items as $p => $item ) {
            // assert( $p<count($stored_items ) );
            $output .='<tr id="row' . $row . '_' . esc_attr( "$webform_id" ) . '">';
            foreach ( $fields_arr2 as $n => $field ) {
                $reqflag = $field->required ? '*' : ' ';
                if( $showfield[$n] ){
                    $savedval = ewz_get_saved_value( $field, $item );
                    if( $append[$n] ){
                        $output .= '<br \>';
                    } else {
                        $output .= '<td>';
                    }
                    if( $append[$n] || $append[$n+1] ){

                        $output .= "<label for='rdata_{$row}__{$field->field_id}__$webform_id'>" .  $reqflag . esc_html( $field->field_header ) . ':</label>';
                    }

                    // dont allow editing of data that has already been included in the filename
                    if( $webform->apply_prefix && ( strpos( $webform->prefix, '[~'.$field->field_ident.']' ) !== false ) ){
                        $output .=   ewz_display_webform_field( $row, $webform_id, $savedval, $field, true );
                    } else {
                        $output .= ewz_display_webform_field( $row, $webform_id, $savedval, $field, false );
                    }
                    if( !$append[$n+1] ){
                        $output .= '</td>';
                    }
                }
            }
            $output .= '<td class="ewz-btn"><input type="hidden"   name="item_id[' . $row . ']" value="' .  esc_attr( "$item->item_id" ) . '">';
            $output .= '<button  type="button" id="delete_row' . $row . '_' . esc_attr( "$webform_id" ) .
                '" onClick="delete_item(this, ' . esc_js( "$webform_id" ) . ')">' . esc_html__('Delete', 'entrywizard') . '</button>';
            $output .= '</td>';
            $output .= "</tr>\n";
            ++$row;
        }
        // add blank rows to fill to max_num_items
        while ( $row < $webform->num_items ) {
            $output .="<tr id='row" . $row . "_" . esc_attr( "$webform_id" ) . "'>";

            foreach ( $fields_arr2 as $n => $field ) {
                if( $showfield[$n] ){
                    $reqflag = $field->required ? '*' : ' ';
                    if( $append[$n] ){
                        $output .= '<br \>';
                    } else {
                        $output .= '<td>';
                    }
                    if( $append[$n] || $append[$n+1] ){
                        $output .= "<label for='rdata_{$row}__{$field->field_id}__$webform_id'>" .  $reqflag . esc_html( $field->field_header ) . ':</label>';
                    }
                    $output .=  ewz_display_webform_field( $row, $webform_id, '', $field, false );
                    if( !$append[$n+1] ){
                        $output .= '</td>';
                    }
                }
            }
            if( $has_data ){
                 $output .= '<td class="ewz-btn"></td>';
            }
            $output .= "</tr>\n";

            ++$row;
        }

        $output .= "</table>";
    }
    $output .= "</div>\n";

    /*     * ******************************** */
    /* Submit button and progress area */
    /*     * ******************************** */
    // progress bar above submit button so visible after submit is clicked.
    $esc_wid = esc_attr( "$webform_id" );
    $output .= '      <div class="ewz_progress" >';
    $output .= '            <div id="pleasewait_' . $esc_wid . '"></div>';
    $output .= '            <div id="progress_info_' . $esc_wid . '">';
    $output .= '                <div id="complete_' . $esc_wid . '">';
    $output .= '                   <div id="progress_bar_' . $esc_wid . '"></div>';
    $output .= '                </div>';
    $output .= '                <div id="progress_percent_' . $esc_wid . '"></div>';
    $output .= '                <div class="ewz_clear_both"></div>';
    $output .= '                <div>';
    $output .= '                    <div id="speed_' . $esc_wid . '"></div>';
    $output .= '                    <div id="remaining_' . $esc_wid . '"></div>';
    $output .= '                    <div id="b_transfered_' . $esc_wid . '"></div>';
    $output .= '                    <div class="ewz_clear_both"></div>';
    $output .= '                </div>';
    $output .= '                <div id="upload_response_' . $esc_wid . '"></div>';
    $output .= '            </div>';
    $output .= '      </div>';
    $output .= '      <div class="ewz_pcentre"><button type="button" disabled="disabled" id="ewz_fsubmit_' . $esc_wid;
    $output .=             '" onclick="startUploading( ' . esc_js( "$webform_id" ) . ')" >' . esc_html__('Submit', 'entrywizard') .'</button>';
    $output .= '      </div>';

    $output .= '</div>';

    $output .= "</form>\n";
    $output .= "</div>\n";

    return $output;
}

/**
 * Return the html for displaying a single field
 *
 * @param   int         $rownum    row number
 * @param   int         $webform_id
 * @param   string      $savedval  data currently stored for the field
 * @param   Ewz_Field   $field     field info
 * @param   boolean     $fixed
 * @return  string      $display   -- the html
 */
function ewz_display_webform_field( $rownum, $webform_id, $savedval, $field, $fixed )
{
    if( $rownum == -1 ){
        $ename  = esc_attr( "rdata1[" . $field->field_id . "]" );
    } else {
        $ename  = esc_attr( "rdata[$rownum][" . $field->field_id . "]" );
    }
    $input_id =  str_replace( '[', '_', str_replace( ']', '_', $ename ) ) . '_' . esc_attr( "$webform_id" );

    $display = '<div class="fdisplay">';

    switch ( $field->field_type ) {
    case 'str': $display .= ewz_display_str_form_field( $ename, $input_id, $savedval, $field );
            break;
    case 'opt': $display .= ewz_display_opt_form_field( $ename, $input_id, $savedval, $field, $fixed );
            break;
    case 'rad': $display .= ewz_display_rad_form_field( $ename, $input_id, $savedval, $field );
            break;
    case 'chk': $display .= ewz_display_chk_form_field( $ename, $input_id, $savedval );
            break;
    case 'img': $display .= ewz_display_img_form_field( $ename, $input_id, $savedval, $field, $webform_id );
            break;
    case 'lab': $display .= ewz_display_lab_form_field( $ename, $input_id, $savedval, $field );
            break;
    default:
        throw new EWZ_Exception( sprintf( __("Invalid field type %s ", 'entrywizard' ), $field->field_type ) );
    }
    $display .= '</div>';
    return $display;
}

/**
 * Return the value saved on the database for the item
 *
 * @param  Ewz_Field  $field
 * @param  Ewz_Item   $item
 * @return string
 */
function ewz_get_saved_value( $field, $item ){
    $savedval = '';
    if ( 'img' == $field->field_type ) {
        if ( array_key_exists( $field->field_id, $item->item_files ) ) {
            $savedval = $item->item_files[$field->field_id]['thumb_url'];
        }
    } else {
        if ( isset( $item->item_data[$field->field_id]['value'] ) ) {
            $savedval = $item->item_data[$field->field_id]['value'];
        }
    }
    return $savedval;
}



/**
 * Generate a javascript array to store info needed in client
 *
 * @param   Ewz_Layout $layout
 * @return  array<mixed>      $ewzG
 */
function ewz_get_layout_info( $layout )
{
    global $post;
    $ewzG = array('layout' => $layout);

    // required for viewer-side ajax, automatically defined for admin
    $ewzG['ajaxurl'] = admin_url('admin-ajax.php', (is_ssl() ? 'https' : 'http'));
    $ewzG['uploadurl'] = get_permalink( $post );
    $ewzG['load_gif'] = plugins_url( 'images/loading.gif' , dirname(__FILE__) ) ;
    $ewzG['margin'] =  Ewz_Settings::get_ewz_option('margin');

    $ewzG['nomorethan'] = esc_html__("No more than %d items may have ",  'entrywizard');
    $ewzG['clear_msg'] = esc_html__('Clear',  'entrywizard');
    $ewzG['server_err'] =  esc_html__('Sorry, there was an unknown server error: ',  'entrywizard');
    $ewzG['refresh_msg'] = esc_html__("Refresh the page to see the current status.",  'entrywizard');
    $ewzG['row_err'] =  esc_html__("Sorry, there was an error in row %d ",  'entrywizard');
    $ewzG['req_msg_err'] = esc_html__("Sorry, there are some required items missing: ", 'entrywizard');
    $ewzG['upload_err'] = esc_html__("An unknown error occurred while uploading the data.", 'entrywizard');
    $ewzG['abort_err'] = esc_html__("Either you cancelled the upload, or your browser dropped the conection.", 'entrywizard');
    $ewzG['ftype_err'] = esc_html__("Either it's type could not be detected or it is not an acceptable image type for this application.", 'entrywizard');
    $ewzG['ismall_err'] = esc_html__("Its longest dimension is smaller than the minimum of: %d pixels.", 'entrywizard');
    $ewzG['enlarge_err'] = esc_html__("It could be enlarged up to: %s.", 'entrywizard');
    $ewzG['notaccepted'] = esc_html__("Sorry, this image will not be accepted.", 'entrywizard');
    $ewzG['margin_err'] = esc_html__("It could be enlarged within the required bounds of: ", 'entrywizard');
    $ewzG['isize_err'] = esc_html__("It does not fit within the required bounds of: ", 'entrywizard');
    $ewzG['fsize_err'] = esc_html__("Its size ( %d ) is greater than the limit of ", 'entrywizard');
    $ewzG['pixels_msg'] = esc_html__("pixels", 'entrywizard');
    $ewzG['confirm_msg'] = esc_html__("You have not clicked 'Submit'. Changes you made may not be saved.", 'entrywizard');
    $ewzG['toonarrow_err'] = esc_html__("The available space is too narrow to display all of this form. You should see a horizontal scrollbar just above the submit button.", 'entrywizard');
    $ewzG['wait'] = esc_html__('PLEASE WAIT until all processing is finished and the screen refreshes. ', 'entrywizard');
    $ewzG['complete'] = esc_html__('PLEASE WAIT. Upload is complete, processing may take a few moments .... ', 'entrywizard');
    $ewzG['oldform'] = esc_html__('Upload may take some time, depending on image size and network speed.  More feedback is available using a browser with better support for HTML5.', 'entrywizard');
    $ewzG['size_msg'] = esc_html__("Size: ", 'entrywizard');
    $ewzG['cspace_msg'] = esc_html__('Colour Space: ', 'entrywizard');
    $ewzG['width_msg'] = esc_html__('Width: ', 'entrywizard');
    $ewzG['type_msg'] = esc_html__('Type: ', 'entrywizard');
    $ewzG['height_msg'] = esc_html__('Height: ', 'entrywizard');
    $ewzG['colsp_warn'] = esc_html__("Caution, the color space should be sRGB. It not set in the image's EXIF data, which MAY indicate a problem. Make sure the image is really sRGB.", 'entrywizard');
    $ewzG['cantread_msg'] = esc_html__("Unable to read image.", 'entrywizard');
    $ewzG['upload_fail_msg'] = esc_html__("Upload may not have succeeded: ", 'entrywizard');
    $ewzG['will_reload_msg'] = esc_html__("Page will reload to show current status.", 'entrywizard');
    $ewzG['upload_ng_msg'] = esc_html__("Error in upload: ", 'entrywizard');
    $ewzG['upload_abort'] = esc_html__("Upload aborted: ", 'entrywizard');


    $ewzG['iBytesUploaded'] = 0;
    $ewzG['iBytesTotal'] = 0;
    $ewzG['iPreviousBytesLoaded'] = 0;
    $ewzG['timer'] = 0;
    $ewzG['inProgress'] = false;
    $ewzG['sResultFileSize'] = '';

    return $ewzG;
}

/**
 * Return the html for the "current status" display, plus an array of the data
 *
 * @param   array<mixed>   $stored_items  --  contents of an item table row
 * @param   array<mixed>   $fields
 * @param   string  $name -- current user name
 * @param   int     $webform_id -- id of webform being used for upload
 * @return  string  $output is the html
 */
function ewz_current_status( $stored_items, $fields, $name, $webform_id ) {

    $fields_arr = array_values($fields);

    $output = '<div id="ewz_stored_' . esc_attr( "$webform_id" ) . '">';
    $output .= ewz_safe_tags( Ewz_Settings::get_ewz_option('curr_stored_txt') ) . " $name: <br \><br \>";

    $append = array_map ( function($v){ return $v->append == 1; },  $fields_arr );
    $append[ count($fields_arr) ] = false;
    $showfield = array_map ( function($v){ return ('lab' !== $v->field_type || ('lab' == $v->field_type && $v->fdata['showlabel'] ) ); }, $fields_arr );
    $fields_arr1 = array_filter ( $fields_arr, function($v ){ return $v->all_items == 1; } );
    $fields_arr2 = array_filter ( $fields_arr, function($v ){ return $v->all_items == 0; } );

    /***************** All-Items Fields ******************/
    if(  $fields_arr1  ){
        $output .= '<table id="all_items_table_' . esc_attr( "$webform_id" ) . '" class="ewz_upload_table ewz_padded">';
        $output .= "\n";

        $output .= '<tbody>';
        $output .= "   <tr>";
        $item = $stored_items[0];
        foreach ( $fields_arr1 as $n => $field ) {
            // assert( $n < count($fields_arr) );
            if( $showfield[$n] ){
                $reqflag = $field->required ? '*' : ' ';
                $output .= '<th>';
                $output .= $reqflag;
                $output .= esc_html(  $field->field_header );
                $output .= '</th>';
                $output .= '<td>';

                if ( isset( $item->item_data[$field->field_id] ) ) {
                    $output .= esc_html( ewz_display_item( $field, $item->item_data[$field->field_id]['value'] ) );
                }
                $output .= '</td>';
            }
        }
        $output .= "</tr>\n";
        $output .= "</tbody></table>";
        $output .= '<p class="ewz_spacer"></p>';
    }
    /***************** Per-Item Fields ******************/

    if(  $fields_arr2  ){
        $output .= '<table id="datatable_' . esc_attr( "$webform_id" ) . '"  class="ewz_upload_table ewz_padded"><thead>';
        $output .= '   <tr>';
        foreach ( $fields_arr2 as $n => $field ) {
            // assert( $n < count($fields));
            if( $showfield[$n] ){
                if( $append[$n] ){
                    $output .= ' / <br \>';
                } else {
                    $output .= "<th>";
                }
                $output .=  esc_html( $field->field_header );
                if( !$append[$n+1] ){
                    $output .= '</th>';
                }
            }
        }
        $output .=    '</tr>';
        $output .= '</thead><tbody>';
        clearstatcache();
        foreach ( $stored_items as $m => $item ) {
            // assert( $m < count($stored_items));
            $output .= '<tr>';
            foreach ( $fields_arr2 as $n => $field ) {
                if( $showfield[$n] ){
                    if( $append[$n] ){
                        $output .= '<br \>';
                    } else {
                        $output .= '<td>';
                    }
                    if( $append[$n] || $append[$n+1] ){
                        $output .='<b>' . esc_html( $field->field_header ) . ":</b> ";
                    }
                    if ( 'img' == $field->field_type ) {
                        // an image field
                        if ( array_key_exists( $field->field_id, $item->item_files ) ) {
                            if ( is_array( $item->item_files[$field->field_id] ) ) {
                                $thumbpath = $item->item_files[$field->field_id]['fname'];
                                $mtime = is_file( $thumbpath ) ? filemtime($thumbpath) : '99';
                                $thumburl = esc_attr($item->item_files[$field->field_id]['thumb_url']);
                                $output .= '<img src="' . $thumburl . "?$mtime" . '">';   // escaped
                                $output .= '<br \>' . esc_html( basename( $item->item_files[$field->field_id]['fname'] ) );
                            } else {
                                $output .= esc_html( $item->item_files[$field->field_id] );
                            }
                        }
                    } else {
                        // a data field
                        if ( 'lab' == $field->field_type || isset( $item->item_data[$field->field_id] ) ) {
                            $output .= esc_html( ewz_display_item( $field, $item->item_data[$field->field_id]['value'] ) );
                        }
                    }
                    if( !$append[$n+1] ){
                        $output .= '</td>';
                    }
                }
            }
            $output .= '</tr>';
        }
        $output .= "</tbody></table>";
    }
    $output .= '</div>';       // <div id="stored">
    return $output;
}

/**
 * Process the uploaded data
 *
 *
 *
 * @param  array<mixed>  $postdata    sanitized version of $_POST
 * @param  int    $user_id     id of current user
 * @param  int    $webform_id  id of the webform
 * @return string blank or error message  -- a hack to allow server checking of image dimensions, etc
 *                                    without blocking subsequent items.  Only needed for older IE.
 */
function ewz_process_upload( $postdata, $user_id, $webform_id )
{
    /*     * ************************** */
    /* Get the field information */
    /*     * ************************** */
    $webform = new Ewz_Webform( $webform_id );
    $layout = new Ewz_Layout( $webform->layout_id, Ewz_Layout::EXCLUDE_FOLLOWUP );

    // Reformat the post data to a more usable form,  upload any files,
    // and add the uploaded file data to the item data

    $post_arr = ewz_to_upload_arr( $webform_id, $postdata, $layout->fields );

    /* ********************************************************* */
    /* Process the items                                         */
    /* If there is matching data stored, update the database     */
    /* Otherwise, create a new item entry                        */
    /* ********************************************************* */
    $errs = '';
    $created_count = 0;
    $updated_count = 0;
    foreach ( $post_arr as $row => $item ) {
        // $row index may not be 0,1,... if rows are missing
        $data = array(
            'user_id' => $user_id,
            'webform_id' => $webform_id,
            'item_data' => $item['data'],
        );
        if ( array_key_exists( 'item_id', $item ) ) {
            $data['item_id'] = $item['item_id'];
        }
        // only create item_files if we are uploading.  $item['files'] is created from $_FILES in ewz_to_upload_arr
        if ( array_key_exists( 'files', $item ) ) {
            foreach( $item['files'] as $field_id => $uploaded_file ){
                // error is stored here instead of being raised as an exception because older IE's dont allow
                // checking dimensions on client.  We don't want to ignore the rest of the upload if one
                // has a dimension error.
                if( preg_match('/^___/', $uploaded_file['fname'] ) ){
                    // i.e there was an error picked up by ewz_handle_img_upload in ewz_to_upload_arr
                    $errs .= "<br>" . preg_replace('/^___/', '', $uploaded_file['fname'] );
                    $data = NULL;
                } else {
                    $data['item_files'][$field_id] = $uploaded_file;
                }
            }
        }
        if( $data ){
            try {
                $item_obj = new Ewz_Item( $data );
                $status = $item_obj->save();
                $updated_count += $status['updated'];
                $created_count += $status['created'];
            } catch( Exception $e ) {
                $errs .= $e->getMessage();
            }
        }
    }
    do_action('ewz_after_upload', $webform->webform_title, $user_id, $created_count, $updated_count );
    return $errs;
}

/**
 * Reformat the input data and add the uploaded file data
 *
 * To aid in correlating POST data with FILES, output array is indexed by the row number,
 *      so it will have missing items if there are blank rows in the uploaded data.
 *
 * @param   int    $webform_id id of webform
 * @param   array<mixed>  $postdata   sanitized version of $_POST
 * @param   array<Ewz_Field>  $fields     array of Ewz_Fields indexed on field_id
 * @return  array<mixed>  $upload     uploaded items
 */
function ewz_to_upload_arr( $webform_id, $postdata, $fields ) {

    $upload = array( );
    foreach ( $postdata['rdata'] as $row => $datavalues ) {
        // there may be missing rows, so $rows may not be 0,1,2...
        foreach ( $datavalues as $field_id => $val ) {
            $upload[$row]['data'][$field_id] = array( "field_id" => $field_id, "value" => $val );
        }
        if( isset( $postdata['rdata1'] ) ){
            // apply the "all_items" fields to each item
            foreach ( $postdata['rdata1'] as $field_id1 => $val1 ) {
                $upload[$row]['data'][$field_id1] = array( "field_id" => $field_id1, "value" => $val1 );
            }
        }
    }

    if ( array_key_exists( 'item_id', $postdata ) ) {
        foreach ( $postdata['item_id'] as $row => $item_id ) {
            // there may be missing rows, so $rows may not be 0,1,2...
            $upload[$row]['item_id'] = $item_id;
        }
    }

    if ( $_FILES ) {
        $webform = new Ewz_Webform( $webform_id );

        // ensure the next uploaded items are stored in uploads/ewz_upload_dir
        add_filter( 'upload_dir', array( $webform, 'ewz_upload_dir' ) );
        $subst_data = array();
        if( $webform->apply_prefix ){
            $user_id = get_current_user_id();
            $customdata = new Ewz_Custom_Data( $user_id );
            $subst_data = array(
                                'user_id' => $user_id,
                                );
            foreach ( (array)$customdata as $custkey => $custval ) {
                $subst_data[$custkey] = $custval;
            }
        }
        foreach ( $_FILES['rdata']['name'] as $row => $fileset ) {
            // there is a $_FILES['rdata']['name'] for each "used" row with a file input, indexed on row number
            foreach ( $fileset as $field_id => $filename ) {
                if( isset( $filename ) && $filename ){
                    $prefix = '';
                    $newfilename = $filename;

                    // if filename ends in a digit, we normally add an underscore to the end, but not
                    // if the final digit came from a prescribed "prefix" being used as the filename
                    $add_underscore = !( $webform->apply_prefix && $webform->gen_fname );

                    if( $webform->apply_prefix ){
                        // do the substitutions in the prefix
                        foreach( $upload[$row]['data'] as $fid => $val ){
                            $subst_data[$fid] = $val;
                        }
                        $subst_data['file_field_id'] = $field_id;
                        $prefix = $webform->generated_prefix( $subst_data );

                        if( $webform->gen_fname ){
                            $ext = pathinfo( $filename, PATHINFO_EXTENSION );
                            if ( strpos( $prefix, '[~1]' ) !== false ) {
                                // make sure the filename is unique for the directory -- append 1,2,3,... until it is
                                clearstatcache();
                                $uniq = ewz_get_fname_num( $prefix, $ext, EWZ_IMG_UPLOAD_DIR . '/' . $webform->webform_ident );
                                $newfilename = str_replace( '[~1]', $uniq, $prefix ). '.' . $ext;
                            } else {
                                $newfilename =  $prefix . '.' . $ext;
                            }
                        } else {
                            // let WP take care of making the name unique.
                            $newfilename = $prefix . $filename;
                        }
                    }
                    try{
                        // sanitize $prefix.$filename and if ok save the uploaded image
                        $upload[$row]['files'][$field_id] = ewz_handle_img_upload( $newfilename, $row, $fields[$field_id],
                                                                                   $add_underscore );
                    } catch( Exception $e ){
                        $upload[$row]['files'][$field_id]['fname'] = '___' . $e->getMessage();
                    }
                }
            }
        }
        // back to WP normal upload directory
        remove_filter( 'upload_dir', array( $webform, 'ewz_upload_dir' ) );
    }
    return $upload;
}

/**
 * Create the thumbnail for use with EntryWizard
 *
 *
 * @param  string $img_filepath  path to the image to be thumbnailed
 * @return string path to the newly-created thumbnail
 */
function ewz_create_thumbfile( $img_filepath ){
    if( ! ewz_is_valid_ewz_path( $img_filepath ) ){
        throw new EWZ_Exception( __("Invalid file path '$img_filepath'", 'entrywizard') );
    }

    // create the thumbnail
    $dim =  ewz_thumbfile_dimensions();

    $thumb_filepath =  ewz_thumbfile_path( $img_filepath );

    $image = wp_get_image_editor( $img_filepath );
    if ( is_wp_error( $image ) ) {
        throw new EWZ_Exception( sprintf( __('error reading image %s: %s', 'entrywizard' ), basename( $img_filepath ), $image->get_error_message() ) );
    } else {
        $image->resize( $dim['w'], $dim['h'], false );
        $image->save( $thumb_filepath );
    }
    return $thumb_filepath;
}


/**
 * Save the uploaded image file in the correct folder and generate the thumbnail
 *
 * Return an array containing the filename, type, width, height, orientation
 * NB:  should be called with the upload directory set to 'ewz_upload_dir' using
 *      the 'upload_dir' filter
 *
 * @param   string     $filename  location of temp uploaded file
 * @param   int        $row       row of upload form
 * @param   Ewz_Field  $field     layout field under which the image was uploaded
 * @param   boolean    $appendU   do we add an underscore to the end of a filename ending in a digit
 * @return  array<mixed>  of  field_id,thumb_url,filename, type, width, height, orientation
 */
function ewz_handle_img_upload( $filename,  $row,  $field, $appendU ){

    if ( $filename ) {
        $filename = ewz_to_valid_fname( $filename,  $appendU );
        $field_id = $field->field_id;
        $file = array(
                      'name'     => $filename,
                      'type'     => $_FILES['rdata']['type'][$row][$field_id],
                      'tmp_name' => $_FILES['rdata']['tmp_name'][$row][$field_id],
                      'error'    => $_FILES['rdata']['error'][$row][$field_id],
                      'size'     => $_FILES['rdata']['size'][$row][$field_id]
                      );

        $errmsg = ewz_image_file_check( $file, $field );

        if ( $errmsg ) {
            throw new EWZ_Exception( __('Image ', 'entrywizard' ) . basename( $filename) .  __(" not uploaded:", 'entrywizard') . "<br>$errmsg" );
        } else {
            if ( !function_exists( 'wp_handle_upload' ) ) {
                require_once( ABSPATH . 'wp-admin/includes/file.php' );
            }

            // wp handles creating the image file and folder, no need for mkdir
            // NB: this is done with the upload directory set to 'ewz_upload_dir'
            $uploaded_file = wp_handle_upload( $file, array( 'test_form' => false ) );

            // $uploaded_file now contains array('file'=>path, 'url'=>url, 'type'=>mime)
            if ( isset( $uploaded_file['file'] ) ) {
                $size = getimagesize( $uploaded_file['file'] );

                $thumbfile = ewz_create_thumbfile( $uploaded_file['file'] );
                $thumburl = ewz_file_to_url( $thumbfile );

                if ( $thumbfile == $uploaded_file['file'] ) {
                    throw new EWZ_Exception( __("Thumb filename ", 'entrywizard' ) . basename( $thumbfile ) . __(' is same as original filename.', 'entrywizard') );
                }
                return  array( 'field_id'  => $field_id,
                               'thumb_url' => $thumburl,
                               'fname'     => $uploaded_file['file'],
                               'type'      => $uploaded_file['type'],
                               'width'     => $size[0],
                               'height'    => $size[1],
                               'orient'    => ( $size[0] > $size[1] ) ? 'L' : 'P',
                               );
            } else {
                throw new EWZ_Exception( sprintf(__('Error in file upload for %s: ', 'entrywizard'),  basename( $filename ) ) .  ewz_upload_errmsg( $uploaded_file['error'] ) );
            }
        }
    }
    return array();
}


/**
 * Check an uploaded image file for size, dimensions, etc
 *
 * @param   array<mixed>      $imgfile_data    uploaded data for the file
 * @param   Ewz_Field  $field_data  stored characteristics for the field the file was uploaded by
 * @return  string  error message or blank
 */
function ewz_image_file_check( $imgfile_data, $field_data ) {

    $maxw = $field_data->fdata['max_img_w'];  // ['max_img_w'];
    $maxh = $field_data->fdata['max_img_h'];  // ['max_img_h'];
    $maxs = $field_data->fdata['max_img_size'] * 1048576;  // ['max_img_size'] * 1048576;
    $minld = $field_data->fdata['min_longest_dim'];  // ['min_longest_dim'];
    $canrot = $field_data->fdata['canrotate'];
    $mustfill = isset( $field_data->fdata['mustfill'] ) ? $field_data->fdata['mustfill'] : false;
    $types = $field_data->fdata['allowed_image_types'];  // ['allowed_image_types'];

    if ( ( 0 === $imgfile_data['size'] ) && ( 0 === $imgfile_data['error'] ) ) {
        $imgfile_data['error'] = UPLOAD_ERR_EMPTY;
    }

    // first check the things we already have the info for

    if ( $imgfile_data['error'] ) {
        return __("File failed to upload: ", 'entrywizard') . ewz_upload_errmsg( $imgfile_data['error'] );
    }
    if ( !in_array( $imgfile_data['type'], array_values( $types ) ) ) {
        return __("The image file appears to be of type: ", 'entrywizard') . $imgfile_data['type'] .
            __(" It should be one of ", 'entrywizard') . join( ',', array_values( $types ) ) . " .";
    }
    if ( $imgfile_data['size'] > $maxs ) {
        return sprintf(__("Image file is larger than the limit of %dM", 'entrywizard'), $field_data->fdata['max_img_size'] );
    }

    // ok, passed. Now check the dimension constraints
    $imgResource = wp_get_image_editor( $imgfile_data['tmp_name'] );
    if ( is_wp_error( $imgResource ) ) {
        return __("Unable to read image file: ", 'entrywizard') . $imgResource->get_error_message();
    }
    $size = $imgResource->get_size();
    $w = $size['width'];
    $h = $size['height'];

    // Max dimensions are always set for landscape mode.
    // If rotation allowed and image is in portrait format, interchange max width and height
    if ( $canrot && ( $h > $w ) ) {
        $tmp = $maxw;
        $maxw = $maxh;
        $maxh = $tmp;
    }

    if ( ( $w > $maxw ) || ( $h > $maxh ) ) {
        $msg = "Image dimensions $w x $h do not fit within the allowed dimensions of $maxw pixels wide x $maxh pixels high";
        if ( $canrot ) {
            $msg .= sprintf(__(" ( or %d pixels wide x %d  pixels high ) ", 'entrywizard'), $maxw, $maxh);
        }
        return $msg;
    }
    if( $mustfill ){
        $margin = Ewz_Settings::get_ewz_option('margin');
        if( ( $w < $maxw - $margin ) && ( $h < $maxh - $margin ) ){
            $msg = sprintf(__("Sorry, this image will not be accepted.<br>It could be enlarged and still be within the required bounds of %s.", 'entrywizard') , " $maxw x $maxh");
            return $msg;
        }
    }


    $longest = ( $w > $h ) ? $w : $h;

    if ( $longest < $minld  ) {
        return sprintf(__("The longest side is only %d pixels, which is too small for this application.<br><br>The longest side should be at least %d pixels.", 'entrywizard'), $longest, $minld );
    }

    return '';
}


/**
 * User deletion of an item.
 * Called using ajax via the 'Delete' button on the Upload page.
 * Return '1' for success, message for error.
 * Runs hook ewz_after_delete_item
 *
 * @param   int  $item_id
 * @return  string
 */
function ewz_user_delete_item( $item_id ){

    $item = new Ewz_Item( $item_id );
    $user_id = $item->user_id;
    $webform = new Ewz_Webform( $item->webform_id );
    if( $webform->open_for_current_user() ){
        $missingFileMsg = $item->delete();
        if( $missingFileMsg ){
            // if there is a missing file issue here, admin needs the info.
            // Thumbnail should have been visible when button was clicked.
            error_log("EWZ: $missingFileMsg");
        }
        do_action( 'ewz_after_delete_item', $webform->webform_title, $user_id );
        return '1';
    } else {
        return __('Sorry, this form is no longer open for uploads.', 'entrywizard');
    }
}

/**
 * Validate POST data and handle any uploads
 * Called using ajax via the 'Submit' button on the Upload page for newer browsers.
 * After this, the javascript controls re-load the page
 * @return string
 */
function ewz_validate_and_upload( )
{
    if ( $_POST && isset( $_POST['identifier'] ) && is_string( $_POST['identifier'] ) ) {
        // first grab the webform data, since layout is needed for full validation.
        // Do a quick sanitization of $_POST['identifier'] before passing it -- later it's compared to allowed values.
        $atts = array( 'identifier' => preg_replace( '/[^a-z0-9 _-]/i', '_', $_POST['identifier'] ) );
        $webformdata = ewz_get_webform_data( $atts );

        // not logged in or form not open - display the html failmsg
        if ( array_key_exists( 'failmsg', $webformdata ) ) {
            return wp_strip_all_tags( $webformdata['failmsg'] );
        }

        // had problems with more than 10 3M images
        $n = $webformdata['webform']->num_items;
        $timelimit = ini_get('max_execution_time');
        if( 15 * $n > $timelimit ){
            set_time_limit ( 15 * $n );
        }

        $input = new Ewz_Upload_Input( stripslashes_deep( $_POST ), $_FILES, $webformdata['layout'] );
        // return error messages
        return ewz_process_upload( $input->get_input_data(), $webformdata['user_id'],
                                   $webformdata['webform']->webform_id );
    } else {
        return __("Invalid upload form.", 'entrywizard');
    }
}

/**
 * Check for valid shortcode arguments
 *
 * @param    array<mixed>   $atts   arguments "ewz_show_webform" was called with
 * @return  string  message to show to users
 */
function ewz_check_upload_atts( $atts )
{
    if ( !array_key_exists( 'identifier', $atts ) ) {
        return __('Missing identifier attribute for shortcode', 'entrywizard');
    }
    $idents = Ewz_Webform::get_all_idents();
    if ( !in_array( $atts['identifier'], $idents ) ) {
        return sprintf(__('Invalid identifier %s in shortcode', 'entrywizard'), $atts['identifier'] );
    }
    return '';
}


/**
 * Make sure the filename is unique for the directory
 * -- return a number to append to make it so
 *
 * @param    string   $in_fname
 * @param    string   $ext
 * @param    string   $dir
 * @return  string
 */
function ewz_get_fname_num( $in_fname, $ext, $dir ){

    // some belt-and-braces checks before a potentially long loop
    if( strlen( $in_fname ) < 5 ){
        return '';
    }
    if( strpos( $in_fname, '[~1]' ) === false ){
        return '';
    }
    if( !is_dir( $dir ) ){
        // first image, no dups
        return "1";
    }

    $num = 1;
    while( $num < 999 ){
        $testfname = str_replace( '[~1]', "$num", $in_fname );
        if( !file_exists( "{$dir}/{$testfname}.{$ext}" ) ){
            return "$num";
        }
        ++$num;
    }
    return "$num";
}
