*This extension is now updated to work with non latin characters and and works with 1.2
This extension is to save pretty url's from titles to be used as unique identifier's
Note it overwrite's beforeSave... use:
return parent::beforeSave();
In your model if it contains beforeSave method..
http://www.yiiframework.com/forum/index.php?/topic/4485-slug-behavior/
Discussion
http://www.yiiframework.com/forum/index.php?/topic/4485-slug-behavior/
Documentation ¶
Requirements ¶
one primary key like id, use utf8 strings
Installation ¶
- Extract the release file under
models/behaviors
Usage ¶
Add in model..
public function behaviors(){
return array(
'SlugBehavior' => array(
'class' => 'application.models.behaviors.SlugBehavior',
'slug_col' => 'slug',
'title_col' => 'title',
'max_slug_chars' => 125,
'overwrite' => false
)
);
}
Table example:
CREATE TABLE IF NOT EXISTS `pages` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(125) NOT NULL,
`slug` varchar(125) NOT NULL,
`body` text,
PRIMARY KEY (`id`),
UNIQUE KEY `slug` (`slug`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=0 ;
*Aditional notes becarefull of non latin languages they need to be displayed normally with urldecode(); and take up alot of database characters as they are long like %23# etc :P
Change Log ¶
June 3, 2010 ¶
Generate slug only if it needs to be saved.. don't clean non latin characters..
September 22, 2009 ¶
- Update uses primary key to see if the slug has changed so it not keeps changing the slug
September 20, 2009 ¶
- Initial release.
Hey :)
Thanks for the comments guys.. I have updated the Behavior with your suggestions... I also replaced the iconv with the same functions from Wordpress as I had some problems with iconv and asian languages.. Now they are still being saved as slug :)
And it will not generate the slug anymore if it does not have to be saved ;)
And it will work on Yii 1.1 / 1.2..
@zitter.. My behavior did check if the slug was unique before ;) and would add the database id if there was a duplicate.. only with asian characters it would be empty.
Cool behavior
Mech7 thanks for sharing this. If you want to improve I suggest you to make slug 'unique'. IMO in most cases slug has sense if it is unique; so I think you could add a mechanism to make each slug unique.
Good!
on svn 1.1 version,
must be beforeSave($event) ?
Code got messed up
The final code block there (main config) has code missing. Markdown was dropping anything inside ' that was also inside <>. It should be:
return array( ... 'components'=>array( ... 'urlManager'=>array( 'urlFormat'=>'path', 'showScriptName'=>false, 'rules'=>array( ... '<slug:[a-zA-Z0-9_ -]+>'=>'pages/show/slug/<slug>', ... ), ), ... ), );
Updated slugBehaviour
I've modified this behaviour a bit inline with jonah's suggestions and my own thoughts.
Most of my changes are to the function beforesave(). I also changed getSlug() to return the AR by ID.
SlugBehavior.php
<?php /** * SlugBehavior * * Saves pretty url's from titles to be used as unique identifier's * * @author Chris de Kok <chris.de.kok@gmail.com> * @copyright Copyright (c) 2009 Chris de Kok. (http://mech7.net) * */ class SlugBehavior extends CActiveRecordBehavior { /** * The column name for the unqiue url */ public $slug_col = 'slug'; /** * The column name for the title */ public $title_col = 'title'; /** * Primary key column name needs to be an id * @var string */ private $pk_col; /** * Character to replace spaces * @var string */ public $replace_space = '-'; /** * Overwrite slug when updating */ public $overwrite = true; /** * Before saving to database */ public function beforeSave() { $this->pk_col = $this->Owner->getMetaData()->tableSchema->primaryKey; $new_slug = $this->makeSlug($this->Owner->{$this->title_col}); $old_slug = $this->getSlug($this->Owner->{$this->pk_col})->{$this->slug_col}; if($new_slug != $old_slug) { /* * Either this is a new page, or the page title has been updated */ if (!$old_slug || $this->overwrite === true) { $this->Owner->{$this->slug_col} = $new_slug; } } return true; } /** * Lookup current slug for a page * @param string $id */ public function getSlug($id){ $slug=$this->Owner->findByPk($id); return $slug; } /** * Convert string to slug * @param string $title * @return mixed $slug */ public function makeSlug($title){ $clean = iconv('UTF-8', 'ASCII//TRANSLIT', $title); $clean = preg_replace("/[^a-zA-Z0-9\/_| -]/", '', $clean); $clean = strtolower(trim($clean)); $clean = preg_replace("/[\/_| -]+/", $this->replace_space, $clean); return $clean; } }
I added into my protected/config/params.php file:
return array( ... // Overwrite page slugs when updating 'overwriteSlugs'=>false, );
Which allows me to change whether slugs get updated for the entire site via a single location.
Then in my Model files (where I want the slugs, leaving all of the other options at their default):
public function behaviors(){ return array( 'SlugBehavior' => array( 'class' => 'application.models.behaviors.SlugBehavior', 'overwrite' => Yii::app()->params['overwriteSlugs'], ), ); }
In my controllers I can load pages using:
/** * Returns the data model based on the primary key given in the GET variable. * If the data model is not found, an HTTP exception will be raised. * @param integer the primary key value. Defaults to null, meaning using the 'id' GET variable */ protected function loadPage($id=null,$slug=null) { if($this->_page===null) { if($slug!==null || isset($_GET['slug'])) { $criteria=new CDbCriteria; $criteria->condition='slug=:slug'; $criteria->params=array(':slug'=>Pages::model()->makeSlug($_GET['slug'])); $this->_page=Pages::model()->find($criteria); } if($id!==null || isset($_GET['id'])) $this->_page=Pages::model()->findbyPk($id!==null ? $id : $_GET['id']); if($this->_page===null) throw new CHttpException(404,'The requested page does not exist.'); } return $this->_page; }
Which gets the slugs passed to it from the urlManager (in config/main.php):
return array( ... 'components'=>array( ... 'urlManager'=>array( 'urlFormat'=>'path', 'showScriptName'=>false, 'rules'=>array( 'tag/<tag>'=>'post/list', 'posts'=>'post/list', 'post/<id:\d+>'=>'post/show', 'post/update/<id:\d+>'=>'post/update', '<slug:[a-zA-Z0-9_ -]+>'=>'pages/show/slug/<slug>', ), ), ... ), );
Hope this helps someone in getting slugs working how they (or at least I) expect.
Cool!
good job!
Some notes:
1) pk_col attribute could be protected
2) This behavior generates the slug even if it's not going to be saved
3) there could be a condition in getMatches() so that it does not return the AR in question. This will allow you to get rid of a whole foreach loop.
4) perhaps prefix slug with the PK value, so that you don't have to worry about making sure there are no duplicate slugs.
Awesome but...
Nice extension but I had to make the following changes to make it work.
add: public $max_slug_chars = 125; change 63: $matches = $this->getMatches($this->Owner->slug); to 63: $matches = $this->getMatches($this->Owner->{$this->slug_col}); and change 70: $ar_matches[] = $match->slug; to 70: $ar_matches[] = $match->{$this->slug_col};
Otherwise it seems to work nicely
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.