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

require_once( EWZ_PLUGIN_DIR . "includes/ewz-common.php");
require_once( EWZ_PLUGIN_DIR . "classes/ewz-base.php");

/* * ******************************************************************** */
/* Interaction with the EWZ_ITEM table.                                   */
/* An item corresponds to a single row of uploaded data from a webform    */
/* * ******************************************************************** */

class Ewz_Item extends Ewz_Base {

    const GET_USER_ITEMS = 1;
    const GET_ALL_ITEMS = 0;

    /**
     * @var int  $item_id   key
     */
    public $item_id;

    // database
    /**
     * @var int  $user_id
     */
    public $user_id;

    /**
     * @var int  $webform_id
     */
    public $webform_id;

    /**
     * @var string    $last_change
     */
    public $last_change;

    /**
     * @var string   $upload_date
     */
    public $upload_date;

    /**
     * @var string|array<array{'thumb_url':string,'fname':string}>   $item_files
     */
    public $item_files;

    /**
     * @var string|array<mixed>   $item_data
     */
    public $item_data;

    /**
     * @var int  $layout_id
     */
    public $layout_id;

    // Keep list of db data names/types as a convenience for iteration and so
    // we can easily add new ones. Dont include item_id here for safety
    /**
     * @var array<string,string>   $varlist
     */
    public static $varlist = array(
        'user_id' => 'integer',
        'webform_id' => 'integer',
        'last_change' => 'string',
        'upload_date' => 'string',
        'item_files' => 'array',
        'item_data' => 'array',
    );

    /**
     * Return the number of items uploaded via the webform
     *
     * @param int   $webform_id
     *
     * @return int  $count
     */
    public static function get_itemcount_for_webform( $webform_id ) {
        global $wpdb;

        $count = $wpdb->get_var( $wpdb->prepare(
                                    "SELECT count(*)  FROM " . EWZ_ITEM_TABLE .
                                    " WHERE webform_id = %d", $webform_id ) );
        return (int)$count;
    }

    /**
     * Return the number of items in webforms using the input layout
     *
     * @param   int  $layout_id   id of the layout
     * @return  int  number of items
     */
    public static function get_itemcount_for_layout( $layout_id ) {
        global $wpdb;

        $db_item_count = wp_cache_get( $layout_id, 'ewz_item_count' );
        if( !$db_item_count ){
           $db_item_count = $wpdb->get_var( $wpdb->prepare(
                               "SELECT count(*)  FROM " . EWZ_WEBFORM_TABLE . " frm, " . EWZ_ITEM_TABLE . " itm " .
                               " WHERE frm.layout_id = %d AND frm.webform_id = itm.webform_id",
                           $layout_id ) );
           wp_cache_set( $layout_id, $db_item_count, 'ewz_item_count');
        }
        return $db_item_count;
    }

    /**
     * Deal with upgrade where last_change was previously the only date
     * @return void
     */
    public static function upgrade_set_upload_date() {
        global $wpdb;
        // the built-in update function won't work here
        $rowsupdated = $wpdb->query("UPDATE " . EWZ_ITEM_TABLE . " SET upload_date = last_change WHERE upload_date IS NULL" );  // no tainted data
        if( false === $rowsupdated || $wpdb->last_error ){
            throw new EWZ_Exception( 'Failed to set upload date', $wpdb->last_error );
        }
        wp_cache_flush();
    }

     /**
     * Called when a user is deleted.
     *
     * @param int $user_id
     *
     * @return void
     */
    public static function  delete_user_items( $user_id ){
        global $wpdb;
        $list = $wpdb->get_results( $wpdb->prepare( "SELECT item_id  FROM " . EWZ_ITEM_TABLE .
                                                    " WHERE user_id = %d" , $user_id ), OBJECT );        // for deletion
        $errmsg = '';
        foreach ( $list as $itm ) {
            $newItem = new Ewz_Item( $itm->item_id );
            $errmsg .= $newItem->delete(true);
        }
        if( $errmsg ){
            add_action('admin_notices', function() use ($errmsg){
                echo $errmsg ;
            });
        }
        wp_cache_flush();
    }

