Override CustomerFormatter.php for birthday form - prestashop-1.7

I would like to put the birthday field mandatory, so I override the CustomerFormatter but it does not work at all. That's how I did:
<?php
class CustomerFormatter extends CustomerFormatterCore
{
public function getFormat()
{
$customerForm = parent::getFormat();
if ($customerForm->ask_for_birthdate) {
$format['birthday'] = (new FormField)
->setName('birthday')
->setType('text')
->setLabel(
$customerForm->translator->trans(
'Birthdate', [], 'Shop.Forms.Labels'
)
)
->addAvailableValue('placeholder', Tools::getDateFormat())
->addAvailableValue(
'comment',
$customerForm->translator->trans('(E.g.: %date_format%)', array('%date_format%' => Tools::formatDateStr('31 May 1970')), 'Shop.Forms.Help')
)
->setRequired(true)
;
}
}
}
I am under Prestashop 1.7.3.3
Do you have an idea of ​​the problem ? Thank you for your help

Probably to late for you but it might help others, this is unfortunately impossible with 1.7.
"Yes, overrides work as usual on all classes that have no namespace (so you can still override Product, Address, etc.)."
http://build.prestashop.com/news/prestashop-1-7-faq/#can-developers-still-use-overrides-in-17

You need to go to public_html/override/classes/form/ and create the file CustomerFormatter.php then type this code:
<?php
/**
* #Override CustomerFormatter
*/
use Symfony\Component\Translation\TranslatorInterface;
class CustomerFormatter extends CustomerFormatterCore
{
private $translator;
private $language;
private $ask_for_birthdate = true;
private $ask_for_password = true;
private $password_is_required = true;
private $ask_for_new_password = false;
public function __construct(
TranslatorInterface $translator,
Language $language
) {
parent::__construct($translator, $language);
$this->translator = $translator;
$this->language = $language;
}
public function getFormat()
{
$format = parent::getFormat();
$format = [];
if ($this->ask_for_birthdate) {
$format['birthday'] = (new FormField())
->setName('birthday')
->setType('birthday')
->setLabel(
$this->translator->trans(
'Birthdate',
[],
'Shop.Forms.Labels'
)
)
->setRequired($this->password_is_required);
}
}
You need to use:
->setRequired($this->password_is_required)
Because there's an error on checkout for guests purchases. So like this, it would be an optional field.

Related

Yii2 Activerecord not saved before redirect and shown in "view"

Yii2 framework. When I save multiple ActiveRecords in AFTER_INSERT_EVENT of another ActiveRecord, the values in the database is not updated fast enough, so old values are shown when redirect to viewing the data.
To be more specific: Standard XAMPP environment with PHP 7.2.9. I have made a trait to make it easy to have extra attributes with history in model (either existing attributes or new attributes). The trait is used on ActiveRecord.
Notice the sleep(5) in function TL_save. This handled the problem, but it is not the correct solution. How do I ensure all is updated before it is read again? I want to avoid use locks on the row as that would require alteration of a table before it can be used. Is there a way around it? Transactions - I have tried it but perhaps not correct as it had no effect. A reload of the view page also solves the problem, but again: not very classy :-)
Also: Should I share this code on GitHub? I have not done so before and are not quite sure if it would be of any value to others really.
trait TimelineTrait
{
private $timelineConfig;
public function timelineInit($config)
{
$std = [
'attributes' => [], // required
'_oldAttributes'=>[],
'datetime'=> date('Y-m-d H:i:s'),
'validationRule'=>'safe',
'table'=>$this->tableName(),
'onlyDirty'=>true, // using !=, not !==
'events'=>[
self::EVENT_AFTER_INSERT=>[$this, 'TL_EventAfterInsert'],
self::EVENT_AFTER_UPDATE=>[$this, 'TL_EventAfterUpdate'],
self::EVENT_AFTER_FIND=>[$this, 'TL_EventAfterFind'],
self::EVENT_AFTER_DELETE=>[$this, 'TL_EventAfterDelete'],
],
'TimelineClass'=>Timeline::class,
/*
Must have the following attributes
id integer primary key auto increment not null,
table varchar(64) not null,
table_id integer not null,
attribute varchar(64) not null,
datetime datetime not null
value text (can be null)
*/
];
$this->timelineConfig = array_replace_recursive($std, $config);
foreach($this->timelineConfig["events"]??[] as $trigger=>$handler)
$this->on($trigger, $handler);
}
public function __get($attr)
{
$cfg = &$this->timelineConfig;
if (in_array($attr, array_keys($cfg["attributes"])))
return $cfg["attributes"][$attr];
else
return parent::__get($attr);
}
public function __set($attr, $val)
{
$cfg = &$this->timelineConfig;
if (in_array($attr, array_keys($cfg["attributes"]))) {
$cfg["attributes"][$attr] = $val;
} else
parent::__set($attr, $val);
}
public function attributes()
{
return array_merge(parent::attributes(), $this->timelineConfig["attributes"]);
}
public function rules()
{
$temp = parent::rules();
$temp[] = [array_keys($this->timelineConfig["attributes"]), $this->timelineConfig["validationRule"]];
return $temp;
}
public function TL_EventAfterInsert($event)
{
$this->TL_save($event, true);
}
public function TL_EventAfterUpdate($event)
{
$this->TL_save($event, false);
}
private function TL_save($event, $insert)
{
$cfg = &$this->timelineConfig;
if ($cfg["onlyDirty"])
$cfg["_oldAttributes"] = $this->TL_attributesOnTime();
foreach($cfg["attributes"] as $attr=>$val) {
$a = [
'table'=>$cfg["table"],
'table_id'=>$this->id,
'attribute'=>$attr,
'datetime'=>$cfg["datetime"],
];
if ($insert)
$model=null;
else
$model = Timeline::find()->where($a)->one();
$isNew = empty($model); // this exact attribute does not exist on timeline already
if ($isNew)
$model = new $cfg["TimelineClass"]($a);
$model->value = $val;
if (!$cfg["onlyDirty"]
|| $cfg["onlyDirty"] && $model->value!=($cfg["_oldAttributes"][$attr]??\uniqid('force_true'))) {
$ok = $model->save();
if (!$ok) $this->addErrors($attr, $model->getErrorSummary());
}
}
sleep(5);
}
public function TL_EventAfterFind($event)
{
$cfg = &$this->timelineConfig;
$data = $this->TL_attributesOnTime();
foreach($data as $attr=>$val)
$cfg["attributes"][$attr] = $val;
$cfg["_oldAttributes"] = $cfg["attributes"];
}
private function TL_attributesOnTime()
{
$cfg = &$this->timelineConfig;
$timelineTable = $cfg["TimelineClass"]::tableName();
$sql = "SELECT t1.* FROM $timelineTable AS t1
LEFT JOIN (SELECT * FROM $timelineTable WHERE `table`=:table AND table_id=:table_id AND datetime<=:datetime) AS t2
ON (t1.table=t2.table and t1.table_id=t2.table_id and t1.datetime<t2.datetime AND t1.attribute=t2.attribute)
WHERE t2.id IS NULL AND t1.datetime<:datetime AND t1.table=:table AND t1.table_id=:table_id
";
$params = [
'table'=>$cfg["table"],
'table_id'=>$this->id,
':datetime'=>$cfg["datetime"],
];
$data = \Yii::$app->db->createCommand($sql,$params)->queryAll();
$data = ArrayHelper::map($data,'attribute','value');
return $data;
}
public function TL_EventAFterDelete($event)
{
$cfg = &$this->timelineConfig;
$cfg["TimelineClass"]::deleteAll([
'table'=>$cfg["table"],
'table_id'=>$event->sender->id
]);
}
}
Example of it's use:
<?php
namespace app\models;
class KeyTime extends Key
{
use \app\behaviors\TimelineTrait;
public function init()
{
parent::init();
$this->timelineInit([
'attributes'=>[
// default values for attributes
'keyid'=>'historic id', // this is existing attribute in Key model
'label'=>'mylabel', // label and color does not exist in Key model
'color'=>'red',
],
]);
}
}
The actionUpdate
public function actionUpdate($id)
{
$model = $this->findModel($id);
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
}
return $this->render('update', [
'model' => $model,
]);
}
After many "flashes" with microtime(true) on, I found the reason it worked sometimes with sleep(1).
The answer is in TL_attributesOnTime. the last line in $sql was
WHERE t2.id IS NULL AND t1.datetime<:datetime AND t1.table=:table AND t1.table_id=:table_id
…but it should be…
WHERE t2.id IS NULL AND t1.datetime<=:datetime AND t1.table=:table AND t1.table_id=:table_id
Notice the < is changed to <= Otherwise when the record was saved in the same second as it was populated it would not be included.
Hope it can help somebody else.

