Building a Page Based on Roles in Session from Relational Database in CodeIgniter - codeigniter

I'm new to codeigniter and just about everything at this point, but I have had some success with feeling my way around php, mysql, ci, and web application development in general. Though, I'm kind of stuck at the moment. So, I wanted to throw this out there to get your opinion on what I'm trying to do, hopefully - you can have the patience to get through to understand my problem and have a solution, as I will attempt my best to give you all the details.
First, I am in the process of developing a web application, to help ease the logging of attendance at a certain organization. I created a member table to hold all the members of the organization, a login table to hold the username and password of members' who hold roles in the organization, a role table, to contain the roles held at the organization, and finally a member_role table to account for any member who may have more than 1 role at the organization. (and of course an attendance table, with Foreign Key to member, but that is outside my current question).
MySQL code includes only the pertinent tables: memeber, login, role, and member_role.
<!-- language: lang-mysql -->
CREATE TABLE `member` (
`id` int(10) NOT NULL auto_increment,
`fname` varchar(32) NOT NULL,
`lname` varchar(32) NOT NULL,
...
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE `login` (
`memberid` int(10) NOT NULL,
`username` varchar(32) NOT NULL,
`password` varchar(32) NOT NULL,
FOREIGN KEY (`memberid`) REFERENCES member(`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE `role` (
`id` integer(10) NOT NULL auto_increment,
`role` varchar(32) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE `member_role` (
`memberid` integer(10),
`roleid` integer(10),
`active` char(1) NOT NULL,
FOREIGN KEY (`memberid`) REFERENCES member(`id`),
FOREIGN KEY (`roleid`) REFERENCES role(`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
So far, I think I did a pretty good job, in defining the database for this purpose. Your opinions here would be good, since I am a novice, I only know what a newbie would know.
Here's the real problem I'm currently having. I've created a user that has 2 roles. I have it properly storing it into a session, an array of a particular member's roles. The session array returns, roleid 1, and roleid 3.
Let's say I have certain buttons or functions assigned to each roleid. For memeberid = 1, I have assigned to him roleid = 1 and roleid = 3, therefor I only want to build a page including only the functions available with roleid = 1 and roleid = 3. Make sense?
This is where I'm stuck, I have the array, but not sure how to build a page for the user. Should I put the code into the controller or the view? and either way, I am not sure how to populate a page of buttons having only those regarding roleid = 1 and roleid = 3. Hmm, I hope you can understand, because that is about the best I think I can make it clear.
If you do understand, please perhaps if you could give an example of what/how I could achieve this.
Thanks for your time.

suggestion before you try to reinvent the wheel...
if you are trying to build a hrm application use orangehrm and work on that basis to your own needs.
otherwise use this
foreach($rolesarray as $currentrole)
{
//if currentrole is x then do that, if y then do different.
}
you can use this everywhere where you need to display multiple buttons, displays etc

The tables seem good to me except I wonder why you are separating the member and login tables. The columns in Login seem to be just additional attributes of each Member record in which case the tables could be merged into one.
Regarding your question on showing the right buttons for a person belonging to groups 1 and 3 -- I think you're getting stuck because you're thinking about creating one static page for a person with this combination (maybe I'm wrong..?). Instead, you create one page that everyone goes to and at the respective areas of the page where permissions are a factor, you put if statements to determine whether to display any given element, piece by piece. For example, you would come to the "Start Time" button and put logic to display as long as member belongs to at least one group. Then you would get to the "Management Dashboard" and only display if member has group 3 (if not, you would skip the element both in the controller and the view). I hope I understood your question properly.
Regarding whether to check for the permissions in Controller or View, the spirit of MVC is to perform this kind of logic in the Controller. In the View, then, you would check whether the controller prepared information for certain areas of the page. If the view doesn't need anything specific prepared by the controller to display an element (i.e. you don't need anything prepared to display a link/button to the Management Dashboard--you simply need to know whether the person is in group 3), you can have the controller set a variable like $display_management_db_button.
Lastly, user authentication is a very common thing to need in any web app, and CodeIgniter packages do exist for user authentication. I've recently come across Ion Auth which seems to be the right mix of lightweight and robust that I'm looking for, but you can search and find many others out there.
-Gus
(edit)
Example:
In the controller:
if($this->member->has_group(3)) { $display_management_db_button=true; }
else { $display_management_db_button=false; }
$data = array('display_management_db_button' => $display_management_db_button;);
$this->load->view('time_screen', $data);
In the view:
if($display_management_db_button) { echo '<input type="button" ... />'; }
So the logic remains in the controller. Separating the logic becomes more important in maintaining clean code as your program gets more complex.

Related

CakePHP - Dynamically create new database table

I need to create a new table dynamically from within the code. The current proof of concept is currently working, but what I was given as POC has several issues and I have some questions about whether this is the best way. I cannot change the architecture, I can only implement as best I can.
Assume I have a controller called WorldController with accompanying Model WorldTable and accompanying views to list all Worlds and also add new Worlds. Also assume every user has their own universe, called $universe. Lastly, note that we are using Postgres and subsequently, schema.
Right now,I can add a new World to the Database in the World table and I can successfully create the required tables that the World requires, using $universe.'world_geography' syntax. So if $universe = 'milkyway', then that tables are milkyway.world_climate and milkyway.world_geography etc.
The additional tables are created in the WorldTable model by using SQL statements:
$sql = "
CREATE TABLE $universe.world_geography (
id serial4 NOT NULL,
landmasses_id int4 NOT NULL,
layers_id int4 NOT NULL,
materials_id int4 NOT NULL
);
";
$connection = ConnectionManager::get('default');
$submit = $connection->execute($sql);
This is obviously problematic due to SQL code injection. What would
be a better way to do this?
Second question: Seeing that this code is creating Tables that
aren't part of this model, should this code then rather be run from
the Controller?
And lastly, does CakePHP have any better way of doing this that I
might have missed?
Comment: I have read many, many other questions here on SO and around the net and none of them ask this question or have sufficient answers to my question.

Understanding why relationship works

I am trying to understand why something works. I have a user model and within it I state that a User can have one Project e.g.
public function project()
{
return $this->hasOne('App\Project', 'userId');
}
Then within my Project model I define a user belongs to a Project
public function user()
{
return $this->belongsTo('App\User', 'userId');
}
So the way I understand this, a User should be allowed only one project? So within my Projects controller, I have my store function. I wont go over all of it but I essentially do the following
$newProject = new Project();
$newProject->projectName = Input::get('projectName');
$newProject->projectValue = Input::get('projectValue');
$newProject->userId = Input::get('user');
$newProject->save();
Now where I get the input user, this is always the logged in users id. So say I log into the system and I create a new project. This project then has my userID. This works fine.
My question is why does it allow me to create a second project with my same ID? So if I am logged into the system, I can essentially create as many projects as I want under my name. Does this not go against what my relatiohsips are defined as? According to the relationship, I should only be allowed to create one Project.
I am really just looking for information as to why this is allowed?
Thanks
In One to One,
You're linking a record to another record in another table.
That means, You're checking for single record rather than entire table.
So in your example, As you defined it's a correct One - to - One relationship.
Why it's adding new more than one project?
As i said before, We are checking for a single record.
How do i restrict that?
use UNIQUE constraint for Foreign key
User table
----------
`id` int,
`project_id` UNIQUE,
Project table
----------
`id` int,
`user_id` UNIQUE,
Example without Unique Constraint http://sqlfiddle.com/#!9/f2b07/1/0
(Try to put same values it will fail to insert)
Example with Unique Constraint http://sqlfiddle.com/#!9/dda24/1

Multiple Sessions in Codeigniter/PHP

I am developing a e-commerce website in codeigniter having sessions for following:
admin login
shopping cart
user login
Now i am unable to figure out a mechanism for managing these three sessions with different sets of data.
I know it can be achieved with session_name() in core php. But with codeigniter i am a bit confused.
Googled it a lot but couldn't found a proper answer.
I want a clear understanding of multiple sessions in codeigniter so that it doesn't create any confusion in future. Any link to tutorial would be great.
$this->session->set_data('admin',$array_of_admin_data);
$this->session->set_data('user',$array_of_user_data);
$this->session->set_data('cart',$array_of_cart_data);
then retrieve each seesion data ? using
$this->session->userdata('admin');
why wouldnt this work ?
Codeigniter's session is completely different from Native PHP Session and it's very secure especially when stored in a database. You can work with codeigniter's sessions and Native PHP Session they would work fine together because PHP would see the as different variables.
If you want to check for the existence of a codeigniter session value use
if(isset($this->session->userdata['course_id']))
{
$this->data['course_id'] = $this->session->userdata('course_id');
}
While Native PHP would look like
if(isset($_SESSION['course_id']))
{
$this->data['course_id'] = $_SESSION['course_id'];
}
I'm also developing a e-commerce system and have the same problem. I didn't like to manage all the data stored as serialized data in the database, especially not for the cart/basket, as it makes it hard to query the database and i simply think it's kinda messy.
I'm quite new to CI and stackoverflow, but my idea was to set up the ci_session table as it is written in the docs, thus keeping the consistency and security of CI sessions. Then i only add one reference id to the session varialbe for each session data you want to manage. E.g. for the cart:
$unique_cart_id = random_string('unique');
$this->session->set_userdata('cart_id', $unique_cart_id);
and now i reference my cart_id in a separate "basket"-table with all the data. E.g.:
CREATE TABLE basket (
id int(11) NOT NULL auto_increment,
cart_id varchar(32) default NULL,
timestamp timestamp NULL default NULL,
price float(10,2) default NULL,
title varchar(100) default NULL,
serialized_data text,
product_id int(11) default NULL,
PRIMARY KEY (id)
)
Now each time someone wants to add something to the basket i get the cart_id from the session variable:
$cart_id = $this->session->userdata('cart_id');
and store the $cart_id together with any other cart data in my "basket"-table, by writing a little insert script.
$data = array(
'cart_id' => $cart_id,
'title' => $product_title,
'product_id' => $product_id,
'price' => $price,
'serialized_data' => serialize(array('what ever you need else'))
);
$this->db->insert('basket', $data);
Now if someone wants to view the basket I just retrieve the $cart_id from the session variable again and query the database where it matches the cart_id.
$this->db->query("SELECT * FROM basket WHERE cart_id = ?", array($cart_id));
And you use the same principle for the your other session data, admin and user login. Thus I keep only to have a single session cookie for each user but I can manage each type of session data separately in the database.
This was just a rough breakdown of the idea. I hope it helped.
With the userdata() method, you can retrieve the fields you saved during the creation of the session.
For example, you pass the $array_of_admin_data array, which holds some information like userType. If you want to retrieve this value, you must call userdata() like this:
$this->session->userdata('userType');
Hope, that this helps.

refactoring a database and application due to new requirements

My application manages customer's complaints and has already been deployed into production. Each complaint has a code to identify it (for eaxmple "late delivery" ), a "department" type (wich is essentially the department responsible for that kind of complaint) and another "model" code which identifies the route through department's employees this complaint dossier has to follow (first to hr responsible then to hr big boss finally back to customer care). Each dossier has some common info and can have department specific infos, that's why i need deparment code.
For example Customer care get a complaint about "rudeness" of a call center operator, opens a dossier with code ABC and type "HR" (there's could be more HR dossier types). When the customer care has filled all the infos, forward it to hr(a mail is sent to the user configured in the system as HR responsible ). The hr employee fills his own section and send it back to customer care.
Till now each complaint code might have only one department and one model, now requirements have changed and i've two problems:
Some complaints are identified by the same code but might be due to different departments . For example a complaint about employees rudeness could be sent to the department which rules the call centers or to the department which rules logistics
i could solve this simply extending the table primary key to include the department (hoping they'll not decide the same code for the same department can follow different routes), changing application code might be a bit painful but it can be done :
Does extending primary keys to composite keys is a problem in Oracle or have side effects on existing records? the actual primary key is not used as foreign key anywere and all fields are filled.
this is a quite more difficult problem (at least for me): marketing department (the rulers) wants a special dossier.They monitor time departments take to answer complaints and open a new type of dossier if they exceeds the standard time.
For the above example, if hr always needs the 30% more time to complete employees rudeness dossiers, marketing can open an "inquire" dossier about that complaint code directed to hr.
Now, referring to point 1, i could add a new record for each complaint code having the second part of the key being the marketing code and associating it to a new model.This is going to double the rows of the table (which is already quite large). I see it very error prone for inserting new complaint codes.
I know it's very hard to give an opinion without being able to see the schema and the code, but i would appreciate your opinion anyway
"Does extending primary keys to
composite keys is a problem in Oracle
or have side effects on existing
records? the actual primary key is not
used as foreign key anywere and all
fields are filled."
Oracle allows us to have composite primary keys. They are not a problem from a relational perspective.
The only objection to primary composite keys is the usual one, that they make foreign key relationships and joins more cumbersome. You say you currently don't have foreign keys which reference this table. Nevertheless I would suggest you define a synthetic (surrogate) primary key using an index, and enforce the composite key as a unique constraint. Because you may well have foreign keys in the future: your very predicament shows that your current data model is not correct, or at least not complete.
"i could add a new record for each
complaint code having the second part
of the key being the marketing code"
Smart keys are dumb. Add a separate column for a marketing code if necessary. This would be populated if Marketing open their own dossier. I don't see why it needs to be associated with the Complaint Code or form part of any primary key (other than the Marketing Code lookup table).
I admit I don't fully understand your data model or business logic, so the following might be wrong. However what I think you want is a table DOSSIERS which can have two dossier types:
normal dossier identified by DEPT_CODE and COMPLAINT_CODE
Marketing dossier which I presume would be identified by DEPT_CODE, COMPLAINT_CODE and MARKETING_CODE.
Unique constraints permit NULL columns, so MARKETING_CODE can be optional. This is another advantage of using one instead of a composite primary key.
"I see it very error prone for
inserting new complaint codes."
Do you mean creating new complaints? Or new complaint types? Creating new complaints shouldn't be a problem: the process for creating Normal Dossiers will offer a choice of COMPLAINT_CODES where MARKETING_CODE is null, whereas the process for creating Marketing Dossiers will offer a choice of COMPLAINT_CODES where MARKETING_CODE is not null.
If you're talking about adding new complaint types then I suppose the question becomes: does there have to be a separate MARKETING_CODE for each regular COMPLAINT_CODE? I suspect not. In which case, instead of a MARKETING_CODE perhaps you need a CODE_TYPE - values NORMAL or MARKETING.

Best practices for autosaving drafts?

What is the best strategy for applications that autosave an email before it is sent or save a blog post before it's finished or officially saved? Would it be best to use a separate table in the database for temporary drafts or to have a status column that marks a post as draft or published? I'm not looking for code, just methods, but any other related advice would be welcome as well, like how often to save, etc.
Considering that separate tables for drafts and published articles would be essentially duplicates of each other, I would lean towards just one table with a status column to differentiate between the two.
I do drafting on the Wikipedia way: I save the first version, and all modification's saved (based on time or explicit user command) as a next version. After ie. publication you can delete the draft-graph - or not.
If you save data in database I think it's good to use the same table (you can avoid schema conflicts), and use version/status to track drafts lifecycle.
this applies to more than emails...
I changed my mind on this one. The best way is to use a is_draft column in your table and store both drafts and valid entities in the same table. this has the advantage of the entity keeping the same id even if it switches in and out of draft state (you might want to edit it after you save it, but temporarily remove a required value). it would be confusing for users if they were collaborating on the same document and the id kept changing, amirite?
you would use is_draft=1 to turn off ORM validation rules, trigger validations or check constraints to allow an invalid object to save. yes, you'd likely have to allow nullable fields in your table.
process:
try to save object. validation fails. set is_draft=1 and try to save again. it saves. put big "DRAFT" on the screen somewhere :)
user fills in required info. try to save object. validation passes. set is_draft=0. it saves.
now, regarding email and blog posts, your server shouldn't try to send it or post it right away unless the user hits the save/post button, but that is a different issue really.
OLD ANSWER
The problem is that a draft might not be valid, and cannot be saved in the actual table. For example, say your table demands that the subject be not null, but the user hasn't filled it in yet.
One way would be to have a draft table, and store a serialized version of the entity (and its children) to it. php's serialize() would be something to use, or you could use json. when it is finally valid, the system would save instead to the email (or whatever) table, and delete the draft:
pseudo sql:
create table draft
id int primary key auto increment,
entity varchar(64) not null comment 'this way you can find all drafts of say type Email',
contents longblob not null,
modified timestamp comment 'this way you can sort by newer drafts'
modified_by int not null foreign key to user.id comment 'this way you can filter by the user\'s drafts'
you could also consider a draft_file table for storing attachments or photos for the draft, and be able to access them individually:
create table draft_file
id int primary key auto increment,
draft_id int not null foreign key to draft.id on delete cascade,
size int not null comment 'bytes',
mime_type varchar(64) not null,
file_name varchar(255) not null,
contents longblob,
thumbnail blob comment 'this could be an icon for files/documents'
so, a user starts composing an email, maybe just types in the body, and adds some attachments. your gui saves the email to drafts, and uploads the attachments, saves them to draft_file, and returns the draft id, and the download urls for the files which you display in your gui.
he types in the Subject (To is still blank). Your gui saves the email to drafts, updating the draft table by id, as it knows its id from the previous step.
your users fills in the To field, and hits Send. Your server saves the email to the email table, copies the attachments from draft_file to the email_attachment table, and deletes the draft, preferably within a transaction.
this allows for long-term drafts, gmail-style attachment uploads, while maintaining integrity of your real entity table.

Resources