CakePHP 3 - implementedEvents() - does not fire implemented event - cakephp

Trying to start off with CakePHP's EventListener. I have set an event up, but it is not firing. I can't figure out why? This is the code I have so far...
public function view($slug = null) {
$profile = $this->Profiles->find()->where(['slug' => $slug])->first();
$this->set('profile', $profile);
$this->set('_serialize', ['profile']);
}
// I have confirmed this works.. but it is not calling the updateCountEvent method
public function implementedEvents(){
$_events = parent::implementedEvents();
$events['Controller.afterView'] = 'updateCountEvent';
return array_merge($_events, $events);
}
/**
* Triggered when a profile is viewed...
*/
public function updateCountEvent(){
Log::write('error', "Update count events"); // I dont get this line in the log. Not sure why this does not fire...
}

I revisited this question and was able to come up with a solution that works for me. Thanks Jose Lorenzo for the 'heads up'. This is my solution:
use Cake\Event\Event;
public function view($slug = null) {
$profile = $this->Profiles->find()->where(['slug' => $slug])->first();
$this->profileId = $profile->id;
$event = new Event('Controller.Profiles.afterView', $this);
$this->eventManager()->dispatch($event);
$this->set('title', $profile->name);
$this->set('profile', $profile);
$this->set('_serialize', ['profile']);
}
public function implementedEvents(){
$_events = parent::implementedEvents();
$events['Controller.Profiles.afterView'] = 'updateCountEvent';
return array_merge($_events, $events);
}
public function updateCountEvent(){
$profile = $this->Profiles->get($this->profileId);
$profile->set('views_count', $profile->views_count + 1);
$this->Profiles->save($profile);
}
I see the power of Events especially if I get to send out emails, update more tables and perhaps run a cron..., however instead of writing these 2 lines of code and create 2 more methods for this specific case, I could have made this simple call within the view action as such
$profile->set('views_count', $profile->views_count + 1);
$this->Profiles->save($profile);
The question would be.... Should I have opted for this simpler process, or stick with the events route?

Related

cant add paramater when caling view with search function in cakephp

