Sanitizing user data: How and where to do it | Diovo:
'via Blog this'
Tips and musings about the Yii Framework and other Web development-related topics, including CSS, HTML, code deployment, and version control
Monday, August 29, 2011
Help Central » Blog Archive » How to create custom validation rule - Discretelogix
Creating your own validation rule for a model. It's basically a one-off for that particular model, since the rule is both used and defined within the model class definition.
'via Blog this'
Example of Including 'criteria' with a Unique Validation Rule
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('entity_category_id, entity_id', 'required'),
array('preferred','boolean'),
// unique with condition
array('preferred','unique',
'criteria'=>array(
'condition'=>'entity_id=:entity_id',
'params'=>array(':entity_id'=>$this->entity_id),
),
),
// The following rule is used by search().
// Please remove those attributes that should not be searched.
array('id, entity_category_id, entity_id', 'safe', 'on'=>'search'),
);
}
Thursday, August 25, 2011
Setting the returnUrl for a User in Yii
When you have logged in a user in Yii, the default is for them to be redirected to the main home page of the site. But sometimes, you want them to go elsewhere. Here's a forum question (in which the op answers it for himself) that is very helpful in figuring out how to do this (minimally documented) function:
'via Blog this'
In my case, I needed the user to return to an admin module's home page. Here's what I did:
In SiteController, in the actionLogin function, add a line of code after the user has successfully logged in, like this: Yii::app()->user->setReturnUrl('/admin');
Including the slash on the front of "admin" tells the app to start at the top level (the root of the actual site) when determining the path to take rather than a relative path. For my app, '/admin' means the admin module.
Here is what the entire actionLogin function in SiteController.php looks like:
public function actionLogin()
{
$model=new LoginForm;
// if it is ajax validation request
if(isset($_POST['ajax']) && $_POST['ajax']==='login-form')
{
echo CActiveForm::validate($model);
Yii::app()->end();
}
// collect user input data
if(isset($_POST['LoginForm']))
{
$model->attributes=$_POST['LoginForm'];
// validate user input and redirect to the previous page if valid
if($model->validate() && $model->login())
Yii::app()->user->setReturnUrl('/admin');
$this->redirect(Yii::app()->user->returnUrl);
}
// display the login form
$this->render('login',array('model'=>$model));
}
Monday, August 22, 2011
Finding Out Your Current Subversion Repository URL
Have you been working on this project so long that you've forgotten where your repository is? Do this:
svn info | grep URL
Nice.
svn info | grep URL
Nice.
Switching a Working Copy in SVN
Use "svn switch" for this. It's not very painful, and it keeps your local changes. Basically, it's "svn update" on steroids: not only does it update to the latest modifications in the repository (again, keeping your local changes), it also relocates where your working copy is "looking to" to know what has been changed. Think of your local working copy as keeping its eye on the master list of files and changes. Now, that gaze has shifted elsewhere, perhaps to a branch. This is fantastic, for example, if you want your current local modifications to be checked into a branch instead of the head. Just use svn switch, and then you'll be interacting with a different batch of project files.
The command itself is simple:
svn switch new/repository/location
The new repository location could be a file:/// location, if you're doing this on the same server on which the repository resides, or an https:// location, if it's remote. Be sure to include the full path, including any directories, like /trunk or /braches/new-branch.
The command itself is simple:
svn switch new/repository/location
The new repository location could be a file:/// location, if you're doing this on the same server on which the repository resides, or an https:// location, if it's remote. Be sure to include the full path, including any directories, like /trunk or /braches/new-branch.
Subversion Users: Re: create trunk for an existing rep
Like a lot of people (I think/maybe/probably/noobs), I was in a hurry when I created the svn repository for my current project and didn't bother setting up trunk, branches, and tags subdirs. Now, I regret it, because I am suddenly interested in branching.
This is all very scary, because we're in the middle of testing an app that is supposed to go to the client at the end of the week. If I break this, I screw up our beta testing for everyone. So, I was a bit nervous.
I found the following forum posting, though, and it was very helpful:
In a nutshell, I did everything recommended on the forum:
- Set up a sandbox somewhere to check out the files. I'm on Linux (and this assumes you are, too), so I just logged onto a server to which I had access and went straight home. (I was already in home upon logging in via ssh, but you can just do this: cd ~)
- Check out the current head: svn checkout https://path/to/project (you're on your own for this, since I don't know where your repos lives)
- Alternatively, you could check it out to a specific directory: https://path/to/project somedir (specifying 'somedir' as the new directory to which to check the files)
- Go into the directory (either 'somedir' or the default, if you didn't provide the 'somedir' directory): cd somedir
- Now, make your new trunk directory: svn mkdir trunk (actually type 'trunk' if that's what you want to call your trunk)
- Use a little ol' shell script to loop through the existing files, doing svn mv on each one: for f in *; do svn mv $f trunk; done
- Now, all of your files are actually svn deleted (scary) from the original location and svn added to the new place (trunk)
- You can safely create branches and tags dirs, now. Doing it before would have resulted in them being moved to trunk, I think: svn mkdir branches tags
- Now, commit your changes: svn commit -m "moved files into trunk; created branches and tags dirs"
Not too bad. It seems to have worked without a hitch. I used svn switch on my working repository so that I could continue working on the relocated trunk. (I'm using Subclipse, so I just did it in Eclipse.) Now all I have to do is go onto the test server and do an svn switch to get it pointing at the new repos there, and I should be done. Cross your fingers, and good luck!
What Mother never told you about SVN Branching and Merging « Design By Gravity
Another useful article about branching and merging back into the trunk with svn. I need to look at it more closely, but the digrams are very helpful!
svn branching best practices (in practice) « snax
This article on branching in svn in well-written. I'm sure everyone has their differences with the techniques, even if only minor ones, but it's a great start:
Wednesday, August 17, 2011
Easy RSS feed reading with SimplePie // Corvid Works
Really good article to help get you started using SimplePie to parse RSS feeds with PHP:
Formatting Yii's CGridView Can be a Bit Tricky
First, an example:
<?php $this->widget('zii.widgets.grid.CGridView', array(
'id'=>'news-grid',
'dataProvider'=>$model->search(),
'filter'=>$model,
'columns'=>array(
'type',
'title',
array(
'name'=>'content',
'type'=>'ntext',
),
'release_date',
array(
'name'=>'external_link',
'type'=>'url',
),
array(
'type'=>'image',
'value'=>'$data->image->getImgSrc()',
),
array(
'class'=>'CButtonColumn',
),
),
)); ?>
This is part of an admin section for a site, so I've included Yii's CButtonColumn.
This is similar to my previous post on CDetailView. I'll try not to repeat myself.
Primary points of interest:
That's it!
<?php $this->widget('zii.widgets.grid.CGridView', array(
'id'=>'news-grid',
'dataProvider'=>$model->search(),
'filter'=>$model,
'columns'=>array(
'type',
'title',
array(
'name'=>'content',
'type'=>'ntext',
),
'release_date',
array(
'name'=>'external_link',
'type'=>'url',
),
array(
'type'=>'image',
'value'=>'$data->image->getImgSrc()',
),
array(
'class'=>'CButtonColumn',
),
),
)); ?>
This is part of an admin section for a site, so I've included Yii's CButtonColumn.
This is similar to my previous post on CDetailView. I'll try not to repeat myself.
Primary points of interest:
- Individual models are referred to as $data. This is "automagically" done by Yii. It's the variable CGridView provides. You must use it if you want to access that model for anything fancy.
- Most of the things I'm trying to display, here, are attributes belonging to the main model. I don't need to use $data to get the content, for example. All I have to do is include 'content' in the output. Because I wanted to format content using the ntext formatter, I had to use an array to tell CGridView to include 'content' and to format as 'ntext.' (See above. You'll get it.)
- I have a method for getting the image source of a related Image model. I used eager loading to give myself access to $data->image, so now I can call the Image method, getImgSrc(). Important: the whole expression to be evaluated is surrounded by quotes! That's necessary. Even though I'm using single quotes, Yii will evaluate it rather than render it as a literal. Trust me.
That's it!
Formatting CDetailView
CFormatter is used when you're doing some custom formatting to a CDetailView (among other places). Here's the link to the Class Reference on the Yii site:
Here's an example of using some of the formatting options:
widget('zii.widgets.CDetailView', array(
'data'=>$model,
'attributes'=>array(
'id',
'type',
'title',
array(
'name'=>'content',
'type'=>'ntext',
),
array(
'name'=>'release_date',
'type'=>'text',
'value'=>date_format(new DateTime($model->release_date), 'm-d-Y'),
),
array(
'name'=>'external_link',
'type'=>'url',
),
array(
'label'=>'Image',
'type'=>'image',
'value'=>$model->image->getImgSrc(),
),
),
)); ?>
In this example, I've included a few fields - id, type, and title - in the straightforward, default, manner. These will be formatted as text, by default. But I wanted to include my carriage returns for the content, so instead of simply specifying my content attribute the same way I did 'type' or 'title' I have created an array of options. The first element specifies the name of one of this model's attributes, 'content.' I want the value of 'content' to be formatted as 'ntext.' This will convert line breaks to '
' tags for me. The attribute 'external_link' is formatted as a 'url.'
' tags for me. The attribute 'external_link' is formatted as a 'url.'
The model's image is a little trickier. The model I'm displaying with this CDetailView has a relation, 'image,' that joins records out of the images table with details about a particular image. But this main model, itself, only has a foreign key - image_id - to relate to the images table. It's not very interesting to see image_id, to tell you the truth, so I like to display the actual image using the HTML image tag. Doing this within CDetailView requires that I get the details for the Image model that is related to this particular model I'm displaying.
How do we do this? The first step is not to use the 'name' attribute as I have elsewhere; 'name' tells CDetailView to expect to display the attribute for this main model (which would render the image_id integer), not information from a relation (that is, data from the join part of the query). Instead, I use 'label.' This just tells CDetailView to use whatever value I provide for 'label' as the label when displaying the rest of the information. The type has been set to 'image,' which lets CDetailView know to format this using the tag.
Now, here's the crux of it. So far, we have a label and a tag, but we do not have a value to include. That value is important, because it ends up being used as the 'src' attribute within our image tag (). I happen to have a helper method for my Image model that gets the image source so that I can use it in image tags. It just creates a string of the relative path in the app for whatever image is being handled. Now, all I have to do for the value is get the related image for our main model and call the Image method, getImgSrc() on it. (You don't know this, because I didn't mention it, but I have direct access to the image relation because I used 'with' in my query when I loaded this particular model. No great details on this, here, but suffice to say that you can do this when you load your model: instead of simply doing ModelName::model()->findByPk($id), do ModelName::model()->with('somerelation')->findByPk($id). Google "eager loading Yii." Anyway, this is why I can do: $model->image->imageMethod().)
One other thing. Did you notice I used 'text' for my type for the date? There is an actual 'date' type, but I could not get it to work to my liking. The common workaround I've seen is simply to use standard PHP date formatting and just output the result as text. It's a real pain, IMO. If I figure it out, I'll post it.
Really Basic CJuiDatePicker Example
Here's a CJuiDatePicker example with the basic parameters and a couple of the optional JavaScript options. In particular, I like using the dateFormat option to format my date the way I want to save it in the db.
<?php
$this->widget('zii.widgets.jui.CJuiDatePicker', array(
'model'=>$model,
'attribute'=>'release_date',
// additional javascript options for the date picker plugin
'options'=>array(
'showAnim'=>'fold',
'dateFormat'=>'yy-mm-dd',
),
'htmlOptions'=>array(
'style'=>'height:20px;',
),
));
?>
Tuesday, August 16, 2011
PHP Tutorials Examples Filtering Data with PHP
A terrific tutorial on PHP's wild and wooly filter_var family. If you need to filter input (and you do), check it out.
Also, if you decide you want to include multiple flags for one of the filters, here's a guide to help:
How to add multiple options/flags to PHP filter_var()
Monday, August 15, 2011
Accessing View Files from Anywhere in the Yii Application
By default, view files are accessible using a "double slash" notation, like so:
'//directoryName/fileName'
where 'directoryName' refers to the ID of the view (and, incidentally, its corresponding controller) and 'fileName' is the actual file you wish to use within that directory. For example, if you have a controller called StoreController, it will most likely have a views directory called 'store' in which you might have index.php, update.php or whatever. To include the update.php page from another controller or another view, you can do this:
$this->render('//store/update');
Easy, right?
'//directoryName/fileName'
where 'directoryName' refers to the ID of the view (and, incidentally, its corresponding controller) and 'fileName' is the actual file you wish to use within that directory. For example, if you have a controller called StoreController, it will most likely have a views directory called 'store' in which you might have index.php, update.php or whatever. To include the update.php page from another controller or another view, you can do this:
$this->render('//store/update');
Easy, right?
Tuesday, August 9, 2011
Fun with Google Calendar API (Sort of...)
I've been working with Google's Calendar API a bit, as of late, and I wanted to share a few hiccups I ran into in the hopes of avoiding them again, myself, and helping someone else. Some of this is certainly my own impatience with reading the (large amounts of "strangely" organized) documentation. I have a hard time imagining anyone sitting and reading this stuff from start to finish, so I end up Googling for help (how ironic). Here are some highlights:
- When requesting a calendar feed, if you include "futureevents=true" in the URL, any start-max value you have in the string will be overridden. Sort of makes sense, but it seems arbitrary. In fact, if I go through the trouble of including a start-max, I would think that it should override futureevents (which, IMHO, is much easier to include accidentally on purpose, and has a fifty-fifty chance of being set to true). But whatever.
- Requesting the "basic" version of a calendar will result in a very different response format. This sort of makes sense (since "basic" vs. "full" by definition defines the format). In particular, the start/end times are included in the "detail" part of the returned information, NOT in a "when" array, as they are with full. I thought this was strange since, regardless of how extensive the details are, I would expect the date/time information to have remained the same, whether I'm getting a lot of other details or not. (After all - isn't date and time what it's all about, anyway?)
- This one didn't really trip me up, but I'll throw it in for good measure: setting singleevents=true in the query string returns individual event listings for each event, even if that event is recurring. In other words, you will get a unique event entry, for example, for every meeting on Tuesday that you can parse out individually. Otherwise, you get one mention of the Tuesday meeting with a bunch of dates/times attached to the one event. This is intuitive, really, but it makes a difference in dealing with a recurring event. Note that, in either case, the "when" data comes back as an array, so you still need to loop through it, even if it's an individual event with only one start and end date.
Here's a quick example of a feed request url:
https://www.google.com/calendar/feeds/atlanticstationatl%40gmail.com/public/full?alt=jsonc&start-min=2011-08-09T00:00:00&start-max=2011-08-30T00:00:00&orderby=starttime&sortorder=ascending&futureevents=false&singleevents=true
In it, I'm requesting a full calendar feed (so I get my "when" values) in the json-c format, with a start and end time as listed (note the standardized format it needs), ordered by the start time (ascending), with no future events (so that I don't override my end times), with recurring events treated as individual entries.
Good luck!
Monday, August 8, 2011
Yii CActiveDataProvider and a Limit Condition
Just discovered that, if you're trying to limit the number of results in a query using "limit" in your CActiveDataProvider configuration, you need to explicitly include 'pagination'=>false for it to work. For example:
$fCriteria = new CDbCriteria;
$fCriteria->with = array('textualContent','entity');
$fCriteria->limit = 2;
$fashionFindsDataProvider = new CActiveDataProvider('FashionFinds',
array(
'criteria'=>$fCriteria,
'pagination'=>false,
)
);
If you don't, limit does not take effect. Note that I'm using a CDbCriteria object, but you could also do this:
$fCriteria = new CDbCriteria;
$fCriteria->with = array('textualContent','entity');
$fCriteria->limit = 2;
$fashionFindsDataProvider = new CActiveDataProvider('FashionFinds',
array(
'criteria'=>$fCriteria,
'pagination'=>false,
)
);
If you don't, limit does not take effect. Note that I'm using a CDbCriteria object, but you could also do this:
$fashionFindsDataProvider = new CActiveDataProvider('FashionFinds',
array(
'criteria'=>array('limit'=>2), // Leaving out the other junk from above for easy reading
'pagination'=>false,
)
);
Abstract Data Types in Yii's Database Query Builder
I've been making use of Yii's database migration feature a lot, lately. I'm refactoring some things in my app, so I frequently have had to add columns. You can specify the explicit column type, or you can use the abstract types provided by Yii. I tend to use the abstract types, since it makes my life easier. (If you want greater control, you'll need to state them explicitly.) I won't go into detail about CDbMigration in this article. I'll save it for a later time. But I wanted to include the link to the abstract data types explanation in the Yii Class Reference, because I use it a lot, right now, and I keep having to remember where it is! So, here you go.
Abstract Data Types Used in Yii's Query Builder
P.S. If you want some help getting started with Yii's database migration feature, here's a great introduction by Dana Luther on her blog, Adventures in Programming:
Yii 1.1.6 and the beauty of yiic migrate
P.S. If you want some help getting started with Yii's database migration feature, here's a great introduction by Dana Luther on her blog, Adventures in Programming:
Yii 1.1.6 and the beauty of yiic migrate
Friday, August 5, 2011
Avoiding Duplicate Records When Using Yii's With/Together Combo
Came across a situation in which a query with relations returns duplicate records. First of all, for those of you just getting started, you can retrieve a model's relations with the 'with' method. For example, say you have a model, Person, and Person has a relation, CellPhone. If you want to get Person's CellPhone, you can use with() to retrieve the related record(s) when you query for Person. For example, assuming you've created a relation for the Person model called 'cellPhone':
$model = Person::model()->with('cellPhone')->findAll()
This allows you to do things like this:
echo $model->first_name; // prints the first name
echo $model->cellPhone->os_type // prints information about the cell phone, like 'Android'
The issue, here, however, is that Yii will actually do two queries, one to get the Person and one to get the CellPhone. To get everything in one query (more efficient), use "together" like so:
$model=Person::model()->with(array('cellPhone'=>array('together'=>true)))->findAll()
The syntax is a little different, since I'm including a parameter, here, for the query, namely together. (You could do this with a CDbCriteria, but we'll save that for another time.) The main point is that this will execute the select query once to get the Person and any related CellPhone records.
Now that we've covered that, the gotcha I wanted to mention, here, is that, if your Person has many CellPhones, you're likely to end up with duplicate records when you perform the together version of the query. The solution I found was to use a 'group by' clause in my query, grouping by the primary key for Person. You could do something like this;
$model=Person::model()->with(array('cellPhone'=>array('together'=>true)))->findAll(array('group'=>'t.id'))
where the 't' in 't.id' refers to the main table being queried (e.g., the table for Person). For all queries, the main table will use the alias 't' and you can use 't' in your queries for disambiguation.
I hope this gives you some idea of the solution. There is probably a better one, but this one works for me, and it makes sense. You'll especially want to watch this if you're going to be paging through your records, because the duplicates will throw off the total count returned from the query, but Yii will only show the distinct rows! So, you end up with something like "Showing 2 through 12 of 38," which is weird enough, but even worse, you don't actually get to page through 38 records, because many of them are duplicates!
By the way, I don't actually use the syntax above to pass in the query criteria. I'll cover CDbCriteria in a later post.
$model = Person::model()->with('cellPhone')->findAll()
This allows you to do things like this:
echo $model->first_name; // prints the first name
echo $model->cellPhone->os_type // prints information about the cell phone, like 'Android'
The issue, here, however, is that Yii will actually do two queries, one to get the Person and one to get the CellPhone. To get everything in one query (more efficient), use "together" like so:
$model=Person::model()->with(array('cellPhone'=>array('together'=>true)))->findAll()
The syntax is a little different, since I'm including a parameter, here, for the query, namely together. (You could do this with a CDbCriteria, but we'll save that for another time.) The main point is that this will execute the select query once to get the Person and any related CellPhone records.
Now that we've covered that, the gotcha I wanted to mention, here, is that, if your Person has many CellPhones, you're likely to end up with duplicate records when you perform the together version of the query. The solution I found was to use a 'group by' clause in my query, grouping by the primary key for Person. You could do something like this;
$model=Person::model()->with(array('cellPhone'=>array('together'=>true)))->findAll(array('group'=>'t.id'))
where the 't' in 't.id' refers to the main table being queried (e.g., the table for Person). For all queries, the main table will use the alias 't' and you can use 't' in your queries for disambiguation.
I hope this gives you some idea of the solution. There is probably a better one, but this one works for me, and it makes sense. You'll especially want to watch this if you're going to be paging through your records, because the duplicates will throw off the total count returned from the query, but Yii will only show the distinct rows! So, you end up with something like "Showing 2 through 12 of 38," which is weird enough, but even worse, you don't actually get to page through 38 records, because many of them are duplicates!
By the way, I don't actually use the syntax above to pass in the query criteria. I'll cover CDbCriteria in a later post.
Monday, August 1, 2011
Storing a NULL Value in a MySQL Date/Time Field
I had a table with a column of type 'time.' It's set up so that the values can be null, and the default is, well, NULL. The create code looks like this:
CREATE TABLE IF NOT EXISTS `tbl_event` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`start_date` date DEFAULT NULL,
`end_date` date DEFAULT NULL,
`start_time` time DEFAULT NULL,
`end_time` time DEFAULT NULL,
`title` varchar(255) DEFAULT NULL,
`subheading` varchar(255) DEFAULT NULL,
`external_link` varchar(255) DEFAULT NULL,
`link_text` varchar(255) DEFAULT NULL,
`description` text,
`is_featured` tinyint(1) DEFAULT NULL,
`image_id` int(11) DEFAULT NULL,
`location` varchar(255) DEFAULT NULL,
`event_type_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=17 ;
CREATE TABLE IF NOT EXISTS `tbl_event` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`start_date` date DEFAULT NULL,
`end_date` date DEFAULT NULL,
`start_time` time DEFAULT NULL,
`end_time` time DEFAULT NULL,
`title` varchar(255) DEFAULT NULL,
`subheading` varchar(255) DEFAULT NULL,
`external_link` varchar(255) DEFAULT NULL,
`link_text` varchar(255) DEFAULT NULL,
`description` text,
`is_featured` tinyint(1) DEFAULT NULL,
`image_id` int(11) DEFAULT NULL,
`location` varchar(255) DEFAULT NULL,
`event_type_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=17 ;
Pretty straighforward, right? Wrong! It turns out that MySQL is pretty persnickety about what you put in a time column. If it considers it "illegal," it will default to its version of an "empty" field which, in this case, is "00:00:00" for zero hours, zero minutes, and zero seconds. Unfortunately, this is not really nothing, it's midnight!
So, why wasn't it just defaulting to NULL, like I'd asked it to? Although midnight as a translation for an illegal insert is arguably a pretty poor choice, the problem wasn't entirely MySQL's fault. When I tried to directly insert NULL without going through Yii (okay - I cheated and checked the "null" box in PHPMyAdmin just to see if it would take the value), it worked. In other words, you can use a NULL value in there if you ask it nicely. Knowing that, I tried a variety of different ways in my Yii code, like setting the attribute to 'NULL' in single quotes ('NULL'), null with no quotes (null), and plain old empty string (''). None of these things worked. MySQL considered all of them to be illegal entries.
What, you may ask, is the right way to ask MySQL to insert NULL in the time field, particularly if you're depending on Yii to do the asking for you? The answer is to use Yii's CDbExpression. When you have a time-related field, and you want a NULL value to be inserted, do it like this:
$model->myTimeColumn = new CDbExpression('NULL')
It's that simple. True, it takes a little extra code to make sure that's what you want, actually do the setting, etc., But it's much better than inadvertently setting it to midnight!
Subscribe to:
Posts (Atom)