    /**
     * Return an array of items attached to the input webform_id
     *
     * @param   int $webform_id
     * @param   int $user_only     self::GET_USER_ITEMS (belonging to logged-in user)
     *                             or self::GET_ALL_ITEMS
     * @return  array<Ewz_Item>  of Ewz_Items
     */
    public static function get_items_for_webform( $webform_id, $user_only ) {
        global $wpdb;
        $clause = "";
        if ( $user_only == self::GET_USER_ITEMS ) {
            $clause = " AND user_id = " . get_current_user_id();
        }
        $list = $wpdb->get_results( $wpdb->prepare( "SELECT item_id  FROM " . EWZ_ITEM_TABLE .
                                                    " WHERE webform_id = %d" . $clause . " ORDER BY item_id",
                                                    $webform_id ), OBJECT );
        $items = array( );
        foreach ( $list as $itm ) {
            $newItem = new Ewz_Item( $itm->item_id );
            array_push( $items, $newItem );
        }
        return $items;
    }


    /**
     * Return those members of the input items list that match the $field_opts list of values.
     *
     * @param array<mixed>   $field_opts   validated $_POST array from webforms page
     * @param array<mixed>   $custom_opts
     * @param array<mixed>   $extra_opts
     * @param array<Ewz_Item>   $in_items     list of items to be filtered
     * @return array<Ewz_Item>  those members of the input items list that match all the options
     */
    public static function filter_items( $field_opts, $custom_opts, $extra_opts, $in_items ) {
        $out_items = array( );
        foreach ( $in_items as $item ) {
            // go through all the item's fields. If all match, add to $out_items
            foreach ( $field_opts as $field_id => $optval ) {
                if ( isset( $item->item_data[$field_id] ) && isset( $item->item_data[$field_id]['value'] ) ) {
                    $itemval = $item->item_data[$field_id]['value'];
                } else {
                    $itemval = NULL;
                }
                switch ( $optval ) {
                    case "~*~":           // anything
                        break;
                    case "~+~":           // not empty
                        if ( !$itemval ) {
                            continue 3;   // need continue3 because switch statement counts as a loop
                        }
                        break;
                    case "~-~":           // empty
                        if ( $itemval ) {
                            continue 3;
                        }
                        break;
                    default:             // exact match
                        if ( $itemval != $optval ) {
                            continue 3;
                        }
                }
            }

            $custom = new Ewz_Custom_Data( $item->user_id );
            foreach ( $custom_opts as $key => $value ){
                if( ( $value != '~*~' ) && ( $custom->$key !=  $value ) ){
                    continue 2;      // go to next item
                }
            }
            array_push( $out_items, $item );
        }

        // only want those uploaded in the last 'uploaddays' days
        if ( isset( $extra_opts['uploaddays'] ) && ( $extra_opts['uploaddays'] > 0 ) ) {
            $days = $extra_opts['uploaddays'];
            $out_items = self::only_recent( $out_items, $days );
        }
        return $out_items;
    }

    /** Return only those members of the $items list that were uploaded within
     * the last $days days
     *
     * @param  array<Ewz_Item>  $items
     * @param  int    $days
     *
     * @return array<Ewz_Item>  $filtered
     */
    private static function only_recent( $items, $days ) {

        $filtered = array( );
        $seconds = 3600 * 24 * $days;
        // upload_date added in 0.9.9 -- use last_change if no value
        $now = time();  // current unix timestamp
        foreach ( $items as $item ) {
            $uploadedtime = Ewz_Base::datetime_to_timestamp( ( empty( $item->upload_date ) ?  $item->last_change : $item->upload_date ) );
            if ( ( $now - $uploadedtime ) < $seconds ) {
                array_push( $filtered, $item );
            }
        }
        return $filtered;
    }

    /******************** Construction **************************/