Yii2 - dynamically switch rules set in model

I want to dynamically substitute rules in model according to switch value on form.
In view:
<?php
$form = ActiveForm::begin([
'enableAjaxValidation' => true,
'validationUrl' => Url::toRoute('anounce/validation')
]);
?>
In controller:
public function actionValidation()
{
$model = new Anounce();
if (Yii::$app->request->isAjax && $model->load(Yii::$app->
request->post())) {
Yii::$app->response->format = 'json';
return ActiveForm::validate($model);
}
}
Excerpts from model:
class Anounce extends \yii\db\ActiveRecord
{
private $currentRuleSet; // Current validation set
// Here are arrays of rules with assignment
private $fullRuleSet; // = [...];
private $shortRuleSet; // = [...];
private $minRuleSet; // = [...];
public function init()
{
parent::init();
$this->currentRuleSet = $this->fullRuleSet;
}
public function rules()
{
return $this->currentRuleSet;
}
public function beforeValidate()
{
if ($this->idanounce_type === self::FULL) {
$this->currentRuleSet = $this->fullRuleSet;
} else if ($this->idanounce_type === self::SHORTER) {
$this->currentRuleSet = $this->shortRuleSet;
} else if ($this->idanounce_type === self::MINIMAL) {
$this->currentRuleSet = $this->minRuleSet;
}
return parent::beforeValidate();
}
}
Variable idanounce_type is a switch between rules.
Unfortunately, validation made according to full rules set (or rules set used in init), despite on which *RuleSet value assigned to currentRuleSet.
How to write dynamic switching of rules?
What you want here is to change validation according to the user's input. You can do this by defining scenarios in your model.
So firstly set scenarios where you put in it the fields that are to be validated. Example if you have username, password, and email fields, and you defined two scenarios, in SCENARIO_FIRST only username and password will get validated.
public function scenarios()
{
return [
self::SCENARIO_FIRST => ['username', 'password'],
self::SCENARIO_SECOND => ['username', 'email', 'password'],
];
}
Then in your controller, set the scenario according to the input:
public function actionValidation()
{
$model = new Anounce();
//example
if($condition == true)
{
$model->scenario = Anounce::SCENARIO_FIRST;
}
if (Yii::$app->request->isAjax && $model->load(Yii::$app->
request->post())) {
Yii::$app->response->format = 'json';
return ActiveForm::validate($model);
}
}
Read more about scenarios here and how to use them with validation here:
http://www.yiiframework.com/doc-2.0/guide-structure-models.html#scenarios

