"ad_title", "details" => "ad_details", "contact_name" => "ad_contact_name", "contact_email" => "ad_contact_email", "category_name" => "ad_category_id", "category_parent" => "ad_category_parent_id", "contact_phone" => "ad_contact_phone", "website_url" => "websiteurl", "city" => "ad_city", "state" => 'ad_state', "country" => "ad_country", "county_village" => "ad_county_village", "item_price" => "ad_item_price", "start_date" => "ad_startdate", "end_date" => "ad_enddate", 'username' => 'user_id', ); private $region_columns = array( 'city', 'state', 'country', 'county_village' ); private $required_columns = array( 'title', 'details', 'contact_name', 'contact_email', 'category_name', ); // empty string to indicate integers :\ private $types = array( "title" => "varchar", "details" => "varchar", "contact_name" => "varchar", "contact_email" => "varchar", "category_name" => "", "category_parent" => "", "contact_phone" => "varchar", "website_url" => "varchar", "city" => "varchar", 'state' => 'varchar', "country" => "varchar", "county_village" => "varchar", "item_price" => "", "start_date" => "date", "end_date" => "date", 'username' => '', "images" => "varchar" ); private $auto_columns = array( "is_featured_ad" => 0, "disabled" => 0, "adterm_id" => 0, "ad_postdate" => "?", "disabled_date" => "", "ad_views" => 0, "ad_last_updated" => "?", "ad_key" => "" ); private $auto_columns_types = array( "is_featured_ad" => "", "disabled" => "", "adterm_id" => "", "ad_postdate" => "?", "disabled_date" => "date", "ad_views" => "", "ad_last_updated" => "?", "ad_key" => "varchar" ); private $extra_fields = null; public function __construct( $import_session, $db ) { $this->import_session = $import_session; $this->db = $db; } public function import_row( $row_data ) { $columns = array(); $values = array(); $placeholders = array(); $region = array(); $contact_email = awpcp_array_data( 'contact_email', '', $row_data ); $category_name = awpcp_array_data( 'category_name', '', $row_data ); list( $category_id, $category_parent_id ) = $this->get_category_id( $category_name ); foreach ( $this->accepted_columns as $column_name => $database_column ) { // DO NOT USE awpcp_array_data BECAUSE IT WILL TREAT '0' AS // AN EMPTY VALUE $value = isset( $row_data[ $column_name ] ) ? $row_data[ $column_name ] : ''; $_errors = array(); if ( $column_name == 'username' ) { $value = $this->get_user_id( $value, $contact_email ); if ( empty( $value ) && ! empty( $_errors ) ) { $exception = new AWPCP_CSV_Importer_Exception(); $exception->setErrors( $_errors ); throw $exception; } } else if ( $column_name == 'category_name' ) { $value = $category_id; } else if ( $column_name == 'category_parent' ) { $value = $category_parent_id; } else { $value = $this->parse( $value, $column_name ); } // missing value, mark row as bad if ( strlen( $value ) === 0 && in_array( $column_name, $this->required_columns ) ) { $message = __( 'Missing value for required column %s.', 'another-wordpress-classifieds-plugin' ); $message = sprintf( $message, $column_name ); throw new AWPCP_CSV_Importer_Exception( $message ); } // if there was an error getting a value for this field, // but the field wasn't included in the CSV, skip and mark // the row as good if ( $value === false && ! isset( $row_data[ $column_name ] ) ) { continue; } if ( in_array( $column_name, $this->region_columns ) ) { if ( $column_name == 'county_village' ) { $region['county'] = $value; } else { $region[ $column_name ] = $value; } } else { $placeholders[] = empty( $this->types[ $column_name ] ) ? '%d' : '%s'; $values[] = $value; $columns[] = $database_column; if ( $column_name == 'contact_phone' ) { $placeholders[] = '%s'; $values[] = awpcp_get_digits_from_string( $value ); $columns[] = 'phone_number_digits'; } } } foreach ( $this->auto_columns as $column_name => $value ) { if ( $value == '?' ) { $value = $this->parse( $value, $column_name ); } $columns[] = $column_name; $placeholders[] = empty( $this->auto_columns_types[ $column_name ] ) ? '%d' : '%s'; $values[] = empty( $this->auto_columns_types[ $column_name ]) ? 0 : $value; } foreach ( $this->get_extra_fields() as $field ) { $name = $field->field_name; // validate only extra fields present in the CSV file if ( ! isset( $row_data[ $name ] ) ) { continue; } $validate = $field->field_validation; $type = $field->field_input_type; $options = $field->field_options; $category = $field->field_category; $errors = array(); $enforce = in_array( $category_id, $category ) && $field->required; $value = awpcp_validate_extra_field( $name, $row_data[ $name ], $validate, $type, $options, $enforce, $errors ); if ( ! empty( $errors ) ) { throw new AWPCP_CSV_Importer_Exception( array_shift( $errors ) ); } switch ( $field->field_mysql_data_type ) { case 'VARCHAR': case 'TEXT': $placeholders[] = '%s'; break; case 'INT': $placeholders[] = '%d'; break; case 'FLOAT': $placeholders[] = '%f'; break; } $columns[] = $name; $values[] = $value; } $image_names = array_filter( explode( ';', $row_data['images'] ) ); if ( $image_names ) { $images = $this->import_images( $image_names ); } else { $images = array(); } // $this->images_imported += count( $images ); // // save created images to be deleted later, if test mode is on // array_splice( $images_created, 0, 0, $images ); $sql = 'INSERT INTO ' . AWPCP_TABLE_ADS . ' '; $sql.= '( ' . implode( ', ', $columns ) . ' ) VALUES ( ' . implode( ', ', $placeholders ) . ' ) '; $sql = $this->db->prepare( $sql, $values); if ( $this->import_session->is_test_mode_enabled() ) { $inserted_id = 5; } else { $this->db->query( $sql ); $inserted_id = $this->db->insert_id; } if ( !empty( $region ) ) { $this->save_regions( $region, $inserted_id ); } if ( $images && $this->import_session->is_test_mode_enabled() ) { $this->delete_imported_images( $images ); } else if ( $images ) { $this->save_images( $images, $inserted_id ); } if ( ! $this->import_session->is_test_mode_enabled() ) { do_action( 'awpcp-listing-imported', $inserted_id, $row_data ); } } private function get_category_id( $category_name ) { $auto = $this->import_session->get_param( 'create_missing_categories', false ); $test = $this->import_session->is_test_mode_enabled(); $sql = 'SELECT category_id, category_parent_id FROM ' . AWPCP_TABLE_CATEGORIES . ' '; $sql.= 'WHERE category_name = %s'; $sql = $this->db->prepare( $sql, $category_name ); $category = $this->db->get_row( $sql, ARRAY_N ); if ( is_null( $category ) && $auto && ! $test ) { $sql = 'INSERT INTO ' . AWPCP_TABLE_CATEGORIES . ' '; $sql.= '(category_parent_id, category_name, category_order) VALUES (0, %s, 0)'; $sql = $this->db->prepare( $sql, $category_name ); $this->db->query( $sql ); return array( $this->db->insert_id, 0 ); } else if ( ! is_null( $category ) ) { return $category; } else if ( $auto && $test ) { return array( 5, 0 ); } return false; } /** * Attempts to find a user by its username or email. If a user can't be * found one will be created. * * @param $username string User's username * @param $email string User's email address * @param $row int The index of the row being processed * @param $errors array Used to pass errors back to the caller. * @param $messages array Used to pass messages back to the caller * * @return User ID or false on error */ public function get_user_id( $username, $email ) { static $users = array(); if ( ! $this->import_session->get_param( 'assign_listings_to_user', false ) ) { return ''; } if ( isset( $users[ $username ] ) ) { return $users[ $username ]; } $user = empty( $username ) ? false : get_user_by( 'login', $username ); if ( $user === false ) { $user = empty( $email ) ? false : get_user_by( 'email', $email ); } else { $users[ $user->user_login ] = $user->ID; return $user->ID; } if ( is_object( $user ) ) { $users[ $user->user_login ] = $user->ID; return $user->ID; } $default_user = $this->import_session->get_param( 'default_user' ); // a default user was selected, do not attempt to create a new one if ( $default_user > 0) { return $default_user; } if ( empty( $username ) ) { $message = __( "No user could be assigned to this listing. A new user couldn't be created because the username column has an empty value. Please include a username or select a default user.", 'another-wordpress-classifieds-plugin' ); throw new AWPCP_CSV_Importer_Exception( $message ); } else if ( empty( $email ) ) { $message = __( "No user could be assigned to this listing. A new user couldn't be created because the contact_email column has an empty value. Please include a contact_email or select a default user.", 'another-wordpress-classifieds-plugin' ); throw new AWPCP_CSV_Importer_Exception( $message ); } $password = wp_generate_password( 8, false, false ); if ( $this->import_session->is_test_mode_enabled() ) { $result = 1; // fake it! } else { $result = wp_create_user( $username, $password, $email ); } if ( is_wp_error( $result ) ) { $message = __( 'No user could be assigned to this listing. Our attempt to create a new user failed with the following error: .', 'another-wordpress-classifieds-plugin' ); $message = str_replace( '', $result->get_error_message(), $message ); throw new AWPCP_CSV_Importer_Exception( $message ); } $users[ $username ] = $result; $message = __( "A new user '%s' with email address '%s' and password '%s' was created.", 'another-wordpress-classifieds-plugin' ); $messages[] = sprintf( $message, $username, $email, $password ); return $result; } private function parse( $val, $key ) { $start_date = $this->import_session->get_param( 'default_start_date' ); $end_date = $this->import_session->get_param( 'default_end_date' ); $import_date_format = $this->import_session->get_param( 'date_format' ); $date_sep = $this->import_session->get_param( 'date_separator' ); $time_sep = $this->import_session->get_param( 'time_separator' ); if ( $key == "item_price" ) { if ( empty( $val ) ) { return 0; } // numeric validation if ( is_numeric( $val ) ) { // AWPCP stores Ad prices using an INT column (WTF!) so we need to // store 99.95 as 9995 and 99 as 9900. return $val * 100; } $message = __( 'Item price is not a numeric value.', 'another-wordpress-classifieds-plugin' ); throw new AWPCP_CSV_Importer_Exception( $message ); } else if ($key == "start_date") { // TODO: validation if ( ! empty( $val ) ) { $val = $this->parse_date( $val, $import_date_format, $date_sep, $time_sep ); if ( empty( $val ) || $val == null ) { $message = __( 'Invalid Start date.', 'another-wordpress-classifieds-plugin' ); throw new AWPCP_CSV_Importer_Exception( $message ); } return $val; } if ( empty( $start_date ) ) { $message = __( 'Start date missing. You can define a default value for this column changing the import configuration.', 'another-wordpress-classifieds-plugin' ); throw new AWPCP_CSV_Importer_Exception( $message ); } else { // TODO: validation $val = $this->parse_date( $start_date, 'us_date', $date_sep, $time_sep ); // $start_date; } return $val; } else if ($key == "end_date") { // TODO: validation if ( ! empty( $val ) ) { $val = $this->parse_date( $val, $import_date_format, $date_sep, $time_sep ); if ( empty( $val ) || $val == null ) { $message = __( 'Invalid End date.', 'another-wordpress-classifieds-plugin' ); throw new AWPCP_CSV_Importer_Exception( $message ); } return $val; } if ( empty( $end_date ) ) { $message = __( 'End date missing. You can define a default value for this column changing the import configuration.', 'another-wordpress-classifieds-plugin' ); throw new AWPCP_CSV_Importer_Exception( $message ); } else { // TODO: validation $val = $this->parse_date( $end_date, 'us_date', $date_sep, $time_sep ); // $end_date; } return $val; } else if ( $key == "ad_postdate" ) { if ( empty( $start_date ) ) { $date = new DateTime(); $val = $date->format( 'Y-m-d' ); } else { // TODO: validation $val = $this->parse_date( $start_date, 'us_date', $date_sep, $time_sep, 'Y-m-d' ); // $start_date; } return $val; } else if ( $key == "ad_last_updated" ) { $date = new DateTime(); $val = $date->format( 'Y-m-d' ); return $val; } else if ( ! empty( $val ) ) { return $val; } return false; } public function parse_date($val, $date_time_format, $date_separator, $time_separator, $format = "Y-m-d H:i:s") { $date_formats = array( 'us_date' => array( array('%m', '%d', '%y'), // support both two and four digits years array('%m', '%d', '%Y'), ), 'uk_date' => array( array('%d', '%m', '%y'), array('%d', '%m', '%Y'), ) ); $date_formats['us_date_time'] = $date_formats['us_date']; $date_formats['uk_date_time'] = $date_formats['uk_date']; if (in_array($date_time_format, array('us_date_time', 'uk_date_time'))) $suffix = implode($time_separator, array('%H', '%M', '%S')); else $suffix = ''; $date = null; foreach ($date_formats[$date_time_format] as $_format) { $_format = trim(sprintf("%s %s", implode($date_separator, $_format), $suffix)); $parsed = awpcp_strptime( $val, $_format ); if ($parsed && empty($parsed['unparsed'])) { $date = $parsed; break; } } if (is_null($date)) return null; $datetime = new DateTime(); try { $datetime->setDate($parsed['tm_year'] + 1900, $parsed['tm_mon'] + 1, $parsed['tm_mday']); $datetime->setTime($parsed['tm_hour'], $parsed['tm_min'], $parsed['tm_sec']); } catch (Exception $ex) { echo "Exception: " . $ex->getMessage(); } return $datetime->format($format); } private function import_images( $filenames ) { $images_directory = $this->import_session->get_images_directory(); if ( empty( $images_directory ) ) { throw new AWPCP_CSV_Importer_Exception( __( 'No images directory was configured. Are you sure you uploaded a ZIP file or defined a local directory?', 'another-wordpress-classifieds-plugin' ) ); } list( $images_dir, $_ ) = awpcp_setup_uploads_dir(); list( $min_width, $min_height, $min_size, $max_size ) = awpcp_get_image_constraints(); $entries = array(); foreach ( array_filter( $filenames ) as $filename ) { $uploaded = awpcp_upload_image_file( $images_dir, awpcp_unique_filename( $images_directory . DIRECTORY_SEPARATOR . $filename, basename( $filename ), array( $images_dir, $images_dir . 'images/', $images_dir . 'thumbs/' ) ), $images_directory . DIRECTORY_SEPARATOR . $filename, $min_size, $max_size, $min_width, $min_height, false ); if ( is_array( $uploaded ) && isset( $uploaded['filename'] ) ) { $entries[] = $uploaded; } else { $message = __( 'An image could not be succesfully imported. The operation failed with the following error: ', 'another-wordpress-classifieds-plugin' ); $message = str_replace( '', $uploaded, $message ); throw new AWPCP_CSV_Importer_Exception( $message ); } } return $entries; } private function save_regions( $region, $ad_id ) { if ( ! $this->import_session->is_test_mode_enabled() ) { $ad = AWPCP_Ad::find_by_id( $ad_id ); awpcp_basic_regions_api()->update_ad_regions( $ad, array( $region ), 1 ); } } private function delete_imported_images( $images ) { list( $images_dir, $thumbs_dir ) = awpcp_setup_uploads_dir(); foreach ( $images as $image ) { $basename = $image['filename']; $filename = awpcp_utf8_pathinfo( $basename, PATHINFO_FILENAME ); $extension = awpcp_utf8_pathinfo( $basename, PATHINFO_EXTENSION ); if ( file_exists( $images_dir . $basename ) ) { unlink( $images_dir . $basename ); } if ( file_exists( $thumbs_dir . $basename ) ) { unlink( $thumbs_dir . $basename ); } if ( file_exists( $images_dir . $filename . '-large.' . $extension ) ) { unlink( $images_dir . $filename . '-large.' . $extension ); } if ( file_exists( $thumbs_dir . $filename . '-primary.' . $extension ) ) { unlink( $thumbs_dir . $filename . '-primary.' . $extension ); } } } private function save_images( $entries, $adid ) { $media_api = awpcp_media_api(); foreach ( $entries as $entry ) { $extension = awpcp_get_file_extension( $entry['filename'] ); $mime_type = sprintf( 'image/%s', $extension ); $data = array( 'ad_id' => $adid, 'name' => $entry['filename'], 'path' => $entry['filename'], 'mime_type' => $mime_type, 'enabled' => true, 'is_primary' => false, ); if ( $media_api->create( $data ) === false ) { $message =__( 'Could not save the information for image into the database.', 'another-wordpress-classifieds-plugin' ); $message = str_replace( '', $entry['original'], $message ); throw new AWPCP_CSV_Importer_Exception( $message ); } } } private function get_extra_fields() { if ( is_array( $this->extra_fields ) ) { return $this->extra_fields; } $this->extra_fields = array(); if ( function_exists( 'awpcp_get_extra_fields' ) ) { foreach ( awpcp_get_extra_fields() as $field ) { $this->extra_fields[ $field->field_name ] = $field; } } return $this->extra_fields; } } /** * Validate extra field values and return value. * * @param name field name * @param value field value in CSV file * @param row row number in CSV file * @param validate type of validation * @param type type of input field (Input Box, Textarea Input, Checkbox, * SelectMultiple, Select, Radio Button) * @param options list of options for fields that accept multiple values * @param enforce true if the Ad that's being imported belongs to the same category * that the extra field was assigned to, or if the extra field was * not assigned to any category. * required fields may be empty if enforce is false. */ function awpcp_validate_extra_field( $name, $value, $validate, $type, $options, $enforce, &$errors ) { $validation_errors = array(); $serialize = false; $list = null; switch ( $type ) { case 'Input Box': case 'Textarea Input': // nothing special here, proceed with validation break; case 'Checkbox': case 'Select Multiple': // value can be any combination of items from options list $msg = sprintf( __( "The value for Extra Field %s's is not allowed. Allowed values are: %%s", 'another-wordpress-classifieds-plugin' ), $name ); $list = explode( ';', $value ); $serialize = true; case 'Select': case 'Radio Button': $list = is_array( $list ) ? $list : array( $value ); if ( ! isset( $msg ) ) { $msg = sprintf( __( "The value for Extra Field %s's is not allowed. Allowed value is one of: %%s", 'another-wordpress-classifieds-plugin' ), $name, $row ); } // only attempt to validate if the field is required (has validation) foreach ( $list as $item ) { if ( empty( $item ) ) { continue; } if ( ! in_array( $item, $options ) ) { $msg = sprintf( $msg, implode( ', ', $options ) ); $validation_errors[] = $msg; } } // extra fields multiple values are stored serialized if ( $serialize ) { $value = maybe_serialize( $list ); } break; default: break; } if ( ! empty( $validation_errors ) ) { array_splice( $errors, count( $errors ), 0, $validation_errors ); return false; } $list = is_array( $list ) ? $list : array( $value ); foreach ( $list as $k => $item ) { if ( ! $enforce && empty( $item ) ) { continue; } switch ( $validate ) { case 'missing': if ( empty( $value ) ) { $validation_errors[] = "A value for Extra Field $name is required."; } break; case 'url': if ( ! isValidURL( $item ) ) { $validation_errors[] = "The value for Extra Field $name must be a valid URL."; } break; case 'email': if ( ! awpcp_is_valid_email_address( $item ) ) { $validation_errors[] = "The value for Extra Field $name must be a valid email address."; } break; case 'numericdeci': if ( ! is_numeric( $item ) ) { $validation_errors[] = "The value for Extra Field $name must be a number."; } break; case 'numericnodeci': if ( ! ctype_digit( $item ) ) { $validation_errors[ $name ] = "The value for Extra Field $name must be an integer number."; } break; default: break; } } if ( ! empty( $validation_errors ) ) { array_splice( $errors, count( $errors ), 0, $validation_errors ); return false; } return $value; }