    /**
     * Assign the object variables from an array
     *
     * Calls parent::base_set_data with the list of variables and the data structure
     *
     * @param  array<mixed> $data input data array.
     * @return void
     */
    public function set_data( $data ) {
        parent::base_set_data( array_merge( self::$varlist, array( 'item_id' => 'integer' ) ), $data );
    }

    /**
     * Constructor
     *
     * @param  mixed $init  item_id or array of data
     * @return void
     */
    public function __construct( $init ) {
        //        && isset( $init['user_id'] )
        //        && isset( $init['item_data'] )
        //        && isset( $init['webform_id'] ) ) );

        if ( Ewz_Base::is_pos_int( $init ) ) {
            $this->create_from_id( $init );
        } elseif ( is_array( $init ) ) {
            $this->create_from_data( $init );
        } else {
            throw new EWZ_Exception("Attempt to create item with invalid data" );
        }
    }

    /**
     * Create a new item object from the item_id by getting the data from the database
     *
     * @param  int  $id the item id
     * @return void
     */
    protected function create_from_id( $id ) {
        global $wpdb;

        $dbitem = wp_cache_get( $id, 'ewz_item' );
        if( !$dbitem ){
            $dbitem = $wpdb->get_row( $wpdb->prepare( "SELECT  w.layout_id, i.item_id, " . implode( ',i.', array_keys( self::$varlist ) ) .
                                                      " FROM " . EWZ_ITEM_TABLE . " i, " . EWZ_WEBFORM_TABLE . " w ".
                                                      " WHERE w.webform_id = i.webform_id AND item_id=%d", $id ), ARRAY_A );
            if ( !$dbitem ) {
                throw new EWZ_Exception( 'Unable to find matching item in database', $id );
            }
            wp_cache_set( $id, $dbitem, 'ewz_item');
        }
        $this->set_data( $dbitem );
        $this->layout_id = $dbitem['layout_id'];

        foreach( $this->item_files as $n => $fileinfo ){
            // old version stored whole path in db -- not portable
            if( strpos( $fileinfo['thumb_url'], '/ewz_img_uploads/' ) !== false ){
                $relative_url = preg_replace( '/^.*\/ewz_img_uploads\//', '', $fileinfo['thumb_url']);
                $this->item_files[$n]['thumb_url'] =  EWZ_IMG_UPLOAD_URL . '/' . $relative_url;
            } else {
                $this->item_files[$n]['thumb_url'] = EWZ_IMG_UPLOAD_URL . '/' . $fileinfo['thumb_url'];
            }
            // old version stored whole path in db -- not portable
            if( strpos( $fileinfo['fname'], '/ewz_img_uploads/' ) !== false ){
                $relative_path = preg_replace( '/^.*\/ewz_img_uploads\//', '',   $fileinfo['fname']);
                $this->item_files[$n]['fname'] =  EWZ_IMG_UPLOAD_DIR . '/' . $relative_path;
            } else {
                $this->item_files[$n]['fname'] =  EWZ_IMG_UPLOAD_DIR . '/' . $fileinfo['fname'];
            }
        }
    }

    /**
     * Create an item object from $data
     *
     * @param  array<mixed>  $data
     * @return void
     */
    protected function create_from_data( $data ) {
        if ( !array_key_exists( 'item_id', $data ) ) {
            $data['item_id'] = 0;
        } else {
            $data['item_id'] = (int)$data['item_id'];   // data may come in as string from admin list view
        }
        $this->set_data( $data );

        $this->layout_id = Ewz_Webform::get_layout_id ( $this->webform_id );
        $this->check_errors();
    }


    /********************  Validation  *******************************/

