├── .coveralls.yml
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ └── php.yml
├── .gitignore
├── .php_cs
├── .styleci.yml
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.txt
├── README.md
├── SECURITY.md
├── composer.json
├── migrations
├── 2018_05_28_224023_create_blog_etc_posts_table.php
├── 2018_09_16_224023_add_author_and_url_blog_etc_posts_table.php
├── 2018_09_26_085711_add_short_desc_textrea_to_blog_etc.php
└── 2018_09_27_122627_create_blog_etc_uploaded_photos_table.php
├── phpunit.xml
├── src
├── BlogEtcServiceProvider.php
├── Captcha
│ ├── Basic.php
│ └── CaptchaAbstract.php
├── Config
│ └── blogetc.php
├── Controllers
│ ├── Admin
│ │ ├── ManageCategoriesController.php
│ │ ├── ManageCommentsController.php
│ │ ├── ManagePostsController.php
│ │ └── ManageUploadsController.php
│ ├── BlogEtcAdminController.php
│ ├── BlogEtcCategoryAdminController.php
│ ├── BlogEtcCommentWriterController.php
│ ├── BlogEtcCommentsAdminController.php
│ ├── BlogEtcImageUploadController.php
│ ├── BlogEtcReaderController.php
│ ├── BlogEtcRssFeedController.php
│ ├── CommentsController.php
│ ├── FeedController.php
│ └── PostsController.php
├── Events
│ ├── BlogPostAdded.php
│ ├── BlogPostEdited.php
│ ├── BlogPostWillBeDeleted.php
│ ├── CategoryAdded.php
│ ├── CategoryEdited.php
│ ├── CategoryWillBeDeleted.php
│ ├── CommentAdded.php
│ ├── CommentApproved.php
│ ├── CommentWillBeDeleted.php
│ └── UploadedImage.php
├── Exceptions
│ ├── BlogEtcAuthGateNotImplementedException.php
│ ├── CategoryNotFoundException.php
│ ├── CommentNotFoundException.php
│ ├── PostNotFoundException.php
│ └── UploadedPhotoNotFoundException.php
├── Factories
│ ├── CategoryFactory.php
│ ├── CommentFactory.php
│ └── PostFactory.php
├── Gates
│ ├── DefaultAddCommentsGate.php
│ ├── DefaultAdminGate.php
│ └── GateTypes.php
├── Helpers.php
├── Interfaces
│ ├── BaseRequestInterface.php
│ ├── CaptchaInterface.php
│ ├── LegacyGetImageFileInterface.php
│ └── SearchResultInterface.php
├── Middleware
│ └── UserCanManageBlogPosts.php
├── Models
│ ├── BlogEtcCategory.php
│ ├── BlogEtcComment.php
│ ├── BlogEtcPost.php
│ ├── BlogEtcUploadedPhoto.php
│ ├── Category.php
│ ├── Comment.php
│ ├── Post.php
│ └── UploadedPhoto.php
├── Repositories
│ ├── CategoriesRepository.php
│ ├── CommentsRepository.php
│ ├── PostsRepository.php
│ └── UploadedPhotosRepository.php
├── Requests
│ ├── AddNewCommentRequest.php
│ ├── BaseAdminRequest.php
│ ├── BaseBlogEtcPostRequest.php
│ ├── BaseRequest.php
│ ├── CategoryRequest.php
│ ├── CreateBlogEtcPostRequest.php
│ ├── DeleteBlogEtcPostRequest.php
│ ├── FeedRequest.php
│ ├── SearchRequest.php
│ ├── Traits
│ │ ├── HasCategoriesTrait.php
│ │ └── HasImageUploadTrait.php
│ ├── UpdateBlogEtcPostRequest.php
│ └── UploadImageRequest.php
├── Scopes
│ ├── BlogCommentApprovedAndDefaultOrderScope.php
│ └── BlogEtcPublishedScope.php
├── Services
│ ├── CaptchaService.php
│ ├── CategoriesService.php
│ ├── CommentsService.php
│ ├── FeedService.php
│ ├── PostsService.php
│ └── UploadsService.php
├── Views
│ ├── blogetc
│ │ ├── captcha
│ │ │ └── basic.blade.php
│ │ ├── index.blade.php
│ │ ├── partials
│ │ │ ├── add_comment_form.blade.php
│ │ │ ├── author.blade.php
│ │ │ ├── built_in_comments.blade.php
│ │ │ ├── categories.blade.php
│ │ │ ├── custom_comments.blade.php
│ │ │ ├── disqus_comments.blade.php
│ │ │ ├── full_post_details.blade.php
│ │ │ ├── index_loop.blade.php
│ │ │ ├── search_form.blade.php
│ │ │ ├── show_comments.blade.php
│ │ │ ├── show_errors.blade.php
│ │ │ └── use_view_file.blade.php
│ │ ├── saved_comment.blade.php
│ │ ├── search.blade.php
│ │ ├── single_post.blade.php
│ │ └── sitewide
│ │ │ ├── random_posts.blade.php
│ │ │ ├── recent_posts.blade.php
│ │ │ ├── search_form.blade.php
│ │ │ └── show_all_categories.blade.php
│ └── blogetc_admin
│ │ ├── categories
│ │ ├── add_category.blade.php
│ │ ├── deleted_category.blade.php
│ │ ├── edit_category.blade.php
│ │ ├── form.blade.php
│ │ └── index.blade.php
│ │ ├── comments
│ │ └── index.blade.php
│ │ ├── imageupload
│ │ ├── create.blade.php
│ │ ├── delete-post-image.blade.php
│ │ ├── deleted-post-image.blade.php
│ │ ├── index.blade.php
│ │ └── uploaded.blade.php
│ │ ├── index.blade.php
│ │ ├── layouts
│ │ ├── admin_layout.blade.php
│ │ └── sidebar.blade.php
│ │ └── posts
│ │ ├── add_post.blade.php
│ │ ├── deleted_post.blade.php
│ │ ├── edit_post.blade.php
│ │ └── form.blade.php
├── css
│ └── blogetc_admin_css.css
└── routes.php
└── tests
├── Feature
├── Controllers
│ ├── Admin
│ │ ├── ManageCategoriesControllerTest.php
│ │ ├── ManageCommentsControllerTest.php
│ │ ├── ManagePostsControllerTest.php
│ │ └── ManageUploadsControllerTest.php
│ ├── CommentsControllerTest.php
│ ├── FeedControllerTest.php
│ └── PostsControllerTest.php
├── Repositories
│ ├── CategoriesRepositoryTest.php
│ ├── CommentsRepositoryTest.php
│ └── PostsRepositoryTest.php
└── Services
│ ├── CaptchaServiceTest.php
│ ├── CategoriesServiceTest.php
│ ├── CommentsServiceTest.php
│ ├── FeedServiceTest.php
│ └── PostsServiceTest.php
├── TestCase.php
├── Unit
└── Services
│ └── PostsServiceTest.php
└── views
└── layouts
└── app.blade.php
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | src_dir: src
2 | coverage_clover: build/logs/clover.xml
3 | json_path: build/logs/coveralls-upload.json
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
12 | **Describe the bug**
13 | A clear and concise description of what the bug is.
14 |
15 | **To Reproduce**
16 | Steps to reproduce the behavior:
17 | 1. Go to '...'
18 | 2. Click on '....'
19 | 3. Scroll down to '....'
20 | 4. See error
21 |
22 | **Expected behavior**
23 | A clear and concise description of what you expected to happen.
24 |
25 | **Screenshots**
26 | If applicable, add screenshots to help explain your problem.
27 |
28 | **Additional context**
29 | Add any other context about the problem here.
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/php.yml:
--------------------------------------------------------------------------------
1 | name: PHP Composer and tests
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | strategy:
8 | matrix:
9 | operating-system: [ubuntu-latest, windows-latest, macos-latest]
10 | php-versions: ['7.3', '7.4']
11 | dependency-version: [prefer-lowest, prefer-stable]
12 | laravel: ['7.6.0', '7.28.0', '8.0.1', '8.2.0', '8.1.0']
13 | runs-on: ${{ matrix.operating-system }}
14 | name: PHP ${{ matrix.php-versions }} Laravel L${{ matrix.laravel }} Test on ${{ matrix.operating-system }}
15 |
16 | steps:
17 | - uses: actions/checkout@v2
18 |
19 | - name: Setup PHP
20 | uses: shivammathur/setup-php@v2
21 | with:
22 | php-version: ${{ matrix.php-versions }}
23 | extensions: mbstring, intl, fileinfo, pdo_sqlite, mysql
24 | ini-values: post_max_size=256M, short_open_tag=On
25 | coverage: xdebug
26 |
27 | - name: Validate composer.json and composer.lock
28 | run: composer validate
29 |
30 | - name: Install dependencies
31 | run: |
32 | composer self-update
33 | composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update
34 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest
35 | # composer update --prefer-source --no-interaction --no-suggest
36 |
37 | - name: Run phpunit tests
38 | run: ./vendor/bin/phpunit
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | build/
3 | vendor/
4 | node_modules/
5 | npm-debug.log
6 | composer.lock
7 | .php_cs.cache
8 | bootstrap/compiled.php
9 | app/storage/
10 | public/storage
11 | public/hot
12 | storage/*.key
13 | .env.*.php
14 | .env.php
15 | .env
16 | Homestead.yaml
17 | Homestead.json
18 |
--------------------------------------------------------------------------------
/.php_cs:
--------------------------------------------------------------------------------
1 | exclude($excluded_folders)
12 | ->in(__DIR__);
13 | ;
14 |
15 | return PhpCsFixer\Config::create()
16 | ->setRules(array(
17 | '@Symfony' => true,
18 | 'binary_operator_spaces' => ['align_double_arrow' => true],
19 | 'array_syntax' => ['syntax' => 'short'],
20 | 'linebreak_after_opening_tag' => true,
21 | 'not_operator_with_successor_space' => true,
22 | 'ordered_imports' => true,
23 | 'phpdoc_order' => true,
24 | ))
25 | ->setFinder($finder)
26 | ;
27 |
--------------------------------------------------------------------------------
/.styleci.yml:
--------------------------------------------------------------------------------
1 | # Styleci for WebDevEtc.
2 |
3 | preset: laravel
4 |
5 | disabled:
6 | - not_operator_with_successor_space
7 |
8 | #finder:
9 | # exclude:
10 | # - "tests"
11 | # - "node_modules"
12 | # - "storage"
13 | # - "vendor"
14 | # not-name:
15 | # - "AcceptanceTester.php"
16 | # - "FunctionalTester.php"
17 | # - "UnitTester.php"
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 7.3
5 | - 7.4
6 |
7 | env:
8 | - LARAVEL_VERSION=8.2.0
9 | - LARAVEL_VERSION=8.1.0
10 | - LARAVEL_VERSION=8.0.1
11 | - LARAVEL_VERSION=7.28.0
12 | - LARAVEL_VERSION=7.6.0
13 | - LARAVEL_VERSION=7.0.0
14 | - LARAVEL_VERSION=6.18.8
15 | - LARAVEL_VERSION=6.8.0
16 | - LARAVEL_VERSION=6.5.2
17 | - LARAVEL_VERSION=5.8.35
18 | - LARAVEL_VERSION=dev-master
19 | - LARAVEL_VERSION=
20 | matrix:
21 | fast_finish: true
22 |
23 | before_script:
24 | - travis_retry composer self-update
25 | - travis_retry composer install --prefer-source --no-interaction
26 | - if [ "$LARAVEL_VERSION" != "" ]; then composer require --dev "laravel/laravel:${LARAVEL_VERSION}" --no-update; fi;
27 | - composer update
28 |
29 | script:
30 | - vendor/bin/phpunit
31 |
32 | after_script:
33 | # - php vendor/bin/php-coveralls -v
34 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at ghcodeofconduct@webdevetc.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Feel free to suggest PRs.
2 |
3 | Email me (github@webdevet.com) or message me on https://twitter.com/web_dev_etc with any questions.
4 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018-2020 WebDevEtc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, 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,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Reporting a Vulnerability
4 |
5 | Contact me at github@webdevetc.com or on twitter https://twitter.com/web_dev_etc
6 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webdevetc/blogetc",
3 | "keywords": [
4 | "laravel",
5 | "blog",
6 | "package",
7 | "admin",
8 | "panel",
9 | "rss",
10 | "feed",
11 | "write",
12 | "posts",
13 | "news",
14 | "update",
15 | "webdevetc",
16 | "webdev"
17 | ],
18 | "description": "Simple blog (with admin panel) for Laravel from https://webdevetc.com/",
19 | "license": "MIT",
20 | "support": {
21 | "docs": "https://webdevetc.com/laravel/packages/blogetc-blog-system-for-your-laravel-app/help-documentation/laravel-blog-package-blogetc"
22 | },
23 | "authors": [
24 | {
25 | "name": "WebDevEtc",
26 | "homepage": "https://webdevetc.com/",
27 | "role": "developer",
28 | "email": "packages@webdevetc.com"
29 | }
30 | ],
31 | "require": {
32 | "intervention/image": "2.*",
33 | "cviebrock/eloquent-sluggable": "~8.0|~7.0|~6.0|~4.8|~4.7|~4.6|~4.5",
34 | "laravel/framework": "~5.8|~6.0|~7.0|~8.0",
35 | "laravel/helpers": "^1.2",
36 | "laravelium/feed": "~8.0|~7.0|~6.0|~3.1"
37 | },
38 | "require-dev": {
39 | "phpunit/phpunit": "~9.1|~8.4|~8.1",
40 | "orchestra/testbench": "~5.2|~4.8|~4.0|~3.8|~3.7",
41 | "friendsofphp/php-cs-fixer": "~2.16"
42 | },
43 | "autoload": {
44 | "psr-4": {
45 | "WebDevEtc\\BlogEtc\\": "src"
46 | }
47 | },
48 | "autoload-dev": {
49 | "psr-4": {
50 | "WebDevEtc\\BlogEtc\\Tests\\": "tests"
51 | }
52 | },
53 | "extra": {
54 | "laravel": {
55 | "providers": [
56 | "WebDevEtc\\BlogEtc\\BlogEtcServiceProvider"
57 | ],
58 | "aliases": {
59 | }
60 | }
61 | },
62 | "abandoned": true
63 | }
64 |
--------------------------------------------------------------------------------
/migrations/2018_05_28_224023_create_blog_etc_posts_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
19 |
20 | $table->string('slug')->unique();
21 | $table->unsignedInteger('user_id')->index()->nullable();
22 | $table->string('title')->nullable()->default('New blog post');
23 | $table->string('subtitle')->nullable()->default('');
24 | $table->text('meta_desc')->nullable();
25 | $table->mediumText('post_body')->nullable();
26 | $table->string('use_view_file')
27 | ->nullable()
28 | ->comment('If not null, this should refer to a blade file in /views/');
29 | $table->dateTime('posted_at')->index()->nullable()
30 | ->comment('Public posted at time, if this is in future then it wont appear yet');
31 | $table->boolean('is_published')->default(true);
32 | $table->string('image_large')->nullable();
33 | $table->string('image_medium')->nullable();
34 | $table->string('image_thumbnail')->nullable();
35 |
36 | $table->timestamps();
37 | });
38 |
39 | Schema::create('blog_etc_categories', static function (Blueprint $table) {
40 | $table->increments('id');
41 |
42 | $table->string('category_name')->nullable();
43 | $table->string('slug')->unique();
44 | $table->mediumText('category_description')->nullable();
45 | $table->unsignedInteger('created_by')->nullable()->index()->comment('user id');
46 |
47 | $table->timestamps();
48 | });
49 |
50 | // linking table:
51 | Schema::create('blog_etc_post_categories', static function (Blueprint $table) {
52 | $table->increments('id');
53 |
54 | $table->unsignedInteger('blog_etc_post_id')->index();
55 | $table->foreign('blog_etc_post_id')->references('id')->on('blog_etc_posts')->onDelete('cascade');
56 | $table->unsignedInteger('blog_etc_category_id')->index();
57 |
58 | $table->foreign('blog_etc_category_id')->references('id')->on('blog_etc_categories')->onDelete('cascade');
59 | });
60 |
61 | Schema::create('blog_etc_comments', static function (Blueprint $table) {
62 | $table->increments('id');
63 |
64 | $table->unsignedInteger('blog_etc_post_id')->index();
65 | $table->foreign('blog_etc_post_id')->references('id')->on('blog_etc_posts')->onDelete('cascade');
66 | $table->unsignedInteger('user_id')->nullable()->index()->comment('if user was logged in');
67 | $table->string('ip')->nullable()->comment('if enabled in the config file');
68 | $table->string('author_name')->nullable()->comment('if not logged in');
69 | $table->text('comment')->comment('the comment body');
70 | $table->boolean('approved')->default(true);
71 |
72 | $table->timestamps();
73 | });
74 | }
75 |
76 | /**
77 | * Reverse the migrations.
78 | */
79 | public function down(): void
80 | {
81 | Schema::dropIfExists('blog_etc_comments');
82 | Schema::dropIfExists('blog_etc_post_categories');
83 | Schema::dropIfExists('blog_etc_categories');
84 | Schema::dropIfExists('blog_etc_posts');
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/migrations/2018_09_16_224023_add_author_and_url_blog_etc_posts_table.php:
--------------------------------------------------------------------------------
1 | string('author_email')->nullable();
19 | $table->string('author_website')->nullable();
20 | });
21 | }
22 |
23 | /**
24 | * Reverse the migrations.
25 | */
26 | public function down(): void
27 | {
28 | Schema::table('blog_etc_comments', static function (Blueprint $table) {
29 | $table->dropColumn('author_email');
30 | $table->dropColumn('author_website');
31 | });
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/migrations/2018_09_26_085711_add_short_desc_textrea_to_blog_etc.php:
--------------------------------------------------------------------------------
1 | text('short_description')->nullable();
21 | });
22 | }
23 |
24 | /**
25 | * Reverse the migrations.
26 | */
27 | public function down(): void
28 | {
29 | Schema::table('blog_etc_posts', static function (Blueprint $table) {
30 | $table->dropColumn('short_description');
31 | });
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/migrations/2018_09_27_122627_create_blog_etc_uploaded_photos_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
19 | $table->text('uploaded_images')->nullable();
20 | $table->string('image_title')->nullable();
21 | $table->string('source')->default('unknown');
22 | $table->unsignedInteger('uploader_id')->nullable()->index();
23 |
24 | $table->timestamps();
25 | });
26 |
27 | Schema::table('blog_etc_posts', static function (Blueprint $table) {
28 | $table->string('seo_title')->nullable();
29 | });
30 | }
31 |
32 | /**
33 | * Reverse the migrations.
34 | */
35 | public function down(): void
36 | {
37 | Schema::dropIfExists('blog_etc_uploaded_photos');
38 |
39 | Schema::table('blog_etc_posts', static function (Blueprint $table) {
40 | $table->dropColumn('seo_title');
41 | });
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 | ./tests/
17 | ./tests/views/layouts/app.blade.php
18 |
19 |
20 |
21 |
22 | src/
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/BlogEtcServiceProvider.php:
--------------------------------------------------------------------------------
1 | publishes([
29 | __DIR__.'/../migrations/'.$file => database_path('migrations/'.$file),
30 | ]);
31 | }
32 |
33 | // Set up default gates to allow/disallow access to features.
34 | $this->setupDefaultGates();
35 |
36 | $this->publishes([
37 | __DIR__.'/Views/blogetc' => base_path('resources/views/vendor/blogetc'),
38 | __DIR__.'/Config/blogetc.php' => config_path('blogetc.php'),
39 | __DIR__.'/css/blogetc_admin_css.css' => public_path('blogetc_admin_css.css'),
40 | ]);
41 | }
42 |
43 | /**
44 | * Set up default gates.
45 | */
46 | protected function setupDefaultGates(): void
47 | {
48 | if (!Gate::has(GateTypes::MANAGE_BLOG_ADMIN)) {
49 | Gate::define(GateTypes::MANAGE_BLOG_ADMIN, include(__DIR__.'/Gates/DefaultAdminGate.php'));
50 | }
51 |
52 | /*
53 | * For people to add comments to your blog posts. By default it will allow anyone - you can add your
54 | * own logic here if needed.
55 | */
56 | if (!Gate::has(GateTypes::ADD_COMMENT)) {
57 | Gate::define(GateTypes::ADD_COMMENT, include(__DIR__.'/Gates/DefaultAddCommentsGate.php'));
58 | }
59 | }
60 |
61 | /**
62 | * Register services.
63 | *
64 | * @return void
65 | */
66 | public function register()
67 | {
68 | $this->loadViewsFrom(__DIR__.'/Views/blogetc_admin', 'blogetc_admin');
69 |
70 | // if you do the vendor:publish, these will be copied to /resources/views/vendor/blogetc anyway
71 | $this->loadViewsFrom(__DIR__.'/Views/blogetc', 'blogetc');
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Captcha/Basic.php:
--------------------------------------------------------------------------------
1 | captchaFieldName();
64 | }
65 |
66 | /**
67 | * What should the field name be (in the ).
68 | */
69 | public function captchaFieldName(): string
70 | {
71 | return 'captcha';
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Captcha/CaptchaAbstract.php:
--------------------------------------------------------------------------------
1 | middleware(UserCanManageBlogPosts::class);
28 | $this->service = $service;
29 | }
30 |
31 | /**
32 | * Show list of categories.
33 | *
34 | * @return mixed
35 | */
36 | public function index(): View
37 | {
38 | $categories = $this->service->indexPaginated();
39 |
40 | return view(
41 | 'blogetc_admin::categories.index',
42 | [
43 | 'categories' => $categories,
44 | ]
45 | );
46 | }
47 |
48 | /**
49 | * @deprecated - use store()
50 | */
51 | public function store_category(CategoryRequest $request)
52 | {
53 | return $this->store($request);
54 | }
55 |
56 | /**
57 | * Store a new category.
58 | */
59 | public function store(CategoryRequest $request)
60 | {
61 | $this->service->create($request->validated());
62 |
63 | Helpers::flashMessage('Saved new category');
64 |
65 | return redirect(route('blogetc.admin.categories.index'));
66 | }
67 |
68 | /**
69 | * @deprecated - use edit()
70 | */
71 | public function edit_category($categoryId)
72 | {
73 | return $this->edit($categoryId);
74 | }
75 |
76 | /**
77 | * Show the edit form for category.
78 | */
79 | public function edit(int $categoryID): View
80 | {
81 | $category = $this->service->find($categoryID);
82 |
83 | return view(
84 | 'blogetc_admin::categories.edit_category',
85 | [
86 | 'category' => $category,
87 | ]
88 | );
89 | }
90 |
91 | /**
92 | * @deprecated - use create()
93 | */
94 | public function create_category()
95 | {
96 | return $this->create();
97 | }
98 |
99 | /**
100 | * Show the form for creating new category.
101 | */
102 | public function create(): View
103 | {
104 | return view('blogetc_admin::categories.add_category');
105 | }
106 |
107 | /**
108 | * @deprecated - use update()
109 | */
110 | public function update_category(CategoryRequest $request, $categoryId)
111 | {
112 | return $this->update($request, $categoryId);
113 | }
114 |
115 | /**
116 | * Save submitted changes.
117 | *
118 | * @return RedirectResponse|Redirector
119 | */
120 | public function update(CategoryRequest $request, $categoryID)
121 | {
122 | $category = $this->service->update($categoryID, $request->validated());
123 |
124 | Helpers::flashMessage('Updated category');
125 |
126 | return redirect($category->editUrl());
127 | }
128 |
129 | /**
130 | * @deprecated - use destroy()
131 | */
132 | public function destroy_category(CategoryRequest $request, $categoryId)
133 | {
134 | return $this->destroy($request, $categoryId);
135 | }
136 |
137 | /**
138 | * Delete the category.
139 | */
140 | public function destroy(/** @scrutinizer ignore-unused */ CategoryRequest $request, $categoryID)
141 | {
142 | $this->service->delete($categoryID);
143 |
144 | return view('blogetc_admin::categories.deleted_category');
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/Controllers/Admin/ManageCommentsController.php:
--------------------------------------------------------------------------------
1 | middleware(UserCanManageBlogPosts::class);
28 | $this->service = $service;
29 | }
30 |
31 | /**
32 | * Show all comments (and show buttons with approve/delete).
33 | *
34 | * @return mixed
35 | */
36 | public function index(Request $request)
37 | {
38 | //TODO - use service
39 | $comments = Comment::withoutGlobalScopes()
40 | ->orderBy('created_at', 'desc')
41 | ->with('post');
42 |
43 | if ($request->get('waiting_for_approval')) {
44 | $comments->where('approved', false);
45 | }
46 |
47 | $comments = $comments->paginate(100);
48 |
49 | return view('blogetc_admin::comments.index', ['comments' => $comments]);
50 | }
51 |
52 | /**
53 | * Approve a comment.
54 | *
55 | * @param $blogCommentID
56 | */
57 | public function approve(int $blogCommentID): RedirectResponse
58 | {
59 | $this->service->approve($blogCommentID);
60 |
61 | Helpers::flashMessage('Approved comment!');
62 |
63 | return back();
64 | }
65 |
66 | /**
67 | * Delete a submitted comment.
68 | *
69 | * @param $blogCommentID
70 | *
71 | * @throws Exception
72 | */
73 | public function destroy(int $blogCommentID): RedirectResponse
74 | {
75 | $this->service->delete($blogCommentID);
76 |
77 | Helpers::flashMessage('Deleted comment!');
78 |
79 | return back();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Controllers/Admin/ManagePostsController.php:
--------------------------------------------------------------------------------
1 | middleware(UserCanManageBlogPosts::class);
35 |
36 | $this->uploadsService = $uploadsService;
37 |
38 | if (!is_array(config('blogetc'))) {
39 | throw new RuntimeException('The config/blogetc.php does not exist. Publish the vendor files for the BlogEtc package by running the php artisan publish:vendor command');
40 | }
41 | }
42 |
43 | /**
44 | * View all posts.
45 | *
46 | * @return mixed
47 | */
48 | public function index()
49 | {
50 | $posts = Post::orderBy('posted_at', 'desc')->paginate(10);
51 |
52 | return view('blogetc_admin::index', ['posts' => $posts]);
53 | }
54 |
55 | /**
56 | * Show form for creating new post.
57 | *
58 | * @return Factory|View
59 | */
60 | public function create()
61 | {
62 | return view('blogetc_admin::posts.add_post');
63 | }
64 |
65 | /**
66 | * @deprecated - use create() instead
67 | */
68 | public function create_post()
69 | {
70 | return $this->create();
71 | }
72 |
73 | /**
74 | * Save a new post.
75 | *
76 | * @throws Exception
77 | *
78 | * @return RedirectResponse|Redirector
79 | */
80 | public function store(CreateBlogEtcPostRequest $request)
81 | {
82 | $editUrl = $this->uploadsService->legacyStorePost($request);
83 |
84 | return redirect($editUrl);
85 | }
86 |
87 | /**
88 | * @deprecated use store() instead
89 | */
90 | public function store_post(CreateBlogEtcPostRequest $request)
91 | {
92 | return $this->store($request);
93 | }
94 |
95 | /**
96 | * Show form to edit post.
97 | *
98 | * @param $blogPostId
99 | *
100 | * @return mixed
101 | */
102 | public function edit($blogPostId)
103 | {
104 | $post = Post::findOrFail($blogPostId);
105 |
106 | return view('blogetc_admin::posts.edit_post', ['post' => $post]);
107 | }
108 |
109 | /**
110 | * @deprecated - use edit() instead
111 | */
112 | public function edit_post($blogPostId)
113 | {
114 | return $this->edit($blogPostId);
115 | }
116 |
117 | /**
118 | * Save changes to a post.
119 | *
120 | * This uses some legacy code. This will get refactored soon into something nicer.
121 | *
122 | * @param $blogPostId
123 | *
124 | * @throws Exception
125 | *
126 | * @return RedirectResponse|Redirector
127 | */
128 | public function update(UpdateBlogEtcPostRequest $request, $blogPostId)
129 | {
130 | $editUrl = $this->uploadsService->legacyUpdatePost($request, $blogPostId);
131 |
132 | return redirect($editUrl);
133 | }
134 |
135 | /**
136 | * @deprecated use update() instead
137 | */
138 | public function update_post(UpdateBlogEtcPostRequest $request, $blogPostId)
139 | {
140 | return $this->update($request, $blogPostId);
141 | }
142 |
143 | /**
144 | * Delete a post.
145 | *
146 | * @param $blogPostId
147 | *
148 | * @return mixed
149 | */
150 | public function destroy(DeleteBlogEtcPostRequest $request, $blogPostId)
151 | {
152 | $deletedPost = $this->uploadsService->legacyDestroyPost($request, $blogPostId);
153 |
154 | return view('blogetc_admin::posts.deleted_post')
155 | ->withDeletedPost($deletedPost);
156 | }
157 |
158 | /**
159 | * @deprecated - use destroy() instead
160 | */
161 | public function destroy_post(DeleteBlogEtcPostRequest $request, $blogPostId)
162 | {
163 | return $this->destroy($request, $blogPostId);
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/Controllers/Admin/ManageUploadsController.php:
--------------------------------------------------------------------------------
1 | middleware(UserCanManageBlogPosts::class);
36 | $this->uploadsService = $uploadsService;
37 | $this->postsService = $postsService;
38 |
39 | if (!config('blogetc.image_upload_enabled')) {
40 | throw new RuntimeException('The blogetc.php config option is missing or has not enabled image uploading');
41 | }
42 | }
43 |
44 | /**
45 | * Show the main listing of uploaded images.
46 | *
47 | * @return mixed
48 | */
49 | public function index()
50 | {
51 | return view('blogetc_admin::imageupload.index', [
52 | 'uploaded_photos' => UploadedPhoto::orderBy('id', 'desc')
53 | ->paginate(10),
54 | ]);
55 | }
56 |
57 | /**
58 | * show the form for uploading a new image.
59 | *
60 | * @return Factory|View
61 | */
62 | public function create()
63 | {
64 | return view('blogetc_admin::imageupload.create', []);
65 | }
66 |
67 | /**
68 | * Save a new uploaded image.
69 | *
70 | * @throws Exception
71 | */
72 | public function store(UploadImageRequest $request)
73 | {
74 | // Uses some legacy code - this will be refactored and fixed soon!
75 | $processed_images = $this->uploadsService->legacyProcessUploadedImagesSingle($request);
76 |
77 | return view('blogetc_admin::imageupload.uploaded', ['images' => $processed_images]);
78 | }
79 |
80 | public function deletePostImage(int $postId)
81 | {
82 | return view('blogetc_admin::imageupload.delete-post-image', ['postId' => $postId]);
83 | }
84 |
85 | public function deletePostImageConfirmed(int $postId)
86 | {
87 | $post = $this->postsService->findById($postId);
88 |
89 | $deletedSizes = $this->uploadsService->deletePostImage($post);
90 | $this->postsService->clearImageSizes($post, $deletedSizes);
91 |
92 | return view('blogetc_admin::imageupload.deleted-post-image');
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Controllers/BlogEtcAdminController.php:
--------------------------------------------------------------------------------
1 | postsService = $postsService;
42 | $this->commentsService = $commentsService;
43 | $this->captchaService = $captchaService;
44 | }
45 |
46 | /**
47 | * @deprecated - use store instead
48 | */
49 | public function addNewComment(AddNewCommentRequest $request, $slug)
50 | {
51 | return $this->store($request, $slug);
52 | }
53 |
54 | /**
55 | * Let a guest (or logged in user) submit a new comment for a blog post.
56 | */
57 | public function store(AddNewCommentRequest $request, $slug)
58 | {
59 | if (CommentsService::COMMENT_TYPE_BUILT_IN !== config('blogetc.comments.type_of_comments_to_show')) {
60 | throw new RuntimeException('Built in comments are disabled');
61 | }
62 |
63 | if (Gate::denies(GateTypes::ADD_COMMENT)) {
64 | abort(Response::HTTP_FORBIDDEN, 'Unable to add comments');
65 | }
66 |
67 | $blogPost = $this->postsService->repository()->findBySlug($slug);
68 |
69 | $captcha = $this->captchaService->getCaptchaObject();
70 |
71 | if ($captcha && method_exists($captcha, 'runCaptchaBeforeAddingComment')) {
72 | $captcha->runCaptchaBeforeAddingComment($request, $blogPost);
73 | }
74 |
75 | $comment = $this->commentsService->create(
76 | $blogPost,
77 | $request->validated(),
78 | $request->ip(),
79 | (int) Auth::id()
80 | );
81 |
82 | return response()->view('blogetc::saved_comment', [
83 | 'captcha' => $captcha,
84 | 'blog_post' => $blogPost,
85 | 'new_comment' => $comment,
86 | ])->setStatusCode(Response::HTTP_CREATED);
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/Controllers/FeedController.php:
--------------------------------------------------------------------------------
1 | id : 'guest';
29 |
30 | $feed->setCache(
31 | config('blogetc.rssfeed.cache_in_minutes', 60),
32 | 'blogetc-'.$request->getFeedType().$user_or_guest
33 | );
34 |
35 | if (!$feed->isCached()) {
36 | $this->makeFreshFeed($feed);
37 | }
38 |
39 | return $feed->render($request->getFeedType());
40 | }
41 |
42 | /**
43 | * @param $feed
44 | */
45 | protected function makeFreshFeed(Feed $feed)
46 | {
47 | $posts = Post::orderBy('posted_at', 'desc')
48 | ->limit(config('blogetc.rssfeed.posts_to_show_in_rss_feed', 10))
49 | ->with('author')
50 | ->get();
51 |
52 | $this->setupFeed($feed, $posts);
53 |
54 | /** @var Post $post */
55 | foreach ($posts as $post) {
56 | $feed->add($post->title,
57 | $post->authorString(),
58 | $post->url(),
59 | $post->posted_at,
60 | $post->short_description,
61 | $post->generateIntroduction()
62 | );
63 | }
64 | }
65 |
66 | /**
67 | * @param $posts
68 | *
69 | * @return mixed
70 | */
71 | protected function setupFeed(Feed $feed, $posts)
72 | {
73 | $feed->title = config('app.name').' Blog';
74 | $feed->description = config('blogetc.rssfeed.description', 'Our blog RSS feed');
75 | $feed->link = route('blogetc.index');
76 | $feed->setDateFormat('carbon');
77 | $feed->pubdate = isset($posts[0]) ? $posts[0]->posted_at : Carbon::now()->subYear();
78 | $feed->lang = config('blogetc.rssfeed.language', 'en');
79 | $feed->setShortening(config('blogetc.rssfeed.should_shorten_text', true));
80 | $feed->setTextLimit(config('blogetc.rssfeed.text_limit', 100));
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Controllers/PostsController.php:
--------------------------------------------------------------------------------
1 | postsService = $postsService;
38 | $this->categoriesService = $categoriesService;
39 | $this->captchaService = $captchaService;
40 | }
41 |
42 | /**
43 | * Show the search results.
44 | */
45 | public function search(SearchRequest $request): \Illuminate\Contracts\View\View
46 | {
47 | // Laravel full text search (swisnl/laravel-fulltext) disabled due to poor Laravel 8 support.
48 | // If you wish to add it, copy the code in this method that was in commit 9aff6c37d130.
49 |
50 | // The LIKE query is not efficient. Search can be disabled in config.
51 | $searchResults = Post::where('title', 'LIKE', '%'.$request->get('s').'%')->limit(100)->get();
52 |
53 | // Map it so the post is actually accessible with ->indexable, for backwards compatibility in old view files.
54 | $searchResultsMappedWithIndexable = $searchResults->map(function (Post $post) {
55 | return new class($post) {
56 | public $indexable;
57 |
58 | public function __construct(Post $post)
59 | {
60 | $this->indexable = $post;
61 | }
62 | };
63 | });
64 |
65 | return view('blogetc::search', [
66 | 'title' => 'Search results for '.e($request->searchQuery()),
67 | 'query' => $request->searchQuery(),
68 | 'search_results' => $searchResultsMappedWithIndexable,
69 | ]);
70 | }
71 |
72 | /**
73 | * @deprecated - use showCategory() instead
74 | */
75 | public function view_category($categorySlug): \Illuminate\Contracts\View\View
76 | {
77 | return $this->showCategory($categorySlug);
78 | }
79 |
80 | /**
81 | * View posts in a category.
82 | */
83 | public function showCategory($categorySlug): \Illuminate\Contracts\View\View
84 | {
85 | return $this->index($categorySlug);
86 | }
87 |
88 | /**
89 | * Show blog posts
90 | * If $categorySlug is set, then only show from that category.
91 | *
92 | * @param string $categorySlug
93 | *
94 | * @return mixed
95 | */
96 | public function index(string $categorySlug = null)
97 | {
98 | // the published_at + is_published are handled by BlogEtcPublishedScope, and don't take effect if the logged
99 | // in user can manage log posts
100 | $title = config('blogetc.blog_index_title', 'Viewing blog');
101 |
102 | // default category ID
103 | $categoryID = null;
104 |
105 | if ($categorySlug) {
106 | // get the category
107 | $category = $this->categoriesService->findBySlug($categorySlug);
108 |
109 | // get category ID to send to service
110 | $categoryID = $category->id;
111 |
112 | // TODO - make configurable
113 | $title = config('blogetc.blog_index_category_title', 'Viewing blog posts in ').$category->category_name;
114 | }
115 |
116 | $posts = $this->postsService->indexPaginated(config('blogetc.per_page'), $categoryID);
117 |
118 | return view('blogetc::index', [
119 | 'posts' => $posts,
120 | 'title' => $title,
121 | 'blogetc_category' => $category ?? null,
122 | ]);
123 | }
124 |
125 | /**
126 | * @deprecated - use show()
127 | */
128 | public function viewSinglePost(Request $request, string $blogPostSlug)
129 | {
130 | return $this->show($request, $blogPostSlug);
131 | }
132 |
133 | /**
134 | * View a single post and (if enabled) comments.
135 | */
136 | public function show(Request $request, string $postSlug): \Illuminate\View\View
137 | {
138 | $blogPost = $this->postsService->findBySlug($postSlug);
139 |
140 | $usingCaptcha = $this->captchaService->getCaptchaObject();
141 |
142 | if (null !== $usingCaptcha && method_exists($usingCaptcha, 'runCaptchaBeforeShowingPosts')) {
143 | $usingCaptcha->runCaptchaBeforeShowingPosts($request, $blogPost);
144 | }
145 |
146 | return view(
147 | 'blogetc::single_post',
148 | [
149 | 'post' => $blogPost,
150 | 'captcha' => $usingCaptcha,
151 | 'comments' => $blogPost->comments->load('user'),
152 | ]
153 | );
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/Events/BlogPostAdded.php:
--------------------------------------------------------------------------------
1 | blogEtcPost = $post;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Events/BlogPostEdited.php:
--------------------------------------------------------------------------------
1 | blogEtcPost = $post;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Events/BlogPostWillBeDeleted.php:
--------------------------------------------------------------------------------
1 | blogEtcPost = $post;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Events/CategoryAdded.php:
--------------------------------------------------------------------------------
1 | blogEtcCategory = $category;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Events/CategoryEdited.php:
--------------------------------------------------------------------------------
1 | blogEtcCategory = $category;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Events/CategoryWillBeDeleted.php:
--------------------------------------------------------------------------------
1 | blogEtcCategory = $category;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Events/CommentAdded.php:
--------------------------------------------------------------------------------
1 | blogEtcPost = $post;
26 | $this->newComment = $newComment;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Events/CommentApproved.php:
--------------------------------------------------------------------------------
1 | comment = $comment;
25 | // you can get the blog post via $comment->post
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Events/CommentWillBeDeleted.php:
--------------------------------------------------------------------------------
1 | comment = $comment;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Events/UploadedImage.php:
--------------------------------------------------------------------------------
1 | image_filename = $image_filename;
40 | $this->blogEtcPost = $blogEtcPost;
41 | $this->image = $image;
42 | $this->source = $source;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Exceptions/BlogEtcAuthGateNotImplementedException.php:
--------------------------------------------------------------------------------
1 | is_admin === true;
19 | // or:
20 | // return $model->email === 'your-email@your-site.com';
21 | // });
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Exceptions/CategoryNotFoundException.php:
--------------------------------------------------------------------------------
1 | define(Category::class, static function (Faker $faker) {
13 | return [
14 | 'category_name' => $faker->sentence,
15 | 'slug' => Str::slug($faker->sentence),
16 | 'category_description' => $faker->paragraph,
17 | ];
18 | });
19 |
--------------------------------------------------------------------------------
/src/Factories/CommentFactory.php:
--------------------------------------------------------------------------------
1 | define(Comment::class, static function (Faker $faker) {
13 | return [
14 | 'blog_etc_post_id' => static function () {
15 | return factory(Post::class)->create()->id;
16 | },
17 | 'user_id' => null,
18 | 'ip' => $faker->ipv4,
19 | 'author_name' => $faker->name,
20 | 'comment' => $faker->sentence,
21 | 'approved' => true,
22 | ];
23 | });
24 |
--------------------------------------------------------------------------------
/src/Factories/PostFactory.php:
--------------------------------------------------------------------------------
1 | define(Post::class, static function (Faker $faker) {
14 | return [
15 | 'title' => $faker->sentence,
16 | 'slug' => $faker->uuid,
17 | 'subtitle' => $faker->sentence,
18 | 'meta_desc' => $faker->paragraph,
19 | 'post_body' => $faker->paragraphs(5, true),
20 | 'posted_at' => Carbon::now()->subWeek(),
21 | 'is_published' => true,
22 | 'short_description' => $faker->paragraph,
23 | 'seo_title' => $faker->sentence,
24 | 'user_id' => null,
25 | ];
26 | });
27 |
28 | // Non published state.
29 | $factory->state(Post::class, 'not_published', [
30 | 'is_published' => false,
31 | ]);
32 |
33 | // Post in future.
34 | $factory->state(Post::class, 'in_future', static function (Faker $faker) {
35 | return [
36 | 'posted_at' => $faker->dateTimeBetween('now', '+2 years'),
37 | ];
38 | });
39 |
--------------------------------------------------------------------------------
/src/Gates/DefaultAddCommentsGate.php:
--------------------------------------------------------------------------------
1 | canManageBlogEtcPosts();
17 | }
18 |
19 | throw new BlogEtcAuthGateNotImplementedException();
20 | };
21 |
--------------------------------------------------------------------------------
/src/Gates/GateTypes.php:
--------------------------------------------------------------------------------
1 | to auto insert the links to rss feed.
89 | */
90 | public static function rssHtmlTag(): string
91 | {
92 | return ' '
94 | .' ';
96 | }
97 |
98 | /**
99 | * This method is depreciated. Just use the config() directly.
100 | *
101 | * @deprecated
102 | */
103 | public static function image_sizes(): array
104 | {
105 | return config('blogetc.image_sizes');
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/Interfaces/BaseRequestInterface.php:
--------------------------------------------------------------------------------
1 | ).
9 | *
10 | * @return string
11 | */
12 | public function captcha_field_name();
13 |
14 | /**
15 | * What view file should we use for the captcha field?
16 | *
17 | * @return string
18 | */
19 | public function view();
20 |
21 | /**
22 | * What rules should we use for the validation for this field?
23 | *
24 | * @return array
25 | */
26 | public function rules();
27 |
28 | // // optional methods, which are run if method_exists($captcha,'...'):
29 | // // do a search in the project to see how they are used.
30 |
31 | // /**
32 | // * executed when viewing single post
33 | // * @return void
34 | // */
35 | // public function runCaptchaBeforeShowingPosts();
36 | //
37 | // /**
38 | // * executed when posting new comment
39 | // * @return void
40 | // */
41 | // public function runCaptchaBeforeAddingComment();
42 | }
43 |
--------------------------------------------------------------------------------
/src/Interfaces/LegacyGetImageFileInterface.php:
--------------------------------------------------------------------------------
1 | belongsToMany(
24 | Post::class,
25 | 'blog_etc_post_categories',
26 | 'blog_etc_category_id',
27 | 'blog_etc_post_id'
28 | );
29 | }
30 |
31 | /**
32 | * Returns the public facing URL of showing blog posts in this category.
33 | *
34 | * @return string
35 | */
36 | public function url(): string
37 | {
38 | return route('blogetc.view_category', $this->slug);
39 | }
40 |
41 | /**
42 | * @deprecated - use editUrl()
43 | */
44 | public function edit_url(): string
45 | {
46 | return $this->editUrl();
47 | }
48 |
49 | /**
50 | * Returns the URL for an admin user to edit this category.
51 | */
52 | public function editUrl(): string
53 | {
54 | return route('blogetc.admin.categories.edit_category', $this->id);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Models/Comment.php:
--------------------------------------------------------------------------------
1 | 'boolean',
14 | ];
15 |
16 | public $fillable = [
17 | 'comment',
18 | 'author_name',
19 | ];
20 |
21 | protected $table = 'blog_etc_comments';
22 |
23 | /**
24 | * The "booting" method of the model.
25 | *
26 | * @return void
27 | */
28 | protected static function boot()
29 | {
30 | parent::boot();
31 |
32 | static::addGlobalScope(new BlogCommentApprovedAndDefaultOrderScope());
33 | }
34 |
35 | /**
36 | * The associated Post for this comment..
37 | *
38 | * @return BelongsTo
39 | */
40 | public function post(): BelongsTo
41 | {
42 | return $this->belongsTo(Post::class, 'blog_etc_post_id');
43 | }
44 |
45 | /**
46 | * Comment author user (if set).
47 | *
48 | * @return BelongsTo
49 | */
50 | public function user(): BelongsTo
51 | {
52 | return $this->belongsTo(User::class);
53 | }
54 |
55 | /**
56 | * Return author string (either from the User (via ->user_id), or the submitted author_name value.
57 | *
58 | * @return string
59 | */
60 | public function author()
61 | {
62 | if ($this->user_id) {
63 | $field = config('blogetc.comments.user_field_for_author_name', 'name');
64 |
65 | return optional($this->user)->$field;
66 | }
67 |
68 | return $this->author_name;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Models/UploadedPhoto.php:
--------------------------------------------------------------------------------
1 | 'array',
13 | ];
14 |
15 | public $fillable = [
16 | 'image_title',
17 | 'uploader_id',
18 | 'source',
19 | 'uploaded_images',
20 | ];
21 | }
22 |
--------------------------------------------------------------------------------
/src/Repositories/CategoriesRepository.php:
--------------------------------------------------------------------------------
1 | model = $model;
22 | }
23 |
24 | /**
25 | * Return all blog etc categories, ordered by category_name, paginated.
26 | */
27 | public function indexPaginated(int $perPage = 25): LengthAwarePaginator
28 | {
29 | return $this->query()
30 | ->orderBy('category_name')
31 | ->paginate($perPage);
32 | }
33 |
34 | /**
35 | * Return new instance of the Query Builder for this model.
36 | */
37 | public function query(): Builder
38 | {
39 | return $this->model->newQuery();
40 | }
41 |
42 | /**
43 | * Find and return a blog etc category.
44 | */
45 | public function find(int $categoryID): Category
46 | {
47 | try {
48 | return $this->query()->findOrFail($categoryID);
49 | } catch (ModelNotFoundException $e) {
50 | throw new CategoryNotFoundException('Unable to find a blog category with ID: '.$categoryID);
51 | }
52 | }
53 |
54 | /**
55 | * Find and return a blog etc category, based on its slug.
56 | */
57 | public function findBySlug(string $categorySlug): Category
58 | {
59 | try {
60 | return $this->query()->where('slug', $categorySlug)->firstOrFail();
61 | } catch (ModelNotFoundException $e) {
62 | throw new CategoryNotFoundException('Unable to find a blog category with slug: '.$categorySlug);
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Repositories/CommentsRepository.php:
--------------------------------------------------------------------------------
1 | model = $model;
24 | }
25 |
26 | /**
27 | * Approve a blog comment.
28 | */
29 | public function approve(int $blogCommentID): Comment
30 | {
31 | $comment = $this->find($blogCommentID, false);
32 | $comment->approved = true;
33 | $comment->save();
34 |
35 | return $comment;
36 | }
37 |
38 | /**
39 | * Find and return a comment by ID.
40 | *
41 | * If $onlyApproved is true, then it will only return an approved comment
42 | * If it is false then it can return it even if not yet approved
43 | */
44 | public function find(int $blogEtcCommentID, bool $onlyApproved = true): Comment
45 | {
46 | try {
47 | $queryBuilder = $this->query(true);
48 |
49 | if (!$onlyApproved) {
50 | $queryBuilder->withoutGlobalScopes();
51 | }
52 |
53 | return $queryBuilder->findOrFail($blogEtcCommentID);
54 | } catch (ModelNotFoundException $e) {
55 | throw new CommentNotFoundException('Unable to find blog post comment with ID: '.$blogEtcCommentID);
56 | }
57 | }
58 |
59 | /**
60 | * Return new instance of the Query Builder for this model.
61 | */
62 | public function query(bool $eagerLoad = false): Builder
63 | {
64 | $queryBuilder = $this->model->newQuery();
65 |
66 | if (true === $eagerLoad) {
67 | $queryBuilder->with('post');
68 | }
69 |
70 | return $queryBuilder;
71 | }
72 |
73 | /**
74 | * Create a comment.
75 | */
76 | public function create(
77 | Post $post,
78 | array $attributes,
79 | string $ip = null,
80 | string $authorWebsite = null,
81 | string $authorEmail = null,
82 | int $userID = null,
83 | bool $autoApproved = false
84 | ): Comment {
85 | // TODO - inject the model object, put into repo, generate $attributes
86 | $newComment = new Comment($attributes);
87 |
88 | $newComment->ip = $ip;
89 | $newComment->author_website = $authorWebsite;
90 | $newComment->author_email = $authorEmail;
91 | $newComment->user_id = $userID;
92 | $newComment->approved = $autoApproved;
93 |
94 | $post->comments()->save($newComment);
95 |
96 | return $newComment;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Repositories/PostsRepository.php:
--------------------------------------------------------------------------------
1 | model = $model;
29 | }
30 |
31 | /**
32 | * Return blog posts ordered by posted_at, paginated.
33 | *
34 | * @param int $categoryID
35 | */
36 | public function indexPaginated(int $perPage = 10, int $categoryID = null): LengthAwarePaginator
37 | {
38 | $query = $this->query(true)
39 | ->orderBy('posted_at', 'desc');
40 |
41 | if ($categoryID > 0) {
42 | $query->whereHas('categories', static function (Builder $query) use ($categoryID) {
43 | $query->where('blog_etc_post_categories.blog_etc_category_id', $categoryID);
44 | })->get();
45 | }
46 |
47 | return $query->paginate($perPage);
48 | }
49 |
50 | /**
51 | * Return new instance of the Query Builder for this model.
52 | */
53 | public function query(bool $eagerLoad = false): Builder
54 | {
55 | $queryBuilder = $this->model->newQuery();
56 |
57 | if (true === $eagerLoad) {
58 | $queryBuilder->with(['categories']);
59 | }
60 |
61 | return $queryBuilder;
62 | }
63 |
64 | /**
65 | * Return posts for RSS feed.
66 | *
67 | * @return Builder[]|Collection
68 | */
69 | public function rssItems(): Collection
70 | {
71 | return $this->query(false)
72 | ->orderBy('posted_at', 'desc')
73 | ->limit(config('blogetc.rssfeed.posts_to_show_in_rss_feed'))
74 | ->with('author')
75 | ->get();
76 | }
77 |
78 | /**
79 | * Find a blog etc post by slug
80 | * If cannot find, throw exception.
81 | */
82 | public function findBySlug(string $slug): Post
83 | {
84 | try {
85 | // the published_at + is_published are handled by BlogEtcPublishedScope, and don't take effect if the
86 | // logged in user can manage log posts
87 | return $this->query(true)
88 | ->where('slug', $slug)
89 | ->firstOrFail();
90 | } catch (ModelNotFoundException $e) {
91 | throw new PostNotFoundException('Unable to find blog post with slug: '.$slug);
92 | }
93 | }
94 |
95 | /**
96 | * Find a blog etc post by ID
97 | * If cannot find, throw exception.
98 | */
99 | public function findById(int $id): Post
100 | {
101 | try {
102 | // the published_at + is_published are handled by BlogEtcPublishedScope, and don't take effect if the
103 | // logged in user can manage log posts
104 | return $this->query(true)
105 | ->where('id', $id)
106 | ->firstOrFail();
107 | } catch (ModelNotFoundException $e) {
108 | throw new PostNotFoundException('Unable to find blog post with id: '.$id);
109 | }
110 | }
111 |
112 | /**
113 | * Create a new BlogEtcPost post.
114 | */
115 | public function create(array $attributes): Post
116 | {
117 | return $this->query()->create($attributes);
118 | }
119 |
120 | /**
121 | * Delete a post.
122 | *
123 | * @throws Exception
124 | */
125 | public function delete(int $postID): bool
126 | {
127 | $post = $this->find($postID);
128 |
129 | return (bool) $post->delete();
130 | }
131 |
132 | /**
133 | * Find a blog etc post by ID
134 | * If cannot find, throw exception.
135 | */
136 | public function find(int $blogEtcPostID): Post
137 | {
138 | try {
139 | return $this->query(true)->findOrFail($blogEtcPostID);
140 | } catch (ModelNotFoundException $e) {
141 | throw new PostNotFoundException('Unable to find blog post with ID: '.$blogEtcPostID);
142 | }
143 | }
144 |
145 | /**
146 | * Update image sizes (or in theory any attribute) on a blog etc post.
147 | *
148 | * TODO - currently untested.
149 | *
150 | * @param array $uploadedImages
151 | */
152 | public function updateImageSizes(Post $post, ?array $uploadedImages): Post
153 | {
154 | if (!empty($uploadedImages)) {
155 | // does not use update() here as it would require fillable for each field - and in theory someone
156 | // might want to add more image sizes.
157 | foreach ($uploadedImages as $size => $imageName) {
158 | $post->$size = $imageName;
159 | }
160 | $post->save();
161 | }
162 |
163 | return $post;
164 | }
165 |
166 | /**
167 | * Search for posts.
168 | *
169 | * This is a rough implementation - proper full text search has been removed in current version.
170 | */
171 | public function search(string $search, int $max = 25): Collection
172 | {
173 | $query = $this->query(true)->limit($max);
174 |
175 | trim($search)
176 | ? $query->where('title', 'like', '%'.$search)
177 | : $query->where('title', '');
178 |
179 | return $query->get();
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/src/Repositories/UploadedPhotosRepository.php:
--------------------------------------------------------------------------------
1 | model = $model;
24 | }
25 |
26 | /**
27 | * Create a new Uploaded Photo row in the database.
28 | */
29 | public function create(array $attributes): UploadedPhoto
30 | {
31 | return $this->query()->create($attributes);
32 | }
33 |
34 | /**
35 | * Return new instance of the Query Builder for this model.
36 | */
37 | public function query(): Builder
38 | {
39 | return $this->model->newQuery();
40 | }
41 |
42 | /**
43 | * Delete a uploaded photo from the database.
44 | */
45 | public function delete(int $uploadedPhotoID): ?bool
46 | {
47 | $uploadedPhoto = $this->find($uploadedPhotoID);
48 |
49 | return $uploadedPhoto->delete();
50 | }
51 |
52 | /**
53 | * Find a blog etc uploaded photo by ID.
54 | *
55 | * If cannot find, throw exception.
56 | */
57 | public function find(int $uploadedPhotoID): UploadedPhoto
58 | {
59 | try {
60 | return $this->query()->findOrFail($uploadedPhotoID);
61 | } catch (ModelNotFoundException $e) {
62 | throw new UploadedPhotoNotFoundException('Unable to find Uploaded Photo with ID: '.$uploadedPhotoID);
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Requests/AddNewCommentRequest.php:
--------------------------------------------------------------------------------
1 | ['required', 'string', 'min:3', 'max:1000'],
23 | 'author_name' => ['string', 'min:1', 'max:50'],
24 | 'author_email' => ['string', 'nullable', 'min:1', 'max:254', 'email'],
25 | 'author_website' => ['string', 'nullable', 'min:'.strlen('http://a.b'), 'max:175', 'active_url'],
26 | ];
27 |
28 | $return['author_name'][] = Auth::check() && config('blogetc.comments.save_user_id_if_logged_in', true)
29 | ? 'nullable'
30 | : 'required';
31 |
32 | if (config('blogetc.captcha.captcha_enabled')) {
33 | /** @var string $captcha_class */
34 | $captcha_class = config('blogetc.captcha.captcha_type');
35 |
36 | /** @var CaptchaInterface $captcha */
37 | $captcha = new $captcha_class();
38 |
39 | $return[$captcha->captcha_field_name()] = $captcha->rules();
40 | }
41 |
42 | // in case you need to implement something custom, you can use this...
43 | if (config('blogetc.comments.rules') && is_callable(config('blogetc.comments.rules'))) {
44 | /** @var callable $func */
45 | $func = config('blogetc.comments.rules');
46 | $return = $func($return);
47 | }
48 |
49 | if (config('blogetc.comments.require_author_email')) {
50 | $return['author_email'][] = 'required';
51 | }
52 |
53 | return $return;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Requests/BaseAdminRequest.php:
--------------------------------------------------------------------------------
1 | ['nullable', 'date'],
44 | 'title' => ['required', 'string', 'min:1', 'max:255'],
45 | 'subtitle' => ['nullable', 'string', 'min:1', 'max:255'],
46 | 'post_body' => ['required_without:use_view_file', 'max:2000000'], //medium text
47 | 'meta_desc' => ['nullable', 'string', 'min:1', 'max:1000'],
48 | 'short_description' => ['nullable', 'string', 'max:30000'],
49 | 'slug' => [
50 | 'nullable',
51 | 'string',
52 | 'min:1',
53 | 'max:150',
54 | 'alpha_dash', // this field should have some additional rules, which is done in the subclasses.
55 | ],
56 | 'category' => ['nullable', 'array'],
57 | ];
58 |
59 | // is use_custom_view_files true?
60 | if (config('blogetc.use_custom_view_files')) {
61 | $return['use_view_file'] = ['nullable', 'string', 'alpha_num', 'min:1', 'max:75'];
62 | } else {
63 | // use_view_file is disabled, so give an empty if anything is submitted via this function:
64 | $return['use_view_file'] = ['string', $disabled_use_view_file];
65 | }
66 |
67 | // some additional rules for uploaded images
68 | foreach ((array) config('blogetc.image_sizes') as $size => $image_detail) {
69 | if ($image_detail['enabled'] && config('blogetc.image_upload_enabled')) {
70 | $return[$size] = ['nullable', 'image'];
71 | } else {
72 | // was not enabled (or all images are disabled), so show an error if it was submitted:
73 | $return[$size] = $show_error_if_has_value;
74 | }
75 | }
76 |
77 | return $return;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Requests/BaseRequest.php:
--------------------------------------------------------------------------------
1 | method()) {
18 | // No rules are required for deleting.
19 | return [];
20 | }
21 | $rules = [
22 | 'category_name' => ['required', 'string', 'min:1', 'max:200'],
23 | 'slug' => ['required', 'alpha_dash', 'max:100', 'min:1'],
24 | 'category_description' => ['nullable', 'string', 'min:1', 'max:5000'],
25 | ];
26 |
27 | if (Request::METHOD_POST === $this->method()) {
28 | $rules['slug'][] = Rule::unique('blog_etc_categories', 'slug');
29 | }
30 |
31 | if (in_array($this->method(), [Request::METHOD_PUT, Request::METHOD_PATCH], true)) {
32 | $rules['slug'][] = Rule::unique('blog_etc_categories', 'slug')
33 | ->ignore($this->route()->parameter('categoryId'));
34 | }
35 |
36 | return $rules;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Requests/CreateBlogEtcPostRequest.php:
--------------------------------------------------------------------------------
1 | baseBlogPostRules();
23 | $return['slug'][] = Rule::unique('blog_etc_posts', 'slug');
24 |
25 | return $return;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Requests/DeleteBlogEtcPostRequest.php:
--------------------------------------------------------------------------------
1 | [Rule::in(['rss', 'atom'])],
27 | ];
28 | }
29 |
30 | /**
31 | * Is this request for an RSS feed or Atom feed? defaults to atom.
32 | *
33 | * @return string
34 | */
35 | public function getFeedType(): string
36 | {
37 | return 'rss' === $this->get('type')
38 | ? 'rss'
39 | : 'atom';
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Requests/SearchRequest.php:
--------------------------------------------------------------------------------
1 | ['nullable', 'string', 'min:3', 'max:40'],
27 | ];
28 | }
29 |
30 | /**
31 | * Return the query that user searched for.
32 | */
33 | public function searchQuery(): string
34 | {
35 | return $this->get('s', '');
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Requests/Traits/HasCategoriesTrait.php:
--------------------------------------------------------------------------------
1 | get('category') || !is_array($this->get('category'))) {
20 | return [];
21 | }
22 |
23 | //$this->get("category") is an array of category SLUGs and we need IDs
24 |
25 | // check they are valid, return the IDs
26 | // limit to 1000 ... just in case someone submits with too many for the web server. No error is given if they submit more than 1k.
27 | $vals = Category::whereIn('id', array_keys($this->get('category')))->select('id')->limit(1000)->get();
28 | $vals = array_values($vals->pluck('id')->toArray());
29 |
30 | return $vals;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Requests/Traits/HasImageUploadTrait.php:
--------------------------------------------------------------------------------
1 | file($size)) {
17 | return $this->file($size);
18 | }
19 |
20 | // not found? lets cycle through all the images and see if anything was submitted, and use that instead
21 | foreach (config('blogetc.image_sizes') as $image_size_name => $image_size_info) {
22 | if ($this->file($image_size_name)) {
23 | return $this->file($image_size_name);
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Requests/UpdateBlogEtcPostRequest.php:
--------------------------------------------------------------------------------
1 | baseBlogPostRules();
23 | $return['slug'][] = Rule::unique('blog_etc_posts', 'slug')->ignore($this->route()->parameter('blogPostId'));
24 |
25 | return $return;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Requests/UploadImageRequest.php:
--------------------------------------------------------------------------------
1 | [
11 | 'required',
12 | 'array',
13 | ],
14 | 'sizes_to_upload.*' => [
15 | 'string',
16 | 'max:100',
17 | ],
18 | 'upload' => [
19 | 'required',
20 | 'image',
21 | ],
22 | 'image_title' => [
23 | 'required',
24 | 'string',
25 | 'min:1',
26 | 'max:150',
27 | ],
28 | ];
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Scopes/BlogCommentApprovedAndDefaultOrderScope.php:
--------------------------------------------------------------------------------
1 | orderBy('id', 'asc');
21 | $builder->limit(config('blogetc.comments.max_num_of_comments_to_show', 500));
22 | $builder->where('approved', true);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Scopes/BlogEtcPublishedScope.php:
--------------------------------------------------------------------------------
1 | where('is_published', true);
23 | $builder->where('posted_at', '<=', Carbon::now());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Services/CaptchaService.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
31 | }
32 |
33 | /**
34 | * Return paginated collection of categories.
35 | */
36 | public function indexPaginated(int $perPage = 25): LengthAwarePaginator
37 | {
38 | return $this->repository->indexPaginated($perPage);
39 | }
40 |
41 | /**
42 | * Find and return a blog etc category, based on its slug.
43 | */
44 | public function findBySlug(string $categorySlug): Category
45 | {
46 | return $this->repository->findBySlug($categorySlug);
47 | }
48 |
49 | /**
50 | * Create a new Category entry.
51 | */
52 | public function create(array $attributes): Category
53 | {
54 | $newCategory = new Category($attributes);
55 | $newCategory->save();
56 |
57 | event(new CategoryAdded($newCategory));
58 |
59 | return $newCategory;
60 | }
61 |
62 | /**
63 | * Update an existing Category entry.
64 | */
65 | public function update(int $categoryID, array $attributes): Category
66 | {
67 | $category = $this->find($categoryID);
68 | $category->fill($attributes);
69 | $category->save();
70 |
71 | event(new CategoryEdited($category));
72 |
73 | return $category;
74 | }
75 |
76 | /**
77 | * Find and return a blog etc category from it's ID.
78 | */
79 | public function find(int $categoryID): Category
80 | {
81 | return $this->repository->find($categoryID);
82 | }
83 |
84 | /**
85 | * Delete a BlogEtcCategory.
86 | *
87 | * @throws Exception
88 | */
89 | public function delete(int $categoryID): void
90 | {
91 | $category = $this->find($categoryID);
92 |
93 | event(new CategoryWillBeDeleted($category));
94 |
95 | $category->delete();
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/Services/CommentsService.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
35 | }
36 |
37 | /**
38 | * BlogEtcCategoriesRepository repository - for query heavy method.
39 | */
40 | public function repository(): CommentsRepository
41 | {
42 | return $this->repository;
43 | }
44 |
45 | /**
46 | * Create a new comment.
47 | */
48 | public function create(
49 | Post $blogEtcPost,
50 | array $attributes,
51 | string $ip = null,
52 | int $userID = null
53 | ): Comment {
54 | $ip = config('blogetc.comments.save_ip_address')
55 | ? $ip : null;
56 |
57 | $authorWebsite = config('blogetc.comments.ask_for_author_website') && !empty($attributes['author_website'])
58 | ? $attributes['author_website']
59 | : null;
60 |
61 | $authorEmail = config('blogetc.comments.ask_for_author_website') && !empty($attributes['author_email'])
62 | ? $attributes['author_email']
63 | : null;
64 |
65 | $userID = config('blogetc.comments.save_user_id_if_logged_in')
66 | ? $userID
67 | : null;
68 |
69 | $approved = $this->autoApproved();
70 |
71 | $newComment = $this->repository->create(
72 | $blogEtcPost,
73 | $attributes,
74 | $ip,
75 | $authorWebsite,
76 | $authorEmail,
77 | $userID,
78 | $approved
79 | );
80 |
81 | event(new CommentAdded($blogEtcPost, $newComment));
82 |
83 | return $newComment;
84 | }
85 |
86 | /**
87 | * Are comments auto approved?
88 | */
89 | protected function autoApproved(): bool
90 | {
91 | return true === config('blogetc.comments.auto_approve_comments', true);
92 | }
93 |
94 | /**
95 | * Approve a blog comment.
96 | */
97 | public function approve(int $blogCommentID): Comment
98 | {
99 | $comment = $this->repository->approve($blogCommentID);
100 | event(new CommentApproved($comment));
101 |
102 | return $comment;
103 | }
104 |
105 | /**
106 | * Delete a blog comment.
107 | *
108 | * Returns the now deleted comment object
109 | *
110 | * @throws Exception
111 | */
112 | public function delete(int $blogCommentID): Comment
113 | {
114 | $comment = $this->find($blogCommentID, false);
115 | event(new CommentWillBeDeleted($comment));
116 | $comment->delete();
117 |
118 | return $comment;
119 | }
120 |
121 | /**
122 | * Find and return a comment by ID.
123 | */
124 | public function find(int $blogEtcCommentID, bool $onlyApproved = true): Comment
125 | {
126 | return $this->repository->find($blogEtcCommentID, $onlyApproved);
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/Services/FeedService.php:
--------------------------------------------------------------------------------
1 | postsService = $postsService;
28 | }
29 |
30 | /**
31 | * Build the Feed object and populate it with blog posts.
32 | */
33 | public function getFeed(Feed $feed, string $feedType): Response
34 | {
35 | // RSS feed is cached. Admin/writer users might see different content, so
36 | // use a different cache for different users.
37 |
38 | // This should not be a problem unless your site has many logged in users.
39 | // (Use check(), as it is possible for user to be logged in without having an ID (depending on how the guard
40 | // is set up...)
41 | $userOrGuest = Auth::check()
42 | ? 'logged-in-'.Auth::id()
43 | : 'guest';
44 |
45 | $key = 'blogetc-'.$feedType.$userOrGuest;
46 |
47 | $feed->setCache(
48 | config('blogetc.rssfeed.cache_in_minutes', 60),
49 | $key
50 | );
51 |
52 | if (!$feed->isCached()) {
53 | $this->makeFreshFeed($feed);
54 | }
55 |
56 | return $feed->render($feedType);
57 | }
58 |
59 | /**
60 | * Create fresh feed by passing latest blog posts.
61 | *
62 | * @param $feed
63 | */
64 | protected function makeFreshFeed(Feed $feed): void
65 | {
66 | $blogPosts = $this->postsService->rssItems();
67 |
68 | $this->setupFeed(
69 | $feed,
70 | $this->pubDate($blogPosts)
71 | );
72 |
73 | /** @var Post $blogPost */
74 | foreach ($blogPosts as $blogPost) {
75 | $feed->add(
76 | $blogPost->title,
77 | $blogPost->authorString(),
78 | $blogPost->url(),
79 | $blogPost->posted_at,
80 | $blogPost->short_description
81 | );
82 | }
83 | }
84 |
85 | /**
86 | * Basic set up of the Feed object.
87 | */
88 | protected function setupFeed(Feed $feed, Carbon $pubDate): Feed
89 | {
90 | $feed->title = config('blogetc.rssfeed.title');
91 | $feed->description = config('blogetc.rssfeed.description');
92 | $feed->link = route('blogetc.index');
93 | $feed->lang = config('blogetc.rssfeed.language');
94 | $feed->setShortening(config('blogetc.rssfeed.should_shorten_text'));
95 | $feed->setTextLimit(config('blogetc.rssfeed.text_limit'));
96 | $feed->setDateFormat('carbon');
97 | $feed->pubdate = $pubDate;
98 |
99 | return $feed;
100 | }
101 |
102 | /**
103 | * Return the first post posted_at date, or if none exist then return today.
104 | */
105 | protected function pubDate(Collection $blogPosts): Carbon
106 | {
107 | return $blogPosts->first()
108 | ? $blogPosts->first()->posted_at
109 | : Carbon::now();
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/Views/blogetc/captcha/basic.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | Captcha: {{ config('blogetc.captcha.basic_question', '[error - undefined captcha question]' )}}
4 |
5 |
7 |
8 |
--------------------------------------------------------------------------------
/src/Views/blogetc/index.blade.php:
--------------------------------------------------------------------------------
1 | @extends("layouts.app",['title'=>$title])
2 | @section("content")
3 |
4 |
5 | @can(\WebDevEtc\BlogEtc\Gates\GateTypes::MANAGE_BLOG_ADMIN)
6 |
7 |
8 | You are logged in as a blog admin user.
9 |
10 |
12 |
13 | Go To Blog Admin Panel
14 |
15 |
16 |
17 | @endcan
18 |
19 | @if(isset($blogetc_category) && $blogetc_category)
20 |
21 | Viewing Category: {{$blogetc_category->category_name}}
22 |
23 | @if($blogetc_category->category_description)
24 |
{{$blogetc_category->category_description}}
25 | @endif
26 | @endif
27 |
28 | @forelse($posts as $post)
29 | @include("blogetc::partials.index_loop")
30 | @empty
31 |
No posts
32 | @endforelse
33 |
34 |
35 | {{$posts->appends( [] )->links()}}
36 |
37 | @include("blogetc::sitewide.search_form")
38 |
39 |
40 | @endsection
41 |
--------------------------------------------------------------------------------
/src/Views/blogetc/partials/add_comment_form.blade.php:
--------------------------------------------------------------------------------
1 | @php
2 | use WebDevEtc\BlogEtc\Captcha\CaptchaAbstract;
3 | use WebDevEtc\BlogEtc\Models\Post;
4 | /** @var Post $post */
5 | /** @var CaptchaAbstract $captcha */
6 | @endphp
7 | @can(\WebDevEtc\BlogEtc\Gates\GateTypes::ADD_COMMENT)
8 |
92 | @endcan
93 |
--------------------------------------------------------------------------------
/src/Views/blogetc/partials/author.blade.php:
--------------------------------------------------------------------------------
1 | @php
2 | /** @var \WebDevEtc\BlogEtc\Models\Post $post */
3 | @endphp
4 |
5 | by {{$post->author->name}}
6 |
--------------------------------------------------------------------------------
/src/Views/blogetc/partials/built_in_comments.blade.php:
--------------------------------------------------------------------------------
1 | @php
2 | /** @var \WebDevEtc\BlogEtc\Models\Comment[] $comments */
3 | @endphp
4 | @forelse($comments as $comment)
5 |
6 |
15 |
16 |
{!! nl2br(e($comment->comment)) !!}
17 |
18 |
19 | @empty
20 |
21 | No comments yet!
22 | @can(\WebDevEtc\BlogEtc\Gates\GateTypes::ADD_COMMENT)
23 | Why don't you be the first?
24 | @endcan
25 |
26 | @endforelse
27 |
28 | @if(count($comments) >= config('blogetc.comments.max_num_of_comments_to_show', 500))
29 |
30 | Only the first {{ config('blogetc.comments.max_num_of_comments_to_show', 500) }} comments are shown.
31 |
32 | @endif
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/Views/blogetc/partials/categories.blade.php:
--------------------------------------------------------------------------------
1 | @php
2 | /** @var \WebDevEtc\BlogEtc\Models\Post $post */
3 | @endphp
4 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Views/blogetc/partials/custom_comments.blade.php:
--------------------------------------------------------------------------------
1 |
2 | Error! custom_comments: You must customise this by creating a file in
3 | /resources/views/vendor/blogetc/partials/custom_comments.blade.php
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/Views/blogetc/partials/disqus_comments.blade.php:
--------------------------------------------------------------------------------
1 | @php
2 | /** @var \WebDevEtc\BlogEtc\Models\Post $post */
3 | @endphp
4 |
5 |
6 |
18 | Please enable JavaScript to view the comments powered by Disqus ,
19 | and run by WebDevEtc's BlogEtc Laravel Blog package
20 |
21 |
--------------------------------------------------------------------------------
/src/Views/blogetc/partials/full_post_details.blade.php:
--------------------------------------------------------------------------------
1 | @php
2 | /** @var \WebDevEtc\BlogEtc\Models\Post $post */
3 | @endphp
4 | @can(\WebDevEtc\BlogEtc\Gates\GateTypes::MANAGE_BLOG_ADMIN)
5 |
6 | Edit Post
7 |
8 | @endcan
9 |
10 | {{$post->title}}
11 | {{$post->subtitle}}
12 |
13 | {!! $post->imageTag('medium', false, 'd-block mx-auto') !!}
14 |
15 |
16 | {!! $post->renderBody() !!}
17 |
18 | {{--@if(config("blogetc.use_custom_view_files") && $post->use_view_file)--}}
19 | {{-- // use a custom blade file for the output of those blog post--}}
20 | {{-- @include("blogetc::partials.use_view_file")--}}
21 | {{--@else--}}
22 | {{-- {!! $post->post_body !!} // unsafe, echoing the plain html/js--}}
23 | {{-- {{ $post->post_body }} // for safe escaping --}}
24 | {{--@endif--}}
25 |
26 |
27 |
28 |
29 | @if($post->posted_at)
30 | Posted {{ $post->posted_at->diffForHumans() }}
31 | @endif
32 |
33 | @includeWhen($post->author, 'blogetc::partials.author', ['post'=>$post])
34 | @includeWhen($post->categories, 'blogetc::partials.categories', ['post'=>$post])
35 |
36 |
--------------------------------------------------------------------------------
/src/Views/blogetc/partials/index_loop.blade.php:
--------------------------------------------------------------------------------
1 | @php
2 | /** @var \WebDevEtc\BlogEtc\Models\Post $post */
3 | @endphp
4 | {{--Used on the index page (so shows a small summary--}}
5 | {{--See the guide on webdevetc.com for how to copy these files to your /resources/views/ directory--}}
6 | {{--https://webdevetc.com/laravel/packages/blogetc-blog-system-for-your-laravel-app/help-documentation/laravel-blog-package-blogetc#guide_to_views--}}
7 |
8 |
9 |
10 |
11 | {!! $post->imageTag('medium', true, '') !!}
12 |
13 |
14 |
15 |
{{$post->subtitle}}
16 |
17 | @if(config('blogetc.show_full_post_on_index'))
18 | {!! $post->renderBody() !!}
19 | @else
20 |
{!! $post->generateIntroduction(400) !!}
21 | @endif
22 |
23 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/Views/blogetc/partials/search_form.blade.php:
--------------------------------------------------------------------------------
1 | {{--This is only included for backwards compatibility. It will be removed at a future stage.--}}
2 | @include('blogetc::sitewide.search_form')
3 |
4 |
--------------------------------------------------------------------------------
/src/Views/blogetc/partials/show_comments.blade.php:
--------------------------------------------------------------------------------
1 | @switch(config("blogetc.comments.type_of_comments_to_show","built_in"))
2 |
3 | @case("built_in")
4 | @include("blogetc::partials.built_in_comments")
5 | @include("blogetc::partials.add_comment_form")
6 | @break
7 |
8 | @case("disqus")
9 | @include("blogetc::partials.disqus_comments")
10 | @break
11 |
12 |
13 | @case("custom")
14 | @include("blogetc::partials.custom_comments")
15 | @break
16 |
17 | @case("disabled")
18 |
21 | @break
22 |
23 | @default
24 |
25 | Invalid comment type_of_comments_to_show
config option
26 |
27 | @endswitch
28 |
--------------------------------------------------------------------------------
/src/Views/blogetc/partials/show_errors.blade.php:
--------------------------------------------------------------------------------
1 | @if (isset($errors) && count($errors))
2 |
3 |
Sorry, but there was an error:
4 |
5 | @foreach($errors->all() as $error)
6 | {{ $error }}
7 | @endforeach
8 |
9 |
10 | @endif
11 |
--------------------------------------------------------------------------------
/src/Views/blogetc/partials/use_view_file.blade.php:
--------------------------------------------------------------------------------
1 | @php
2 | /** @var \WebDevEtc\BlogEtc\Models\Post $post */
3 | @endphp
4 | @if(View::exists($post->bladeViewFile()))
5 | {{--view file existed, so include it.--}}
6 | @include("custom_blog_posts." . $post->use_view_file, ['post' =>$post])
7 | @else
8 | {{-- the view file wasn't there. Show a detailed error if user is logged in and can manage the blog, otherwise show generic error.--}}
9 |
10 | @can(\WebDevEtc\BlogEtc\Gates\GateTypes::MANAGE_BLOG_ADMIN)
11 |
20 | @else
21 |
22 | Sorry, but there is an error showing that blog post. Please come back later.
23 |
24 | @endcan
25 | @endif
26 |
27 |
--------------------------------------------------------------------------------
/src/Views/blogetc/saved_comment.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app',['title'=>'Saved comment'])
2 | @section('content')
3 |
4 |
5 | Thanks! Your comment has been saved!
6 |
7 |
8 | @if(!config('blogetc.comments.auto_approve_comments', false))
9 |
10 | After an admin user approves the comment, it'll appear on the site!
11 |
12 | @endif
13 |
14 |
15 | Back to blog post
16 |
17 |
18 | @endsection
19 |
20 |
--------------------------------------------------------------------------------
/src/Views/blogetc/search.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.app',['title' => $title])
2 | @section('content')
3 |
4 | {{--https://webdevetc.com/laravel/packages/blogetc-blog-system-for-your-laravel-app/help-documentation/laravel-blog-package-blogetc#guide_to_views--}}
5 |
6 |
7 |
8 |
Search Results for {{ $query }}
9 |
10 | @forelse($search_results as $result)
11 |
Search result #{{ $loop->iteration }}
12 | @include('blogetc::partials.index_loop', ['post' => $result->indexable])
13 | @empty
14 |
Sorry, but there were no results!
15 | @endforelse
16 |
17 | @include('blogetc::sitewide.search_form')
18 |
19 |
20 | @endsection
21 |
22 |
--------------------------------------------------------------------------------
/src/Views/blogetc/single_post.blade.php:
--------------------------------------------------------------------------------
1 | @extends("layouts.app",['title'=>$post->genSeoTitle()])
2 | @section("content")
3 | {{--https://webdevetc.com/laravel/packages/blogetc-blog-system-for-your-laravel-app/help-documentation/laravel-blog-package-blogetc#guide_to_views--}}
4 |
5 |
6 |
7 | @include("blogetc::partials.show_errors")
8 | @include("blogetc::partials.full_post_details")
9 |
10 | @if(config("blogetc.comments.type_of_comments_to_show","built_in") !== 'disabled')
11 |
12 |
13 | @include("blogetc::partials.show_comments")
14 |
15 | @endif
16 |
17 |
18 |
19 | @endsection
20 |
21 |
--------------------------------------------------------------------------------
/src/Views/blogetc/sitewide/random_posts.blade.php:
--------------------------------------------------------------------------------
1 | Random Posts
2 |
3 | {{-- TODO replace with repo!--}}
4 | @foreach(\WebDevEtc\BlogEtc\Models\Post::inRandomOrder()->limit(5)->get() as $post)
5 |
6 |
7 | {{ $post->title }}
8 |
9 |
10 | @endforeach
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/Views/blogetc/sitewide/recent_posts.blade.php:
--------------------------------------------------------------------------------
1 | Recent Posts
2 |
3 | {{-- TODO replace with repo--}}
4 | @foreach(\WebDevEtc\BlogEtc\Models\Post::orderBy('posted_at','desc')->limit(5)->get() as $post)
5 |
6 |
7 | {{ $post->title }}
8 |
9 |
10 | @endforeach
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/Views/blogetc/sitewide/search_form.blade.php:
--------------------------------------------------------------------------------
1 | @if (config('blogetc.search.search_enabled') )
2 |
3 |
8 |
9 | @endif
10 |
--------------------------------------------------------------------------------
/src/Views/blogetc/sitewide/show_all_categories.blade.php:
--------------------------------------------------------------------------------
1 | Post Categories
2 |
3 | {{-- TODO replace with repo--}}
4 | @foreach(\WebDevEtc\BlogEtc\Models\Category::orderBy('category_name')->limit(200)->get() as $category)
5 |
6 | {{ $category->category_name }}
7 |
8 | @endforeach
9 |
10 |
--------------------------------------------------------------------------------
/src/Views/blogetc_admin/categories/add_category.blade.php:
--------------------------------------------------------------------------------
1 | @extends('blogetc_admin::layouts.admin_layout')
2 | @section('title', 'BlogEtc - Add Category')
3 | @section('content')
4 | Admin - Add Category
5 |
6 |
12 | @endsection
13 |
--------------------------------------------------------------------------------
/src/Views/blogetc_admin/categories/deleted_category.blade.php:
--------------------------------------------------------------------------------
1 | @extends('blogetc_admin::layouts.admin_layout')
2 | @section('title','Category Deleted')
3 | @section('content')
4 |
5 | Category deleted
6 |
7 | @endsection
8 |
--------------------------------------------------------------------------------
/src/Views/blogetc_admin/categories/edit_category.blade.php:
--------------------------------------------------------------------------------
1 | @extends('blogetc_admin::layouts.admin_layout')
2 | @section('title', 'Edit Category ' . $category->category_name)
3 | @section('content')
4 | Admin - Edit Category
5 |
6 |
14 | @endsection
15 |
--------------------------------------------------------------------------------
/src/Views/blogetc_admin/categories/form.blade.php:
--------------------------------------------------------------------------------
1 |
24 |
25 | Category Name
26 |
27 | category_name)}}"
35 | >
36 | The name of the category
37 |
38 |
39 |
40 |
41 | Category slug
42 | slug)}}"
53 | >
54 |
55 |
56 | Letters, numbers, dash only. The slug i.e. {{ route("blogetc.view_category", "") }}/this_part .
57 | This must be unique (two categories can't share the same slug).
58 |
59 |
60 |
61 |
62 | Category Description (optional)
63 |
66 |
67 |
68 |
71 |
--------------------------------------------------------------------------------
/src/Views/blogetc_admin/categories/index.blade.php:
--------------------------------------------------------------------------------
1 | @extends('blogetc_admin::layouts.admin_layout')
2 | @section('content')
3 | @forelse ($categories as $category)
4 |
22 | @empty
23 | None found, why don't you add one?
24 | @endforelse
25 |
26 |
27 | {{ $categories->links() }}
28 |
29 | @endsection
30 |
--------------------------------------------------------------------------------
/src/Views/blogetc_admin/comments/index.blade.php:
--------------------------------------------------------------------------------
1 | @php
2 | /** @var \WebDevEtc\BlogEtc\Models\Comment[] $comments */
3 | @endphp
4 | @extends('blogetc_admin::layouts.admin_layout')
5 | @section('title', 'BlogEtc Manage Comments')
6 | @section('content')
7 | @forelse ($comments as $comment)
8 |
9 |
10 |
11 | {{$comment->author()}} commented on:
12 |
13 | @if($comment->post)
14 | {{$comment->post->title}}
15 | @else
16 | Unknown blog post
17 | @endif
18 |
19 | on {{$comment->created_at}}
20 |
21 |
{{$comment->comment}}
22 | @if($comment->post)
23 |
24 |
25 | View Post
26 |
27 |
28 |
29 | Edit Post
30 |
31 | @endif
32 |
33 | @if(!$comment->approved)
34 | {{--APPROVE BUTTON--}}
35 |
41 | @endif
42 |
43 | {{--DELETE BUTTON--}}
44 |
52 |
53 |
54 | @empty
55 | None found
56 | @endforelse
57 |
58 |
59 | {{ $comments->links() }}
60 |
61 | @endsection
62 |
63 |
--------------------------------------------------------------------------------
/src/Views/blogetc_admin/imageupload/create.blade.php:
--------------------------------------------------------------------------------
1 | @extends('blogetc_admin::layouts.admin_layout')
2 | @section('title','Blog Etc Admin - Upload Images')
3 | @section('content')
4 | Admin - Upload Images
5 |
6 | You can use this to upload images.
7 |
42 | @endsection
43 |
44 |
--------------------------------------------------------------------------------
/src/Views/blogetc_admin/imageupload/delete-post-image.blade.php:
--------------------------------------------------------------------------------
1 | @extends('blogetc_admin::layouts.admin_layout')
2 | @section('content')
3 | Are you sure you want to delete the featured image for the selected post?
4 |
5 |
10 | @endsection
11 |
--------------------------------------------------------------------------------
/src/Views/blogetc_admin/imageupload/deleted-post-image.blade.php:
--------------------------------------------------------------------------------
1 | @extends('blogetc_admin::layouts.admin_layout')
2 | @section('content')
3 | Deleted featured images for the selected post.
4 | @endsection
5 |
--------------------------------------------------------------------------------
/src/Views/blogetc_admin/imageupload/index.blade.php:
--------------------------------------------------------------------------------
1 | @php
2 | /** @var UploadedPhoto[] $uploaded_photos */
3 | use WebDevEtc\BlogEtc\Models\UploadedPhoto;$uploadedPhoto = $uploaded_photos
4 | @endphp
5 | @extends('blogetc_admin::layouts.admin_layout')
6 | @section('content')
7 | Admin - Uploaded Images
8 |
9 | You can view all previously uploaded images here.
10 |
11 | It includes one thumbnail per photo - the smallest image is selected.
12 |
13 |
22 | @foreach($uploaded_photos as $uploadedPhoto)
23 |
24 |
25 |
Image ID: {{$uploadedPhoto->id}}: {{$uploadedPhoto->image_title ?? "Untitled Photo"}}
26 |
27 |
28 | Uploaded {{$uploadedPhoto->created_at->diffForHumans()}}
29 |
30 |
31 |
32 |
33 |
34 | uploaded_images as $file_key => $file) {
38 | $id = 'uploaded_' . ($uploadedPhoto->id) . '_' . $file_key; ?>
39 |
40 |
41 |
42 | {{$file_key}} - {{$file['w']}} x {{$file['h']}}:
43 |
44 |
[link] / show
50 |
51 |
52 |
53 |
54 |
55 |
56 | Image URL
57 |
59 |
60 |
61 |
62 |
63 | img tag
64 | "}}'>
66 |
67 |
68 |
69 |
78 |
79 |
80 |
81 | @if($smallest)
82 |
90 |
91 | @else
92 |
93 | No image found
94 |
95 | @endif
96 |
97 |
98 |
99 | @endforeach
100 |
101 |
102 | {{ $uploaded_photos->links() }}
103 |
104 | @endsection
105 |
--------------------------------------------------------------------------------
/src/Views/blogetc_admin/imageupload/uploaded.blade.php:
--------------------------------------------------------------------------------
1 | @extends('blogetc_admin::layouts.admin_layout')
2 | @section('content')
3 | Admin - Upload Images
4 |
5 | Upload was successful.
6 |
7 | @forelse($images as $image)
8 |
9 |
{{ $image['filename'] }}
10 |
11 | {{ $image['w'] . 'x' . $image['h'] }}
12 |
13 |
14 |
15 |
19 |
20 |
22 |
"}}">
24 |
25 | @empty
26 |
27 | No image was processed
28 |
29 | @endforelse
30 | @endsection
31 |
--------------------------------------------------------------------------------
/src/Views/blogetc_admin/index.blade.php:
--------------------------------------------------------------------------------
1 | @extends("blogetc_admin::layouts.admin_layout")
2 | @section("content")
3 | Admin - Manage Blog Posts
4 |
5 | @forelse($posts as $post)
6 |
7 |
8 |
9 |
{{$post->subtitle}}
10 |
{{$post->html}}
11 |
12 | {!! $post->imageTag('thumbnail', false, 'float-right') !!}
13 |
14 |
15 | Author
16 | {{$post->author_string()}}
17 | Posted at
18 | {{$post->posted_at}}
19 | Is published?
20 |
21 | {!!($post->is_published ? "Yes" : 'No ')!!}
22 |
23 |
24 | Categories
25 |
26 | @if(count($post->categories))
27 | @foreach($post->categories as $category)
28 |
29 |
30 |
31 | {{$category->category_name}}
32 |
33 | @endforeach
34 | @else No Categories
35 | @endif
36 |
37 |
38 |
39 |
40 |
41 | @if($post->use_view_file)
42 |
Uses Custom Viewfile:
43 |
44 |
View file:
45 |
{{$post->use_view_file}}
46 | @php
47 | $viewfile = resource_path('views/custom_blog_posts/' . $post->use_view_file . '.blade.php');
48 | @endphp
49 |
50 |
Full filename:
51 |
52 |
53 | {{$viewfile}}
54 |
55 |
56 | @if(!file_exists($viewfile))
57 |
Warning! The custom view file does not exist. Create the
58 | file for this post to display correctly.
59 |
60 | @endif
61 |
62 |
63 | @endif
64 |
65 |
66 |
68 | View Post
69 |
70 |
71 | Edit Post
72 |
81 |
82 |
83 | @empty
84 | No posts to show you. Why don't you add one?
85 | @endforelse
86 |
87 |
88 | {{$posts->appends( [] )->links()}}
89 |
90 | @endsection
91 |
--------------------------------------------------------------------------------
/src/Views/blogetc_admin/layouts/admin_layout.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | BlogEtcPost Blog Admin - {{ config('app.name') }}
11 |
12 |
16 |
17 |
18 |
19 |
21 |
22 |
23 |
24 |
25 | @if(file_exists(public_path('blogetc_admin_css.css')))
26 |
27 | @else
28 |
29 | {{--Edited your css/app.css file? Uncomment these lines to use plain bootstrap:--}}
30 | {{-- --}}
31 | {{-- --}}
32 | @endif
33 |
34 |
35 |
36 |
37 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | @include("blogetc_admin::layouts.sidebar")
86 |
87 |
88 |
89 |
90 | @if (isset($errors) && count($errors))
91 |
92 |
Sorry, but there was an error:
93 |
94 | @foreach($errors->all() as $error)
95 | {{ $error }}
96 | @endforeach
97 |
98 |
99 | @endif
100 | {{--REPLACING THIS FILE WITH YOUR OWN LAYOUT FILE? Don't forget to include the following section!--}}
101 | @if(\WebDevEtc\BlogEtc\Helpers::hasFlashedMessage())
102 |
103 |
{{\WebDevEtc\BlogEtc\Helpers::pullFlashedMessage() }}
104 |
105 | @endif
106 |
107 | @yield('content')
108 |
109 |
110 |
111 |
112 |
113 |
114 |
117 |
118 |
119 | @if( config("blogetc.use_wysiwyg") && config("blogetc.echo_html") && (in_array( Request::route()->getName() ,[ 'blogetc.admin.create_post' , 'blogetc.admin.edit_post' ])))
120 |
121 |
122 |
127 | @endif
128 |
129 |
130 |
--------------------------------------------------------------------------------
/src/Views/blogetc_admin/layouts/sidebar.blade.php:
--------------------------------------------------------------------------------
1 |
2 | Welcome to the admin panel for your blog posts.
3 |
4 |
5 |
6 |
7 |
18 |
Overview of your posts
19 |
20 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | Comments
40 |
41 | @php
42 | $commentCount = Comment::withoutGlobalScopes()->count();
43 | @endphp
44 |
45 | {{'('.$commentCount.' '.str_plural('Comment', $commentCount).')'}}
46 |
47 |
48 |
Manage your comments
49 |
50 |
51 |
56 |
57 | where('approved', false)->count(); ?>
59 |
60 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
Categories
73 |
74 |
75 | {{ '(' . $postCount.' '.str_plural('Category', $postCount) . ')' }}
76 |
77 |
78 |
Blog post categories
79 |
91 |
92 |
93 |
94 |
95 | @if(config("blogetc.image_upload_enabled"))
96 |
97 |
114 |
115 | @endif
116 |
117 |
--------------------------------------------------------------------------------
/src/Views/blogetc_admin/posts/add_post.blade.php:
--------------------------------------------------------------------------------
1 | @extends('blogetc_admin::layouts.admin_layout')
2 | @section('content')
3 | Admin - Add post
4 |
5 |
10 | @endsection
11 |
--------------------------------------------------------------------------------
/src/Views/blogetc_admin/posts/deleted_post.blade.php:
--------------------------------------------------------------------------------
1 | @extends("blogetc_admin::layouts.admin_layout")
2 | @section("content")
3 |
10 |
11 | $image_size_info) {
14 | if (!$deletedPost->$image_size) {
15 | continue;
16 | }
17 | $images_to_delete[] = $image_size;
18 | }?>
19 |
20 | @if(count($images_to_delete))
21 | However, the following images were not deleted:
22 |
23 |
24 |
25 |
26 | Image/link
27 | Filename / filesize
28 | Full location
29 |
30 |
31 |
32 | @foreach($images_to_delete as $image_size)
33 |
34 |
35 | $image_size) }}"
36 | target="_blank" class="btn btn-primary m-1">
37 | view
38 |
39 |
40 | $image_size) }}"
41 | width="100" />
42 |
43 |
44 | {{$deletedPost->$image_size}}
45 | {{--check filesize returns something, so we don't divide by 0--}}
46 | @if(filesize(public_path(config("blogetc.blog_upload_dir","blog_images")."/".$deletedPost->$image_size)))
47 | ({{ (round(filesize(public_path(config("blogetc.blog_upload_dir","blog_images")."/".$deletedPost->$image_size)) / 1000 ,1)). " kb"}})
48 | @endif
49 |
50 |
51 | {{ public_path(config("blogetc.blog_upload_dir","blog_images")."/".$deletedPost->$image_size) }}
52 |
53 |
54 | @endforeach
55 |
56 |
57 |
58 | Please manually remove those files from the filesystem if desired.
59 |
60 | @endif
61 | @endsection
62 |
63 |
--------------------------------------------------------------------------------
/src/Views/blogetc_admin/posts/edit_post.blade.php:
--------------------------------------------------------------------------------
1 | @php
2 | /** @var \WebDevEtc\BlogEtc\Models\Post $post */
3 | @endphp
4 | @extends('blogetc_admin::layouts.admin_layout')
5 | @section('content')
6 |
11 |
12 |
18 | @endsection
19 |
--------------------------------------------------------------------------------
/src/routes.php:
--------------------------------------------------------------------------------
1 | ['web'], 'namespace' => '\WebDevEtc\BlogEtc\Controllers'], static function () {
4 | /* The main public facing blog routes - show all posts, view a category, rss feed, view a single post, also the add comment route */
5 | Route::group(['prefix' => config('blogetc.blog_prefix', 'blog')], static function () {
6 | Route::get('/', 'PostsController@index')->name('blogetc.index');
7 | Route::get('/search', 'PostsController@search')->name('blogetc.search');
8 | Route::get('/feed', 'BlogEtcRssFeedController@feed')->name('blogetc.feed');
9 | Route::get('/category/{categorySlug}', 'PostsController@showCategory')->name('blogetc.view_category');
10 | Route::get('/{blogPostSlug}', 'PostsController@show')->name('blogetc.single');
11 |
12 | Route::group(['middleware' => 'throttle:10,3'], static function () {
13 | Route::post('save_comment/{blogPostSlug}', 'CommentsController@store')->name('blogetc.comments.add_new_comment');
14 | });
15 | });
16 |
17 | /* Admin backend routes - CRUD for posts, categories, and approving/deleting submitted comments */
18 | Route::group(['prefix' => config('blogetc.admin_prefix', 'blog_admin')], static function () {
19 | Route::get('/', 'Admin\ManagePostsController@index')->name('blogetc.admin.index');
20 |
21 | Route::get('/add_post', 'Admin\ManagePostsController@create')->name('blogetc.admin.create_post');
22 | Route::post('/add_post', 'Admin\ManagePostsController@store')->name('blogetc.admin.store_post');
23 |
24 | Route::get('/edit_post/{blogPostId}', 'Admin\ManagePostsController@edit')->name('blogetc.admin.edit_post');
25 | Route::patch('/edit_post/{blogPostId}', 'Admin\ManagePostsController@update')->name('blogetc.admin.update_post');
26 |
27 | Route::group(['prefix' => 'image_uploads'], static function () {
28 | Route::get('/', 'Admin\ManageUploadsController@index')->name('blogetc.admin.images.all');
29 |
30 | Route::get('/upload', 'Admin\ManageUploadsController@create')->name('blogetc.admin.images.upload');
31 | Route::post('/upload', 'Admin\ManageUploadsController@store')->name('blogetc.admin.images.store');
32 |
33 | Route::get('/post/{postId}/delete-images', 'Admin\ManageUploadsController@deletePostImage')->name('blogetc.admin.images.delete-post-image');
34 | Route::delete('/post/{postId}/delete-images', 'Admin\ManageUploadsController@deletePostImageConfirmed')->name('blogetc.admin.images.delete-post-image-confirmed');
35 | });
36 |
37 | Route::delete('/delete_post/{blogPostId}', 'Admin\ManagePostsController@destroy')->name('blogetc.admin.destroy_post');
38 |
39 | Route::group(['prefix' => 'comments'], static function () {
40 | Route::get('/', 'Admin\ManageCommentsController@index')->name('blogetc.admin.comments.index');
41 | Route::patch('/{commentId}', 'Admin\ManageCommentsController@approve')->name('blogetc.admin.comments.approve');
42 |
43 | Route::delete('/{commentId}', 'Admin\ManageCommentsController@destroy')->name('blogetc.admin.comments.delete');
44 | });
45 |
46 | Route::group(['prefix' => 'categories'], static function () {
47 | Route::get('/', 'Admin\ManageCategoriesController@index')->name('blogetc.admin.categories.index');
48 |
49 | Route::get('/add_category', 'Admin\ManageCategoriesController@create')->name('blogetc.admin.categories.create_category');
50 | Route::post('/add_category', 'Admin\ManageCategoriesController@store')->name('blogetc.admin.categories.store_category');
51 |
52 | Route::get('/edit_category/{categoryId}', 'Admin\ManageCategoriesController@edit')->name('blogetc.admin.categories.edit_category');
53 | Route::patch('/edit_category/{categoryId}', 'Admin\ManageCategoriesController@update')->name('blogetc.admin.categories.update_category');
54 |
55 | Route::delete('/delete_category/{categoryId}', 'Admin\ManageCategoriesController@destroy')->name('blogetc.admin.categories.destroy_category');
56 | });
57 | });
58 | });
59 |
--------------------------------------------------------------------------------
/tests/Feature/Controllers/Admin/ManageCommentsControllerTest.php:
--------------------------------------------------------------------------------
1 | featureSetUp();
17 | }
18 |
19 | public function testNonLoggedInUserForbidden(): void
20 | {
21 | $response = $this->get(route('blogetc.admin.comments.index'));
22 | $response->assertUnauthorized();
23 | }
24 |
25 | public function testNonLoggedInUserForbiddenWithGate(): void
26 | {
27 | $this->setAdminGate();
28 | $response = $this->get(route('blogetc.admin.comments.index'));
29 | $response->assertUnauthorized();
30 | }
31 |
32 | public function testGatedAdminUserCanViewIndex(): void
33 | {
34 | $this->beAdminUserWithGate();
35 | $response = $this->get(route('blogetc.admin.comments.index'));
36 | $response->assertOk();
37 | }
38 |
39 | public function testGatedNonAdminUserCannotViewIndex(): void
40 | {
41 | $this->beNonAdminUserWithGate();
42 | $response = $this->get(route('blogetc.admin.comments.index'));
43 | $response->assertUnauthorized();
44 | }
45 |
46 | public function testLegacyAdminUserCanViewIndex(): void
47 | {
48 | $this->beLegacyAdminUser();
49 | $response = $this->get(route('blogetc.admin.comments.index'));
50 | $response->assertOk();
51 | }
52 |
53 | public function testLegacyNonAdminUserCannotViewIndex(): void
54 | {
55 | $this->beLegacyNonAdminUser();
56 | $response = $this->get(route('blogetc.admin.comments.index'));
57 | $response->assertUnauthorized();
58 | }
59 |
60 | public function testCommentsIndex(): void
61 | {
62 | $comment = factory(Comment::class)->create();
63 |
64 | $this->beLegacyAdminUser();
65 | $response = $this->get(route('blogetc.admin.comments.index'));
66 |
67 | $response->assertSee($comment->comment);
68 | }
69 |
70 | public function testApproveComment(): void
71 | {
72 | $this->beAdminUserWithGate();
73 | $comment = factory(Comment::class)->create(['approved'=> false]);
74 |
75 | $response = $this->patch(route('blogetc.admin.comments.approve', $comment->id));
76 |
77 | $response->assertSessionHasNoErrors()->assertRedirect();
78 | $this->assertDatabaseHas('blog_etc_comments', ['id' => $comment->id, 'approved' => true]);
79 | }
80 |
81 | public function testApproveNonExistingComment(): void
82 | {
83 | $this->beAdminUserWithGate();
84 | $response = $this->patch(route('blogetc.admin.comments.approve', 0));
85 | $response->assertNotFound();
86 | }
87 |
88 | public function testDenyComment(): void
89 | {
90 | $this->beAdminUserWithGate();
91 | $response = $this->delete(route('blogetc.admin.comments.delete', 0));
92 | $response->assertNotFound();
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/tests/Feature/Controllers/Admin/ManageUploadsControllerTest.php:
--------------------------------------------------------------------------------
1 | featureSetUp();
16 | }
17 |
18 | public function testNonLoggedInUserForbidden(): void
19 | {
20 | $response = $this->get(route('blogetc.admin.images.all'));
21 | $response->assertUnauthorized();
22 | }
23 |
24 | public function testNonLoggedInUserForbiddenWithGate(): void
25 | {
26 | $this->setAdminGate();
27 | $response = $this->get(route('blogetc.admin.images.all'));
28 | $response->assertUnauthorized();
29 | }
30 |
31 | public function testGatedAdminUserCanViewIndex(): void
32 | {
33 | $this->withoutExceptionHandling();
34 | $this->beAdminUserWithGate();
35 | $response = $this->get(route('blogetc.admin.images.all'));
36 | $response->assertOk();
37 | }
38 |
39 | public function testGatedNonAdminUserCannotViewIndex(): void
40 | {
41 | $this->beNonAdminUserWithGate();
42 | $response = $this->get(route('blogetc.admin.images.all'));
43 | $response->assertUnauthorized();
44 | }
45 |
46 | public function testLegacyAdminUserCanViewIndex(): void
47 | {
48 | $this->beLegacyAdminUser();
49 | $response = $this->get(route('blogetc.admin.images.all'));
50 | $response->assertOk();
51 | }
52 |
53 | public function testLegacyNonAdminUserCannotViewIndex(): void
54 | {
55 | $this->beLegacyNonAdminUser();
56 | $response = $this->get(route('blogetc.admin.images.all'));
57 | $response->assertUnauthorized();
58 | }
59 |
60 | // TODO test upload form & storing upload.
61 | }
62 |
--------------------------------------------------------------------------------
/tests/Feature/Controllers/CommentsControllerTest.php:
--------------------------------------------------------------------------------
1 | withoutExceptionHandling();
29 | $post = factory(Post::class)->create();
30 | $this->beLegacyAdminUser();
31 |
32 | $url = route('blogetc.comments.add_new_comment', $post->slug);
33 |
34 | $params = [
35 | 'comment' => $this->faker->sentence,
36 | 'author_name' => $this->faker->name,
37 | 'author_email' => $this->faker->safeEmail,
38 | 'author_website' => 'http://'.$this->faker->safeEmailDomain,
39 | ];
40 |
41 | $response = $this->postJson($url, $params);
42 |
43 | $this->assertSame(Response::HTTP_CREATED, $response->getStatusCode());
44 |
45 | $postResponse = $this->get(route('blogetc.single', $post->slug));
46 | $postResponse->assertSee($params['comment']);
47 | }
48 |
49 | /**
50 | * Test the store method for saving a new comment.
51 | */
52 | public function testDisabledCommentsStore(): void
53 | {
54 | config(['blogetc.comments.type_of_comments_to_show' => 'disabled']);
55 |
56 | $post = factory(Post::class)->create();
57 |
58 | $url = route('blogetc.comments.add_new_comment', $post->slug);
59 |
60 | $params = [
61 | 'comment' => $this->faker->sentence,
62 | 'author_name' => $this->faker->name,
63 | 'author_email' => $this->faker->safeEmail,
64 | 'author_website' => 'http://'.$this->faker->safeEmailDomain,
65 | ];
66 |
67 | $response = $this->postJson($url, $params);
68 |
69 | $response->assertForbidden();
70 |
71 | $this->assertDatabaseMissing('blog_etc_comments', ['comment' => $params['comment']]);
72 | }
73 |
74 | /**
75 | * Setup the feature test.
76 | */
77 | protected function setUp(): void
78 | {
79 | parent::setUp();
80 |
81 | $this->featureSetUp();
82 |
83 | config(['blogetc.comments.type_of_comments_to_show' => 'built_in']);
84 | config(['blogetc.comments.auto_approve_comments' => true]);
85 | config(['blogetc.captcha.captcha_enabled' => false]);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/tests/Feature/Controllers/FeedControllerTest.php:
--------------------------------------------------------------------------------
1 | get(route('blogetc.feed'));
20 |
21 | $response->assertOk()
22 | ->assertHeader('content-type', 'application/atom+xml; charset=utf-8');
23 | }
24 |
25 | /**
26 | * Test the feed includes a recent post.
27 | */
28 | public function testIncludesRecentPost(): void
29 | {
30 | $post = factory(Post::class)->create();
31 |
32 | $response = $this->get(route('blogetc.feed'));
33 |
34 | $response->assertOk()
35 | ->assertSee($post->title);
36 | }
37 |
38 | /**
39 | * Test the feed does not include posts not published.
40 | */
41 | public function testExcludesUnpublishedPosts(): void
42 | {
43 | $post = factory(Post::class)->state('not_published')->create();
44 |
45 | $response = $this->get(route('blogetc.feed'));
46 |
47 | $response->assertOk()
48 | ->assertDontSee($post->title);
49 | }
50 |
51 | /**
52 | * Test the feed does not include posts not published.
53 | */
54 | public function testExcludesFuturePosts(): void
55 | {
56 | $post = factory(Post::class)->state('in_future')->create();
57 |
58 | $response = $this->get(route('blogetc.feed'));
59 |
60 | $response->assertOk()
61 | ->assertDontSee($post->title);
62 | }
63 |
64 | /**
65 | * Test works for logged in users.
66 | */
67 | public function testLoggedIn(): void
68 | {
69 | $this->beLegacyNonAdminUser();
70 |
71 | $response = $this->get(route('blogetc.feed'));
72 |
73 | $response->assertOk()
74 | ->assertHeader('content-type', 'application/atom+xml; charset=utf-8');
75 | }
76 |
77 | /**
78 | * Test that logged in users which pass the blog-etc-admin gate can see unpublished posts.
79 | */
80 | public function testLoggedInCanSeeUnpublishedPosts(): void
81 | {
82 | $this->beLegacyAdminUser();
83 |
84 | $post = factory(Post::class)->state('not_published')->create();
85 |
86 | $response = $this->get(route('blogetc.feed'));
87 |
88 | $response->assertOk()->assertSee($post->title);
89 | }
90 |
91 | /**
92 | * Test that logged in users which pass the blog-etc-admin gate can see unpublished posts.
93 | */
94 | public function testLoggedInCanSeeFuturePosts(): void
95 | {
96 | $this->beLegacyAdminUser();
97 |
98 | $post = factory(Post::class)->state('in_future')->create();
99 |
100 | $response = $this->get(route('blogetc.feed'));
101 |
102 | $response->assertOk()
103 | ->assertSee($post->title);
104 | }
105 |
106 | /**
107 | * RSS is cached.
108 | * If viewing it with an admin user then a guest user, the guest user should not see the cached admin results.
109 | */
110 | public function testLoggedInCacheDoesNotShowToNonLoggedInUsers(): void
111 | {
112 | $this->beLegacyAdminUser();
113 |
114 | $post = factory(Post::class)->state('not_published')->create();
115 |
116 | $adminResponse = $this->get(route('blogetc.feed'));
117 |
118 | $adminResponse->assertOk()
119 | ->assertSee($post->title);
120 |
121 | Auth::logout();
122 |
123 | $guestResponse = $this->get(route('blogetc.feed'));
124 | $guestResponse->assertOk()
125 | ->assertDontSee($post->title);
126 | }
127 |
128 | /**
129 | * Setup the feature test.
130 | */
131 | protected function setUp(): void
132 | {
133 | parent::setUp();
134 |
135 | $this->featureSetUp();
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/tests/Feature/Repositories/CategoriesRepositoryTest.php:
--------------------------------------------------------------------------------
1 | featureSetUp();
26 | Category::truncate();
27 |
28 | $this->categoriesRepository = resolve(CategoriesRepository::class);
29 | }
30 |
31 | public function testIndexPaginated()
32 | {
33 | factory(Category::class, 30)->create();
34 |
35 | $response = $this->categoriesRepository->indexPaginated(25);
36 |
37 | $this->assertSame(30, $response->total());
38 | $this->assertSame(2, $response->lastPage());
39 | }
40 |
41 | public function testFind()
42 | {
43 | $category = factory(Category::class)->create();
44 | $response = $this->categoriesRepository->find($category->id);
45 | $this->assertTrue($category->is($response));
46 | }
47 |
48 | public function testFindNonExisting()
49 | {
50 | $this->expectException(CategoryNotFoundException::class);
51 |
52 | $this->categoriesRepository->find(0);
53 | }
54 |
55 | public function testFindBySlug()
56 | {
57 | $category = factory(Category::class)->create();
58 | $response = $this->categoriesRepository->findBySlug($category->slug);
59 | $this->assertTrue($category->is($response));
60 | }
61 |
62 | public function testFindBySlugNonExisting()
63 | {
64 | $this->expectException(CategoryNotFoundException::class);
65 |
66 | $this->categoriesRepository->findBySlug('non-existing');
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/tests/Feature/Repositories/CommentsRepositoryTest.php:
--------------------------------------------------------------------------------
1 | featureSetUp();
26 | Comment::truncate();
27 |
28 | $this->commentsRepository = resolve(CommentsRepository::class);
29 | }
30 |
31 | public function testApprove()
32 | {
33 | $comment = factory(Comment::class)->create(['approved' => false]);
34 |
35 | $this->commentsRepository->approve($comment->id);
36 |
37 | $comment->refresh();
38 |
39 | $this->assertTrue($comment->fresh()->approved);
40 | }
41 |
42 | /**
43 | * Approving an already approved comment should still work.
44 | */
45 | public function testApproveAlreadyApproved()
46 | {
47 | $comment = factory(Comment::class)->create(['approved' => true]);
48 | $this->commentsRepository->approve($comment->id);
49 | $this->assertTrue($comment->fresh()->approved);
50 | }
51 |
52 | public function testApproveNonExistingComment()
53 | {
54 | $this->expectException(CommentNotFoundException::class);
55 | $this->commentsRepository->approve(0);
56 | }
57 |
58 | public function testFind()
59 | {
60 | $comment = factory(Comment::class)->create();
61 |
62 | $response = $this->commentsRepository->find($comment->id);
63 |
64 | $this->assertTrue($comment->is($response));
65 | }
66 |
67 | public function testFindApproved()
68 | {
69 | $comment = factory(Comment::class)->create(['approved' => true]);
70 | $response = $this->commentsRepository->find($comment->id, true);
71 | $this->assertTrue($comment->is($response));
72 | }
73 |
74 | public function testFindNonApproved()
75 | {
76 | $comment = factory(Comment::class)->create(['approved' => false]);
77 | $this->expectException(CommentNotFoundException::class);
78 | $this->commentsRepository->find($comment->id, true);
79 | }
80 |
81 | public function testFindNonExisting()
82 | {
83 | $this->expectException(CommentNotFoundException::class);
84 | $response = $this->commentsRepository->find(0);
85 | }
86 |
87 | public function testCreate()
88 | {
89 | $post = factory(Post::class)->create();
90 |
91 | $commentText = $this->faker->sentence;
92 | $this->commentsRepository->create($post, ['comment' => $commentText], '127.0.0.1', null, null, null, false);
93 |
94 | $this->assertDatabaseHas('blog_etc_comments', ['comment' => $commentText, 'blog_etc_post_id' => $post->id, 'approved' => false]);
95 | }
96 |
97 | public function testCreateAutoApproved()
98 | {
99 | $post = factory(Post::class)->create();
100 |
101 | $commentText = $this->faker->sentence;
102 | $this->commentsRepository->create($post, ['comment' => $commentText], '127.0.0.1', null, null, null, true);
103 |
104 | $this->assertDatabaseHas('blog_etc_comments', ['comment' => $commentText, 'approved' => true]);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/tests/Feature/Services/CaptchaServiceTest.php:
--------------------------------------------------------------------------------
1 | featureSetUp();
23 |
24 | $this->captchaService = resolve(CaptchaService::class);
25 | }
26 |
27 | public function testGetCaptchaObjectDisabled(): void
28 | {
29 | Config::set('blogetc.captcha.captcha_enabled', false);
30 |
31 | $result = $this->captchaService->getCaptchaObject();
32 |
33 | $this->assertNull($result);
34 | }
35 |
36 | public function testGetCaptchaObjectEnabled(): void
37 | {
38 | Config::set('blogetc.captcha.captcha_enabled', true);
39 | Config::set('blogetc.captcha.captcha_type', Basic::class);
40 |
41 | $result = $this->captchaService->getCaptchaObject();
42 |
43 | $this->assertInstanceOf(Basic::class, $result);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/Feature/Services/CategoriesServiceTest.php:
--------------------------------------------------------------------------------
1 | featureSetUp();
23 |
24 | $this->categoriesService = resolve(CategoriesService::class);
25 |
26 | Category::truncate();
27 | }
28 |
29 | public function testIndexPaginated(): void
30 | {
31 | factory(Category::class, 25)->create();
32 | $result = $this->categoriesService->indexPaginated(10);
33 |
34 | $this->assertSame(25, $result->total());
35 | $this->assertSame(3, $result->lastPage());
36 | }
37 |
38 | public function testFindBySlug(): void
39 | {
40 | $category = factory(Category::class)->create();
41 |
42 | $result = $this->categoriesService->findBySlug($category->slug);
43 |
44 | $this->assertTrue($category->is($result));
45 | }
46 |
47 | public function testFindBySlugNotFound(): void
48 | {
49 | $this->expectException(CategoryNotFoundException::class);
50 | $this->categoriesService->findBySlug('not-found');
51 | }
52 |
53 | public function testCreate(): void
54 | {
55 | $attributes = factory(Category::class)->make()->toArray();
56 |
57 | $result = $this->categoriesService->create($attributes);
58 |
59 | $this->assertInstanceOf(Category::class, $result);
60 |
61 | $this->assertDatabaseHas('blog_etc_categories', $attributes);
62 | }
63 |
64 | public function testUpdate(): void
65 | {
66 | $category = factory(Category::class)->create();
67 |
68 | $updatedCategory = $this->categoriesService->update($category->id, ['category_name' => 'updated']);
69 |
70 | $this->assertSame('updated', $updatedCategory->category_name);
71 |
72 | $this->assertDatabaseHas('blog_etc_categories', ['id' => $category->id, 'category_name' => 'updated']);
73 | }
74 |
75 | public function testUpdateNotFound(): void
76 | {
77 | $this->expectException(CategoryNotFoundException::class);
78 |
79 | $this->categoriesService->update(0, ['category_name' => 'updated']);
80 | }
81 |
82 | public function testFind(): void
83 | {
84 | $category = factory(Category::class)->create();
85 |
86 | $result = $this->categoriesService->find($category->id);
87 |
88 | $this->assertTrue($category->is($result));
89 | }
90 |
91 | public function testFindNotFound(): void
92 | {
93 | $this->expectException(CategoryNotFoundException::class);
94 | $this->categoriesService->find(0);
95 | }
96 |
97 | public function testDelete(): void
98 | {
99 | $category = factory(Category::class)->create();
100 |
101 | $this->categoriesService->delete($category->id);
102 |
103 | $this->assertDatabaseMissing('blog_etc_categories', ['id' => $category->id]);
104 | }
105 |
106 | public function testDeleteNotFound(): void
107 | {
108 | $this->expectException(CategoryNotFoundException::class);
109 |
110 | $this->categoriesService->delete(0);
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/tests/Feature/Services/CommentsServiceTest.php:
--------------------------------------------------------------------------------
1 | featureSetUp();
24 |
25 | $this->commentsService = resolve(CommentsService::class);
26 | Comment::truncate();
27 | }
28 |
29 | public function testApprove()
30 | {
31 | $comment = factory(Comment::class)->create(['approved' => false]);
32 |
33 | $this->commentsService->approve($comment->id);
34 |
35 | $comment->refresh();
36 |
37 | $this->assertTrue($comment->fresh()->approved);
38 | }
39 |
40 | /**
41 | * Approving an already approved comment should still work.
42 | */
43 | public function testApproveAlreadyApproved()
44 | {
45 | $comment = factory(Comment::class)->create(['approved' => true]);
46 | $this->commentsService->approve($comment->id);
47 | $this->assertTrue($comment->fresh()->approved);
48 | }
49 |
50 | public function testApproveNonExistingComment()
51 | {
52 | $this->expectException(CommentNotFoundException::class);
53 | $this->commentsService->approve(0);
54 | }
55 |
56 | public function testFind()
57 | {
58 | $comment = factory(Comment::class)->create();
59 |
60 | $response = $this->commentsService->find($comment->id);
61 |
62 | $this->assertTrue($comment->is($response));
63 | }
64 |
65 | public function testFindApproved()
66 | {
67 | $comment = factory(Comment::class)->create(['approved' => true]);
68 | $response = $this->commentsService->find($comment->id, true);
69 | $this->assertTrue($comment->is($response));
70 | }
71 |
72 | public function testFindNonApproved()
73 | {
74 | $comment = factory(Comment::class)->create(['approved' => false]);
75 | $this->expectException(CommentNotFoundException::class);
76 | $this->commentsService->find($comment->id, true);
77 | }
78 |
79 | public function testFindNonExisting()
80 | {
81 | $this->expectException(CommentNotFoundException::class);
82 | $this->commentsService->find(0);
83 | }
84 |
85 | public function testCreate()
86 | {
87 | $post = factory(Post::class)->create();
88 |
89 | $commentText = $this->faker->sentence;
90 | $this->commentsService->create($post, ['comment' => $commentText], '127.0.0.1', null);
91 |
92 | $this->assertDatabaseHas('blog_etc_comments', ['comment' => $commentText, 'blog_etc_post_id' => $post->id, 'approved' => false]);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/tests/Feature/Services/FeedServiceTest.php:
--------------------------------------------------------------------------------
1 | create();
24 |
25 | $response = $this->feedService->getFeed($feed, 'rss');
26 |
27 | $this->assertInstanceOf(Response::class, $response);
28 | }
29 |
30 | /**
31 | * Setup the feature test.
32 | */
33 | protected function setUp(): void
34 | {
35 | parent::setUp();
36 |
37 | $this->featureSetUp();
38 | Post::truncate();
39 |
40 | $this->feedService = resolve(FeedService::class);
41 | }
42 |
43 | // Todo: test content, test logged in vs logged out, test cache, test empty posts
44 | }
45 |
--------------------------------------------------------------------------------
/tests/Feature/Services/PostsServiceTest.php:
--------------------------------------------------------------------------------
1 | featureSetUp();
25 |
26 | $this->postsService = resolve(PostsService::class);
27 | Post::truncate();
28 | }
29 |
30 | public function testIndexPaginated()
31 | {
32 | factory(Post::class, 25)->create();
33 |
34 | $response = $this->postsService->indexPaginated(10, null);
35 |
36 | $this->assertSame(25, $response->total());
37 | $this->assertSame(3, $response->lastPage());
38 | }
39 |
40 | public function testIndexPaginatedUnpublished()
41 | {
42 | factory(Post::class)->create(['is_published' => false]);
43 | factory(Post::class)->create(['posted_at' => Carbon::now()->addHour()]);
44 |
45 | $response = $this->postsService->indexPaginated(10, null);
46 |
47 | $this->assertSame(0, $response->total());
48 | }
49 |
50 | public function testIndexPaginatedCategoryWithNoPosts()
51 | {
52 | $category = factory(Category::class)->create();
53 |
54 | factory(Post::class, 25)->create();
55 |
56 | $response = $this->postsService->indexPaginated(10, $category->id);
57 |
58 | $this->assertSame(0, $response->total());
59 | }
60 |
61 | public function testRssItems()
62 | {
63 | factory(Post::class, 11)->create();
64 |
65 | $response = $this->postsService->rssItems();
66 |
67 | $this->assertCount(10, $response);
68 | }
69 |
70 | public function testRssItemsDoesNotIncludeUnpublished()
71 | {
72 | factory(Post::class)->create(['is_published' => false]);
73 | factory(Post::class)->create(['posted_at' => Carbon::now()->addHour()]);
74 |
75 | $response = $this->postsService->rssItems();
76 |
77 | $this->assertEmpty($response);
78 | }
79 |
80 | public function testSearch(): void
81 | {
82 | [$post1, $post2] = factory(Post::class, 2)->create(['title' => 'an example title']);
83 |
84 | $response = $this->postsService->search($post1->title);
85 |
86 | $this->assertCount(2, $response);
87 |
88 | $this->assertTrue($response[0]->is($post1));
89 | }
90 |
91 | public function testSearchDoesNotIncludeUnpublished(): void
92 | {
93 | factory(Post::class)->create(['title' => 'test-unpublished', 'is_published' => false]);
94 | factory(Post::class)->create(['title' => 'test-unpublished', 'posted_at' => Carbon::now()->addDay()]);
95 | $published = factory(Post::class)->create(['title' => 'test-unpublished']);
96 |
97 | $response = $this->postsService->search('test-unpublished');
98 |
99 | $this->assertCount(1, $response);
100 |
101 | $this->assertTrue($response[0]->is($published));
102 | }
103 |
104 | public function testFindBySlug(): void
105 | {
106 | $post = factory(Post::class)->create();
107 |
108 | $response = $this->postsService->findBySlug($post->slug);
109 |
110 | $this->assertTrue($post->is($response));
111 | }
112 |
113 | public function testFindBySlugFails(): void
114 | {
115 | $this->expectException(PostNotFoundException::class);
116 | $this->postsService->findBySlug('invalid');
117 | }
118 |
119 | public function testFindBySlugFailsWhenEmpty(): void
120 | {
121 | $this->expectException(PostNotFoundException::class);
122 | $this->postsService->findBySlug('');
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/tests/views/layouts/app.blade.php:
--------------------------------------------------------------------------------
1 | Test layout app - in case main layouts.app does not exist
2 |
3 | @yield('content')
4 |
--------------------------------------------------------------------------------
Add a comment
10 | 91 |