├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml └── src ├── Fbf └── LaravelBlog │ └── LaravelBlogServiceProvider.php ├── config ├── administrator │ └── posts.php ├── images.php ├── link.php ├── meta.php ├── routes.php ├── seed.php ├── views.php └── you_tube.php ├── controllers └── PostsController.php ├── lang ├── .gitkeep └── en │ └── messages.php ├── migrations ├── .gitkeep └── 2013_08_21_143501_create_posts_table.php ├── models └── Post.php ├── routes.php ├── seeds └── PostTableFakeSeeder.php └── views ├── partials ├── adjacent.blade.php ├── archives.blade.php ├── details.blade.php ├── list.blade.php └── share.blade.php └── posts ├── index.blade.php └── view.blade.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | 8 | before_script: 9 | - curl -s http://getcomposer.org/installer | php 10 | - php composer.phar install --dev 11 | 12 | script: phpunit -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Five by Five 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Laravel Blog 2 | ============ 3 | 4 | A Laravel 4 package to add a simple blog to a site 5 | 6 | ## Features 7 | 8 | * Paginated index view with configurable results per page 9 | * Year/Month archive filtering, including an archive partial that you can include in your own views (requires MySQL) 10 | * Draft/Approved status and published date fields for approvals and scheduled publishing of posts 11 | * Configurable URLs, e.g. /blog or /news/yyyy/mm or /articles/< article slug > 12 | * Fields for title, slug, main image or YouTube Video ID, summary, content, link text and url for external links, is sticky?, published date, status, meta description and keywords, in RSS? 13 | * Faker seed to seed your blog with loads of good test data 14 | * Administrator config file for use with FrozenNode's Administrator package 15 | * Bundled migration for building the database schema 16 | * Extendable to add categories or tags relationships and filter by them, see below 17 | * 18 | 19 | ## Installation 20 | 21 | Add the following to you composer.json file (Recommend swapping "dev-master" for the latest release) 22 | 23 | "fbf/laravel-blog": "dev-master" 24 | 25 | Run 26 | 27 | composer update 28 | 29 | Add the following to app/config/app.php 30 | 31 | 'Fbf\LaravelBlog\LaravelBlogServiceProvider' 32 | 33 | Run the package migration 34 | 35 | php artisan migrate --package=fbf/laravel-blog 36 | 37 | Publish the config 38 | 39 | php artisan config:publish fbf/laravel-blog 40 | 41 | Optionally tweak the settings in the many config files for your app 42 | 43 | Optionally copy the administrator config file (`src/config/administrator/posts.php`) to your administrator model config directory. 44 | 45 | Create the relevant image upload directories that you specify in your config, e.g. 46 | 47 | public/uploads/packages/fbf/laravel-blog/main_image/original 48 | public/uploads/packages/fbf/laravel-blog/main_image/thumbnail 49 | public/uploads/packages/fbf/laravel-blog/main_image/resized 50 | 51 | ## Faker seed 52 | 53 | The package comes with a seed that can populate the table with a whole bunch of sample posts. There are some configuration options for the seed in the config file. To run it: 54 | 55 | php artisan db:seed --class="Fbf\LaravelBlog\PostTableFakeSeeder" 56 | 57 | ## Configuration 58 | 59 | See the many configuration options in the files in the config directory 60 | 61 | ## Administrator 62 | 63 | You can use the excellent Laravel Administrator package by FrozenNode to administer your blog. 64 | 65 | http://administrator.frozennode.com/docs/installation 66 | 67 | A ready-to-use model config file for the Post model (posts.php) is provided in the src/config/administrator directory of the package, which you can copy into the app/config/administrator directory (or whatever you set as the model_config_path in the administrator config file). 68 | 69 | ## Usage 70 | 71 | The package should work out the box (provided you have a master blade layout file, since the out-of-the-box views extend this) 72 | but if you want to add other content to the pages, such as your own header, logo, navigation, sidebar etc, you'll want to 73 | override the views provided. 74 | 75 | The package views declare several sections that you may want to `yield` in your `app/views/layouts/master.blade.php` file, e.g.: 76 | 77 | ```html 78 | 79 | 80 | 81 | @yield('title') 82 | 83 | 84 | 85 | 86 |
87 | @yield('content') 88 |
89 | 90 | 91 | ``` 92 | 93 | The package's views are actually really simple, and most of the presentation is done in partials. This is deliberate so you 94 | can override the package's views in your own app, so you can include your own chrome, navigation and sidebars etc, yet 95 | you can also still make use of the partials provided, if you want to. 96 | 97 | To override any view in your own app, just create the following directories and copy the file from the package into it, then hack away 98 | * `app/views/packages/fbf/laravel-blog/posts` 99 | * `app/views/packages/fbf/laravel-blog/partials` 100 | 101 | ## Extending the package 102 | 103 | This can be done for the purposes of say, relating the Post model to a Category model and allowing filtering by category, 104 | or relating the Post model to a User model to add and Author to a Post, or simply just for overriding the functionality 105 | in the bundled Post model. 106 | 107 | ### Basic approach 108 | 109 | (See the example below for more specific information.) 110 | 111 | To override the `Post` model in the package, create a model in you app/models directory that extends the package model. 112 | 113 | Finally, update the IoC Container to inject an instance of your model into the `Fbf\LaravelBlog\PostsController`, 114 | instead of the package's model, e.g. in `app/start/global.php` 115 | 116 | ```php 117 | App::bind('Fbf\LaravelBlog\PostsController', function() { 118 | return new Fbf\LaravelBlog\PostsController(new Post); 119 | }); 120 | ``` 121 | 122 | To achieve adding a relationship and then filtering by that relationship, this involves extending the `Post` model to 123 | add the relationship method that you want, to another existing model in your app, and overriding the 124 | `Post::indexByRelationship()` method. 125 | 126 | In addition, you'll need to create a link between the Post model and the related model. Depending on whether you are going for 127 | a `belongsTo` relationship or a `belongsToMany` relationship, this will either involve adding a field to the `fbf_posts` 128 | table, e.g. `category_id` or creating a many-to-many join table between the `fbf_posts` table and the related model's 129 | table, e.g. `post_tag`. So create a migration for this and run it. 130 | 131 | If you are using FrozenNode's Administrator package, update the `app/config/administrator/posts.php` config file to use 132 | your new model e.g. `Post` instead of `Fbf\LaravelBlog\Post`, and add the field to the `edit_fields` section to allow 133 | you to attach the Post to the related model. 134 | 135 | Finally you need to enable the filter by relationship route. In the package's routes config file in `src/config/routes.php` 136 | change the `'relationship_uri_prefix'` value from false to the string you'd like to use in the URL, after the base part, 137 | and before the 'relationship identifier'. For example, if you want your URLs to be `http://domain.com/posts/tagged/my-tag` 138 | change the `'relationship_uri_prefix'` value to be `'tagged'`. 139 | 140 | ### Example for Post belongsToMany Categories (hierarchical) 141 | 142 | This is a sample model class that demonstrates how you can add a many to many relationship to the Post model and 143 | allow filtering of posts by an item in the related model. This example relates the Post model to a Category model 144 | in the `fbf/laravel-categories` package, which is a hierarchy, so this not only allows you to filter posts to those 145 | assigned to a selected category, but to filter them by the selected category and/or any of it's children. 146 | 147 | To implement as is, create a file in `app/models/Post.php`, and copy this code into it 148 | 149 | ```php 150 | belongsToMany('Fbf\LaravelCategories\Category', 'category_post'); 164 | } 165 | 166 | /** 167 | * Query scope to filter posts by a given category 168 | * 169 | * @param $query 170 | * @param $categorySlug 171 | * @throws InvalidArgumentException 172 | * @return mixed 173 | */ 174 | public function scopeByRelationship($query, $categorySlug) 175 | { 176 | $category = Category::live() 177 | ->where('slug', '=', $categorySlug) 178 | ->first(); 179 | 180 | if (!$category) 181 | { 182 | throw new InvalidArgumentException('Category not found'); 183 | } 184 | 185 | return $query->join('category_post', $this->getTable().'.id', '=', 'category_post.post_id') 186 | ->join('fbf_categories', 'category_post.category_id', '=', 'fbf_categories.id') 187 | ->whereBetween('fbf_categories.lft', array($category->lft, $category->rgt)) 188 | ->groupBy($this->getTable().'.id'); 189 | } 190 | 191 | } 192 | ``` 193 | 194 | Here is a sample migration to link Posts to Categories. Create a migration called `2014_02_25_154025_link_posts_categories.php` 195 | and paste this code into it, then run the migration. 196 | 197 | ```php 198 | integer('category_id'); 215 | $table->integer('post_id'); 216 | }); 217 | } 218 | 219 | /** 220 | * Reverse the migrations. 221 | * 222 | * @return void 223 | */ 224 | public function down() 225 | { 226 | Schema::drop('category_post'); 227 | } 228 | 229 | } 230 | ``` 231 | 232 | If you are using FrozenNode's Administrator package, update the `app/config/administrator/posts.php` config file to use 233 | your new model e.g. `Post` instead of `Fbf\LaravelBlog\Post`, and add the field to the `edit_fields` section to allow 234 | you to attach the Post to the related model. 235 | 236 | ```php 237 | /** 238 | * The class name of the Eloquent model that this config represents 239 | * 240 | * @type string 241 | */ 242 | 'model' => 'Post', 243 | ``` 244 | ... 245 | ```php 246 | 'categories' => array( 247 | 'title' => 'Categories', 248 | 'type' => 'relationship', 249 | 'name_field' => 'path', 250 | 'options_sort_field' => 'name', 251 | ), 252 | ``` 253 | 254 | As mentioned above, update the IoC Container to inject an instance of your model into the `Fbf\LaravelBlog\PostsController`, 255 | instead of the package's model, e.g. in `app/start/global.php` 256 | 257 | ```php 258 | App::bind('Fbf\LaravelBlog\PostsController', function() { 259 | return new Fbf\LaravelBlog\PostsController(new Post); 260 | }); 261 | ``` 262 | 263 | Finally you need to enable the 'filter by relationship' route. In the package's routes config file in `src/config/routes.php` 264 | change the `'relationship_uri_prefix'` value from false to `'category'`. 265 | 266 | Once you've created some categories and assigned some posts to them, you can filter the posts by visiting a URL like 267 | `http://domain.com/blog/category/my-category`. 268 | 269 | ### Need comments? 270 | 271 | Have a look at https://github.com/FbF/Laravel-Comments -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fbf/laravel-blog", 3 | "description": "A Laravel 4 package to add a simple blog to a site", 4 | "keywords": ["laravel","blog"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Neil Crookes", 9 | "email": "neil.crookes@fivebyfiveuk.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.3.0", 14 | "illuminate/support": "4.*", 15 | "thujohn/rss": "dev-master", 16 | "cviebrock/eloquent-sluggable": "1.0.*", 17 | "fzaninotto/faker": "1.4.*" 18 | }, 19 | "autoload": { 20 | "classmap": [ 21 | "src/controllers", 22 | "src/migrations", 23 | "src/models", 24 | "src/seeds" 25 | ], 26 | "psr-0": { 27 | "Fbf\\LaravelBlog": "src/" 28 | } 29 | }, 30 | "minimum-stability": "dev" 31 | } 32 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Fbf/LaravelBlog/LaravelBlogServiceProvider.php: -------------------------------------------------------------------------------- 1 | package('fbf/laravel-blog'); 22 | 23 | if (\Config::get('laravel-blog::routes.use_package_routes', true)) 24 | { 25 | include __DIR__.'/../../routes.php'; 26 | } 27 | 28 | \App::register('Thujohn\Rss\RssServiceProvider'); 29 | \App::register('Cviebrock\EloquentSluggable\SluggableServiceProvider'); 30 | 31 | // Shortcut so developers don't need to add an Alias in app/config/app.php 32 | $this->app->booting(function() 33 | { 34 | $loader = \Illuminate\Foundation\AliasLoader::getInstance(); 35 | $loader->alias('Fbf\LaravelBlog\Rss', 'Thujohn\Rss\RssFacade'); 36 | $loader->alias('Sluggable', 'Cviebrock\EloquentSluggable\Facades\Sluggable'); 37 | }); 38 | 39 | } 40 | 41 | /** 42 | * Register the service provider. 43 | * 44 | * @return void 45 | */ 46 | public function register() 47 | { 48 | 49 | } 50 | 51 | /** 52 | * Get the services provided by the provider. 53 | * 54 | * @return array 55 | */ 56 | public function provides() 57 | { 58 | return array(); 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/config/administrator/posts.php: -------------------------------------------------------------------------------- 1 | 'Posts', 11 | 12 | /** 13 | * The singular name of your model 14 | * 15 | * @type string 16 | */ 17 | 'single' => 'post', 18 | 19 | /** 20 | * The class name of the Eloquent model that this config represents 21 | * 22 | * @type string 23 | */ 24 | 'model' => 'Fbf\LaravelBlog\Post', 25 | 26 | /** 27 | * The columns array 28 | * 29 | * @type array 30 | */ 31 | 'columns' => array( 32 | 'title' => array( 33 | 'title' => 'Title' 34 | ), 35 | 'published_date' => array( 36 | 'title' => 'Published' 37 | ), 38 | 'status' => array( 39 | 'title' => 'Status', 40 | 'select' => "CASE (:table).status WHEN '".Fbf\LaravelBlog\Post::APPROVED."' THEN 'Approved' WHEN '".Fbf\LaravelBlog\Post::DRAFT."' THEN 'Draft' END", 41 | ), 42 | 'updated_at' => array( 43 | 'title' => 'Last Updated' 44 | ), 45 | ), 46 | 47 | /** 48 | * The edit fields array 49 | * 50 | * @type array 51 | */ 52 | 'edit_fields' => array( 53 | 'title' => array( 54 | 'title' => 'Title', 55 | 'type' => 'text', 56 | ), 57 | 'slug' => array( 58 | 'title' => 'Slug', 59 | 'type' => 'text', 60 | 'visible' => function($model) 61 | { 62 | return $model->exists; 63 | }, 64 | ), 65 | 'main_image' => array( 66 | 'title' => 'Main Image', 67 | 'type' => 'image', 68 | 'naming' => 'random', 69 | 'location' => public_path() . Config::get('laravel-blog::images.main_image.original.dir'), 70 | 'size_limit' => 5, 71 | 'sizes' => array( 72 | array( 73 | Config::get('laravel-blog::images.main_image.sizes.thumbnail.width'), 74 | Config::get('laravel-blog::images.main_image.sizes.thumbnail.height'), 75 | Config::get('laravel-blog::images.main_image.sizes.thumbnail.method'), 76 | public_path() . Config::get('laravel-blog::images.main_image.sizes.thumbnail.dir'), 77 | 100 78 | ), 79 | array( 80 | Config::get('laravel-blog::images.main_image.sizes.resized.width'), 81 | Config::get('laravel-blog::images.main_image.sizes.resized.height'), 82 | Config::get('laravel-blog::images.main_image.sizes.resized.method'), 83 | public_path() . Config::get('laravel-blog::images.main_image.sizes.resized.dir'), 84 | 100 85 | ), 86 | ), 87 | ), 88 | 'main_image_alt' => array( 89 | 'title' => 'Image ALT text', 90 | 'type' => 'text', 91 | ), 92 | 'you_tube_video_id' => array( 93 | 'title' => 'YouTube Video ID (Takes precedence over the image if it\'s populated)', 94 | 'type' => 'text', 95 | ), 96 | 'summary' => array( 97 | 'title' => 'Summary', 98 | 'type' => 'textarea', 99 | 'limit' => 300, //optional, defaults to no limit 100 | 'height' => 130, //optional, defaults to 100 101 | ), 102 | 'content' => array( 103 | 'title' => 'Content', 104 | 'type' => 'wysiwyg', 105 | ), 106 | 'link_text' => array( 107 | 'title' => 'Link Text', 108 | 'type' => 'text', 109 | 'visible' => Config::get('laravel-blog::link.show'), 110 | ), 111 | 'link_url' => array( 112 | 'title' => 'Link URL', 113 | 'type' => 'text', 114 | 'visible' => Config::get('laravel-blog::link.show'), 115 | ), 116 | 'is_sticky' => array( 117 | 'title' => 'Is sticky?', 118 | 'type' => 'bool', 119 | ), 120 | 'in_rss' => array( 121 | 'title' => 'In RSS Feed?', 122 | 'type' => 'bool', 123 | ), 124 | 'page_title' => array( 125 | 'title' => 'Page Title', 126 | 'type' => 'text', 127 | ), 128 | 'meta_description' => array( 129 | 'title' => 'Meta Description', 130 | 'type' => 'textarea', 131 | ), 132 | 'meta_keywords' => array( 133 | 'title' => 'Meta Keywords', 134 | 'type' => 'textarea', 135 | ), 136 | 'published_date' => array( 137 | 'title' => 'Published Date', 138 | 'type' => 'datetime', 139 | 'date_format' => 'yy-mm-dd', //optional, will default to this value 140 | 'time_format' => 'HH:mm', //optional, will default to this value 141 | ), 142 | 'status' => array( 143 | 'type' => 'enum', 144 | 'title' => 'Status', 145 | 'options' => array( 146 | Fbf\LaravelBlog\Post::DRAFT => 'Draft', 147 | Fbf\LaravelBlog\Post::APPROVED => 'Approved', 148 | ), 149 | ), 150 | 'created_at' => array( 151 | 'title' => 'Created', 152 | 'type' => 'datetime', 153 | 'editable' => false, 154 | ), 155 | 'updated_at' => array( 156 | 'title' => 'Updated', 157 | 'type' => 'datetime', 158 | 'editable' => false, 159 | ), 160 | ), 161 | 162 | /** 163 | * The filter fields 164 | * 165 | * @type array 166 | */ 167 | 'filters' => array( 168 | 'title' => array( 169 | 'title' => 'Title', 170 | ), 171 | 'summary' => array( 172 | 'type' => 'text', 173 | 'title' => 'Summary', 174 | ), 175 | 'content' => array( 176 | 'type' => 'text', 177 | 'title' => 'Content', 178 | ), 179 | 'published_date' => array( 180 | 'title' => 'Published Date', 181 | 'type' => 'date', 182 | ), 183 | 'status' => array( 184 | 'type' => 'enum', 185 | 'title' => 'Status', 186 | 'options' => array( 187 | Fbf\LaravelBlog\Post::DRAFT => 'Draft', 188 | Fbf\LaravelBlog\Post::APPROVED => 'Approved', 189 | ), 190 | ), 191 | ), 192 | 193 | /** 194 | * The width of the model's edit form 195 | * 196 | * @type int 197 | */ 198 | 'form_width' => 500, 199 | 200 | /** 201 | * The validation rules for the form, based on the Laravel validation class 202 | * 203 | * @type array 204 | */ 205 | 'rules' => array( 206 | 'title' => 'required|max:255', 207 | 'main_image' => 'max:255', 208 | 'main_image_alt' => 'max:255', 209 | 'you_tube_video_id' => 'max:255', 210 | 'status' => 'required|in:'.Fbf\LaravelBlog\Post::DRAFT.','.Fbf\LaravelBlog\Post::APPROVED, 211 | 'published_date' => 'required|date_format:"Y-m-d H:i:s"|date', 212 | ), 213 | 214 | /** 215 | * The sort options for a model 216 | * 217 | * @type array 218 | */ 219 | 'sort' => array( 220 | 'field' => 'updated_at', 221 | 'direction' => 'desc', 222 | ), 223 | 224 | /** 225 | * If provided, this is run to construct the front-end link for your model 226 | * 227 | * @type function 228 | */ 229 | 'link' => function($model) 230 | { 231 | return $model->getUrl(); 232 | }, 233 | 234 | ); 235 | -------------------------------------------------------------------------------- /src/config/images.php: -------------------------------------------------------------------------------- 1 | array( 16 | 17 | /** 18 | * Whether the Main Image field is shown in the administrator config, and whether it's created for the seed 19 | */ 20 | 'show' => true, 21 | 22 | /** 23 | * Settings for the original image uploaded 24 | */ 25 | 'original' => array( 26 | 27 | /** 28 | * Location, relative to public_path(), where the image is stored. 29 | */ 30 | 'dir' => '/uploads/packages/fbf/laravel-blog/main_image/original/', 31 | 32 | ), 33 | 34 | /** 35 | * Settings for the various sizes of the images that are created from the original 36 | */ 37 | 'sizes' => array( 38 | 39 | /** 40 | * The thumbnail size is used on the list view 41 | */ 42 | 'thumbnail' => array( 43 | 'dir' => '/uploads/packages/fbf/laravel-blog/main_image/thumbnail/', 44 | 'method' => 'crop', 45 | 'width' => 150, 46 | 'height' => 150, 47 | ), 48 | 49 | /** 50 | * The resized size is used on the details view 51 | */ 52 | 'resized' => array( 53 | 'dir' => '/uploads/packages/fbf/laravel-blog/main_image/resized/', 54 | 'method' => 'crop', 55 | 'width' => 400, 56 | 'height' => 400, 57 | ), 58 | ), 59 | ), 60 | ); -------------------------------------------------------------------------------- /src/config/link.php: -------------------------------------------------------------------------------- 1 | true, 13 | 14 | ); -------------------------------------------------------------------------------- /src/config/meta.php: -------------------------------------------------------------------------------- 1 | array( 12 | 13 | /** 14 | * Page title of the blog index page 15 | * 16 | * @type string 17 | */ 18 | 'page_title' => 'My blog', 19 | 20 | /** 21 | * Meta description of the blog index page 22 | * 23 | * @type string 24 | */ 25 | 'meta_description' => 'This is the description for my blog', 26 | 27 | /** 28 | * Meta keywords of the blog index page 29 | * 30 | * @type string 31 | */ 32 | 'meta_keywords' => 'These are the keywords for my blog', 33 | 34 | ), 35 | 36 | /** 37 | * Meta configuration for RSS feed (title and description) 38 | */ 39 | 'rss_feed' => array( 40 | 41 | /** 42 | * RSS feed title 43 | * 44 | * @type string 45 | */ 46 | 'title' => 'My blog', 47 | 48 | /** 49 | * RSS feed description 50 | * 51 | * @type string 52 | */ 53 | 'description' => 'This is the description for my blog', 54 | 55 | ), 56 | 57 | ); -------------------------------------------------------------------------------- /src/config/routes.php: -------------------------------------------------------------------------------- 1 | true, 10 | 11 | /** 12 | * Base URI of the package's pages, e.g. "blog" in http://domain.com/blog and http://domain.com/blog/my-post 13 | */ 14 | 'base_uri' => 'blog', 15 | 16 | /** 17 | * URI prefix of the blog relationship filter 18 | * 19 | * @type mixed false | string 20 | */ 21 | 'relationship_uri_prefix' => false, 22 | 23 | ); -------------------------------------------------------------------------------- /src/config/seed.php: -------------------------------------------------------------------------------- 1 | true, 12 | 13 | /** 14 | * Number of fake posts to create 15 | */ 16 | 'number' => 20, 17 | 18 | /** 19 | * Configuration options for YouTube for the fake seeder 20 | */ 21 | 'you_tube' => array( 22 | 23 | /** 24 | * One in every X posts is a YouTube Video (use 0 for no YouTube Videos, or set you_tube.show to false) 25 | */ 26 | 'freq' => 5, 27 | 28 | /** 29 | * List of the you tube video ids that could be used 30 | */ 31 | 'video_ids' => array( 32 | 'dQw4w9WgXcQ' 33 | ), 34 | 35 | ), 36 | 37 | /** 38 | * Configuration options for the images for the fake seeder 39 | */ 40 | 'images' => array( 41 | 42 | /** 43 | * Configuration options for the main image for the fake seeder 44 | */ 45 | 'main_image' => array( 46 | 47 | /** 48 | * One in every X posts that is not a YouTube Video, has a main image (use 0 for no main images, or set 49 | * images.main_image.show to false) 50 | */ 51 | 'freq' => 2, 52 | 53 | /** 54 | * Lorem Pixel category to use for the main image 55 | */ 56 | 'category' => 'abstract', 57 | 58 | /** 59 | * Width of the original image to fetch by the fake seeder 60 | */ 61 | 'original_width' => 400, 62 | 63 | /** 64 | * Height of the original image to fetch by the fake seeder 65 | */ 66 | 'original_height' => 400, 67 | 68 | ), 69 | 70 | ), 71 | 72 | /** 73 | * Configuration options for the seeder links 74 | */ 75 | 'link' => array( 76 | 77 | /** 78 | * One in every X items has a link (use 0 for no links) 79 | */ 80 | 'freq' => 2, 81 | 82 | /** 83 | * Options for the link text 84 | */ 85 | 'texts' => array( 86 | 'Visit website', 87 | 'Read more', 88 | 'Find out more', 89 | 'Go to the official site', 90 | 'Check this out' 91 | ), 92 | 93 | /** 94 | * Options for the link urls 95 | */ 96 | 'urls' => array( 97 | 'http://www.google.com', 98 | 'http://www.bbc.co.uk', 99 | 'http://www.fivebyfiveuk.com', 100 | ), 101 | 102 | ), 103 | 104 | ); -------------------------------------------------------------------------------- /src/config/views.php: -------------------------------------------------------------------------------- 1 | 'j\<\s\u\p\>S\<\/\s\u\p\> F \'y', 15 | 16 | /** 17 | * Configuration options for the index page 18 | */ 19 | 'index_page' => array( 20 | 21 | /** 22 | * The view to use for the blog index pages (standard default index, index by year/month and index by 23 | * relationship). You can change this to a view in your app, and inside your own view you can @include the 24 | * various partials in the package if you want to, or you can use this one provided, provided you have a blade 25 | * layout called `master`, or you can leave this setting as it is, but create a new view file inside you own app 26 | * at `app/views/packages/fbf/laravel-blog/posts/index.blade.php`. 27 | * 28 | * @type string 29 | */ 30 | 'view' => 'laravel-blog::posts.index', 31 | 32 | /** 33 | * Determines whether to show the archives on the index page 34 | * 35 | * @type bool 36 | */ 37 | 'show_archives' => true, 38 | 39 | /** 40 | * The number of items to show per page on the index page 41 | * 42 | * @type int 43 | */ 44 | 'results_per_page' => 4, 45 | 46 | ), 47 | 48 | /** 49 | * Configuration options for the view page 50 | */ 51 | 'view_page' => array( 52 | 53 | /** 54 | * The view to use for the blog post detail page. You can change this to a view in your app, and inside your own 55 | * view you can @include the various partials in the package if you want to, or you can use this one provided, 56 | * provided you have a blade layout called `master`, or you can leave this setting as it is, but create a new 57 | * view file inside you own app at `app/views/packages/fbf/laravel-blog/posts/view.blade.php`. 58 | */ 59 | 'view' => 'laravel-blog::posts.view', 60 | 61 | /** 62 | * Determines whether to show the archives on the view page 63 | * 64 | * @type bool 65 | */ 66 | 'show_archives' => true, 67 | 68 | /** 69 | * Determines whether to show links to adjacent (i.e. previous and next) items on the view page 70 | * 71 | * @type bool 72 | */ 73 | 'show_adjacent_items' => true, 74 | 75 | /** 76 | * Determines whether to show the share partial on the post view page. Note, if you want to change the share 77 | * partial, just override it by creating a new file at `app/views/packages/fbf/laravel-blog/partials/share.blade.php` 78 | * 79 | * @type bool 80 | */ 81 | 'show_share_partial' => true, 82 | 83 | ), 84 | 85 | ); -------------------------------------------------------------------------------- /src/config/you_tube.php: -------------------------------------------------------------------------------- 1 | true, 12 | 13 | /** 14 | * YouTube Embed Player code used if an item has a You Tube Video ID set 15 | * instead of a Main Image. Changing the settings will apply to all items that 16 | * have a You Tube Video on them. The placeholder "%YOU_TUBE_VIDEO_ID%" is 17 | * replaced with the You Tube Video ID in the database for this item. 18 | */ 19 | 'embed_code' => '', 20 | 21 | /** 22 | * YouTube Thumbnail code used if an item has a You Tube Video ID set instead of a Main Image. Changing the 23 | * settings will apply to all entries on the index pages for items that have a You Tube Video. The 24 | * placeholder "%YOU_TUBE_VIDEO_ID%" is replaced with the You Tube Video ID in the database for this item. 25 | */ 26 | 'thumbnail_code' => '', 27 | 28 | ); -------------------------------------------------------------------------------- /src/controllers/PostsController.php: -------------------------------------------------------------------------------- 1 | post = $post; 16 | } 17 | 18 | /** 19 | * @return mixed 20 | */ 21 | public function index() 22 | { 23 | // Get the selected posts 24 | $posts = $this->post->live() 25 | ->orderBy($this->post->getTable().'.is_sticky', 'desc') 26 | ->orderBy($this->post->getTable().'.published_date', 'desc') 27 | ->paginate(\Config::get('laravel-blog::views.index_page.results_per_page')); 28 | 29 | // Get the archives data if the config says to show the archives on the index page 30 | if (\Config::get('laravel-blog::views.index_page.show_archives')) 31 | { 32 | $archives = $this->post->archives(); 33 | } 34 | 35 | return \View::make(\Config::get('laravel-blog::views.index_page.view'), compact('posts', 'archives')); 36 | } 37 | 38 | /** 39 | * @param $selectedYear 40 | * @param $selectedMonth 41 | * @return mixed 42 | */ 43 | public function indexByYearMonth($selectedYear, $selectedMonth) 44 | { 45 | // Get the selected posts 46 | $posts = $this->post->live() 47 | ->byYearMonth($selectedYear, $selectedMonth) 48 | ->orderBy($this->post->getTable().'.is_sticky', 'desc') 49 | ->orderBy($this->post->getTable().'.published_date', 'desc') 50 | ->paginate(\Config::get('laravel-blog::views.index_page.results_per_page')); 51 | 52 | // Get the archives data if the config says to show the archives on the index page 53 | if (\Config::get('laravel-blog::views.index_page.show_archives')) 54 | { 55 | $archives = $this->post->archives(); 56 | } 57 | 58 | return \View::make(\Config::get('laravel-blog::views.index_page.view'), compact('posts', 'selectedYear', 'selectedMonth', 'archives')); 59 | } 60 | 61 | /** 62 | * @param $relationshipIdentifier 63 | * @return mixed 64 | */ 65 | public function indexByRelationship($relationshipIdentifier) 66 | { 67 | // Get the selected posts 68 | $posts = $this->post->live() 69 | ->byRelationship($relationshipIdentifier) 70 | ->orderBy($this->post->getTable().'.is_sticky', 'desc') 71 | ->orderBy($this->post->getTable().'.published_date', 'desc') 72 | ->paginate(\Config::get('laravel-blog::views.index_page.results_per_page')); 73 | 74 | // Get the archives data if the config says to show the archives on the index page 75 | if (\Config::get('laravel-blog::views.index_page.show_archives')) 76 | { 77 | $archives = $this->post->archives(); 78 | } 79 | 80 | return \View::make(\Config::get('laravel-blog::views.index_page.view'), compact('posts', 'archives')); 81 | } 82 | 83 | /** 84 | * @param $slug 85 | * @return mixed 86 | */ 87 | public function view($slug) 88 | { 89 | // Get the selected post 90 | $post = $this->post->live() 91 | ->where($this->post->getTable().'.slug', '=', $slug) 92 | ->firstOrFail(); 93 | 94 | // Get the next newest and next oldest post if the config says to show these links on the view page 95 | $newer = $older = false; 96 | if (\Config::get('laravel-blog::views.view_page.show_adjacent_items')) 97 | { 98 | $newer = $post->newer(); 99 | $older = $post->older(); 100 | } 101 | 102 | // Get the archives data if the config says to show the archives on the view page 103 | if (\Config::get('laravel-blog::views.view_page.show_archives')) 104 | { 105 | $archives = $this->post->archives(); 106 | } 107 | 108 | return \View::make(\Config::get('laravel-blog::views.view_page.view'), compact('post', 'newer', 'older', 'archives')); 109 | 110 | } 111 | 112 | /** 113 | * @return mixed 114 | */ 115 | public function rss() 116 | { 117 | $feed = Rss::feed('2.0', 'UTF-8'); 118 | $feed->channel(array( 119 | 'title' => \Config::get('laravel-blog::meta.rss_feed.title'), 120 | 'description' => \Config::get('laravel-blog::meta.rss_feed.description'), 121 | 'link' => \URL::current(), 122 | )); 123 | $posts = $this->post->live() 124 | ->where($this->post->getTable().'.in_rss', '=', true) 125 | ->orderBy($this->post->getTable().'.published_date', 'desc') 126 | ->take(10) 127 | ->get(); 128 | foreach ($posts as $post){ 129 | $feed->item(array( 130 | 'title' => $post->title, 131 | 'description' => $post->summary, 132 | 'link' => \URL::action('Fbf\LaravelBlog\PostsController@view', array('slug' => $post->slug)), 133 | )); 134 | } 135 | return \Response::make($feed, 200, array('Content-Type', 'application/rss+xml')); 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /src/lang/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FbF/Laravel-Blog/d731e710664fbc8c11a4162804bc77888b5a3743/src/lang/.gitkeep -------------------------------------------------------------------------------- /src/lang/en/messages.php: -------------------------------------------------------------------------------- 1 | array( 6 | 'more_link_text' => 'Read more', 7 | 'no_items' => 'Sorry, there are no articles.' 8 | ), 9 | 10 | 'details' => array( 11 | 'share_label' => 'Share', 12 | 'all_link_text' => 'All posts', 13 | ), 14 | 15 | 'share' => array( 16 | 'facebook' => 'Share on Facebook', 17 | 'twitter' => 'Share on Twitter', 18 | 'label' => 'Share', 19 | ), 20 | 21 | 'adjacent' => array( 22 | 'next_link_text' => '« :title', 23 | 'prev_link_text' => ':title »', 24 | ), 25 | 26 | ); -------------------------------------------------------------------------------- /src/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FbF/Laravel-Blog/d731e710664fbc8c11a4162804bc77888b5a3743/src/migrations/.gitkeep -------------------------------------------------------------------------------- /src/migrations/2013_08_21_143501_create_posts_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('title'); 18 | $table->string('main_image')->nullable(); 19 | $table->string('main_image_alt')->nullable(); 20 | $table->string('you_tube_video_id')->nullable(); 21 | $table->text('summary'); 22 | $table->text('content'); 23 | $table->string('link_text')->nullable(); 24 | $table->string('link_url')->nullable(); 25 | $table->boolean('in_rss'); 26 | $table->boolean('is_sticky'); 27 | $table->string('slug')->unique(); 28 | $table->string('page_title'); 29 | $table->text('meta_description'); 30 | $table->text('meta_keywords'); 31 | $table->enum('status', array('DRAFT', 'APPROVED'))->default('DRAFT'); 32 | $table->dateTime('published_date')->nullable(); 33 | $table->dateTime('deleted_at')->nullable(); 34 | $table->timestamps(); 35 | }); 36 | } 37 | 38 | /** 39 | * Reverse the migrations. 40 | * 41 | * @return void 42 | */ 43 | public function down() 44 | { 45 | Schema::drop('fbf_blog_posts'); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/models/Post.php: -------------------------------------------------------------------------------- 1 | 'title', 37 | 'save_to' => 'slug', 38 | 'separator' => '-', 39 | 'unique' => true, 40 | 'include_trashed' => true, 41 | ); 42 | 43 | /** 44 | * Query scope for "live" posts, adds conditions for status = APPROVED and published date is in the past 45 | * 46 | * @param $query 47 | * @return mixed 48 | */ 49 | public function scopeLive($query) 50 | { 51 | return $query->where($this->getTable().'.status', '=', Post::APPROVED) 52 | ->where($this->getTable().'.published_date', '<=', \Carbon\Carbon::now()); 53 | } 54 | 55 | /** 56 | * Query scope for posts published within the given year and month number. 57 | * 58 | * @param $query 59 | * @param $year 60 | * @param $month 61 | * @return mixed 62 | */ 63 | public function scopeByYearMonth($query, $year, $month) 64 | { 65 | return $query->where(\DB::raw('DATE_FORMAT(published_date, "%Y%m")'), '=', $year.$month); 66 | } 67 | 68 | /** 69 | * Returns a formatted multi-dimensional array, indexed by year and month number, each with an array with keys for 70 | * 'month name' and the count / number of items in that month. For example: 71 | * 72 | * array( 73 | * 2014 => array( 74 | * 01 => array( 75 | * 'monthname' => 'January', 76 | * 'count' => 4, 77 | * ), 78 | * 02 => array( 79 | * 'monthname' => 'February', 80 | * 'count' => 3, 81 | * ), 82 | * ) 83 | * ) 84 | * 85 | * @return array 86 | */ 87 | public static function archives() 88 | { 89 | // Get the data 90 | $archives = self::live() 91 | ->select(\DB::raw(' 92 | YEAR(`published_date`) AS `year`, 93 | DATE_FORMAT(`published_date`, "%m") AS `month`, 94 | MONTHNAME(`published_date`) AS `monthname`, 95 | COUNT(*) AS `count` 96 | ')) 97 | ->groupBy(\DB::raw('DATE_FORMAT(`published_date`, "%Y%m")')) 98 | ->orderBy('published_date', 'desc') 99 | ->get(); 100 | 101 | // Convert it to a nicely formatted array so we can easily render the view 102 | $results = array(); 103 | foreach ($archives as $archive) 104 | { 105 | $results[$archive->year][$archive->month] = array( 106 | 'monthname' => $archive->monthname, 107 | 'count' => $archive->count, 108 | ); 109 | } 110 | return $results; 111 | } 112 | 113 | /** 114 | * To filter posts by a relationship, you should extend the Post class and define both the relationship and the 115 | * scopeByRelationship() method in your subclass. See the package readme for an example. 116 | * 117 | * @param $query 118 | * @param $relationshipIdentifier 119 | * @throws Exception 120 | */ 121 | public function scopeByRelationship($query, $relationshipIdentifier) 122 | { 123 | throw new Exception('Extend this class and override this method according to your app\s requirements'); 124 | } 125 | 126 | /** 127 | * Returns the HTML img tag for the requested image type and size for this item 128 | * 129 | * @param $type 130 | * @param $size 131 | * @return null|string 132 | */ 133 | public function getImage($type, $size, array $attributes = array()) 134 | { 135 | if (empty($this->$type)) 136 | { 137 | return null; 138 | } 139 | $html = '{$type.'_alt'} . '"'; 141 | $html .= ' width="' . $this->getImageWidth($type, $size) . '"'; 142 | $html .= ' height="' . $this->getImageHeight($type, $size) . '"'; 143 | 144 | $html_attributes = ''; 145 | if (!empty($attributes)) { 146 | $html_attributes = join(' ', array_map(function($key) use ($attributes) { 147 | if(is_bool($attributes[$key])) 148 | { 149 | return $attributes[$key] ? $key : ''; 150 | } 151 | return "{$key}=\"{$attributes[$key]}\""; 152 | }, array_keys($attributes))); 153 | } 154 | 155 | return $html; 156 | } 157 | 158 | /** 159 | * Returns the value for use in the src attribute of an img tag for the given image type and size 160 | * 161 | * @param $type 162 | * @param $size 163 | * @return null|string 164 | */ 165 | public function getImageSrc($type, $size) 166 | { 167 | if (empty($this->$type)) 168 | { 169 | return null; 170 | } 171 | return $this->getImageConfig($type, $size, 'dir') . $this->$type; 172 | } 173 | 174 | /** 175 | * Returns the value for use in the width attribute of an img tag for the given image type and size 176 | * 177 | * @param $type 178 | * @param $size 179 | * @return null|string 180 | */ 181 | public function getImageWidth($type, $size) 182 | { 183 | if (empty($this->$type)) 184 | { 185 | return null; 186 | } 187 | $method = $this->getImageConfig($type, $size, 'method'); 188 | 189 | // Width varies for images that are 'portrait', 'auto', 'fit', 'crop' 190 | if (in_array($method, array('portrait', 'auto', 'fit', 'crop'))) 191 | { 192 | list($width) = $this->getImageDimensions($type, $size); 193 | return $width; 194 | } 195 | return $this->getImageConfig($type, $size, 'width'); 196 | } 197 | 198 | /** 199 | * Returns the value for use in the height attribute of an img tag for the given image type and size 200 | * 201 | * @param $type 202 | * @param $size 203 | * @return null|string 204 | */ 205 | public function getImageHeight($type, $size) 206 | { 207 | if (empty($this->$type)) 208 | { 209 | return null; 210 | } 211 | $method = $this->getImageConfig($type, $size, 'method'); 212 | 213 | // Height varies for images that are 'landscape', 'auto', 'fit', 'crop' 214 | if (in_array($method, array('landscape', 'auto', 'fit', 'crop'))) 215 | { 216 | list($width, $height) = $this->getImageDimensions($type, $size); 217 | return $height; 218 | } 219 | return $this->getImageConfig($type, $size, 'height'); 220 | } 221 | 222 | /** 223 | * Returns an array of the width and height of the current instance's image $type and $size 224 | * 225 | * @param $type 226 | * @param $size 227 | * @return array 228 | */ 229 | protected function getImageDimensions($type, $size) 230 | { 231 | $pathToImage = public_path($this->getImageConfig($type, $size, 'dir') . $this->$type); 232 | if (is_file($pathToImage) && file_exists($pathToImage)) 233 | { 234 | list($width, $height) = getimagesize($pathToImage); 235 | } 236 | else 237 | { 238 | $width = $height = false; 239 | } 240 | return array($width, $height); 241 | } 242 | 243 | /** 244 | * Returns the config setting for an image 245 | * 246 | * @param $imageType 247 | * @param $size 248 | * @param $property 249 | * @internal param $type 250 | * @return mixed 251 | */ 252 | public function getImageConfig($imageType, $size, $property) 253 | { 254 | $config = $this->getConfigPrefix().'images.' . $imageType . '.'; 255 | if ($size == 'original') 256 | { 257 | $config .= 'original.'; 258 | } 259 | elseif (!is_null($size)) 260 | { 261 | $config .= 'sizes.' . $size . '.'; 262 | } 263 | $config .= $property; 264 | return \Config::get($config); 265 | } 266 | 267 | /** 268 | * Helper function to determine whether the item has a YouTube video 269 | * 270 | * @return bool 271 | */ 272 | public function hasYouTubeVideo() 273 | { 274 | return !empty($this->you_tube_video_id); 275 | } 276 | 277 | /** 278 | * Returns the thumbnail image code defined in the config, for the current item's you tube video id 279 | * 280 | * @return string 281 | */ 282 | public function getYouTubeThumbnailImage() 283 | { 284 | return str_replace('%YOU_TUBE_VIDEO_ID%', $this->you_tube_video_id, \Config::get($this->getConfigPrefix().'you_tube.thumbnail_code')); 285 | } 286 | 287 | /** 288 | * Returns the embed code defined in the config, for the current item's you tube video id 289 | * 290 | * @return string 291 | */ 292 | public function getYouTubeEmbedCode() 293 | { 294 | return str_replace('%YOU_TUBE_VIDEO_ID%', $this->you_tube_video_id, \Config::get($this->getConfigPrefix().'you_tube.embed_code')); 295 | } 296 | 297 | /** 298 | * Returns the published date formatted according to the config setting 299 | * @return string 300 | */ 301 | public function getDate() 302 | { 303 | return date(\Config::get($this->getConfigPrefix().'views.published_date_format'), strtotime($this->published_date)); 304 | } 305 | 306 | /** 307 | * Returns the URL of the post 308 | * @return string 309 | */ 310 | public function getUrl() 311 | { 312 | return \URL::action('Fbf\LaravelBlog\PostsController@view', array('slug' => $this->slug)); 313 | } 314 | 315 | /** 316 | * Help function to determine whether the item has a link 317 | * @return bool 318 | */ 319 | public function hasLink() 320 | { 321 | return !empty($this->link_text) && !empty($this->link_url); 322 | } 323 | 324 | /** 325 | * Returns the next newer post, relative to the current one, if it exists 326 | * @return mixed 327 | */ 328 | public function newer() 329 | { 330 | return $this->live() 331 | ->where('published_date', '>=', $this->published_date) 332 | ->where('id', '<>', $this->id) 333 | ->orderBy('published_date', 'asc') 334 | ->orderBy('id', 'asc') 335 | ->first(); 336 | } 337 | 338 | /** 339 | * Returns the next older post, relative to the current one, if it exists 340 | * @return mixed 341 | */ 342 | public function older() 343 | { 344 | return $this->live() 345 | ->where('published_date', '<=', $this->published_date) 346 | ->where('id', '<>', $this->id) 347 | ->orderBy('published_date', 'desc') 348 | ->orderBy('id', 'desc') 349 | ->first(); 350 | } 351 | 352 | /** 353 | * Returns the config prefix 354 | * 355 | * @return string 356 | */ 357 | public function getConfigPrefix() 358 | { 359 | return $this->configPrefix; 360 | } 361 | 362 | /** 363 | * Sets the config prefix string 364 | * 365 | * @param $configBase string 366 | * @return string 367 | */ 368 | public function setConfigPrefix($configBase) 369 | { 370 | return $this->configPrefix = $configBase; 371 | } 372 | 373 | } 374 | -------------------------------------------------------------------------------- /src/routes.php: -------------------------------------------------------------------------------- 1 | where(array('year' => '\d{4}', 'month' => '\d{2}')); 8 | 9 | if (Config::get('laravel-blog::routes.relationship_uri_prefix')) 10 | { 11 | // Relationship filtered listing, e.g. by category or tag, e.g. http://domain.com/blog/category/my-category 12 | Route::get(Config::get('laravel-blog::routes.base_uri').'/'.Config::get('laravel-blog::routes.relationship_uri_prefix').'/{relationshipIdentifier}', 'Fbf\LaravelBlog\PostsController@indexByRelationship'); 13 | } 14 | 15 | // Blog post detail page e.g. http://domain.com/blog/my-post 16 | Route::get(Config::get('laravel-blog::routes.base_uri').'/{slug}', 'Fbf\LaravelBlog\PostsController@view'); 17 | 18 | // RSS feed URL e.g. http://domain.com/blog.rss 19 | Route::get(Config::get('laravel-blog::routes.base_uri').'.rss', 'Fbf\LaravelBlog\PostsController@rss'); -------------------------------------------------------------------------------- /src/seeds/PostTableFakeSeeder.php: -------------------------------------------------------------------------------- 1 | truncate(); 10 | 11 | $this->faker = \Faker\Factory::create(); 12 | 13 | $numberToCreate = \Config::get('laravel-blog::seed.number'); 14 | 15 | for ($i = 0; $i < $numberToCreate; $i++) 16 | { 17 | $this->create(); 18 | } 19 | 20 | echo 'Database seeded' . PHP_EOL; 21 | } 22 | 23 | protected function truncate() 24 | { 25 | $replace = \Config::get('laravel-blog::seed.replace'); 26 | if ($replace) 27 | { 28 | \DB::table('fbf_blog_posts')->delete(); 29 | } 30 | } 31 | 32 | protected function create() 33 | { 34 | $this->post = new Post(); 35 | $this->setTitle(); 36 | $this->setMedia(); 37 | $this->setSummary(); 38 | $this->setContent(); 39 | $this->setLink(); 40 | $this->setIsSticky(); 41 | $this->setInRss(); 42 | $this->setPageTitle(); 43 | $this->setMetaDescription(); 44 | $this->setMetaKeywords(); 45 | $this->setStatus(); 46 | $this->setPublishedDate(); 47 | $this->post->save(); 48 | } 49 | 50 | protected function setTitle() 51 | { 52 | $title = $this->faker->sentence(rand(1, 10)); 53 | $this->post->title = $title; 54 | } 55 | 56 | protected function setMedia() 57 | { 58 | if ($this->hasYouTubeVideos()) 59 | { 60 | $this->setYouTubeVideoId(); 61 | } 62 | elseif ($this->hasMainImage()) 63 | { 64 | $this->doMainImage(); 65 | } 66 | } 67 | 68 | protected function hasYouTubeVideos() 69 | { 70 | $youTubeVideoFreq = \Config::get('laravel-blog::seed.you_tube.freq'); 71 | $hasYouTubeVideos = $youTubeVideoFreq > 0 && rand(1, $youTubeVideoFreq) == $youTubeVideoFreq; 72 | return $hasYouTubeVideos; 73 | } 74 | 75 | protected function setYouTubeVideoId() 76 | { 77 | $this->post->you_tube_video_id = $this->faker->randomElement(\Config::get('laravel-blog::seed.you_tube.video_ids')); 78 | } 79 | 80 | protected function hasMainImage() 81 | { 82 | $mainImageFreq = \Config::get('laravel-blog::seed.images.main_image.freq'); 83 | $hasMainImage = $mainImageFreq > 0 && rand(1, $mainImageFreq) == $mainImageFreq; 84 | return $hasMainImage; 85 | } 86 | 87 | protected function doMainImage() 88 | { 89 | $imageOptions = \Config::get('laravel-blog::images.main_image'); 90 | if (!$imageOptions['show']) 91 | { 92 | return false; 93 | } 94 | $seedOptions = \Config::get('laravel-blog::seed.images.main_image'); 95 | $original = $this->faker->image( 96 | public_path($imageOptions['original']['dir']), 97 | $seedOptions['original_width'], 98 | $seedOptions['original_height'], 99 | $seedOptions['category'] 100 | ); 101 | $filename = basename($original); 102 | foreach ($imageOptions['sizes'] as $sizeOptions) 103 | { 104 | $image = $this->faker->image( 105 | public_path($sizeOptions['dir']), 106 | $sizeOptions['width'], 107 | $sizeOptions['height'] 108 | ); 109 | rename($image, public_path($sizeOptions['dir']) . $filename); 110 | } 111 | $this->post->main_image = $filename; 112 | $this->post->main_image_alt = $this->post->title; 113 | } 114 | 115 | protected function setSummary() 116 | { 117 | $this->post->summary = '