    /**
     * Check for various error conditions
     *
     */
    protected function check_errors() {
        global $wpdb;
        if ( isset( $this->item_files ) ) {
            if ( is_string( $this->item_files ) ) {
                $this->item_files = self::check_unserial_arr( $this->item_files );
            }
        }
        foreach ( self::$varlist as $key => $type ) {
            settype( $this->$key, $type );
        }
        if ( !get_user_by( 'id', $this->user_id ) ) {
            throw new EWZ_Exception( 'No such user', $this->user_id );
        }
        if ( !Ewz_Webform::is_valid_webform( $this->webform_id ) ) {
            throw new EWZ_Exception( 'No such webform', $this->webform_id );
        }
        foreach( $this->item_data as $data_id => $field_data ){
            if( ( $data_id != 'attachedto' ) && ( $data_id != 'admin_data' ) ){
                if( !Ewz_Field::is_valid_field( $data_id, $this->layout_id ) ){
                    throw new EWZ_Exception( "No such field for the layout", "field $data_id, layout " . $this->layout_id );
                }
            }
        }

        // check for change of owner or webform ( should not happen )
        if( $this->item_id ) {
            $dbitem = $wpdb->get_row( $wpdb->prepare( "SELECT user_id, webform_id FROM " . EWZ_ITEM_TABLE .
                                                      " WHERE item_id=%d", $this->item_id ), ARRAY_A );

            if ( !$dbitem ) {
                throw new EWZ_Exception( 'Unable to find matching item', $this->item_id );
            }
            // only item owner or admin (via list view) can edit
            if ( ( $dbitem['user_id'] != $this->user_id ) && !Ewz_Permission::can_manage_webform( $this->webform_id, $this->layout_id ) ) {
                throw new EWZ_Exception( 'Invalid user for item', "Uploaded by {$this->user_id}, owner " . $dbitem['user_id'] );
            }
            if ( $dbitem['webform_id'] != $this->webform_id ){
                throw new EWZ_Exception( 'Invalid webform for item', "Uploaded by {$this->user_id}, owner " . $dbitem['user_id'] );
            }
        }
    }

    /******************** Utilities  *******************************/
    /**
     * Record the page each image was attached to
     /**
     * @param  int  $post_id  post_id of the page
     *
     * @return void
     */
    public function record_attachment( $post_id ){
        $title =  get_the_title( $post_id );
        if( isset( $this->item_data['attachedto'] ) ){
            $this->item_data['attachedto'] .= ", $title";
        } else {
            $this->item_data['attachedto'] = $title;
        }
    }


    /**
     * Remove the attachment record ( for use if the item was detached from the page )
     * Currently unused within EntryWizard
     * @param  int  $post_id  post_id of the page
     * @param  int  $max_remove remove at most this number of attachments
     *
     * @return void
     */
    public function record_detachments( $post_id, $max_remove ){

        $title =  get_the_title( $post_id );
        if( isset( $this->item_data['attachedto'] ) ){
            $this->item_data['attachedto'] =  preg_replace('/,,+/',',',
                                                           preg_replace("/ *$title */", '', $this->item_data['attachedto'], $max_remove ) );
        }
    }

