A user should be able to set a rating for more than one book, it allows a user to set a rating for ONLY one book, but not any other.
The problem
the user is not able to make another rating for
another book. However, another user
can make a rating for the same book, but cant make another rating for
other books. Not even if the user logged out.
I'm using rateYo and laravel-ratable
The way i have it set up, a rating type is set to false by default, enabling a user to set stars and make a rating pretty much.
And once again, once a user makes a rating for any book doesn't matter which, the rating type is set to true which disables a user to set a star.
Here is what i have
here is how the html setup is like
HTML
<div id="rateYo" data-rateyo-rating="{{ $book->userSumRating or 0}}" data-rateyo-read-only="{{ $book->rated ? 'true' : 'false' }}"></div`>
BookController.php
public function rate(Request $request, $book_id)
{
$book = Book::find($book_id);
$rating = $book->ratings()->where('user_id', auth()->user()->id)->first();
if(is_null($rating)){
$ratings = new Rating();
$ratings->rating = $request['rating'];
$ratings->user_id = auth()->user()->id;
$ratings->type = Book::RATED;
$book->ratings()->save($ratings);
return json_encode($book);
}
else{
return response()->json(['status' => 'You already left a review']);
}
}
public function show($book_name)
{
$books = Book::GetBook($book_name)->get();
$data = $books->map(function(Book $book){
$book['rated'] = $book->type;
return $book;
});
return view('books.show', compact('data', $data));
}
Book.php(relevant code) On default if a rating has not been set, type is equal to false enabling the user to make a rating, if a rating is set, type is equal to true disabling the star feature/read mode.
use Rateable;
const RATED = "true";
const NOT_RATED = "false";
protected $fillable = [ 'user_id', 'title', 'description'];
protected $appends = ['rated'];
public function getRatedAttribute()
{
return Rate::where('type', $this->owl() )->where('user_id', auth()->user()->id )->first();
}
public function owl(){
foreach($this->rate as $rates){
if ($rates->type != "true"){
return self::NOT_RATED;
}
}
return self::RATED;
}
public function rate()
{
return $this->hasMany('App\Rate', 'type', 'user_id');
}
You need to add the book id in the where clause to check if the user has left a rating for that particular book. As you have it it will find the first rating a user has left for any book.
$constraints = [
'user_id' => auth()->id(),
'book_id' => $book_id
];
// checks if the user has not left a rating for this particular book
if ($book->ratings()->where($constraints)->doesntExist()) {
// create new rating...
} else {
// user has already rated this book...
}
i need to change my Book model to the following, thank you digital for steering me into the right direction
Book.php
public function getRatedAttribute()
{
$this->constraints = [
'user_id' => auth()->id()
];
if(Rating::where($this->constraints)->exists()){
return Rating::where(['type' => self::RATED, 'book_id' => $this->getKey()])->first();
}else{
return Rating::where('type' ,self::NOT_RATED)->first();
}
}
Related
I am working on a Register and Login application with CodeIgniter 3 and Bootstrap.
In my "users" table I have an "active" column that can take either 0 or 1 as value.
I want to be able to change the value of the "active" column corresponding to a user from 0 to 1 (activate the user) by clicking a link in my users view:
The "Activate" button code in the users view:
<span class="glyphicon glyphicon-ok"></span> Enable
Still in the users view every table row has the id of the user:
<tr id="<?php echo $user->id ?>">
In my Usermodel model I have:
public function activateUser($user_id) {
$query = $this->db->get_where('users', ['id' => $user_id]);
return $query->row();
}
In my User controller I have:
public function activate($user_id) {
$this->load->model('Usermodel');
$user = $this->Usermodel->activateUser($user_id);
if ($user->active == 0) {
echo 'activate user';
} else {
echo 'user already active';
}
}
The url users/activate/1 returns "user already active" , while users/activate/2 returns "activate user", as expected. Being new to Codeigniter, I have tried numerous versions of the code above that resulted in errors:
public function activateUser($user_id) {
$query = $this->db->get_where('users', ['id' => $user_id])->update('users', $data);
return $query->row();
}
is one of those versions resulting in errors.
Can you please tell me what shall I change in the code to make work as desired?
If I understand correctly, activateUser should update the database row for that user and then return all updated user information. You are trying to mash two queries together that should be separate. Just take it in two steps:
public function activateUser($user_id) {
$user = null;
$updateQuery = $this->db->where('id', $user_id)->update('users', ['active' => 1]);
if ($updateQuery !== false) {
$userQuery = $this->db->get_where('users', ['id' => $user_id]);
$user = $userQuery->row();
}
return $user;
}
I put in a little bit of error checking; if for instance the user id was not valid this will return null.
Based on that error checking, your controller code might look something like:
public function activate($user_id) {
$this->load->model('Usermodel');
$user = $this->Usermodel->activateUser($user_id);
// $user->active will always be 1 here, unless there was an error
if (is_null($user) {
echo 'error activating user - check user id';
} else {
// I was assuming you would want to do something with the user object,
// but if not, you can simply return a success message.
echo 'user is now active';
}
}
In the CakePHP 3 Blog Tutorial, users are conditionally authorized to use actions like edit and delete based on ownership with the following code:
public function isAuthorized($user)
{
// All registered users can add articles
if ($this->request->getParam('action') === 'add') {
return true;
}
// The owner of an article can edit and delete it
if (in_array($this->request->getParam('action'), ['edit', 'delete'])) {
$articleId = (int)$this->request->getParam('pass.0');
if ($this->Articles->isOwnedBy($articleId, $user['id'])) {
return true;
}
}
return parent::isAuthorized($user);
}
public function isOwnedBy($articleId, $userId)
{
return $this->exists(['id' => $articleId, 'user_id' => $userId]);
}
I've been attempting to implement something similar for my own tables. For example, I have a Payments table, which is linked to Users through several different tables as follows:
Users->Customers->Bookings->Payments.
Foreign keys for each:
user_id in Customers table = Users->id (User hasOne Customer)
customer_id in Bookings table = Customers->id (Customer hasMany Bookings)
booking_id in Payments table = Bookings->id(Booking hasMany Payments)
My AppController's initialize function:
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
$this->loadComponent('Flash');
$this->loadComponent('Auth',[
'authorize' => 'Controller',
]);
$this->Auth->allow(['display']); //primarily for PagesController, all other actions across the various controllers deny access by default
}
In my PaymentsController, I have the following
public function initialize()
{
parent::initialize();
}
public function isAuthorized($user)
{
if (in_array($this->request->action,['view', 'edit', 'index', 'add']
return (bool)($user['role_id'] === 1); //admin functions
}
if (in_array($this->request->action,['cart'])) {
return (bool)($user['role_id'] === 2) //customer function
}
if (in_array($this->request->action, ['cart'])) {
$bookingId = (int)$this->request->getParam('pass.0');
if ($this->Payments->isOwnedBy($bookingId, $user['id'])) {
return true;
}
}
return parent::isAuthorized($user);
}
public function isOwnedBy($bookingId, $userId)
{
return $this->exists(['id' => $bookingId, 'user_id' => $userId]);
}
I'm unsure as to how to link through the different tables to determine ownership.
Currently if a customer who is paying for Booking #123 could just change the URL so they are paying for Booking #111, provided that Booking exists in the database.
Additionally, the Booking ID is passed to the Cart function (since customers are paying for a specific booking). For example: If customer is paying for Booking #123, then the URL = localhost/project/payments/cart/123. Upon submitting their cart, a new Payment entry is created.
Also, regarding the getParam and isOwnedBy methods, hovering over them in my editor shows this:
Method 'getParam' not found in \Cake\Network\Request
Method 'isOwnedBy' not found in App\Model\Table\PaymentsTable
However, I've gone through the entire BlogTutorial and can't find anywhere else that getParam or isOwnedBy is used or set up in the Model.
In the IsAuthorized function in PaymentsController:
if (in_array($this->request->action, ['cart'])) {
$id = $this->request->getParam('pass'); //use $this->request->param('pass') for CakePHP 3.3.x and below.
$booking = $this->Payments->Bookings->get($id,[
'contain' => ['Artists']
]);
if ($booking->artist->user_id == $user['id']) {
return true;
}
}
I have two models Session and SessionTicketType as below. I want to to display SessionTicketType error in Session model on validation.
model Session.php
public function relations() {
$relations['sessionTicketTypes'] = [
self::HAS_MANY,
'SessionTicketType',
'session_id',
'index' => 'ticket_type_id'
];
return $relations;
}
And
model SessionTicketType.php
public function relations() {
$relations['session'] = [self::BELONGS_TO, 'Session', 'session_id'];
return $relations;
}
public function rules(){
$rules = parent::rules();
$rules[] = ['selected', 'safe'];
$rules[] = ['slots', 'validateSlots'];
return $rules;
}
public function validateSlots($attribute,$params) {
if($this->slots < $this->getQuantitySold()){
$this->addError('slots', 'Slots can not be less than total booking.');
}
public function getQuantitySold()
{
$sql = "SELECT SUM(i.quantity)
FROM booking b
INNER JOIN booking_item i ON b.id = i.booking_id
WHERE b.session_id = :session_id
AND i.ticket_type_id = :ticket_type_id
AND b.status IN (:status_completed)";
return Yii::app()->db->createCommand($sql)->queryScalar([
':session_id' => $this->session_id,
':ticket_type_id' => $this->ticket_type_id,
':status_completed' => BookingStatus::STATUS_CONFIRMED,
]);
}
I have Session form in which multiple sessionTicketype added.
I want to add this error (validateSlots()) on session validation.
Validation code validateSlots() in sessionTicketType is not displaying any error on session update and save data.
How can i display error and restrict saving .
Or is there any other way where i can add this validation in session model
I have table which have multiple reference to ohter tables like
user
id name email
categories
id title
user_categories
user_id category_id
Here a user will have multiple category associated with him/her
I am able to save these successfully with new records like following
View File:
echo $form->field($package_categories, 'category_id')->dropDownList( ArrayHelper::map(
StudyMaterialCategories::find()->all(), 'id', 'title'),
['multiple' => true]
);
Save New record:
$model = new Packages();
$package_categories = new PackageCategories();
$request = Yii::$app->request;
if ($request->isPost) {
$transaction = Yii::$app->db->beginTransaction();
try {
$post = $request->post();
$model->load($post);
$model->save();
foreach ($post['PackageCategories']['category_id'] as $key => $value) {
$package_categories = new PackageCategories();
$package_categories->category_id = $value;
$package_categories->package_id = $model->id;
$package_categories->save();
}
$transaction->commit();
return $this->redirect(['view', 'id' => $model->id]);
} catch (Exception $ex) {
$transaction->rolback();
Yii::$app->session->setFlash("error", $ex->getMessage());
}
}
Till now It's running successfully.
But I'm stuck when going to update the table. The problem part is dropdown list. How to set multiple selected option as per database if I'm coming with array of object.
Have a look on the following code
$package_categories = PackageCategories::find()
->where('package_id=:package_id', ['package_id' => $id])->all();
if (count($package_categories) < 1) {
$package_categories = new PackageCategories();
}
$request = Yii::$app->request;
if ($request->isPost) {
$transaction = Yii::$app->db->beginTransaction();
try {
$post = $request->post();
$model->load($post);
$model->save();
$package_categories = new PackageCategories();
$package_categories->deleteAll(
"package_id=:package_id",
[':package_id' => $model->id]
);
foreach ($post['PackageCategories']['category_id'] as $key => $value) {
$package_categories = new PackageCategories();
$package_categories->category_id = $value;
$package_categories->package_id = $model->id;
$package_categories->save();
}
$transaction->commit();
return $this->redirect(['view', 'id' => $model->id]);
} catch (Exception $ex) {
$transaction->rolback();
Yii::$app->session->setFlash("error", $ex->getMessage());
}
}
if I try to get first object of the array $package_categories of only able to set selected one option
This is an example code of a model class Permit which has a many to many relationship with Activity through PermitActivity (pivot table model).
Model Class Activity
public class Permit extends \yii\db\ActiveRecord {
public $activities_ids;
...
public function rules() {
return [
...
[['activities_ids'], 'safe'],
...
];
}
...
// Method called after record is saved, be it insert or update.
public function afterSave($insert, $changedAttributes) {
// If this is not a new record, unlink all records related through relationship 'activities'
if(!$this->isNewRecord) {
// We unlink all related records from the 'activities' relationship.
$this->unlinkAll('activities', true);
// NOTE: because this is a many to many relationship, we send 'true' as second parameter
// so the records in the pivot table are deleted. However on a one to many relationship
// if we send true, this method will delete the records on the related table. Because of this,
// send false on one to many relationships if you don't want the related records deleted.
}
foreach($this->activities_ids as $activity_id) {
// Find and link every model from the array of ids we got from the user.
$activity = Activity::findOne($activity_id);
$this->link('activities', $activity);
}
parent::afterSave($insert, $changedAttributes);
}
...
// Declare relationship with Activity through the pivot table permitActivity
public function getActivities(){
return $this->hasMany(Activitiy::className(), ['id' => 'activity_id'])
->viaTable('permitActivity',['permit_id' => 'id']);
}
...
public function afterFind(){
parent::afterFind();
$this->activities_id = ArrayHelper::getColumn($this->activities, 'id');
}
}
This way the model class is the one responsible for creating and updating the relationship using the pivot table.
The most important thing is to have the relationship method declared correctly.
Edit
This is an example of the view using kartikv\widgets\Select2. I don't really know if dropDownList supports multiple select, however Select2 has so many useful features i usually use it over other options.
echo $form->field($model, 'activities')->widget(Select2::classname(), [
'data' => $data,
'options' => [
'placeholder' => '...'
],
'pluginOptions' => [
'allowClear' => true,
'multiple' => true,
],
]);
I am trying to send the latest user's id from UsersController to AdminController whose add_employee() action creates a new employee. My users and employees table are separate and what I want to do is when Admin creates a new user its entry go into users table. Then he opens create employee form and the latest user id will be assigned to the new employee the admin is creating. So when admin will open create new employee form the latest user id will be shown in the form.
My UsersController has this code for sending latest user it to AdminsController:
public function get_latest_user_id()
{
$content = $this->User->query("SELECT id FROM users ORDER BY id DESC LIMIT 0,1");
$this->set('latest_user', $content);
}
AdminsController page's add_employee contains this code:
public function add_employee()
{
$this->loadModel('Employee');
$this->set('latest_user', $this->requestAction('/Users/get_latest_user_id'));
if ($this->request->is('post'))
{
$this->Employee->create();
if ($this->Employee->save($this->request->data))
{
$this->Session->setFlash(__('The employee profile has been saved.'));
return $this->redirect(array('action' => 'list_of_employees'));
}
else
{
$this->Session->setFlash(__('The employee profile could not be saved. Please, try again.'));
}
}
}
So UserController's get_latest_user_id function sends latest user id to add_employee function of AdminController. There latest_user is set to latest user id so that when add_employee view is called it is there. But it is not showing. So I want to know that am i doing it right? Please help and thanks.
In add_employee.ctp I am displaying it like this:
echo $latest_user['User']['id'];
Move get_latest_user_id to the User model
public function get_latest_user_id()
{
$user = $this->query("SELECT id FROM users ORDER BY id DESC LIMIT 1");
if (empty($user)) {
return 0;
}
// return only the Id
return $user[0]['users']['id'];
}
In the controller:
public function add_employee()
{
$this->loadModel('Employee');
$this->loadModel('User');
$this->set('latest_user', $this->User->get_latest_user_id());
if ($this->request->is('post'))
{
// ....
}
}
cornelb is right that you should move the method to your User model. Although a more Cake-ish approach would be to use a find('first'), rather than doing a direct query():
// app/Model/User.php
public function getLatest() {
// Get the latest user
$user = $this->find('first', array(
'fields' => array('id'), // Only interested in id? Use this.
'order' => array('User.id' => 'DESC')
));
if (!empty($user)) {
return $user['User']['id'];
} else {
// Nothing was returned, this is very awkward
throw new NotFoundException(__('No users found!'));
}
}
And in your controller:
// app/Controller/AdminsController.php
public function add_employee() {
$this->loadModel('User');
$this->set('latestUser', $this->User->getLatest());
// ...
}