├── public
└── .gitkeep
├── src
├── lang
│ ├── .gitkeep
│ ├── en
│ │ └── base.php
│ ├── fr
│ │ └── base.php
│ └── de
│ │ └── base.php
├── views
│ ├── .gitkeep
│ ├── partials
│ │ ├── errorbox.blade.php
│ │ ├── csslinks.blade.php
│ │ ├── postbutton.blade.php
│ │ ├── pathdisplay.blade.php
│ │ └── message.blade.php
│ ├── layouts
│ │ └── master.blade.php
│ ├── topic.blade.php
│ ├── edit.blade.php
│ ├── post.blade.php
│ ├── index.blade.php
│ ├── reply.blade.php
│ └── category.blade.php
├── config
│ ├── .gitkeep
│ ├── routes.php
│ ├── integration.php
│ └── rights.php
├── migrations
│ ├── .gitkeep
│ ├── 2014_05_19_151759_create_forum_table_categories.php
│ ├── 2014_05_19_152425_create_forum_table_topics.php
│ └── 2014_05_19_152611_create_forum_table_messages.php
├── Atrakeur
│ └── Forum
│ │ ├── Repositories
│ │ ├── TopicsRepository.php
│ │ ├── CategoriesRepository.php
│ │ ├── MessagesRepository.php
│ │ └── AbstractBaseRepository.php
│ │ ├── Controllers
│ │ ├── AbstractForumController.php
│ │ ├── AbstractViewForumController.php
│ │ └── AbstractPostForumController.php
│ │ ├── ForumServiceProvider.php
│ │ ├── Models
│ │ ├── ForumMessage.php
│ │ ├── ForumTopic.php
│ │ ├── AbstractForumBaseModel.php
│ │ └── ForumCategory.php
│ │ └── Commands
│ │ └── InstallCommand.php
└── routes.php
├── tests
├── .gitkeep
├── ForumBaseTest.php
├── Controllers
│ ├── AbstractForumControllerTest.php
│ └── AbstractViewForumControllerTest.php
└── Repositories
│ └── CategoriesRepositoryTest.php
├── .gitignore
├── .travis.yml
├── phpunit.xml
├── composer.json
├── LICENSE
└── readme.md
/public/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/lang/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/views/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/config/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/migrations/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.phar
3 | composer.lock
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.4
5 | - 5.5
6 | - 5.6
7 |
8 | before_script:
9 | - composer self-update
10 | - composer install --prefer-source --no-interaction --dev
11 |
12 | script: phpunit
13 |
--------------------------------------------------------------------------------
/tests/ForumBaseTest.php:
--------------------------------------------------------------------------------
1 | has())
2 | @foreach ($errors->all() as $error)
3 |
4 |
5 | {{ $error }}
6 |
7 | @endforeach
8 | @endif
--------------------------------------------------------------------------------
/src/views/partials/csslinks.blade.php:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/views/layouts/master.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ trans('forum::base.home_title') }}
7 | @include('forum::partials.csslinks')
8 |
9 |
12 |
13 |
14 | @if(isset($content))
15 | {{ $content }}
16 | @else
17 | {{ trans('forum::base.no_content') }}
18 | @endif
19 |
20 |
--------------------------------------------------------------------------------
/src/views/partials/postbutton.blade.php:
--------------------------------------------------------------------------------
1 |
12 | @if (isset($accessModel) && $accessModel->canPost === true)
13 | {{{ $message }}}
14 | @endif
--------------------------------------------------------------------------------
/src/views/partials/pathdisplay.blade.php:
--------------------------------------------------------------------------------
1 | {{ trans('forum::base.index') }}
2 | @if (isset($parentCategory) && $parentCategory)
3 | > {{{ $parentCategory->title }}}
4 | @endif
5 | @if (isset($category) && $category)
6 | > {{{ $category->title }}}
7 | @endif
8 | @if (isset($topic) && $topic)
9 | > {{{ $topic->title }}}
10 | @endif
--------------------------------------------------------------------------------
/src/Atrakeur/Forum/Repositories/TopicsRepository.php:
--------------------------------------------------------------------------------
1 | model = $model;
10 | }
11 |
12 | public function getById($ident, array $with = array())
13 | {
14 | if (!is_numeric($ident))
15 | {
16 | throw new \InvalidArgumentException();
17 | }
18 |
19 | return $this->getFirstBy('id', $ident, $with);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/views/partials/message.blade.php:
--------------------------------------------------------------------------------
1 |
2 | |
3 | {{{ $message->author->username }}}
4 | |
5 |
6 | {{ nl2br(e($message->data)) }}
7 | |
8 |
9 |
10 | |
11 | @include('forum::partials.postbutton', array('message' => 'Edit', 'url' => $message->postUrl, 'accessModel' => $message))
12 | |
13 |
14 | {{ trans('forum::base.posted_at') }} {{ $message->created_at }}
15 | @if ($message->updated_at != null && $message->created_at != $message->updated_at)
16 | {{ trans('forum::base.last_update') }} {{ $message->updated_at }}
17 | @endif
18 | |
19 |
--------------------------------------------------------------------------------
/src/views/topic.blade.php:
--------------------------------------------------------------------------------
1 | @include('forum::partials.pathdisplay')
2 |
3 | @include('forum::partials.postbutton', array('message' => trans('forum::base.new_reply'), 'url' => $topic->postUrl, 'accessModel' => $topic))
4 |
5 |
6 |
7 | |
8 | {{ trans('forum::base.author') }}
9 | |
10 |
11 | {{ trans('forum::base.message') }}
12 | |
13 |
14 |
15 |
16 | @foreach ($messages as $message)
17 | @include('forum::partials.message', compact('message'))
18 | @endforeach
19 |
20 |
21 | {{ $paginationLinks }}
22 |
--------------------------------------------------------------------------------
/src/config/routes.php:
--------------------------------------------------------------------------------
1 | true,
14 |
15 | /*
16 | |--------------------------------------------------------------------------
17 | | Forum route base
18 | |--------------------------------------------------------------------------
19 | |
20 | | The base url of the forum inside your application including trailing slash
21 | |
22 | */
23 | 'base' => '/forum/',
24 |
25 | );
26 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | ./tests/
16 |
17 |
18 |
19 |
20 | ./vendor
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/migrations/2014_05_19_151759_create_forum_table_categories.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->integer('parent_category')->unsigned()->nullable();
19 | $table->string('title');
20 | $table->string('subtitle');
21 | });
22 | }
23 |
24 | /**
25 | * Reverse the migrations.
26 | *
27 | * @return void
28 | */
29 | public function down()
30 | {
31 | Schema::drop('forum_categories');
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "atrakeur/forum",
3 | "description": "A complete forum package designed for integration in any website build with laravel",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Atrakeur",
8 | "email": "atrakeur@gmail.com"
9 | }
10 | ],
11 | "require": {
12 | "php": ">=5.3.0",
13 | "illuminate/support": "4.2.*"
14 | },
15 | "require-dev": {
16 | "orchestra/testbench": "2.2.*",
17 | "mockery/mockery": "0.9.*"
18 | },
19 | "autoload": {
20 | "classmap": [
21 | "src/migrations",
22 | "tests"
23 | ],
24 | "psr-0": {
25 | "Atrakeur\\Forum\\": "src/"
26 | }
27 | },
28 | "minimum-stability": "stable"
29 | }
30 |
--------------------------------------------------------------------------------
/src/migrations/2014_05_19_152425_create_forum_table_topics.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 |
19 | /* Attributes */
20 | $table->integer('parent_category')->unsigned();
21 | $table->integer('author_id')->unsigned();
22 | $table->string('title');
23 |
24 | $table->timestamps();
25 | $table->softDeletes();
26 | });
27 | }
28 |
29 | /**
30 | * Reverse the migrations.
31 | *
32 | * @return void
33 | */
34 | public function down()
35 | {
36 | Schema::drop('forum_topics');
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/migrations/2014_05_19_152611_create_forum_table_messages.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 |
19 | /* Attributes */
20 | $table->integer('parent_topic')->unsigned();
21 | $table->integer('author_id')->unsigned();
22 | $table->text('data');
23 |
24 | $table->timestamps();
25 | $table->softDeletes();
26 | });
27 | }
28 |
29 | /**
30 | * Reverse the migrations.
31 | *
32 | * @return void
33 | */
34 | public function down()
35 | {
36 | Schema::drop('forum_messages');
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/views/edit.blade.php:
--------------------------------------------------------------------------------
1 | @include('forum::partials.pathdisplay', compact('parentCategory', 'category', 'topic'))
2 |
3 | @include('forum::partials.errorbox')
4 |
5 | {{ Form::open(array('url' => $actionUrl, 'class' => 'form-horizontal')) }}
6 |
27 | {{ Form::close() }}
28 |
--------------------------------------------------------------------------------
/src/Atrakeur/Forum/Repositories/CategoriesRepository.php:
--------------------------------------------------------------------------------
1 | model = $model;
10 | }
11 |
12 | public function getById($ident, array $with = array())
13 | {
14 | if (!is_numeric($ident))
15 | {
16 | throw new \InvalidArgumentException();
17 | }
18 |
19 | return $this->getFirstBy('id', $ident, $with);
20 | }
21 |
22 | public function getByParent($parent, array $with = array())
23 | {
24 | if (is_array($parent) && isset($parent['id']))
25 | {
26 | $parent = $parent['id'];
27 | }
28 |
29 | if ($parent != null && !is_numeric($parent))
30 | {
31 | throw new \InvalidArgumentException();
32 | }
33 |
34 | return $this->getManyBy('parent_category', $parent, $with);
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/Atrakeur/Forum/Controllers/AbstractForumController.php:
--------------------------------------------------------------------------------
1 | layout != NULL)
14 | {
15 | $this->layout = \View::make($this->layout);
16 | }
17 | }
18 |
19 | protected function getCurrentUser()
20 | {
21 | $userfunc = \Config::get('forum::integration.currentuser');
22 |
23 | $user = $userfunc();
24 | if (is_object($user) && get_class($user) == \Config::get('forum::integration.usermodel'))
25 | {
26 | return $user;
27 | }
28 |
29 | return null;
30 | }
31 |
32 | protected function fireEvent($event, $data)
33 | {
34 | return \Event::fire($event, $data);
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/views/post.blade.php:
--------------------------------------------------------------------------------
1 | @include('forum::partials.pathdisplay', compact('parentCategory', 'category', 'topic'))
2 |
3 | @include('forum::partials.errorbox')
4 |
5 | {{ Form::open(array('url' => $actionUrl, 'class' => 'form-horizontal')) }}
6 |
35 | {{ Form::close() }}
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Valentin Letourneur
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.
--------------------------------------------------------------------------------
/src/routes.php:
--------------------------------------------------------------------------------
1 | model = $model;
10 | }
11 |
12 | public function getById($messageId, array $with = array())
13 | {
14 | if (!is_numeric($messageId))
15 | {
16 | throw new \InvalidArgumentException();
17 | }
18 |
19 | return $this->getFirstBy('id', $messageId, $with);
20 | }
21 |
22 | public function getByTopic($topicId, array $with = array())
23 | {
24 | if (!is_numeric($topicId))
25 | {
26 | throw new \InvalidArgumentException();
27 | }
28 |
29 | return $this->getManyBy('parent_topic', $topicId, $with);
30 | }
31 |
32 | public function getLastByTopic($topicId, $count = 10, array $with = array())
33 | {
34 | if (!is_numeric($topicId))
35 | {
36 | throw new \InvalidArgumentException();
37 | }
38 |
39 | $model = $this->model->where('parent_topic', '=', $topicId);
40 | $model = $model->orderBy('created_at', 'DESC')->take($count);
41 | $model = $model->with($with);
42 | return $this->model->convertToObject($model->get());
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/views/index.blade.php:
--------------------------------------------------------------------------------
1 | @include('forum::partials.pathdisplay')
2 |
3 | @foreach ($categories as $category)
4 |
5 |
6 |
7 | |
8 |
11 | {{{ $category->subtitle }}}
12 | |
13 |
14 |
15 |
16 |
17 | | {{ trans('forum::base.col_forum') }} |
18 | {{ trans('forum::base.col_topics') }} |
19 | {{ trans('forum::base.col_posts') }} |
20 |
21 | @if (count($category->subcategories) > 0)
22 | @foreach($category->subcategories AS $subcategory)
23 |
24 | |
25 |
28 | {{{ $subcategory->subtitle }}}
29 | |
30 | {{ $subcategory->topicCount }} |
31 | {{ $subcategory->replyCount }} |
32 |
33 | @endforeach
34 | @else
35 |
36 | |
37 | {{ trans('forum::base.no_categories') }}
38 | |
39 |
40 | @endif
41 |
42 |
43 | @endforeach
44 |
--------------------------------------------------------------------------------
/src/lang/en/base.php:
--------------------------------------------------------------------------------
1 | 'Laravel forum',
14 |
15 | 'col_forum' => 'Forum',
16 | 'col_topics' => 'Topics',
17 | 'col_posts' => 'Posts',
18 |
19 | 'index' => 'Index',
20 | 'author' => 'Author',
21 | 'message' => 'Message',
22 | 'subject' => 'Subject',
23 | 'title' => 'Title',
24 |
25 | 'label_your_message' => 'Your message',
26 |
27 | 'latest_messages' => 'Latests messages',
28 | 'edit_message' => 'Edit a message',
29 | 'post_message' => 'Post a new message',
30 | 'posting_into' => 'You\'re posting into',
31 | 'your_editing' => 'You\'re editing',
32 | 'posted_at' => 'Posted at',
33 | 'last_update' => 'Last update at',
34 |
35 | 'reply' => 'Reply',
36 | 'new_reply' => 'New Reply',
37 | 'new_topic' => 'New Topic',
38 | 'new_topic_title' => 'Post a new topic',
39 | 'send' => 'Send',
40 | 'first_topic' => 'Post the first!',
41 |
42 | 'no_categories' => 'No categories found',
43 | 'no_topics' => 'No topics found',
44 | 'no_content' => 'Nothing to display here (did you set $this->layout->content in your controller?)',
45 | );
--------------------------------------------------------------------------------
/src/lang/fr/base.php:
--------------------------------------------------------------------------------
1 | 'Laravel forum',
14 |
15 | 'col_forum' => 'Forum',
16 | 'col_topics' => 'Sujets',
17 | 'col_posts' => 'Messages',
18 |
19 | 'index' => 'Index',
20 | 'author' => 'Auteur',
21 | 'message' => 'Message',
22 | 'subject' => 'Sujet',
23 | 'title' => 'Titre',
24 |
25 | 'label_your_message' => 'Votre message',
26 |
27 | 'latest_messages' => 'Derniers messages',
28 | 'edit_message' => 'Modifier un message',
29 | 'post_message' => 'Poster un nouveau message',
30 | 'posting_into' => 'Vous postez dans',
31 | 'your_editing' => 'Vous éditez',
32 | 'posted_at' => 'Posté à',
33 | 'last_update' => 'Dernière mise à jour à',
34 |
35 | 'reply' => 'Répondre',
36 | 'new_reply' => 'Nouvelle réponse',
37 | 'new_topic' => 'Nouveau sujet',
38 | 'new_topic_title' => 'Publier un nouveau sujet',
39 | 'send' => 'Envoyer',
40 | 'first_topic' => 'Postez le premier!',
41 |
42 | 'no_categories' => 'Aucune catégorie trouvée',
43 | 'no_topics' => 'Aucun thème trouvé',
44 | 'no_content' => 'Rien à afficher (ici avez-vous mis $this->layout->content dans votre contrôleur?)',
45 | );
--------------------------------------------------------------------------------
/src/Atrakeur/Forum/ForumServiceProvider.php:
--------------------------------------------------------------------------------
1 | package('atrakeur/forum');
23 |
24 | if (\Config::get('forum::routes.enable')) {
25 | $routebase = \Config::get('forum::routes.base');
26 | $viewController = \Config::get('forum::integration.viewcontroller');
27 | $postController = \Config::get('forum::integration.postcontroller');
28 |
29 | include __DIR__.'/../../routes.php';
30 | }
31 | }
32 |
33 | /**
34 | * Register the service provider.
35 | *
36 | * @return void
37 | */
38 | public function register()
39 | {
40 | $this->registerCommands();
41 | }
42 |
43 | /**
44 | * Register package artisan commands.
45 | *
46 | * @return void
47 | */
48 | public function registerCommands()
49 | {
50 | $this->app['foruminstallcommand'] = $this->app->share(function($app)
51 | {
52 | return new InstallCommand;
53 | });
54 | $this->commands('foruminstallcommand');
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/src/views/reply.blade.php:
--------------------------------------------------------------------------------
1 | @include('forum::partials.pathdisplay', compact('parentCategory', 'category', 'topic'))
2 |
3 | @include('forum::partials.errorbox')
4 |
5 | @if (isset($prevMessages) && count($prevMessages) > 0)
6 |
7 | {{ trans('forum::base.latest_messages') }}
8 |
9 |
10 |
11 |
12 | |
13 | {{ trans('forum::base.author') }}
14 | |
15 |
16 | {{ trans('forum::base.message') }}
17 | |
18 |
19 |
20 |
21 | @foreach ($prevMessages as $message)
22 | @include('forum::partials.message', compact('message'))
23 | @endforeach
24 |
25 |
26 | @endif
27 |
28 | {{ Form::open(array('url' => $actionUrl, 'class' => 'form-horizontal')) }}
29 |
50 | {{ Form::close() }}
51 |
--------------------------------------------------------------------------------
/src/Atrakeur/Forum/Models/ForumMessage.php:
--------------------------------------------------------------------------------
1 | belongsTo('\Atrakeur\Forum\Models\ForumTopic', 'parent_topic');
18 | }
19 |
20 | public function author()
21 | {
22 | return $this->belongsTo(\Config::get('forum::integration.usermodel'), 'author_id');
23 | }
24 |
25 | public function scopeWhereTopicIn($query, Array $topics)
26 | {
27 | if (count($topics) == 0)
28 | {
29 | return $query;
30 | }
31 |
32 | return $query->whereIn('parent_topic', $topics);
33 | }
34 |
35 | public function getUrlAttribute()
36 | {
37 | //TODO add page get parameter
38 | return $this->topic->url;
39 | }
40 |
41 | public function getPostUrlAttribute()
42 | {
43 | $topic = $this->topic;
44 | $category = $topic->category;
45 |
46 | return action(\Config::get('forum::integration.postcontroller').'@postEditMessage',
47 | array(
48 | 'categoryId' => $category->id,
49 | 'categoryUrl' => \Str::slug($category->title, '_'),
50 | 'topicId' => $topic->id,
51 | 'topicUrl' => \Str::slug($topic->title, '_'),
52 | 'messageId' => $this->id
53 | )
54 | );
55 | }
56 |
57 | public function getCanPostAttribute()
58 | {
59 | return $this->computeCanPostAttribute('rights.postmessage');
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/Atrakeur/Forum/Repositories/AbstractBaseRepository.php:
--------------------------------------------------------------------------------
1 | model->where($index, '=', $value)->with($with)->first();
12 | return $this->model->convertToObject($model);
13 | }
14 |
15 | protected function getManyBy($index, $value, array $with = array())
16 | {
17 | $model = $this->model->where($index, '=', $value)->with($with);
18 |
19 | if ($this->itemsPerPage != 0)
20 | {
21 | $model = $model->paginate($this->itemsPerPage);
22 | }
23 | else
24 | {
25 | $model = $model->get();
26 | }
27 |
28 | return $this->model->convertToObject($model);
29 | }
30 |
31 | public function paginate($itemsPerPage = 0)
32 | {
33 | if (!is_numeric($itemsPerPage))
34 | {
35 | throw new \InvalidArgumentException();
36 | }
37 |
38 | $this->itemsPerPage = $itemsPerPage;
39 | }
40 |
41 | public function getPaginationLinks()
42 | {
43 | return $this->model->paginate($this->itemsPerPage)->links();
44 | }
45 |
46 | public function create(\stdClass $data)
47 | {
48 | //TODO validate?
49 | $array = get_object_vars($data);
50 | $model = $this->model->create($array);
51 | return $this->model->convertToObject($model);
52 | }
53 |
54 | public function update(\stdClass $data)
55 | {
56 | //TODO validate?
57 | $array = get_object_vars($data);
58 | $model = $this->model->find($array['id']);
59 | if ($model != null)
60 | {
61 | $model->fill($array);
62 | $model->save();
63 | return $this->model->convertToObject($model);
64 | }
65 | else
66 | {
67 | throw new \InvalidArgumentException('Data must contain an existing id to update');
68 | }
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/Atrakeur/Forum/Models/ForumTopic.php:
--------------------------------------------------------------------------------
1 | belongsTo('\Atrakeur\Forum\Models\ForumCategory', 'parent_category');
19 | }
20 |
21 | public function author()
22 | {
23 | return $this->belongsTo(\Config::get('forum::integration.usermodel'), 'author_id');
24 | }
25 |
26 | public function messages()
27 | {
28 | return $this->hasMany('\Atrakeur\Forum\Models\ForumMessage', 'parent_topic');
29 | }
30 |
31 | public function getReplyCountAttribute()
32 | {
33 | return $this->messages()->count();
34 | }
35 |
36 | public function getUrlAttribute()
37 | {
38 | return action(\Config::get('forum::integration.viewcontroller').'@getTopic',
39 | array(
40 | 'categoryId' => $this->category->id,
41 | 'categoryUrl' => \Str::slug($this->category->title, '_'),
42 | 'topicId' => $this->id,
43 | 'topicUrl' => \Str::slug($this->title, '_'),
44 | )
45 | );
46 | }
47 |
48 | public function getPostUrlAttribute()
49 | {
50 | return action(\Config::get('forum::integration.postcontroller').'@postNewMessage',
51 | array(
52 | 'categoryId' => $this->category->id,
53 | 'categoryUrl' => \Str::slug($this->category->title, '_'),
54 | 'topicId' => $this->id,
55 | 'topicUrl' => \Str::slug($this->title, '_'),
56 | )
57 | );
58 | }
59 |
60 | public function getCanPostAttribute()
61 | {
62 | return $this->computeCanPostAttribute('rights.posttopic');
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/config/integration.php:
--------------------------------------------------------------------------------
1 | '\User',
14 |
15 | /*
16 | |--------------------------------------------------------------------------
17 | | Closure to determine the current user model
18 | |--------------------------------------------------------------------------
19 | |
20 | | Must return the current logged in user model to use
21 | | Non object, or NULL response is considered as not logged in
22 | |
23 | */
24 | 'currentuser' => function() {
25 | //Here you can use confide facade,
26 | //or just the default facade, or whatever else
27 |
28 | return Auth::user();
29 | },
30 |
31 | /*
32 | |--------------------------------------------------------------------------
33 | | Application forum view controller
34 | |--------------------------------------------------------------------------
35 | |
36 | | The controller used as application level hook for the forum (visualisation part)
37 | | This class must extend \Atrakeur\Forum\Controllers\AbstractViewForumController
38 | |
39 | */
40 | 'viewcontroller' => '\ForumController',
41 |
42 | /*
43 | |--------------------------------------------------------------------------
44 | | Application forum post controller
45 | |--------------------------------------------------------------------------
46 | |
47 | | The controller used as application level hook for the forum (post part)
48 | | This class must extend \Atrakeur\Forum\Controllers\AbstractPostForumController
49 | |
50 | */
51 | 'postcontroller' => '\ForumPostController',
52 |
53 | /*
54 | |--------------------------------------------------------------------------
55 | | The number of messages per page
56 | |--------------------------------------------------------------------------
57 | |
58 | */
59 | 'messagesperpage' => 15
60 |
61 | );
62 |
--------------------------------------------------------------------------------
/src/Atrakeur/Forum/Models/AbstractForumBaseModel.php:
--------------------------------------------------------------------------------
1 | id.$item;
10 |
11 | //TODO make cache duration tweakable
12 | $value = \Cache::remember($cacheItem, 1, $function);
13 |
14 | return $value;
15 | }
16 |
17 | protected static function clearAttributeCache($model)
18 | {
19 | foreach ($model->appends as $attribute) {
20 | $cacheItem = get_class($model).$model->id.$attribute;
21 | \Cache::forget($cacheItem);
22 | }
23 | }
24 |
25 | public function toObject()
26 | {
27 | return $this->convertToObject($this);
28 | }
29 |
30 | public function convertToObject($value)
31 | {
32 | if ($value instanceof \Eloquent)
33 | {
34 | $attributes = $value->toArray();
35 | $relations = $value->relationsToArray();
36 |
37 | $object = new stdClass();
38 | foreach($attributes as $key => $attribute)
39 | {
40 | if (array_key_exists($key, $relations))
41 | {
42 | $key = camel_case($key);
43 | $object->$key = $this->convertToObject($value->$key);
44 | }
45 | else
46 | {
47 | $object->$key = $attribute;
48 | }
49 | }
50 | return $object;
51 | }
52 |
53 | if ($value instanceof \Illuminate\Database\Eloquent\Collection)
54 | {
55 | $array = array();
56 | foreach($value as $key => $element)
57 | {
58 | $array[$key] = $this->convertToObject($element);
59 | }
60 | return $array;
61 | }
62 | return $value;
63 | }
64 |
65 | protected function computeCanPostAttribute($configItem)
66 | {
67 | // Fetch the current user (config callback)
68 | $userfunc = \Config::get('forum::integration.currentuser');
69 | $user = $userfunc();
70 |
71 | // Fetch the current rights (config callback)
72 | $rightsfunc = \Config::get('forum::'.$configItem);
73 |
74 | //True will give rights, any other will block
75 | return $rightsfunc($this, $user);
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/src/config/rights.php:
--------------------------------------------------------------------------------
1 | function(\Atrakeur\Forum\Models\ForumCategory $category, $user) {
16 | //Here we allow only logged in users
17 | if ($user != NULL)
18 | {
19 | return true;
20 | }
21 |
22 | return false;
23 | },
24 |
25 | /*
26 | |--------------------------------------------------------------------------
27 | | Can post in topic
28 | |--------------------------------------------------------------------------
29 | |
30 | | Callback used to determine if a given user is allowed to post in a given
31 | | topic. Return true if the user can respond to this topic. Any other return
32 | | value will be used like false.
33 | |
34 | */
35 | 'posttopic' => function(\Atrakeur\Forum\Models\ForumTopic $topic, $user) {
36 | //Here we allow only logged in users
37 | if ($user != NULL)
38 | {
39 | return true;
40 | }
41 |
42 | return false;
43 | },
44 |
45 | /*
46 | |--------------------------------------------------------------------------
47 | | Can post in message
48 | |--------------------------------------------------------------------------
49 | |
50 | | Callback used to determine if a given user is allowed to edit a given
51 | | message. Return true if the user can edit this message, false otherwise.
52 | |
53 | */
54 | 'postmessage' => function(\Atrakeur\Forum\Models\ForumMessage $message, $user) {
55 | //Here we allow only logged in users
56 | if ($user != NULL)
57 | {
58 | //Here we allow only user to edit their own messages
59 | if ($user->id == $message->author)
60 | {
61 | return true;
62 | }
63 | }
64 |
65 | return false;
66 | },
67 |
68 |
69 |
70 | );
71 |
--------------------------------------------------------------------------------
/src/Atrakeur/Forum/Commands/InstallCommand.php:
--------------------------------------------------------------------------------
1 | info('Fetching controller names from integration config...');
13 | $viewController = class_basename(\Config::get('forum::integration.viewcontroller'));
14 | $postController = class_basename(\Config::get('forum::integration.postcontroller'));
15 |
16 | $this->info('Config specify controllers "'.$viewController.'" and "'.$postController.'"');
17 | if (!$this->confirm('Proceed with controllers creation (no override)? [Yes|no]'))
18 | {
19 | $this->info('Action aborted. No changes done.');
20 | return 1;
21 | }
22 |
23 | $this->installController($viewController, '\Atrakeur\Forum\Controllers\AbstractViewForumController');
24 | $this->installController($postController, '\Atrakeur\Forum\Controllers\AbstractPostForumController');
25 |
26 | $this->info('Forum installation done.');
27 | return 0;
28 | }
29 |
30 | private function installController($controllerName, $sourceController)
31 | {
32 | $controllerFile = $this->laravel->path.'/controllers/'.$controllerName.'.php';
33 | if(file_exists($controllerFile))
34 | {
35 | $this->info('File app/controllers/'.$controllerName.' Exists. Action aborted.');
36 | return 1;
37 | }
38 |
39 | file_put_contents($controllerFile, $this->getControllerContent($controllerName, $sourceController));
40 | $this->info('File app/controllers/'.$controllerName.' Created');
41 | }
42 |
43 | public function getControllerContent($controllerName, $sourceController)
44 | {
45 | $content = " trans('forum::base.new_topic') , 'url' => $category->postUrl, 'accessModel' => $category))
4 | @if ($subCategories != NULL && count($subCategories) != 0)
5 |
6 |
7 |
8 | | {{ trans('forum::base.col_forum') }} |
9 | {{ trans('forum::base.col_topics') }} |
10 | {{ trans('forum::base.col_posts') }} |
11 |
12 |
13 |
14 | @foreach ($subCategories as $subcategory)
15 |
16 | |
17 |
20 | {{{ $subcategory->subtitle }}}
21 | |
22 | {{ $subcategory->topicCount }} |
23 | {{ $subcategory->replyCount }} |
24 |
25 | @endforeach
26 |
27 |
28 | @endif
29 |
30 | @if ($topics != NULL && count($topics) != 0)
31 |
32 |
33 |
34 | | {{ trans('forum::base.subject') }} |
35 | {{ trans('forum::base.reply') }} |
36 |
37 |
38 |
39 | @foreach($topics as $topic)
40 |
41 | |
42 | url}}>{{{ $topic->title }}}
43 | |
44 | {{ $topic->replyCount }} |
45 |
46 | @endforeach
47 |
48 |
49 | @endif
50 |
51 | @if (($subCategories == NULL || count($subCategories) == 0) && ($topics == NULL || count($topics) == 0))
52 |
53 |
54 |
55 | | {{ trans('forum::base.subject') }} |
56 | {{ trans('forum::base.reply') }} |
57 |
58 |
59 |
60 |
61 | |
62 | {{ trans('forum::base.no_topics') }}
63 | |
64 |
65 | @include('forum::partials.postbutton',array('message' => trans('forum::base.first_topic'), 'url' => $category->postUrl, 'accessModel' => $category))
66 | |
67 |
68 |
69 |
70 | @endif
71 |
--------------------------------------------------------------------------------
/tests/Controllers/AbstractForumControllerTest.php:
--------------------------------------------------------------------------------
1 | once()->with('randomEvent', 'randomData');
17 |
18 | //change visibility of fireEvent from protected to public
19 | $controller = $this->getMockForAbstractClass('\Atrakeur\Forum\Controllers\AbstractForumController');
20 |
21 | $reflectionOfController = new \ReflectionClass('\Atrakeur\Forum\Controllers\AbstractForumController');
22 | $method = $reflectionOfController->getMethod('fireEvent');
23 | $method->setAccessible(true);
24 |
25 | $method->invoke($controller, 'randomEvent', 'randomData');
26 | }
27 |
28 | public function testCurrentUser()
29 | {
30 | //use an simple stdClass object to represent user model in tests
31 | \Config::set('forum::integration.usermodel', "stdClass");
32 |
33 | //change visibility of getCurrentUser from protected to public
34 | $controller = $this->getMockForAbstractClass('\Atrakeur\Forum\Controllers\AbstractForumController');
35 |
36 | $reflectionOfController = new \ReflectionClass('\Atrakeur\Forum\Controllers\AbstractForumController');
37 | $method = $reflectionOfController->getMethod('getCurrentUser');
38 | $method->setAccessible(true);
39 |
40 | //test good current user
41 | $user = new \stdClass();
42 | \Config::set('forum::integration.currentuser', function() use ($user)
43 | {
44 | return $user;
45 | });
46 | $data = $method->invoke($controller);
47 | $this->assertEquals($user, $data);
48 |
49 | //test falsey responce
50 | \Config::set('forum::integration.currentuser', function() {
51 | return false;
52 | });
53 | $this->assertEquals(null, $method->invoke($controller));
54 |
55 | //test true like responce
56 | \Config::set('forum::integration.currentuser', function() {
57 | return true;
58 | });
59 | $this->assertEquals(null, $method->invoke($controller));
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/Atrakeur/Forum/Models/ForumCategory.php:
--------------------------------------------------------------------------------
1 | belongsTo('\Atrakeur\Forum\Models\ForumCategory', 'parent_category');
14 | }
15 |
16 | public function subcategories()
17 | {
18 | return $this->hasMany('\Atrakeur\Forum\Models\ForumCategory', 'parent_category');
19 | }
20 |
21 | public function topics()
22 | {
23 | return $this->hasMany('\Atrakeur\Forum\Models\ForumTopic', 'parent_category');
24 | }
25 |
26 | public function scopeWhereTopLevel($query)
27 | {
28 | return $query->where('parent_category', '=', NULL);
29 | }
30 |
31 | public function getTopicCountAttribute()
32 | {
33 | return $this->rememberAttribute('topicCount', function(){
34 | return $this->topics()->count();
35 | });
36 | }
37 |
38 | public function getReplyCountAttribute()
39 | {
40 | return $this->rememberAttribute('replyCount', function(){
41 | $replyCount = 0;
42 |
43 | $topicsIds = array();
44 | $topics = $this->topics()->get(array('id'));
45 |
46 | foreach ($topics AS $topic) {
47 | $topicsIds[] = $topic->id;
48 | }
49 |
50 | if (!empty($topicsIds))
51 | {
52 | $replyCount = ForumMessage::whereIn('parent_topic', $topicsIds)->count();
53 | }
54 | return $replyCount;
55 | });
56 | }
57 |
58 | public function getUrlAttribute()
59 | {
60 | return action(\Config::get('forum::integration.viewcontroller').'@getCategory',
61 | array(
62 | 'categoryId' => $this->id,
63 | 'categoryUrl' => \Str::slug($this->title, '_')
64 | )
65 | );
66 | }
67 |
68 | public function getPostUrlAttribute()
69 | {
70 | return action(\Config::get('forum::integration.postcontroller').'@postNewTopic',
71 | array(
72 | 'categoryId' => $this->id,
73 | 'categoryUrl' => \Str::slug($this->title, '_')
74 | )
75 | );
76 | }
77 |
78 | public function getCanPostAttribute()
79 | {
80 | return $this->computeCanPostAttribute('rights.postcategory');
81 | }
82 |
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/src/Atrakeur/Forum/Controllers/AbstractViewForumController.php:
--------------------------------------------------------------------------------
1 | categories = $categories;
16 | $this->topics = $topics;
17 | $this->messages = $messages;
18 | }
19 |
20 | public function getIndex()
21 | {
22 | $categories = $this->categories->getByParent(null, array('subcategories'));
23 |
24 | $this->layout->content = \View::make('forum::index', compact('categories'));
25 | }
26 |
27 | public function getCategory($categoryId, $categoryUrl)
28 | {
29 | $category = $this->categories->getById($categoryId, array('parentCategory', 'subCategories', 'topics'));
30 | if ($category == NULL)
31 | {
32 | return \App::abort(404);
33 | }
34 |
35 | $parentCategory = $category->parentCategory;
36 | $subCategories = $category->subCategories;
37 | $topics = $category->topics;
38 |
39 | $this->layout->content = \View::make('forum::category', compact('parentCategory', 'category', 'subCategories', 'topics'));
40 |
41 | }
42 |
43 | public function getTopic($categoryId, $categoryUrl, $topicId, $topicUrl, $page = 0)
44 | {
45 | $category = $this->categories->getById($categoryId, array('parentCategory'));
46 | if ($category == NULL)
47 | {
48 | return \App::abort(404);
49 | }
50 |
51 | $topic = $this->topics->getById($topicId);
52 | if ($topic == NULL)
53 | {
54 | return \App::abort(404);
55 | }
56 |
57 | $parentCategory = $category->parentCategory;
58 | $messagesPerPage = \Config::get('forum::integration.messagesperpage');
59 | //$this->messages->paginate($messagesPerPage);
60 | $messages = $this->messages->getByTopic($topic->id, array('author'));
61 | $paginationLinks = $this->messages->getPaginationLinks($messagesPerPage);
62 |
63 | $this->layout->content = \View::make('forum::topic', compact('parentCategory', 'category', 'topic', 'messages', 'paginationLinks'));
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/src/lang/de/base.php:
--------------------------------------------------------------------------------
1 | "Forum",
13 | 'index' => "Forum",
14 | 'author' => "Autor",
15 | 'title' => "Titel",
16 | 'subject' => "Thema",
17 | 'edit' => "Bearbeiten",
18 | 'delete' => "Löschen",
19 | 'reply' => "Antworten",
20 | 'new_reply' => "Neue Antwort",
21 | 'quick_reply' => "Schnelle Antwort",
22 | 'reply_added' => "Antwort hinzugefügt",
23 | 'send' => "Senden",
24 | 'posting_into' => "Du schreibst in",
25 | 'you_are_editing' => "Du bearbeitest",
26 | 'posted_at' => "Geschrieben am",
27 | 'last_update' => "Letzte Aktualisierung",
28 | 'generic_confirm' => "Bist du sicher?",
29 | 'actions' => "Aktionen",
30 | 'cancel' => "Abbrechen",
31 |
32 | // Categories
33 | 'category' => "Kategorie",
34 | 'no_categories' => "Keine Kategorien gefunden",
35 |
36 | // Threads
37 | 'threads' => "Themen",
38 | 'first_thread' => "Erstelle das erste Thema!",
39 | 'new_thread' => "Neues Thema",
40 | 'thread_created' => "Thema erstellt",
41 | 'pin_thread' => "Thema priorisieren",
42 | 'pinned' => "Priorisiert",
43 | 'lock_thread' => "Sperren/Freigeben des Themas",
44 | 'locked' => "Gesperrt",
45 | 'thread_updated' => "Thema gelöscht",
46 | 'delete_thread' => "Dieses Thema löschen",
47 | 'thread_deleted' => "Thema gelöscht",
48 | 'no_threads' => "Keine Themen gefunden",
49 | 'newest_thread' => "Neuestes Thema",
50 |
51 | // Posts
52 | 'posts' => "Einträge",
53 | 'post' => "Eintrag",
54 | 'your_post' => "Dein Eintrag",
55 | 'latest_posts' => "Letzte Einträge",
56 | 'edit_post' => "Eintrag bearbeiten",
57 | 'post_updated' => "Eintrag aktualisiert",
58 | 'post_deleted' => "Eintrag gelöscht",
59 | 'last_post' => "Letzter Eintrag",
60 | 'view_post' => "Eintrag ansehen",
61 |
62 | );
63 |
--------------------------------------------------------------------------------
/tests/Repositories/CategoriesRepositoryTest.php:
--------------------------------------------------------------------------------
1 | shouldReceive('where')->with('id', '=', 1)->once()->andReturn($modelMock);
18 | $modelMock->shouldReceive('with')->with(array())->once()->andReturn($modelMock);
19 | $modelMock->shouldReceive('first')->once()->andReturn($modelMock);
20 | $modelMock->shouldReceive('convertToObject')->once()->andReturn($modelMock);
21 |
22 | $repository = new CategoriesRepository($modelMock);
23 |
24 | $this->assertEquals($modelMock, $repository->getById(1));
25 | }
26 |
27 | public function testGetByIdNull()
28 | {
29 | $modelMock = \Mockery::mock('\Atrakeur\Forum\Models\ForumCategory');
30 | $repository = new CategoriesRepository($modelMock);
31 |
32 | $this->setExpectedException('\InvalidArgumentException');
33 | $this->assertEquals(array(), $repository->getById("a"));
34 | }
35 |
36 | private function getCategoryModelForParentTest($id, $with, $return)
37 | {
38 | $modelMock = \Mockery::mock('\Atrakeur\Forum\Models\ForumCategory');
39 |
40 | $modelMock->shouldReceive('where')->with('parent_category', '=', $id)->once()->andReturn($modelMock);
41 | $modelMock->shouldReceive('with')->with($with)->once()->andReturn($modelMock);
42 | $modelMock->shouldReceive('get')->once()->andReturn($modelMock);
43 | $modelMock->shouldReceive('convertToObject')->once()->andReturn($return);
44 |
45 | return new CategoriesRepository($modelMock);
46 | }
47 |
48 | public function testGetByParentNull()
49 | {
50 | $repository = $this->getCategoryModelForParentTest(null, array(), array());
51 |
52 | $this->assertEquals(array(), $repository->getByParent(null));
53 | }
54 |
55 | public function testGetByParentInvalid()
56 | {
57 | $modelMock = \Mockery::mock('\Atrakeur\Forum\Models\ForumCategory');
58 | $repository = new CategoriesRepository($modelMock);
59 |
60 | $this->setExpectedException('\InvalidArgumentException');
61 | $this->assertEquals(array(), $repository->getByParent("a"));
62 | }
63 |
64 | public function testGetByParentInteger()
65 | {
66 | //mock the ForumCategory model
67 | $repository = $this->getCategoryModelForParentTest(1, array(), array());
68 |
69 | $this->assertEquals(array(), $repository->getByParent(1));
70 | }
71 |
72 | public function testGetByParentArray()
73 | {
74 | //mock the ForumCategory model
75 | $repository = $this->getCategoryModelForParentTest(1, array(), array());
76 |
77 | $this->assertEquals(array(), $repository->getByParent(array('id' => 1)));
78 | }
79 |
80 | public function tearDown()
81 | {
82 | \Mockery::close();
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/tests/Controllers/AbstractViewForumControllerTest.php:
--------------------------------------------------------------------------------
1 | getMockForAbstractClass(
15 | '\Atrakeur\Forum\Controllers\AbstractViewForumController',
16 | array($categories, $topics, $messages)
17 | );
18 | }
19 |
20 | private function createCategoriesMock()
21 | {
22 | return \Mockery::mock('Eloquent', '\Atrakeur\Forum\Repositories\CategoriesRepository');
23 | }
24 |
25 | private function createTopicsMock()
26 | {
27 | return \Mockery::mock('Eloquent', '\Atrakeur\Forum\Repositories\TopicsRepository');
28 | }
29 |
30 | private function createMessagesMock()
31 | {
32 | return \Mockery::mock('Eloquent', '\Atrakeur\Forum\Repositories\MessagesRepository');
33 | }
34 |
35 | public function testGetIndex()
36 | {
37 | $categoriesMock = $this->createCategoriesMock();
38 | $topicsMock = $this->createTopicsMock();
39 | $messagesMock = $this->createMessagesMock();
40 |
41 | $categoryMock = new \stdClass();
42 | $categoryMock->url = 'url';
43 | $categoryMock->title = 'title';
44 | $categoryMock->subtitle = 'title';
45 | $categoryMock->subcategories = array();
46 | $categoriesMock->shouldReceive('getByParent')->andReturn(array($categoryMock));
47 |
48 | $controller = $this->createController($categoriesMock, $topicsMock, $messagesMock);
49 |
50 | \App::instance('\Atrakeur\Forum\Repositories\CategoriesRepository', $categoriesMock);
51 | \App::instance('\Atrakeur\Forum\Models\ForumTopic', $topicsMock);
52 | \App::instance('\Atrakeur\Forum\Controllers\AbstractViewForumController', $controller);
53 |
54 | \Route:: get('testRoute', '\Atrakeur\Forum\Controllers\AbstractViewForumController@getIndex');
55 | $this->call('GET', 'testRoute');
56 |
57 | //$this->assertViewHas('categories');
58 | }
59 |
60 | public function testGetCategoryInvalid()
61 | {
62 | $categoriesMock = $this->createCategoriesMock();
63 | $topicsMock = $this->createTopicsMock();
64 | $messagesMock = $this->createMessagesMock();
65 |
66 | $categoriesMock->shouldReceive('getById')->once()->with(31415, array('parentCategory', 'subCategories', 'topics'))->andReturn(null);
67 |
68 | $controller = $this->createController($categoriesMock, $topicsMock, $messagesMock);
69 |
70 | \App::instance('\Atrakeur\Forum\Repositories\CategoriesRepository', $categoriesMock);
71 | \App::instance('\Atrakeur\Forum\Models\ForumTopic', $topicsMock);
72 | \App::instance('\Atrakeur\Forum\Controllers\AbstractViewForumController', $controller);
73 |
74 | $this->setExpectedException('\Symfony\Component\HttpKernel\Exception\NotFoundHttpException');
75 | \Route:: get('testRoute/{categoryId}/{categoryUrl}', '\Atrakeur\Forum\Controllers\AbstractViewForumController@getCategory');
76 | $this->call('GET', 'testRoute/31415/FalseTestName');
77 | }
78 |
79 | public function testGetCategory()
80 | {
81 | $categoriesMock = $this->createCategoriesMock();
82 | $topicsMock = $this->createTopicsMock();
83 | $messagesMock = $this->createMessagesMock();
84 |
85 | $categoryMock = new \stdClass();
86 | $categoryMock->url = 'url';
87 | $categoryMock->postUrl = 'url';
88 | $categoryMock->title = 'title';
89 | $categoryMock->canPost = false;
90 | $categoryMock->subtitle = 'title';
91 | $categoryMock->subCategories = array();
92 | $categoryMock->topics = array();
93 | $categoryMock->parentCategory = null;
94 | $categoriesMock->shouldReceive('getById')->once()->with(1, array('parentCategory', 'subCategories', 'topics'))->andReturn($categoryMock);
95 |
96 | $controller = $this->createController($categoriesMock, $topicsMock, $messagesMock);
97 |
98 | \App::instance('\Atrakeur\Forum\Repositories\CategoriesRepository', $categoriesMock);
99 | \App::instance('\Atrakeur\Forum\Models\ForumTopic', $topicsMock);
100 | \App::instance('\Atrakeur\Forum\Controllers\AbstractViewForumController', $controller);
101 |
102 | //$this->setExpectedException('\Symfony\Component\HttpKernel\Exception\NotFoundHttpException');
103 | \Route:: get('testRoute/{categoryId}/{categoryUrl}', '\Atrakeur\Forum\Controllers\AbstractViewForumController@getCategory');
104 | $this->call('GET', 'testRoute/1/title');
105 | }
106 |
107 | public function tearDown()
108 | {
109 | \Mockery::close();
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Laravel forum package
2 |
3 | **
4 | Note: This package is now discontinued as I don't have time to improve it anymore.
5 | However, a nice guy has forked it and improved it on top of my work and is now keeping it alive.
6 | I suggest you have a look at [this repo](https://github.com/Riari/laravel-forum) for the new, updated version.
7 | This repo will only stay here for documentation purposes. Please submit your pull requests and issues to Riari's one instead.
8 | **
9 |
10 |
11 | [](https://travis-ci.org/atrakeur/laravel-forum)
12 | [](https://scrutinizer-ci.com/g/atrakeur/laravel-forum/?branch=master)
13 |
14 | ## Goals
15 |
16 | This package aims to provide a good starting point implementing a forum inside a Laravel application.
17 | It focus on taking care of all the tedious and repetiting work of forum creation (categories, subcategories, topics, messages). Allowing you to spend more time on you website features and how the forum integrates with you application.
18 | This package will provide multiple hooks such as specials events and custom closures to allow you to customise his behavior. Additionnaly, you'll be able to extends forum's core classes to implement you own methods directly inside the core.
19 |
20 | This package is far from finished, and pull requests are always welcome to make this package better together.
21 |
22 | ## Installation
23 |
24 | ### Import the package
25 |
26 | To install, simply add the following line to your composer .json and run composer update:
27 |
28 | ```json
29 | "atrakeur/forum": "dev-master"
30 | ```
31 |
32 | Then add the following service provider to your app.php:
33 |
34 | ```php
35 | 'Atrakeur\Forum\ForumServiceProvider',
36 | ```
37 |
38 | ### Integrate into your app
39 |
40 | Before anything, in some cases (L4) you may need to run an update for composer before these next steps, so:
41 |
42 | ```php
43 | composer update
44 | ```
45 | and, just to be safe, run:
46 |
47 | ```php
48 | composer dump-autoload
49 | ```
50 |
51 | Now publish forum's files right into your Laravel app:
52 | `php artisan config:publish atrakeur/forum`
53 | `php artisan migrate:publish atrakeur/forum`
54 |
55 | If all goes well, you should find configuration files inside app/config/packages/atrakeur/forum and three new migrations in app/database/migrations.
56 |
57 | Now you can create the database schema using the default Laravel command `php artisan migrate` .
58 |
59 | To enable you to fully customise the package to your website, the package is integrated inside your application using two application level controllers.
60 | Run the command `php artisan forum:install` to auto-deploy the controllers in your app/controllers folder. (Please note that if a file with the same name allready exist, the command above will fail before overriding your files.)
61 |
62 | ### Customise
63 |
64 | To tweak the views publish them to your views folder using the Laravel command:
65 |
66 | `php artisan view:publish atrakeur/forum`
67 |
68 | The very last step needed is to create some categories and subcategories into the forum_categories tables. The schema is very basic and you should be able to do that on your own using Laravel seeds (TODO: give some examples)
69 |
70 | Now you are ready to go, just load http://localhost/forum and you should see a brand new forum.
71 |
72 | More information on how to integrate it with your login system is available through the config files comments. (TODO: give some examples) By default, it should run well on Laravel default auth.
73 |
74 | ## Features
75 |
76 | This package is currently in (very-)alpha stage, so all of the following features may or may not work yet. However, feel free to post issues and features requests at https://github.com/atrakeur/laravel-forum/issues . I'll try to fix and improve the package as fast as I can based on your help!
77 |
78 | * Category nesting on 2 levels
79 | * Topic and messages inside categories
80 | * Easy user integration (through config files and callbacks)
81 | * Easy user right integration (through config files and callbacks)
82 | * Message posting (with hooks for app integration)
83 | * Light weight & blasing fast (designed with caching and high speed in mind)
84 | * Designed on bootstrap (clean and simple markup, no messy css and should integrate directly into your website)
85 |
86 | ## Events
87 |
88 | This package provides various events as hooks to enable you to implement you own functionnality on top of forum's functionnality.
89 | Here is a complete list of all events, as to when they are fired. When a parameter is given, you can use this parameter to change a forum's iternal object to fit your needs.
90 |
91 | | Events | Params | Usage |
92 | | ------------- |:-------------:| ---------------------------------------------: |
93 | | forum.new.topic | $topic | Called before topic save. Can be used to modify topic contents |
94 | | forum.new.message | $message | Called before message save. Can be used to modify message contents |
95 | | forum.saved.topic | $topic | Called after topic save. Can be used for logging purposes |
96 | | forum.saved.message | $message | Called after message save. Can be used for logging purposes |
97 |
--------------------------------------------------------------------------------
/src/Atrakeur/Forum/Controllers/AbstractPostForumController.php:
--------------------------------------------------------------------------------
1 | 'required',
11 | );
12 |
13 | protected $messageRules = array(
14 | 'data' => 'required|min:5',
15 | );
16 |
17 | private $categories;
18 | private $topics;
19 | private $messages;
20 |
21 | public function __construct(CategoriesRepository $categories, TopicsRepository $topics, MessagesRepository $messages)
22 | {
23 | $this->categories = $categories;
24 | $this->topics = $topics;
25 | $this->messages = $messages;
26 | }
27 |
28 | public function getNewTopic($categoryId, $categoryUrl)
29 | {
30 | $user = $this->getCurrentUser();
31 | if ($user == NULL)
32 | {
33 | return \App::abort(403, 'Access denied');
34 | }
35 |
36 | $category = $this->categories->getById($categoryId, array('parentCategory'));
37 | $parentCategory = $category->parentCategory;
38 | $actionUrl = $category->postUrl;
39 |
40 | $this->layout->content = \View::make('forum::post', compact('parentCategory', 'category', 'actionUrl'));
41 | }
42 |
43 | public function postNewTopic($categoryId, $categoryUrl)
44 | {
45 | $user = $this->getCurrentUser();
46 | if ($user == NULL)
47 | {
48 | return \App::abort(403, 'Access denied');
49 | }
50 |
51 | $category = $this->categories->getById($categoryId);
52 | $validator = \Validator::make(\Input::all(), array_merge($this->topicRules, $this->messageRules));
53 | if ($validator->passes())
54 | {
55 | $title = \Input::get('title');
56 | $data = \Input::get('data');
57 |
58 | $topic = new \stdClass();
59 | $topic->author_id = $user->id;
60 | $topic->parent_category = $category->id;
61 | $topic->title = $title;
62 |
63 | $this->fireEvent('forum.new.topic', array($topic));
64 | $topic = $this->topics->create($topic);
65 | $this->fireEvent('forum.saved.topic', array($topic));
66 |
67 | $message = new \stdClass();
68 | $message->parent_topic = $topic->id;
69 | $message->author_id = $user->id;
70 | $message->data = $data;
71 |
72 | $this->fireEvent('forum.new.message', array($message));
73 | $message = $this->messages->create($message);
74 | $this->fireEvent('forum.saved.message', array($message));
75 |
76 | return \Redirect::to($topic->url)->with('success', 'topic created');
77 | }
78 | else
79 | {
80 | return \Redirect::to($category->postUrl)->withErrors($validator)->withInput();
81 | }
82 | }
83 |
84 | public function getNewMessage($categoryId, $categoryUrl, $topicId, $topicUrl)
85 | {
86 | $user = $this->getCurrentUser();
87 | if ($user == NULL)
88 | {
89 | return \App::abort(403, 'Access denied');
90 | }
91 |
92 | $category = $this->categories->getById($categoryId, array('parentCategory'));
93 | $topic = $this->topics->getById($topicId);
94 | if ($category == NULL || $topic == NULL)
95 | {
96 | return \App::abort(404);
97 | }
98 |
99 | $parentCategory = $category->parentCategory;
100 | $actionUrl = $topic->postUrl;
101 | $prevMessages = $this->messages->getLastByTopic($topicId);
102 |
103 | $this->layout->content = \View::make('forum::reply', compact('parentCategory', 'category', 'topic', 'actionUrl', 'prevMessages'));
104 | }
105 |
106 | public function postNewMessage($categoryId, $categoryUrl, $topicId, $topicUrl)
107 | {
108 | $user = $this->getCurrentUser();
109 | if ($user == NULL)
110 | {
111 | return \App::abort(403, 'Access denied');
112 | }
113 |
114 | $category = $this->categories->getById($categoryId);
115 | $topic = $this->topics->getById($topicId);
116 | $validator = \Validator::make(\Input::all(), $this->messageRules);
117 | if ($validator->passes())
118 | {
119 | $data = \Input::get('data');
120 |
121 | $message = new \stdClass();
122 | $message->parent_topic = $topic->id;
123 | $message->author_id = $user->id;
124 | $message->data = $data;
125 |
126 | $this->fireEvent('forum.new.message', array($message));
127 | $message = $this->messages->create($message);
128 | $this->fireEvent('forum.saved.message', array($message));
129 |
130 | return \Redirect::to($topic->url)->with('success', 'topic created');
131 | }
132 | else
133 | {
134 | return \Redirect::to($topic->postUrl)->withErrors($validator)->withInput();
135 | }
136 | }
137 |
138 | public function getEditMessage($categoryId, $categoryUrl, $topicId, $topicUrl, $messageId)
139 | {
140 | $user = $this->getCurrentUser();
141 | if ($user == NULL)
142 | {
143 | return \App::abort(403, 'Access denied');
144 | }
145 |
146 | $category = $this->categories->getById($categoryId, array('parentCategory'));
147 | $topic = $this->topics->getById($topicId);
148 | $message = $this->messages->getById($messageId);
149 | if ($category == NULL || $topic == NULL || $message == NULL)
150 | {
151 | return \App::abort(404);
152 | }
153 |
154 | $parentCategory = $category->parentCategory;
155 | $actionUrl = $message->postUrl;
156 |
157 | $this->layout->content = \View::make('forum::edit', compact('parentCategory', 'category', 'topic', 'message', 'actionUrl'));
158 | }
159 |
160 | public function postEditMessage($categoryId, $categoryUrl, $topicId, $topicUrl, $messageId)
161 | {
162 | $user = $this->getCurrentUser();
163 | if ($user == NULL)
164 | {
165 | return \App::abort(403, 'Access denied');
166 | }
167 |
168 | $category = $this->categories->getById($categoryId, array('parentCategory'));
169 | $topic = $this->topics->getById($topicId);
170 | $message = $this->messages->getById($messageId);
171 | if ($category == NULL || $topic == NULL || $message == NULL)
172 | {
173 | return \App::abort(404);
174 | }
175 |
176 | $validator = \Validator::make(\Input::all(), $this->messageRules);
177 | if ($validator->passes())
178 | {
179 | $data = \Input::get('data');
180 |
181 | $message = new \stdClass();
182 | $message->id = $messageId;
183 | $message->parent_topic = $topic->id;
184 | $message->author_id = $user->id;
185 | $message->data = $data;
186 |
187 | $this->fireEvent('forum.new.message', array($message));
188 | $message = $this->messages->update($message);
189 | $this->fireEvent('forum.saved.message', array($message));
190 |
191 | return \Redirect::to($message->url)->with('success', 'topic created');
192 | }
193 | else
194 | {
195 | return \Redirect::to($message->postUrl)->withErrors($validator)->withInput();
196 | }
197 | }
198 |
199 | }
200 |
--------------------------------------------------------------------------------