    /**
     * Add extra info uploaded via admin .csv file ( see webforms ).
     * This function must also validate, since the input comes from a
     * user-written text file, not a web page which would be validated earlier.
     *
     * @param  array<mixed>  $ddata ( field_ident, title,  excerpt, content [, field_ident, title,  excerpt, content,... ] )
     * @return void
     */
    public function set_uploaded_info( $ddata ) {
        // $ddata is expected to have the following structure:
        // field_ident, title,  excerpt, content [, field_ident, title,  excerpt, content,... ]
        $col = 1;
        while ( isset( $ddata[0] ) ) {
            $field_ident = array_shift( $ddata );

            ++$col;
            if ( !isset( $field_ident ) || !$field_ident ) {
                throw new EWZ_Exception( "Missing field 'identifier' for $this->item_id in column $col of .csv file" );
            }
            $field_id = Ewz_Field::field_id_from_ident_arr(
                    array( 'layout_id' => $this->layout_id, 'field_ident' => $field_ident ) );

            $title = array_shift( $ddata );
            if ( !isset( $title ) ) {
                throw new EWZ_Exception( "Missing field 'title' for $this->item_id in column $col of .csv file" );
            }
            $excerpt = array_shift( $ddata );
            if ( !isset( $excerpt ) ) {
                throw new EWZ_Exception( "Missing field 'caption' or 'excerpt' for $this->item_id in column $col of .csv file" );
            }
            $content = array_shift( $ddata );
            if ( !isset( $content ) ) {
                throw new EWZ_Exception( "Missing field 'content' or 'description' for $this->item_id in column $col of .csv file" );
            }
            $tag = array_shift( $ddata );
            if( !isset($tag) ){
                $tag = '';
            }
            // quote replacement is needed because the strings are single-quoted in some javascript
            // convert to ascii for safety
            if( Ewz_Settings::get_ewz_option('csv_ascii') ){
                $title = str_replace( "'", '&#039;',   mb_convert_encoding( $title, 'ASCII' ));
                $excerpt = str_replace( "'", '&#039;', mb_convert_encoding( $excerpt, 'ASCII' ));
                $content = str_replace( "'", '&#039;', mb_convert_encoding( $content, 'ASCII' ));
                $tag = str_replace( "'", '&#039;', mb_convert_encoding( $tag, 'ASCII' ));
            } else {
                $title = str_replace( "'", '&#039;',   $title );
                $excerpt = str_replace( "'", '&#039;', $excerpt );
                $content = str_replace( "'", '&#039;', $content );
                $tag = str_replace( "'", '&#039;', $tag );
            }
            $this->item_data[$field_id]['ptitle']   = ewz_safe_tags( $title );
            $this->item_data[$field_id]['pexcerpt'] = ewz_safe_tags( $excerpt );
            $this->item_data[$field_id]['pcontent'] = ewz_safe_tags( $content );
            $this->item_data[$field_id]['ptag'] = ewz_safe_tags( $tag );
        }
    }

    /**
     * Add extra data uploaded via admin .csv file ( see webforms )
     * This function must also validate, since the input comes from a
     * user-written text file, not a web page which would be validated earlier.
     *
     * @param  string   $admin_data ( field_ident, data )
     * @return void
     */
    public function set_uploaded_admin_data( $admin_data ) {
        // wp_kses checks for invalid UTF-8, converts single < characters to entities,
        // *** strips all html tags except those in $allowed_html***,
        // removes other line breaks, tabs and extra white space, strips octets.

        // what wp allows in comments, plus <br>
        global $allowedtags;

        $allowed_html = $allowedtags;
        $allowed_html['br'] = array();

        // convert to ASCII for safety
        $this->item_data['admin_data']   = wp_kses( str_replace( "'", '&#039;', mb_convert_encoding( $admin_data, 'ASCII') ), $allowed_html );
    }

    /* * ******************  Database Updates ********************* */

