Showing posts Blog->filterDescription(); ?>, Html->link(__('Show all', true), array('action' => 'index')); ?>
5 | 6 | 7 | 8 | 9 | 10 | 11 |├── Config ├── routes.php └── schema │ └── schema.sql ├── Controller ├── BlogPostCategoriesController.php ├── BlogPostTagsController.php ├── BlogPostsController.php └── BlogSettingsController.php ├── Model ├── BlogPost.php ├── BlogPostCategory.php ├── BlogPostTag.php └── BlogSetting.php ├── README.markdown └── View ├── BlogPostCategories ├── admin_add.ctp ├── admin_edit.ctp ├── admin_index.ctp └── admin_view.ctp ├── BlogPostTags ├── admin_add.ctp ├── admin_edit.ctp ├── admin_index.ctp └── admin_view.ctp ├── BlogPosts ├── admin_add.ctp ├── admin_edit.ctp ├── admin_index.ctp ├── admin_view.ctp ├── index.ctp ├── rss │ └── index.ctp └── view.ctp ├── BlogSettings ├── admin_edit.ctp ├── admin_index.ctp └── admin_view.ctp ├── Elements ├── archives.ctp ├── categories.ctp ├── rss.ctp ├── share.ctp └── tag_cloud.ctp └── Helper ├── BlogHelper.php └── blog.php /Config/routes.php: -------------------------------------------------------------------------------- 1 | 6 | * @link http://www.neilcrookes.com http://neil.crook.es 7 | * @copyright (c) 2011 Neil Crookes 8 | * @license MIT License - http://www.opensource.org/licenses/mit-license.php 9 | */ 10 | 11 | Router::parseExtensions('rss'); 12 | 13 | Router::connect( 14 | '/blog/:year/:month/*', 15 | array( 16 | 'plugin' => 'blog', 17 | 'controller' => 'blog_posts', 18 | 'action' => 'index' 19 | ), 20 | array( 21 | 'pass' => array('year', 'month'), 22 | 'year' => '2[0-9]{3}', 23 | 'month' => '0[1-9]|1[012]', 24 | ) 25 | ); 26 | 27 | Router::connect( 28 | '/blog/category/:category/*', 29 | array( 30 | 'plugin' => 'blog', 31 | 'controller' => 'blog_posts', 32 | 'action' => 'index' 33 | ), 34 | array( 35 | 'pass' => array('category'), 36 | 'category' => '[a-zA-Z0-9\-\_]+', 37 | ) 38 | ); 39 | 40 | Router::connect( 41 | '/blog/tag/:tag/*', 42 | array( 43 | 'plugin' => 'blog', 44 | 'controller' => 'blog_posts', 45 | 'action' => 'index' 46 | ), 47 | array( 48 | 'pass' => array('tag'), 49 | 'category' => '[a-zA-Z0-9\-\_]+', 50 | ) 51 | ); 52 | 53 | Router::connect( 54 | '/blog/:slug', 55 | array( 56 | 'plugin' => 'blog', 57 | 'controller' => 'blog_posts', 58 | 'action' => 'view' 59 | ), 60 | array( 61 | 'slug' => '[a-zA-Z0-9\-\_]+', 62 | ) 63 | ); 64 | 65 | Router::connect('/blog/*', array( 66 | 'plugin' => 'blog', 67 | 'controller' => 'blog_posts', 68 | 'action' => 'index', 69 | )); 70 | 71 | Router::connect('/admin/blog_posts', array( 72 | 'plugin' => 'blog', 73 | 'controller' => 'blog_posts', 74 | 'action' => 'index', 'admin' => true 75 | )); 76 | -------------------------------------------------------------------------------- /Config/schema/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `blog_post_categories` ( 2 | `id` int(11) NOT NULL AUTO_INCREMENT, 3 | `parent_id` int(11) DEFAULT NULL, 4 | `lft` int(11) DEFAULT NULL, 5 | `rght` int(11) DEFAULT NULL, 6 | `name` varchar(255) NOT NULL, 7 | `slug` varchar(255) NOT NULL, 8 | `meta_title` varchar(255) DEFAULT NULL, 9 | `meta_description` text, 10 | `meta_keywords` text, 11 | `rss_channel_title` varchar(255) DEFAULT NULL, 12 | `rss_channel_description` text, 13 | `blog_post_count` int(11) NOT NULL DEFAULT '0', 14 | `under_blog_post_count` int(11) NOT NULL DEFAULT '0', 15 | `created` datetime DEFAULT NULL, 16 | `modified` datetime DEFAULT NULL, 17 | PRIMARY KEY (`id`), 18 | UNIQUE KEY `slug` (`slug`) 19 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 20 | 21 | CREATE TABLE `blog_post_categories_blog_posts` ( 22 | `blog_post_category_id` int(11) NOT NULL, 23 | `blog_post_id` int(11) NOT NULL, 24 | PRIMARY KEY (`blog_post_category_id`,`blog_post_id`) 25 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 26 | 27 | CREATE TABLE `blog_post_tags` ( 28 | `id` int(11) NOT NULL AUTO_INCREMENT, 29 | `name` varchar(255) NOT NULL, 30 | `slug` varchar(255) NOT NULL, 31 | `meta_title` varchar(255) DEFAULT NULL, 32 | `meta_description` text, 33 | `meta_keywords` text, 34 | `rss_channel_title` varchar(255) DEFAULT NULL, 35 | `rss_channel_description` text, 36 | `blog_post_count` int(11) NOT NULL DEFAULT '0', 37 | `created` datetime DEFAULT NULL, 38 | `modified` datetime DEFAULT NULL, 39 | PRIMARY KEY (`id`), 40 | UNIQUE KEY `name` (`name`), 41 | UNIQUE KEY `slug` (`slug`) 42 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 43 | 44 | CREATE TABLE `blog_post_tags_blog_posts` ( 45 | `blog_post_tag_id` int(11) NOT NULL, 46 | `blog_post_id` int(11) NOT NULL, 47 | PRIMARY KEY (`blog_post_tag_id`,`blog_post_id`) 48 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 49 | 50 | CREATE TABLE `blog_posts` ( 51 | `id` int(11) NOT NULL AUTO_INCREMENT, 52 | `title` varchar(255) NOT NULL, 53 | `slug` varchar(255) NOT NULL, 54 | `summary` text, 55 | `body` longtext, 56 | `published` tinyint(1) NOT NULL, 57 | `sticky` tinyint(1) NOT NULL DEFAULT '0', 58 | `in_rss` tinyint(1) NOT NULL DEFAULT '1', 59 | `meta_title` varchar(255) DEFAULT NULL, 60 | `meta_description` varchar(255) DEFAULT NULL, 61 | `meta_keywords` varchar(255) DEFAULT NULL, 62 | `created` datetime DEFAULT NULL, 63 | `modified` datetime DEFAULT NULL, 64 | PRIMARY KEY (`id`) 65 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 66 | 67 | CREATE TABLE `blog_settings` ( 68 | `id` int(11) NOT NULL AUTO_INCREMENT, 69 | `setting` varchar(255) NOT NULL, 70 | `setting_text` varchar(255) NOT NULL, 71 | `tip` varchar(255) DEFAULT NULL, 72 | `value` text, 73 | `modified` datetime DEFAULT NULL, 74 | PRIMARY KEY (`id`), 75 | UNIQUE KEY `setting_text_UNIQUE` (`setting_text`), 76 | UNIQUE KEY `setting_UNIQUE` (`setting`) 77 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 78 | 79 | INSERT INTO `blog_settings` VALUES (1,'meta_title','Meta Title',NULL,'My New Blog',NULL),(2,'meta_description','Meta Description',NULL,'','NULL'),(3,'meta_keywords','Meta Keywords',NULL,'',NULL),(4,'rss_channel_title','RSS Channel Title',NULL,'My New Blog',NULL),(5,'rss_channel_description','RSS Channel Description',NULL,'',NULL),(6,'show_summary_on_post_view','Show post summary on post detail page?','\'Yes\' or \'No\'','No',NULL),(7,'show_categories_on_post_view','Show post categories on post detail page?','\'Yes\' or \'No\'','No',NULL),(8,'show_tags_on_post_view','Show post tags on post detail page?','\'Yes\' or \'No\'','Yes',NULL),(9,'use_summary_or_body_on_post_index','Use the summary or the post body on the post index page?','\'Summary\' or \'Body\'','Summary',NULL),(10,'use_summary_or_body_in_rss_feed','Use the summary or the post body in the RSS feed?','\'Summary\' or \'Body\'','Body',NULL),('11', 'published_format_on_post_index', 'Published date/time format on post index page', 'e.g. \'d M Y\' see php.net/date', '<\\s\\p\\a\\n \\c\\l\\a\\s\\s="\\d\\a\\y">d\\s\\p\\a\\n> <\\s\\p\\a\\n \\c\\l\\a\\s\\s="\\m\\o\\n\\t\\h">M\\s\\p\\a\\n> <\\s\\p\a\\n \\c\\l\\a\\s\\s="\\y\\e\\a\\r">y\\s\\p\\a\\n>', NULL),('12', 'published_format_on_post_view', 'Published date/time format on post view page', 'e.g. \'d M Y\' see php.net/date', '<\\s\\p\\a\\n \\c\\l\\a\\s\\s="\\d\\a\\y">d\\s\\p\\a\\n> <\\s\\p\\a\\n \\c\\l\\a\\s\\s="\\m\\o\\n\\t\\h">M\\s\\p\\a\\n> <\\s\\p\\a\\n \\c\\l\\a\\s\\s="\\y\\e\\a\\r">y\\s\\p\\a\\n>', NULL),('13', 'og:site_name', 'Open Graph: Site Name', NULL, 'My New Blog', NULL),('14', 'fb_admins', 'Facebook Admins', NULL, NULL, NULL),(15, 'use_disqus', 'Use Disqus', '\'Yes\' or \'No\'','No', NULL),(16, 'disqus_shortname', 'Disqus Shortname', NULL, NULL, NULL),(17, 'disqus_developer', 'Disqus Developer Mode', '\'Yes\' or \'No\'', 'Yes', NULL),(18,'show_share_links','Show the share buttons on blog posts?','\'Yes\' or \'No\'','Yes', NULL); 80 | -------------------------------------------------------------------------------- /Controller/BlogPostCategoriesController.php: -------------------------------------------------------------------------------- 1 | 9 | * @link http://www.neilcrookes.com http://neil.crook.es 10 | * @copyright (c) 2011 Neil Crookes 11 | * @license MIT License - http://www.opensource.org/licenses/mit-license.php * 12 | */ 13 | class BlogPostCategoriesController extends AppController { 14 | 15 | 16 | /** 17 | * index method 18 | * 19 | * @return void 20 | */ 21 | public function admin_index() { 22 | $this->BlogPostCategory->recursive = 0; 23 | $this->set('blogPostCategories', $this->paginate()); 24 | } 25 | 26 | /** 27 | * view method 28 | * 29 | * @param string $id 30 | * @return void 31 | */ 32 | public function admin_view($id = null) { 33 | $this->BlogPostCategory->id = $id; 34 | if (!$this->BlogPostCategory->exists()) { 35 | throw new NotFoundException(__('Invalid blog post category')); 36 | } 37 | $this->set('blogPostCategory', $this->BlogPostCategory->read(null, $id)); 38 | } 39 | 40 | /** 41 | * add method 42 | * 43 | * @return void 44 | */ 45 | public function admin_add() { 46 | if ($this->request->is('post')) { 47 | $this->BlogPostCategory->create(); 48 | if ($this->BlogPostCategory->save($this->request->data)) { 49 | $this->Session->setFlash(__('The blog post category has been saved')); 50 | $this->redirect(array('action' => 'index')); 51 | } else { 52 | $this->Session->setFlash(__('The blog post category could not be saved. Please, try again.')); 53 | } 54 | } 55 | $parents = $this->BlogPostCategory->generateTreeList(); 56 | $this->set(compact('parents')); 57 | } 58 | 59 | /** 60 | * edit method 61 | * 62 | * @param string $id 63 | * @return void 64 | */ 65 | public function admin_edit($id = null) { 66 | $this->BlogPostCategory->id = $id; 67 | if (!$this->BlogPostCategory->exists()) { 68 | throw new NotFoundException(__('Invalid blog post category')); 69 | } 70 | if ($this->request->is('post') || $this->request->is('put')) { 71 | if ($this->BlogPostCategory->save($this->request->data)) { 72 | $this->Session->setFlash(__('The blog post category has been saved')); 73 | $this->redirect(array('action' => 'index')); 74 | } else { 75 | $this->Session->setFlash(__('The blog post category could not be saved. Please, try again.')); 76 | } 77 | } else { 78 | $this->request->data = $this->BlogPostCategory->read(null, $id); 79 | } 80 | $parents = $this->BlogPostCategory->generateTreeList(); 81 | $this->set(compact('parents')); 82 | } 83 | 84 | /** 85 | * delete method 86 | * 87 | * @param string $id 88 | * @return void 89 | */ 90 | public function admin_delete($id = null) { 91 | if (!$this->request->is('post')) { 92 | throw new MethodNotAllowedException(); 93 | } 94 | $this->BlogPostCategory->id = $id; 95 | if (!$this->BlogPostCategory->exists()) { 96 | throw new NotFoundException(__('Invalid blog post category')); 97 | } 98 | if ($this->BlogPostCategory->delete()) { 99 | $this->Session->setFlash(__('Blog post category deleted')); 100 | $this->redirect(array('action'=>'index')); 101 | } 102 | $this->Session->setFlash(__('Blog post category was not deleted')); 103 | $this->redirect(array('action' => 'index')); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Controller/BlogPostTagsController.php: -------------------------------------------------------------------------------- 1 | 8 | * @link http://www.neilcrookes.com http://neil.crook.es 9 | * @copyright (c) 2011 Neil Crookes 10 | * @license MIT License - http://www.opensource.org/licenses/mit-license.php 11 | */ 12 | class BlogPostTagsController extends AppController { 13 | 14 | 15 | /** 16 | * index method 17 | * 18 | * @return void 19 | */ 20 | public function admin_index() { 21 | $this->BlogPostTag->recursive = 0; 22 | $this->set('blogPostTags', $this->paginate()); 23 | } 24 | 25 | /** 26 | * view method 27 | * 28 | * @param string $id 29 | * @return void 30 | */ 31 | public function admin_view($id = null) { 32 | $this->BlogPostTag->id = $id; 33 | if (!$this->BlogPostTag->exists()) { 34 | throw new NotFoundException(__('Invalid blog post tag')); 35 | } 36 | $this->set('blogPostTag', $this->BlogPostTag->read(null, $id)); 37 | } 38 | 39 | /** 40 | * add method 41 | * 42 | * @return void 43 | */ 44 | public function admin_add() { 45 | if ($this->request->is('post')) { 46 | $this->BlogPostTag->create(); 47 | if ($this->BlogPostTag->save($this->request->data)) { 48 | $this->Session->setFlash(__('The blog post tag has been saved')); 49 | $this->redirect(array('action' => 'index')); 50 | } else { 51 | $this->Session->setFlash(__('The blog post tag could not be saved. Please, try again.')); 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * edit method 58 | * 59 | * @param string $id 60 | * @return void 61 | */ 62 | public function admin_edit($id = null) { 63 | $this->BlogPostTag->id = $id; 64 | if (!$this->BlogPostTag->exists()) { 65 | throw new NotFoundException(__('Invalid blog post tag')); 66 | } 67 | if ($this->request->is('post') || $this->request->is('put')) { 68 | if ($this->BlogPostTag->save($this->request->data)) { 69 | $this->Session->setFlash(__('The blog post tag has been saved')); 70 | $this->redirect(array('action' => 'index')); 71 | } else { 72 | $this->Session->setFlash(__('The blog post tag could not be saved. Please, try again.')); 73 | } 74 | } else { 75 | $this->request->data = $this->BlogPostTag->read(null, $id); 76 | } 77 | } 78 | 79 | /** 80 | * delete method 81 | * 82 | * @param string $id 83 | * @return void 84 | */ 85 | public function admin_delete($id = null) { 86 | if (!$this->request->is('post')) { 87 | throw new MethodNotAllowedException(); 88 | } 89 | $this->BlogPostTag->id = $id; 90 | if (!$this->BlogPostTag->exists()) { 91 | throw new NotFoundException(__('Invalid blog post tag')); 92 | } 93 | if ($this->BlogPostTag->delete()) { 94 | $this->Session->setFlash(__('Blog post tag deleted')); 95 | $this->redirect(array('action'=>'index')); 96 | } 97 | $this->Session->setFlash(__('Blog post tag was not deleted')); 98 | $this->redirect(array('action' => 'index')); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Controller/BlogPostsController.php: -------------------------------------------------------------------------------- 1 | 6 | * @link http://www.neilcrookes.com http://neil.crook.es 7 | * @copyright (c) 2011 Neil Crookes 8 | * @license MIT License - http://www.opensource.org/licenses/mit-license.php 9 | */ 10 | class BlogPostsController extends AppController { 11 | 12 | /** 13 | * Components this controller uses 14 | * 15 | * @var array 16 | */ 17 | public $components = array('RequestHandler'); 18 | 19 | /** 20 | * Helpers this controller uses 21 | * 22 | * @var array 23 | */ 24 | public $helpers = array('Time', 'Rss', 'Blog.Blog'); 25 | 26 | /** 27 | * Default pagination options 28 | * 29 | * @var array 30 | */ 31 | public $paginate = array( 32 | 'limit' => 10, 33 | 'order' => array( 34 | 'BlogPost.created DESC' 35 | ), 36 | 'recursive' => 0, 37 | ); 38 | 39 | public function beforeFilter() { 40 | parent::beforeFilter(); 41 | $this->set('blogSettings', ClassRegistry::init('Blog.BlogSetting')->getSettings()); 42 | } 43 | 44 | /** 45 | * Index action handles all blog post index views, whether filtered by 46 | * category, tag or archive, or rendered as HTML or RSS 47 | */ 48 | public function index() { 49 | 50 | $this->paginate['conditions'][]['BlogPost.published'] = 1; 51 | 52 | if ($this->RequestHandler->isRss()) { 53 | 54 | // Add RSS condition to default options defined in paginate 55 | $options = array_merge( 56 | $this->paginate, 57 | array('conditions' => array('BlogPost.in_rss' => 1)) 58 | ); 59 | 60 | // Fetch blog posts according to the current mode: category, tag or 61 | // default 62 | switch ($this->_filtered()) { 63 | case 'category': 64 | $options = Set::merge($options, $this->_category()); 65 | $blogPosts = $this->BlogPost->find('byCategory', $options); 66 | break; 67 | case 'tag': 68 | $options = Set::merge($options, $this->_tag()); 69 | $blogPosts = $this->BlogPost->find('byTag', $options); 70 | break; 71 | default: 72 | $blogPosts = $this->BlogPost->find('all', $options); 73 | break; 74 | } 75 | 76 | $this->set(compact('blogPosts')); 77 | 78 | return; 79 | 80 | } 81 | 82 | // Add in the priority order to sticky posts when not rendering RSS 83 | array_unshift($this->paginate['order'], 'BlogPost.sticky DESC'); 84 | 85 | switch ($this->_filtered()) { 86 | case 'category': 87 | $this->paginate = Set::merge($this->paginate, array('byCategory'), $this->_category()); 88 | break; 89 | case 'tag': 90 | $this->paginate = Set::merge($this->paginate, array('byTag'), $this->_tag()); 91 | break; 92 | case 'archive': 93 | $this->paginate['conditions'][]["DATE_FORMAT(BlogPost.created, '%Y%m')"] = $this->params['year'].$this->params['month']; 94 | break; 95 | } 96 | 97 | $blogPosts = $this->paginate(); 98 | 99 | $this->set(compact('blogPosts')); 100 | 101 | $this->_setArchivesCategoriesAndTags(); 102 | 103 | } 104 | 105 | /** 106 | * Returns the current filter mode, either 'category', 'tag' or 'archive' 107 | * determined by the params in the URL. 108 | * 109 | * @return string 110 | */ 111 | protected function _filtered() { 112 | 113 | if (!empty($this->params['category'])) { 114 | return 'category'; 115 | } 116 | 117 | if (!empty($this->params['tag'])) { 118 | return 'tag'; 119 | } 120 | 121 | if (!empty($this->params['year']) && !empty($this->params['month'])) { 122 | return 'archive'; 123 | } 124 | 125 | return false; 126 | 127 | } 128 | 129 | /** 130 | * Finds and sets the selected category in the view. Returns the conditions 131 | * where the lft value is between the selected category's lft and rght value. 132 | * Called from index() action for both HTML and RSS views 133 | * 134 | * @return array 135 | */ 136 | protected function _category() { 137 | 138 | $category = $this->BlogPost->BlogPostCategory->find('first', array( 139 | 'conditions' => array( 140 | 'slug' => $this->params['category'] 141 | ) 142 | )); 143 | 144 | if (!$category) { 145 | throw new NotFoundException(__('Invalid Blog Post Category')); 146 | } 147 | 148 | $this->set(compact('category')); 149 | 150 | $options['conditions'][]['BlogPostCategory.lft BETWEEN ? AND ?'] = array( 151 | $category['BlogPostCategory']['lft'], 152 | $category['BlogPostCategory']['rght'], 153 | ); 154 | 155 | return $options; 156 | 157 | } 158 | 159 | /** 160 | * Finds and sets the selected tag in the view. Returns the conditions where 161 | * the blog_post_tag_id in the join model is the id of the selected tag. 162 | * Called from index() action for both HTML and RSS views 163 | * 164 | * @return array 165 | */ 166 | protected function _tag() { 167 | 168 | $tag = $this->BlogPost->BlogPostTag->find('first', array( 169 | 'conditions' => array( 170 | 'BlogPostTag.slug' => $this->params['tag'] 171 | ) 172 | )); 173 | 174 | if (!$tag) { 175 | throw new NotFoundException(__('Invalid Blog Post Tag')); 176 | } 177 | 178 | $this->set(compact('tag')); 179 | 180 | $options['conditions'][]['BlogPostTagsBlogPost.blog_post_tag_id'] = $tag['BlogPostTag']['id']; 181 | 182 | return $options; 183 | 184 | } 185 | 186 | /** 187 | * View a blog post 188 | */ 189 | public function view() { 190 | 191 | if (empty($this->params['named']['slug'])) { 192 | throw new NotFoundException(__('Invalid Blog Post')); 193 | } 194 | 195 | $blogPost = $this->BlogPost->find('forView', array( 196 | 'conditions' => array( 197 | 'BlogPost.slug' => $this->params['named']['slug'], 198 | ) 199 | )); 200 | 201 | if (!$blogPost) { 202 | throw new NotFoundException(__('Invalid Blog Post')); 203 | } 204 | 205 | $this->set(compact('blogPost')); 206 | 207 | $this->_setArchivesCategoriesAndTags(); 208 | 209 | } 210 | 211 | /** 212 | * Fetch the data for archives, categories and tags and set them as available 213 | * in the View so they can be rendered on the index and view pages. 214 | */ 215 | public function _setArchivesCategoriesAndTags() { 216 | 217 | if (!$archives = Cache::read('blog_archives')) { 218 | $archives = $this->BlogPost->find('archives'); 219 | Cache::write('blog_archives', $archives); 220 | } 221 | 222 | if (!$tags = Cache::read('blog_tags')) { 223 | $tags = $this->BlogPost->BlogPostTag->find('cloud'); 224 | Cache::write('blog_tags', $tags); 225 | } 226 | 227 | if (!$categories = Cache::read('blog_categories')) { 228 | // getMenuWithUnderCounts() is a method on the HabtmCounterCache Behavior 229 | // which returns a nest list of categories, in a format that can be 230 | // rendered by the BlogHelper::menu method, with the number of posts in 231 | // each category, or a it's child categories, next to the category name. 232 | $categories = $this->BlogPost->getMenuWithUnderCounts('BlogPostCategory', array('url' => array('slug' => 'category'))); 233 | Cache::write('blog_categories', $categories); 234 | } 235 | 236 | // Set the selected keys in the options that are sent to the methods which 237 | // fetch the archives, categories and tags, to the selected value according 238 | // to the current mode. This is so when the data is rendered, we can 239 | // indicate the current selected one, if any. 240 | switch ($this->_filtered()) { 241 | case 'category': 242 | list($categories) = $this->_setSelectedCategory($categories, $this->params['category']); 243 | break; 244 | case 'archive': 245 | // Sets the selected month and parent-selected year keys to true 246 | if (isset($archives[$this->params['year']]) && isset($archives[$this->params['year']]['children'][$this->params['month']])) { 247 | $archives[$this->params['year']]['children'][$this->params['month']]['selected'] = true; 248 | $archives[$this->params['year']]['parent-selected'] = true; 249 | } 250 | break; 251 | default: 252 | break; 253 | } 254 | 255 | $this->set(compact('archives', 'categories', 'tags')); 256 | 257 | } 258 | 259 | /** 260 | * Recursively traverses the passed categories and sets the 'selected' => true 261 | * and 'parent-selected' => true in the categories array. This has to be done 262 | * after the list of categories is fetched, and not in the find method, which 263 | * would have been nicer, so that the categories array can be cached, and we 264 | * don't have to fetch them for each page view. 265 | * 266 | * @param $categories array A nested array of categories returned by the 267 | * HabtmCounterCache::getMenuWithUnderCounts() method 268 | * @param $selected string The slug of the selected category 269 | * @return array An array with 2 keys, the first containing the modified 270 | * nested array of categories passed in, and the second with the 271 | * $parentSelected value set to tru or false for the current node in the 272 | * nested list. The latter is only used to set the parent-selected key in the 273 | * internal iterations of the nested list, so does not need to be captured 274 | * from the external call to this function - it's only used when the fucntion 275 | * calls itself. 276 | */ 277 | protected function _setSelectedCategory($categories, $selected) { 278 | 279 | // Initalise this to false, if a category is identified as the selected, 280 | // category, it is set to true below and then passed back up the levels of 281 | // recursion so that parent-selected keys can be set. 282 | $parentSelected = false; 283 | 284 | foreach ($categories as $k => $category) { 285 | 286 | // Set the children and parent-selected keys by calling this method again 287 | // with the category's children passed into the $categories parameter. 288 | list ($categories[$k]['children'], $categories[$k]['parent-selected']) = $this->_setSelectedCategory($category['children'], $selected); 289 | 290 | // Check the data against the selected key in the options parameter and 291 | // set the selected key to true, and the parent selected variable, ready 292 | // for passing back up the levels of recursion. 293 | if ($categories[$k]['url']['category'] == $selected) { 294 | $categories[$k]['selected'] = true; 295 | $parentSelected = true; 296 | } 297 | 298 | } 299 | 300 | return array($categories, $parentSelected); 301 | 302 | } 303 | 304 | // The following methods are pretyy much just the baked admin CRUD actions. 305 | // The only difference is we use generateTreeList() to generate the 306 | // categories that can be selected in the add/edit blog post actions, so we 307 | // can visualise the hierarchy of categories. 308 | 309 | /** 310 | * admin index method 311 | * 312 | * @return void 313 | */ 314 | public function admin_index() { 315 | $this->BlogPost->recursive = 0; 316 | $this->set('blogPosts', $this->paginate()); 317 | } 318 | 319 | /** 320 | * admin view method 321 | * 322 | * @param string $id 323 | * @return void 324 | */ 325 | public function admin_view($id = null) { 326 | $this->BlogPost->id = $id; 327 | if (!$this->BlogPost->exists()) { 328 | throw new NotFoundException(__('Invalid blog post')); 329 | } 330 | $this->set('blogPost', $this->BlogPost->read(null, $id)); 331 | } 332 | 333 | /** 334 | * admin add method 335 | * 336 | * @return void 337 | */ 338 | public function admin_add() { 339 | if ($this->request->is('post')) { 340 | $this->BlogPost->create(); 341 | if ($this->BlogPost->save($this->request->data)) { 342 | $this->Session->setFlash(__('The blog post has been saved')); 343 | $this->redirect(array('action' => 'index')); 344 | } else { 345 | $this->Session->setFlash(__('The blog post could not be saved. Please, try again.')); 346 | } 347 | } 348 | $blogPostCategories = $this->BlogPost->BlogPostCategory->generateTreeList(); 349 | $blogPostTags = $this->BlogPost->BlogPostTag->find('list'); 350 | $this->set(compact('blogPostCategories', 'blogPostTags')); 351 | } 352 | 353 | /** 354 | * admin edit method 355 | * 356 | * @param string $id 357 | * @return void 358 | */ 359 | public function admin_edit($id = null) { 360 | $this->BlogPost->id = $id; 361 | if (!$this->BlogPost->exists()) { 362 | throw new NotFoundException(__('Invalid blog post')); 363 | } 364 | if ($this->request->is('post') || $this->request->is('put')) { 365 | if ($this->BlogPost->save($this->request->data)) { 366 | $this->Session->setFlash(__('The blog post has been saved')); 367 | $this->redirect(array('action' => 'index')); 368 | } else { 369 | $this->Session->setFlash(__('The blog post could not be saved. Please, try again.')); 370 | } 371 | } else { 372 | $this->request->data = $this->BlogPost->read(null, $id); 373 | } 374 | $blogPostCategories = $this->BlogPost->BlogPostCategory->generateTreeList(); 375 | $blogPostTags = $this->BlogPost->BlogPostTag->find('list'); 376 | $this->set(compact('blogPostCategories', 'blogPostTags')); 377 | } 378 | 379 | /** 380 | * admin delete method 381 | * 382 | * @param string $id 383 | * @return void 384 | */ 385 | public function admin_delete($id = null) { 386 | if (!$this->request->is('post')) { 387 | throw new MethodNotAllowedException(); 388 | } 389 | $this->BlogPost->id = $id; 390 | if (!$this->BlogPost->exists()) { 391 | throw new NotFoundException(__('Invalid blog post')); 392 | } 393 | if ($this->BlogPost->delete()) { 394 | $this->Session->setFlash(__('Blog post deleted')); 395 | $this->redirect(array('action'=>'index')); 396 | } 397 | $this->Session->setFlash(__('Blog post was not deleted')); 398 | $this->redirect(array('action' => 'index')); 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /Controller/BlogSettingsController.php: -------------------------------------------------------------------------------- 1 | 13 | * @link http://www.neilcrookes.com http://neil.crook.es 14 | * @copyright (c) 2011 Neil Crookes 15 | * @license MIT License - http://www.opensource.org/licenses/mit-license.php 16 | */ 17 | class BlogSettingsController extends AppController { 18 | 19 | public $helpers = array('Text', 'Time'); 20 | 21 | /** 22 | * admin_index method 23 | * 24 | * @return void 25 | */ 26 | public function admin_index() { 27 | $this->BlogSetting->recursive = 0; 28 | $this->set('blogSettings', $this->paginate()); 29 | } 30 | 31 | /** 32 | * admin_view method 33 | * 34 | * @param string $id 35 | * @return void 36 | */ 37 | public function admin_view($id = null) { 38 | $this->BlogSetting->id = $id; 39 | if (!$this->BlogSetting->exists()) { 40 | throw new NotFoundException(__('Invalid blog setting')); 41 | } 42 | $this->set('blogSetting', $this->BlogSetting->read(null, $id)); 43 | } 44 | 45 | /** 46 | * admin_edit method 47 | * 48 | * @param string $id 49 | * @return void 50 | */ 51 | public function admin_edit($id = null) { 52 | $this->BlogSetting->id = $id; 53 | if (!$this->BlogSetting->exists()) { 54 | throw new NotFoundException(__('Invalid blog setting')); 55 | } 56 | if ($this->request->is('post') || $this->request->is('put')) { 57 | if ($this->BlogSetting->save($this->request->data)) { 58 | $this->Session->setFlash(__('The blog setting has been saved')); 59 | $this->redirect(array('action' => 'index')); 60 | } else { 61 | $this->Session->setFlash(__('The blog setting could not be saved. Please, try again.')); 62 | $blogSetting = $this->BlogSetting->read(null, $id); 63 | } 64 | } else { 65 | $this->request->data = $blogSetting = $this->BlogSetting->read(null, $id); 66 | } 67 | $this->set('blogSetting', $blogSetting); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Model/BlogPost.php: -------------------------------------------------------------------------------- 1 | 6 | * @link http://www.neilcrookes.com http://neil.crook.es 7 | * @copyright (c) 2011 Neil Crookes 8 | * @license MIT License - http://www.opensource.org/licenses/mit-license.php 9 | */ 10 | class BlogPost extends AppModel { 11 | 12 | public $actsAs = array( 13 | 'HabtmCounterCache.HabtmCounterCache' => array( 14 | 'counterScope' => array( 15 | 'BlogPost.published' => 1 16 | ), 17 | ), 18 | ); 19 | 20 | /** 21 | * Validation rules 22 | * 23 | * @var array 24 | */ 25 | public $validate = array( 26 | 'title' => array( 27 | 'notEmpty' => array( 28 | 'rule' => 'notEmpty', 29 | 'message' => 'Please enter a title', 30 | 'required' => false, 31 | 'allowEmpty' => false, 32 | 'on' => null, 33 | 'last' => true, 34 | ), 35 | ), 36 | 'slug' => array( 37 | 'notEmpty' => array( 38 | 'rule' => 'notEmpty', 39 | 'message' => 'Please enter a slug', 40 | 'required' => false, 41 | 'allowEmpty' => false, 42 | 'on' => null, 43 | 'last' => true, 44 | ), 45 | 'regex0' => array( 46 | 'rule' => '/^[a-z0-9\_\-]*$/i', 47 | 'message' => 'Please enter a valid slug', 48 | 'required' => false, 49 | 'allowEmpty' => false, 50 | 'on' => null, 51 | 'last' => true, 52 | ), 53 | 'isUnique' => array( 54 | 'rule' => 'isUnique', 55 | 'message' => 'This slug is already in use', 56 | ), 57 | ), 58 | 'published' => array( 59 | 'boolean' => array( 60 | 'rule' => 'boolean', 61 | 'message' => 'Please enter a valid value', 62 | 'required' => false, 63 | 'allowEmpty' => false, 64 | 'on' => null, 65 | 'last' => true, 66 | ), 67 | ), 68 | 'sticky' => array( 69 | 'boolean' => array( 70 | 'rule' => 'boolean', 71 | 'message' => 'Please enter a valid value', 72 | 'required' => false, 73 | 'allowEmpty' => false, 74 | 'on' => null, 75 | 'last' => true, 76 | ), 77 | ), 78 | 'in_rss' => array( 79 | 'boolean' => array( 80 | 'rule' => 'boolean', 81 | 'message' => 'Please enter a valid value', 82 | 'required' => false, 83 | 'allowEmpty' => false, 84 | 'on' => null, 85 | 'last' => true, 86 | ), 87 | ), 88 | ); 89 | 90 | //The Associations below have been created with all possible keys, those that are not needed can be removed 91 | 92 | /** 93 | * hasAndBelongsToMany associations 94 | * 95 | * @var array 96 | */ 97 | public $hasAndBelongsToMany = array( 98 | 'BlogPostCategory' => array( 99 | 'className' => 'Blog.BlogPostCategory', 100 | 'joinTable' => 'blog_post_categories_blog_posts', 101 | 'foreignKey' => 'blog_post_id', 102 | 'associationForeignKey' => 'blog_post_category_id', 103 | 'unique' => true, 104 | 'conditions' => '', 105 | 'fields' => '', 106 | 'order' => '', 107 | 'limit' => '', 108 | 'offset' => '', 109 | 'finderQuery' => '', 110 | 'deleteQuery' => '', 111 | 'insertQuery' => '' 112 | ), 113 | 'BlogPostTag' => array( 114 | 'className' => 'Blog.BlogPostTag', 115 | 'joinTable' => 'blog_post_tags_blog_posts', 116 | 'foreignKey' => 'blog_post_id', 117 | 'associationForeignKey' => 'blog_post_tag_id', 118 | 'unique' => true, 119 | 'conditions' => '', 120 | 'fields' => '', 121 | 'order' => '', 122 | 'limit' => '', 123 | 'offset' => '', 124 | 'finderQuery' => '', 125 | 'deleteQuery' => '', 126 | 'insertQuery' => '' 127 | ) 128 | ); 129 | 130 | public function afterSave($created, $options = array()) { 131 | parent::afterSave($created, $options); 132 | Cache::delete('blog_archives'); 133 | Cache::delete('blog_categories'); 134 | Cache::delete('blog_tags'); 135 | } 136 | 137 | public function afterDelete() { 138 | Cache::delete('blog_archives'); 139 | Cache::delete('blog_categories'); 140 | Cache::delete('blog_tags'); 141 | } 142 | 143 | 144 | /** 145 | * Custom find methods this model uses. 146 | * 147 | * @var array 148 | */ 149 | public $findMethods = array( 150 | 'archives' => true, 151 | 'byCategory' => true, 152 | 'byTag' => true, 153 | 'forView' => true, 154 | ); 155 | 156 | /** 157 | * Custom paginateCount method behaves as usual for normal find types but has 158 | * to do something special for the findByCategory custom find type. 159 | * 160 | * @param integer $conditions 161 | * @param integer $recursive 162 | * @param array $extra 163 | * @return integer 164 | */ 165 | public function paginateCount($conditions = null, $recursive = 0, $extra = array()) { 166 | $options = array( 167 | 'conditions' => $conditions, 168 | 'recursive' => $recursive, 169 | ); 170 | // Call the custom find type but add the field param for COUNT(DISTINCT) 171 | if (!empty($extra['type']) && in_array($extra['type'], array('byCategory', 'byTag'))) { 172 | $count = 'COUNT(DISTINCT BlogPost.id)'; 173 | $options['fields'] = $count; 174 | $result = $this->find($extra['type'], $options); 175 | return $result[0][0][$count]; 176 | } 177 | return $this->find('count', $options); 178 | } 179 | 180 | /** 181 | * Returns an array with each item being another array with keys for text, id 182 | * url, selected-parent, selected and children, which may contain the same 183 | * again. Essentially it's a datastructure that represents a hierarchical menu 184 | * with items being the year or month in a year, followed by the count in of 185 | * posts created in that year/month in brackets. E.g. 186 | * 187 | * array( 188 | * array( 189 | * 'text' => '2010 (10)', 190 | * 'id' => 2010, 191 | * 'selected-parent' => true, 192 | * 'children' => array( 193 | * array( 194 | * 'text' => 'December (5)', 195 | * 'id' => 201012, 196 | * 'url' => array('year' => '2010', 'month' => '12'), 197 | * 'selected' => true, 198 | * 'children' => array())))) 199 | * 200 | * @param array $state 201 | * @param array $query 202 | * @param array $results 203 | * @return array 204 | */ 205 | protected function _findArchives($state, $query = array(), $results = array()) { 206 | 207 | if ($state == 'before') { 208 | 209 | $query['conditions'][]['BlogPost.published'] = 1; 210 | 211 | $query['fields'] = array( 212 | 'YEAR(BlogPost.created) AS `year`', 213 | "DATE_FORMAT(BlogPost.created, '%m') AS `month`", 214 | 'MONTHNAME(BlogPost.created) AS `monthname`', 215 | 'COUNT(*) AS `count`' 216 | ); 217 | 218 | $query['group'] = array("DATE_FORMAT(BlogPost.created, '%Y%m')"); 219 | 220 | $query['order'] = 'BlogPost.created DESC'; 221 | 222 | return $query; 223 | 224 | } else { 225 | 226 | $return = array(); 227 | 228 | foreach ($results as $result) { 229 | 230 | // If this is the first result of the given year, add it as a text node 231 | if (!isset($return[$result[0]['year']])) { 232 | $return[$result[0]['year']] = array( 233 | 'text' => $result[0]['year'], 234 | 'id' => $result[0]['year'], 235 | ); 236 | } 237 | 238 | // Add in the current month result to the the current year's result's 239 | // children key, setting the text to "monthname (count)" 240 | $return[$result[0]['year']]['children'][$result[0]['month']] = array( 241 | 'text' => $result[0]['monthname'] . ' (' . $result[0]['count'] . ')', 242 | 'id' => $result[0]['year'] . $result[0]['month'], 243 | 'url' => array( 244 | 'year' => $result[0]['year'], 245 | 'month' => $result[0]['month'], 246 | ), 247 | ); 248 | 249 | } 250 | 251 | // Sets the selected month and parent-selected year keys to true 252 | if (isset($query['selected']['year']) && isset($query['selected']['month']) 253 | && isset($return[$query['selected']['year']]) && isset($return[$query['selected']['year']]['children'][$query['selected']['month']])) { 254 | $return[$query['selected']['year']]['children'][$query['selected']['month']]['selected'] = true; 255 | $return[$query['selected']['year']]['parent-selected'] = true; 256 | } 257 | 258 | return $return; 259 | 260 | } 261 | 262 | } 263 | 264 | /** 265 | * Custom find type for finding all posts assigned to a category or any of its 266 | * children. 267 | * 268 | * @param array $state 269 | * @param array $query 270 | * @param array $results 271 | * @return array 272 | */ 273 | protected function _findByCategory($state, $query = array(), $results = array()) { 274 | 275 | if ($state == 'before') { 276 | 277 | $query['conditions'][]['BlogPost.published'] = 1; 278 | 279 | $query['joins'] = array( 280 | array( 281 | 'type' => 'INNER', 282 | 'table' => 'blog_post_categories_blog_posts', 283 | 'alias' => 'BlogPostCategoriesBlogPost', 284 | 'conditions' => array( 285 | 'BlogPost.id = BlogPostCategoriesBlogPost.blog_post_id' 286 | ), 287 | ), 288 | array( 289 | 'type' => 'INNER', 290 | 'table' => 'blog_post_categories', 291 | 'alias' => 'BlogPostCategory', 292 | 'conditions' => array( 293 | 'BlogPostCategoriesBlogPost.blog_post_category_id = BlogPostCategory.id' 294 | ), 295 | ), 296 | ); 297 | 298 | if (!isset($query['fields']) || $query['fields'] != 'COUNT(DISTINCT BlogPost.id)') { 299 | $query['group'] = 'BlogPost.id'; 300 | } 301 | 302 | if (!isset($query['order'])) { 303 | $query['order'] = array( 304 | 'BlogPost.sticky DESC', 305 | 'BlogPost.created DESC' 306 | ); 307 | } 308 | 309 | if (!isset($query['limit'])) { 310 | $query['limit'] = 10; 311 | } 312 | 313 | if (!isset($query['recursive'])) { 314 | $query['recursive'] = 0; 315 | } 316 | 317 | return $query; 318 | 319 | } else { 320 | 321 | return $results; 322 | 323 | } 324 | 325 | } 326 | 327 | /** 328 | * Custom find type for finding all posts assigned to a tag. 329 | * 330 | * @param array $state 331 | * @param array $query 332 | * @param array $results 333 | * @return array 334 | */ 335 | protected function _findByTag($state, $query = array(), $results = array()) { 336 | 337 | if ($state == 'before') { 338 | 339 | $query['conditions'][]['BlogPost.published'] = 1; 340 | 341 | $query['joins'] = array( 342 | array( 343 | 'type' => 'INNER', 344 | 'table' => 'blog_post_tags_blog_posts', 345 | 'alias' => 'BlogPostTagsBlogPost', 346 | 'conditions' => array( 347 | 'BlogPost.id = BlogPostTagsBlogPost.blog_post_id' 348 | ), 349 | ), 350 | array( 351 | 'type' => 'INNER', 352 | 'table' => 'blog_post_tags', 353 | 'alias' => 'BlogPostTag', 354 | 'conditions' => array( 355 | 'BlogPostTagsBlogPost.blog_post_tag_id = BlogPostTag.id' 356 | ), 357 | ), 358 | ); 359 | 360 | if (!isset($query['fields']) || $query['fields'] != 'COUNT(DISTINCT BlogPost.id)') { 361 | $query['group'] = 'BlogPost.id'; 362 | } 363 | 364 | if (!isset($query['order'])) { 365 | $query['order'] = array( 366 | 'BlogPost.sticky DESC', 367 | 'BlogPost.created DESC' 368 | ); 369 | } 370 | 371 | if (!isset($query['limit'])) { 372 | $query['limit'] = 10; 373 | } 374 | 375 | if (!isset($query['recursive'])) { 376 | $query['recursive'] = 0; 377 | } 378 | 379 | return $query; 380 | 381 | } else { 382 | 383 | return $results; 384 | 385 | } 386 | 387 | } 388 | 389 | /** 390 | * Custom find type for the post view page. 391 | * 392 | * @param array $state 393 | * @param array $query 394 | * @param array $results 395 | * @return array 396 | */ 397 | protected function _findForView($state, $query = array(), $results = array()) { 398 | 399 | if ($state == 'before') { 400 | 401 | $query['conditions'][]['BlogPost.published'] = 1; 402 | 403 | $query['contain'] = array( 404 | 'BlogPostCategory' => array('order' => 'lft ASC'), 405 | 'BlogPostTag' => array('order' => 'name'), 406 | ); 407 | 408 | return $query; 409 | 410 | } else { 411 | 412 | $result = $results[0]; 413 | 414 | foreach ($result['BlogPostCategory'] as $k => $blogPostCategory) { 415 | $result['BlogPostCategory'][$k] = array( 416 | 'id' => $blogPostCategory['id'], 417 | 'text' => $blogPostCategory['name'], 418 | 'url' => array('category' => $blogPostCategory['slug']), 419 | ); 420 | } 421 | 422 | foreach ($result['BlogPostTag'] as $k => $blogPostTag) { 423 | $result['BlogPostTag'][$k] = array( 424 | 'id' => $blogPostTag['id'], 425 | 'text' => $blogPostTag['name'], 426 | 'url' => array('tag' => $blogPostTag['slug']), 427 | ); 428 | } 429 | 430 | return $result; 431 | 432 | } 433 | 434 | } 435 | 436 | } 437 | -------------------------------------------------------------------------------- /Model/BlogPostCategory.php: -------------------------------------------------------------------------------- 1 | 6 | * @link http://www.neilcrookes.com http://neil.crook.es 7 | * @copyright (c) 2011 Neil Crookes 8 | * @license MIT License - http://www.opensource.org/licenses/mit-license.php 9 | */ 10 | class BlogPostCategory extends AppModel { 11 | 12 | public $actsAs = array( 13 | 'Tree', 14 | ); 15 | 16 | /** 17 | * Validation rules 18 | * 19 | * @var array 20 | */ 21 | public $validate = array( 22 | 'parent_id' => array( 23 | 'numeric' => array( 24 | 'rule' => 'numeric', 25 | 'message' => 'The parent value must be numeric', 26 | 'required' => false, 27 | 'allowEmpty' => true, 28 | 'on' => null, 29 | 'last' => true, 30 | ), 31 | ), 32 | 'name' => array( 33 | 'notEmpty' => array( 34 | 'rule' => 'notEmpty', 35 | 'message' => 'Please enter a name', 36 | 'required' => false, 37 | 'allowEmpty' => false, 38 | 'on' => null, 39 | 'last' => true, 40 | ), 41 | ), 42 | 'slug' => array( 43 | 'notEmpty' => array( 44 | 'rule' => 'notEmpty', 45 | 'message' => 'Please enter a slug', 46 | 'required' => false, 47 | 'allowEmpty' => false, 48 | 'on' => null, 49 | 'last' => true, 50 | ), 51 | 'regex0' => array( 52 | 'rule' => '/^[a-z0-9\_\-]*$/i', 53 | 'message' => 'Please enter a valid slug', 54 | 'required' => false, 55 | 'allowEmpty' => false, 56 | 'on' => null, 57 | 'last' => true, 58 | ), 59 | 'isUnique' => array( 60 | 'rule' => 'isUnique', 61 | 'message' => 'This slug is already in use', 62 | ), 63 | ), 64 | ); 65 | 66 | } 67 | -------------------------------------------------------------------------------- /Model/BlogPostTag.php: -------------------------------------------------------------------------------- 1 | 6 | * @link http://www.neilcrookes.com http://neil.crook.es 7 | * @copyright (c) 2011 Neil Crookes 8 | * @license MIT License - http://www.opensource.org/licenses/mit-license.php 9 | */ 10 | class BlogPostTag extends AppModel { 11 | /** 12 | * Validation rules 13 | * 14 | * @var array 15 | */ 16 | public $validate = array( 17 | 'name' => array( 18 | 'notEmpty' => array( 19 | 'rule' => 'notEmpty', 20 | 'message' => 'Please enter a name', 21 | 'required' => false, 22 | 'allowEmpty' => false, 23 | 'on' => null, 24 | 'last' => true, 25 | ), 26 | ), 27 | 'slug' => array( 28 | 'notEmpty' => array( 29 | 'rule' => 'notEmpty', 30 | 'message' => 'Please enter a slug', 31 | 'required' => false, 32 | 'allowEmpty' => false, 33 | 'on' => null, 34 | 'last' => true, 35 | ), 36 | 'regex0' => array( 37 | 'rule' => '/^[a-z0-9\_\-]*$/i', 38 | 'message' => 'Please enter a valid slug', 39 | 'required' => false, 40 | 'allowEmpty' => false, 41 | 'on' => null, 42 | 'last' => true, 43 | ), 44 | 'isUnique' => array( 45 | 'rule' => 'isUnique', 46 | 'message' => 'This slug is already in use', 47 | ), 48 | ), 49 | ); 50 | 51 | public $findMethods = array( 52 | 'cloud' => true, 53 | ); 54 | 55 | /** 56 | * Returns an array of tags that have blog posts assigned to them, along with 57 | * a weight score which is determined by the relative blog post count of the 58 | * tag compared to the tags with the least counts and the tags with the most 59 | * counts. 60 | * 61 | * @param string $state 62 | * @param array $query 63 | * @param array $results 64 | * @return array 65 | */ 66 | protected function _findCloud($state, $query = array(), $results = array()) { 67 | 68 | if ($state == 'before') { 69 | 70 | $query['fields'] = array( 71 | 'BlogPostTag.id', 72 | 'BlogPostTag.name', 73 | 'BlogPostTag.slug', 74 | 'BlogPostTag.blog_post_count', 75 | ); 76 | 77 | // We only want tags that have some posts assigned to them 78 | $query['conditions'][]['BlogPostTag.blog_post_count >'] = 0; 79 | 80 | $query['order'] = 'RAND()'; 81 | 82 | return $query; 83 | 84 | } else { 85 | 86 | // Extract all the post counts, sort them and take the first and the last 87 | // so we can work out the weight for the current tag. 88 | $blogPostCounts = Set::extract('/BlogPostTag/blog_post_count', $results); 89 | sort($blogPostCounts); 90 | $minBlogPostCount = current($blogPostCounts); 91 | $maxBlogPostCount = end($blogPostCounts); 92 | 93 | foreach ($results as $k => $result) { 94 | $results[$k] = array( 95 | 'text' => $result['BlogPostTag']['name'], 96 | 'id' => $result['BlogPostTag']['id'], 97 | 'url' => array( 98 | 'tag' => $result['BlogPostTag']['slug'], 99 | ), 100 | 'weight' => round(($result['BlogPostTag']['blog_post_count'] - $minBlogPostCount) / $maxBlogPostCount, 2), 101 | ); 102 | } 103 | 104 | return $results; 105 | 106 | } 107 | 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /Model/BlogSetting.php: -------------------------------------------------------------------------------- 1 | 6 | * @link http://www.neilcrookes.com http://neil.crook.es 7 | * @copyright (c) 2011 Neil Crookes 8 | * @license MIT License - http://www.opensource.org/licenses/mit-license.php 9 | */ 10 | class BlogSetting extends AppModel { 11 | /** 12 | * Display field 13 | * 14 | * @var string 15 | */ 16 | public $displayField = 'setting'; 17 | 18 | /** 19 | * The cache key used to cache the settings with 20 | * 21 | * @var string 22 | */ 23 | protected $_cacheKey = 'blog_settings'; 24 | 25 | /** 26 | * Clears cache files after saving 27 | * 28 | */ 29 | public function afterSave($created) { 30 | parent::afterSave($created); 31 | Cache::delete($this->_cacheKey); 32 | } 33 | 34 | 35 | /** 36 | * Returns the an array of setting => value pairs. Handles caching. 37 | * 38 | * @return array 39 | */ 40 | public function getSettings() { 41 | if ($blogSettings = Cache::read($this->_cacheKey)) { 42 | return $blogSettings; 43 | } 44 | $blogSettings = $this->find('list', array( 45 | 'fields' => array('setting', 'value') 46 | )); 47 | Cache::write($this->_cacheKey, $blogSettings); 48 | return $blogSettings; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | CakePHP Blog Plugin 2 | =================== 3 | 4 | A CakePHP blog plugin for CakePHP2.0+ 5 | 6 | Features 7 | -------- 8 | 9 | * Blog posts 10 | ** Paginated across all filter types (see below) 11 | ** Sticky flag 12 | ** In/out RSS feed flag 13 | * Filter by 14 | ** Categories (HABTM, hierarchy, only shows categories with posts in, displays number of posts in each category or one of its children) 15 | ** Tags (HABTM, including tag cloud) 16 | ** Year/month archive (based on created date/time, only shows months with posts in, grouped by year, displays number of posts in each month) 17 | * RSS for all posts, or posts in a particular category or tag 18 | * Settings 19 | ** Meta title, description, keywords for the unfiltered list and filtered by archive list 20 | 21 | Customisations 22 | -------------- 23 | 24 | Create custom views in your app directory e.g. app/View/Plugin/Blog/BlogPosts/index.ctp 25 | 26 | Requirements 27 | ------------ 28 | 29 | * CakePHP 2.0+ (so PHP5.2+) 30 | * MySQL v4+ 31 | * CakePHP HABTM Counter Cache behavior (http://github.com/neilcrookes/CakePHP-HABTM-Counter-Cache-Plugin) 32 | 33 | Installation 34 | ------------ 35 | 36 | git submodule add git://github.com/neilcrookes/CakePHP-Blog-Plugin.git app/Plugin/Blog 37 | 38 | or download from http://github.com/neilcrookes/CakePHP-Blog-Plugin 39 | 40 | Add the following line to your /app/config/bootstrap.php 41 | 42 | CakePlugin::load( array( 'Blog' => array( 'routes' => True ) ) ); 43 | 44 | And add this line to /app/config/routes.php 45 | 46 | CakePlugin::routes('Blog'); // Load Blog plugin routes 47 | 48 | Run the SQL script in Blog/Config/schema/schema.sql 49 | 50 | Go to mydomain.com/blog 51 | 52 | See: 53 | 54 | * mydomain.com/admin/blog_posts for creating blog posts (and follow links to create the tags and categories first) 55 | * mydomain.com/admin/blog_settings for editing the settings (things like the index page title and RSS feed title etc) 56 | 57 | (Requires that your Routing.prefixes includes 'admin') 58 | 59 | To-do 60 | ---- 61 | 62 | * Custom blog post content implementations 63 | * Internationalisation 64 | * Improve the admin interface 65 | 66 | All contributions welcome and will be attributed 67 | 68 | Copyright 69 | --------- 70 | 71 | Copyright Neil Crookes 2011 72 | 73 | License 74 | ------- 75 | 76 | The MIT License 77 | -------------------------------------------------------------------------------- /View/BlogPostCategories/admin_add.ctp: -------------------------------------------------------------------------------- 1 |
Paginator->sort('id');?> | 6 |Paginator->sort('parent_id');?> | 7 |Paginator->sort('lft');?> | 8 |Paginator->sort('rght');?> | 9 |Paginator->sort('name');?> | 10 |Paginator->sort('slug');?> | 11 |Paginator->sort('blog_post_count');?> | 12 |Paginator->sort('under_blog_post_count');?> | 13 |Paginator->sort('created');?> | 14 |Paginator->sort('modified');?> | 15 |16 | |
---|---|---|---|---|---|---|---|---|---|---|
27 | | 28 | | 29 | | 30 | | 31 | | 32 | | 33 | | 34 | | 35 | | 36 | | 37 | Html->link(__('View'), array('action' => 'view', $blogPostCategory['BlogPostCategory']['id'])); ?> 38 | Html->link(__('Edit'), array('action' => 'edit', $blogPostCategory['BlogPostCategory']['id'])); ?> 39 | Form->postLink(__('Delete'), array('action' => 'delete', $blogPostCategory['BlogPostCategory']['id']), null, __('Are you sure you want to delete # %s?', $blogPostCategory['BlogPostCategory']['id'])); ?> 40 | | 41 |
45 | Paginator->counter(array( 47 | 'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%') 48 | )); 49 | ?>
50 | 51 |Paginator->sort('id');?> | 6 |Paginator->sort('name');?> | 7 |Paginator->sort('slug');?> | 8 |Paginator->sort('blog_post_count');?> | 9 |Paginator->sort('created');?> | 10 |Paginator->sort('modified');?> | 11 |12 | |
---|---|---|---|---|---|---|
23 | | 24 | | 25 | | 26 | | 27 | | 28 | | 29 | Html->link(__('View'), array('action' => 'view', $blogPostTag['BlogPostTag']['id'])); ?> 30 | Html->link(__('Edit'), array('action' => 'edit', $blogPostTag['BlogPostTag']['id'])); ?> 31 | Form->postLink(__('Delete'), array('action' => 'delete', $blogPostTag['BlogPostTag']['id']), null, __('Are you sure you want to delete # %s?', $blogPostTag['BlogPostTag']['id'])); ?> 32 | | 33 |
37 | Paginator->counter(array( 39 | 'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%') 40 | )); 41 | ?>
42 | 43 |Paginator->sort('id');?> | 6 |Paginator->sort('title');?> | 7 |Paginator->sort('slug');?> | 8 |Paginator->sort('published');?> | 9 |Paginator->sort('sticky');?> | 10 |Paginator->sort('in_rss');?> | 11 |Paginator->sort('created');?> | 12 |Paginator->sort('modified');?> | 13 |14 | |
---|---|---|---|---|---|---|---|---|
25 | | 26 | | 27 | | 28 | | 29 | | 30 | | 31 | | 32 | | 33 | Html->link(__('View'), array('action' => 'view', $blogPost['BlogPost']['id'])); ?> 34 | Html->link(__('Edit'), array('action' => 'edit', $blogPost['BlogPost']['id'])); ?> 35 | Form->postLink(__('Delete'), array('action' => 'delete', $blogPost['BlogPost']['id']), null, __('Are you sure you want to delete # %s?', $blogPost['BlogPost']['id'])); ?> 36 | | 37 |
41 | Paginator->counter(array( 43 | 'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%') 44 | )); 45 | ?>
46 | 47 |Showing posts Blog->filterDescription(); ?>, Html->link(__('Show all', true), array('action' => 'index')); ?>
5 | 6 | 7 | 8 | 9 | 10 | 11 |14 | 15 |
16 | 17 | 18 |Paginator->sort('id');?> | 6 |Paginator->sort('setting_text');?> | 7 |Paginator->sort('value');?> | 8 |Paginator->sort('modified');?> | 9 |10 | |
---|---|---|---|---|
21 | | Text->truncate($blogSetting['BlogSetting']['setting_text'], 100)); ?> | 22 |Text->truncate($blogSetting['BlogSetting']['value'], 100)); ?> | 23 |Time->nice($blogSetting['BlogSetting']['modified']); ?> | 24 |25 | Html->link(__('View'), array('action' => 'view', $blogSetting['BlogSetting']['id'])); ?> 26 | Html->link(__('Edit'), array('action' => 'edit', $blogSetting['BlogSetting']['id'])); ?> 27 | | 28 |
32 | Paginator->counter(array( 34 | 'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%') 35 | )); 36 | ?>
37 | 38 |