'.implode('

', $this->faker->paragraphs(rand(1, 2))).'

'; 118 | } 119 | 120 | protected function setContent() 121 | { 122 | $this->post->content = '

'.implode('

', $this->faker->paragraphs(rand(4, 10))).'

'; 123 | } 124 | 125 | protected function setLink() 126 | { 127 | if ($this->hasLink()) 128 | { 129 | $this->setLinkText(); 130 | $this->setLinkUrl(); 131 | } 132 | } 133 | 134 | protected function hasLink() 135 | { 136 | $showLink = \Config::get('laravel-blog::link.show'); 137 | if (!$showLink) 138 | { 139 | return false; 140 | } 141 | $linkFreq = \Config::get('laravel-blog::seed.link.freq'); 142 | $hasLink = $linkFreq > 0 && rand(1, $linkFreq) == $linkFreq; 143 | return $hasLink; 144 | } 145 | 146 | protected function setLinkText() 147 | { 148 | $linkTexts = \Config::get('laravel-blog::seed.link.texts'); 149 | $this->post->link_text = $this->faker->randomElement($linkTexts); 150 | } 151 | 152 | protected function setLinkUrl() 153 | { 154 | $linkUrls = \Config::get('laravel-blog::seed.link.urls'); 155 | $this->post->link_url = $this->faker->randomElement($linkUrls); 156 | } 157 | 158 | protected function setIsSticky() 159 | { 160 | $this->post->is_sticky = (bool) rand(0, 1); 161 | } 162 | 163 | protected function setInRss() 164 | { 165 | $this->post->in_rss = (bool) rand(0, 1); 166 | } 167 | 168 | protected function setPageTitle() 169 | { 170 | $this->post->page_title = $this->post->title; 171 | } 172 | 173 | protected function setMetaDescription() 174 | { 175 | $this->post->meta_description = $this->faker->paragraph(rand(1, 2)); 176 | } 177 | 178 | protected function setMetaKeywords() 179 | { 180 | $this->post->meta_keywords = $this->faker->words(10, true); 181 | } 182 | 183 | protected function setStatus() 184 | { 185 | $statuses = array( 186 | Post::DRAFT, 187 | Post::APPROVED 188 | ); 189 | $this->post->status = $this->faker->randomElement($statuses); 190 | } 191 | 192 | protected function setPublishedDate() 193 | { 194 | $this->post->published_date = $this->faker->dateTimeBetween('-2 years', '+1 month')->format('Y-m-d H:i:s'); 195 | } 196 | } -------------------------------------------------------------------------------- /src/views/partials/adjacent.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | @if ($newer) 4 |