    /**
     * Save the item to the database
     *
     * Update or insert the item data
     *
     * @param boolean $nodelete
     * @param boolean $nocountcheck
     * @return  array{'created':int,'updated':int} ( 'created' => $rowscreated, 'updated' => $rowsupdated )
     */
    public function save( $nodelete=false, $nocountcheck=false ) {   /** @phpstan-ignore-line */
        global $wpdb;

        $this->check_errors();  // raises exceptions if errors found
        if ( !( $this->user_id == get_current_user_id()   // user can edit own data
                ||
                Ewz_Permission::can_manage_webform( $this->webform_id, $this->layout_id ) )   // admin can manage webform
        ) {
            throw new EWZ_Exception( 'Insufficient permissions to edit item',
                    "item $this->item_id in webform $this->webform_id" );
        }

        wp_cache_delete( $this->item_id, 'ewz_item' );

        $num = $wpdb->get_var( $wpdb->prepare( "SELECT count(*)  FROM " . EWZ_ITEM_TABLE .
                                                   " WHERE user_id = %d AND webform_id = %d ",
                                                   $this->user_id, $this->webform_id ) );

        // if creating a new item and not a batch job, check for too many items for the user
        // NB: must do this here, before the file path is cleaned up for save
        if( !$this->item_id && !$nocountcheck && ( Ewz_Webform::get_num_items( $this->webform_id ) < ( $num + 1 ) ) ){
            // delete the uploaded image(s)
            $errors = '';
            if ( !$nodelete && isset( $this->item_files )  ){
                foreach( $this->item_files as $item_file ){
                    $errors .= $this->delete_file( $item_file );
                }
            }
            throw new EWZ_Exception( "Too many items uploaded for user " .
                                     get_user_by( 'id', $this->user_id )->user_login .".\n", $errors );
        }

        //**NB: need to stripslashes *before* serialize, otherwise character counts are wrong
        // WP automatically adds slashes for quotes
        $data = stripslashes_deep( array(
                                         'user_id' => $this->user_id,            // %d
                                         'webform_id' => $this->webform_id,      // %d
                                         'item_data' => serialize( stripslashes_deep( $this->item_data ) ),  // %s
                                         ) );


        $datatypes = array( '%d',  // = user_id
                            '%d',  // = webform_id
                            '%s',  // = item_data
                            );

        // *** don't update item_files unless there was a real image upload ***
        // item_files is set from the uploaded data, so does not contain any previously uploaded image files
        // when it is saved, it overwrites anything that was already there
        // -- no problem for single-image layout, but means we cant change just one image in a multi-image one
        if ( isset( $this->item_files ) && count( $this->item_files ) > 0 ) {
            //** dont store the full path on the db -- not portable
            foreach( $this->item_files as $n => $fileinfo ){
                if( strpos( $fileinfo['thumb_url'], '/ewz_img_uploads/' ) !== false ){
                     $relative_url = preg_replace( '/^.*\/ewz_img_uploads\//', '',  $fileinfo['thumb_url']);
                     $this->item_files[$n]['thumb_url'] = $relative_url;
                 }
                if( strpos( $fileinfo['fname'], '/ewz_img_uploads/' ) !== false ){
                     $relative_path = preg_replace( '/^.*\/ewz_img_uploads\//', '',  $fileinfo['fname']);
                     $this->item_files[$n]['fname'] = $relative_path;
                 }
            }
            // No stripslashes here - wp doesnt escape $_FILES as it does $_POST
            $data['item_files'] = serialize( $this->item_files ) ;
            array_push( $datatypes, '%s' );
        }

        /***** NB: from here on, filename may not contain full path            ***/
        /*****     it needs to be added back in if outside functions are called ***/
        $rowscreated = 0;
        $rowsupdated = 0;
        if ( $this->item_id ) {
            $rows = $wpdb->update( EWZ_ITEM_TABLE,                                                       // no esc
                                  $data,      array( 'item_id' => $this->item_id ),
                                  $datatypes, array( '%d' ) );

            if ( ( false === $rows ) || ( $rows > 1 ) || $wpdb->last_error) {
                throw new EWZ_Exception( 'Problem updating item, please reload the page to see your current status.' ,
                                         $wpdb->last_error );
            }
            // only alter last_change if there really was a change
            if ( 1 == $rows ) {
                 $rowsupdated = $wpdb->update( EWZ_ITEM_TABLE,                                          // no esc
                                                array( 'last_change' => current_time( 'mysql' ) ), array( 'item_id' => $this->item_id ),
                                                array( '%s' ),                                     array( '%d' )
                                              );
                 if( false === $rowsupdated || $wpdb->last_error ){   /** @phpstan-ignore-line */
                     throw new EWZ_Exception( 'Failed to update item: '. $wpdb->last_error,  $data );
                 }
            }
        } else {
            // actually creating a new item
            wp_cache_delete( $this->layout_id, 'ewz_item_count');

            if( !isset( $data['item_files'] ) ){
                $data['item_files'] = serialize( array() ) ;
            }
            $data['upload_date'] = current_time( 'mysql' );
            array_push( $datatypes, '%s' );
            $data['last_change'] = $data['upload_date'];
            array_push( $datatypes, '%s' );

            $rows = $wpdb->insert( EWZ_ITEM_TABLE, $data, $datatypes );                                   // no esc
            if( false === $rows || $wpdb->last_error ){
                throw new EWZ_Exception( 'Failed to create item: '. $wpdb->last_error, $data );
            }
            $this->item_id = $wpdb->insert_id;
            if( $this->item_id ){
                $rowscreated = 1;
            } else {
                // error in db create of item, delete any uploaded image or created thumbnail
                $errors = '';
                if ( isset( $this->item_files ) && !$nodelete ){
                    $uploads = wp_upload_dir();
                    foreach( $this->item_files as $item_file ){
                        //*** put full path back in filename before deleting (nb: thumb_url is not a url)
                        unlink( $uploads['basedir']. "/ewz_img_uploads/" . $item_file['fname'] );
                        unlink( $uploads['basedir']. "/ewz_img_uploads/" . $item_file['thumb_url'] );
                    }
                }
                throw new EWZ_Exception( "Sorry, there was a problem saving, please refresh the page to see your current status.\n", $errors );
            }
        }
        return array( 'created' => $rowscreated, 'updated' => $rowsupdated );
    }

