I am just wondering if anyone can get me started in writing a private messaging system on the CakePHP framework. I am aiming for something similar to the Facebook inbox system. Of course it doesnt have to be as complicated!
I currently have a AUTH system with users whom can log on and off.
The simplest way would be to just create a messages database table with at the very least, five columns: id, sender_id, recipient_id, subject, body. You can then also add other columns you desire, such as created.
You can then set up your controller as follows:
<?php
class MessagesController extends AppController {
public function inbox() {
$messages = $this->Message->find('all', array(
'conditions' => array(
'recipient_id' => $this->Auth->user('id')
)
));
}
public function outbox() {
$messages = $this->Message->find('all', array(
'conditions' => array(
'sender_id' => $this->Auth->user('id')
)
));
}
public function compose() {
if ($this->request->is('post')) {
$this->request->data['Message']['sender_id'] = $this->Auth->user('id');
if ($this->Message->save($this->request->data)) {
$this->Session->setFlash('Message successfully sent.');
$this->redirect(array('action' => 'outbox'));
}
}
}
}
Obviously you'll need to flesh this example out, and change anything that may not apply to your application. You'll also need to add in checks for if the user is friends with the person they're trying to message if you want that as well.
Related
I came accross curious problem. Lets say we want to validate some id. Validation should pass 10 different conditions(constraints) and we have to do it in 10 different places. I thought I can save myself writing unnessecary code by nesting one validation in another.
Here's what I did:
I've created new, custom validation constraint called IdParameter
I've registered IdParameterValidator.php file as service and injected validator service to it
I've put there another validation process(in our example 10 constraints which I will have to use in 10 different places) - I used Constraints\Collection to do it, so it looks kinda like this:
<?php
namespace Awesome\BlogBundle\Validator\Constraints;
use Symfony\Component\Validator;
class IdParameterValidator extends Validator\ConstraintValidator
{
private $_data = array();
private $_validator;
public function __construct(Validator\Validator\RecursiveValidator $validator)
{
$this->_validator = $validator;
}
public function validate($value, Validator\Constraint $constraint)
{
/* Preparing object of constraints */
$postIDConstraints = new Validator\Constraints\Collection(array(
'postId' => array(
new Validator\Constraints\Type(array(
'type' => 'integer',
'message' => 'This ain\'t no integer man!'
)),
new Validator\Constraints\Range(array(
'min' => 1,
'minMessage' => 'Post id is not valid'
))
)
));
/* Validating ID */
$this->_data['errors'] = $this->_validator->validate(array('postId' => $value), $postIDConstraints);
/* Checking validation result */
if(count($this->_data['errors']) > 0) {
$this->context->buildViolation($constraint->message)->addViolation();
}
}
}
So now I can use as many constraint as I whish and still have a clean service code:
$postIDConstraints = new Validator\Constraints\Collection(array(
'postId' => array(
new myValidator\Constraints\IdParameter()
)
));
/* Validating postID */
$this->_data['errors'] = $this->_validator->validate(array('postId' => (int)$postID), $postIDConstraints);
I'm wondering if it's correct approach?
Best Regards,
R.
P.S
I always comment my code - I didn't put comments here to keep it clean.
I am working on my first Zend Framework 2 Project. I needed a User Module and integrated ZfcUser for this. Because I have a slight difference in my User Table, I had to use my own User Entity and User Mapper. I created a new Module called ZfcUserExtension.
I then copied a lot of files from the original ZfcUSer Module like:
Entity/User.php
Entity/UserInterface.php
Factory/Entity/IndexControllerFactory.php
Factory/Mapper/UserHydratorFactory.php
Mapper/Exeption/ExceptionInterface
Mapper/Exeption/InvalidArgumentException.php
Mapper/Exeption/RuntimeException.php Mapper/HydratorInterface.php
Mapper/User.php Mapper/UserHydrator.php Mapper/UserHydrator.php
Mapper/UserInterface.php
In zfcuser.global.php I set the user_entity_class to use my own Entity.
'user_entity_class' => 'ZfcUserExtension\Entity\User',
In the module.config.php from the ZfcUserExtension I add the below to make sure that I use my own User Mapper and UserHydrator. The reason for that was that I use "id" as a Primary Key in my User table instead of "user_id", so I had to make sure that this gets overwritten as well.
<?php
return array(
'controllers' => array(
'factories' => array(
'ZfcUserExtension\Controller\Index' => function(Zend\Mvc \Controller\ControllerManager $cm) {
$sm = $cm->getServiceLocator();
return new \ZfcUserExtension\Controller\IndexController(
$sm->get("doctrine.entitymanager.orm_default")
);
}
),
),
'service_manager' => array(
'factories' => array(
'zfcuser_user_mapper' => function ($sm) {
$options = $sm->get('zfcuser_module_options');
$mapper = new \ZfcUserExtension\Mapper\User();
// No db adapter present add below line
$mapper->setDbAdapter($sm->get('zfcuser_zend_db_adapter'));
$entityClass = $options->getUserEntityClass();
// No entity prototype set add below line
$mapper->setEntityPrototype(new $entityClass);
$mapper->setHydrator($sm->get('zfcuser_user_hydrator'));
$mapper->setTableName($options->getTableName());
return $mapper;
},
// 'zfcuserextension_change_password_form' => 'ZfcUserExtension\Factory\Form\ChangePhoneFormFactory',
),
),
I finally got all this to work, till I now run into another problem. I want some additional fields for the User like Phone Number. How would I approach this? I know there are some ideas on the Internet, but I am mainly interested to know how I would actually offer the option to have a "Change Phone" Form. I have created a Form, similar to the "Change Password and "Change Email". I have then created a IndexController.php in my ZfcUSerExtension, again followed the set-up of the UserController from the ZfcUser Module
class IndexController extends AbstractActionController {
const ROUTE_LOGIN = 'zfcuser/login';
/**
* #var \Doctrine\ORM\EntityManager
*/
protected $em;
public function __construct(\Doctrine\ORM\EntityManager $em)
{
$this->em = $em;
}
/**
* #var Form
*/
protected $changeEmailForm;
public function indexAction() {
if (!$this->zfcUserAuthentication()->hasIdentity()) {
return $this->redirect()->toRoute(static::ROUTE_LOGIN);
}
return new ViewModel();
}
public function changephoneAction() {
// if the user isn't logged in, we can't change phone
if (!$this->zfcUserAuthentication()->hasIdentity()) {
return $this->redirect()->toRoute(static::ROUTE_LOGIN);
}
$form = $this->getChangePhoneForm();
$request = $this->getRequest();
$request->getPost()->set('PrevPhone', $this->getUserService()->getAuthService()->getIdentity()->getPrevPhone());
return array(
'status' => false,
'changePhoneForm' => $form,
);
$fm = $this->flashMessenger()->setNamespace('change-phone')->getMessages();
if (isset($fm[0])) {
$status = $fm[0];
} else {
$status = null;
}
$prg = $this->prg(static::ROUTE_LOGIN);
if ($prg instanceof Response) {
return $prg;
} elseif ($prg === false) {
return array(
'status' => $status,
'changePhoneForm' => $form,
);
}
$form->setData($prg);
if (!$form->isValid()) {
return array(
'status' => false,
'changePhoneForm' => $form,
);
}
$change = $this->getUserService()->changeEmail($prg);
if (!$change) {
$this->flashMessenger()->setNamespace('change-email')->addMessage(false);
return array(
'status' => false,
'changeEmailForm' => $form,
);
}
$this->flashMessenger()->setNamespace('change-email')->addMessage(true);
return $this->redirect()->toRoute(static::ROUTE_CHANGEEMAIL);
}
public function getChangePhoneForm()
{
$sl = $this->getServiceLocator();
$this->setChangePhoneForm($sl->get('zfcuserextension_change_phone_form'));
return $this->changePhoneForm;
}
public function setChangePhoneForm($changePhoneForm)
{
$this->changePhoneForm = $changePhoneForm;
return $this;
}
I now noticed that I will face a problem with the User Service Service/User.php. The Service offers a changePassword() and changeEmail() Method. I now thought that I need to copy this file into my own Modules. Am I right that if I extend the User Service from ZfcUser then the Methods changePassword() and changeEmail() will still be available, so I would delete it from the just copied file and just add changePhone()?
And if I am right with my thoughts, the User Service currently starts like this:
class User extends EventProvider implements ServiceManagerAwareInterface
How would I have to change it that I extend the original User Service? I hope somebody can help, I am still rather confused with all this. Thanky you very much in advance.
There are two possible methods:
Build custom classes extending ZfcUser's entity, form and input filter and add your custom fields. In the ZfcUser configuration change aliases or override factories to ensure your custom classes are instantiated rather than the built in ones.
If you are OK with having the custom profile fields stored and accessed separately from the ZfcUser user entity, check out my module on GitHub: LdcUserProfile. It provides a profile system for ZfcUser but also makes it easy to add your own custom profile fieldsets linked to a user.
I have a couple of different 'items' on my website that I am building with cakePHP, for instance a Recipe and a ShoppingList.
I want certain items in my view (e.g. update and delete functionality links) to only be visible to the person who uploaded that item.
I want to add a function that would compare any given id to the currently logged in user's id. It would look something like this:
public function compareUser($id){
if(!empty($this->userInfo) && $this->userInfo['User']['id'] == $id){
return true;
}
}
$this->userInfo is set in beforeFilter:
$this->userInfo = $this->User->find('first', array('conditions' => array('id' => $this->Auth->user('id'))));
I have tried putting it in my appController, but that doesn't seem to work.
How can I implement this properly? Thanks!
This is best done using the isAuthorized($user) method.
All the information about your current user is stored in $this->Session->read('Auth.User') (this retrieves the full array, if you just wanted to get their 'id' you use $this->Auth->user('id') as you already did).
From the above it should hopefully be clear that normally you don't need to retrieve the user's details through an extra query as they are already stored in the Auth component of the session :)
Make sure in the setup for your Auth component you have 'authorize' => 'controller' and add the following to your AppController:
public function isAuthorized($user) {
//I want the default to be allow the user access so I will return true
return TRUE;
}
Then add the following to your RecipesController (and ShoppingListsController if you want the same thing there):
public function isAuthorized($user) {
if ($this->action === 'update' || $this->action === 'delete') {
$recipe = $this->Recipe->find(
'first',
'conditions' => array(
'id' => $this->params['pass'][0]
)
'fields' => array(
'user_id'
)
);
if ($this->Auth->user('id') == $recipe['Recipe']['user_id']) {
return TRUE;
}
else {
return FALSE;
}
}
return parent::isAuthorized($user);
}
Now if someone tries to access www.yourDomain.com/recipes/update/2 or www.yourDomain.com/recipes/delete/2 it will check if the current user's id is 2, if it is you're good to go, if not then it blocks them from that page.
Edit:
Easiest way to have a method accessible from all places I would suggest putting it in the AppModel that way all your models will inherit it:
//inside AppModel
public function isOwnedBy($id) {
if (AuthComponent::user('id) == $id) {
return TRUE;
}
return FALSE;
}
I am creating a basic CMS to teach myself the fundamentals of Laravel and PHP.
I have a 'pages' table and I am storing a url_title. I want this URL title to be unique for obvious reasons. However, whatever I do to validate it, fails. It just saves anyway. I'm sure it is something simple. Can you spot what is wrong with this code?
I am also using Former in the view, that doesn't validate either. I have tried hard-coding a value as the last option in the unique method and it fails also.
http://anahkiasen.github.io/former/
http://laravel.com/docs/validation#rule-unique
States: unique:table,column,except,idColumn
Here is my Controller:
public function store()
{
$validation = Pages::validate(Input::all());
if($validation->fails()) {
Former::withErrors($validation);
return View::make('myview');
} else {
Pages::create(array(
'title' => Input::get('title'),
'url_title' => Input::get('url_title'),
'status' => Input::get('status'),
'body' => Input::get('body'),
'seo_title' => Input::get('seo_title'),
'seo_description' => Input::get('seo_description')
));
//check which submit was clicked on
if(Input::get('save')) {
return Redirect::route('admin_pages')->with('message', 'Woo-hoo! page was created successfully!')->with('message_status', 'success');
}
elseif(Input::get('continue')) {
$id = $page->id;
return Redirect::route('admin_pages_edit', $id)->with('message', 'Woo-hoo! page was created successfully!')->with('message_status', 'success');
}
}
}
Here is my model:
class Pages extends Eloquent {
protected $guarded = array('id');
public static $rules = array(
'id' => 'unique:pages,url_title,{{$id}}'
);
public static function validate($data) {
return Validator::make($data, static::$rules);
}
}
I have tried the following:
public static $rules = array(
// 'id'=> 'unique:pages,url_title,{{$id}}'
// 'id'=> 'unique:pages,url_title,$id'
// 'id'=> 'unique:pages,url_title,:id'
// 'id'=> 'unique:pages,url_title,'. {{$id}}
// 'id'=> 'unique:pages,url_title,'. $id
);
Any ideas? I spoke to the guy who created Former. He can't make head nor tail about it either. He suggested tracking it back to find our what query Laravel uses to check the uniqueness and try running that directly in my DB to see what happens. I can't find the query to do this. Does anyone know where to track it down?
Many thanks
Your rule should be:
public static $rules = array(
'url_title' => 'unique:pages,url_title,{{$id}}'
);
As I guessed from your code Input::get('url_title')
You have to use the field name used in the form.
Thanks peeps. I have been using the Laravel unique solutions and it hasn't been working well. I found this package which solves the issue brilliantly.
https://github.com/cviebrock/eloquent-sluggable#eloquent
Definitely worth a look.
Thanks for your feedback.
I'm attempting to build an achievements system in my CakePHP app using CakeEvents. I've been using the following website for helping me put the Events together: http://martinbean.co.uk/blog/2013/11/22/getting-to-grips-with-cakephps-events-system/
In my app, achievements are called badges and can be awarded to users. These badges are awarded based on rules that link the badge to a Cake event.
So for example if a user creates a post, that will fire the Model.Post.add event which should check if any rules exist for that event, and if so do the parameters match up, and again if all checks out then award a badge connected to that rule to the user.
The schema comprises of the following tables:
users
id
username
password
posts
id
title
content
created
modified
user_id
badges
id
title
description
events
id
title
name
event_badges
id
event_id
badge_id
badge_users
id
badge_id
user_id
Hopefully that all makes sense. And here are the Models to show how they connect.
User.php
class User extends AppModel
{
public $name = 'User';
public $hasMany = array(
'Post', 'Badge'
);
public $belongsTo = array(
'BadgeUser'
);
}
Badge.php
class Badge extends AppModel {
public $hasMany = array(
'BadgeUser'
);
public $actsAs = array('Containable');
}
Event.php
class Event extends AppModel {
public $hasMany = array(
'EventBadge'
);
public $actsAs = array('Containable');
}
EventBadge.php
class ActionBadge extends AppModel {
public $belongsTo = array(
'Action', 'Badge'
);
public $actsAs = array('Containable');
}
BadgeUser.php
class BadgeUser extends AppModel {
public $belongsTo = array(
'Badge', 'User'
);
public $actsAs = array('Containable');
}
** Feel free to comment if you think the schema is incorrect to achieve what is being described in this question.**
So example badges might be:
Title: First Post, Description: Awarded for creating a post
Title: 10 Posts, Description: Awarded for creating 10 posts
Title: Editor, Description: Editing a post
Title: Deleter, Description: Deleting a post
And some example rules in the Events table:
Title: Add Post, Event Model.Post.add
Title: Edit Post, Event: Model.Post.edit
Title: Delete Post, Event: Model.Post.delete
Title: View Post, Event: Model.Post.view
So as you can see the Events are linked to the above Badges and the Events are called using the CakeEvents system.
Okay so when a person does something, let's say saves a new post, I have the following in the Post model:
public function afterSave($created, $options = array()) {
if ($created) {
$event = new CakeEvent('Model.Post.add', $this, array(
'id' => $this->id,
'data' => $this->data[$this->alias]
));
$this->getEventManager()->dispatch($event);
}
}
The first question is, how do I pass different events to the afterSave? As both Add and Edit methods in the controller would fire this...
And then I have a PostListener.php file in /app/Event
class PostListener implements CakeEventListener {
public function implementedEvents() {
return array(
'Model.Post.add' => 'postAdded',
'Model.Post.edit' => 'postEdited',
'Model.Post.view' => 'postViewed',
'Model.Post.delete' => 'postDeleted',
);
}
public function postAdded(CakeEvent $event) {
$this->Badge->awardBadge($badgeId, $userId);
}
public function postEdited(CakeEvent $event) {
}
public function postViewed(CakeEvent $event) {
}
public function postDeleted(CakeEvent $event) {
}
}
So the next question is how do I link the event listener back up to my Events table?
And then award the badge connected to that action? Noting that some will need to do extra checks like a user must of created 10 posts to achieve the 10 Posts badged and not just because they have created a post.
In the Badge.php model I have the following function to award badges:
public function awardBadge($badgeId, $userId) {
$controlFind = $this->BadgeUser->find(
'first',
array(
'conditions' => array(
'badge_id' => $badgeId,
'user_id' => $userId,
)
)
);
if(!$controlFind) {
$temp = array(
'BadgeUser' => array(
'badge_id' => $badgeId,
'user_id' => $userId,
'created' => date('Y-m-d H:i:s')
)
);
$collection[] = $temp;
return $this->BadgeUser->saveAll($collection, array('validate' => false));
}
}
So I need to run the code above from the listener when things match up with the DB rules. Just struggling to make it all stick together.
I think your database schema may be complicating things by having a notion of events. Personally, I’d have a badges table that stores badge title and description (and also image path if it’s needed). I’d then have an event listener that corresponds to each badge, so there will be some degree of manual labour involved.
So let’s think of a sample scenario. Let’s say a badge is awarded when a user posts a 100 times. So you’d have your Post model that fires an event when a post is saved:
<?php
App::uses('CakeEvent', 'Event');
class Post extends AppModel {
public function afterSave($created, $options = array()) {
$event = new CakeEvent('Model.User.afterSave', $this);
$this->getEventManager()->dispatch($event);
}
}
You can then create a corresponding handler:
<?php
// Event/BadgeListener.php
App::uses('CakeEventListener', 'Event');
App::uses('ClassRegistry', 'Utility');
class BadgeListener implements CakeEventListener {
public function implementedEvents() {
return array(
'Model.Post.afterSave' => 'afterSaveListener'
);
}
public function afterSaveListener(CakeEvent $event) {
// check number of posts
$this->Post = ClassRegistry::init('Post');
$count = $this->Post->find('count', array(
'Post.author_id' => $event->subject()->data[$event->subject()->alias]['author_id'];
));
// award badge
if ($count > 100) {
// TODO: check user does not already have badge
$this->BadgeUser = ClassRegistry::init('BadgeUser');
$this->BadgeUser->create();
$this->BadgeUser->set('badge_id', 'whatever_badge_id_actually_is');
$this->BadgeUser->set('user_id', $this->Auth->user('id')); // logged in user ID
$this->BadgeUser->save();
}
}
}
This is a rough example written off-the-cuff, but hopefully it should steer you in the right direction.
If there’s anything you want me to clear up, let me know and I’ll do my best.
The first question - differentiate between new post and edit
To differentiate between add and edit you need to pass along the $created parameter when you create your CakeEvent. Something like this:
public function afterSave($created, $options = array()) {
//NOTICE: it's afterSave now, since this will be called after edit or new
$event = new CakeEvent('Model.Post.afterSave', $this, array(
'created' => $created, //NOW you will know when you process the even if this was a new post or post edit
'id' => $this->id,
'data' => $this->data[$this->alias]
));
$this->getEventManager()->dispatch($event);
}
You should add to the data of your event all the data that you will need later on to process it and to match your rules. You can even add the model object itself, if you think you will need that to do some queries with it.
Second Question - how to link event processing to the Events table
In the Event listener you can use the ClassRegistry to get an instance of the Event model (and any other models you require). You can do your rules matching and once you have all the data you need you save the badge to the database.
Since you now know if it's an edit or a new post (from the event->data) you can take appropriate action.
The editor id I guess you can take it with the Auth component as being the logged in user. So when you have that you can use it to read what you need about the editor from the database.
To conclude: when you send your events, use the event data to pass all the information that you will need in the listener. Event can carry so much more information, not just their names and the fact that they were triggered. You can send and then look at the entire "context".
In the listener, make good use of the data you get from the event and of the currently logged in user ID or the post author, do the matching that you need to do and then save the badge for the appropriate user.
Hopefully this is what you were looking for :).