In BroadcastServiceProvider.php I've got data when user joins the channel and I would like to store it to DB. I am wondering how to override this storeUser() function to make it work (I've used this function before but it was in other circumstances).
public function storeUser() {
UserInfo::create([
'ip' => Request::ip(),
'name' => Auth::user()->name
]);
}
BroadcastServiceProvider.php
Broadcast::channel('chat', function ($user) {
$ip = Request::ip();
if (auth()->check()) {
return [
'id' => $user->id,
'ip' => $ip,
'name' => $user->name
];
}
});
Update the UserInfo model to have the storeUser method.
class UserInfo
{
public static function storeUser() {
UserInfo::create([
'ip' => Request::ip(),
'name' => Auth::user()->name
]);
}
Then you can call it in the broadcaster
Broadcast::channel('chat', function ($user) {
$ip = Request::ip();
if (auth()->check()) {
UserInfo::storeUser();
return [
'id' => $user->id,
'ip' => $ip,
'name' => $user->name
];
}
});
You can also call it in the same way UserInfo::storeUser(); in the users controller where ever you need it.
Related
I want to change my password which is been hashed while saving.
How can i change the password?
'password' => Hash::make($data->password).
My Controller
$request->validate([
'oldpass' => 'required',
'password' => 'required|alphaNum|min:6',
'password_confirmation' => 'required|same:newpass',
]);
$id = $request->id;
$users = Auth::user()->whereId($id)->get();
foreach ($users as $user) {
if ($oldpass == $user->password) {
$user->update([
'password' => Hash::make($request->newpass)
]);
return view('\balance');
} else {
return 'error';
}
}
You should use Hash::check($old_password, $hashed_password), something like this:
public function passwordChange(Request $request, User $user_name) {
// find the loggedin user
$user = User::find(Auth::user()->id);
// validate rules
$validator = Validator::make($request->all(), [
'old_password' => 'required|min:6',
'password' => 'required_with:password_confirmation|required|min:6',
'password_confirmation' => 'confirmed|required|min:6',
]);
// what to do if validator fails
if ($validator->fails()) {
return redirect($user->user_name . '/settings')->withErrors($validator)->withInput();
} else {
$old_password = $request->input('old_password');
$new_password = $request->input('password');
$hashed_password = Auth::user()->password;
// checking the old pass with new one
if (Hash::check($old_password, $hashed_password)) {
$user->update([
'password'=> Hash::make($new_password)
]);
return redirect($user->user_name . '/settings')->with('success', 'Your Password updated.');
} else {
return redirect($user->user_name . '/settings')->with('success', 'Your Old password is wrong!');
}
}
}
Please also notice 'password' => 'required_with:password_confirmation and 'password_confirmation' => 'required|same:newpass' on validator. Hope it helps.
First way using the sort option from ActiveDataProvider in the controller.
public function prepareDataProvider()
{
$query = $this->setupQueryWithPermissions();
$dataProvider = new ActiveDataProvider([
'query' => $query,
'sort' => [
'defaultOrder' => [
'id' => SORT_DESC,
]
],
]);
return $dataProvider;
}
Or the following in the model:
public static function find()
{
$query = new CallQuery(get_called_class());
$query->leftJoin('call_rec_files', 'calls.callref = call_rec_files.callref')
->select('calls.*, call_rec_files.crfref as callrecord_id')
->orderBy(['calls.callref' => SORT_DESC]);
return $query;
}
I am working on multilingual posts. I have added beforefind() in the PostsTable so I can list posts for current language
public function beforeFind(Event $event, Query $query) {
$query->where(['Posts.locale' => I18n::locale()]);
}
In order to allow users duplicate posts in different languages i wrote following function:
public function duplicate(){
$this->autoRender = false;
$post_id= $this->request->data['post_id'];
$post = $this->Posts
->findById($post_id)
->select(['website_id', 'category_id', 'locale', 'title', 'slug', 'body', 'image', 'thumb', 'meta_title', 'meta_description', 'other_meta_tags', 'status'])
->first()
->toArray();
foreach($this->request->data['site'] as $site) {
if($site['name'] == false) {
continue;
}
$data = array_merge($post, [
'website_id' => $site['website_id'],
'locale' => $site['locale'],
'status' => 'Draft',
'duplicate' => true
]);
$pageData = $this->Posts->newEntity($data);
if($this->Posts->save($pageData)) {
$this->Flash->success(__('Post have been created.'));;
} else{
$this->Flash->error(__('Post is not created.'));
}
}
return $this->redirect(['action' => 'edit', $post_id]);
}
In order to check if the posts are already duplicated. I am doing a check in 'edit' functino:
$languages = TableRegistry::get('Websites')->find('languages');
foreach($languages as $language)
{
$exists[] = $this->Posts
->findByTitleAndWebsiteId($post['title'], $language['website_id'])
->select(['locale', 'title', 'website_id'])
->first();
}
$this->set('exists',$exists);
but as the beforefind() is appending query to above query. I am not getting any results. Is there any way I can ignore beforefind() for only cerrtain queries. I tried using entity as below:
public function beforeFind(Event $event, Query $query) {
if(isset($entity->duplicate)) {
return true;
}
$query->where(['Posts.locale' => I18n::locale()]);
}
but no luck. Could anyone guide me? Thanks for reading.
There are various possible ways to handle this, one would be to make use of Query::applyOptions() to set an option that you can check in your callback
$query->applyOptions(['injectLocale' => false])
public function beforeFind(Event $event, Query $query, ArrayObject $options)
{
if(!isset($options['injectLocale']) || $options['injectLocale'] !== false) {
$query->where(['Posts.locale' => I18n::locale()]);
}
}
Warning: The $options argument is currently passed as an array, while it should be an instance of ArrayObject (#5621)
Callback methods can be ignored using this:
$this->Model->find('all', array(
'conditions' => array(...),
'order' => array(...),
'callbacks' => false
));
Everyone, who started ZF2 learning with the "Get started" tutorial, will know the model class Album (s. below).
Now I want to extend my model with songs. One album can have 0 or more songs. The songs will get a new talbe songs (id, title, album_id) and the mapper Album\Model\Song. The mapper Album\Model\Song will be built similar to Album\Model\Album. The mapper Album\Model\Album will get a new property songCollection (array of Album\Model\Song objects or maybe something like Album\Model\SongCollection object).
Does it make sence to use the InputFilter for "nested" (mapper) classes?
How should the getInputFilter() be modified?
How should the setInputFilter() be modified? OK, now it is not implemented at all. But it's approximately clear how to do it for a shallow class structure -- and not clear how to implement it for a mapper, that references another mapper(-s).
Album\Model\Album
<?php
namespace Album\Model;
use Zend\Stdlib\ArraySerializableInterface;
use Zend\InputFilter\Factory as InputFactory;
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
class Album implements InputFilterAwareInterface, ArraySerializableInterface {
public $id;
public $artist;
public $title;
protected $inputFilter;
public function exchangeArray(array $data) {
$this->id = (isset($data['id'])) ? $data['id'] : null;
$this->artist = (isset($data['artist'])) ? $data['artist'] : null;
$this->title = (isset($data['title'])) ? $data['title'] : null;
}
public function toArray() {
return $this->getArrayCopy();
}
public function getArrayCopy() {
return get_object_vars($this);
}
public function setInputFilter(InputFilterInterface $inputFilter) {
throw new \Exception('Not used');
}
public function getInputFilter() {
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$factory = new InputFactory();
$inputFilter->add($factory->createInput(array(
'name' => 'id',
'required' => true,
'filters' => array(
array('name' => 'Int')
)
)));
$inputFilter->add($factory->createInput(array(
'name' => 'artist',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim')
),
'validarots' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100
)
)
)
)));
$inputFilter->add($factory->createInput(array(
'name' => 'title',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim')
),
'validarots' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100
)
)
)
)));
$this->inputFilter = $inputFilter;
}
return $this->inputFilter;
}
}
I think you are a little confused with the relationship with the models and mappers set out in this example.
The 'mappers' would be the TableGateway objects, such as AlbumTable, SongTable etc. The Album and Song classes yo would call models, or Domain Objects, these are what represent the actual entities in your application. The Mappers just take care of persisting them in your database etc.
When using the TableGateway implementation, I would let each Domain Object (such as Ablum) handle the InputFilter for the attributes it's TableGateway is going to persist (such as AlbumTable).
For the example you stated, I would not change the Album Models InputFilter at all. The reason is the relationship with Songs is this:
Album HAS many songs, Song Belongs to Album (the Song would have the link back to the Album)
Add a new Song Object and Gateway:
<?php
namespace Album\Model;
use Zend\Stdlib\ArraySerializableInterface;
use Zend\InputFilter\Factory as InputFactory;
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
class Song implements InputFilterAwareInterface, ArraySerializableInterface {
protected $id;
protected $album;
protected $title;
protected $inputFilter;
// Added Getters / Setters for the attributes rather than
// having public scope ...
public function setAlbum(Album $album)
{
$this->album = $album;
}
public function getAlbum()
{
return $this->album;
}
public function exchangeArray(array $data) {
$this->id = (isset($data['id'])) ? $data['id'] : null;
$this->title = (isset($data['title'])) ? $data['title'] : null;
if(isset($data['album_id'])) {
$album = new Album();
$album->exchangeArray($data['album_id']);
$this->setAlbum($album);
}
}
public function toArray() {
return $this->getArrayCopy();
}
public function getArrayCopy() {
return array(
'id' => $this->id,
'album_id' => $this->getAlbum()->id,
'title' => $this->title,
);
}
public function setInputFilter(InputFilterInterface $inputFilter) {
throw new \Exception('Not used');
}
public function getInputFilter() {
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$factory = new InputFactory();
$inputFilter->add($factory->createInput(array(
'name' => 'id',
'required' => true,
'filters' => array(
array('name' => 'Int')
)
)));
$inputFilter->add($factory->createInput(array(
'name' => 'album_id',
'required' => true,
'filters' => array(
array('name' => 'Int')
)
)));
$inputFilter->add($factory->createInput(array(
'name' => 'title',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim')
),
'validarots' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100
)
)
)
)));
$this->inputFilter = $inputFilter;
}
return $this->inputFilter;
}
}
Notice no need to change the Album Model as the relationship is 'Song Belongs to Album'.
When you object relationships get more complex you will want to look at using Hydrators to build the objects for you (http://framework.zend.com/manual/2.0/en/modules/zend.stdlib.hydrator.html)
Now you would create a SongTable to persist this new Object for you:
<?php
namespace Album\Model;
use Zend\Db\TableGateway\TableGateway;
class SongTable
{
protected $tableGateway;
public function __construct(TableGateway $tableGateway)
{
$this->tableGateway = $tableGateway;
}
public function fetchAll()
{
$resultSet = $this->tableGateway->select();
return $resultSet;
}
public function getSong($id)
{
$id = (int) $id;
$rowset = $this->tableGateway->select(array('id' => $id));
$row = $rowset->current();
if (!$row) {
throw new \Exception("Could not find row $id");
}
return $row;
}
public function saveSong(Song $song)
{
$data = array(
'album_id' => $song->getAlbum()->id,
'title' => $song->title,
);
$id = (int)$song->id;
if ($id == 0) {
$this->tableGateway->insert($data);
} else {
if ($this->getSong($id)) {
$this->tableGateway->update($data, array('id' => $id));
} else {
throw new \Exception('Form id does not exist');
}
}
}
public function fetchAlbumSongs(Album $album)
{
$resultSet = $this->tableGateway->select(array(
'album_id' => $album->id
));
return $resultSet;
}
public function addSongsToAlbum(Album $album)
{
foreach($this->fetchAlbumSongs($album) as $song) {
$album->addSong($song);
}
}
}
You Could then Modify you Album model to allow Songs to be added:
class Album implements InputFilterAwareInterface, ArraySerializableInterface {
// Other stuff here
/**
* #var array
*/
protected $songs = array();
public function addSong(Song $song)
{
$this->songs[] = $song;
}
public function getSongs()
{
return $this->songs;
}
}
You can then build your object graph easily, I would usually make a server to do do this kind of thing:
AlbumService.php
public function getAlumbWithSongs(int $id)
{
$album = $this->getAlbumTable()->getAlbum($id);
if($album) {
$this->getSongTable()->addSongsToAlbum($album);
}
return $album;
}
Back in May, I posted this question. I'm trying to do the same thing again on a different app, but I haven't found a solution to this problem. I do have more information and better code, so I'm hoping you guys can help me sort this out.
Use Case:
Doctor's office has a website with admin users. The users login successfully with CakePHP's Auth via User model and UsersController.
Doctors have referring physicians with completely different profiles and actions. Doctors need to login via example.com/physicians/login. However, this login is failing with this
authError => 'You are not authorized to access that location.'
Here is my code in AppController:
class AppController extends Controller {
public $helpers = array('Form', 'Html', 'Time', 'Session', 'Js' => array('Jquery'));
public $components = array(
'Session',
'Auth' => array(
'autoRedirect' => false,
'authorize' => 'Controller'
)
);
public function beforeFilter() {
$this->Auth->allow('index', 'view', 'edit', 'display', 'featured', 'events', 'contact', 'signup', 'search', 'view_category', 'view_archive', 'addComment', 'schedule', 'login');
}
}
And here is my UsersController that is working:
class UsersController extends AppController {
public $components = array(
'Auth' => array(
'authenticate' => array(
'Form' => array(
'userModel' => 'User',
'fields' => array(
'username' => 'username',
'password' => 'password'
)
)
),
'loginRedirect' => array('controller' => 'users', 'action' => 'admin'),
'logoutRedirect' => array('controller' => 'pages', 'action' => 'index'),
'loginAction' => array('controller' => 'users', 'action' => 'login'),
'sessionKey' => 'Admin'
)
);
public function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('add', 'login', 'logout');
}
function isAuthorized() {
return true;
}
public function login() {
if ($this->request->is('post')) {
if ($this->Auth->login()) {
$this->redirect($this->Auth->redirect());
} else {
$this->Session->setFlash(__('Invalid username or password, try again'));
}
}
}
public function logout() {
$this->Session->destroy();
$this->redirect($this->Auth->logout());
}
Here is my PhysiciansController code that is NOT working:
class PhysiciansController extends AppController {
public $components = array(
'Auth' => array(
'authenticate' => array(
'Form' => array(
'userModel' => 'Physician',
'fields' => array(
'username' => 'username',
'password' => 'password'
)
)
),
'loginRedirect' => array('controller' => 'physicians', 'action' => 'dashboard'),
'logoutRedirect' => array('controller' => 'pages', 'action' => 'index'),
'loginAction' => array('controller' => 'physicians', 'action' => 'login'),
'sessionKey' => 'Physician'
)
);
public function beforeFilter() {
parent::beforeFilter();
$this->Auth->authorize = array(
'Actions' => array(
'userModel' => 'Physician',
'actionPath' => 'physicians'
)
);
$this->Auth->allow('login', 'logout');
// $this->Session->write('Auth.redirect','/physicians/index');
}
function isAuthorized() {
return true;
}
public function login() {
if ($this->request->is('post')) {
if ($this->Auth->login()) {
$this->redirect(array('controller' => 'physicians', 'action' => 'dashboard'));
} else {
$this->Session->read();
debug($this->Auth);
$this->Session->setFlash(__('Invalid username or password, try again'));
}
}
}
public function logout() {
$this->Session->destroy();
$this->redirect($this->Auth->logout());
}
I really don't want to start over and switch to ACL -- I'm not sure that's necessary for just two logins. Help would be very much appreciated!
EDIT: Joshua's answer below is awesome and super helpful. I implemented it, but I'm still receiving an unauthorized error when I try to login as a Physician via /phys/physican/login (prefix/controller/action). The Admin setup works great. Here's the debug code when I try to login:
object(AuthComponent) {
components => array(
(int) 0 => 'Session',
(int) 1 => 'RequestHandler'
)
authenticate => array(
'Form' => array(
'userModel' => 'Physician'
)
)
authorize => false
ajaxLogin => null
flash => array(
'element' => 'default',
'key' => 'auth',
'params' => array()
)
loginAction => array(
'controller' => 'physicians',
'action' => 'phys_login'
)
loginRedirect => null
logoutRedirect => '/'
authError => 'You are not authorized to access that location.'
allowedActions => array()
request => object(CakeRequest) {
params => array(
'prefix' => '*****',
'plugin' => null,
'controller' => 'physicians',
'action' => 'phys_login',
'named' => array(),
'pass' => array(),
'phys' => true,
'_Token' => array(
'key' => 'ad1ea69c3b2c7b9e833bbda03ef18b04079b23c3',
'unlockedFields' => array()
),
'isAjax' => false
)
data => array(
'Physician' => array(
'password' => '*****',
'username' => 'deewilcox'
)
)
query => array()
url => 'phys/physicians/login'
base => ''
webroot => '/'
here => '/phys/physicians/login'
}
response => object(CakeResponse) {
}
settings => array()
}
OK I've got a way to do it. You know about prefix routing? If not, read my answer here: CakePHP/MVC Admin functions placement That answer describes how to set up a single routing prefix ('admin'). But you can have any number - just like this:
Configure::write('Routing.prefixes', array('admin','phys','member','user'));
// now we have admin, phys, member and user prefix routing enabled.
What you can do is have all the doctor's methods use 'admin' prefix routing, and all the physicians methods use 'phys' prefix routing.
So the below is code I've hacked together pretty quickly, so it might not be perfect but it should show the concept. Here it is in pseudo code for the before filter method of your app controller:
if (USER IS TRYING TO ACCESS AN ADMIN PREFIXED METHOD) {
Then use the users table for auth stuff
} else if (USER IS TRYING TO ACCESS A PHYS PREFIXED METHOD) {
Then use the physicians table for auth stuff
} else {
It's neither an admin method, not a physicians method. So just always allow access. Or always deny access - depending on your site
}
Here's my app controller code:
App::uses('Controller', 'Controller');
class AppController extends Controller {
public $components = array('Security','Cookie','Session','Auth','RequestHandler');
public $helpers = array('Cache','Html','Session','Form');
function beforeFilter() {
if ($this->request->prefix == 'admin') {
$this->layout = 'admin';
// Specify which controller/action handles logging in:
AuthComponent::$sessionKey = 'Auth.Admin'; // solution from https://stackoverflow.com/questions/10538159/cakephp-auth-component-with-two-models-session
$this->Auth->loginAction = array('controller'=>'administrators','action'=>'login');
$this->Auth->loginRedirect = array('controller'=>'some_other_controller','action'=>'index');
$this->Auth->logoutRedirect = array('controller'=>'administrators','action'=>'login');
$this->Auth->authenticate = array(
'Form' => array(
'userModel' => 'User',
)
);
$this->Auth->allow('login');
} else if ($this->request->prefix == 'phys') {
// Specify which controller/action handles logging in:
AuthComponent::$sessionKey = 'Auth.Phys'; // solution from https://stackoverflow.com/questions/10538159/cakephp-auth-component-with-two-models-session
$this->Auth->loginAction = array('controller'=>'users','action'=>'login');
$this->Auth->logoutRedirect = '/';
$this->Auth->authenticate = array(
'Form' => array(
'userModel' => 'Physician',
)
);
} else {
// If we get here, it is neither a 'phys' prefixed method, not an 'admin' prefixed method.
// So, just allow access to everyone - or, alternatively, you could deny access - $this->Auth->deny();
$this->Auth->allow();
}
}
public function isAuthorized($user){
// You can have various extra checks in here, if needed.
// We'll just return true though. I'm pretty certain this method has to exist, even if it just returns true.
return true;
}
}
Note the lines:
AuthComponent::$sessionKey = 'Auth.Admin'; // solution from https://stackoverflow.com/questions/10538159/cakephp-auth-component-with-two-models-session
and
AuthComponent::$sessionKey = 'Auth.Phys'; // solution from https://stackoverflow.com/questions/10538159/cakephp-auth-component-with-two-models-session
What that does is allows a person to be logged in as both a physician, and an admin, in the one browser, without interfering with each other's session. You may not need it in the live site, but it's certainly handy while testing.
Now, in you're respective controllers, you'll need straight-forward login/logout methods, with the appropriate prefix.
So, for admin prefixing, in your users controller:
public function admin_login() {
if ($this->request->is('post')) {
if ($this->Auth->login()) {
return $this->redirect($this->Auth->redirect());
} else {
$this->Session->setFlash(__('Username or password is incorrect'), 'default', array(), 'auth');
}
}
}
public function admin_logout() {
$this->Session->setFlash('Successfully Logged Out');
$this->redirect($this->Auth->logout());
}
And in your physicians controller:
public function phys_login() {
if ($this->request->is('post')) {
if ($this->Auth->login()) {
return $this->redirect($this->Auth->redirect());
} else {
$this->Session->setFlash(__('Username or password is incorrect'), 'default', array(), 'auth');
}
}
}
public function phys_logout() {
$this->Session->setFlash('Successfully Logged Out');
$this->redirect($this->Auth->logout());
}
Like I said, that all code I hacked together pretty quickly, so it might not work verbatim, but it should show the concept. Let me know if you have any questions.
Instead of
$this->Session->write('Auth.redirect','/physicians/index');
you should use
setcookie("keys", value);