├── .github └── workflows │ └── tests.yml ├── .gitignore ├── BulkActions ├── Comments.php └── Posts.php ├── Config ├── my-blog.php ├── search-string.php └── type.php ├── Console └── Inspire.php ├── Database ├── Factories │ ├── Comment.php │ └── Post.php ├── Migrations │ ├── 2021_06_30_000000_my_blog_v1.php │ └── 2021_09_07_000000_my_blog_v110.php └── Seeds │ ├── Categories.php │ ├── Dashboards.php │ ├── EmailTemplates.php │ ├── Install.php │ └── Reports.php ├── Exports ├── Comments.php └── Posts.php ├── Http ├── Controllers │ ├── Api │ │ ├── Comments.php │ │ └── Posts.php │ ├── Comments.php │ ├── Portal │ │ ├── Comments.php │ │ └── Posts.php │ └── Posts.php ├── Requests │ ├── Comment.php │ └── Post.php └── Resources │ ├── Comment.php │ └── Post.php ├── Imports └── Posts.php ├── Jobs ├── CreateComment.php ├── CreatePost.php ├── DeleteComment.php ├── DeletePost.php ├── UpdateComment.php └── UpdatePost.php ├── Listeners ├── AddAuthorsToReport.php ├── AddCategoriesToReport.php ├── AddPostsToReport.php ├── AddToAdminMenu.php ├── AddToNewwMenu.php ├── AddToPortalMenu.php └── FinishInstallation.php ├── Models ├── Comment.php └── Post.php ├── Notifications └── Comment.php ├── Observers └── Comment.php ├── Providers ├── Event.php ├── Main.php └── Observer.php ├── README.md ├── Reports ├── CommentSummary.php └── PostSummary.php ├── Resources ├── assets │ ├── js │ │ ├── comments.js │ │ └── posts.js │ ├── posts.xlsx │ └── sass │ │ └── app.scss ├── lang │ └── en-GB │ │ ├── email_templates.php │ │ ├── general.php │ │ ├── notifications.php │ │ ├── reports.php │ │ ├── settings.php │ │ └── widgets.php └── views │ ├── comments │ ├── create.blade.php │ ├── edit.blade.php │ └── index.blade.php │ ├── portal │ └── posts │ │ ├── index.blade.php │ │ └── show.blade.php │ ├── posts │ ├── create.blade.php │ ├── edit.blade.php │ ├── index.blade.php │ └── show.blade.php │ └── widgets │ ├── latest_comments.blade.php │ ├── latest_posts.blade.php │ └── top_authors.blade.php ├── Routes ├── admin.php ├── api.php └── portal.php ├── Tests └── Feature │ ├── AdminMenuTest.php │ ├── CommentsTest.php │ ├── PortalMenuTest.php │ └── PostsTest.php ├── Widgets ├── CommentsByPost.php ├── LatestComments.php ├── LatestPosts.php ├── PostsByCategory.php └── TopAuthors.php ├── composer.json ├── mix-manifest.json ├── module.json ├── package-lock.json ├── package.json └── webpack.mix.js /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: '0 0 * * *' 8 | workflow_dispatch: 9 | 10 | jobs: 11 | tests: 12 | 13 | name: PHP ${{ matrix.php }} 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | php: ['7.3', '7.4'] 20 | 21 | steps: 22 | - name: Checkout Akaunting 23 | uses: actions/checkout@v2 24 | with: 25 | repository: akaunting/akaunting 26 | 27 | - name: Checkout My Blog module 28 | uses: actions/checkout@v2 29 | with: 30 | path: modules/MyBlog 31 | 32 | - name: Cache Composer 33 | uses: actions/cache@v1 34 | with: 35 | path: ~/.composer/cache/files 36 | key: php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} 37 | 38 | - name: Setup PHP 39 | uses: shivammathur/setup-php@v2 40 | with: 41 | php-version: ${{ matrix.php }} 42 | extensions: bcmath, ctype, dom, fileinfo, intl, gd, json, mbstring, pdo, pdo_sqlite, openssl, sqlite, xml, zip 43 | coverage: none 44 | 45 | - name: Copy .env 46 | run: cp .env.testing .env 47 | 48 | - name: Install Composer 49 | run: cd modules/MyBlog ; composer test ; cd ../.. ; composer test 50 | 51 | - name: Execute tests 52 | run: php artisan test --parallel 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.vscode 3 | /.vagrant 4 | /node_modules 5 | /Resources/assets/js/*.min.js 6 | /storage/*.keyr 7 | /vendor 8 | -------------------------------------------------------------------------------- /BulkActions/Comments.php: -------------------------------------------------------------------------------- 1 | 'my-blog', 18 | 'type' => 'comments', 19 | ]; 20 | 21 | public $actions = [ 22 | 'delete' => [ 23 | 'name' => 'general.delete', 24 | 'message' => 'bulk_actions.message.delete', 25 | 'permission' => 'delete-my-blog-comments', 26 | ], 27 | 'export' => [ 28 | 'name' => 'general.export', 29 | 'message' => 'bulk_actions.message.export', 30 | 'type' => 'download', 31 | ], 32 | ]; 33 | 34 | public function destroy($request) 35 | { 36 | $comments = $this->getSelectedRecords($request); 37 | 38 | foreach ($comments as $comment) { 39 | try { 40 | $this->dispatch(new DeleteComment($comment)); 41 | } catch (\Exception $e) { 42 | flash($e->getMessage())->error()->important(); 43 | } 44 | } 45 | } 46 | 47 | public function export($request) 48 | { 49 | $selected = $this->getSelectedInput($request); 50 | 51 | return $this->exportExcel(new Export($selected), trans_choice('my-blog::general.comments', 2)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /BulkActions/Posts.php: -------------------------------------------------------------------------------- 1 | 'my-blog', 18 | 'type' => 'posts', 19 | ]; 20 | 21 | public $actions = [ 22 | 'enable' => [ 23 | 'name' => 'general.enable', 24 | 'message' => 'bulk_actions.message.enable', 25 | 'permission' => 'update-my-blog-posts', 26 | ], 27 | 'disable' => [ 28 | 'name' => 'general.disable', 29 | 'message' => 'bulk_actions.message.disable', 30 | 'permission' => 'update-my-blog-posts', 31 | ], 32 | 'delete' => [ 33 | 'name' => 'general.delete', 34 | 'message' => 'bulk_actions.message.delete', 35 | 'permission' => 'delete-my-blog-posts', 36 | ], 37 | 'export' => [ 38 | 'name' => 'general.export', 39 | 'message' => 'bulk_actions.message.export', 40 | 'type' => 'download', 41 | ], 42 | ]; 43 | 44 | public function destroy($request) 45 | { 46 | $posts = $this->getSelectedRecords($request, 'comments'); 47 | 48 | foreach ($posts as $post) { 49 | try { 50 | $this->dispatch(new DeletePost($post)); 51 | } catch (\Exception $e) { 52 | flash($e->getMessage())->error()->important(); 53 | } 54 | } 55 | } 56 | 57 | public function export($request) 58 | { 59 | $selected = $this->getSelectedInput($request); 60 | 61 | return $this->exportExcel(new Export($selected), trans_choice('my-blog::general.posts', 2)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Config/my-blog.php: -------------------------------------------------------------------------------- 1 | env('MY_BLOG_EXAMPLE_KEY'), 6 | 7 | ]; 8 | -------------------------------------------------------------------------------- /Config/search-string.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'columns' => [ 7 | 'id', 8 | 'name' => ['searchable' => true], 9 | 'description' => ['searchable' => true], 10 | 'category_id' => [ 11 | 'route' => ['categories.index', 'search=type:post'], 12 | ], 13 | 'enabled' => ['boolean' => true], 14 | ], 15 | ], 16 | 17 | 'Modules\MyBlog\Models\Comment' => [ 18 | 'columns' => [ 19 | 'id', 20 | 'description' => ['searchable' => true], 21 | 'post_id' => [ 22 | 'route' => 'my-blog.posts.index', 23 | ], 24 | ], 25 | ], 26 | 27 | ]; 28 | -------------------------------------------------------------------------------- /Config/type.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'post' => [ 8 | 'alias' => 'my-blog', 9 | 'translation' => [ 10 | 'prefix' => 'general', 11 | ], 12 | ], 13 | ], 14 | 15 | ]; 16 | -------------------------------------------------------------------------------- /Console/Inspire.php: -------------------------------------------------------------------------------- 1 | info($quote); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Database/Factories/Comment.php: -------------------------------------------------------------------------------- 1 | $this->company->id, 27 | 'post_id' => Post::enabled()->get()->random(1)->pluck('id')->first(), 28 | 'description' => $this->faker->text(100), 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Database/Factories/Post.php: -------------------------------------------------------------------------------- 1 | $this->company->id, 26 | 'name' => $this->faker->text(15), 27 | 'description' => $this->faker->text(100), 28 | 'enabled' => $this->faker->boolean ? 1 : 0, 29 | ]; 30 | } 31 | 32 | /** 33 | * Indicate that the model is enabled. 34 | * 35 | * @return \Illuminate\Database\Eloquent\Factories\Factory 36 | */ 37 | public function enabled() 38 | { 39 | return $this->state([ 40 | 'enabled' => 1, 41 | ]); 42 | } 43 | 44 | /** 45 | * Indicate that the model is disabled. 46 | * 47 | * @return \Illuminate\Database\Eloquent\Factories\Factory 48 | */ 49 | public function disabled() 50 | { 51 | return $this->state([ 52 | 'enabled' => 0, 53 | ]); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Database/Migrations/2021_06_30_000000_my_blog_v1.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->integer('company_id'); 19 | $table->string('name'); 20 | $table->text('description'); 21 | $table->integer('category_id')->nullable(); 22 | $table->boolean('enabled')->default(1); 23 | $table->unsignedInteger('created_by')->nullable(); 24 | $table->timestamps(); 25 | $table->softDeletes(); 26 | 27 | $table->index('company_id'); 28 | }); 29 | 30 | Schema::create('my_blog_comments', function (Blueprint $table) { 31 | $table->increments('id'); 32 | $table->integer('company_id'); 33 | $table->integer('post_id'); 34 | $table->text('description'); 35 | $table->unsignedInteger('created_by')->nullable(); 36 | $table->timestamps(); 37 | $table->softDeletes(); 38 | 39 | $table->index('company_id'); 40 | }); 41 | } 42 | 43 | /** 44 | * Reverse the migrations. 45 | * 46 | * @return void 47 | */ 48 | public function down() 49 | { 50 | Schema::dropIfExists('posts'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Database/Migrations/2021_09_07_000000_my_blog_v110.php: -------------------------------------------------------------------------------- 1 | string('created_from', 100)->nullable()->after('enabled'); 18 | }); 19 | 20 | Schema::table('my_blog_comments', function (Blueprint $table) { 21 | $table->string('created_from', 100)->nullable()->after('description'); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | // 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Database/Seeds/Categories.php: -------------------------------------------------------------------------------- 1 | create(); 19 | 20 | Model::reguard(); 21 | } 22 | 23 | private function create() 24 | { 25 | $company_id = $this->command->argument('company'); 26 | 27 | $categories = [ 28 | [ 29 | 'company_id' => $company_id, 30 | 'name' => trans('my-blog::general.demo.categories.php'), 31 | 'type' => 'post', 32 | 'color' => '#55588b', 33 | 'enabled' => '1', 34 | ], 35 | [ 36 | 'company_id' => $company_id, 37 | 'name' => trans('my-blog::general.demo.categories.laravel'), 38 | 'type' => 'post', 39 | 'color' => '#de4b4b', 40 | 'enabled' => '1', 41 | ], 42 | ]; 43 | 44 | foreach ($categories as $category) { 45 | $category['created_from'] = 'my-blog::seed'; 46 | 47 | $this->dispatch(new CreateCategory($category)); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Database/Seeds/Dashboards.php: -------------------------------------------------------------------------------- 1 | create(); 24 | 25 | Model::reguard(); 26 | } 27 | 28 | private function create() 29 | { 30 | $company_id = $this->command->argument('company'); 31 | 32 | $this->dispatch(new CreateDashboard([ 33 | 'company_id' => $company_id, 34 | 'name' => trans('my-blog::general.name'), 35 | 'all_users' => true, 36 | 'default_widgets' => 'my-blog', 37 | 'created_from' => 'my-blog::seed', 38 | ])); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Database/Seeds/EmailTemplates.php: -------------------------------------------------------------------------------- 1 | create(); 19 | 20 | Model::reguard(); 21 | } 22 | 23 | private function create() 24 | { 25 | $company_id = $this->command->argument('company'); 26 | 27 | $templates = [ 28 | [ 29 | 'alias' => 'comment_new_author', 30 | 'class' => 'Modules\MyBlog\Notifications\Comment', 31 | 'name' => 'my-blog::settings.email.templates.comment_new_author', 32 | ], 33 | [ 34 | 'alias' => 'comment_delete_author', 35 | 'class' => 'Modules\MyBlog\Notifications\Comment', 36 | 'name' => 'my-blog::settings.email.templates.comment_delete_author', 37 | ], 38 | ]; 39 | 40 | foreach ($templates as $template) { 41 | $this->dispatch(new CreateEmailTemplate([ 42 | 'company_id' => $company_id, 43 | 'alias' => $template['alias'], 44 | 'class' => $template['class'], 45 | 'name' => $template['name'], 46 | 'subject' => trans('my-blog::email_templates.' . $template['alias'] . '.subject'), 47 | 'body' => trans('my-blog::email_templates.' . $template['alias'] . '.body'), 48 | 'created_from' => 'my-blog::seed', 49 | ])); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Database/Seeds/Install.php: -------------------------------------------------------------------------------- 1 | call(Categories::class); 20 | $this->call(Dashboards::class); 21 | $this->call(EmailTemplates::class); 22 | $this->call(Reports::class); 23 | 24 | Model::reguard(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Database/Seeds/Reports.php: -------------------------------------------------------------------------------- 1 | create(); 24 | 25 | Model::reguard(); 26 | } 27 | 28 | private function create() 29 | { 30 | $company_id = $this->command->argument('company'); 31 | 32 | $rows = [ 33 | [ 34 | 'company_id' => $company_id, 35 | 'class' => 'Modules\MyBlog\Reports\PostSummary', 36 | 'name' => trans('my-blog::reports.post_name'), 37 | 'description' => trans('my-blog::reports.post_description'), 38 | 'settings' => ['group' => 'category', 'period' => 'monthly'], 39 | ], 40 | [ 41 | 'company_id' => $company_id, 42 | 'class' => 'Modules\MyBlog\Reports\CommentSummary', 43 | 'name' => trans('my-blog::reports.comment_name'), 44 | 'description' => trans('my-blog::reports.comment_description'), 45 | 'settings' => ['group' => 'post', 'period' => 'monthly'], 46 | ], 47 | ]; 48 | 49 | foreach ($rows as $row) { 50 | $row['created_from'] = 'core::seed'; 51 | 52 | $this->dispatch(new CreateReport($row)); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Exports/Comments.php: -------------------------------------------------------------------------------- 1 | collectForExport($this->ids); 13 | } 14 | 15 | public function map($model): array 16 | { 17 | $model->author_name = $model->owner->name; 18 | $model->post_name = $model->post->name; 19 | 20 | return parent::map($model); 21 | } 22 | 23 | public function fields(): array 24 | { 25 | return [ 26 | 'post_name', 27 | 'description', 28 | 'author_name', 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Exports/Posts.php: -------------------------------------------------------------------------------- 1 | collectForExport($this->ids); 13 | } 14 | 15 | public function map($model): array 16 | { 17 | $model->author_name = $model->owner->name; 18 | $model->category_name = $model->category->name; 19 | 20 | return parent::map($model); 21 | } 22 | 23 | public function fields(): array 24 | { 25 | return [ 26 | 'name', 27 | 'description', 28 | 'author_name', 29 | 'category_name', 30 | 'enabled', 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Http/Controllers/Api/Comments.php: -------------------------------------------------------------------------------- 1 | dispatch(new CreateComment($request)); 47 | 48 | return $this->created(route('api.my-blog.comments.show', $comment->id), new Resource($comment)); 49 | } 50 | 51 | /** 52 | * Update the specified resource in storage. 53 | * 54 | * @param $comment 55 | * @param $request 56 | * @return \Illuminate\Http\JsonResponse 57 | */ 58 | public function update(Comment $comment, Request $request) 59 | { 60 | $comment = $this->dispatch(new UpdateComment($comment, $request)); 61 | 62 | return new Resource($comment->fresh()); 63 | } 64 | 65 | /** 66 | * Remove the specified resource from storage. 67 | * 68 | * @param Comment $comment 69 | * @return \Illuminate\Http\Response 70 | */ 71 | public function destroy(Comment $comment) 72 | { 73 | try { 74 | $this->dispatch(new DeleteComment($comment)); 75 | 76 | return $this->noContent(); 77 | } catch(\Exception $e) { 78 | $this->errorUnauthorized($e->getMessage()); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Http/Controllers/Api/Posts.php: -------------------------------------------------------------------------------- 1 | collect(); 23 | 24 | return Resource::collection($posts); 25 | } 26 | 27 | /** 28 | * Display the specified resource. 29 | * 30 | * @param int $id 31 | * @return \Illuminate\Http\JsonResponse 32 | */ 33 | public function show($id) 34 | { 35 | $post = Post::with('category', 'comments')->find($id); 36 | 37 | return new Resource($post); 38 | } 39 | 40 | /** 41 | * Store a newly created resource in storage. 42 | * 43 | * @param $request 44 | * @return \Illuminate\Http\JsonResponse 45 | */ 46 | public function store(Request $request) 47 | { 48 | $post = $this->dispatch(new CreatePost($request)); 49 | 50 | return $this->created(route('api.my-blog.posts.show', $post->id), new Resource($post)); 51 | } 52 | 53 | /** 54 | * Update the specified resource in storage. 55 | * 56 | * @param $post 57 | * @param $request 58 | * @return \Illuminate\Http\JsonResponse 59 | */ 60 | public function update(Post $post, Request $request) 61 | { 62 | $post = $this->dispatch(new UpdatePost($post, $request)); 63 | 64 | return new Resource($post->fresh()); 65 | } 66 | 67 | /** 68 | * Enable the specified resource in storage. 69 | * 70 | * @param Post $post 71 | * @return \Illuminate\Http\JsonResponse 72 | */ 73 | public function enable(Post $post) 74 | { 75 | $post = $this->dispatch(new UpdatePost($post, request()->merge(['enabled' => 1]))); 76 | 77 | return new Resource($post->fresh()); 78 | } 79 | 80 | /** 81 | * Disable the specified resource in storage. 82 | * 83 | * @param Post $post 84 | * @return \Illuminate\Http\JsonResponse 85 | */ 86 | public function disable(Post $post) 87 | { 88 | $post = $this->dispatch(new UpdatePost($post, request()->merge(['enabled' => 0]))); 89 | 90 | return new Resource($post->fresh()); 91 | } 92 | 93 | /** 94 | * Remove the specified resource from storage. 95 | * 96 | * @param int $id 97 | * @return \Illuminate\Http\Response 98 | */ 99 | public function destroy($id) 100 | { 101 | $post = Post::with('comments')->find($id); 102 | 103 | try { 104 | $this->dispatch(new DeletePost($post)); 105 | 106 | return $this->noContent(); 107 | } catch(\Exception $e) { 108 | $this->errorUnauthorized($e->getMessage()); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Http/Controllers/Comments.php: -------------------------------------------------------------------------------- 1 | collect(['created_at' => 'desc']); 25 | 26 | return $this->response('my-blog::comments.index', compact('comments')); 27 | } 28 | 29 | /** 30 | * Show the form for creating a new resource. 31 | * 32 | * @return Response 33 | */ 34 | public function create() 35 | { 36 | $posts = Post::orderBy('name')->take(setting('default.select_limit'))->pluck('name', 'id'); 37 | 38 | return view('my-blog::comments.create', compact('posts')); 39 | } 40 | 41 | /** 42 | * Store a newly created resource in storage. 43 | * 44 | * @param Request $request 45 | * 46 | * @return Response 47 | */ 48 | public function store(Request $request) 49 | { 50 | $response = $this->ajaxDispatch(new CreateComment($request)); 51 | 52 | if ($response['success']) { 53 | $response['redirect'] = route('my-blog.comments.index'); 54 | 55 | $message = trans('messages.success.added', ['type' => trans_choice('my-blog::general.comments', 1)]); 56 | 57 | flash($message)->success(); 58 | } else { 59 | $response['redirect'] = route('my-blog.comments.create'); 60 | 61 | $message = $response['message']; 62 | 63 | flash($message)->error()->important(); 64 | } 65 | 66 | return response()->json($response); 67 | } 68 | 69 | /** 70 | * Show the form for editing the specified resource. 71 | * 72 | * @param Comment $comment 73 | * 74 | * @return Response 75 | */ 76 | public function edit(Comment $comment) 77 | { 78 | $posts = Post::orderBy('name')->take(setting('default.select_limit'))->pluck('name', 'id'); 79 | 80 | return view('my-blog::comments.edit', compact('comment', 'posts')); 81 | } 82 | 83 | /** 84 | * Update the specified resource in storage. 85 | * 86 | * @param Comment $comment 87 | * @param Request $request 88 | * 89 | * @return Response 90 | */ 91 | public function update(Comment $comment, Request $request) 92 | { 93 | $response = $this->ajaxDispatch(new UpdateComment($comment, $request)); 94 | 95 | if ($response['success']) { 96 | $response['redirect'] = route('my-blog.comments.index'); 97 | 98 | $message = trans('messages.success.updated', ['type' => $comment->id]); 99 | 100 | flash($message)->success(); 101 | } else { 102 | $response['redirect'] = route('my-blog.comments.edit', $comment->id); 103 | 104 | $message = $response['message']; 105 | 106 | flash($message)->error()->important(); 107 | } 108 | 109 | return response()->json($response); 110 | } 111 | 112 | /** 113 | * Remove the specified resource from storage. 114 | * 115 | * @param Comment $comment 116 | * 117 | * @return Response 118 | */ 119 | public function destroy(Comment $comment) 120 | { 121 | $response = $this->ajaxDispatch(new DeleteComment($comment)); 122 | 123 | $response['redirect'] = route('my-blog.comments.index'); 124 | 125 | if ($response['success']) { 126 | $message = trans('messages.success.deleted', ['type' => $comment->id]); 127 | 128 | flash($message)->success(); 129 | } else { 130 | $message = $response['message']; 131 | 132 | flash($message)->error()->important(); 133 | } 134 | 135 | return response()->json($response); 136 | } 137 | 138 | /** 139 | * Export the specified resource. 140 | * 141 | * @return Response 142 | */ 143 | public function export() 144 | { 145 | return $this->exportExcel(new Export, trans_choice('my-blog::general.comments', 2)); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Http/Controllers/Portal/Comments.php: -------------------------------------------------------------------------------- 1 | ajaxDispatch(new CreateComment($request)); 22 | 23 | $response['redirect'] = route('portal.my-blog.posts.show', $request->get('post_id')); 24 | 25 | if ($response['success']) { 26 | $message = trans('messages.success.added', ['type' => trans_choice('my-blog::general.comments', 1)]); 27 | 28 | flash($message)->success(); 29 | } else { 30 | $message = $response['message']; 31 | 32 | flash($message)->error()->important(); 33 | } 34 | 35 | return response()->json($response); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Http/Controllers/Portal/Posts.php: -------------------------------------------------------------------------------- 1 | enabled()->collect(); 19 | 20 | return $this->response('my-blog::portal.posts.index', compact('posts')); 21 | } 22 | 23 | /** 24 | * Show the form for viewing the specified resource. 25 | * 26 | * @return Response 27 | */ 28 | public function show(Post $post) 29 | { 30 | if (setting('my-blog.enable_comments')) { 31 | $post->load('category', 'comments'); 32 | 33 | $limit = (int) request('limit', setting('default.list_limit', '25')); 34 | $comments = $this->paginate($post->comments->sortByDesc('created_at'), $limit); 35 | } else { 36 | $post->load('category'); 37 | 38 | $comments = []; 39 | } 40 | 41 | return view('my-blog::portal.posts.show', compact('post', 'comments')); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Http/Controllers/Posts.php: -------------------------------------------------------------------------------- 1 | collect(); 27 | 28 | return $this->response('my-blog::posts.index', compact('posts')); 29 | } 30 | 31 | /** 32 | * Show the form for viewing the specified resource. 33 | * 34 | * @return Response 35 | */ 36 | public function show(Post $post) 37 | { 38 | if (setting('my-blog.enable_comments')) { 39 | $post->load('category', 'comments'); 40 | 41 | $limit = (int) request('limit', setting('default.list_limit', '25')); 42 | $comments = $this->paginate($post->comments->sortByDesc('created_at'), $limit); 43 | } else { 44 | $post->load('category'); 45 | 46 | $comments = []; 47 | } 48 | 49 | return view('my-blog::posts.show', compact('post', 'comments')); 50 | } 51 | 52 | /** 53 | * Show the form for creating a new resource. 54 | * 55 | * @return Response 56 | */ 57 | public function create() 58 | { 59 | $categories = Category::type('post')->enabled()->orderBy('name')->take(setting('default.select_limit'))->pluck('name', 'id'); 60 | 61 | return view('my-blog::posts.create', compact('categories')); 62 | } 63 | 64 | /** 65 | * Store a newly created resource in storage. 66 | * 67 | * @param Request $request 68 | * 69 | * @return Response 70 | */ 71 | public function store(Request $request) 72 | { 73 | $response = $this->ajaxDispatch(new CreatePost($request)); 74 | 75 | if ($response['success']) { 76 | $response['redirect'] = route('my-blog.posts.index'); 77 | 78 | $message = trans('messages.success.added', ['type' => trans_choice('my-blog::general.posts', 1)]); 79 | 80 | flash($message)->success(); 81 | } else { 82 | $response['redirect'] = route('my-blog.posts.create'); 83 | 84 | $message = $response['message']; 85 | 86 | flash($message)->error()->important(); 87 | } 88 | 89 | return response()->json($response); 90 | } 91 | 92 | /** 93 | * Duplicate the specified resource. 94 | * 95 | * @param Post $post 96 | * 97 | * @return Response 98 | */ 99 | public function duplicate(Post $post) 100 | { 101 | $clone = $post->duplicate(); 102 | 103 | $message = trans('messages.success.duplicated', ['type' => trans_choice('my-blog::general.posts', 1)]); 104 | 105 | flash($message)->success(); 106 | 107 | return redirect()->route('my-blog.posts.edit', $clone->id); 108 | } 109 | 110 | /** 111 | * Import the specified resource. 112 | * 113 | * @param ImportRequest $request 114 | * 115 | * @return Response 116 | */ 117 | public function import(ImportRequest $request) 118 | { 119 | $response = $this->importExcel(new Import, $request, trans_choice('my-blog::general.posts', 2)); 120 | 121 | if ($response['success']) { 122 | $response['redirect'] = route('my-blog.posts.index'); 123 | 124 | flash($response['message'])->success(); 125 | } else { 126 | $response['redirect'] = route('import.create', ['my-blog', 'posts']); 127 | 128 | flash($response['message'])->error()->important(); 129 | } 130 | 131 | return response()->json($response); 132 | } 133 | 134 | /** 135 | * Show the form for editing the specified resource. 136 | * 137 | * @param Post $post 138 | * 139 | * @return Response 140 | */ 141 | public function edit(Post $post) 142 | { 143 | $categories = Category::type('post')->enabled()->orderBy('name')->take(setting('default.select_limit'))->pluck('name', 'id'); 144 | 145 | return view('my-blog::posts.edit', compact('post', 'categories')); 146 | } 147 | 148 | /** 149 | * Update the specified resource in storage. 150 | * 151 | * @param Post $post 152 | * @param Request $request 153 | * 154 | * @return Response 155 | */ 156 | public function update(Post $post, Request $request) 157 | { 158 | $response = $this->ajaxDispatch(new UpdatePost($post, $request)); 159 | 160 | if ($response['success']) { 161 | $response['redirect'] = route('my-blog.posts.index'); 162 | 163 | $message = trans('messages.success.updated', ['type' => $post->name]); 164 | 165 | flash($message)->success(); 166 | } else { 167 | $response['redirect'] = route('my-blog.posts.edit', $post->id); 168 | 169 | $message = $response['message']; 170 | 171 | flash($message)->error()->important(); 172 | } 173 | 174 | return response()->json($response); 175 | } 176 | 177 | /** 178 | * Enable the specified resource. 179 | * 180 | * @param Post $post 181 | * 182 | * @return Response 183 | */ 184 | public function enable(Post $post) 185 | { 186 | $response = $this->ajaxDispatch(new UpdatePost($post, request()->merge(['enabled' => 1]))); 187 | 188 | if ($response['success']) { 189 | $response['message'] = trans('messages.success.enabled', ['type' => $post->name]); 190 | } 191 | 192 | return response()->json($response); 193 | } 194 | 195 | /** 196 | * Disable the specified resource. 197 | * 198 | * @param Post $post 199 | * 200 | * @return Response 201 | */ 202 | public function disable(Post $post) 203 | { 204 | $response = $this->ajaxDispatch(new UpdatePost($post, request()->merge(['enabled' => 0]))); 205 | 206 | if ($response['success']) { 207 | $response['message'] = trans('messages.success.disabled', ['type' => $post->name]); 208 | } 209 | 210 | return response()->json($response); 211 | } 212 | 213 | /** 214 | * Remove the specified resource from storage. 215 | * 216 | * @param Post $post 217 | * 218 | * @return Response 219 | */ 220 | public function destroy(Post $post) 221 | { 222 | $response = $this->ajaxDispatch(new DeletePost($post)); 223 | 224 | $response['redirect'] = route('my-blog.posts.index'); 225 | 226 | if ($response['success']) { 227 | $message = trans('messages.success.deleted', ['type' => $post->name]); 228 | 229 | flash($message)->success(); 230 | } else { 231 | $message = $response['message']; 232 | 233 | flash($message)->error()->important(); 234 | } 235 | 236 | return response()->json($response); 237 | } 238 | 239 | /** 240 | * Export the specified resource. 241 | * 242 | * @return Response 243 | */ 244 | public function export() 245 | { 246 | return $this->exportExcel(new Export, trans_choice('my-blog::general.posts', 2)); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /Http/Requests/Comment.php: -------------------------------------------------------------------------------- 1 | 'required', 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Http/Requests/Post.php: -------------------------------------------------------------------------------- 1 | 'required|string', 18 | 'enabled' => 'integer|boolean', 19 | 'created_by' => 'integer', 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Http/Resources/Comment.php: -------------------------------------------------------------------------------- 1 | $this->id, 19 | 'company_id' => $this->company_id, 20 | 'post_id' => $this->post_id, 21 | 'description' => $this->description, 22 | 'created_by' => $this->created_by, 23 | 'created_at' => $this->created_at ? $this->created_at->toIso8601String() : '', 24 | 'updated_at' => $this->updated_at ? $this->updated_at->toIso8601String() : '', 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Http/Resources/Post.php: -------------------------------------------------------------------------------- 1 | $this->id, 21 | 'company_id' => $this->company_id, 22 | 'name' => $this->name, 23 | 'description' => $this->description, 24 | 'category_id' => $this->category_id, 25 | 'enabled' => $this->enabled, 26 | 'created_by' => $this->created_by, 27 | 'created_from' => $this->created_from, 28 | 'created_at' => $this->created_at ? $this->created_at->toIso8601String() : '', 29 | 'updated_at' => $this->updated_at ? $this->updated_at->toIso8601String() : '', 30 | 'category' => new Category($this->category), 31 | 'comments' => [static::$wrap => Comment::collection($this->comments)], 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Imports/Posts.php: -------------------------------------------------------------------------------- 1 | getCategoryId($row, 'post'); 21 | 22 | return $row; 23 | } 24 | 25 | public function rules(): array 26 | { 27 | return (new Request())->rules(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Jobs/CreateComment.php: -------------------------------------------------------------------------------- 1 | model = Comment::create($this->request->all()); 17 | }); 18 | 19 | return $this->model; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Jobs/CreatePost.php: -------------------------------------------------------------------------------- 1 | model = Post::create($this->request->all()); 17 | }); 18 | 19 | return $this->model; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Jobs/DeleteComment.php: -------------------------------------------------------------------------------- 1 | model->delete(); 14 | }); 15 | 16 | return true; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Jobs/DeletePost.php: -------------------------------------------------------------------------------- 1 | deleteRelationships($this->model, ['comments']); 14 | 15 | $this->model->delete(); 16 | }); 17 | 18 | return true; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Jobs/UpdateComment.php: -------------------------------------------------------------------------------- 1 | model->update($this->request->all()); 15 | }); 16 | 17 | return $this->model; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Jobs/UpdatePost.php: -------------------------------------------------------------------------------- 1 | model->update($this->request->all()); 15 | }); 16 | 17 | return $this->model; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Listeners/AddAuthorsToReport.php: -------------------------------------------------------------------------------- 1 | skipThisClass($event)) { 29 | return; 30 | } 31 | 32 | $event->class->filters['authors'] = $this->getAuthors($event, true); 33 | $event->class->filters['routes']['authors'] = 'users.index'; 34 | $event->class->filters['names']['authors'] = trans_choice('my-blog::general.authors', 1); 35 | } 36 | 37 | /** 38 | * Handle filter applying event. 39 | * 40 | * @param $event 41 | * @return void 42 | */ 43 | public function handleFilterApplying(FilterApplying $event) 44 | { 45 | if ($this->skipThisClass($event)) { 46 | return; 47 | } 48 | 49 | $author_id = $this->getSearchStringValue('author_id'); 50 | 51 | if (empty($author_id)) { 52 | return; 53 | } 54 | 55 | $event->model->where('created_by', $author_id); 56 | } 57 | 58 | /** 59 | * Handle group showing event. 60 | * 61 | * @param $event 62 | * @return void 63 | */ 64 | public function handleGroupShowing(GroupShowing $event) 65 | { 66 | if ($this->skipThisClass($event)) { 67 | return; 68 | } 69 | 70 | $event->class->groups['author'] = trans_choice('my-blog::general.authors', 1); 71 | } 72 | 73 | /** 74 | * Handle group applying event. 75 | * 76 | * @param $event 77 | * @return void 78 | */ 79 | public function handleGroupApplying(GroupApplying $event) 80 | { 81 | if ($this->skipThisClass($event)) { 82 | return; 83 | } 84 | 85 | $event->model->author_id = $event->model->created_by; 86 | } 87 | 88 | /** 89 | * Handle rows showing event. 90 | * 91 | * @param $event 92 | * @return void 93 | */ 94 | public function handleRowsShowing(RowsShowing $event) 95 | { 96 | if ($this->skipRowsShowing($event, 'author')) { 97 | return; 98 | } 99 | 100 | $all_authors = $this->getAuthors($event); 101 | 102 | if ($author_ids = $this->getSearchStringValue('author_id')) { 103 | $authors = explode(',', $author_ids); 104 | 105 | $rows = collect($all_authors)->filter(function ($value, $key) use ($authors) { 106 | return in_array($key, $authors); 107 | }); 108 | } else { 109 | $rows = $all_authors; 110 | } 111 | 112 | $this->setRowNamesAndValues($event, $rows); 113 | } 114 | 115 | public function getAuthors($event, $limit = false) 116 | { 117 | $relation_name = (class_basename($event->class) == 'PostSummary') ? 'my_blog_posts' : 'my_blog_comments'; 118 | 119 | $model = User::whereHas($relation_name); 120 | 121 | if ($limit !== false) { 122 | $model->take(setting('default.select_limit')); 123 | } 124 | 125 | return $model->pluck('name', 'id')->toArray(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Listeners/AddCategoriesToReport.php: -------------------------------------------------------------------------------- 1 | skipThisClass($event)) { 25 | return; 26 | } 27 | 28 | $event->class->filters['categories'] = $this->getCategories('post', true); 29 | $event->class->filters['routes']['categories'] = ['categories.index', 'search=type:post']; 30 | } 31 | 32 | /** 33 | * Handle group showing event. 34 | * 35 | * @param $event 36 | * @return void 37 | */ 38 | public function handleGroupShowing(GroupShowing $event) 39 | { 40 | if ($this->skipThisClass($event)) { 41 | return; 42 | } 43 | 44 | $event->class->groups['category'] = trans_choice('general.categories', 1); 45 | } 46 | 47 | /** 48 | * Handle rows showing event. 49 | * 50 | * @param $event 51 | * @return void 52 | */ 53 | public function handleRowsShowing(RowsShowing $event) 54 | { 55 | if ($this->skipRowsShowing($event, 'category')) { 56 | return; 57 | } 58 | 59 | $all_categories = $this->getCategories('post'); 60 | 61 | if ($category_ids = $this->getSearchStringValue('category_id')) { 62 | $categories = explode(',', $category_ids); 63 | 64 | $rows = collect($all_categories)->filter(function ($value, $key) use ($categories) { 65 | return in_array($key, $categories); 66 | }); 67 | } else { 68 | $rows = $all_categories; 69 | } 70 | 71 | $this->setRowNamesAndValues($event, $rows); 72 | 73 | $event->class->row_tree_nodes = []; 74 | 75 | $nodes = $this->getCategoriesNodes($rows); 76 | 77 | $this->setTreeNodes($event, $nodes); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Listeners/AddPostsToReport.php: -------------------------------------------------------------------------------- 1 | skipThisClass($event)) { 26 | return; 27 | } 28 | 29 | $event->class->filters['posts'] = $this->getPosts(true); 30 | $event->class->filters['routes']['posts'] = 'my-blog.posts.index'; 31 | $event->class->filters['names']['posts'] = trans_choice('my-blog::general.posts', 1); 32 | } 33 | 34 | /** 35 | * Handle group showing event. 36 | * 37 | * @param $event 38 | * @return void 39 | */ 40 | public function handleGroupShowing(GroupShowing $event) 41 | { 42 | if ($this->skipThisClass($event)) { 43 | return; 44 | } 45 | 46 | $event->class->groups['post'] = trans_choice('my-blog::general.posts', 1); 47 | } 48 | 49 | /** 50 | * Handle rows showing event. 51 | * 52 | * @param $event 53 | * @return void 54 | */ 55 | public function handleRowsShowing(RowsShowing $event) 56 | { 57 | if ($this->skipRowsShowing($event, 'post')) { 58 | return; 59 | } 60 | 61 | $all_posts = $this->getPosts(); 62 | 63 | if ($post_ids = $this->getSearchStringValue('post_id')) { 64 | $posts = explode(',', $post_ids); 65 | 66 | $rows = collect($all_posts)->filter(function ($value, $key) use ($posts) { 67 | return in_array($key, $posts); 68 | }); 69 | } else { 70 | $rows = $all_posts; 71 | } 72 | 73 | $this->setRowNamesAndValues($event, $rows); 74 | } 75 | 76 | public function getPosts($limit = false) 77 | { 78 | $model = Post::whereHas('comments'); 79 | 80 | if ($limit !== false) { 81 | $model->take(setting('default.select_limit')); 82 | } 83 | 84 | return $model->pluck('name', 'id')->toArray(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Listeners/AddToAdminMenu.php: -------------------------------------------------------------------------------- 1 | menu; 21 | 22 | $attr = ['icon' => '']; 23 | 24 | $title = trim(trans('my-blog::general.name')); 25 | if ($this->canAccessMenuItem($title, ['read-my-blog-posts', 'read-my-blog-comments'])) { 26 | $menu->dropdown($title, function ($sub) use ($attr) { 27 | $title = trim(trans_choice('my-blog::general.posts', 2)); 28 | if ($this->canAccessMenuItem($title, 'read-my-blog-posts')) { 29 | $sub->route('my-blog.posts.index', $title, [], 10, $attr); 30 | } 31 | 32 | $title = trim(trans_choice('my-blog::general.comments', 2)); 33 | if ($this->canAccessMenuItem($title, 'read-my-blog-comments')) { 34 | $sub->route('my-blog.comments.index', $title, [], 20, $attr); 35 | } 36 | }, 15, [ 37 | 'title' => $title, 38 | 'icon' => 'edit', 39 | ]); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Listeners/AddToNewwMenu.php: -------------------------------------------------------------------------------- 1 | menu; 21 | 22 | $title = trim(trans_choice('my-blog::general.posts', 1)); 23 | if ($this->canAccessMenuItem($title, 'create-my-blog-posts')) { 24 | $menu->route('my-blog.posts.create', $title, [], 80, ['icon' => 'edit']); 25 | } 26 | 27 | $title = trim(trans_choice('my-blog::general.comments', 1)); 28 | if ($this->canAccessMenuItem($title, 'create-my-blog-comments')) { 29 | $menu->route('my-blog.comments.create', $title, [], 81, ['icon' => 'chat']); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Listeners/AddToPortalMenu.php: -------------------------------------------------------------------------------- 1 | cannot('read-my-blog-portal-posts')) { 17 | return; 18 | } 19 | 20 | $event->menu->add([ 21 | 'route' => ['portal.my-blog.posts.index', []], 22 | 'title' => trans_choice('my-blog::general.posts', 2), 23 | 'icon' => 'edit', 24 | 'order' => 40, 25 | ]); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Listeners/FinishInstallation.php: -------------------------------------------------------------------------------- 1 | alias != $this->alias) { 24 | return; 25 | } 26 | 27 | $this->updatePermissions(); 28 | 29 | $this->callSeeds(); 30 | } 31 | 32 | protected function updatePermissions() 33 | { 34 | // c=create, r=read, u=update, d=delete 35 | $this->attachPermissionsToAdminRoles([ 36 | $this->alias . '-posts' => 'c,r,u,d', 37 | $this->alias . '-comments' => 'c,r,u,d', 38 | ]); 39 | 40 | // c=create, r=read, u=update, d=delete 41 | $this->attachPermissionsToPortalRoles([ 42 | $this->alias . '-portal-posts' => 'r', 43 | $this->alias . '-portal-comments' => 'c,r', 44 | ]); 45 | } 46 | 47 | protected function callSeeds() 48 | { 49 | Artisan::call('company:seed', [ 50 | 'company' => company_id(), 51 | '--class' => 'Modules\MyBlog\Database\Seeds\Install', 52 | ]); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Models/Comment.php: -------------------------------------------------------------------------------- 1 | belongsTo('Modules\MyBlog\Models\Post')->withDefault(['name' => trans('general.na')]); 26 | } 27 | 28 | /** 29 | * Get the line actions. 30 | * 31 | * @return array 32 | */ 33 | public function getLineActionsAttribute() 34 | { 35 | $actions = []; 36 | 37 | $actions[] = [ 38 | 'title' => trans('general.edit'), 39 | 'icon' => 'edit', 40 | 'url' => route('my-blog.comments.edit', $this->id), 41 | 'permission' => 'update-my-blog-comments', 42 | ]; 43 | 44 | $actions[] = [ 45 | 'type' => 'delete', 46 | 'title' => trans_choice('my-blog::general.comments', 1), 47 | 'icon' => 'delete', 48 | 'route' => 'my-blog.comments.destroy', 49 | 'permission' => 'delete-my-blog-comments', 50 | 'model' => $this, 51 | ]; 52 | 53 | return $actions; 54 | } 55 | 56 | /** 57 | * Create a new factory instance for the model. 58 | * 59 | * @return \Illuminate\Database\Eloquent\Factories\Factory 60 | */ 61 | protected static function newFactory() 62 | { 63 | return \Modules\MyBlog\Database\Factories\Comment::new(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Models/Post.php: -------------------------------------------------------------------------------- 1 | belongsTo('App\Models\Setting\Category')->withDefault(['name' => trans('general.na')]); 26 | } 27 | 28 | public function comments() 29 | { 30 | return $this->hasMany('Modules\MyBlog\Models\Comment'); 31 | } 32 | 33 | /** 34 | * Get the line actions. 35 | * 36 | * @return array 37 | */ 38 | public function getLineActionsAttribute() 39 | { 40 | $actions = []; 41 | 42 | $actions[] = [ 43 | 'title' => trans('general.edit'), 44 | 'icon' => 'edit', 45 | 'url' => route('my-blog.posts.edit', $this->id), 46 | 'permission' => 'update-my-blog-posts', 47 | ]; 48 | 49 | $actions[] = [ 50 | 'title' => trans('general.duplicate'), 51 | 'icon' => 'file_copy', 52 | 'url' => route('my-blog.posts.duplicate', $this->id), 53 | 'permission' => 'create-my-blog-posts', 54 | ]; 55 | 56 | $actions[] = [ 57 | 'type' => 'delete', 58 | 'title' => trans_choice('my-blog::general.posts', 1), 59 | 'icon' => 'delete', 60 | 'route' => 'my-blog.posts.destroy', 61 | 'permission' => 'delete-my-blog-posts', 62 | 'model' => $this, 63 | ]; 64 | 65 | return $actions; 66 | } 67 | 68 | /** 69 | * Create a new factory instance for the model. 70 | * 71 | * @return \Illuminate\Database\Eloquent\Factories\Factory 72 | */ 73 | protected static function newFactory() 74 | { 75 | return \Modules\MyBlog\Database\Factories\Post::new(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Notifications/Comment.php: -------------------------------------------------------------------------------- 1 | comment = $comment; 31 | $this->template = EmailTemplate::alias($template_alias)->first(); 32 | } 33 | 34 | /** 35 | * Get the mail representation of the notification. 36 | */ 37 | public function toMail($notifiable): MailMessage 38 | { 39 | $message = $this->initMailMessage(); 40 | 41 | return $message; 42 | } 43 | 44 | /** 45 | * Get the array representation of the notification. 46 | */ 47 | public function toArray($notifiable): array 48 | { 49 | $this->initArrayMessage(); 50 | 51 | return [ 52 | 'template_alias' => $this->template->alias, 53 | 'title' => trans('my-blog::notifications.menu.' . $this->template->alias . '.title'), 54 | 'description' => trans('my-blog::notifications.menu.' . $this->template->alias . '.description', $this->getTagsBinding()), 55 | 'post_id' => $this->comment->post->id, 56 | 'post_name' => $this->comment->post->name, 57 | 'comment_id' => $this->comment->id, 58 | 'comment_author' => $this->comment->owner->name, 59 | ]; 60 | } 61 | 62 | public function getTags(): array 63 | { 64 | return [ 65 | '{post_name}', 66 | '{post_author}', 67 | '{post_admin_link}', 68 | '{comment_author}', 69 | '{comment_description}', 70 | '{company_name}', 71 | ]; 72 | } 73 | 74 | public function getTagsReplacement(): array 75 | { 76 | return [ 77 | $this->comment->post->name, 78 | $this->comment->post->owner->name, 79 | route('my-blog.posts.show', $this->comment->post->id), 80 | $this->comment->owner->name, 81 | $this->comment->description, 82 | $this->comment->company->name, 83 | ]; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Observers/Comment.php: -------------------------------------------------------------------------------- 1 | post->owner; 21 | 22 | $user->notify(new Notification($comment, 'comment_new_author')); 23 | } 24 | 25 | /** 26 | * Listen to the deleted event. 27 | * 28 | * @param Model $comment 29 | * 30 | * @return void 31 | */ 32 | public function deleted(Model $comment) 33 | { 34 | $user = $comment->post->owner; 35 | 36 | $user->notify(new Notification($comment, 'comment_delete_author')); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Providers/Event.php: -------------------------------------------------------------------------------- 1 | loadRoutes(); 22 | } 23 | 24 | /** 25 | * Boot the application events. 26 | * 27 | * @return void 28 | */ 29 | public function boot() 30 | { 31 | $this->loadViews(); 32 | $this->loadViewComponents(); 33 | $this->loadTranslations(); 34 | $this->loadMigrations(); 35 | $this->loadConfig(); 36 | $this->loadDynamicRelationships(); 37 | $this->loadCommands(); 38 | $this->scheduleCommands(); 39 | } 40 | 41 | /** 42 | * Load views. 43 | * 44 | * @return void 45 | */ 46 | public function loadViews() 47 | { 48 | $this->loadViewsFrom(__DIR__ . '/../Resources/views', 'my-blog'); 49 | } 50 | 51 | /** 52 | * Load view components. 53 | * 54 | * @return void 55 | */ 56 | public function loadViewComponents() 57 | { 58 | Blade::componentNamespace('Modules\MyBlog\View\Components', 'my-blog'); 59 | } 60 | 61 | /** 62 | * Load translations. 63 | * 64 | * @return void 65 | */ 66 | public function loadTranslations() 67 | { 68 | $this->loadTranslationsFrom(__DIR__ . '/../Resources/lang', 'my-blog'); 69 | } 70 | 71 | /** 72 | * Load migrations. 73 | * 74 | * @return void 75 | */ 76 | public function loadMigrations() 77 | { 78 | $this->loadMigrationsFrom(__DIR__ . '/../Database/Migrations'); 79 | } 80 | 81 | /** 82 | * Load config. 83 | * 84 | * @return void 85 | */ 86 | public function loadConfig() 87 | { 88 | $merge_to_core_configs = ['search-string', 'type']; 89 | 90 | foreach ($merge_to_core_configs as $config) { 91 | Config::set($config, array_merge_recursive( 92 | Config::get($config), 93 | require __DIR__ . '/../Config/' . $config . '.php' 94 | )); 95 | } 96 | 97 | $this->mergeConfigFrom(__DIR__ . '/../Config/my-blog.php', 'my-blog'); 98 | } 99 | 100 | /** 101 | * Load dynamic relationships. 102 | * 103 | * @return void 104 | */ 105 | public function loadDynamicRelationships() 106 | { 107 | User::resolveRelationUsing('my_blog_posts', function ($user) { 108 | return $user->hasMany('Modules\MyBlog\Models\Post', 'created_by', 'id'); 109 | }); 110 | 111 | User::resolveRelationUsing('my_blog_comments', function ($user) { 112 | return $user->hasMany('Modules\MyBlog\Models\Comment', 'created_by', 'id'); 113 | }); 114 | 115 | Category::resolveRelationUsing('my_blog_posts', function ($category) { 116 | return $category->hasMany('Modules\MyBlog\Models\Post', 'category_id', 'id'); 117 | }); 118 | } 119 | 120 | /** 121 | * Load commands. 122 | * 123 | * @return void 124 | */ 125 | public function loadCommands() 126 | { 127 | $this->commands(\Modules\MyBlog\Console\Inspire::class); 128 | } 129 | 130 | /** 131 | * Schedule commands. 132 | * 133 | * @return void 134 | */ 135 | public function scheduleCommands() 136 | { 137 | $this->app->booted(function () { 138 | $schedule_time = config('app.schedule_time', '09:00'); 139 | 140 | app(Schedule::class)->command('my-blog:inspire')->dailyAt($schedule_time); 141 | }); 142 | } 143 | 144 | /** 145 | * Load routes. 146 | * 147 | * @return void 148 | */ 149 | public function loadRoutes() 150 | { 151 | if (app()->routesAreCached()) { 152 | return; 153 | } 154 | 155 | $routes = [ 156 | 'admin.php', 157 | 'portal.php', 158 | 'api.php', 159 | ]; 160 | 161 | foreach ($routes as $route) { 162 | $this->loadRoutesFrom(__DIR__ . '/../Routes/' . $route); 163 | } 164 | } 165 | 166 | /** 167 | * Get the services provided by the provider. 168 | * 169 | * @return array 170 | */ 171 | public function provides() 172 | { 173 | return []; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /Providers/Observer.php: -------------------------------------------------------------------------------- 1 | applyFilters($query, ['date_field' => 'created_at'])->get(); 25 | 26 | $this->setArithmeticTotals($comments, 'created_at'); 27 | } 28 | 29 | public function getFields() 30 | { 31 | return [ 32 | $this->getGroupField(), 33 | $this->getPeriodField(), 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Reports/PostSummary.php: -------------------------------------------------------------------------------- 1 | enabled(); 23 | 24 | $posts = $this->applyFilters($query, ['date_field' => 'created_at'])->get(); 25 | 26 | $this->setArithmeticTotals($posts, 'created_at'); 27 | } 28 | 29 | public function getFields() 30 | { 31 | return [ 32 | $this->getGroupField(), 33 | $this->getPeriodField(), 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Resources/assets/js/comments.js: -------------------------------------------------------------------------------- 1 | /** 2 | * First we will load all of this project's JavaScript dependencies which 3 | * includes Vue and other libraries. It is a great starting point when 4 | * building robust, powerful web applications using Vue and Laravel. 5 | */ 6 | 7 | require('./../../../../../resources/assets/js/bootstrap'); 8 | 9 | import Vue from 'vue'; 10 | 11 | import Global from './../../../../../resources/assets/js/mixins/global'; 12 | import DashboardPlugin from './../../../../../resources/assets/js/plugins/dashboard-plugin'; 13 | import Form from './../../../../../resources/assets/js/plugins/form'; 14 | import BulkAction from './../../../../../resources/assets/js/plugins/bulk-action'; 15 | 16 | // plugin setup 17 | Vue.use(DashboardPlugin); 18 | 19 | const app = new Vue({ 20 | el: '#main-body', 21 | 22 | mixins: [ 23 | Global 24 | ], 25 | 26 | data() { 27 | return { 28 | form: new Form('comment'), 29 | bulk_action: new BulkAction('comments') 30 | } 31 | }, 32 | }); 33 | -------------------------------------------------------------------------------- /Resources/assets/js/posts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * First we will load all of this project's JavaScript dependencies which 3 | * includes Vue and other libraries. It is a great starting point when 4 | * building robust, powerful web applications using Vue and Laravel. 5 | */ 6 | 7 | require('./../../../../../resources/assets/js/bootstrap'); 8 | 9 | import Vue from 'vue'; 10 | 11 | import Global from './../../../../../resources/assets/js/mixins/global'; 12 | import DashboardPlugin from './../../../../../resources/assets/js/plugins/dashboard-plugin'; 13 | import Form from './../../../../../resources/assets/js/plugins/form'; 14 | import BulkAction from './../../../../../resources/assets/js/plugins/bulk-action'; 15 | 16 | // plugin setup 17 | Vue.use(DashboardPlugin); 18 | 19 | const app = new Vue({ 20 | el: '#main-body', 21 | 22 | mixins: [ 23 | Global 24 | ], 25 | 26 | data() { 27 | return { 28 | form: new Form('post'), 29 | bulk_action: new BulkAction('posts') 30 | } 31 | }, 32 | }); 33 | -------------------------------------------------------------------------------- /Resources/assets/posts.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akaunting/module-my-blog/faad2e407f0cc1452f592d6fed91c832b614b474/Resources/assets/posts.xlsx -------------------------------------------------------------------------------- /Resources/assets/sass/app.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akaunting/module-my-blog/faad2e407f0cc1452f592d6fed91c832b614b474/Resources/assets/sass/app.scss -------------------------------------------------------------------------------- /Resources/lang/en-GB/email_templates.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'subject' => 'New comment on {post_name}', 7 | 'body' => 'Dear {post_author},

You have a new comment from {comment_author}:

{comment_description}

Best Regards,
{company_name}', 8 | ], 9 | 10 | 'comment_delete_author' => [ 11 | 'subject' => 'Comment of {post_name} has been deleted', 12 | 'body' => 'Dear {post_author},

The following comment from {comment_author} has been deleted:

{comment_description}

Best Regards,
{company_name}', 13 | ], 14 | 15 | ]; 16 | -------------------------------------------------------------------------------- /Resources/lang/en-GB/general.php: -------------------------------------------------------------------------------- 1 | 'Blog', 6 | 'description' => 'Example module with beyond CRUD functions', 7 | 8 | 'posts' => 'Post|Posts', 9 | 'comments' => 'Comment|Comments', 10 | 'authors' => 'Author|Authors', 11 | 12 | 'demo' => [ 13 | 'categories' => [ 14 | 'php' => 'PHP', 15 | 'laravel' => 'Laravel', 16 | ], 17 | ], 18 | 19 | 'form_description' => [ 20 | 'post' => 'Enter the post name, description and select the category.', 21 | 'comment' => 'Select the post and enter the comment in the description field.', 22 | ], 23 | 24 | 'empty' => [ 25 | 'comments' => 'Write comments for posts.', 26 | 'posts' => 'Write posts and publish them.', 27 | ], 28 | 29 | ]; 30 | -------------------------------------------------------------------------------- /Resources/lang/en-GB/notifications.php: -------------------------------------------------------------------------------- 1 | [ 6 | 7 | 'comment_new_author' => [ 8 | 9 | 'title' => 'New Comment', 10 | 'description' => 'New comment from :comment_author on :post_name. You can click here to see the details.', 11 | 12 | ], 13 | 14 | 'comment_delete_author' => [ 15 | 16 | 'title' => 'Comment Deleted', 17 | 'description' => 'Comment from :comment_author on :post_name has been deleted.', 18 | 19 | ], 20 | 21 | ], 22 | 23 | ]; 24 | -------------------------------------------------------------------------------- /Resources/lang/en-GB/reports.php: -------------------------------------------------------------------------------- 1 | 'Post Summary', 6 | 'post_description' => 'Monthly post summary by category.', 7 | 8 | 'comment_name' => 'Comment Summary', 9 | 'comment_description' => 'Monthly comment summary by post.', 10 | 11 | ]; 12 | -------------------------------------------------------------------------------- /Resources/lang/en-GB/settings.php: -------------------------------------------------------------------------------- 1 | 'Header', 6 | 'enable_comments' => 'Enable Comments', 7 | 'meta_description' => 'Meta Description', 8 | 9 | 'email' => [ 10 | 'templates' => [ 11 | 'comment_new_author' => 'New Comment Template (sent to author)', 12 | 'comment_delete_author' => 'Comment Deleted Template (sent to author)', 13 | ], 14 | ], 15 | 16 | ]; 17 | -------------------------------------------------------------------------------- /Resources/lang/en-GB/widgets.php: -------------------------------------------------------------------------------- 1 | 'Posts by Category', 6 | 'comments_by_post' => 'Comments by Post', 7 | 'top_authors' => 'Top Authors', 8 | 'latest_posts' => 'Latest Posts', 9 | 'latest_comments' => 'Latest Comments', 10 | 11 | 'description' => [ 12 | 'posts_by_category' => 'Number of posts by category', 13 | 'comments_by_post' => 'Number of comments by post', 14 | 'top_authors' => 'List of authors with most posts', 15 | 'latest_posts' => 'List of the latest posts', 16 | 'latest_comments' => 'List of the latest comments', 17 | ], 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /Resources/views/comments/create.blade.php: -------------------------------------------------------------------------------- 1 | 2 | {{ trans('general.title.new', ['type' => trans_choice('my-blog::general.comments', 1)]) }} 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Resources/views/comments/edit.blade.php: -------------------------------------------------------------------------------- 1 | 2 | {{ trans('general.title.edit', ['type' => trans_choice('my-blog::general.comments', 1)]) }} 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | @can('update-my-blog-comments') 20 | 21 | 22 | 23 | 24 | 25 | @endcan 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Resources/views/comments/index.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ trans_choice('my-blog::general.comments', 2) }} 4 | 5 | 6 | 11 | 12 | 13 | @can('create-my-blog-comments') 14 | 15 | {{ trans('general.title.new', ['type' => trans_choice('my-blog::general.comments', 1)]) }} 16 | 17 | @endcan 18 | 19 | 20 | 21 | 22 | 23 | more_horiz 24 | 25 | 26 | 27 | {{ trans('general.export') }} 28 | 29 | 30 | 31 | 32 | 33 | @if ($comments->count() || request()->get('search', false)) 34 | 35 | 39 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | @foreach($comments as $item) 64 | 65 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | {{ $item->post->name }} 75 | 76 | 77 | 78 | 79 |
80 | {{ $item->description }} 81 |
82 |
83 | 84 | 85 | 86 | 87 |
88 | @endforeach 89 |
90 |
91 | 92 | 93 |
94 | @else 95 | 96 | @endif 97 |
98 | 99 | 100 |
101 | -------------------------------------------------------------------------------- /Resources/views/portal/posts/index.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ trans_choice('my-blog::general.posts', 2) }} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | @stack('name_th_start') 14 | 15 | 30 | 31 | @stack('category_th_start') 32 | 33 | 36 | 37 | @stack('description_th_start') 38 | 39 | 40 | 41 | 42 | 43 | @stack('description_th_end') 44 | 45 | 46 | 47 | 48 | @foreach($posts as $item) 49 | 50 | 51 | 52 | @stack('name_td_start') 53 | 54 | 73 | 74 | @stack('category_td_start') 75 | 76 | 81 | 82 | @stack('description_td_start') 83 | 84 | 85 |
86 | {{ $item->description }} 87 |
88 |
89 | 90 | @stack('description_td_end') 91 |
92 | @endforeach 93 |
94 |
95 | 96 | 97 |
98 |
99 | 100 | 101 |
102 | -------------------------------------------------------------------------------- /Resources/views/portal/posts/show.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ $post->name }} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | @stack('category_start') 12 | 13 |
14 |
15 | {{ trans_choice('general.categories', 1) }} 16 |
17 | 18 | 19 | {{ $post->category->name }} 20 | 21 |
22 | 23 | @stack('status_start') 24 | 25 |
26 |
27 | {{ trans('general.enabled') }} 28 |
29 | 30 | 31 | @if ($post->enabled) 32 | {{ trans('general.yes') }} 33 | @else 34 | {{ trans('general.no') }} 35 | @endif 36 | 37 |
38 | 39 | @stack('status_end') 40 |
41 | 42 | 43 |
44 | {{ $post->description }} 45 | 46 | @if (setting('my-blog.enable_comments')) 47 |