5 | 6 | {{ trans('laravel-blog::messages.adjacent.next_link_text', array('title' => $newer->title)) }} 7 | 8 |

9 | @endif 10 | 11 | @if ($older) 12 |

13 | 14 | {{ trans('laravel-blog::messages.adjacent.prev_link_text', array('title' => $older->title)) }} 15 | 16 |

17 | @endif 18 | 19 |
-------------------------------------------------------------------------------- /src/views/partials/archives.blade.php: -------------------------------------------------------------------------------- 1 | @if (isset($archives) && !empty($archives)) 2 | 17 | @endif 18 | -------------------------------------------------------------------------------- /src/views/partials/details.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 8 | 9 |

10 | {{ $post->title }} 11 |

12 | 13 |

14 | {{ $post->getDate() }} 15 |

16 | 17 |
18 | {{ $post->summary }} 19 |
20 | 21 | @if (Config::get('laravel-blog::views.view_page.show_share_partial')) 22 | @include('laravel-blog::partials.share') 23 | @endif 24 | 25 | @if (!empty($post->you_tube_video_id)) 26 |
27 | {{ $post->getYouTubeEmbedCode() }} 28 |
29 | @elseif (!empty($post->main_image)) 30 |
31 | {{ $post->getImage('main_image', 'resized') }} 32 |
33 | @endif 34 | 35 | {{ $post->content }} 36 | 37 | @if (Config::get('laravel-blog::link.show') && !empty($post->link_url) && !empty($post->link_text)) 38 | 43 | @endif 44 | 45 |
46 | 47 | @if (Config::get('laravel-blog::views.view_page.show_adjacent_items') && ($newer || $older)) 48 | @include('laravel-blog::partials.adjacent') 49 | @endif -------------------------------------------------------------------------------- /src/views/partials/list.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | @if (!$posts->isEmpty()) 4 | 5 | @foreach ($posts as $post) 6 | 7 |
8 | 9 |