In cakephp I have a search function which uses parameters and call backs. My issue is that I want to call this function with a parameter but the function cant accept parameters when called.
//I cant call this function with a paramater
$this->redirect(array('action' => 'email_list',$id,));
....
public function email_list($search = 0) {
$this->set( 'search',$search);
;
if (($this->request->is('post') || $this->request->is('put'))) {
if (isset($this->request->data['searchFilter'])) {
$filter_url['controller'] = $this->request->params['controller'];
$filter_url['action'] = $this->request->params['action'];
$filter_url['page'] = 1;
// for each filter we will add a GET parameter for the generated url
foreach($this->data['User'] as $name => $value){
if($value){
$filter_url[$name] = urlencode($value);
}
}
//Post params are now GET paramaters
return $this->redirect($filter_url);
}//isset
worked out a solution using a session variable that gets destroyed on entry. It is quick and easy. Having to redo a search function in cakephp ver 2 is not something i want to do. I am updating to cakephp ver 3
public function email_list($search = 0) {
if ($this->Session->check('sessrowid'))
{
$searchRowId=$this->Session->read('sessrowid');
$this->Session->delete('sessrowid');
}

Blowfish passwords indirectly saved through a separate Model method do not work

The following does work:
// app/Controller/UsersController.php
$this->User->save(array('pwd'=>$new_pwd),false);
The following does not work:
// app/Controller/UsersController.php
$this->User->setPassword($new_pwd);
The User model has the beforeSave() which works and the custom method setPassword() that does not:
// app/Model/User.php
public function beforeSave($options = array()) {
if (isset($this->data[$this->alias]['pwd'])&&!empty($this->data[$this->alias]['pwd'])) {
$new_password = $this->data[$this->alias]['pwd'];
$passwordHasher = new BlowfishPasswordHasher();
$this->data[$this->alias]['pwd'] = $passwordHasher->hash($new_password);
}
return true;
}
public function setPassword($new_password) {
$passwordHasher = new BlowfishPasswordHasher();
$result = $this->save(array(
'pwd' => $passwordHasher->hash($new_password),
), false);
return $result;
}
So the setPassword() is more or less identical yet whenever I try to log in with the password saved that way, $this->Auth->login() returns false. I can see the password hash updated in the database though.
Am I missing something? Please help
setPassword() internally also calls beforeSave() via save().
It becomes pretty clear that you are hashing it twice then, making it impossible to be used anymore.

Cakephp 2.3 beforeFilter and implementedEvents aren't able to co-exist

Using cakephp 2.3.5.
I use beforeFilter in several controllers to allow certain actions without the need to log in. I've used it successfully for quite some time. However, I've noticed that I can't get beforeFilter to fire if the controller also has the implementedEvents() method.
public function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('matchWwiProducts');
}
public function implementedEvents() {
return array(
'Controller.Product.delete' => 'deleteSku',
'Controller.Product.price' => 'notifySubscribers',
'Controller.Product.stock' => 'notifySubscribers'
);
}
For the code as displayed above I will be forced to do a login if I call the method www.example.com/products/matchWwiProducts.
When I comment out the implementedEvents() everything works as intended. I've searched around and can't find any references to implementedEvents() creating issues with beforeFilter.
The action matchWwiProducts() is as follows. It works perfectly when I log in. However, I don't want to force a log in for this action to take place.
public function matchWwiProducts() {
// this is an audit function that matches Products sourced by wwi
//
$this->autoRender = false; // no view to be rendered
// retrieve products sourced from wwi from Table::Product
$this->Product->contain();
$wwiProducts = $this->Product->getWwiSkus();
$wwiProductCount = count($wwiProducts);
// retrieve products sourced from wwi from Table:Wwiproduct
$this->loadModel('WwiProduct');
$this->Wwiproduct->contain();
$wwiSource = $this->Wwiproduct->getSkuList();
$wwiSourceCount = count($wwiSource);
// identify SKUs in $wwiProducts that are not in $wwiSource
$invalidSkus = array_diff($wwiProducts, $wwiSource);
// identify SKUs in $wwiSource that are not in $wwiProducts
$missingSkus = array_diff($wwiSource, $wwiProducts);
$missingSkuDetails = array();
foreach ($missingSkus as $missingSku) {
$skuStockStatus = $this->Wwiproduct->getStockStatus($missingSku);
$missingSkuDetails[$missingSku] = $skuStockStatus;
}
$email = new CakeEmail();
$email->config('sourcewwi');
$email->template('sourcewwiaudit', 'sourcewwi');
if (count($invalidSkus) > 0 || count($missingSkus) > 0) {
$email->subject('WWI Source Audit: Invalid or Missing SKUs');
$email->viewVars(array('invalidSkus' => $invalidSkus,
'missingSkuDetails' => $missingSkuDetails,
'wwiProductCount' => $wwiProductCount,
'wwiSourceCount' => $wwiSourceCount));
} else {
$email->subject('WWI Source Audit: No Exceptions');
$email->viewVars(array('wwiProductCount' => $wwiProductCount,
'wwiSourceCount' => $wwiSourceCount));
}
$email->send();
}
It doesn't fire because you're overloading the implementendEvents() method without making sure you keep the existing events there.
public function implementedEvents() {
return array_merge(parent::implementedEvents(), array(
'Controller.Product.delete' => 'deleteSku',
'Controller.Product.price' => 'notifySubscribers',
'Controller.Product.stock' => 'notifySubscribers'
));
}
Overloading in php.
Check most of the base classes, Controller, Table, Behavior, Component, they all fire or listen to events. So be careful when extending certain methods there. Most simple way might to do a search for "EventListenerInterface" in all classes. A class that implements this interface is likely to implement event callbacks.

Cakephp validation bug in controller?

I am trying to invalidate a field by a condition in controller instead of Model.
$this->Model->invalidate('check_out_reason', __('Please specify check out reason.', true));
The above won't work to invalidate the field. Instead, I need the below:
$this->Model->invalidate('Model.check_out_reason', __('Please specify check out reason.', true));
However, if I wish get the error message show up in the "field" itself ($this->model->validationErrors), it needs to be "check_out_reason" instead of "Model.check_out_reason". That means, I can't get the error message to show up in the field itself if I wish to invalidate the input in controller.
May I know is this a bug in CakePHP?
i created a test controller called "Invoices", just for testing, and i developed the following function
public function index(){
if (!empty($this->request->data)) {
$this->Invoice->invalidate('nombre', __('Please specify check out reason.'));
if ($this->Invoice->validates()) {
// it validated logic
if($this->Invoice->save($this->request->data)){
# everthing ok
} else {
# not saved
}
} else {
// didn't validate logic
$errors = $this->Invoice->validationErrors;
}
}
}
i think it worked for me
Change the field "nombre" for your field called "check_out_reason" to adapt the function to your code
I found a workaround for manual invalidates from controller. Reading a lot on this issue I found out that the save() function doesn't take in consideration the invalidations set through invalidate() function called in controller, but (this is very important) if it is called directly from the model function beforeValidate() it's working perfectly.
So I recommend to go in AppModel.php file and create next public methods:
public $invalidatesFromController = array();
public function beforeValidate($options = array()) {
foreach($this->invalidatesFromController as $item){
$this->invalidate($item['fieldName'], $item['errorMessage'], true);
}
return parent::beforeValidate($options);
}
public function invalidateField($fieldName, $errorMessage){
$this->invalidatesFromController[] = array(
'fieldName' => $fieldName,
'errorMessage' => $errorMessage
);
}
After that, make sure that your model's beforeValidate() function calls the parent's one:
public function beforeValidate($options = array()) {
return parent::beforeValidate($options);
}
In your controller for invalidating a field use next line:
$this->MyModel->invalidateField('fieldName', "error message");
Hope it helps! For me it's working!

Cakephp 2.0 change password

I am attempting to create a change password form in cakephp 2.0. I found a behavior that EuroMark created for 1.3 and now am having a tough time converting this code to work with 2.0. I know that it has something to do with the Auth Component as there were major changes to this component in 2.0.
public function validateCurrentPwd(Model $Model, $data) {
if (is_array($data)) {
$pwd = array_shift($data);
} else {
$pwd = $data;
}
$uid = null;
if ($Model->id) {
$uid = $Model->id;
} elseif (!empty($Model->data[$Model->alias]['id'])) {
$uid = $Model->data[$Model->alias]['id'];
} else {
return false;
}
if (class_exists('AuthExtComponent')) {
$this->Auth = new AuthExtComponent();
} elseif (class_exists($this->settings[$Model->alias]['auth'].'Component')) {
$auth = $this->settings[$Model->alias]['auth'].'Component';
$this->Auth = new $auth();
} else {
return true;
}
return $this->Auth->verifyUser($uid, $pwd);
}
I am getting an error on the line that reads $this->Auth = new $auth();
The error is as follows:
Argument 1 passed to Component::__construct() must be an instance of ComponentCollection, none given, called in C:\UniServer\www\new_company_test\app\Model\Behavior\change_password.php on line 117 and defined [CORE\Cake\Controller\Component.php, line 77]
and
Undefined variable: collection [CORE\Cake\Controller\Component.php, line 78]
it's also throwing this
Call to undefined method AuthComponent::verifyUser() in C:\UniServer\www\new_company_test\app\Model\Behavior\change_password.php on line 121
I am not sure if there is anything else that needs to be addressed in the script, I'm guessing not as there is no other place where Auth is used.
Any suggestions on what I need to do to get this to work? Any help is appreciated.
Thanks
you did discover that there is also a 2.0 branch, didnt you? :)
it should contain the same behavior:
https://github.com/dereuromark/tools/tree/2.0
either way, you need to pass a component collection into it:
$this->Auth = new AuthExtComponent(new ComponentCollection());
You should create a method verifyUser in your custom AuthExt Component which extends Auth Component for "current password" to work like so:
/**
* Quickfix
* TODO: improve - maybe use Authenticate
* #return bool $success
*/
public function verifyUser($id, $pwd) {
$options = array(
'conditions' => array('id'=>$id, 'password'=>$this->password($pwd)),
);
return $this->getModel()->find('first', $options);
$this->constructAuthenticate();
$this->request->data['User']['password'] = $pwd;
return $this->identify($this->request, $this->response);
}
/**
* returns the current User model
* #return object $User
*/
public function getModel() {
return ClassRegistry::init(CLASS_USER);
}
Maybe it is also possible to use the existing identify method in combination with a fake request object in the behavior directly?
I am thinking about using
$this->authenticate = array('Form'=>array('fields'=>array('username' => 'id')));
feel free to fork the behavior and submit a pull request.
"current password" is the only thing that is not yet cleanly solved right now.

Resources