{{ trans_choice('my-blog::general.comments', 2) }}

48 | 49 | 50 | 51 | 52 | 53 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | @foreach($comments as $item) 74 | 75 | 76 | 77 | 78 | 79 | 80 | {{ $item->owner->name }} 81 | 82 | 83 | 84 | 85 |
86 | {{ $item->description }} 87 |
88 |
89 |
90 | @endforeach 91 |
92 |
93 | 94 | 95 | 96 |

{{ trans('general.title.new', ['type' => trans_choice('my-blog::general.comments', 1)]) }}

97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | @endif 112 |
113 |
114 | 115 |
116 |
117 |
118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /Resources/views/posts/create.blade.php: -------------------------------------------------------------------------------- 1 | 2 | {{ trans('general.title.new', ['type' => trans_choice('my-blog::general.posts', 1)]) }} 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Resources/views/posts/edit.blade.php: -------------------------------------------------------------------------------- 1 | 2 | {{ trans('general.title.edit', ['type' => trans_choice('my-blog::general.posts', 1)]) }} 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | @can('update-my-blog-posts') 24 | 25 | 26 | 27 | 28 | 29 | @endcan 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Resources/views/posts/index.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ trans_choice('my-blog::general.posts', 2) }} 4 | 5 | 6 | 11 | 12 | 13 | @can('create-my-blog-posts') 14 | 15 | {{ trans('general.title.new', ['type' => trans_choice('my-blog::general.posts', 1)]) }} 16 | 17 | @endcan 18 | 19 | 20 | 21 | 22 | 23 | more_horiz 24 | 25 | 26 | @can('create-my-blog-posts') 27 | 28 | {{ trans('import.import') }} 29 | 30 | @endcan 31 | 32 | 33 | {{ trans('general.export') }} 34 | 35 | 36 | 37 | 38 | 39 | @if ($posts->count() || request()->get('search', false)) 40 | 41 | 45 | 46 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | @foreach($posts as $item) 74 | 75 | 78 | 79 | 80 | 81 |
82 | {{ $item->name }} 83 |
84 | 85 | @if (! $item->enabled) 86 | 87 | @endif 88 |
89 | 90 | {{ $item->owner->name }} 91 | 92 |
93 | 94 | 99 | 100 | 101 |
102 | {{ $item->description }} 103 |
104 |
105 | 106 | 107 | 108 | 109 |
110 | @endforeach 111 |
112 |
113 | 114 | 115 |
116 | @else 117 | 118 | @endif 119 |
120 | 121 | 122 |
123 | -------------------------------------------------------------------------------- /Resources/views/posts/show.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ $post->name }} 4 | 5 | 6 | 11 | 12 | 13 | @stack('new_button_start') 14 | 15 | @can('create-my-blog-posts') 16 | 17 | {{ trans('general.title.new', ['type' => trans_choice('my-blog::general.posts', 1)]) }} 18 | 19 | @endcan 20 | 21 | @stack('edit_button_start') 22 | 23 | @can('update-my-blog-posts') 24 | 25 | {{ trans('general.edit') }} 26 | 27 | @endcan 28 | 29 | @stack('more_button_start') 30 | 31 | 32 | 33 | more_horiz 34 | 35 | 36 | @stack('duplicate_button_start') 37 | 38 | @can('create-my-blog-posts') 39 | 40 | {{ trans('general.duplicate') }} 41 | 42 | @endcan 43 | 44 | @stack('delete_button_start') 45 | 46 | @can('delete-my-blog-posts') 47 | 48 | @endcan 49 | 50 | @stack('delete_button_end') 51 | 52 | 53 | @stack('more_button_end') 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | @stack('category_start') 62 | 63 |
64 |
65 | {{ trans_choice('general.categories', 1) }} 66 |
67 | 68 | 69 | {{ $post->category->name }} 70 | 71 |
72 | 73 | @stack('status_start') 74 | 75 |
76 |
77 | {{ trans('general.enabled') }} 78 |
79 | 80 | 81 | @if ($post->enabled) 82 | {{ trans('general.yes') }} 83 | @else 84 | {{ trans('general.no') }} 85 | @endif 86 | 87 |
88 | 89 | @stack('status_end') 90 |
91 | 92 | 93 |
94 | {{ $post->description }} 95 | 96 | @if (setting('my-blog.enable_comments')) 97 |