10 | 11 | {{ $post->title }} 12 | 13 |

14 | 15 |

16 | {{ $post->getDate() }} 17 |

18 | 19 | @if (!empty($post->you_tube_video_id)) 20 | 25 | @elseif (!empty($post->main_image)) 26 | 31 | @endif 32 | 33 |
34 | {{ $post->summary }} 35 |
36 | 37 | 42 | 43 |
44 | 45 | @endforeach 46 | 47 | {{ $posts->links() }} 48 | 49 | @else 50 | 51 |

52 | {{ trans('laravel-blog::messages.list.no_items') }} 53 |

54 | 55 | @endif 56 | 57 |
-------------------------------------------------------------------------------- /src/views/partials/share.blade.php: -------------------------------------------------------------------------------- 1 |
2 |

{{ trans('laravel-blog::messages.share.label') }}

3 |

4 | 5 | {{ trans('laravel-blog::messages.share.twitter') }} 6 | 7 |

8 |

9 | 10 | {{ trans('laravel-blog::messages.share.facebook') }} 11 | 12 |

13 |
-------------------------------------------------------------------------------- /src/views/posts/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.master') 2 | 3 | @section('title') 4 | {{ Config::get('laravel-blog::meta.index_page.page_title') }} 5 | @endsection 6 | 7 | @section('meta_description') 8 | {{ Config::get('laravel-blog::meta.index_page.meta_description') }} 9 | @endsection 10 | 11 | @section('meta_keywords') 12 | {{ Config::get('laravel-blog::meta.index_page.meta_keywords') }} 13 | @endsection 14 | 15 | @section('content') 16 | @include('laravel-blog::partials.list') 17 | @include('laravel-blog::partials.archives') 18 | @stop -------------------------------------------------------------------------------- /src/views/posts/view.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.master') 2 | 3 | @section('title') 4 | {{ !empty($post->page_title) ? $post->page_title : 'Blog · '.$post->title }} 5 | @endsection 6 | 7 | @section('meta_description') 8 | {{ $post->meta_description }} 9 | @endsection 10 | 11 | @section('meta_keywords') 12 | {{ $post->meta_keywords }} 13 | @endsection 14 | 15 | @section('content') 16 | @include('laravel-blog::partials.details') 17 | @include('laravel-blog::partials.archives') 18 | @stop 19 | --------------------------------------------------------------------------------