Completed
Branch #338-Save_posting_before_movin... (90c6c5)
by Schlaefer
02:36
created

PostingComponent::update()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 3
dl 0
loc 23
rs 9.552
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Saito - The Threaded Web Forum
7
 *
8
 * @copyright Copyright (c) the Saito Project Developers
9
 * @link https://github.com/Schlaefer/Saito
10
 * @license http://opensource.org/licenses/MIT
11
 */
12
13
namespace App\Controller\Component;
14
15
use App\Lib\Model\Table\FieldFilter;
16
use App\Model\Entity\Entry;
17
use App\Model\Table\EntriesTable;
18
use Cake\Controller\Component;
19
use Cake\Http\Exception\ForbiddenException;
20
use Cake\ORM\TableRegistry;
21
use Saito\Posting\PostingInterface;
22
use Saito\User\CurrentUser\CurrentUserInterface;
23
24
/**
25
 * Checks, prepares and persists postings to the entries table
26
 *
27
 * This isolates stricter table rules/validations from properties set or checked
28
 * depending on the CurrentUser or other higher level user-input.
29
 */
30
class PostingComponent extends Component
31
{
32
    /** @var EntriesTable */
33
    private $table;
34
35
    /** @var FieldFilter */
36
    private $fieldFilter;
37
38
    /**
39
     * {@inheritDoc}
40
     */
41
    public function initialize(array $config)
42
    {
43
        /** @var EntriesTable */
44
        $this->table = TableRegistry::getTableLocator()->get('Entries');
45
        $this->fieldFilter = (new FieldFilter())
46
            ->setConfig('create', ['category_id', 'pid', 'subject', 'text'])
47
            ->setConfig('update', ['category_id', 'subject', 'text']);
48
    }
49
50
    /**
51
     * Creates a new posting from user
52
     *
53
     * @param array $data raw posting data
54
     * @param CurrentUserInterface $CurrentUser the current user
55
     * @return
56
     */
57
    public function createPosting(array $data, CurrentUserInterface $CurrentUser)
58
    {
59
        $data = $this->fieldFilter->filterFields($data, 'create');
60
61
        if (!empty($data['pid'])) {
62
            /// new posting is answer to existing posting
63
            $parent = $this->table->get($data['pid']);
64
65
            if (empty($parent)) {
66
                throw new \InvalidArgumentException(
67
                    'Parent posting for creating a new answer not found.',
68
                    1564756571
69
                ) ;
70
            }
71
            $parentId = $parent->get('id');
72
            $categoryId = $parent->get('category_id');
73
74
            $data = $this->prepareChildPosting($parent, $data);
75
        } else {
76
            /// if no pid is provided the new posting is root-posting
77
            $data['pid'] = 0;
78
            $parentId = $data['pid'];
79
80
            if (empty($data['category_id'])) {
81
                throw new \InvalidArgumentException(
82
                    'New root posting requires a category.',
83
                    1564756571
84
                ) ;
85
            }
86
            $categoryId = (int)$data['category_id'];
87
        }
88
89
        $isRoot = $parentId == 0;
90
91
        $allowed = $this->validateCategoryIsAllowed($isRoot, $categoryId, $CurrentUser);
92
93
        if (!$allowed) {
94
            return false;
95
        }
96
97
        /// set user who created the posting
98
        $data['user_id'] = $CurrentUser->getId();
99
        $data['name'] = $CurrentUser->get('username');
100
101
        return $this->table->createPosting($data);
102
    }
103
104
    /**
105
     * Updates an existing posting
106
     *
107
     * @param Entry $posting
108
     * @param array $data
109
     * @param CurrentUserInteface $CurrentUser
110
     * @return void
111
     */
112
    public function update(Entry $posting, array $data, CurrentUserInterface $CurrentUser)
113
    {
114
        $data = $this->fieldFilter->filterFields($data, 'update');
115
116
        $isRoot = $posting->isRoot();
117
        $categoryId = $posting->get('category_id');
118
119
        $allowed = $this->validateCategoryIsAllowed($isRoot, $categoryId, $CurrentUser);
120
121
        if (!$allowed) {
122
            return false;
123
        }
124
125
        $parent = $this->table->get($posting->get('pid'));
126
127
        if (!$isRoot) {
128
            $data = $this->prepareChildPosting($parent, $data);
129
        }
130
131
        $data['edited_by'] = $CurrentUser->get('username');
132
133
        return $this->table->update($posting, $data);
134
    }
135
136
    /**
137
     * Marks a sub-posting as solution to a root posting
138
     *
139
     * @param integer $id posting-ID
140
     * @param CurrentUserInterface $CurrentUser current user
141
     * @return boolean success
142
     */
143
    public function toggleSolve(int $id, CurrentUserInterface $CurrentUser): bool
144
    {
145
        $posting = $this->table->get($id, ['return' => 'Entity']);
146
        if (empty($posting) || $posting->isRoot()) {
147
            throw new \InvalidArgumentException;
148
        }
149
150
        $rootId = $posting->get('tid');
151
        $rootPosting = $this->table->get($rootId);
152
        if ($rootPosting->get('user_id') !== $CurrentUser->getId()) {
153
            throw new ForbiddenException;
154
        }
155
156
        return $this->table->toggleSolve($posting);
157
    }
158
159
    /**
160
     * Populates data of an answer derived from parent the parent-posting
161
     *
162
     * @param PostingInterface $parent parent-posting
163
     * @param array $data current posting data
164
     * @return array populated $data
165
     */
166
    public function prepareChildPosting(PostingInterface $parent, array $data): array
167
    {
168
        if (empty($data['subject'])) {
169
            /// if new subject is empty use the parent's subject
170
            $data['subject'] = $parent->get('subject');
171
        }
172
173
        $data['tid'] = $parent->get('tid');
174
        $data['category_id'] = $parent->get('category_id');
175
176
        return $data;
177
    }
178
179
    /**
180
     * Checks that entries are only in existing and allowed categories
181
     *
182
     * @param bool $isRoot Is the posting to create in the category a root posting?
183
     * @param int $categoryId The category to check in.
184
     * @param CurrentUserInterface $CurrentUser Currrent User
185
     * @return bool Is user allowed?
186
     */
187
    private function validateCategoryIsAllowed(bool $isRoot, int $categoryId, CurrentUserInterface $CurrentUser): bool
188
    {
189
        $action = $isRoot ? 'thread' : 'answer';
190
191
        return $CurrentUser->getCategories()->permission($action, $categoryId);
192
    }
193
}
194