    /**
     * Delete the item from the database and its files from the server
     * First calls do_action 'ewz_before_delete_item'
     *
     * @param  boolean $deleteFiles   if true, delete the associated image files, too
     * @return string  error message
     */
    public function delete( $deleteFiles=true) {
        global $wpdb;

        if ( !( ( $this->user_id == get_current_user_id() )        // user can edit own data
                ||
                Ewz_Permission::can_manage_webform( $this->webform_id, $this->layout_id ) )   // admin can manage webform
        ) {
            throw new EWZ_Exception( 'Insufficient permissions to edit item', "item $this->item_id in webform $this->webform_id" );
        }
        try{
            do_action( 'ewz_before_delete_item', $this->item_id );

            $errors = '';
            wp_cache_delete( $this->item_id, 'ewz_item' );
            wp_cache_delete( $this->layout_id, 'ewz_item_count');

        } catch( Exception $e ){
            // bail now, before the deletion gets done
            throw new EWZ_Exception( 'Error deleting items: ' . $e->getMessage() );
        }

        $rows_deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM " . EWZ_ITEM_TABLE . " WHERE item_id = %d ", $this->item_id ) );
        if( false === $rows_deleted || $wpdb->last_error ){
            throw new EWZ_Exception( 'Failed to delete item', $wpdb->last_error );
        }
        if ( $rows_deleted > 1 ) {
            $errors = "WARNING: Tried to delete single item from database, but $rows_deleted rows were deleted. ";
        } elseif ( 1 == $rows_deleted ) {
            $errors = '';
        } else {
            $errors = "ERROR: Problem deleting item from database. ";
        }

        if ( $errors ) {
            throw new EWZ_Exception( "Problem deleting item data:\n$errors\nFiles belonging to item were not deleted" );
        }

        $errmsg = '';
        if( $deleteFiles ){
            // Attempt to delete all files, don't just raise exception after first failure
            if( isset( $this->item_files ) ){
                foreach ( $this->item_files as $item_file ) {
                    $errmsg .= $this->delete_file( $item_file );
                }
            }
        }
        return $errmsg;
    }

    /**
     * Delete a file and its thumbnail
     *
     * @param   array{'fname':string, 'thumb_url':string}  $item_file      ['fname'=>fname, 'thumb_url'=>url ]
     * @return  string  error message
     */
    public function delete_file( $item_file ){
        $errmsg = '';
        $fname = '';
        if( isset( $item_file['fname']) && $item_file['fname'] ){
            $fname = $item_file['fname'];
            if ( file_exists( $fname ) ) {
                $status = unlink( "$fname" );   // clears statcache, according to docs
                if ( !$status ) {
                    $errmsg = "Failed to delete image file: " . basename( $fname );
                }
            }
        }

        if( isset( $item_file['thumb_url'] ) && $item_file['thumb_url'] ){
            $thumbname = ewz_url_to_file( $item_file['thumb_url'] );
            if( !$fname ){
                $fname = $thumbname;
            }
            if ( file_exists( "$thumbname" ) ) {
                $status = unlink( "$thumbname" );
                if ( !$status ) {
                    $errmsg .= "\nFailed to delete thumbnail file for: " . basename( $fname );
                }
            }
        }
        return $errmsg;
    }
}