{{ trans_choice('my-blog::general.comments', 2) }}

98 | 99 | 100 | 101 | 102 | 103 | 104 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | @foreach($comments as $item) 124 | 125 | 126 | 127 | 128 | 129 | 130 | {{ $item->owner->name }} 131 | 132 | 133 | 134 | 135 |
136 | {{ $item->description }} 137 |
138 |
139 | 140 | 141 | 142 | 143 |
144 | @endforeach 145 |
146 |
147 | 148 | 149 | @endif 150 |
151 |
152 | 153 |
154 |
155 |
156 | 157 | 158 |
159 | -------------------------------------------------------------------------------- /Resources/views/widgets/latest_comments.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @include($class->views['header'], ['header_class' => '']) 3 | 4 | @if ($comments->count()) 5 |
    6 | @foreach($comments as $comment) 7 |
  • 8 |
    9 | {{ $comment->description }} 10 |
    11 | {{ $comment->owner->name }} 12 |
  • 13 | @endforeach 14 |
15 | @else 16 |
17 | {{ trans('general.no_records') }} 18 |
19 | @endif 20 |
21 | -------------------------------------------------------------------------------- /Resources/views/widgets/latest_posts.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @include($class->views['header'], ['header_class' => '']) 3 | 4 | @if ($posts->count()) 5 |
    6 | @foreach($posts as $post) 7 |
  • 8 | {{ $post->name }} 9 | {{ $post->category->name }} 10 |
  • 11 | @endforeach 12 |
