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

/*
 *  Base for the database classes
 */
abstract class Ewz_Base
{
    /**
     * @return void
     */
    abstract protected function check_errors();

    /**
     * @return int
     */
    abstract public function save();

    /**
     * @return void
     */
    abstract public function delete();

    // get from database given key
    /**
     * @param  int   $id
     * @return void
     */
    abstract protected function create_from_id( $id );

    // create from uploaded data
    /**
     * @param  array<mixed>   $data
     * @return void
     */
    abstract protected function create_from_data( $data );

    /**
     *  Normally returns true.
     *  Returns false to force all validation to be done on server,
     *  with none on the client.
     * @return boolean
     */
    public static function validate_using_javascript(){
        return true;
    }
    /**
     *  Normally returns true.
     *  Returns false to to stop the "are you sure" alert on leaving a page,
     *  because the testing code does not seem to be able to handle it.
     * @return boolean
     */
    public static function warn_on_leaving(){
        return true;
    }


    /**
     * Delete options whose name starts with ewz_$webform_id.
     * These are one-offs used to save the location of a downloadable file
     * @param  int   $webform_id
     * @return void
     */
    public static function delete_ewz_options( $webform_id ){
        $safe_id = strval(intval( $webform_id ) );
        global $wpdb;

        $options = $wpdb->get_col( 'SELECT option_name FROM ' . $wpdb->options .            // no tainted data
        ' WHERE option_name like "ewz_' . $safe_id . '_%"' );     // for deletion
        foreach ( $options as $option_name ) {
            if( preg_match( '/^ewz_[0-9]+_/', $option_name ) ){
                delete_option( $option_name );
            }
        }
    }

    /**
     * Format the output date in DatePicker
     *
     * @param  string $dateFormat
     *
     * @return string
     */
    public static function toDatePickerFormat( $dateFormat ){
        $map = array(
            'c' => 'yy-mm-dd',         // ISO 8601 date 2004-02-12T15:19:21+00:00
            'd' => 'dd',               // Day of the month, 2 digits with leading zeros
            'D' => 'D',                // Day, three letters
            'j' => 'd',                // Day of the month without leading zeros
            'l' => 'DD',               // Day of the week, full
            'z' => 'oo',               // The day of the year  0 through 365
            'F' => 'MM',               // Month, full
            'm' => 'mm',               // Numeric representation of a month, with leading zeros
            'M' => 'M',                // Month, 3 letters
            'n' => 'm',                // Numeric representation of a month, without leading zeros
            'r' => 'D, dd M YYYY',     // RFC 2822 formatted date Example: Thu, 21 Dec 2000 16:01:07 +0200
            'Y' => 'yy',               // Year, 4 digits
            'y' => 'y',                // Year, 2 digits
            'S' => '',
            'L' => '',
        );

        if( preg_match('/[Nwto]/', $dateFormat) ){
            // some items cannot be represented in datepicker
            return 'yy-mm-dd';
        } else {
            return strtr((string)$dateFormat, $map);
        }
    }

/**
 * Convert datetime to timestamp using strtotime assuming the WP timezone.
 *
 * @param string $datetime_str
 *
 * @return int $timestamp  seconds since the Unix Epoch
 */
    public static function datetime_to_timestamp( $datetime_str ){
        $tz_opt = get_option('timezone_string');
        $timestamp = strtotime( $datetime_str . " $tz_opt");

        if( $timestamp === false ){
            throw new EWZ_Exception(__("Invalid date-time", 'entrywizard' ));
        }
        return $timestamp;
    }

    /**
     * Convert timestamp to human-readable format assuming the WP timezone.
     *
     * @param string $format
     * @param int    $timestamp
     *
     * @return string $datetime
     */
    public static function timestamp_to_datetime( $format, $timestamp ){

        $datetime = false;
        if( version_compare( $GLOBALS['wp_version'], '5.3', '<' )){
            $curr_tz = date_default_timezone_get();
            $tz_opt = get_option('timezone_string');
            if( $tz_opt ){
                date_default_timezone_set( $tz_opt );
            }
            $datetime = date( $format, $timestamp );
            if( $tz_opt ){
                date_default_timezone_set( $curr_tz );
            }
        } else {
            $datetime = wp_date( $format, $timestamp );
        }

        if( $datetime === false ){
            throw new EWZ_Exception(__("Invalid date-time", 'entrywizard' ));
        }

        return $datetime;
    }


    /**
     * Unserialize an array and log any error with an identifying message.
     *
     * @param  string   $string    string to unserialize
     * @param  array<string>   $default
     * @param  string   $msg       identifying message for the log file
     *
     * @return array<mixed>    $arr       the unserialized array
     */
    public static function check_unserial_arr( $string, $default = array(), $msg = '' ) {

        if( !$string ){
            return array();
        }
        $arr = @unserialize(  $string  );
        if( !is_array( $arr ) ){
            error_log('EWZ: ' . __("Serialization error in value ", 'entrywizard' ) . "  $string [ $msg ]");
            $arr = $default;
        }
        return $arr;
    }

    /**
     * Sets the variables to the keys of "data" based on the type in the values
     *
     * Convenience function that sets boolean and integer types to themselves,
     * but unserializes objects and arrays
     *
     * @param   array<string,string>   $variables  array of object variables
     * @param   array<mixed>   $data       whose keys are the variable names and values are their types
     *
     * @return  void
     */
    protected function base_set_data( $variables, $data )
    {
        $id="";
        $ids = array_filter( array_keys($variables), function($v){ return preg_match( '/_id$/', $v ); } );
        if( $ids ){
            $id = join(' ', str_replace('_id', '', $ids ));
        }
        foreach ( $variables as $key => $type ) {
            if ( array_key_exists( $key, $data ) ) {
                switch ( $type ) {
                case 'array':
                    if ( !$data[$key] ){
                        $this->$key = array();
                    } else {
                        if ( is_string( $data[$key] ) ) {
                            $this->$key = self::check_unserial_arr( $data[$key], array(), "$id $key" );
                        } else {
                            $this->$key = (array) $data[$key];
                        }
                    }
                    break;
                case 'boolean':
                    $this->$key = (boolean) $data[$key];
                    break;
                case 'integer':
                    $this->$key = (integer) $data[$key];
                    break;
                case 'string':
                    $this->$key = (string) $data[$key];
                    break;
                default:
                    error_log("EWZ: warning - invalid data type $type passed to base class");
                    $this->$key = NULL;
                }
            } else {
                switch ( $type ) {
                case 'array':
                    $this->$key = array();
                    break;
                case 'boolean':
                    $this->$key = false;
                    break;
                case 'integer':
                    $this->$key = 0;
                    break;
                case 'string':
                    $this->$key = '';
                    break;
                default:
                    error_log("EWZ: warning - invalid data type $type passed to base class");
                    $this->$key = NULL;
                }
            }
        }
    }

    /**
     * Is the arg a non-negative integer, or a string representation of same
     *
     * @param   mixed    $val
     * @return  boolean
     */
    public static function is_nn_int( $val ){
        if( is_string( $val )){
            return preg_match( '/^[0-9]+$/', $val );
        } else {
            return ( is_int( $val ) && ( $val >= 0 ) );
        }
    }

    /**
     * Is the arg a positive integer, or a string representation of same
     *
     * @param   mixed    $val
     * @return  boolean
     */
    public  static function is_pos_int( $val ){
        if( is_string( $val )){
            return preg_match( '/^[1-9][0-9]*$/', $val );
        } else {
            return ( is_int( $val ) && $val > 0 );
        }
    }

}
