First thing I love in Ruby is an ability to extend classes with my own methods. I could just add method to_permalink to any string and then everywhere I could write something like @post.title.to_permalink. It’s amazing!
Here is my version of to_permalink method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | class String def to_permalink result = strip_tags # Preserve escaped octets. result.gsub!(/-+/, '-') result.gsub!(/%([a-f0-9]{2})/i, '--\1--') # Remove percent signs that are not part of an octet. result.gsub!('%', '-') # Restore octets. result.gsub!(/--([a-f0-9]{2})--/i, '%\1') result.gsub!(/&.+?;/, '-') # kill entities result.gsub!(/[^%a-z0-9_-]+/i, '-') result.gsub!(/-+/, '-') result.gsub!(/(^-+|-+$)/, '') return result.downcase end private def strip_tags return clone if blank? if index('<') text = '' tokenizer = HTML::Tokenizer.new(self) while token = tokenizer.next node = HTML::Node.parse(nil, 0, 0, token, false) # result is only the content of any Text nodes text << node.to_s if node.class == HTML::Text end # strip any comments, and if they have a newline at the end (ie. line with # only a comment) strip that too text.gsub(/<!--(.*?)-->[\n]?/m, '') else clone # already plain text end end end |
How it’s working? First thing you would see is a private method strip_tags. Yes, I know about ActionView::Helpers::TextHelper::strip_tags, and this is almost 100% copy of Rails version (the only difference is that my version always returns clone of the original string). I just don’t want to rely on the Rails library.
Then my method replaces all special characters with dashes (only octets like %A0 would be kept), and trims dashed from the beginning and the end of the string. Finally full string will be lowercased.
Of course, in your application you should check collisions (several posts which have the same title should have unique permalinks, for example you could append numbers starting from 1: hello, hello-1, hello-2, etc). This is not my goal to cover all difficulties you could face, it’s small post, do you remember?
Just for your pleasure, here are the RSpec tests for this method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | describe 'String.to_permalink from extensions.rb' do it 'should replace all punctuation marks and spaces with dashes' do "!.@#$\%^&*()Test case\n\t".to_permalink.should == 'test-case' end it 'should preserve _ symbol' do "Test_case".to_permalink.should == 'test_case' end it 'should preserve escaped octets and remove redundant %' do 'Test%%20case'.to_permalink.should == 'test-%20case' end it 'should strip HTML tags' do '<a href="http://example.com">Test</a> <b>case</b>'.to_permalink.should == 'test-case' end it 'should strip HTML entities and insert dashes' do 'Test case'.to_permalink.should == 'test-case' end it 'should trim beginning and ending dashes' do '-. Test case .-'.to_permalink.should == 'test-case' end it 'should not use ---aa--- as octet' do 'b---aa---b'.to_permalink.should == 'b-aa-b' end it 'should replace % with -' do 'Hello%world'.to_permalink.should == 'hello-world' end it 'should not modify original string' do s = 'Hello, <b>world</b>%20' s.to_permalink.should == 'hello-world%20' s.should == 'Hello, <b>world</b>%20' s = 'Hello' s.to_permalink.should == 'hello' s.should == 'Hello' end end |
It’s funny, right?
The post Generating permalink from string in Ruby first appeared on Dmytro Shteflyuk's Home.]]>
Download and unpack plugin files to wp-content/plugins/scategory_permalink directory.
Enable “sCategory Permalink” plugin on your Plugins page in Site Admin.
Go to the Options/Permalinks page in Site Admin and use %scategory% option in Custom text field (you can look here for other options). In this blog I’m using /%scategory%/%postname%/.
Now on Write Post page near the categories checkboxes radio button will appear:
Select radio button near category which will be used in permalink.
Have fun!
You could always download latest version of the plugin here.
The post WordPress Plugins: sCategory Permalink – select category for permalink generation first appeared on Dmytro Shteflyuk's Home.]]>
There some inconsistence in Zend Framework (maybe because this is first release). To create my own router I need to develop class which implements Zend_Controller_Router_Interface interface:
1 2 3 4 5 6 7 8 9 10 11 | interface Zend_Controller_Router_Interface { /** * Processes an HTTP request and routes to a Zend_Controller_Dispatcher_Action object. If * no route was possible, an exception is thrown. * * @throws Zend_Controller_Router_Exception * @return Zend_Controller_Dispatcher_Action|boolean */ public function route(); } |
Zend_Controller_Front uses this interface in following way:
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Zend_Controller_Front { // ... /** * Dispatch an HTTP request to a controller/action. */ public function dispatch() { // ... $action = $this->getRouter()->route($this->getDispatcher()); // ... } } |
Fine joke from Zend Framework’s developers. Front controller passes dispatcher to router, but router interface does not receive it. :-) Simplest way to avoid PHP error message is to define parameter which has default value null. Here my router class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | /** Zend_Controller_Router_Interface */ require_once 'Zend/Controller/Router/Interface.php'; /** Zend_Controller_Dispatcher_Interface */ require_once 'Zend/Controller/Dispatcher/Interface.php'; /** Zend_Controller_Router_Exception */ require_once 'Zend/Controller/Router/Exception.php'; /** Zend_Controller_Dispatcher_Action */ require_once 'Zend/Controller/Dispatcher/Action.php'; class SubDirectoryRouter implements Zend_Controller_Router_Interface { public function route(Zend_Controller_Dispatcher_Interface $dispatcher = null) { // SubDirectoryRouter: what's the path to where we are? $pathIndex = dirname($_SERVER['SCRIPT_NAME']); // SubDirectoryRouter: remove $pathIndex from $_SERVER['REQUEST_URI'] $path = str_replace($pathIndex, '', $_SERVER['REQUEST_URI']); if (strstr($path, '?')) { $path = substr($path, 0, strpos($path, '?')); } $path = explode('/', trim($path, '/')); /** * The controller is always the first piece of the URI, and * the action is always the second: * * http://zend.com/controller-name/action-name/ */ $controller = $path[0]; $action = isset($path[1]) ? $path[1] : null; /** * If no controller has been set, IndexController::index() * will be used. */ if (!strlen($controller)) { $controller = 'index'; $action = 'index'; } /** * Any optional parameters after the action are stored in * an array of key/value pairs: * * http://www.zend.com/controller-name/action-name/param-1/3/param-2/7 * * $params = array(2) { * ["param-1"]=> string(1) "3" * ["param-2"]=> string(1) "7" * } */ $params = array(); for ($i=2; $i<sizeof($path); $i=$i+2) { $params[$path[$i]] = isset($path[$i+1]) ? $path[$i+1] : null; } $actionObj = new Zend_Controller_Dispatcher_Action($controller, $action, $params); if (!$dispatcher->isDispatchable($actionObj)) { /** * @todo error handling for 404's */ throw new Zend_Controller_Router_Exception('Request could not be mapped to a route.'); } else { return $actionObj; } } } |
Sample usage:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | require('Zend.php'); Zend::loadClass('Zend_Controller_Action'); Zend::loadClass('Zend_Controller_Front'); Zend::loadClass('Zend_Filter'); Zend::loadClass('Zend_InputFilter'); Zend::loadClass('Zend_View'); Zend::loadClass('SubDirectoryRouter'); $controller = Zend_Controller_Front::getInstance(); $controller->setControllerDirectory('controllers'); $router = new SubDirectoryRouter(); $controller->setRouter($router); $controller->dispatch(); |
To make it work you need to place SubDirectoryRouter to the SubDirectoryRouter.php file and put it under include path.
The post Zend Framework: Router for subdirectory-based site first appeared on Dmytro Shteflyuk's Home.]]>