class PriceListItemImportForm in Commerce Pricelist 8.2
Hierarchy
- class \Drupal\Core\Form\FormBase implements ContainerInjectionInterface, FormInterface uses DependencySerializationTrait, LoggerChannelTrait, MessengerTrait, LinkGeneratorTrait, RedirectDestinationTrait, UrlGeneratorTrait, StringTranslationTrait
- class \Drupal\commerce_pricelist\Form\PriceListItemImportForm
Expanded class hierarchy of PriceListItemImportForm
1 file declares its use of PriceListItemImportForm
File
- src/
Form/ PriceListItemImportForm.php, line 18
Namespace
Drupal\commerce_pricelist\FormView source
class PriceListItemImportForm extends FormBase {
/**
* The number of price list items to process in each batch.
*
* @var int
*/
const BATCH_SIZE = 25;
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a new PriceListItemImportForm object.
*
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* The entity field manager.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager) {
$this->entityFieldManager = $entity_field_manager;
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container
->get('entity_field.manager'), $container
->get('entity_type.manager'));
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'commerce_pricelist_item_import_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, PriceListInterface $commerce_pricelist = NULL) {
$form_state
->set('price_list_id', $commerce_pricelist
->id());
$purchasable_entity_type_id = $commerce_pricelist
->bundle();
$purchasable_entity_type = $this->entityTypeManager
->getDefinition($purchasable_entity_type_id);
$purchasable_entity_column_types = [
$purchasable_entity_type
->getKey('id'),
$purchasable_entity_type
->getKey('uuid'),
// Product variation specific.
'sku',
];
$purchasable_entity_column_types = array_combine($purchasable_entity_column_types, $purchasable_entity_column_types);
// Get the label for each allowed field.
$base_field_definitions = $this->entityFieldManager
->getBaseFieldDefinitions($purchasable_entity_type_id);
foreach ($base_field_definitions as $field_name => $field_definition) {
if (isset($purchasable_entity_column_types[$field_name])) {
$purchasable_entity_column_types[$field_name] = $field_definition
->getLabel();
}
}
$default_purchasable_entity_column = $purchasable_entity_type
->id();
if (strpos($default_purchasable_entity_column, 'commerce_') === 0) {
$default_purchasable_entity_column = str_replace('commerce_', '', $default_purchasable_entity_column);
}
$sample_file_path = drupal_get_path('module', 'commerce_pricelist') . '/sample_file.csv';
$form['#tree'] = TRUE;
$form['csv'] = [
'#type' => 'file',
'#title' => $this
->t('Choose a file'),
'#description' => $this
->t('Unsure about the format? Download a <a href=":url">sample file</a>.', [
':url' => Url::fromUri('base:' . $sample_file_path)
->toString(),
]),
'#upload_validators' => [
'file_validate_extensions' => [
'csv',
],
'file_validate_size' => [
Environment::getUploadMaxSize(),
],
],
'#upload_location' => 'temporary://',
];
$form['mapping'] = [
'#type' => 'details',
'#title' => $this
->t('CSV mapping options'),
'#collapsible' => TRUE,
'#open' => TRUE,
];
$form['mapping']['purchasable_entity_column_type'] = [
'#type' => 'select',
'#title' => $this
->t('@purchasable_entity_type column type', [
'@purchasable_entity_type' => $purchasable_entity_type
->getLabel(),
]),
'#options' => $purchasable_entity_column_types,
'#default_value' => isset($purchasable_entity_column_types['sku']) ? 'sku' : $purchasable_entity_type
->getKey('uuid'),
'#required' => TRUE,
];
$form['mapping']['purchasable_entity_column'] = [
'#type' => 'textfield',
'#title' => $this
->t('@purchasable_entity_type column', [
'@purchasable_entity_type' => $purchasable_entity_type
->getLabel(),
]),
'#default_value' => $default_purchasable_entity_column,
'#required' => TRUE,
];
$form['mapping']['quantity_column'] = [
'#type' => 'textfield',
'#title' => $this
->t('Quantity column'),
'#description' => $this
->t('If left empty, quantity will default to 1.'),
'#default_value' => 'quantity',
'#required' => TRUE,
];
$form['mapping']['list_price_column'] = [
'#type' => 'textfield',
'#title' => $this
->t('List price column'),
'#description' => $this
->t('If left empty, no list price will be set.'),
'#default_value' => 'list_price',
];
$form['mapping']['price_column'] = [
'#type' => 'textfield',
'#title' => $this
->t('Price column'),
'#default_value' => 'price',
'#required' => TRUE,
];
$form['mapping']['currency_column'] = [
'#type' => 'textfield',
'#title' => $this
->t('Currency column'),
'#default_value' => 'currency_code',
'#required' => TRUE,
];
$form['options'] = [
'#type' => 'details',
'#title' => $this
->t('CSV file options'),
'#collapsible' => TRUE,
'#open' => FALSE,
];
$form['options']['delimiter'] = [
'#type' => 'textfield',
'#title' => $this
->t('Delimiter'),
'#size' => 5,
'#maxlength' => 1,
'#default_value' => ',',
'#required' => TRUE,
];
$form['options']['enclosure'] = [
'#type' => 'textfield',
'#title' => $this
->t('Enclosure'),
'#size' => 5,
'#maxlength' => 1,
'#default_value' => '"',
'#required' => TRUE,
];
$form['delete_existing'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Delete all prices in this price list prior to import.'),
];
$form['actions'] = [
'#type' => 'actions',
];
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this
->t('Import prices'),
'#button_type' => 'primary',
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
/** @var \Symfony\Component\HttpFoundation\File\UploadedFile[] $all_files */
$all_files = $this
->getRequest()->files
->get('files', []);
if (empty($all_files['csv'])) {
$form_state
->setErrorByName('csv', $this
->t('No CSV file was provided.'));
}
elseif (!$all_files['csv']
->isValid()) {
$form_state
->setErrorByName('csv', $this
->t('The provided CSV file is invalid.'));
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$file = file_save_upload('csv', $form['csv']['#upload_validators'], 'temporary://', 0, FileSystemInterface::EXISTS_RENAME);
$values = $form_state
->getValues();
$batch = [
'title' => $this
->t('Importing prices'),
'progress_message' => '',
'operations' => [],
'finished' => [
$this,
'finishBatch',
],
];
if ($values['delete_existing']) {
$batch['operations'][] = [
[
get_class($this),
'batchDeleteExisting',
],
[
$form_state
->get('price_list_id'),
],
];
}
$batch['operations'][] = [
[
get_class($this),
'batchProcess',
],
[
$file
->getFileUri(),
$values['mapping'],
$values['options'],
$form_state
->get('price_list_id'),
(bool) $values['delete_existing'],
],
];
$batch['operations'][] = [
[
get_class($this),
'batchDeleteUploadedFile',
],
[
$file
->id(),
],
];
batch_set($batch);
$form_state
->setRedirect('entity.commerce_pricelist_item.collection', [
'commerce_pricelist' => $form_state
->get('price_list_id'),
]);
}
/**
* Batch operation to delete existing items from the price list.
*
* @param int $price_list_id
* The price list ID.
* @param array $context
* The batch context.
*/
public static function batchDeleteExisting($price_list_id, array &$context) {
$entity_type_manager = \Drupal::entityTypeManager();
$price_list_storage = $entity_type_manager
->getStorage('commerce_pricelist');
$price_list_item_storage = $entity_type_manager
->getStorage('commerce_pricelist_item');
/** @var \Drupal\commerce_pricelist\Entity\PriceListInterface $price_list */
$price_list = $price_list_storage
->load($price_list_id);
$price_list_item_ids = $price_list
->getItemIds();
if (empty($context['sandbox'])) {
$context['sandbox']['delete_total'] = count($price_list_item_ids);
$context['sandbox']['delete_count'] = 0;
$context['results']['delete_count'] = 0;
}
$total_items = $context['sandbox']['delete_total'];
$deleted =& $context['sandbox']['delete_count'];
$remaining = $total_items - $deleted;
$limit = (int) ($remaining < self::BATCH_SIZE) ? $remaining : self::BATCH_SIZE;
if ($total_items == 0 || empty($price_list_item_ids)) {
$context['finished'] = 1;
}
else {
$price_list_item_ids = array_slice($price_list_item_ids, 0, $limit);
$price_list_items = $price_list_item_storage
->loadMultiple($price_list_item_ids);
$price_list_item_storage
->delete($price_list_items);
$deleted = $deleted + $limit;
$context['message'] = t('Deleting price @deleted of @total_items', [
'@deleted' => $deleted,
'@total_items' => $total_items,
]);
$context['finished'] = $deleted / $total_items;
}
// Update the results for finishBatch().
$context['results']['delete_count'] = $deleted;
}
/**
* Batch process to import price list items from the CSV.
*
* @param string $file_uri
* The CSV file URI.
* @param array $mapping
* The mapping options.
* @param array $csv_options
* The CSV options.
* @param string $price_list_id
* The price list ID.
* @param bool $delete_existing
* The "delete existing" flag.
* @param array $context
* The batch context.
*/
public static function batchProcess($file_uri, array $mapping, array $csv_options, $price_list_id, $delete_existing, array &$context) {
$entity_type_manager = \Drupal::entityTypeManager();
$price_list_storage = $entity_type_manager
->getStorage('commerce_pricelist');
$price_list_item_storage = $entity_type_manager
->getStorage('commerce_pricelist_item');
/** @var \Drupal\commerce_pricelist\Entity\PriceList $price_list */
$price_list = $price_list_storage
->load($price_list_id);
$purchasable_entity_storage = $entity_type_manager
->getStorage($price_list
->bundle());
$header_mapping = static::buildHeaderMapping($mapping);
ini_set("auto_detect_line_endings", TRUE);
$csv = new CsvFileObject($file_uri, TRUE, $header_mapping, $csv_options);
if (empty($context['sandbox'])) {
$context['sandbox']['import_total'] = (int) $csv
->count();
$context['sandbox']['import_count'] = 0;
$context['results']['import_created'] = 0;
$context['results']['import_updated'] = 0;
$context['results']['import_skipped'] = 0;
}
// The file is invalid, stop here.
if (!$csv
->valid()) {
$context['results']['error'] = t('The provided CSV file is invalid.');
$context['finished'] = 1;
return;
}
$import_total = $context['sandbox']['import_total'];
$import_count =& $context['sandbox']['import_count'];
$remaining = $import_total - $import_count;
$limit = $remaining < self::BATCH_SIZE ? $remaining : self::BATCH_SIZE;
$csv
->seek($import_count + 1);
for ($i = 0; $i < $limit; $i++) {
$row = $csv
->current();
$row = array_map('trim', $row);
// Skip the row if one of the required columns is empty.
foreach ([
'purchasable_entity',
'price',
'currency_code',
] as $required_column) {
if (empty($row[$required_column])) {
$context['results']['import_skipped']++;
$import_count++;
$csv
->next();
continue 2;
}
}
$purchasable_entity = $purchasable_entity_storage
->loadByProperties([
$mapping['purchasable_entity_column_type'] => $row['purchasable_entity'],
]);
$purchasable_entity = reset($purchasable_entity);
// Skip the row if the purchasable entity could not be loaded.
if (!$purchasable_entity) {
$context['results']['import_skipped']++;
$import_count++;
$csv
->next();
continue;
}
$quantity = !empty($row['quantity']) ? $row['quantity'] : '1';
// If existing price list items weren't deleted before the import,
// try to find one to update.
$price_list_item = NULL;
if (!$delete_existing) {
$result = $price_list_item_storage
->getQuery()
->condition('type', $price_list
->bundle())
->condition('price_list_id', $price_list
->id())
->condition('purchasable_entity', $purchasable_entity
->id())
->condition('quantity', $quantity)
->execute();
if (!empty($result)) {
$existing_price_list_item_id = reset($result);
$price_list_item = $price_list_item_storage
->load($existing_price_list_item_id);
assert($price_list_item instanceof PriceListItemInterface);
}
}
if (is_null($price_list_item)) {
// No price list item was found and updated, create a new one.
$price_list_item = $price_list_item_storage
->create([
'type' => $price_list
->bundle(),
'price_list_id' => $price_list
->id(),
'purchasable_entity' => $purchasable_entity
->id(),
'quantity' => $quantity,
]);
}
try {
static::processRow($row, $price_list_item);
} catch (\Exception $e) {
$context['results']['import_skipped']++;
$import_count++;
$csv
->next();
continue;
}
$import_type = $price_list_item
->isNew() ? 'created' : 'updated';
$price_list_item
->save();
$import_count++;
$context['results']['import_' . $import_type]++;
$csv
->next();
}
$context['message'] = t('Importing @created of @import_total price list items', [
'@created' => $import_count,
'@import_total' => $import_total,
]);
$context['finished'] = $import_count / $import_total;
}
/**
* Batch process to delete the uploaded CSV.
*
* @param int $file_id
* The file ID.
* @param array $context
* The batch context.
*/
public static function batchDeleteUploadedFile($file_id, array &$context) {
$file_storage = \Drupal::entityTypeManager()
->getStorage('file');
$file = $file_storage
->load($file_id);
$file
->delete();
$context['message'] = t('Removing uploaded CSV.');
$context['finished'] = 1;
}
/**
* Batch finished callback: display batch statistics.
*
* @param bool $success
* Indicates whether the batch has completed successfully.
* @param mixed[] $results
* The array of results gathered by the batch processing.
* @param string[] $operations
* If $success is FALSE, contains the operations that remained unprocessed.
*/
public static function finishBatch($success, array $results, array $operations) {
if (!$success) {
$error_operation = reset($operations);
\Drupal::messenger()
->addError(t('An error occurred while processing @operation with arguments: @args', [
'@operation' => $error_operation[0],
'@args' => (string) print_r($error_operation[0], TRUE),
]));
return;
}
if (!empty($results['error'])) {
\Drupal::messenger()
->addError($results['error_message']);
}
else {
if (!empty($results['import_created'])) {
\Drupal::messenger()
->addMessage(\Drupal::translation()
->formatPlural($results['import_created'], 'Imported 1 price.', 'Imported @count prices.'));
}
if (!empty($results['import_updated'])) {
\Drupal::messenger()
->addMessage(\Drupal::translation()
->formatPlural($results['import_updated'], 'Updated 1 price.', 'Updated @count prices.'));
}
if (!empty($results['import_skipped'])) {
\Drupal::messenger()
->addWarning(\Drupal::translation()
->formatPlural($results['import_skipped'], 'Skipped 1 price during import.', 'Skipped @count prices during import.'));
}
}
}
/**
* Builds the header mapping.
*
* @param array $mapping
* The configured column mapping.
*
* @return array
* The header mapping (real_column => mapped_column).
*/
protected static function buildHeaderMapping(array $mapping) {
$header_mapping = [
$mapping['purchasable_entity_column'] => 'purchasable_entity',
$mapping['quantity_column'] => 'quantity',
];
// The list price column is optional.
if (!empty($mapping['list_price_column'])) {
$header_mapping += [
$mapping['list_price_column'] => 'list_price',
];
}
$header_mapping += [
$mapping['price_column'] => 'price',
$mapping['currency_column'] => 'currency_code',
];
return $header_mapping;
}
/**
* Processes the given CSV row and price list item.
*
* @param array $row
* The CSV row to process.
* @param \Drupal\commerce_pricelist\Entity\PriceListItemInterface $price_list_item
* The price list item.
*/
protected static function processRow(array $row, PriceListItemInterface $price_list_item) {
$currency_code = $row['currency_code'];
// If the price is given in a format like "4 000" we should allow it.
$row['price'] = str_replace(' ', '', $row['price']);
$price = new Price($row['price'], $currency_code);
$price_list_item
->setPrice($price);
$list_price = NULL;
if (isset($row['list_price']) && $row['list_price'] !== '') {
$row['list_price'] = str_replace(' ', '', $row['list_price']);
$list_price = new Price($row['list_price'], $currency_code);
$price_list_item
->setListPrice($list_price);
}
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
DependencySerializationTrait:: |
protected | property | An array of entity type IDs keyed by the property name of their storages. | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
DependencySerializationTrait:: |
public | function | 1 | |
DependencySerializationTrait:: |
public | function | 2 | |
FormBase:: |
protected | property | The config factory. | 1 |
FormBase:: |
protected | property | The request stack. | 1 |
FormBase:: |
protected | property | The route match. | |
FormBase:: |
protected | function | Retrieves a configuration object. | |
FormBase:: |
protected | function | Gets the config factory for this form. | 1 |
FormBase:: |
private | function | Returns the service container. | |
FormBase:: |
protected | function | Gets the current user. | |
FormBase:: |
protected | function | Gets the request object. | |
FormBase:: |
protected | function | Gets the route match. | |
FormBase:: |
protected | function | Gets the logger for a specific channel. | |
FormBase:: |
protected | function |
Returns a redirect response object for the specified route. Overrides UrlGeneratorTrait:: |
|
FormBase:: |
public | function | Resets the configuration factory. | |
FormBase:: |
public | function | Sets the config factory for this form. | |
FormBase:: |
public | function | Sets the request stack object to use. | |
LinkGeneratorTrait:: |
protected | property | The link generator. | 1 |
LinkGeneratorTrait:: |
protected | function | Returns the link generator. | |
LinkGeneratorTrait:: |
protected | function | Renders a link to a route given a route name and its parameters. | |
LinkGeneratorTrait:: |
public | function | Sets the link generator service. | |
LoggerChannelTrait:: |
protected | property | The logger channel factory service. | |
LoggerChannelTrait:: |
protected | function | Gets the logger for a specific channel. | |
LoggerChannelTrait:: |
public | function | Injects the logger channel factory. | |
MessengerTrait:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
PriceListItemImportForm:: |
protected | property | The entity field manager. | |
PriceListItemImportForm:: |
protected | property | The entity type manager. | |
PriceListItemImportForm:: |
public static | function | Batch operation to delete existing items from the price list. | |
PriceListItemImportForm:: |
public static | function | Batch process to delete the uploaded CSV. | |
PriceListItemImportForm:: |
public static | function | Batch process to import price list items from the CSV. | |
PriceListItemImportForm:: |
constant | The number of price list items to process in each batch. | ||
PriceListItemImportForm:: |
public | function |
Form constructor. Overrides FormInterface:: |
|
PriceListItemImportForm:: |
protected static | function | Builds the header mapping. | |
PriceListItemImportForm:: |
public static | function |
Instantiates a new instance of this class. Overrides FormBase:: |
|
PriceListItemImportForm:: |
public static | function | Batch finished callback: display batch statistics. | |
PriceListItemImportForm:: |
public | function |
Returns a unique string identifying the form. Overrides FormInterface:: |
|
PriceListItemImportForm:: |
protected static | function | Processes the given CSV row and price list item. | |
PriceListItemImportForm:: |
public | function |
Form submission handler. Overrides FormInterface:: |
|
PriceListItemImportForm:: |
public | function |
Form validation handler. Overrides FormBase:: |
|
PriceListItemImportForm:: |
public | function | Constructs a new PriceListItemImportForm object. | |
RedirectDestinationTrait:: |
protected | property | The redirect destination service. | 1 |
RedirectDestinationTrait:: |
protected | function | Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url. | |
RedirectDestinationTrait:: |
protected | function | Returns the redirect destination service. | |
RedirectDestinationTrait:: |
public | function | Sets the redirect destination service. | |
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. | |
UrlGeneratorTrait:: |
protected | property | The url generator. | |
UrlGeneratorTrait:: |
protected | function | Returns the URL generator service. | |
UrlGeneratorTrait:: |
public | function | Sets the URL generator service. | |
UrlGeneratorTrait:: |
protected | function | Generates a URL or path for a specific route based on the given parameters. |