I have tried to add new field in magento2 billing address section. I have followed below link to add new field in shipping address block
http://oyenetwork.com/articles/magento2-devliery-date-module-creation-from-scratch/
I have added new field to shipping address section successfully. But in my site, I have been using "Virtual products". So I want to add my new custom field into billing section. I just modified that "LayoutProcessorPlugin.php" code like below
$jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['payments-list']['children']['delivery_date']
instead of
$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']['shippingAddress']['children']['shipping-address-fieldset']['children']['delivery_date']
But it doesn't working. How to add my new custom field to Billing address block in magento2?
Please check below code to save custom address attribute in customer,checkout shipping and billing form and also save in order table.
Module Name : Ccc_Checkout
Script to create custom attribute for address and order
app/code/Ccc/Checkout/Setup/UpgradeData.php
<?php
namespace Ccc\Checkout\Setup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\UpgradeDataInterface;
use Magento\Eav\Model\Config;
use Magento\Eav\Model\Entity\Attribute\SetFactory as AttributeSetFactory;
class UpgradeData implements UpgradeDataInterface
{
private $eavSetupFactory;
/**
* #var Config
*/
private $eavConfig;
/**
* #var AttributeSetFactory
*/
private $attributeSetFactory;
public function __construct(
Config $eavConfig,
EavSetupFactory $eavSetupFactory,
AttributeSetFactory $attributeSetFactory
)
{
$this->eavSetupFactory = $eavSetupFactory;
$this->eavConfig = $eavConfig;
$this->attributeSetFactory = $attributeSetFactory;
}
/**
* {#inheritdoc}
*/
public function upgrade(
ModuleDataSetupInterface $setup,
ModuleContextInterface $context
) {
$setup->startSetup();
if (version_compare($context->getVersion(), '0.0.2','<')) {
$this->addUnitNumberFieldToAddress($setup);
}
if (version_compare($context->getVersion(), '0.0.3','<')) {
$this->updateUnitAttribute($setup);
}
$setup->endSetup();
}
/**
* put your comment there...
*
* #param mixed $setup
*/
protected function addUnitNumberFieldToAddress($setup)
{
$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
$eavSetup->addAttribute('customer_address', 'unit_number', [
'type' => 'varchar',
'input' => 'text',
'label' => 'Unit Number',
'visible' => true,
'required' => false,
'user_defined' => true,
'system'=> false,
'group'=> 'General',
'sort_order' => 71,
'global' => true,
'visible_on_front' => true,
]);
$customAttribute = $this->eavConfig->getAttribute('customer_address', 'unit_number');
$customAttribute->setData(
'used_in_forms',
['adminhtml_customer_address','customer_address_edit','customer_register_address']
);
$customAttribute->save();
$installer = $setup;
$installer->getConnection()->addColumn(
$installer->getTable('quote_address'),
'unit_number',
[
'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
'length' => 255,
'comment' => 'Unit Number'
]
);
$installer->getConnection()->addColumn(
$installer->getTable('sales_order_address'),
'unit_number',
[
'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
'length' => 255,
'comment' => 'Unit Number'
]
);
}
public function updateUnitAttribute($setup)
{
$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
$eavSetup->updateAttribute('customer_address', 'unit_number', 'sort_order', '71');
}
}
app/code/Ccc/Checkout/etc/di.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Checkout\Block\Checkout\LayoutProcessor">
<plugin disabled="false" name="BillingLayoutProcessor" sortOrder="99" type="Ccc\Checkout\Plugin\Block\Checkout\LayoutProcessor"/>
</type>
<type name="Magento\Quote\Model\BillingAddressManagement">
<plugin disabled="false" name="Ccc_Checkout_Plugin_Magento_Quote_Model_BillingAddressManagement" sortOrder="10" type="Ccc\Checkout\Plugin\Magento\Quote\Model\BillingAddressManagement"/>
</type>
<type name="Magento\Quote\Model\Quote\Address\BillingAddressPersister">
<plugin disabled="false" name="BillingAddressSave" sortOrder="10" type="Ccc\Checkout\Plugin\Magento\Quote\Model\Quote\Address\BillingAddressPersister"/>
</type>
<type name="Magento\Quote\Model\ShippingAddressManagement">
<plugin disabled="false" name="Ccc_Checkout_Plugin_Magento_Quote_Model_ShippingAddressManagement" sortOrder="10" type="Ccc\Checkout\Plugin\Magento\Quote\Model\ShippingAddressManagement"/>
</type>
</config>
app/code/Ccc/Checkout/etc/events.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="sales_model_service_quote_submit_success">
<observer name="custome_address_attribute_save" instance="Ccc\Checkout\Observer\SaveUnitNumberInOrder"/>
</event>
</config>
app/code/Ccc/Checkout/etc/extension_attributes.xml
<?xml version="1.0" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
<extension_attributes for="Magento\Quote\Api\Data\AddressInterface">
<attribute code="unit_number" type="string"/>
</extension_attributes>
</config>
Create Plugin to display custom attribute in checkout billing and shipping form
app/code/Ccc/Checkout/Plugin/Block/Checkout/LayoutProcessor
<?php
namespace Ccc\Checkout\Plugin\Block\Checkout;
use \Magento\Checkout\Block\Checkout\LayoutProcessor as MageLayoutProcessor;
class LayoutProcessor
{
protected $_customAttributeCode = 'unit_number';
public function afterProcess(MageLayoutProcessor $subject, $jsLayout)
{
if (isset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']
['payment']['children']['payments-list']['children']))
{
foreach ($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['payments-list']['children'] as $key => $payment)
{
$paymentCode = 'billingAddress'.str_replace('-form','',$key);
$jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['payments-list']['children'][$key]['children']['form-fields']['children'][$this->_customAttributeCode] = $this->getUnitNumberAttributeForAddress($paymentCode);
}
}
if(isset($jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']['shippingAddress']['children']['shipping-address-fieldset'])
){
$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']['shippingAddress']['children']['shipping-address-fieldset']['children'][$this->_customAttributeCode] = $this->getUnitNumberAttributeForAddress('shippingAddress');
}
return $jsLayout;
}
public function getUnitNumberAttributeForAddress($addressType)
{
return $customField = [
'component' => 'Magento_Ui/js/form/element/abstract',
'config' => [
'customScope' => $addressType.'.custom_attributes',
'customEntry' => null,
'template' => 'ui/form/field',
'elementTmpl' => 'ui/form/element/input'
],
'dataScope' => $addressType.'.custom_attributes' . '.' . $this->_customAttributeCode,
'label' => 'Unit Number',
'provider' => 'checkoutProvider',
'sortOrder' => 71,
'validation' => [
'required-entry' => false
],
'options' => [],
'filterBy' => null,
'customEntry' => null,
'visible' => true,
];
}
}
To save custom attribute in checkout
app/code/Ccc/Checkout/Plugin/Magento/Quote/Model/ShippingAddressManagement
<?php
namespace Ccc\Checkout\Plugin\Magento\Quote\Model;
class ShippingAddressManagement
{
protected $logger;
public function __construct(
\Psr\Log\LoggerInterface $logger
) {
$this->logger = $logger;
}
public function beforeAssign(
\Magento\Quote\Model\ShippingAddressManagement $subject,
$cartId,
\Magento\Quote\Api\Data\AddressInterface $address
) {
$extAttributes = $address->getExtensionAttributes();
if (!empty($extAttributes)) {
try {
$address->setUnitNumber($extAttributes->getUnitNumber());
} catch (\Exception $e) {
$this->logger->critical($e->getMessage());
}
}
}
}
app/code/Ccc/Checkout/Plugin/Magento/Quote/Model/BillingAddressManagement
<?php
namespace Ccc\Checkout\Plugin\Magento\Quote\Model;
class BillingAddressManagement
{
protected $logger;
public function __construct(
\Psr\Log\LoggerInterface $logger
) {
$this->logger = $logger;
}
public function beforeAssign(
\Magento\Quote\Model\BillingAddressManagement $subject,
$cartId,
\Magento\Quote\Api\Data\AddressInterface $address,
$useForShipping = false
) {
$extAttributes = $address->getExtensionAttributes();
if (!empty($extAttributes)) {
try {
$address->setUnitNumber($extAttributes->getUnitNumber());
} catch (\Exception $e) {
$this->logger->critical($e->getMessage());
}
}
}
}
app/code/Ccc/Checkout/Ccc/Checkout/Plugin/Magento/Quote/Model/Quote/Address
<?php
namespace Ccc\Checkout\Plugin\Magento\Quote\Model\Quote\Address;
class BillingAddressPersister
{
protected $logger;
public function __construct(
\Psr\Log\LoggerInterface $logger
) {
$this->logger = $logger;
}
public function beforeSave(
\Magento\Quote\Model\Quote\Address\BillingAddressPersister $subject,
$quote,
\Magento\Quote\Api\Data\AddressInterface $address,
$useForShipping = false
) {
$extAttributes = $address->getExtensionAttributes();
if (!empty($extAttributes)) {
try {
$address->setUnitNumber($extAttributes->getUnitNumber());
} catch (\Exception $e) {
$this->logger->critical($e->getMessage());
}
}
}
}
To set custom attribute in extension attribute
app/code/Ccc/Checkout/view/frontend/requirejs-config.js
var config = {
config: {
mixins: {
'Magento_Checkout/js/model/payment/method-group': {
'Ccc_Checkout/js/model/payment/method-group-mixin': true
},
'Magento_Checkout/js/action/set-billing-address': {
'Ccc_Checkout/js/action/set-billing-address-mixin': true
},
'Magento_Checkout/js/action/set-shipping-information': {
'Ccc_Checkout/js/action/set-shipping-information-mixin': true
},
'Magento_Checkout/js/action/create-shipping-address': {
'Ccc_Checkout/js/action/create-shipping-address-mixin': true
},
'Magento_Checkout/js/action/place-order': {
'Ccc_Checkout/js/action/set-billing-address-mixin': true
},
'Magento_Checkout/js/action/create-billing-address': {
'Ccc_Checkout/js/action/set-billing-address-mixin': true
}
}
}
};
app/code/Ccc/Checkout/view/frontend/web/js/action/create-shipping-address-mixin.js
define([
'jquery',
'mage/utils/wrapper',
'Magento_Checkout/js/model/quote'
], function ($, wrapper,quote) {
'use strict';
return function (setShippingInformationAction) {
return wrapper.wrap(setShippingInformationAction, function (originalAction, messageContainer) {
if (messageContainer.custom_attributes != undefined) {
$.each(messageContainer.custom_attributes , function( key, value ) {
messageContainer['custom_attributes'][key] = {'attribute_code':key,'value':value};
});
}
return originalAction(messageContainer);
});
};
});
app/code/Ccc/Checkout/view/frontend/web/js/action/set-billing-address-mixin.js
define([
'jquery',
'mage/utils/wrapper',
'Magento_Checkout/js/model/quote'
], function ($, wrapper,quote) {
'use strict';
return function (setBillingAddressAction) {
return wrapper.wrap(setBillingAddressAction, function (originalAction, messageContainer) {
var billingAddress = quote.billingAddress();
if(billingAddress != undefined) {
if (billingAddress['extension_attributes'] === undefined) {
billingAddress['extension_attributes'] = {};
}
if (billingAddress.customAttributes != undefined) {
$.each(billingAddress.customAttributes, function (key, value) {
if($.isPlainObject(value)){
value = value['value'];
}
billingAddress['extension_attributes'][key] = value;
});
}
}
return originalAction(messageContainer);
});
};
});
app/code/Ccc/Checkout/view/frontend/web/js/action/set-shipping-information-mixin.js
define([
'jquery',
'mage/utils/wrapper',
'Magento_Checkout/js/model/quote'
], function ($, wrapper,quote) {
'use strict';
return function (setShippingInformationAction) {
return wrapper.wrap(setShippingInformationAction, function (originalAction, messageContainer) {
var shippingAddress = quote.shippingAddress();
if (shippingAddress['extension_attributes'] === undefined) {
shippingAddress['extension_attributes'] = {};
}
if (shippingAddress.customAttributes != undefined) {
$.each(shippingAddress.customAttributes , function( key, value ) {
if($.isPlainObject(value)){
value = value['value'];
}
shippingAddress['customAttributes'][key] = value;
shippingAddress['extension_attributes'][key] = value;
});
}
return originalAction(messageContainer);
});
};
});
To save custom attribute in orders
app/code/Ccc/Checkout/Observer/SaveUnitNumberInOrder.php
<?php
namespace Ccc\Checkout\Observer;
class SaveUnitNumberInOrder implements \Magento\Framework\Event\ObserverInterface
{
public function execute(\Magento\Framework\Event\Observer $observer) {
$order = $observer->getEvent()->getOrder();
$quote = $observer->getEvent()->getQuote();
if ($quote->getBillingAddress()) {
$order->getBillingAddress()->setUnitNumber($quote->getBillingAddress()->getExtensionAttributes()->getUnitNumber());
}
if (!$quote->isVirtual()) {
$order->getShippingAddress()->setUnitNumber($quote->getShippingAddress()->getUnitNumber());
}
return $this;
}
}
To display custom attribute in customer account you need to overright customer edit.phtml file from vendor to your theme like below :
app/design/frontend/custom_theme/theme_name/Magento_Customer/templates/address/edit.phtml
<div class="field unit_number">
<label class="label" for="unit_number"><span><?php echo $block->escapeHtml(__('Unit Number')) ?></span></label>
<div class="control">
<input type="text" name="unit_number"
value="<?= $block->escapeHtmlAttr($block->getAddress()->getCustomAttribute('unit_number') ? $block->getAddress()->getCustomAttribute('unit_number')->getValue() : '') ?>"
title="<?= $block->escapeHtmlAttr($block->getAddress()->getCustomAttribute('unit_number') ? $block->getAddress()->getCustomAttribute('unit_number')->getValue() : '') ?>"
class="input-text <?= $block->escapeHtmlAttr($block->getAddress()->getCustomAttribute('unit_number') ? $block->getAddress()->getCustomAttribute('unit_number')->getValue() : '') ?>"
id="unit_number">
</div>
</div>
Maybe late, anyway:
$jsLayout['components']
['checkout']['children']
['steps']['children']
['billing-step']['children']
['payment']['children']
['afterMethods']['children']
['billing-address-form']['children']
['form-fields']['children']
[$customAttributeCode] = $customField;
should do the trick.
I can show my field in this way in the billing form.
Anyway, I am still studying which action should I capture to retrieve this value and to send it to the server.
Webkul has a great article on How To Add Custom Field At Billing Address Form In Magento 2, though it is not covering the full details on how to save this field to database and show on order details screen but it is helpful in displaying the field at the billing address.
https://webkul.com/blog/add-custom-field-billing-address-form-magento-2/
Related
** Very new to CodeIgniter so please be kind! **
I have an issue with my two user authentication forms: users/register.php and users/login.php where I cannot pass the post input to functions in my model.
As of now, I'm getting the error Call to member function addUser() on null on the registration form and a validation error on the login form that states the username/password don't match any credentials in the database. Both seem to stem from post being null although it is not.
I have done a var_dump on $login which is defined as $login = $this->request->getPost() as well as inspected the request in Firefox Developers Browser to find all the post data correctly displayed. I am stumped. Why can't I pass this array to my model?
Here is a screenshot of the post request for login.php (the same can be said for registration.php and is not included).
These are my routes:
// Login and Registration
$routes->match(['get', 'post'], 'users/register', 'Users::register');
$routes->match(['get', 'post'], 'users/login', 'Users::login', ["filter" => "noauth"]);
Here is my model UserModel.php in its entirety:
class UserModel extends Model
{
protected $DBGroup = 'default';
protected $table = 'users';
protected $primaryKey = 'username';
protected $useAutoIncrement = false;
protected $insertID = 0;
protected $returnType = 'object';
protected $useSoftDelete = false;
protected $allowedFields = [
'username',
'password',
'id',
'role',
'profile_image',
'profile_views',
'last_login',
'about_me',
'age',
'gender',
'occupation',
'hometown',
'country',
'fav_shape',
'fav_color',
'created',
'modified',
];
// Dates
protected $useTimestamps = true;
protected $dateFormat = 'datetime';
protected $createdField = 'created';
protected $modifiedField = 'modified';
// Callbacks
protected $allowCallbacks = true;
protected $beforeInsert = ['beforeInsert'];
public function __construct()
{
parent::__construct();
}
protected function beforeInsert(array $data)
{
$data = $this->passwordHash($data);
return $data;
}
protected function passwordHash(array $data)
{
if (isset($data['password'])) {
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
}
return $data;
}
public function lastLogin($username)
{
$this->builder()
->where('username', $username)
->update('last_login', date('Y-m-d H:i:s'));
}
public function addUser($newUser)
{
$builder = $this->builder()
->set($newUser)
->insert();
if ($builder->affected_rows() == 1) {
return TRUE;
} else {
return FALSE;
}
}
public function getUser($username)
{
$builder = $this->builder()
->where(['username' => $username])
->limit(1);
if ($builder->countAllResults() === 1) {
return $builder->get()->getRow();
} else {
return FALSE;
}
}
}
Here are excerpts from my controller Users.php:
class Users extends BaseController
{
protected $userModel;
public function __construct()
{
$userModel = new UserModel();
}
public function login()
{
$validation = \Config\Services::validation();
// Set session variable
$session = session();
if ($this->request->getMethod() === 'post' && ! empty($_POST)) {
$validation->getRuleGroup('login');
$validation->setRuleGroup('login');
$validation->withRequest($this->request)->run();
$recaptchaResponse = trim($this->request->getVar('g-recaptcha-response'));
$userIp = $this->request->getIPAddress();
$secret = env('recaptcha2_secretkey');
$credential = [
'secret' => $secret,
'response' => $recaptchaResponse,
'remoteip' => $userIp,
];
$verify = curl_init();
curl_setopt($verify, CURLOPT_URL, 'https://www.google.com/recaptcha/api/siteverify');
curl_setopt($verify, CURLOPT_POST, TRUE);
curl_setopt($verify, CURLOPT_POSTFIELDS, http_build_query($credential));
curl_setopt($verify, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($verify, CURLOPT_RETURNTRANSFER, TRUE);
$response = curl_exec($verify);
$status = json_decode($response, TRUE);
curl_close($verify);
if (empty($validation->getErrors()) && $status['success']) {
$login = $this->request->getPost();
$user = $this->userModel->getUser($login['username']);
// Storing session values
$this->setUserSession($user);
// Storing success message
$session->setFlashdata('success', 'You have successfully logged in!');
// Update last login datetime
$this->userModel->lastLogin($login['username']);
// Redirecting to dashboard after login
if ($user['role'] == 1) {
return redirect()->to('admin/dashboard');
} elseif ($user['role'] == 0) {
return redirect()->to('members/dashboard');
}
} else {
$data = [
'title' => 'Login',
'errors' => $validation->getErrors(),
];
echo view('templates/index_header', $data);
echo view('users/login');
echo view('templates/footer', $data);
}
} else {
$data = [
'title' => 'Login',
];
echo view('templates/index_header', $data);
echo view('users/login');
echo view('templates/footer', $data);
}
}
/**
* Sets session with user id, username, isLoggedIn, and role for use in member/admin site
* #param model user data
* #return boole if session was set successfully
*/
private function setUserSession($user)
{
$data = [
'id' => $user->id,
'username' => $user->username,
'profile_image' => $user->profile_image,
'isLoggedIn' => true,
'role' => $user->role,
];
if (session()->set($data)) {
return true;
} else {
return false;
}
}
public function register()
{
$validation = \Config\Services::validation();
if ($this->request->getMethod() == 'post' && ! empty($_POST)) {
$validation->getRuleGroup('registration');
$validation->setRuleGroup('registration');
$validation->withRequest($this->request)->run();
if (empty($validation->getErrors())) {
$newUser = $this->request->getPost();
if ($this->userModel->addUser($newUser)) {
$this->session->setFlashdata('success', 'Successful Registration');
$data['title'] = 'Login';
echo view('templates/index_header', $data);
echo view('users/login');
echo view('templates/footer', $data);
} else {
$this->session->setFlashdata('error', 'Something went wrong with your registration! Please try again.');
}
} else {
$data = [];
$data = [
'title' => 'Register',
'script' => 'js/click_link',
'errors' => $validation->getErrors(),
];
echo view('templates/index_header', $data);
echo view('users/register', $data);
echo view('templates/footer', $data);
}
} else {
$data = [
'title' => 'Register',
'script' => 'js/click_link',
];
echo view('templates/index_header', $data);
echo view('users/register', $data);
echo view('templates/footer', $data);
}
}
}
These are my validation rules in Config\Validation:
/**
* Registration
*/
public $registration = [
'username' => 'required|is_unique[users.username,username]|min_length[5]|max_length[25]|alpha_dash|badWordsFilter[username]',
'password' => 'required|min_length[8]|max_length[255]|regex_match[/^(?=.*[!##$%^&*-])(?=.*[0-9])(?=.*[A-Z]).{8,255}$/]',
'pass_confirm' => 'required|matches[password]',
'about_me' => 'permit_empty|max_length[250]|alpha_numeric_punct|badWordsFilter[about_me]',
'occupation' => 'permit_empty|max_length[50]|alpha_space|badWordsFilter[occupation]',
'hometown' => 'permit_empty|max_length[50]|alpha_space|badWordsFilter[hometown]',
'age' => 'permit_empty|less_than[100]|greater_than[0]|numeric',
'country' => 'permit_empty',
];
/**
* Password Verification
*/
public $login = [
'password' => 'required|validateUser[username,password]',
];
This is my custom rule to authenticate username and password credentials User_rules:
class User_rules
{
/**
* Checks if input username exists in database and then checks whether the input password matches the hash for that username
* #param string $str is the input password
* #param string $fields are the associated form fields that are being used
* #param array $data is an array containing the values for the fields indexed by field names
* #return boolean true or false depending on if the user exists and the password matches the hashed password stored in the database
*/
public function validateUser(string $str, string $fields, array $data)
{
$userModel = new UserModel();
$user = $userModel->getUser($data['username']);
if(!$user) {
return FALSE;
}
return password_verify($data['password'], $user->password);
}
Lastly, my view for login.php:
<div class='form-container'>
<?= form_open('users/login',['autocomplete' => FALSE]); ?>
<div class='form-header'>
<h2>Login</h2>
</div>
<div class='form-body'>
<div class='form-row'>
<div class='input-container'>
<i class='fas fa-user'></i>
<?php $attributes = [
'type' => 'text',
'name' => 'username',
'class' => 'input-field',
'id' => 'username',
'placeholder' => 'Username',
'required' => TRUE,
]; ?>
<?= form_input($attributes); ?>
</div>
</div>
<div class='form-row'>
<div class='input-container'>
<i class='fas fa-lock'></i>
<?php $attributes = [
'type' => 'password',
'name' => 'password',
'class' => 'input-field',
'placeholder' => 'Password',
'required' => TRUE,
]; ?>
<?= form_input($attributes); ?>
</div`>
</div>
</div>
<div class='captcha-container'>
<div class='g-recaptcha' data-sitekey='<?= env('recaptcha2_sitekey'); ?>'></div>
</div>
<div class='form-footer'>
<?php $submit = [
'name' => 'loginSubmit',
'value' => 'Login',
'class' => 'submit-btn',
];?>
<?= form_submit($submit); ?>
</div>
<h4 style='text-align: center'>Not a member yet? Register
<a href= <?= site_url('users/register'); ?> title = 'Register'> HERE</a>
</h4>
<?= form_close(); ?>
</div>
It was a stupid mistake. Someone on the codeigniter forum answered my question here: CodeIgniter Forum
basically in my constructor I needed $this->userModel = new UserModel(); instead of $userModel = new UserModel();.
Hi guys I have created a stipe payment method a it works fine but I want to upgrade to the stripe v3 but I don't how tp convert my old method to the new one I look at the doc be they are not so clear here is my old code
I am using Altorouter and illuminate for database and blade template engine Vue for front end and axious for HTTP
My route
$router->map('POST', '/cart/payment', 'App\Controllers\CartController#Payment', 'Payment');
my cart.blade.php file
<button #click.prevent='checkout' class="btn btn-outline-success">Checkout - <i class="fa fa-credit-card"
aria-hidden="true"></i></button>
<span id="proporities" class="hide" data-customer-email='{{ user()->email
data-stripe-api='{{ \App\Classes\Session::get('stripe_key') }}'>
</span>
after this button get click I take the information to cart.js here is the cart.js code
var stripe = StripeCheckout.configure({
key: $('#proporities').data('stripe-api'),
locale: 'auto',
token: function(token) {
var data = $.param({
stripeToken: token.id,
stripeEmail: token.email
});
axios.post('/cart/payment', data).then(function(response) {
$('.notifty').css('display', 'block').delay(2000).slideUp(300).html(response.data.success);
app.showcart(10);
}).catch(function(error) {
console.log(error)
});
}
});
var app = new Vue({
el: '#cart',
data: {
amount: 0
},
methods: {
checkout: function() {
stripe.open({
name: 'Cartzilla Store Inc.',
description: 'Your Shopping Cart',
zipCode: true,
amount: app.amount,
email: $('#proporities').data('customer-email')
});
}
in the cart.js axious take to information to PHP controller which I have defined in the top route
here is the controller code
public function payment()
{
$array = [];
if (Request::has('post')) {
$request = Request::get('post');
$email = $request->stripeEmail;
$token = $request->stripeToken;
try {
$customer = Customer::create([
'email' => $email,
'source' => $token
]);
$amount = convertMoney(Session::get('carttotal'));
$charge = Charge::create([
'customer' => $customer->id,
'amount' => $amount,
'description' => user()->fullname . 'Cart Purchase',
'currency' => 'usd'
]);
$orderId = strtoupper(uniqid());
foreach ($_SESSION['user_cart'] as $items) {
$productId = $items['product_id'];
$quantity = $items['quantity'];
$product = Product::where('id', $productId)->first();
if (!$product) {
continue;
}
$totalPrice = $product->price * $quantity;
$totalPrice = number_format($totalPrice, 2);
Order::create([
'user_id' => user()->id,
'product_id' => $productId,
'unit_price' => $product->price,
'status' => 'pending',
'total_price' => $amount,
'order_no' => $orderId,
'quantity' => $quantity
]);
$product->quantity = $product->quantity - $quantity;
$product->save();
array_push($array, [
'name' => $product->name,
'price' => $product->price,
'quantity' => $quantity,
'total' => $totalPrice,
]);
Payment::create([
'user_id' => user()->id,
'amount' => $charge->amount,
'status' => $charge->status,
'order_no' => $orderId
]);
}
Session::remove('user_cart');
echo json_encode(['success' => 'Thank You we Have Recevied Your Payment And Now Processing Your Order']);
} catch (\Throwable $th) {
//throw $th;
}
}
}
apart from this, I have to model to for order table and payment table
and here are the model
Order.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Order extends Model
{
use SoftDeletes;
public $timestamp = true;
public $fillable = ['user_id', 'product_id', 'unit_price', 'qunatity', 'total_price', 'status', 'order_no'];
public $date = ['deleted_at'];
}
and Payment .php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Payment extends Model
{
use SoftDeletes;
public $timestamp = true;
public $fillable = ['user_id', 'amount', 'status', 'order_no'];
public $date = ['deleted_at'];
}
I'm using Sonata for Admin and I have an error with all my Pickers. In the vendor directory, CRUDController, which is in Sonata-project/admin-bundle calls:
$form = $this->admin->getForm();
FormRegistry is called after that, which is in Symfony\Component\Form:
/**
* {#inheritdoc}
*/
public function getType($name)
{
if (!isset($this->types[$name])) {
$type = null;
foreach ($this->extensions as $extension) {
if ($extension->hasType($name)) {
$type = $extension->getType($name);
break;
}
}
if (!$type) {
// Support fully-qualified class names
if (!class_exists($name)) {
throw new InvalidArgumentException(sprintf('Could not load type "%s": class does not exist.', $name));
}
if (!is_subclass_of($name, 'Symfony\Component\Form\FormTypeInterface')) {
throw new InvalidArgumentException(sprintf('Could not load type "%s": class does not implement "Symfony\Component\Form\FormTypeInterface".', $name));
}
**$type = new $name();**
}
$this->types[$name] = $this->resolveType($type);
}
return $this->types[$name];
}
And the error is in bold. It calls the BasePickerType in Sonata\Form\Type:
<?php
declare(strict_types=1);
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix#sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Form\Type;
use Sonata\Form\Date\MomentFormatConverter;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Class BasePickerType (to factorize DatePickerType and DateTimePickerType code.
*
* #author Hugo Briand <briand#ekino.com>
*/
abstract class BasePickerType extends AbstractType
{
/**
* #var TranslatorInterface|null
*/
protected $translator;
/**
* #var string
*/
protected $locale;
/**
* #var MomentFormatConverter
*/
private $formatConverter;
public function __construct(MomentFormatConverter $formatConverter, TranslatorInterface $translator, RequestStack $requestStack)
{
$this->formatConverter = $formatConverter;
$this->translator = $translator;
$this->locale = $this->getLocale($requestStack);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setNormalizer('format', function (Options $options, $format) {
if (isset($options['date_format']) && \is_string($options['date_format'])) {
return $options['date_format'];
}
if (\is_int($format)) {
$timeFormat = \IntlDateFormatter::NONE;
if ($options['dp_pick_time']) {
$timeFormat = $options['dp_use_seconds'] ?
DateTimeType::DEFAULT_TIME_FORMAT :
\IntlDateFormatter::SHORT;
}
$intlDateFormatter = new \IntlDateFormatter(
$this->locale,
$format,
$timeFormat,
null,
\IntlDateFormatter::GREGORIAN
);
return $intlDateFormatter->getPattern();
}
return $format;
});
}
public function finishView(FormView $view, FormInterface $form, array $options): void
{
$format = $options['format'];
// use seconds if it's allowed in format
$options['dp_use_seconds'] = false !== strpos($format, 's');
if ($options['dp_min_date'] instanceof \DateTime) {
$options['dp_min_date'] = $this->formatObject($options['dp_min_date'], $format);
}
if ($options['dp_max_date'] instanceof \DateTime) {
$options['dp_max_date'] = $this->formatObject($options['dp_max_date'], $format);
}
$view->vars['moment_format'] = $this->formatConverter->convert($format);
$view->vars['type'] = 'text';
$dpOptions = [];
foreach ($options as $key => $value) {
if (false !== strpos($key, 'dp_')) {
// We remove 'dp_' and camelize the options names
$dpKey = substr($key, 3);
$dpKey = preg_replace_callback('/_([a-z])/', static function ($c) {
return strtoupper($c[1]);
}, $dpKey);
$dpOptions[$dpKey] = $value;
}
}
$view->vars['datepicker_use_button'] = empty($options['datepicker_use_button']) ? false : true;
$view->vars['dp_options'] = $dpOptions;
}
/**
* Gets base default options for the date pickers.
*/
protected function getCommonDefaults(): array
{
return [
'widget' => 'single_text',
'datepicker_use_button' => true,
'dp_pick_time' => true,
'dp_pick_date' => true,
'dp_use_current' => true,
'dp_min_date' => '1/1/1900',
'dp_max_date' => null,
'dp_show_today' => true,
'dp_language' => $this->locale,
'dp_default_date' => '',
'dp_disabled_dates' => [],
'dp_enabled_dates' => [],
'dp_icons' => [
'time' => 'fa fa-clock-o',
'date' => 'fa fa-calendar',
'up' => 'fa fa-chevron-up',
'down' => 'fa fa-chevron-down',
],
'dp_use_strict' => false,
'dp_side_by_side' => false,
'dp_days_of_week_disabled' => [],
'dp_collapse' => true,
'dp_calendar_weeks' => false,
'dp_view_mode' => 'days',
'dp_min_view_mode' => 'days',
];
}
private function getLocale(RequestStack $requestStack): string
{
if (!$request = $requestStack->getCurrentRequest()) {
throw new \LogicException('A Request must be available.');
}
return $request->getLocale();
}
private function formatObject(\DateTime $dateTime, $format): string
{
$formatter = new \IntlDateFormatter($this->locale, \IntlDateFormatter::NONE, \IntlDateFormatter::NONE);
$formatter->setPattern($format);
return $formatter->format($dateTime);
}
}
I have no idea how to resolve that. Any idea? Thanks
Could you please try to enable SonataFormBundle?
// config/bundles.php
return [
// ...
Sonata\Form\Bridge\Symfony\SonataFormBundle::class => ['all' => true],
];
I've tried to make form input with autonumber using extensions mdmsoft/yii2-autonumber. And the result in my browser this bellow.
And this my code form in view.
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
/* #var $this yii\web\View */
/* #var $model app\models\Donatur */
/* #var $form yii\widgets\ActiveForm */
?>
<div class="donatur-form">
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'kode_donatur')->textInput(['readonly' => true, 'value' => 'kode_donatur']) ?>
<?= $form->field($model, 'nama_donatur')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'alamat')->textArea(['rows' => 6]) ?>
<?= $form->field($model, 'telepon')->textInput(['maxlength' => true]) ?>
<div class="form-group">
<?= Html::submitButton($model->isNewRecord ? 'Simpan' : 'Ubah', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
<?php
echo " ";
echo " ";
echo Html::a('Keluar', ['index'],['class'=>'btn btn-primary']);
?>
</div>
<?php ActiveForm::end(); ?>
</div>
And I want autonumber display in textfield "Kode donatur". An example can be seen in the image below.
Code in controller
<?php
namespace app\controllers;
use Yii;
use app\models\Donatur;
use app\models\SearchDonatur;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
/**
* DonaturController implements the CRUD actions for Donatur model.
*/
class DonaturController extends Controller
{
/**
* #inheritdoc
*/
public function behaviors()
{
return [
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'delete' => ['POST'],
],
],
];
}
/**
* Lists all Donatur models.
* #return mixed
*/
public function actionIndex()
{
$searchModel = new SearchDonatur();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
/**
* Displays a single Donatur model.
* #param string $id
* #return mixed
*/
public function actionView($id)
{
return $this->render('view', [
'model' => $this->findModel($id),
]);
}
/**
* Creates a new Donatur model.
* If creation is successful, the browser will be redirected to the 'view' page.
* #return mixed
*/
public function actionCreate()
{
$model = new Donatur();
if (Yii::$app->request->post()) {
$model->load(Yii::$app->request->post());
if ($model->save()) {
\Yii::$app->session->setFlash('success', 'Data berhasil disimpan!');
} else {
Yii::$app->session->setFlash('error', 'Data gagal disimpan!');
}
return $this->redirect(['index']);
return $this->refresh();
}
else {
return $this->render('create', ['model' => $model]);
}
}
/**
* Updates an existing Donatur model.
* If update is successful, the browser will be redirected to the 'view' page.
* #param string $id
* #return mixed
*/
public function actionUpdate($id)
{
$model = Donatur::findOne($id);
if (Yii::$app->request->post()) {
$model->load(Yii::$app->request->post());
if ($model->save()) {
Yii::$app->session->setFlash('success', 'Data berhasil diupdate!');
} else {
Yii::$app->session->setFlash('error', 'Data gagal diupdate!');
}
return $this->redirect(['index']);
return $this->refresh();
}
else {
return $this->render('update', ['model' => $model]);
}
}
/**
* Deletes an existing Donatur model.
* If deletion is successful, the browser will be redirected to the 'index' page.
* #param string $id
* #return mixed
*/
public function actionDelete($kode_donatur)
{
$model = Donatur::findOne($kode_donatur);
$model->delete();
Yii::$app->session->setFlash('delete', 'Data berhasil dihapus!');
return $this->redirect(['index']);
}
/**
* Finds the Donatur model based on its primary key value.
* If the model is not found, a 404 HTTP exception will be thrown.
* #param string $id
* #return Donatur the loaded model
* #throws NotFoundHttpException if the model cannot be found
*/
protected function findModel($id)
{
if (($model = Donatur::findOne($id)) !== null) {
return $model;
} else {
throw new NotFoundHttpException('The requested page does not exist.');
}
}
}
Code in model
<?php
namespace app\models;
use yii\db\ActiveRecord;
class Donatur extends ActiveRecord
{
public static function tableName()
{
return 'donatur';
}
public function rules()
{
return [
[['nama_donatur', 'alamat', 'telepon'], 'required'],
[['kode_donatur', 'nama_donatur', 'alamat'], 'string'],
[['telepon'], 'integer'],
];
}
public function behaviors()
{
return [
[
'class' => 'mdm\autonumber\Behavior',
'attribute' => 'kode_donatur', // required
//'group' => $this->id_branch, // optional
'value' => 'D'.'?' , // format auto number. '?' will be replaced with generated number
'digit' => 4 // optional, default to null.
],
];
}
}
Your behaviours() function is not in the model it should be in your controller.Like this,
public function behaviors()
{
return [
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'delete' => ['POST'],
],
],
[
'class' => 'mdm\autonumber\Behavior',
'attribute' => 'kode_donatur', // required
//'group' => $this->id_branch, // optional
'value' => 'D'.'?' , // format auto number. '?' will be replaced with generated number
'digit' => 4 // optional, default to null.
],
];
}
I'm having hard time with a weird behaviour of fileinput.
This is my form:
namespace Frontend\Form;
use NW\Form\Form;
use Zend\InputFilter;
use Zend\Form\Element;
use Zend\ServiceManager\ServiceManager;
use Zend\ServiceManager\ServiceManagerAwareInterface;
class EnrollStructure extends Form implements ServiceManagerAwareInterface
{
protected $sm;
public function __construct($name=null) {
parent::__construct("frmEnrollStructure");
$this->setAttribute("action", "/registrazione_struttura/submit")
->setAttribute('method', 'post')
->setAttribute("id", "iscrizione_struttura")
->setAttribute("class", "form fullpage");
$this->addInputFilter();
}
public function init()
{
$structureFs = $this->sm->get('Structure\Form\Fieldsets\Structure');
$structureFs->setUseAsBaseFieldset(true);
$structureFs->remove("id")
->remove("creationTime")
->remove("latLon");
$file = new Element\File("images");
$file->setAttribute('multiple', true);
$this->add($structureFs)->add($file);
$this->add(array(
'name' => 'submit',
'attributes' => array(
'type' => 'submit',
'value' => 'Iscriviti',
'id' => 'sbmtEnrollStructure',
'class' => 'submit_btn'
),
));
$this->setValidationGroup(
array(
'structure' =>
array(
'companyname',
'vatNumber',
'addressStreet',
'addressZip',
'addressCity',
'addressRegion',
'fax',
'publicPhone',
'publicEmail',
'website',
'status',
'ownerNotes',
'category',
'subcategory',
"facilities",
"agreeOnPolicy",
"agreeOnPrivacy",
"subscribeNewsletter",
"contact" => array("name", "surname", "email", "role", "phone"),
),
"images"
));
}
/**
* Set service manager
*
* #param ServiceManager $serviceManager
*/
public function setServiceManager(ServiceManager $serviceManager)
{
$this->sm = $serviceManager;
}
public function addInputFilter()
{
$inputFilter = new InputFilter\InputFilter();
// File Input
$fileInput = new InputFilter\FileInput('images');
$fileInput->setRequired(true);
$fileInput->getValidatorChain()
->attachByName('filesize', array('max' => "2MB"))
->attachByName('filemimetype', array('mimeType' => 'image/png,image/x-png,image/jpg,image/jpeg'))
->attachByName('fileimagesize', array('maxWidth' => 2048, 'maxHeight' => 2048));
$inputFilter->add($fileInput);
$this->setInputFilter($inputFilter);
}
}
Basically, I mainly use a fieldset which contains most of the data I request to the user, plus a File input field.
This is the Fieldset Structure: (most important parts..)
use Zend\Form\Element;
use Zend\Form\Fieldset;
use Zend\InputFilter\InputFilterProviderInterface;
use Zend\ServiceManager\ServiceManager;
use Zend\ServiceManager\ServiceManagerAwareInterface;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject as DoctrineHydrator;
use Zend\Validator\Identical;
use Zend\Validator\NotEmpty;
use Zend\Validator\Regex;
use Zend\Validator\StringLength;
class Structure extends Fieldset implements InputFilterProviderInterface, ServiceManagerAwareInterface
{
protected $sm;
public function __construct()
{
parent::__construct('structure');
}
public function init()
{
$this->setHydrator(new DoctrineHydrator($this->_entityManager(),'Structure\Entity\Structure'));
$this->setObject($this->sm->getServiceLocator()->get("Structure_Structure"));
$id = new Element\Hidden("id");
$name = new Element\Text("companyname");
$name->setLabel("Ragione Sociale");
...........
}
public function getInputFilterSpecification()
{
return array
(
"id" => array(
"required" => false,
),
"companyname" => array(
"required" => true,
"validators" => array(
array('name' => "NotEmpty", 'options' => array("messages" => array( NotEmpty::IS_EMPTY => "Inserire la ragione sociale")))
),
),
.....
}
}
This is my controller:
public function submitAction()
{
try {
$this->layout("layout/json");
$form = $this->getForm('Frontend\Form\EnrollStructure');
//$form->addInputFilter();
$structure = $this->getServiceLocator()->get("Structure_Structure");
$viewModel = new ViewModel();
$request = $this->getRequest();
if ($request->isPost())
{
$post = array_merge_recursive
(
$request->getPost()->toArray(),
$request->getFiles()->toArray()
);
$form->setData($post);
if ($form->isValid())
{
$structure = $form->getObject();
$contact = $structure->getContact();
$this->getServiceLocator()->get('Structure_ContactService')->save($contact);
$files = $request->getFiles()->toArray();
if(isset($files['images']))
{
$count = 3;
foreach($files['images'] as $pos => $file)
{
$fpath = $this->getServiceLocator()->get('RdnUpload\Container')->upload($file);
if(!empty($fpath))
{
if(--$count ==0) break;
$asset = $this->getServiceLocator()->get("Application_AssetService")->fromDisk($fpath, $file['name']);
$this->getServiceLocator()->get("Application_AssetService")->save($asset);
$structure->addImage($asset);
}
}
}
$this->getServiceLocator()->get('Structure_StructureService')->save($structure);
$retCode = RetCode::success(array("iscrizione_struttura!" => array("form_submit_successfull")), true);
}
else
{
$messages = $form->getMessages();
if(empty($messages))
$retCode = RetCode::error(array("iscrizione_struttura" => array("need_at_least_one_file" => "missing file")), true);
else
$retCode = RetCode::error(array("iscrizione_struttura" => $messages), true);
}
$viewModel->setVariable("retcode", $retCode);
return $viewModel;
}
} catch(Exception $e)
{
throw $e;
}
}
The strange thing is that if i remove from the field "images" the "multiple" attribute everything works fine, causing the form not to validate and i get this message:
[images] => Array
(
[fileUploadFileErrorFileNotFound] => File was not found
)
While, if i set the attribute multiple, and the user does not upload a file i get no error, but the form gets invalidated (this is the reason for this "bad" code in my controller:)
$messages = $form->getMessages();
if(empty($messages))
$retCode = RetCode::error(array("iscrizione_struttura" => array("need_at_least_one_file" => "missing file")), true);
else
$retCode = RetCode::error(array("iscrizione_struttura" => $messages), true);
I found the problem was caused by the Jquery form plugin, without it it works fine. :( In case somebody needs, I think the correct action code can be found here (I haven't tryied it anyway)
https://github.com/cgmartin/ZF2FileUploadExamples/blob/master/src/ZF2FileUploadExamples/Controller/ProgressExamples.php