Please forgive me, I am still fairly new to cakephp and in the process of reviving my redundant PhP skills.
I have searched all over the "interweb" for the answer, sadly to no avail.
Here is the scenario:
I have 2 tables - 1 being Users the other Groups ( for permissions).
Users table - pretty standard plus some additional profile info.
Groups table - holds Administrators, Super Users, Auth Users and Unauth Users.
The Users table is being used for authentication and profiles (other users can browse user profiles). Now with this in mind, I cannot work out how to filter out which users get rendered into the index view based on which group the users (not the the currently logged user) belong.
For example, The admin and Super user accounts profiles are being rendered and belongs to the "Administrators" and "Super users" groups respectively along with all the other Users. However all I want end users (Auth Users) to be able to see are other user profiles which are part of the Auth Users group.
So any and all help would be appreciated for both saving the remainder of my hair and finding a resolve to this problem I have.
Ok so here is what I did which is working like a charm. Configured the paginate variable to look like this:
var $paginate = array(
'fields' => array(
'User.img_file', 'User.given_name', 'User.family_name', 'Group.name', 'User.username', 'User.created'),
'limit' => 10,
'order' => array(
'User.given_name' => 'asc'
)
);
Then for the in the index function (which I wanted) I added the following:
function index() {
$this->User->recursive = 0;
$this->paginate = array(
'conditions' => array(
'group_id' => 3
));
$users = $this->paginate('User');
$this->set(compact('users'));
}
It's working like I want it to, but if anything does look malformed, or this can be extended, please do post comments.
First off, take a look at this for information on using the built in authentication features of cakephp.
" I cannot work out how to filter out which users get rendered into the index view based on which group the users (not the the currently logged user) belong."
not 100% on what you mean by this, but if you mean you only want to see other users that are admins (and assuming you know that the admin_group_id == 1) then, what you kind of want is the following.
$administrator_group_id = 1;
$conditions = array(
'group_id' => "{$administrator_group_id}"
);
$this->data = $this->User->findAll($conditions);
// note. automatically makes the result data available in the VIEW layer
pr($this->data); // VARDUMP utility
(ps. check this out for how to paginate this result data)
"...all I want end users (Auth Users) to be able to see are other user profiles which are part of the Auth Users group"
(assuming the auth stuff didn't help you.)
(if there are only 3 types of group types, and I can control them completely, then I would consider not using a group table, and using an enumeration. here are notes on how to do this in MySQL
http://dev.mysql.com/doc/refman/5.0/en/enum.html
or, you can just sort of hack it in PHP.
eg.
define('USER', 1);
define('S_USER', 10);
define('ADMIN', 100);
$user = $this->User->findById($my_user_id); // or get from auth
$conditions = array(
"group_id <= " => "{$user['User']['group_id']}"
);
$this->data = $this->User->findAll($conditions);
pr($this->data); // VARDUMP
( what I did was that you get the logged in user, and I made sure that the ADMIN had the highest level ID. SuperUsers had the 2nd, and Users the lowest. This query will return all users that are on the same level, or lower than their own level. group_id <= User.group_id)
I hope I haven't confused you too much. Just keep on experimenting with cake. It's worth it!
Related
As a novice to laravel I am hoping I can get to the bottom of this rather irritating issue!
I have a small app that consists of 2 user types, Buyers and Providers, The usertype->buyers can request a free estimation for a service they require and the provider can create this manually or have an estimation sent automatically based on what they set as there default value for that specific price range so for example if a user(buyer) requests an estimation for a service that is £200 the provider may have set a default estimation of £180 - £220 for that price range and this is what will be shown to the user.
The problem I have is to make it fair we use the random function within laravel to randomly select 5 providers from the providers_table along with their default estimation and show this to the user from orderBy in descending order and looping through the results in the view.
$projectbids = ProjectBids::where('provider_id', '=', $proid)
->where('category_id', '=', $catid)
->orderBy('bid_price', 'desc')
->get()->random(5);
foreach($projectbids as $bid)
{{ $bid->bid_price }}
endforeach
So I'm trying to find a way to insert the 5 displayed estimations into a saved_estimates table so if the user refreshes the page or comes back to the page the model and view will not reorder the providers results based from the random(5) function that gets called.
I'm aware of this question: Create a Insert... Select statement in Laravel but wasn't to sure if there could be a better or easier way of doing this as my problem lays where I'm looping through the 5 results provided to the view from the table.
In a nutshell: I want to be able to store the 5 dynamic results straight into a saved_estimations table with several columns such as project_id, provider_id, user_id, estimated_price so the user can come back to their saved estimations in the future without them changing.
Very grateful for any input and advice, it will be very helpful to see what the more experienced users would suggest.
Thanks
-- Updated personal approach to see if there is room for improvement and to make it more laravel friendly.
$results = $bidders;
foreach($results as $row){
$project = $projectid;
$category = $row->category_id;
$provider = $row->service_providers_id;
$bidprice = $row->bid_price;
$propvalue = $row->property_value_id;
DB::table('static_bids')->insert(array(
'project_id' => $project,
'category_id' => $category,
'service_providers_id' => $provider,
'bid_price' => $bidprice,
'property_value_id' => $propvalue
));
}
All you need is to use simple insertions into the table, something like this:
foreach ($projectbids as $bid) {
$result = SavedEstimation::create(array(
'project_id' => $bid->project_id,
'provider_id'=> $bid->provider_id,
'user_id' => $bid->user_id,
'estimated_price' => $bid->estimated_price
));
}
There is no way to insert this by using just one query anyway. You could simply store output HTML if the only reason you want to do this is to show same HTML to the user later, but this is bad practice.
I am working on an application to store records for a youth group.
This youth group has a system of badges for members to achieve and I have the following tables;
members
badges
member_badges
The first two tables are self explanatory, members are the youth group members and the badges is the name of a badge, eg. FieldCraft, Life Saving, First Aid and so on.
The member_badges records the achievement of a member and will have the member_id, badge_id, date of attempt and status (Pass or Fail). We record failed attempts to as it will help us to gauge the effectiveness of a course - what's the pass rate and so on.
I want to create a table with the following data;
Badge | Number of Attempts | Pass | Fail
I've created a badgeController function as follow;
public function badgereportsummary() {
$paginate = array(
'contain' => array('MemberBadge'));
$this->Paginator->settings = $paginate;
$this->set('badges', $this->paginate());
}
Now this works in a fashion - it returns an array of Badges, each of which contains an array of the Member Badges and I've got a rough and ready page work with the following in the View
echo h(count($badge['MemberBadge']));
However - this won't allow me to sort on this field, and also in future I want to add the ability to limit on a date range e.g. "Show me the count of First Aid badges attempted from 1/1/15 through 30/3/15". I've seen some suggestions online about using counterCache (I've used that in other places on the app) but this won't allow me to work out count for date range etc.
What I think I want to to is add the count of the Member Badges on a particular page as a virtual field in the controller. I've been searching the docs and Google, and beating my head on the keyboard all day and can't work it out. Any suggestions on how to accomplishment this are greatly appreciated.
Your summary report (Badge | Number of Attempts | Pass | Fail) looks like it needs a SQL statement such as:
SELECT badge.description AS Badge
, COUNT(*) AS NumberOfAttempts
, SUM(IF(member_badges.status = 'Fail',0,1)) AS Fails
, SUM(IF(member_badges.status = 'Pass',0,1)) AS Passes
FROM member_badges
JOIN badges ON badges.id = member_badges.badge_id
WHERE member_badges.date_of_attempt BETWEEN '20140101' AND '20140131'
GROUP BY badge.description
ORDER BY NumberOfAttempts DESC;
I would then change your badgereportsummary method to generate this SQL, which is relatively straightforward.
Move the function from the Badge controller model to the MemberBadge model
Create virtual fields for the new column names within the method ->
$this->MemberBadge->virtualFields['NumberOfAttempts'] = 0;
$this->MemberBadge->virtualFields['Fails'] = 0;
$this->MemberBadge->virtualFields['Passes'] = 0;
Write the find:
Code Example
$fields = array('Badge.description'
,'COUNT(*) AS MemberBadge__NumberOfAttempts'
,'SUM(IF(MemberBadge.status = 'Fail',0,1)) AS MemberBadge__Fails'
,'SUM(IF(MemberBadge.status = 'Pass',0,1)) AS MemberBadge__Passes');
return $this->find('all'
,'conditions' => array('MemberBadge.date_of_attempt BETWEEN ? AND ?' => array($start_date,$end_date)
,'fields' => $fields
,'order' => array('NumberOfAttempts DESC')
,'group' => array('Badge.description'));
Then in any Badge controller method call $this->Badge->MemberBadge-> badgereportsummary($start_date, $end_date)
I would not use a permanent virtual field (i.e., defining the virtual field in the model) since this is an aggregate function. Also, I would not go with counterCache for your exact reason - needing to filter the results based on another field.
I am writting my first application with Yii (v1.1.12), and the learning curve is a bit steep for me, so I need some help.
Imagine the following tables (with their relations):
detail (n:1) document
document (n:1) user
user (n:1) department
document (n:1) category
user is the table that holds the information about the users that can login and use the application.
I have managed to put together (using Gii and hacking about) a view that lists all the documents, and have also managed to display the category name instead of the category ID in the grid.
One of the features I want to implement is allow the user switch the view so (a) only the documents relating to the logged in user are listed, or (b) only the documents relating to his/her department.
I looked around a bit with no luck. Can anyone help?
Cheers,
George
UPDATE: Currenlty I display the list of documents using zii.widgets.grid.CGridView.
UPDATE 2:
Following Omar's reference to CDbCriteria I found this URL with a bit more detail on the subject.
I came up with the following model code, that works fine:
public function searchByUser($user_id)
{
$criteria=new CDbCriteria;
$criteria->condition = " user_id = ".$user_id;
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
));
}
public function searchByDepartment($user_id)
{
$criteria=new CDbCriteria;
$criteria->alias="p";
$criteria->join = "JOIN (SELECT u.id
FROM user u
INNER JOIN user uu
ON u.department_id = uu.department_id
WHERE uu.id = ".$user_id.") uu
ON p.user_id = uu.id";
return new CActiveDataProvider($this, array('criteria'=>$criteria,));
}
While the above works as expected, I was hoping for a solution that would not require me to write chopped SQL code at all. Not due to lazyness, but just to leverage more of the functionality of the framework.
I just have the feeling that this approach doesn't follow best practices (?).
Try to create your own CDBCriteria and define whatever conditions inside it and pass it as data provider to your grid view.
If you allowed the search inside the grid view, pass the criteria to the search function, and inside it, merge the passed criteria with criteria inside the search.
You could use relations to achieve what you're after. For example, to view all the documents and departments of a certain user you first need to set up the relations for that user, in your case you could set your users model up like so;
public function relations()
{
return array(
'documents' => array(self::HAS_MANY, 'Document', 'user_id'),
'department' => array(self::HAS_ONE, 'Department', 'department_id'),
);
}
You can then pull all the documents for the current user like so:
$user = User::model()->findByPk($userId);
$documents = $user->documents;
$documents will then be an array of active models for all that users documents.
To obtain all the documents of that users department, there's a couple of options. You could use relations again, adding to the Department model the following:
public function relations()
{
return array(
'users' => array(self::HAS_MANY, 'User', 'department_id'),
'documents' => array(self::HAS_MANY, 'Document', 'document_id', 'through'=>'users'),
);
}
Which should give you the ability to pull all of a departments documents like so;
$department = Department::model()->findByPk($departmentId);
$documents = $department->documents;
Which in turn would mean you could grab the users department documents like so:
$user = User::model()->findByPk($userId);
$documents = $user->department->documents;
There may well be a more efficient way to grab those by using a relation in the Users model, but it's too late for me to work that our right now ;)
Once you have an array of active record models, you can always pass them to a data provider by using CArrayDataProvider like so;
$dataprovider = new CArrayDataProvider($documents);
I've not tested any of those by the way, so they may need some editing!
You need to modify the search function on the appropriate model (I'm going to guess at documents). You'll already be able to see code in there you can use.
Add some parameters to the search function itself, which can be passed in from the controller. Then use these to determine which compare calls to make.
Using CakePHP 2.2, I am building an application in which each client has it's own "realm" of data and none of the other data is visible to them. For example, a client has his set of users, courses, contractors and jobs. Groups are shared among clients, but they cannot perform actions on groups. All clients can do with groups is assign them to users. So, an administrator (using ACL) can only manage data from the same client id.
All my objects (except groups, of course) have the client_id key.
Now, I know one way to get this done and actually having it working well, but it seems a bit dirty and I'm wondering if there is a better way. Being early in the project and new to CakePHP, I'm eager to get it right.
This is how I'm doing it now :
1- A user logs in. His client_id is written to session according to the data from the user's table.
$user = $this->User->read(null, $this->Auth->user('id'));
$this->Session->write('User.client_id', $user['User']['client_id']);
2- In AppController, I have a protected function that compares that session id to a given parameter.
protected function clientCheck($client_id) {
if ($this->Session->read('User.client_id') == $client_id) {
return true;
} else {
$this->Session->setFlash(__('Invalid object or view.'));
$this->redirect(array('controller' => 'user', 'action' => 'home'));
}
}
3- Im my different index actions (each index, each relevant controller), I check the client_id using a paginate condition.
public function index() {
$this->User->recursive = 0;
$this->paginate = array(
'conditions' => array('User.client_id' => $this->Session->read('User.client_id'))
);
$this->set('users', $this->paginate());
}
4- In other actions, I check the client_id before checking the HTTP request type this way.
$user = $this->User->read(null, $id);
$this->clientCheck($user['User']['client_id']);
$this->set('user', $user);
The concept is good - it's not 'dirty', and it's pretty much exactly the same as how I've handled situations like that.
You've just got a couple of lines of redundant code. First:
$this->Auth->user('id')
That method can actually get any field for the logged in user, so you can do:
$this->Auth->user('client_id')
So your two lines:
$user = $this->User->read(null, $this->Auth->user('id'));
$this->Session->write('User.client_id', $user['User']['client_id']);
Aren't needed. You don't need to re-read the User, or write anything to the session - just grab the client_id directly from Auth any time you need it.
In fact, if you read http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html#accessing-the-logged-in-user it even says you can get it from outside the context of a controller, using the static method like:
AuthComponent::user('client_id')
Though it doesn't seem you'll be needing that.
You could also apply the client_id condition to all finds for a Model by placing something in the beforeFind function in the Model.
For example, in your User model, you could do something like this:
function beforeFind( $queryData ) {
// Automatically filter all finds by client_id of logged in user
$queryData['conditions'][$this->alias . '.client_id'] = AuthComponent::user('client_id');
return $queryData;
}
Not sure if AuthComponent::user('client_id') works in the Model, but you get the idea. This will automatically apply this condition to every find in the model.
You could also use the beforeSave in the model to automatically set that client_id for you in new records.
My answer may be database engine specific as I use PostgreSQL. In my project I used different schema for every client in mysql terms that would be separate database for every client.
In public schema (common database) I store all data that needs to be shared between all clients (objects that do not have client_id in your case), for example, variable constants, profile settings and so on.
In company specific models I define
public $useDbConfig = 'company_data';
In Controller/AppController.php beforeFilter() method I have this code to set schema according to the logged in user.
if ($this->Session->check('User.Company.id')) {
App::uses('ConnectionManager', 'Model');
$dataSource = ConnectionManager::getDataSource('company_data');
$dataSource->config['schema'] =
'company_'.$this->Session->read('User.Company.id');
}
As you see I update dataSource on the fly according to used company. This does exclude any involvement of company_id in any query as only company relevant data is stored in that schema (database). Also this adds ability to scale the project.
Downside of this approach is that it creates pain in the ass to synchronize all database structures on structure change, but it can be done using exporting data, dropping all databases, recreating them with new layout and importing data back again. Just need to be sure to export data with full inserts including column names.
I've this
$reviewModel=Review::model()->findAll();
Now I want to perform a search operation on $reviewModel,say I want to search if a user of id 1 has posted a review or not. so is there any function provided by yii.
Note: I dont want to use
$reviewModel=Review::model()->findAll(array('condition'=>'')); As I
need all the reviews & then perform a search.
This is what I would recommend and is good design practice, also it will allow optimal performance from your database, + reduce the amount of queries. I would make your reviews have a relation with the user. As a user will post many reviews. so user HAS_MANY reviews. and Review and HAS_ONE user. so use that and create a foreign key to build the relationship.
So inside your user model (Relations) id have something like this:
'reviews' => array(self::HAS_MANY, 'Review', 'originator),
now inside your review model id have something like this:
'user' => array(self::BELONGS_TO, 'User', 'originator'),
Once you have done that all you would have to do is loop through all the users reviews. If its null, they don't have any reviews.
$user = User::model()->findbyPk(1);
and user->reviews would contain all the reviews for that user.
Not that I am aware of (if I understand your question correctly). Yii provides various ways to ask for what you want from the DB but not from an array of active record models in memory.
If you want to do it this way then get the array of data as you already did and then use PHP to iterate through the array of models looking for what you want.
You can filter through them manually. For example:
$reviewModel=Review::model()->findAll();
$matched = false;
foreach($reviewModel as $r) //cycle through each review
{
if($r->user == 1) //check if user is desired
{
$matched = true; //set matched as true
break; //stop searching, as we already found one
}
}
if you want to search for multiple things, you can use an array for matched and then check that array.
$reviewModel=Review::model()->findAll();
$wanted = array(0=>false,3=>false,6=>false,8=>false);
foreach($reviewModel as $r) //cycle through each review
{
if(isset($wanted[$r->user]) and !$wanted[$r->user]) //check if user is in wanted list and still false
{
$wanted[$r->user] == true; //set appropiate user to true
}
}