Ignited Datatables Where Not In

I using Ignited Datatables library but it dose not support WhereNotIn functionality, how can I add this feature to this library? Thanks in advance.
Patch the Datatables class like this:
class Datatables
{
//...
private $where_not_in = array();
//...
public function where_not_in($key_condition, $val = NULL, $backtick_protect = TRUE)
{
$this->where_not_in[] = array($key_condition, $val, $backtick_protect);
$this->ci->db->where_not_in($key_condition, $val, $backtick_protect);
return $this;
}
private function get_total_results($filtering = FALSE)
{
//...
foreach($this->where_not_in as $val)
$this->ci->db->where_not_in($val[0], $val[1], $val[2]);
//...
}
}

Laravel , suddenly query error

I used a code in my Basecontroller to share data with all the views ( for footer and header informations ) , it worked great but suddenly i was working on something totally different and i've got an error : No query results for model [Variable]
Can't understand why i didn't even modify my BaseController.
Here is my Base Controller :
public function __construct(){
$this->getVariable('horaire');
$this->getVariable('facebook');
$this->getVariable('contact');
$this->getVariable('twitter');
$main_slider = MainSlider::all();
View::share('main_slider',$main_slider);
}
public function getVariable($setting)
{
$variables[$setting] = Variable::where('name', $setting)->FirstOrFail();
$values[$setting] = $variables[$setting]->value;
View::share($setting, $values[$setting]);
}
And what I was working on
Class VariablesController extends BaseController {
public function settings(){
$settings['about'] = Variable::where('name','about')->FirstOrFail();
$settings['contact'] = Variable::where('name','contact')->FirstOrFail();
$settings['horaire'] = Variable::where('name','horaire')->FirstOrFail();
$settings['legals'] = Variable::where('name','legals')->FirstOrFail();
$settings['facebook'] = Variable::where('name','facebook')->FirstOrFail();
$settings['twitter'] = Variable::where('name','twitter')->FirstOrFail();
$settings['contact_email'] = Variable::where('name','contact_email')->FirstOrFail();
return View::make('admin.settings',compact('settings'));
}
public function update(){
$inputs['facebook'] = e(Input::get('facebook'));
$inputs['twitter'] = e(Input::get('twitter'));
$inputs['contact_email'] = e(Input::get('contact_email'));
$inputs['legals'] = e(Input::get('legals'));
$inputs['horaire'] = e(Input::get('horaire'));
$inputs['contact'] = e(Input::get('contact'));
$inputs['about'] = e(Input::get('about'));
foreach($inputs as $name => $input){
echo "$name => $input";
}
}
And my Variable model :
<?php
class Variable extends \Eloquent {
protected $guarded = ['id','created_at','protected_at'];
protected $table = 'variables';
}
Thank you for your helps

How to cache model attributes in Laravel

In my current configuration, a user's email is stored on a remote server that I need to hit with a curl quest.
Luckily, I only need the email once a day when a certain process runs. However, when that process does run it will need to reference the email multiple times.
This is the current accessor I have set up for email. The problem is the curl request is being called every time I use $user->email. What's the best way to avoid this?
in UserModel:
public function getEmailAttribute(){
$curl = new Curl;
$responseJson = $curl->post('https://www.dailycred.com/admin/api/user.json',array(
'client_id'=>getenv('dailycredId')
,'client_secret'=>getenv('dailycredSecret')
,'user_id'=>$this->id
));
$response = json_decode($responseJson);
return $response->email;
}
private $cached_email = false;
public function getEmailAttribute(){
if ($this->cached_email){
// if set return cached value
return $this->cached_email;
}
// get the email
$curl = new Curl;
$responseJson = $curl->post('https://www.dailycred.com/admin/api/user.json',array(
'client_id'=>getenv('dailycredId')
,'client_secret'=>getenv('dailycredSecret')
,'user_id'=>$this->id
));
$response = json_decode($responseJson);
// cache the value
$this->cached_email = $response->email;
// and return
return $this->cached_email;
}
Depending on your use case make adjustments (ie. session, cache , static property...).
Extend a the Eloquent Model class
namespace App\Models\Utils;
use Illuminate\Database\Eloquent\Model as OldModel;
class MyModel extends OldModel
{
private $cachedAttributes = [];
public function getCachedAttribute(string $key, Callable $callable)
{
if (!array_key_exists($key, $this->cachedAttributes)) {
$this->setCachedAttribute($key, call_user_func($callable));
}
return $this->cachedAttributes[$key];
}
public function setCachedAttribute(string $key, $value)
{
return $this->cachedAttributes[$key] = $value;
}
public function refresh()
{
unset($this->cachedAttributes);
return parent::refresh();
}
}
make your class
class ElementWithEmail extends MyModel
{
const ATTRIBUTE_KEY_FOR_EMAIL = 'Email';
public function getEmailAttribute(){
$key = self::ATTRIBUTE_KEY_FOR_EMAIL;
$callable = [$this, 'getEmail'];
return $this->getCachedAttribute($key, $callable);
}
protected function getEmail()
{
$curl = new Curl;
$responseJson = $curl->post('https://www.dailycred.com/admin/api/user.json',array(
'client_id'=>getenv('dailycredId')
,'client_secret'=>getenv('dailycredSecret')
,'user_id'=>$this->id
));
$response = json_decode($responseJson);
return $response->email;
}
}
Call it from your code
$element = new ElementWithEmail();
echo $element->email;

Resources