13 | @else 14 |
15 | {{ trans('general.no_records') }} 16 |
17 | @endif 18 |
19 | -------------------------------------------------------------------------------- /Resources/views/widgets/top_authors.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @include($class->views['header'], ['header_class' => '']) 3 | 4 | @if ($authors->count()) 5 |
    6 | @foreach($authors as $author) 7 |
  • 8 | {{ $author->name }} 9 | {{ $author->my_blog_posts_count }} 10 |
  • 11 | @endforeach 12 |
13 | @else 14 |
15 | {{ trans('general.no_records') }} 16 |
17 | @endif 18 |
19 | -------------------------------------------------------------------------------- /Routes/admin.php: -------------------------------------------------------------------------------- 1 | name('posts.duplicate'); 13 | Route::get('posts/{post}/enable', 'Posts@enable')->name('posts.enable'); 14 | Route::get('posts/{post}/disable', 'Posts@disable')->name('posts.disable'); 15 | Route::post('posts/import', 'Posts@import')->name('posts.import'); 16 | Route::get('posts/export', 'Posts@export')->name('posts.export'); 17 | Route::resource('posts', 'Posts'); 18 | Route::get('comments/export', 'Comments@export')->name('comments.export'); 19 | Route::resource('comments', 'Comments'); 20 | }); 21 | -------------------------------------------------------------------------------- /Routes/api.php: -------------------------------------------------------------------------------- 1 | name('posts.enable'); 13 | Route::get('posts/{post}/disable', 'Posts@disable')->name('posts.disable'); 14 | Route::apiResource('posts', 'Posts'); 15 | Route::apiResource('comments', 'Comments'); 16 | }); 17 | -------------------------------------------------------------------------------- /Routes/portal.php: -------------------------------------------------------------------------------- 1 | name('comments.store'); 14 | }); 15 | -------------------------------------------------------------------------------- /Tests/Feature/AdminMenuTest.php: -------------------------------------------------------------------------------- 1 | loginAs() 15 | ->get(route('dashboard')) 16 | ->assertOk() 17 | ->assertSeeInOrder([ 18 | '
  • ', 19 | '', 20 | '' . trans_choice('my-blog::general.posts', 2) . '', 21 | ], false); 22 | } 23 | 24 | public function testItShouldNotSeeAdminPostsMenuItem() 25 | { 26 | $this->detachPermissionsFromAdminRoles([ 27 | 'my-blog-posts' => 'r', 28 | ]); 29 | 30 | $this->loginAs() 31 | ->get(route('dashboard')) 32 | ->assertOk() 33 | ->assertDontSee('', false); 34 | } 35 | 36 | public function testItShouldSeeAdminCommentsMenuItem() 37 | { 38 | $this->loginAs() 39 | ->get(route('dashboard')) 40 | ->assertOk() 41 | ->assertSeeInOrder([ 42 | '
  • ', 43 | '', 44 | '' . trans_choice('my-blog::general.comments', 2) . '', 45 | ], false); 46 | } 47 | 48 | public function testItShouldNotSeeAdminCommentsMenuItem() 49 | { 50 | $this->detachPermissionsFromAdminRoles([ 51 | 'my-blog-comments' => 'r', 52 | ]); 53 | 54 | $this->loginAs() 55 | ->get(route('dashboard')) 56 | ->assertOk() 57 | ->assertDontSee('', false); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Tests/Feature/CommentsTest.php: -------------------------------------------------------------------------------- 1 | loginAs() 16 | ->get(route('my-blog.comments.index')) 17 | ->assertStatus(200) 18 | ->assertSeeText(trans_choice('my-blog::general.comments', 2)); 19 | } 20 | 21 | public function testItShouldSeeCommentCreatePage() 22 | { 23 | $this->loginAs() 24 | ->get(route('my-blog.comments.create')) 25 | ->assertStatus(200) 26 | ->assertSeeText(trans('general.title.new', ['type' => trans_choice('my-blog::general.comments', 1)])); 27 | } 28 | 29 | public function testItShouldCreateComment() 30 | { 31 | Post::factory()->enabled()->count(5)->create(); 32 | 33 | $request = $this->getRequest(); 34 | 35 | $this->loginAs() 36 | ->post(route('my-blog.comments.store'), $request) 37 | ->assertStatus(200); 38 | 39 | $this->assertFlashLevel('success'); 40 | 41 | $this->assertDatabaseHas('my_blog_comments', $request); 42 | } 43 | 44 | public function testItShouldSeeCommentUpdatePage() 45 | { 46 | Post::factory()->enabled()->count(5)->create(); 47 | 48 | $request = $this->getRequest(); 49 | 50 | $comment = $this->dispatch(new CreateComment($request)); 51 | 52 | $this->loginAs() 53 | ->get(route('my-blog.comments.edit', $comment->id)) 54 | ->assertStatus(200) 55 | ->assertSee($comment->description); 56 | } 57 | 58 | public function testItShouldUpdateComment() 59 | { 60 | Post::factory()->enabled()->count(5)->create(); 61 | 62 | $request = $this->getRequest(); 63 | 64 | $comment = $this->dispatch(new CreateComment($request)); 65 | 66 | $request['description'] = $this->faker->text(15); 67 | 68 | $this->loginAs() 69 | ->patch(route('my-blog.comments.update', $comment->id), $request) 70 | ->assertStatus(200) 71 | ->assertSee($request['description']); 72 | 73 | $this->assertFlashLevel('success'); 74 | 75 | $this->assertDatabaseHas('my_blog_comments', $request); 76 | } 77 | 78 | public function testItShouldDeleteComment() 79 | { 80 | Post::factory()->enabled()->count(5)->create(); 81 | 82 | $request = $this->getRequest(); 83 | 84 | $comment = $this->dispatch(new CreateComment($request)); 85 | 86 | $this->loginAs() 87 | ->delete(route('my-blog.comments.destroy', $comment->id)) 88 | ->assertStatus(200); 89 | 90 | $this->assertFlashLevel('success'); 91 | 92 | $this->assertSoftDeleted('my_blog_comments', $request); 93 | } 94 | 95 | public function testItShouldExportComments() 96 | { 97 | Post::factory()->enabled()->count(5)->create(); 98 | 99 | $count = 5; 100 | Comment::factory()->count($count)->create(); 101 | 102 | \Excel::fake(); 103 | 104 | $this->loginAs() 105 | ->get(route('my-blog.comments.export')) 106 | ->assertStatus(200); 107 | 108 | \Excel::matchByRegex(); 109 | 110 | \Excel::assertDownloaded( 111 | '/' . \Str::filename(trans_choice('my-blog::general.comments', 2)) . '-\d{10}\.xlsx/', 112 | function (Export $export) use ($count) { 113 | // Assert that the correct export is downloaded. 114 | return $export->collection()->count() === $count; 115 | } 116 | ); 117 | } 118 | 119 | public function testItShouldExportSelectedComments() 120 | { 121 | Post::factory()->enabled()->count(5)->create(); 122 | 123 | $create_count = 5; 124 | $select_count = 3; 125 | 126 | $comments = Comment::factory()->count($create_count)->create(); 127 | 128 | \Excel::fake(); 129 | 130 | $this->loginAs() 131 | ->post( 132 | route('bulk-actions.action', ['group' => 'my-blog', 'type' => 'comments']), 133 | ['handle' => 'export', 'selected' => $comments->take($select_count)->pluck('id')->toArray()] 134 | ) 135 | ->assertStatus(200); 136 | 137 | \Excel::matchByRegex(); 138 | 139 | \Excel::assertDownloaded( 140 | '/' . \Str::filename(trans_choice('my-blog::general.comments', 2)) . '-\d{10}\.xlsx/', 141 | function (Export $export) use ($select_count) { 142 | // Assert that the correct export is downloaded. 143 | return $export->collection()->count() === $select_count; 144 | } 145 | ); 146 | } 147 | 148 | public function getRequest() 149 | { 150 | return Comment::factory()->raw(); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Tests/Feature/PortalMenuTest.php: -------------------------------------------------------------------------------- 1 | loginAs(Contact::first()->user) 16 | ->get(route('portal.dashboard')) 17 | ->assertOk() 18 | ->assertSeeInOrder([ 19 | '
  • ', 20 | '', 21 | '' . trans_choice('my-blog::general.posts', 2) . '', 22 | ], false); 23 | } 24 | 25 | public function testItShouldNotSeePortalPostsMenuItem() 26 | { 27 | $this->detachPermissionsFromPortalRoles([ 28 | 'my-blog-portal-posts' => 'r', 29 | ]); 30 | 31 | $this->loginAs(Contact::first()->user) 32 | ->get(route('portal.dashboard')) 33 | ->assertOk() 34 | ->assertDontSee('', false); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/Feature/PostsTest.php: -------------------------------------------------------------------------------- 1 | loginAs() 17 | ->get(route('my-blog.posts.index')) 18 | ->assertStatus(200) 19 | ->assertSeeText(trans_choice('my-blog::general.posts', 2)); 20 | } 21 | 22 | public function testItShouldSeePostCreatePage() 23 | { 24 | $this->loginAs() 25 | ->get(route('my-blog.posts.create')) 26 | ->assertStatus(200) 27 | ->assertSeeText(trans('general.title.new', ['type' => trans_choice('my-blog::general.posts', 1)])); 28 | } 29 | 30 | public function testItShouldCreatePost() 31 | { 32 | $request = $this->getRequest(); 33 | 34 | $this->loginAs() 35 | ->post(route('my-blog.posts.store'), $request) 36 | ->assertStatus(200); 37 | 38 | $this->assertFlashLevel('success'); 39 | 40 | $this->assertDatabaseHas('my_blog_posts', $request); 41 | } 42 | 43 | public function testItShouldSeePostUpdatePage() 44 | { 45 | $request = $this->getRequest(); 46 | 47 | $post = $this->dispatch(new CreatePost($request)); 48 | 49 | $this->loginAs() 50 | ->get(route('my-blog.posts.edit', $post->id)) 51 | ->assertStatus(200) 52 | ->assertSee($post->name); 53 | } 54 | 55 | public function testItShouldUpdatePost() 56 | { 57 | $request = $this->getRequest(); 58 | 59 | $post = $this->dispatch(new CreatePost($request)); 60 | 61 | $request['name'] = $this->faker->text(15); 62 | 63 | $this->loginAs() 64 | ->patch(route('my-blog.posts.update', $post->id), $request) 65 | ->assertStatus(200) 66 | ->assertSee($request['name']); 67 | 68 | $this->assertFlashLevel('success'); 69 | 70 | $this->assertDatabaseHas('my_blog_posts', $request); 71 | } 72 | 73 | public function testItShouldDeletePost() 74 | { 75 | $request = $this->getRequest(); 76 | 77 | $post = $this->dispatch(new CreatePost($request)); 78 | 79 | $this->loginAs() 80 | ->delete(route('my-blog.posts.destroy', $post->id)) 81 | ->assertStatus(200); 82 | 83 | $this->assertFlashLevel('success'); 84 | 85 | $this->assertSoftDeleted('my_blog_posts', $request); 86 | } 87 | 88 | public function testItShouldExportPosts() 89 | { 90 | $count = 5; 91 | Post::factory()->count($count)->create(); 92 | 93 | \Excel::fake(); 94 | 95 | $this->loginAs() 96 | ->get(route('my-blog.posts.export')) 97 | ->assertStatus(200); 98 | 99 | \Excel::matchByRegex(); 100 | 101 | \Excel::assertDownloaded( 102 | '/' . \Str::filename(trans_choice('my-blog::general.posts', 2)) . '-\d{10}\.xlsx/', 103 | function (Export $export) use ($count) { 104 | // Assert that the correct export is downloaded. 105 | return $export->collection()->count() === $count; 106 | } 107 | ); 108 | } 109 | 110 | public function testItShouldExportSelectedPosts() 111 | { 112 | $create_count = 5; 113 | $select_count = 3; 114 | 115 | $posts = Post::factory()->count($create_count)->create(); 116 | 117 | \Excel::fake(); 118 | 119 | $this->loginAs() 120 | ->post( 121 | route('bulk-actions.action', ['group' => 'my-blog', 'type' => 'posts']), 122 | ['handle' => 'export', 'selected' => $posts->take($select_count)->pluck('id')->toArray()] 123 | ) 124 | ->assertStatus(200); 125 | 126 | \Excel::matchByRegex(); 127 | 128 | \Excel::assertDownloaded( 129 | '/' . \Str::filename(trans_choice('my-blog::general.posts', 2)) . '-\d{10}\.xlsx/', 130 | function (Export $export) use ($select_count) { 131 | // Assert that the correct export is downloaded. 132 | return $export->collection()->count() === $select_count; 133 | } 134 | ); 135 | } 136 | 137 | public function testItShouldImportPosts() 138 | { 139 | \Excel::fake(); 140 | 141 | $this->loginAs() 142 | ->post( 143 | route('my-blog.posts.import'), 144 | [ 145 | 'import' => UploadedFile::fake()->createWithContent( 146 | 'posts.xlsx', 147 | File::get(module_path('my-blog', 'Resources/assets/posts.xlsx')) 148 | ), 149 | ] 150 | ) 151 | ->assertStatus(200); 152 | 153 | \Excel::assertImported('posts.xlsx'); 154 | 155 | $this->assertFlashLevel('success'); 156 | } 157 | 158 | public function getRequest() 159 | { 160 | return Post::factory()->enabled()->raw(); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Widgets/CommentsByPost.php: -------------------------------------------------------------------------------- 1 | enabled()->orderBy('comments_count', 'desc'); 19 | 20 | $this->applyFilters($query, ['date_field' => 'created_at'])->each(function ($post) { 21 | $random_color = '#' . dechex(rand(0x000000, 0xFFFFFF)); 22 | 23 | $this->addToDonut($random_color, $post->name, $post->comments_count); 24 | }); 25 | 26 | $chart = $this->getDonutChart(trans('my-blog::widgets.comments_by_post'), 0, 160, 6); 27 | 28 | return $this->view('widgets.donut_chart', [ 29 | 'chart' => $chart, 30 | ]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Widgets/LatestComments.php: -------------------------------------------------------------------------------- 1 | 'w-full lg:w-1/3 px-6', 14 | ]; 15 | 16 | public $description = 'my-blog::widgets.description.latest_comments'; 17 | 18 | public $report_class = 'Modules\MyBlog\Reports\CommentSummary'; 19 | 20 | public function show() 21 | { 22 | $query = Comment::with('owner')->orderBy('created_at', 'desc')->take(5); 23 | 24 | $comments = $this->applyFilters($query, ['date_field' => 'created_at'])->get(); 25 | 26 | return $this->view('my-blog::widgets.latest_comments', [ 27 | 'comments' => $comments, 28 | ]); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Widgets/LatestPosts.php: -------------------------------------------------------------------------------- 1 | 'w-full lg:w-1/3 px-6', 14 | ]; 15 | 16 | public $description = 'my-blog::widgets.description.latest_posts'; 17 | 18 | public $report_class = 'Modules\MyBlog\Reports\PostSummary'; 19 | 20 | public function show() 21 | { 22 | $query = Post::with('category')->enabled()->orderBy('created_at', 'desc')->take(5); 23 | 24 | $posts = $this->applyFilters($query, ['date_field' => 'created_at'])->get(); 25 | 26 | return $this->view('my-blog::widgets.latest_posts', [ 27 | 'posts' => $posts, 28 | ]); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Widgets/PostsByCategory.php: -------------------------------------------------------------------------------- 1 | type('post')->orderBy('my_blog_posts_count', 'desc'); 19 | 20 | $query->whereHas('my_blog_posts', function ($query) { 21 | $this->applyFilters($query, ['date_field' => 'created_at']); 22 | })->each(function ($category) { 23 | $this->addToDonut($category->color, $category->name, $category->my_blog_posts_count); 24 | }); 25 | 26 | $chart = $this->getDonutChart(trans('my-blog::widgets.posts_by_category'), 0, 160, 6); 27 | 28 | return $this->view('widgets.donut_chart', [ 29 | 'chart' => $chart, 30 | ]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Widgets/TopAuthors.php: -------------------------------------------------------------------------------- 1 | 'w-full lg:w-1/3 px-6', 14 | ]; 15 | 16 | public $description = 'my-blog::widgets.description.top_authors'; 17 | 18 | public function show() 19 | { 20 | $query = User::withCount('my_blog_posts')->orderBy('my_blog_posts_count', 'desc'); 21 | 22 | $authors = $query->whereHas('my_blog_posts', function ($query) { 23 | $this->applyFilters($query, ['date_field' => 'created_at']); 24 | })->get(); 25 | 26 | return $this->view('my-blog::widgets.top_authors', [ 27 | 'authors' => $authors, 28 | ]); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "autoloader-suffix": "MyBlog" 4 | }, 5 | "require": {}, 6 | "replace": { 7 | "guzzlehttp/guzzle": "*", 8 | "guzzlehttp/psr7": "*", 9 | "laravel/framework": "*", 10 | "symfony/http-foundation": "*" 11 | }, 12 | "scripts": { 13 | "test": [ 14 | "composer install --prefer-dist --no-interaction --no-scripts --no-suggest --no-progress --no-ansi" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/Resources/assets/js/posts.min.js": "/Resources/assets/js/posts.min.js", 3 | "/Resources/assets/js/comments.min.js": "/Resources/assets/js/comments.min.js" 4 | } 5 | -------------------------------------------------------------------------------- /module.json: -------------------------------------------------------------------------------- 1 | { 2 | "alias": "my-blog", 3 | "icon": "edit", 4 | "version": "2.0.0", 5 | "active": 1, 6 | "providers": [ 7 | "Modules\\MyBlog\\Providers\\Event", 8 | "Modules\\MyBlog\\Providers\\Main", 9 | "Modules\\MyBlog\\Providers\\Observer" 10 | ], 11 | "aliases": {}, 12 | "files": [], 13 | "requires": [], 14 | "reports": [ 15 | "Modules\\MyBlog\\Reports\\PostSummary", 16 | "Modules\\MyBlog\\Reports\\CommentSummary" 17 | ], 18 | "widgets": [ 19 | "Modules\\MyBlog\\Widgets\\PostsByCategory", 20 | "Modules\\MyBlog\\Widgets\\CommentsByPost", 21 | "Modules\\MyBlog\\Widgets\\TopAuthors", 22 | "Modules\\MyBlog\\Widgets\\LatestPosts", 23 | "Modules\\MyBlog\\Widgets\\LatestComments" 24 | ], 25 | "settings": [ 26 | { 27 | "type": "text", 28 | "name": "header", 29 | "title": "my-blog::settings.header", 30 | "attributes": { 31 | "required": "required" 32 | }, 33 | "rules": "required|string" 34 | }, 35 | { 36 | "type": "switch", 37 | "name": "enable_comments", 38 | "title": "my-blog::settings.enable_comments", 39 | "enable": "general.yes", 40 | "disable": "general.no", 41 | "attributes": {}, 42 | "rules": "integer|boolean" 43 | }, 44 | { 45 | "type": "textarea", 46 | "name": "meta_description", 47 | "title": "my-blog::settings.meta_description", 48 | "attributes": {}, 49 | "rules": "nullable|string" 50 | } 51 | ], 52 | "extra-modules": {} 53 | } 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "module-my-blog", 3 | "version": "3.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "npm run development", 7 | "development": "mix", 8 | "watch": "mix watch", 9 | "watch-poll": "mix watch -- --watch-options-poll=1000", 10 | "hot": "mix watch --hot", 11 | "prod": "npm run production", 12 | "production": "mix --production" 13 | }, 14 | "devDependencies": { 15 | "cross-env": "^5.2.1", 16 | "laravel-mix": "^6.0.39", 17 | "resolve-url-loader": "^5.0.0" 18 | }, 19 | "author": "Akaunting Inc ", 20 | "description": "Free Accounting Software" 21 | } 22 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | const mix = require('laravel-mix'); 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Mix Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Mix provides a clean, fluent API for defining some Webpack build steps 9 | | for your Laravel application. By default, we are compiling the Sass 10 | | file for the application as well as bundling up all the JS files. 11 | | 12 | */ 13 | 14 | mix.options({ 15 | terser: { 16 | extractComments: false, 17 | } 18 | }) 19 | .js('Resources/assets/js/posts.js', 'Resources/assets/js/posts.min.js') 20 | .js('Resources/assets/js/comments.js', 'Resources/assets/js/comments.min.js') 21 | .vue(); 22 | --------------------------------------------------------------------------------