auctions-1.0.x-dev/modules/auctions_core/src/Form/BiddersForm.php
modules/auctions_core/src/Form/BiddersForm.php
<?php
namespace Drupal\auctions_core\Form;
use Drupal\auctions_core\AuctionToolsTrait;
use Drupal\auctions_core\Entity\AuctionAutobidInterface;
use Drupal\auctions_core\Entity\AuctionItem;
use Drupal\auctions_core\Plugin\Validation\Constraint\CurbBids;
use Drupal\auctions_core\Service\AuctionTools;
use Drupal\Component\Utility\Html;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Datetime\DateFormatter;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Class HelloForm.
*/
class BiddersForm extends FormBase {
use AuctionToolsTrait;
/**
* Drupal\Core\Messenger\MessengerInterface definition.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* The current user account.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Entity Type Manager.
*
* @var Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* AuctionTools.
*
* @var Drupal\auctions_core\Service\AuctionTools
*/
protected $auctionTools;
/**
* Core module handler.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
public $configFactory;
/**
* The Date Fromatter.
*
* @var Drupal\Core\Datetime\DateFormatter
*/
protected $dateFormatter;
/**
* Constructs a new BiddersForm object.
*
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* Messenger Factory.
* @param Drupal\Core\Session\AccountInterface $currentUser
* Account Interface.
* @param Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* Entity Type Manager.
* @param \Drupal\auctions_core\Service\AuctionTools $auctionTools
* AuctionTools service.
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* Configuration Factory.
* @param Drupal\Core\Datetime\DateFormatter $dateFormatter
* Date Formatter.
*/
public function __construct(MessengerInterface $messenger, AccountInterface $currentUser, EntityTypeManagerInterface $entityTypeManager, AuctionTools $auctionTools, ConfigFactoryInterface $configFactory, DateFormatter $dateFormatter) {
$this->messenger = $messenger;
$this->currentUser = $currentUser;
$this->entityTypeManager = $entityTypeManager;
$this->auctionTools = $auctionTools;
$this->configFactory = $configFactory;
$this->dateFormatter = $dateFormatter;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('messenger'),
$container->get('current_user'),
$container->get('entity_type.manager'),
$container->get('auctions_core.tools'),
$container->get('config.factory'),
$container->get('date.formatter')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'auctions_core_bidders';
}
/**
* {@inheritdoc}
*
* $method='dialog'. Preliminary ability to use HTML Dialogs with Bidders Form.
*/
public function buildForm(array $form, FormStateInterface $form_state, $auctionItem = NULL, $method = NULL) {
$auctionConf = $this->configFactory()->getEditable('auctions.item_settings');
$dollar = $auctionConf->get('dollar-symbol');
$thous = $auctionConf->get('thousand-separator');
$dec = $auctionConf->get('decimal-separator');
$item = (!empty($auctionItem[0])&& ($auctionItem[0] instanceof AuctionItem)) ? $auctionItem[0] : FALSE;
// Set form for HTML Dialog uses.
if ($method == 'dialog') {
$form['#attributes']['method'] = 'dialog';
}
$form['#attributes']['id'] = Html::getUniqueId(Html::cleanCssIdentifier($this->getFormId() . '-item-' . $item->getId()));
$form['#attributes']['data-auction-item'] = $item->getId();
$currency = '<small>' . $item->getCurrencyCode() . '</small>';
if ($item) {
// Send only id, refresh data.
$form_state->setFormState([
'auction_item_id' => $item->getId(),
]);
$hasBuyNow = $item->hasBuyNow();
$instantOnly = $item->getInstantOnly();
$form['#attributes']['class'][] = 'instant-mode-'. ( $instantOnly ? 'enabled':'disabled');
$canBid = $this->currentUser->hasPermission('add auction bids entities');
if (!$canBid) {
$form['permission-denied'] = [
'#type' => 'inline_template',
'#template' => '<p>{{ message }}</p>',
'#context' => [
'message' => $this->t('You are not able to bid at this time'),
],
];
}
elseif ($this->currentUser->isAnonymous()) {
$destinationUrl = Url::fromRoute('<current>');
$loginLink = Link::createFromRoute($this->t('Login'), 'user.login', [], ['query' => ['destination' => $destinationUrl->toString()]])->toString();
$form['is-anon'] = [
'#type' => 'inline_template',
'#template' => '<p>{{ message }}</p><ul><li>{{ login }}</li><li>{{ register }}</li></ul>',
'#context' => [
'message' => $this->t('Please log in to bid'),
'login' => $loginLink,
'register' => Link::createFromRoute($this->t('Register'), 'user.register')->toString(),
],
];
}
else {
if ($item->isClosed()) {
$form['#attributes']['class'][] = 'auction-item-closed';
$form['aution-closed'] = [
'#type' => 'item',
'#description' => $this->t('Auction is Closed.'),
];
}
elseif ($item->isOpen()) {
$currentHightest = $item->seekCurrentHightest();
$minPrice = $currentHightest['minPrice'];
$selfBid = ($currentHightest['leadBid'] && $currentHightest['leadBid']->getOwnerId() == $this->currentUser->id()) ? TRUE : FALSE;
$form['header'] = [
'#type' => 'container',
'#prefix' => '<header>',
'#suffix' => '</header>',
'#access' => !$instantOnly,
'#attributes' => ['class' => ['bid-refresh-wrapper']],
];
$form['header']['current'] = [
'#type' => 'markup',
'#markup' => '<dl><dt>' . $this->t('Current Offer: ') . '</dt><dd><span class="currency-symbol">' . $dollar . '</span><span class="current-formatted" data-auction-item="' . $item->id() . '">' . $this->showAsCents($minPrice, $dec, $thous) . '</span> <span class="currency-abbr">' . $currency . '</span></dd></dl>',
];
// Phrasing our Rate for such small timeframe.
$refreshRate = $auctionConf->get('ajax-rate');
$ratePhrase = $this->t('@rate seconds', ['@rate' => $refreshRate]);
if ($refreshRate == 60) {
$ratePhrase = $this->t('1 Minute');
}
elseif ($refreshRate == 1) {
$ratePhrase = $this->t('1 Second');
}
elseif ($refreshRate < 1) {
$ratePhrase = $this->t('@rate second', ['@rate' => $refreshRate]);
}
$dates = $item->getDateStatus();
$form['header']['bid-refresh'] = [
'#access' => $auctionConf->get('ajax-refresh') === 1 ? TRUE : FALSE,
'#theme' => 'auction_bid_refresh',
'#id' => $item->getId(),
'#rate' => $auctionConf->get('ajax-rate'),
'#adrenaline' => $auctionConf->get('refresh-adrenaline'),
'#until' => $dates['date_formatted']['end_unix'],
'#rate_phrase' => $this->t('Refresh Rate: @rate', ['@rate' => $ratePhrase]),
'#preactivate' => $auctionConf->get('ajax-preactivate') === 1 ? 'true' : 'false',
];
$form['header']['status'] = [
'#type' => 'container',
'#attributes' => ['class' => ['bid-status-wrapper']],
];
$selfMessage = '';
if ($selfBid) {
$selfMessage = $this->selfBidPhrase();
$form['header']['status']['self-bid'] = [
'#type' => 'markup',
'#markup' => '<span>' . $selfMessage . '</span>',
];
}
$form['#attached']['drupalSettings']['auctionsCoreBiddersForm'] = [
'selfBid' => $selfMessage,
];
$hasAutobid = $this->auctionTools->seekAutobid(
$this->currentUser->id(),
$item->id(),
$item->getRelistCount()
);
// Audobids section.
$currentAutobid = FALSE;
$automaxTitle = $this->t('Your autobid maximum');
$autoBidOptInTitle = $this->t('Autobidding Opt-in.');
if ($hasAutobid instanceof AuctionAutobidInterface) {
if ($hasAutobid->getAmountMax() > $item->seekCurrentHightest()['minPrice']) {
$currentAutobid = $hasAutobid;
$automaxTitle = $this->t('Adjust your autobid maximum');
}
else {
$currentAutobid = FALSE;
$autoBidOptInTitle = $this->t('Your autoBid has been outbidded. re-Autobidding Opt-in.');
}
}
$showAutobidding = !$instantOnly && $auctionConf->get('autobid-mode') == 1;
$form['autobid'] = [
'#type' => 'details',
'#open' => $currentAutobid,
'#access' => $this->currentUser->hasPermission('add auction autobid entities') && $showAutobidding,
'#title' => $this->t('Auto Bidding'),
'#attributes' => [
'class' => [
'autobid-wrapper',
],
],
];
$amountMax = $currentAutobid ? $currentAutobid->getAmountMax() : ($minPrice + $item->getBidStep());
$form['autobid']['last-bidder'] = [
'#type' => 'item',
'#access' => $currentAutobid ? TRUE : FALSE,
'#markup' => $this->t('Your current maximum:'),
'#description' => $dollar . $this->showAsCents($amountMax, $dec, $thous) . ' ' . $currency,
];
$form['autobid']['autobid_opt'] = [
'#type' => 'checkbox',
'#title' => $autoBidOptInTitle,
'#access' => !$currentAutobid ,
'#default_value' => $currentAutobid ? 1 : 0,
'#states' => [
'invisible' => [
':input[name*="autobid_opt"]' => ['checked' => TRUE],
],
],
];
$form['autobid']['amount_max'] = [
'#type' => 'number',
'#field_prefix' => $dollar,
'#title' => $automaxTitle,
'#description' => $this->t("This item's Bid Step is @dollar@bidStep:", [
'@dollar' => $dollar,
'@bidStep' => $auctionItem[0]->getBidStep(),
]),
'#step' => 'any',
'#min' => $minPrice + ($item->getBidStep() * 2),
'#states' => [
'invisible' => [
':input[name*="autobid_opt"]' => ['checked' => FALSE],
],
/* Reminder: can't make required or browser validation will loop if opt-out btn is used. */
],
];
if ($currentAutobid) {
$form['autobid']['amount_max']['#access'] = FALSE;
}
$form['autobid']['opt_out'] = [
'#type' => 'submit',
'#name' => 'opt-out',
'#access' => isset($currentAutobid) && $currentAutobid ? TRUE : FALSE,
'#submit' => ['::optOutSubmit'],
'#limit_validation_errors' => [],
'#value' => $this->t('Remove my Auto Bidding'),
'#attributes' => [
'title' => $this->t("'Opt Out' without placing a bid."),
'alt' => $this->t("Click here to 'opt out' of Auto Bidding without placing a bid."),
],
'#states' => [
'invisible' => [
':input[name*="autobid_opt"]' => ['checked' => FALSE],
],
],
];
$form['bids'] = [
'#type' => 'container',
'#access' => !$instantOnly,
'#attributes' => ['class' => ['standard-bid', 'clearfix']],
];
$form['bids']['amount'] = [
'#type' => 'number',
'#field_prefix' => $dollar,
'#required' => TRUE,
'#field_suffix' => $currency,
'#title' => $this->t('Your Bid'),
'#title_display' => 'invisible',
'#min' => ($minPrice + .01),
'#size' => 12,
'#step' => 'any',
];
$default = $minPrice + $item->getBidStep();
$form['bids']['amount']['#default_value'] = $default;
$form['bids']['amount']['#attributes']['data-refresh'] = $minPrice;
// Bid Refresh, items need always be output.
if ($selfBid) {
// $form['bids']['amount']['#disabled'] = 'disabled';
}
$form['bids']['submit'] = [
'#type' => 'submit',
'#name' => 'submit',
'#value' => $this->t('Place your bid'),
];
// Instant Bid section.
$bidThreshold = $item->seekBidThreshold();
$form['instant'] = [
'#type' => $instantOnly ? 'fieldset' : 'details',
'#open' => $instantOnly,
'#title' => $this->t('Instant Buy'),
/* todo: format from conf . */
'#markup' => $dollar . $this->showAsCents($item->getPriceBuyNow(), $dec, $thous) . ' ' . $currency,
'#access' => ($instantOnly || $hasBuyNow) && !$bidThreshold,
'#attributes' => [
'class' => [
'instant-bid',
],
],
];
$form['instant']['buy_now_verify'] = [
'#type' => 'checkbox',
'#title' => $this->t("Yes, I'ld like to Buy Now"),
];
$form['instant']['buy_now'] = [
'#type' => 'submit',
'#name' => 'buy-now',
'#submit' => ['::buyNowSubmit'],
'#validate' => ['::buyNowValidate'],
'#value' => $this->t('Buy Now!'),
'#states' => [
'invisible' => [
':input[name*="buy_now_verify"]' => ['checked' => FALSE],
],
],
];
}
else {
$form['auction-open'] = [
'#type' => 'item',
'#description' => $this->t('Bidding is not yet Open.'),
];
}
}
}
$form['#attached']['library'][] = 'auctions_core/bidders';
$form['#cache']['max-age'] = 0;
return $form;
}
/**
* Form validation handler for the Buy Now submission.
*
* This method checks if the Buy Now submission is valid and
* displays errors if any validation checks fail.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function buyNowValidate(array &$form, FormStateInterface $form_state) {
$curbBids = new CurbBids();
$itemId = $form_state->get('auction_item_id');
$item = \Drupal::entityTypeManager()->getStorage('auction_item')->load($itemId);
if ($item->isClosed()) {
$form_state->setErrorByName('instant-price', $curbBids->auctionHasClosed);
}
$verify = $form_state->getValues()['buy_now_verify'];
if ($verify == 0) {
$form_state->setErrorByName('buy_now_verify', $this->t('You must pre-verify <q>Buy Now</q> submission.'));
}
}
/**
* Form submission handler for opting out of Auto Bidding.
*
* This method handles the form submission when a user chooses to opt out of
* Auto Bidding for a specific auction item.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function optOutSubmit(array &$form, FormStateInterface $form_state) {
$itemId = $form_state->get('auction_item_id');
$item = \Drupal::entityTypeManager()->getStorage('auction_item')->load($itemId);
$this->auctionTools->removeAutobid($this->currentUser->id(), $itemId, $item->getRelistCount());
}
/**
* {@inheritdoc}
*/
public function buyNowSubmit(array &$form, FormStateInterface $form_state) {
$itemId = $form_state->get('auction_item_id');
$item = \Drupal::entityTypeManager()->getStorage('auction_item')->load($itemId);
$bid = $this->auctionTools->handleBid(
$this->currentUser->id(),
$item,
$item->getPriceBuyNow(),
FALSE,
TRUE
);
if ($bid->id()) {
$this->messenger()->addMessage($this->t('Congratulations! You have placed an Instant Buy! $%price.', [
'%price' => $this->showAsCents($item->getPriceBuyNow(), '.', ','),
]));
}
else {
$this->messenger()->addError($this->t('Something went wrong, please contact an Administrator.'));
}
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$triggerBtn = $form_state->getTriggeringElement();
if ($triggerBtn !== NULL && isset($triggerBtn['#name']) && $triggerBtn['#name'] == 'submit') {
$curbBids = new CurbBids();
$itemId = $form_state->get('auction_item_id');
$item = \Drupal::entityTypeManager()->getStorage('auction_item')->load($itemId);
$itemRelist = $item->getRelistCount();
$currentBids = $item->getBids($itemRelist, 3);
$processBids = $item->summarizeBids($currentBids);
$amount = \floatval($form_state->getValue('amount'));
if (!empty($processBids[0]) && $processBids[0]['uid'] == $this->currentUser->id()) {
// Since this is the heaviest rule: check for it first.
$form_state->setErrorByName('amount', $curbBids->lastBidIsYours);
}
$itemPriceStarting = $item->getPriceStarting();
$itemWorkflow = $item->getWorkflow();
// Check if the value is an number.
if ($itemWorkflow == 3 || $itemWorkflow == 4) {
$form_state->setErrorByName('amount', $curbBids->auctionFinished);
}
if ($itemWorkflow == 0) {
$form_state->setErrorByName('amount', $curbBids->auctionNew);
}
// If is past end time. @page load vs submit post.
$now = new DrupalDateTime('now');
$auctionDates = $item->getDate();
$getUserTimezone = date_default_timezone_get();
$userTimezone = new \DateTimeZone($getUserTimezone);
$auctionEnds = DrupalDateTime::createFromFormat('Y-m-d\TH:i:s', $auctionDates['end'], $this->auctionDefaultTimeZone())->setTimezone($userTimezone)->format('U');
if ($now->format('U') > $auctionEnds) {
$auctionEndDateTime = $auctionDates['end'];
$endDate = DrupalDateTime::createFromFormat('Y-m-d\TH:i:s', $auctionEndDateTime, $this->auctionDefaultTimeZone())
->setTimezone($userTimezone)
->format('Y-m-d H:i:s');
$form_state->setErrorByName('amount', str_replace('%endDate', $endDate, $curbBids->auctionHasExpired));
}
// If isn't higher than last/threshold.
$highestCurrent = $item->seekCurrentHightest();
if ((!empty($processBids[0]) && $amount < $processBids[0]['amount'])
||
(!($amount > ($highestCurrent['minPrice'])))
) {
$form_state->setErrorByName('amount', $curbBids->higherThanLastBid);
}
// Autobid.
$hasAutobid = $this->auctionTools->seekAutobid($this->currentUser->id(), $item->id(), $itemRelist);
$currentAutobid = FALSE;
if ($hasAutobid instanceof AuctionAutobidInterface) {
$currentAutobid = $hasAutobid;
}
$autobidOpt = $form_state->getValue('autobid_opt');
// Reminder: all form buttons should have unique #name.
$triggerBtn = $form_state->getTriggeringElement()['#name'];
$amountMax = $this->roundCents((float) $form_state->getValue('amount_max'));
if ($triggerBtn != 'opt-out'
&& $autobidOpt == 1
&& ($currentAutobid && $amountMax != 0)
&& $amountMax < $highestCurrent['minPrice']
) {
$form_state->setErrorByName('amount_max', $this->t('Your Max autobid has to be higher than current highest bid. Try a higher amount or click <q>Opt Out</q> .'));
}
parent::validateForm($form, $form_state);
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$itemId = $form_state->get('auction_item_id');
$item = \Drupal::entityTypeManager()->getStorage('auction_item')->load($itemId);
$autobidOpt = $form_state->getValue('autobid_opt');
// Reminder: all form buttons should have unique #name.
$amountMax = \floatval($form_state->getValue('amount_max'));
if ($autobidOpt == 1 && $amountMax != 0) {
// Process autobid.
$this->auctionTools->handleAutobid(
$this->currentUser->id(),
$item->id(),
$item->getRelistCount(),
$amountMax
);
}
$amount = \floatval($form_state->getValue('amount'));
$bid = $this->auctionTools->handleBid(
$this->currentUser->id(),
$item,
$amount
);
if ($bid->id()) {
$this->messenger()->addMessage($this->t('Congratulations! You have placed your bid! $%price.', [
'%price' => $this->showAsCents($amount, '.', ','),
]));
}
else {
$this->messenger()->addError($this->t('Bidding failed.'));
}
}
}
