Mage_Core_Exception with message 'Cannot retrieve entity config: sales/Array' - magento

The following code runs fine under Magento 1.6 but raises a Mage_Core_Exception (message: 'Cannot retrieve entity config: sales/Array') when run under 1.5.0.1. What do I need to do to get this code running under Magento 1.5.0.1?
$results = Mage::getResourceModel('sales/order_collection');
$results->join(
array('status_key_table' => 'order_status'),
'main_table.status = status_key_table.status',
array('status_key_table.label')
);
Thank you,
Ben

If you compare the join() methods between 1.5.0.1 and 1.6.2.0:
1.5.0.1: Mage_Core_Model_Mysql4_Collection_Abstract::join()
public function join($table, $cond, $cols='*')
{
if (!isset($this->_joinedTables[$table])) {
$this->getSelect()->join(array($table=>$this->getTable($table)), $cond, $cols);
$this->_joinedTables[$table] = true;
}
return $this;
}
1.6.2.0: Mage_Core_Model_Resource_Db_Collection_Abstract::join()
public function join($table, $cond, $cols = '*')
{
if (is_array($table)) {
foreach ($table as $k => $v) {
$alias = $k;
$table = $v;
break;
...
You can see that 1.5.0.1 doesn't support aliases. Instead, it calls $this->getTable() on the first parameter you pass in -- which should be a string. So, in your case, you'll need to pass in 'sales/order_status' as the first parameter.

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.

Laravel - How to update Input Array without deleting Sales Detail

In my Laravel-8 project, I have this controller for Input Field Array Update.
Controller:
public function update(UpdateSaleRequest $request, $id)
{
try {
$sale = Sale::find($id);
$data = $request->all();
$update['date'] = date('Y-m-d', strtotime($data['date']));
$update['company_id'] = $data['company_id'];
$update['name'] = $data['name'];
$update['remarks'] = $data['remarks'];
$sale->update($update);
SaleDetail::where('sale_id', $sale->id)->delete();
foreach ($data['invoiceItems'] as $item) {
$details = [
'sale_id' => $sale->id,
'item_id' => $item['item_id'],
'employee_id' => $item['employee_id'],
'quantity' => $item['qty'],
'price' => $item['cost'],
'total_price' => $item['cost'] * $item['qty'],
'sale_type_id'=>$item['sale_type_id']
];
$saleDetail = new SaleDetail($details );
$saleDetail->save();
}
} catch (JWTException $e) {
throw new HttpException(500);
}
return response()->json($sale);
}
In the form, the user can add more Sales Detail or remove.
Some of the SaleDetail fields are being used somewhere else.
Is there a way to update the input field array without deleting the SaleDetail as shown in what I did here:
SaleDetail::where('sale_id', $sale->id)->delete();
Thanks
I've tried to restructure your code so that's easier to edit. I've left some comments. I can really recommend refactoring.guru. There you will find many ways to improve your code so that it is more extensible, maintainable and testable. If you have any questions, please feel free to ask.
class Sale extends Model
{
// Use a relationship instead of building your own query
public function details() {
return $this->hasMany(SaleDetail::class);
}
}
class SaleDetail extends Model
{
// Use a computed property instead of manually calculating total price
// You can access it with $saleDetail->totalPrice
public function getTotalPriceAttribute() {
return $this->price * $this->quantity;
}
}
class UpdateSaleRequest extends Request
{
public function authorize() {
return true;
}
protected function prepareForValidation() {
$this->merge([
// Create a Carbon instance by string
'date' => Carbon::make($this->date)
]);
}
public function rules() {
// Your validation rules
// Please also validate your invoice items!
// See https://laravel.com/docs/8.x/validation#validating-arrays
}
}
// We let Laravel solve the sale by dependency injection
// You have to rename the variable name in ihr web.php
public function update(UpdateSaleRequest $request, Sale $sale)
{
// At this point, all inputs are validated!
// See https://laravel.com/docs/8.x/validation#creating-form-requests
$sale->update($request->validated());
// Please ensure, that all properties have the same name
// In your current implementation you have price = cost, be consistent!
foreach($request->input('invoiceItems') as $invoiceItem) {
// How we can consider that a detail is already created?
// I assume that each item_id will only occur once, otherwise you'll
// place the id of each detail in your update form (e.g. in a hidden input)
$candidate = $sale->details()
->where('item_id', $properties['item_id'])
->first();
if($candidate) {
$candidate->update($properties);
} else {
$sale->details()->create($properties);
}
}
// A JWT-Exception should not be necessary, since your authentication
// will be handled by a middleware.
return response()->json($sale);
}
I have not tested the code, few adjustments may be needed.
Laravel has a method called updateOrCreate as follow
/**
* Create or update a record matching the attributes, and fill it with values.
*
* #param array $attributes
* #param array $values
* #return \Illuminate\Database\Eloquent\Model|static
*/
public function updateOrCreate(array $attributes, array $values = [])
{
return tap($this->firstOrNew($attributes), function ($instance) use ($values) {
$instance->fill($values)->save();
});
}
That means you could do some thing like
public function update(UpdateSaleRequest $request, $id)
{
try {
$sale = Sale::find($id);
$data = $request->all();
$update['date'] = date('Y-m-d', strtotime($data['date']));
$update['company_id'] = $data['company_id'];
$update['name'] = $data['name'];
$update['remarks'] = $data['remarks'];
$sale->update($update);
foreach ($data['invoiceItems'] as $item) {
$details = [
'item_id' => $item['item_id'],
'employee_id' => $item['employee_id'],
'quantity' => $item['qty'],
'price' => $item['cost'],
'total_price' => $item['cost'] * $item['qty'],
'sale_type_id'=>$item['sale_type_id']
];
$sale->saleDetail()->updateOrCreate([
'sale_id' => $sale->id
], $details);
}
} catch (JWTException $e) {
throw new HttpException(500);
}
return response()->json($sale);
}
I would encourage you to refactor and clean up your code.You can also read more about it here https://laravel.com/docs/8.x/eloquent#upserts

How to fix error: count(): Parameter must be an array or an object that implements Countable

A PHP Error was encountered
Severity: Warning
Message: count(): Parameter must be an array or an object that implements Countable
Filename: models/login_model.php
Line Number: 17
Backtrace:
File: C:\xampp\htdocs\labexercise007\application\models\login_model.php
Line: 17
Function: _error_handler
File: C:\xampp\htdocs\labexercise007\application\controllers\login.php
Line: 31
Function: login
File: C:\xampp\htdocs\labexercise007\application\controllers\login.php
Line: 14
Function: run
<?php
class login_model extends CI_Model
{
public function __construct()
{
parent::__construct();
$this->load->database();
}
public function login($username, $password)
{
$condition_array = array(
'user_name' => $username,
'user_pass' => md5($password)
);
$rs = $this->db->get_where('users', $condition_array);
$row_count = count($rs->row_array());
if ($row_count > 0) {
return $rs->row_array();
} else {
return FALSE;
}
}
}
<?php
class Login extends CI_Controller
{
public function index()
{
$data['title'] = 'Login';
$this->load->view('login', $data);
}
public function verify()
{
$this->form_validation->set_rules('txtuser', 'Username', 'required');
$this->form_validation->set_rules('txtpass', 'Password', 'required|callback_check_user');
if ($this->form_validation->run() === TRUE) {
if ($this->session->user_lvl == 1) {
redirect('admin/home');
} else {
redirect('home');
}
} else {
$this->index();
}
}
public function check_user()
{
$username = $this->input->post('txtuser');
$password = $this->input->post('txtpass');
$this->load->model('login_model');
$login = $this->login_model->login($username, $password);
if ($login) {
$sess_data = array(
'account_name' => $login['user_accountname'],
'user_lvl' => $login['user_lvl'],
'islogged' => TRUE
);
$this->session->set_userdata($sess_data);
return true;
} else {
$this->form_validation->set_message('check_user', 'Invalid Username/password');
return false;
}
}
}
PHP v7.2.0 count() will now yield a warning on invalid countable types passed to the array_or_countable parameter.
see more
If your result set is empty, the row_array() value is NULL and you cannot use count on it. Check what row_array() returns before counting.
Later edit
Try removing this block:
if ($row_count > 0) {
return $rs->row_array();
} else {
return FALSE;
}
And replacing it with a ternary operator call:
return $rs->row_array() ?: false;
This should solve your warning and return the proper result.
Did you try to get the result array? in the documentation in query builder it gives like this.
$row_count = count($rs->getResultArray()) ;
in this case it will always return an array even if you do not have a result in query.
Instead of counting the row array that might be null in your case so you don't have a countable object or array you should count your query results.
$row_count = count($rs->row_array());
Instead use the count records function built in the query builder.
public function login($username, $password)
{
$condition_array = array(
'user_name' => $username,
'user_pass' => md5($password)
);
$rs = $this->db->from('users')->where($condition_array)->get();
$row_count = $this->db->num_rows();
if ($row_count > 0) {
return $rs->row_array();
}
return FALSE;
}
Changed the function to remove the else statement because it was not needed in this case.
Also, I might add that you shouldn't be using MD5 to encrypt your passwords. MD5 is not really secure at this point.
More information on counting database results on codeigniter 3: https://codeigniter.com/userguide3/database/query_builder.html?highlight=count#limiting-or-counting-results
In Model:
function getPaymentTotalCount($search)
{
$sql = 'SELECT id FROM tbl_payment WHERE '.$search;
$query = $this->db->query($sql);
return $query->num_rows(); // <- replace row() to num_rows()
}

How to change existing tag information in Magento

I am trying to update the popularity count of Magento's Tag module by interacting with this core function in Mage_Tag_Model_API
public function update($tagId, $data, $store)
{
$data = $this->_prepareDataForUpdate($data);
$storeId = $this->_getStoreId($store);
/** #var $tag Mage_Tag_Model_Tag */
$tag = Mage::getModel('tag/tag')->setStoreId($storeId)->setAddBasePopularity()->load($tagId);
if (!$tag->getId()) {
$this->_fault('tag_not_exists');
}
// store should be set for 'base_popularity' to be saved in Mage_Tag_Model_Resource_Tag::_afterSave()
$tag->setStore($storeId);
if (isset($data['base_popularity'])) {
$tag->setBasePopularity($data['base_popularity']);
}
if (isset($data['name'])) {
$tag->setName(trim($data['name']));
}
if (isset($data['status'])) {
// validate tag status
if (!in_array($data['status'], array(
$tag->getApprovedStatus(), $tag->getPendingStatus(), $tag->getDisabledStatus()))) {
$this->_fault('invalid_data');
}
$tag->setStatus($data['status']);
}
try {
$tag->save();
} catch (Mage_Core_Exception $e) {
$this->_fault('save_error', $e->getMessage());
}
return true;
}
In my controller I have this :
public function clickAction()
{
$tagString = $this->getRequest()->getParam('tag');
$tagByName = Mage::getModel('tag/tag')->loadByName($tagString);
$tagId = $tagByName->getTagId();
$basePopularity = ['base_popularity' => '13']; // hard coding while testing
Mage::getModel('tag/api')->update($tagId, $basePopularity, 1);
}
If I put a log statement in this part of the update function :
try {
// log stuff
$tag->save();
}
I can see it makes it to that try but there is no change in the data. What did I screw up? Any other ideas on how I can update the popularity of a tag through a controller? Using this same method and adding 'name' => 'blah' to that $data array parameter works fine..
I also found in Mage_Tag_Model_Indexer_Summary.php this method defined in the PHPdoc * #method Mage_Tag_Model_Indexer_Summary setPopularity(int $value) Maybe that is what I need... can someone provide an example showing how I could use that magic setter?
Try adding Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID); at the start of your clickAction function. base_popularity can only be updated from admin store.

Form validation with custom callback function

I created a "callback" function to check if the username exists in the DB.
I have multiple rules for the "username" field, but the only thing that work is my callback function. It refuses to check against the other rules. I tried leaving the field empty, and the "required" rule never kicked in.
Controller:
account.php
function register() {
$this->load->library('validation');
$fields['username'] = "trim|required|callback_username_check";
etc ...
etc ...
$this->validation->set_rules($fields);
if ($this->validation->run()) {
$records = array();
$records['username'] = $this->validation->username;
etc ...
etc ...
$data = $this->account_model->registerNewAccount($records);
}
$this->load->view('register_view');
}
function username_check($username) {
$m = new Mongo();
$collection = $m->selectDB( DBNAME )->selectCollection( TABLE );
$data = $collection->count(array("username" => $username) );
if($data == 1) {
$this->validation->set_message('username_check', '%s is already taken!');
return false;
} else {
return true;
}
}
Try using the new form_validation class here:
http://ellislab.com/codeigniter/user_guide/libraries/form_validation.html
I believe there was a bug about it.

Resources