How to limit user access in CakePHP: findMy

May 7th, 2010 by Keith Perhac 8 comments »

Perhaps like many people starting a CakePHP project, I created a site where users could log in and create/modify their own files (in my case Japanese flashcards and tests) while not being able to screw with other people’s stuff.

One would think that you could solve this with some nifty ACL and Auth work, but if you thought that, then you would be wrong.

Unforunately ACL only lets you determine what actions a user is allowed to perform, not on which items they’re allowed to perform it.

And Auth only checks to make sure a person is logged in, not who they are or what they’re doing.

The Traditional Way

However, thanks to the glory of cake, it’s not that hard to limit a search by user! Just replace your find with the following function.

$my_file = $this->SomeModel->findAllByUserId($this>Auth->user(id);

And we’re done!

Oops! Not so fast!

We just got all of the SomeModels! Well crap, I guess we’ll have to make an option array

$opt = array(
   'conditions' => array(
      'SomeModel.user_id' => $this->Auth->user('id'),
   ),
   'order' => 'SomeModel.date DESC',
);
$my_file = $this->SomeModel->find('first', $opt);

That’s a lot of work for just getting my latest SomeModel. Even more work if I have to replace every instance of a simple find with something like that. I also need to do the same thing for every save and delete as well to make sure that they’re not saving over someone else’s data.

Sure, I could put that code throughout all my Controllers, but that’s messy — and if I forget to put it somewhere someone gets their data deleted.

So what can we do?

Well, I don’t know about you, but I put a function in my AppModel file that lets me easily make sure that whoever’s touching the file has permission to do so.

function findMy($type, $options=array())
{
   if($this->hasField('user_id') && !empty($_SESSION['Auth']['User']['id'])){
      $options['conditions'][$this->alias.'.user_id'] = $_SESSION['Auth']['User']['id'];
      return parent::find($type, $options);
   }
   else{
      return parent::find($type, $options);
   }
}

What this does

  1. Make sure that the model has an ‘user_id’ field
  2. Make sure that the user is logged in
  3. Add the user_id condition to the find options
    • Be sure to use the $this->alias in case you’re using an alias for the Model in a BelongsTo or HasMany relationship.
  4. Find the data

This is a pretty simple function, and can form the base for any logic that you want to do.

For example, if you want to allow Admins to view anything, just add an $_SESSION['Auth']['User']['role'] == ‘admin’ to the mix.

If you want to allow users access to any ‘public’ items, add a condition for ‘OR is_public == true’

Extending the function

This function is set in the AppModel, so it can be overridden in any of your defined models if you need to do any custom filtering on a per-model basis.

Also, it is possible to extend this to save and delete functions, to make sure that users are only saving or deleting their own files. If they try to delete a file that doesn’t belong to them, return false and alert the user that that file could not be found.

function deleteMy($id = null, $cascade = true)
{
   if (!empty($id)) {
      $this->id = $id;
   }
   $id = $this->id;

   if($this->hasField('user_id') && !empty($_SESSION['Auth']['User']['id'])){
      $opt = array(
         'conditions' => array(
            $this->alias.'.user_id' => $_SESSION['Auth']['User']['id'],
            $this->alias.'.id' => $id,
            ),
         );
      if($this->find('count', $opt) > 0){
         return parent::delete($id, $cascade);
      }
      else{
         return false;
      }

   }
   else
      return parent::delete($id, $cascade);
}

Conclusion

The power of CakePHP comes from it’s infinite extensibility, and the fact that at it’s core, it’s still just a PHP program.

While I recommend following MVC practices, and to use the built-in CakePHP functions as much as possible, there are times when you just need to do it the simple way.

Also, for those who balk at my use of $_SESSION, I talked with one of the CakePHP core developers at a conference a while ago, and was asking him about this problem.

I asked,

“Why is Auth only available in controllers? It would be much more useful if we could use it everywhere. Is it a design decision?”

He replied.

“Because it’s a component. That’s the only reason.”

Remember: the framework is there to help you. You are not its slave.

Widgity! Multi-element designs with CakePHP

April 27th, 2010 by Keith Perhac 1 comment »

Since I started programming with CakePHP almost two years ago, I’ve had a bunch of widgets on the right side of my page ( http://www.JapaneseTesting.com ).

Which pages each of these widgets appear on is guided by the controller, action, user role, or any combination thereof.

I used to deal with this in a Controller-Model combination, called with resource-heavy Request Actions. Then I found a better way. A Simple way. A way with Branching Elements.

Read on to see how to dynamically put customizable element blocks on any of your pages.

JapaneseTesting.com WidgetsThe Layout

On my site, each of the widgets is a separate element, and the way I used to deal with deciding which page to display them on was by running a RequestAction on my Widgets Controller, which in turn grabbed the widget data from the DB, checked which widgets needed to be displayed when, grabbed the elements, and then displayed each of them. Overall a very thorough way of managing and displaying the widgets, but not very nice on my load-time (especially when I started using DebugKit).

Eventually I started using a widgeting component of my own design that took an array of widget types, their caching statuses, various data, etc and threw it all into a controller variable which in turn threw it into a helper and then finally displayed the widgets.

This might have actually been slower than the previous DB calls, and let code to be placed in either controllers or views, which defeated the whole purpose of having a single consolidated place to insert the code.

But then I found a better way:

Enter the $this->name and $this->action variables!

Every view (and every element in that view) has the $this->name (the controller name) and the $this->action (the controller’s action name) available to them. That means that in any view, we know exactly what method has been called to get there (and thus, what variables, data, etc are available).

We’re going to use these variables, along with some Auth information in order to create widgets that obey a set of rules to display a bunch of widgets.

Our five widget elements

We’re going to have 5 elements that are going to be displayed to the user, depending on what page they’re looking at, and if they’re logged in or not.

  • who’s logged in Display  [ Shown to users who are logged in ]
  • latest Articles Display    [ Always Shown ]
  • latest Posts Display         [ Only available on Forum pages ]
  • Upgrade now Advert      [ Available on every page except the Forum pages ]
  • Handy Post editing          [ Shown only on the Forums/edit page ]

We’re going to store these in our elements folder with the following names:

  • /elements/widgets/whos_online.ctp
  • /elements/widgets/latest_articles.ctp
  • /elements/widgets/latest_posts.ctp
  • /elements/widgets/upgrade_now.ctp
  • /elements/widgets/post_edit.ctp

Controller Widgets

Then, we’re going to create a couple of “controller” widgets that allow us to break up the show/don’t show logic a little bit. These are each going to be named after the controller that will display them.

It’s up to you whether or not you want to use these, or just stick them in the main controller (see below) but I like the distribution of data, and it keeps me from having a huge main controller loaded on every page load.

Also, because I might have multiple widget bars with different contents, I’m going to put these in a different elements folder, named “sidebar_rt”

  • Controls all the Widgets that are Available only on the Forms pages [ /elements/sidebar_rt/forms.ctp ]

The Main Controller

Finally, we’re going to make a main controller element that will call all the widgets from a single location so we don’t have a lot of unnecessary code in our layout.

  • Main Widget Controller [ /elements/sidebar_rt/index.ctp ]

Putting it all together

So, now that we have the files created, let’s see what goes in them.

First, in your /layouts/default.ctp file, we put the main sidebar controller into the layout.

// /layouts/default.ctp
<div class="sidebar right">
     <?= $this->element('sidebar_rt/index'); ?>
</div>

Then, set the logic for the sidebar widgets:

// /elements/sidebar_rt/index.ctp

// Include the widget that will always be shown
echo $this->element('widgets/latest_articles');

// Include the widgets that are shown only if the user is logged in
if( !empty( $_user_info ) ){
   echo $this->element('widgets/whos_online');
}

// Include the widget that's shown everywhere except the forums
if( $this->name != 'forums' ){
   echo $this->element('widgets/upgrade_now');
}

// Finally, include the Controller widget, which is named after the controller name
echo $this->element('sidebar_rt/'.$this->name);

Now we’ve got three of our five widgets down.

Next, we just need to set up the controllers for the forums and users widgets

// /elements/sidebar_rt/forums.ctp

// These widgets will be displayed on every page in the forums controller:
echo $this->element('widgets/latest_posts');

// These widgets will only be shown for the corresponding action
switch($this->action){
   case 'edit':
      echo $this->element('widgets/post_edit');
      break;
}

And we’re done!

Using variables in Elements

Remember earlier when we said that we can use our view variables in Elements? Well, since we know when each widget will be used (controller/action), we know what variables will be available to them.

So, let’s look at the ‘post_edit’ widget up above. We want to have buttons to edit the current post, but we need to be able to access the current element’s id and other data in order to make a good edit box.

Luckily, we have all the data from the main view available in the widget as well!

// /elements/widgets/post_edit.ctp

<div class="actions">
   <a href="/posts/publish/<?=$this->data['Post']['id']?>">Publish this post</a>
   <a href="/posts/preview/<?=$this->data['Post']['id']?>">Preview this post</a>
   <a href="/posts/delete/<?=$this->data['Post']['id']?>">Delete this post</a>
</div>

<div class="tags">
   <? foreach($this->data['Tags'] as $tag ):?>
      <a href="/tags/delete/<?=$tag['id']?>"><?=$tag['name']?></a>
   <?endforeach;?>
   <a href="/tags/add/post_id:<?=$this->data['Post']['id']?>">Add a tag</a>
</div>

Conclusion: Really not that hard

Really, getting a good widget system working is fairly simple, and doesn’t require a lot of backend programming, but you need to have a solid idea of what you want to display in your sidebar, and how you want to organize the data.

Remember that your widget data is usually supplemental to your page’s message, and you shouldn’t waste precious processor cycles or memory with a high-overhead system when a simple switch block will do the trick.

Bonus Chatter: Doing it all in one stroke

So, let’s say you don’t like having all those file calls and separation of files: if you want, you can do it all in one “controller” element.

So, like before, we have the same element setup

  • /elements
    • /widgets
      • whos_online.ctp
      • latest_articles.ctp
      • latest_posts.ctp
      • upgrade_now.ctp
      • post_edit.ctp
    • sidebar_rt.ctp

And in the layout, we’re just going to call the simple one liner:

// /layouts/default.ctp
<div class="sidebar right">
     <?= $this->element('sidebar_rt'); ?>
</div>

And then in the sidebar_rt.ctp file, we include a long switch statement

// /elements/sidebar_rt.ctp

// Include the widget that will always be shown
echo $this->element('widgets/latest_articles');

// Include the widgets that are shown only if the user is logged in
if( !empty( $_user_info ) ){
   echo $this->element('widgets/whos_online');
}

// Include the widgets for each controller / action page
switch( $this-> name ){
   case 'users':
      echo $this->element('widgets/upgrade_now');
      break;
   case 'forums':
      echo $this->element('widgets/latest_posts');
      break;
   case 'posts':
      switch( $this->action ){
         case 'edit':
            echo $this->element('widgets/post_edit');
            break;
      }
      echo $this->element('widgets/upgrade_now');
      break;
   default':
      echo $this->element('widgets/upgrade_now');
      break;

}

It’s a little more unwieldy than the separated version, but if you only have a few controllers that have special cases it might be easier to have all your widget data in one place.

Have any comments? Know of a better way? I’d love to hear them!

Taking the pain out of CakePHP deployment: Batch Scripts and SVN

April 23rd, 2010 by Keith Perhac 8 comments »

If you take a look at the update rate of this blog, or the wait time on my email responses, or even the changelogs from my Subversion repository, you will notice one thing — I am a horrible procrastinator.

Well, not actually true. I’m a great procrastinator.

I found out a long time ago that something I think will take 10 minutes ends up taking an hour, and things I think will take an hour end up taking 10 minutes. The downside to this thought pattern is that it makes it hard for me to start anything I know that I can’t get done in 1 minute, unless I have a lot of time to kill on it.

The more steps there are to a process, the less likely I am to feel that I can get it done in the small amount of time I have allowed.

What is the process for deploying a project with CakePHP?

The process for deploying a cakePHP website is monotonous enough, but at the same time contains enough fiddly bits to be annoying to do more than once in a blue moon.

  1. Export the data from the SVN to the directory of your choice (dev or live in my case)
  2. If you’re anal about keeping logs of what you exported (like me), write the version info to a file.
  3. Update the config file so that your local version (which is probably running in debug 1 or 2) runs without debug info.
  4. Remove all the old cache data in case you made any changes to your models
  5. Done

Kind of a pain in the rear.

And we all know what are great for repetitive tasks with lots of fiddly steps that are a pain in the rear:

Enter the bash file

I created a simple batch file that I can run directly on my SliceHost server. It allows me to deploy a specific folder from my SVN, pick whether to deploy to the dev site or the live site, clear the cache, and set the debug settings.

Now let’s do it in one line

./deploy live app

Done.

Here’s what comes out the other end:

Setting LIVE
.....
Lots of SVN Export Data
.....
Reset Config File: [livesite]/app/config/core.php
Cleared out Cache

This gives me a sense of security when updating my site, because I know that I won’t have forgotten any of the tiny repetitive details, and can fill my deploy checklist with more important things (such as “Check that users can login”) instead of “Set Debug to 0.”

Here’s the Code

I know, I know. All you want is the code. Well here you go. Feel free to edit and use as you like, but be sure to set your svn and target paths.

#!/bin/sh
# usage: ./deploy TYPE DIR
# TYPE = live | (anything else)
#
# Be sure to edit the following:
# _MY_SVN_DIR_ -- the base SVN dir for this project
# _MY_LIVE_PATH_ ex: /home/ippatsu/japanesetesting.com/public
# _MY_DEV_PATH_ ex: /home/ippatsu/beta.japanesetesting.com/public

# Set as Life or Dev
if [ "$1" != "live" ]; then
echo "Setting DEVsite"
TARGETP="_MY_DEV_PATH_"
else
echo "Setting LIVE"
TARGETP="_MY_LIVE_PATH_"
fi

# Export Data
svn export --force svn://127.0.0.1/_MY_SVN_DIR_/$2  $TARGETP/$2

# Write Version to File
svn info svn://127.0.0.1/_MY_SVN_DIR_/$2 > $TARGETP/version.txt
svn info svn://127.0.0.1/_MY_SVN_DIR_/$2

# Update Config File
OLD="Configure::write('debug', 2);"
NEW="Configure::write('debug', 0);"
CONFIGFILE="$TARGETP/app/config/core.php"

sed -i "s/$OLD/$NEW/g" $CONFIGFILE
echo "Reset Config File: $CONFIGFILE"

# Clearout Cake Model Cache
rm $TARGETP/app/tmp/cache/models/cake*
rm $TARGETP/app/tmp/cache/persistent/cake*
rm $TARGETP/app/tmp/cache/views/cake*
echo "Cleared out Cache"

exit 0

Hope you enjoy it, and if you have any advice or questions, just leave them in the comments!

The Programmer’s Folly: Simple is best

November 24th, 2009 by Keith Perhac 4 comments »

First, let me get this out in the open: I am not a marketer. I am not a content-organizer. I am a programmer. I like writing code, and I like creating new ways to do things. I like making things because it helps me see how they work — out-of-the-box-solutions bore me.

And that’s where I screw up

On Monday it was a national holiday here in Japan (勤労感謝の日) and I had about 4 hours to kill while the wife was out on her daily walk. I decided to work on my site — and what I needed more than anything was a nice heatmap to log user clicks.

I used the amazing ClickHeat which is a free heat-mapping application written in PHP. It works right out of the box, is amazingly configurable, and runs quickly thanks to it being written in clear PHP.

Wouldn’t this be great as a CakePHP Plugin!

This is where things start to get messy.

I love CakePHP. It makes development easy, is rather fast, and is highly extensible. So, I decided to wrap the ClickHeat application in a cake plugin, make it “easy to integrate” and put it in the CakePHP Bakery, instantly receiving fame, fortune and the accolades of my peers!

4 hours later

I had a mostly-working prototype that logged the data beautifully (although I couldn’t decide how to group the data on a dynamic site),  but didn’t have any of the nifty functions of the ClickHeat software such as sorting by date, admin panels, etc. because I hadn’t built those views yet. But I was hopeful!

Then my wife came home and I stopped programming for the day.

The next day, in 10 minutes

I decided to install ClickHeat on one of my corporate sites at work.

  1. Copy ClickHeat folder
  2. Set Cache & Log permissions
  3. Turn on Japanese Interface
  4. Done

10 minutes. In ten minutes I had accomplished what I couldn’t complete in 4 hours, because I was prepared to use an out-of-the-box solution instead of trying the be the programming bad-ass and integrate it with CakePHP.

So this morning, I copied the folder to my CakePHP dir, made one change to the .htaccess file:

RewriteRule    clickheat/(.*)   -   [L]

And now I have working heatmaps on my site. (I also put them on my wordpress blog, so that you can see them in action for yourself).

See the amazing Japanese Programming Heatmaps! (User: demo / Password: demo)

The final Score

So, what’s the final score?

Being a “bad-ass” programmer

  1. 4 Hours Development
  2. 1 extra hour of planning before sleep
  3. No Viewing Functionality
  4. Installation NOT user friendly
  5. Call time of 300ms per click for spinning up the cake processor
  6. Logging Works

Being a smart programmer

  1. 1 Change in my htaccess file
  2. 10 minutes to install/configure
  3. Call time of 150ms / click
  4. Not following “best practices” for cakephp

I think it’s pretty obvious what the best choice here is.

While I adore CakePHP and the things it lets you do simply by following convention — it’s very easy to get sucked into the “Best Practice” mindset, and waste a lot of time working on something that honestly doesn’t need to be tinkered with.

Don’t re-invent the wheel

It’s amazing how many times you can read that phrase and still find yourself re-inventing wheel after wheel after wheel. If you have a tool that does a job — use it. Do not worry about your fiddly code, and your “it’s not made here” mentality.

If the application is LACKING you can always edit it. But it makes no sense to start with something that does everything that you want, and then try to hack it apart just because you can. (Although I have to admit, it can be fun)

With those four hours, I could have

  • written 1 article for my site,
  • uploaded around 10 flashcard packs
  • added 2 tests to the site
  • read a book
  • played a lot of video games
  • watched a movie
  • watched four (4) episodes of Doctor Who or The Green Wing.

So Keith, this is a message to you:

  • Don’t be a tool
  • Use your time wisely
  • If something works, do NOT break it just because you want to see how it works.
  • (Eat your veggies — love mom)

CakePHP DebugKit: Take Back your Debugging

November 12th, 2009 by Keith Perhac 1 comment »

Yet another foray into the WORD: The Subtitle blog posts; today we’re going to look at the amazing features of the CakePHP DebugKit.

The DebugKit is a standard plugin for CakePHP written by Mark Story. It provides a lot more information that the standard debugging features of cake (Errors, warnings, SQL Queries) and also keeps them in a nice contained collapsible icon at the top of your page so you don’t have to have error messages and sql queries appearing all over the page.

The Debug Kit Interface

DebugKit Icon

DebugKit Icon

Click the Cake icon at the top of your page, and it expands to all the tools you’ll need.

DebugKit Expanded

DebugKit Expanded

If you click any of the titles there, it shows you various debug information such as the current Session information, how the Request was processed, the SQL Log, a timer for all the functions that were called, currently accessible variables and the current memory usage.

DebugKit Variables View

DebugKit Variables View

DebugKit Timer View

DebugKit Timer View

All in all, an amazing amount of information. And best of all? It stays out of the way so your designs don’t get cluttered with debug information while you’re testing.

Installation

Installation is easy.

Download

Download the debug kit from ohloh. http://www.ohloh.net/p/cakephp-debugkit

Install

Place the DebugKig in the plugins folder of your application:

/app/plugins/debug_kit/[INSERT ME HERE]

Connect

Add the DebugKit Toolbar component to your app_controller, so it will be available to all your controllers.


// app_controller.php
var $components = array('DebugKit.Toolbar');

Then set the debug mode to at least 1.

Problems

DebugKit isn’t a perfect system (or maybe my understanding of it isn’t quite perfect). In any case, these are some problems I’ve had with DebugKit, but even they’re not enough to really gripe about.

Speed

DebugKit does seem to take a lot more processing power to run it than the standard Debug functions of cake.

This is easy to understand, though, as with the timers, reporters and everything, it’s doing quite a bit more than the standard Debug functions, and gives you a lot more information. I’ve had my dev-laptop exceed the 30sec execution time limit quite a few times while running DebugKit, but nothing like that has ever happened on my dev-server, so I doubt it’s anything major. It does make navigation a bit slower though, so when testing user flow, I would recommend turning it off by setting the Debug level to 0 for the duration of the session.

And remember, if you switch your debug to 0, then the DebugKit isn’t run at all, so there’s no worries about it eating precious resources on your live server.

Ajax Reporting

Ajax doesn't seem to be affected

Ajax doesn't seem to be affected

When you make a request with Ajax, the DebugKit doesn’t seem to get spun up like it should — which results in it reverting to the standard CakePHP reporting, which (while not bad) is definitely a grade down compared to the glory of DebugKit. It would have been nice to have the reporting fed into another icon inside the div that displays the ajax result, or something to that effect. Perhaps there is a way, and I just haven’t found it yet. Always a possibility.

Conclusion

Really, there isn’t much else to say except: why are you waiting? Go download this thing now!

Return of the FaceBox: Iframes!

October 19th, 2009 by Keith Perhac 4 comments »

This article is a follow-up to my (oddly?) popular article Simple Modal Boxes: FaceBox with Prototype which, as the name implies, talks about how to get FaceBox ( the amazingly cool JQuery Facebox-like modal window) to work with the Prototype framework, and then releases that code under the MIT license.

Ippatsu's Flashcard FaceBox

Ippatsu's Flashcard FaceBox

Where things went wrong: IFrames

I was fairly forthcoming about it when I released version 1.1, but my FaceBox had a major problem: Loading full HTML pages (as opposed to html snippets) borked the system. It was a combination of the <head> and <body> tags along with all that excess Javascript and CSS being loaded into an inline div that pretty much did it in. IE6 would display it (poorly) while anything else just gave up and displayed an empty box.

So I fixed it.

Starting with version 1.2 you can set a facebox to load in an IFrame by simply adding the tag “iframe” to the rel tag of the link.

So
<a rel="facebox" href="mypage.html">My Page!</a>

becomes
<a rel="facebox iframe" href="http://google.com">Google</a>

and now you’re cooking with crisco. The page will be loaded in an IFrame, and any links will be self-contained within that iframe unless you specifically break out of it.

Breaking backwards compatibility

So, I am a big believer in backwards compatibility, but unfortunately adding the iframe option, along with my desire to adhere to better HTML coding practices has made me break backwards compatibility with version 1.1.

But wait!

It’s simple to change over, and you can change over almost all your code with a simple search/replace! So let’s get to it!

New way to handle classes

The old way of handing classes was to simply put all the classes you wanted to use after a hyphen after the facebox tag, like so:

<a href="mypage.html" rel="facebox-class1 class2">My Classy Page!</a>

This brings about the problem that now “iframe” is just thought of as a class. (It’s also not very contained, and rather messy). So I changed to to be more self-contained by putting the classes in square brackets []:

The new Code

<a href="mypage.html" rel="facebox[class1 class2]">My Classy Page!</a>

Now you know exactly what is a class, and what is not, and it’s easier to use it in conjunction with the IFrames option above.

<a href="http://google.com" rel="facebox[class1 class2] iframe">This loads google in its own classy iframe</a>

Simple as pie.

New way to handle styles

Originally, my way to handle styles (useful for setting the height and width of a Facebox for a single message or page) was to put the data in the rev tag and just pass that through to the FaceBox. However, with the new class syntax, I had a chance to change the way that the styling works.

Also, the rev tag is used to describe how the current document applies to the link, so it really isn’t a good place to put the data syntactically. It’s much better to put it in the rel tag along with the class information.

The new code

So where you used to use the rev tag:
<a href="mypage.html" rel="facebox" rev="width: 500px; height: 300px">My Stylish Page!</a>

You'll now use curly brackets in the rel tag:
<a href="mypage.html" rel="facebox{width: 500px; height: 300px}">My Stylish Page!</a>

And we can combine that with the iframes and classes above, to further customize our facebox

Class and Style
<a href="mypage.html" rel="facebox[class1 class2]{width: 500px; height: 300px}">My Classy Stylish Page!</a>

Iframe Class and Style
<a href="http://google.com" rel="facebox[class1 class2]{width: 500px; height: 300px} iframe">This loads google in its own classy and stylish iframe</a>

Download the code

You can download the code right here! It’s Open sourced under the MIT License.

If you find any problems or bugs, feel free to drop me a comment!

Download FaceBox v 1.2 (Open sourced under the MIT License)

Things I never knew about CSS

October 6th, 2009 by Keith Perhac No comments »

Smashing Magazine, for all their “Top 10″ and “Best 50″ articles, continues to occasionally post very helpful and mind-bending articles about Design and Production.

Their newest article about CSS is aimed at the CSS beginner, but has a few tricks that I wasn’t even aware of.

Floating and Overflow

The first, I want to point out is something that has always bugged me: the fact that a floated image is not completely contained within its parent container. Apparently this can be fixed simply by adding overflow:hidden to the div (autoflow:auto also works).

Floats and Images

Floating and Double Margins (ie6 Bug)

Additionally, Internet Explorer 6 seems to double the margin of floated elements. So your 5px margin becomes 10px in IE6, and is the cause of all of my perfectly aligned floating divs crashing to the ground when I test the page in IE6 (grumble grumble hate hate)

This is also easy to fix: just add display: inline to your floated element.

.floated_element {
     float: left;
     width: 200px;
     margin: 5px;
     display: inline; /*--IE6 workaround--*/
     }

Deserves a once-over

There were alot of other great tips and points that even seasoned CSS-ers should look at — you never know when you might find something new, or an easy way to do something you had been futzing with. If you get a chance, it’s definitely worth a look.

Delete Cached Models in CakePHP

September 15th, 2009 by Keith Perhac No comments »

Did you know that CakePHP automatically caches a lot of your data so that you don’t have to call (for example) model references over and over again? Isn’t that nice?

Did you know that if you make a change to your database (for example: adding a column) and the caches haven’t been updated, it will neither SAVE nor RETRIEVE any data relating to those changes?

Did you know that cake won’t tell you that your models might be out of date, and that you can easily waste 2-3 HOURS of your life on something stupid like that?

Do yourself a favor: When you change your database, delete EVERYTHING in app\tmp\cache\models

And thankyou for the heads up, Snook.ca

WordPress and Syntax Highlighting

September 12th, 2009 by Keith Perhac No comments »

Just a quicky:

I was looking for some good syntax-highlighting plugins for WordPress, but so many of them were hard to use, required configuration or just plain mangled my code.

So then I found Syntax Highlighter Evolved. Set it up in 5 minutes, easy as pie to use, and no more code mangling. Definitely recommended.

Syntax Highlighter Evolved is recommended by some guy in Japan!

Now that’s a tagline that will sell products if I ever heard one!

CakePHP and RememberMe — AutoLogins for the soul

September 11th, 2009 by Keith Perhac 4 comments »

So, as I as spent half the day today working on getting my “Remember Me” function to work, I figured it would be a good thing to share with the rest of the world, so that they didn’t have to ever EVER put up with the kind of stuff I went through today.

So, first of all, we all know what a remember me function is, right?

Yup, that's it (WordPress)

Yup, that's it (WordPress)

Yup, that's it (google)

And there it is again (google)

The “Remember me” function is a little checkbox that the user checks when they log in so that they don’t have to bother logging in again as long as they keep visiting the site every XX days where XX is the number of days that the site will remember them for.

Sounds easy! Let’s do it in cake!

Great idea! Cake should make it super easy! And if you search for cake and remember me functions, a lot (or rather a lot of variations based around 2 or 3 guys’ code) of sites will pop up. I personally recommend the RememberMe component from the Neutrino CMS, as it’s small, lightweight and doesn’t take much configuring. (And don’t believe the page that comes up when you enter “CakePHP rememberme” into google. That’s what got me off on the wrong foot)

And if you plan on something really simple with your site (i.e. logged in or not logged in) then it’s really easy to set up.

  1. Get the component that you’re going to use (I used RememberMe)
  2. Plop it in the components.
  3. Set your references to it in your users/login function, and
  4. (The most important part) attach the component’s cookie checking function to the app_controller’s beforeRender function.

Now, why’s the last step the most important? Because the “remember me” cookie needs to be checked for every time a page is visited, otherwise it can’t do it’s job. The first version of the remember me code I had used when I first started CakePHP had the cookie management in the users/login function. Like this:

function login() {
//-- code inside this function will execute only when autoRedirect was set to false (i.e. in a beforeFilter).
if ($this->Auth->user()) {
if (!empty($this->data) &amp;&amp; $this->data['User']['remember_me']) {
$cookie = array();
$cookie['username'] = $this->data['User']['username'];
$cookie['password'] = $this->data['User']['password'];
$this->Cookie->write('Auth.User', $cookie, true, '+2 weeks');
unset($this->data['User']['remember_me']);
}
$this->redirect($this->Auth->redirect());
}
if (empty($this->data)) {
$cookie = $this->Cookie->read('Auth.User');
if (!is_null($cookie)) {
if ($this->Auth->login($cookie)) {
//  Clear auth message, just in case we use it.
$this->Session->del('Message.auth');
$this->redirect($this->Auth->redirect());
} else { // Delete invalid Cookie
$this->Cookie->del('Auth.User');
}
}
}
}

Makes perfect sense, right?

NO

The problem is that users/login only gets called when there’s a login to be processed (which is not explained very well in the documentation). This becomes a problem when you are using the Auth->allow() array to let people access parts of the site, and not others. (Even more of a problem when the same page has different data for logged in and non-logged in users)

What happens is that since the action is allowed, Cake decides that there’s no reason to check for a login, so even if the login cookie exists, it doesn’t get checked because the Users/Login function never gets called.

So, in most cases, this is not a problem — just put the RememberMe->check() function into the AppController’s beforeRender — but what if you want do something more after a user logs in? Something like increasing their login count, setting login time, getting info about them, etc etc.

Custom Functions and Remember Me

Most RememberMe components and solutions have a provision for callbacks after a successful cookie login (much like isAuthorized) however (also like isAuthorized) the function has to be in either:

  • The AppController
  • The Current Controller

Because the component only has access to the controller that’s currently calling it, you’re confined to either the code in the AppController or the currently viewed controller. And if your current controller is Articles (because your user is looking at an article) then we can’t directly access the users controller to run our little _after_login function. (With Models you can get to from almost anywhere in your code through skillful use of relations, but Controllers are a bit trickier)

So, what do we do? We take a little hint from the cake library, and use the wonderful App:import function to forcibly bring the Controller to us. Now this is an expensive call, because it’s essentially ringing up an entire new controller with all its associated models, et al. So we need to be sure to do this only when the user is logging in through a cookie. That way, it’ll only happen once for the user’s session, and we can quickly get rid of its memory-eating load on the next page view.

So, let’s look at some code:

The Original RememberMe component

<?php
class RememberMeComponent extends Object
{
var $components = array('Auth', 'Cookie');
var $controller = null;

/**
* Cookie retention period.
*
* @var string
*/
var $period = '+2 weeks';
var $cookieName = 'User';

function startup(&$controller)
{
$this->controller =& $controller;
}

function remember($username, $password)
{
$cookie = array();
$cookie[$this->Auth->fields['username']] = $username;
$cookie[$this->Auth->fields['password']] = $password;
$this->Cookie->write($this->cookieName, $cookie, true, $this->period);
}

function check()
{
$cookie = $this->Cookie->read($this->cookieName);

if (!is_array($cookie) || $this->Auth->user())
return;

if ($this->Auth->login($cookie))
{
$this->Cookie->write($this->cookieName, $cookie, true, $this->period);
}
else
{
$this->delete();
}
}

function delete()
{
$this->Cookie->del($this->cookieName);
}
}

?>

So, this check function has got to go

We need to be able to load the UsersController so that we can get to our _post_login() function to do all our login magic like checking how the user’s subscription is going, how long many times they’ve logged in, etc.

function check()
{
// If you want to change the cookie name, change it here
$this->Cookie->name = 'rememberme';
$cookie = $this->Cookie->read($this->cookieName);

if (!is_array($cookie) || $this->Auth->user())
return;

if ($this->Auth->login($cookie))
{
$this->Cookie->write($this->cookieName, $cookie, true, $this->period);
if (!App::import('Controller', 'Users')) {
return false;
}
$className = 'UsersController';
$Ctrl =& new $className();
$Ctrl->constructClasses();
$Ctrl->_post_login($this->Auth->user('id'));
}
else
{
$this->delete();
}
}

So, here’s my code as it stands now. What I’ve done is if there is a cookie, and you can successfully login with said cookie (Through $this->Auth->login($cookie) ) then we’re going to spin up the Controller called Users through the App:import function. If it can’t load, then we return false, but if it DOES load, then we create a new Controller and construct it so that we can then call our post_login script with the Auth->user Info. Everything else is set up just how the instructions on the RememberMe Component Page say.

Conclusion

Getting a RememberMe function working in CakePHP really isn’t that difficult, but the problem (like a lot of CakePHP) is that the signal-to-noise ratio of those who know what they’re doing and those that don’t is incredibly high. I don’t profess to have all the answers, but hopefully just some of the problems that I’ve had will help others who are struggling with the same issues.

Till next time!