These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * HiPanel tickets module |
||
4 | * |
||
5 | * @link https://github.com/hiqdev/hipanel-module-ticket |
||
6 | * @package hipanel-module-ticket |
||
7 | * @license BSD-3-Clause |
||
8 | * @copyright Copyright (c) 2015-2017, HiQDev (http://hiqdev.com/) |
||
9 | */ |
||
10 | |||
11 | namespace hipanel\modules\ticket\models; |
||
12 | |||
13 | use hipanel\behaviors\File; |
||
14 | use hipanel\helpers\Markdown; |
||
15 | use hipanel\modules\client\models\Client; |
||
16 | use stdClass; |
||
17 | use Yii; |
||
18 | use yii\helpers\HtmlPurifier; |
||
19 | use yii\web\NotFoundHttpException; |
||
20 | |||
21 | /** |
||
22 | * Class Ticket. |
||
23 | * |
||
24 | * @property string $priority |
||
25 | * @property string $responsible_id |
||
26 | */ |
||
27 | class Thread extends \hipanel\base\Model |
||
28 | { |
||
29 | use \hipanel\base\ModelTrait; |
||
30 | |||
31 | public static $i18nDictionary = 'hipanel:ticket'; |
||
32 | |||
33 | const STATE_OPEN = 'opened'; |
||
34 | const STATE_CLOSE = 'closed'; |
||
35 | |||
36 | const PRIORITY_HIGH = 'high'; |
||
37 | |||
38 | public $search_form; |
||
39 | |||
40 | public function init() |
||
41 | { |
||
42 | $this->on(static::EVENT_BEFORE_INSERT, [$this, 'beforeChange']); |
||
43 | $this->on(static::EVENT_BEFORE_UPDATE, [$this, 'beforeChange']); |
||
44 | } |
||
45 | |||
46 | public function beforeChange($event) |
||
47 | { |
||
48 | $this->prepareSpentTime(); |
||
49 | $this->prepareTopic(); |
||
50 | |||
51 | return true; |
||
52 | } |
||
53 | |||
54 | /** |
||
55 | * @return array |
||
56 | */ |
||
57 | View Code Duplication | public function behaviors() |
|
0 ignored issues
–
show
|
|||
58 | { |
||
59 | return [ |
||
60 | [ |
||
61 | 'class' => File::class, |
||
62 | 'attribute' => 'file', |
||
63 | 'targetAttribute' => 'file_ids', |
||
64 | 'scenarios' => ['create', 'answer'], |
||
65 | ], |
||
66 | ]; |
||
67 | } |
||
68 | |||
69 | /** |
||
70 | * @return array |
||
71 | */ |
||
72 | public function attributes() |
||
73 | { |
||
74 | return [ |
||
75 | 'id', |
||
76 | 'subject', |
||
77 | 'state', |
||
78 | 'state_label', |
||
79 | 'author_email', |
||
80 | 'author', |
||
81 | 'author_id', |
||
82 | 'responsible_id', |
||
83 | 'responsible_email', |
||
84 | 'author_seller', |
||
85 | 'author_seller_id', |
||
86 | 'recipient_id', |
||
87 | 'recipient', |
||
88 | 'recipient_seller', |
||
89 | 'recipient_seller_id', |
||
90 | 'replier_id', |
||
91 | 'replier', |
||
92 | 'replier_seller', |
||
93 | 'replier_name', |
||
94 | 'responsible', |
||
95 | 'priority', |
||
96 | 'priority_label', |
||
97 | 'spent', 'spent_hours', |
||
98 | 'answer_count', |
||
99 | 'status', |
||
100 | 'reply_time', |
||
101 | 'create_time', |
||
102 | 'a_reply_time', |
||
103 | 'elapsed', |
||
104 | 'topics', |
||
105 | 'topic', |
||
106 | 'watchers', |
||
107 | 'watcher_ids', |
||
108 | 'watcher', |
||
109 | 'watcher_id', |
||
110 | 'add_tag_ids', |
||
111 | 'file_ids', |
||
112 | 'file', |
||
113 | 'message', // 'answer_message', |
||
114 | 'is_private', |
||
115 | |||
116 | 'anonym_name', |
||
117 | 'anonym_email', |
||
118 | 'anonym_seller', |
||
119 | |||
120 | 'lastanswer', |
||
121 | 'time', |
||
122 | 'add_watchers', 'del_watchers', |
||
123 | |||
124 | 'time_from', |
||
125 | 'time_till', |
||
126 | |||
127 | 'contact', |
||
128 | ]; |
||
129 | } |
||
130 | |||
131 | /** |
||
132 | * {@inheritdoc} |
||
133 | */ |
||
134 | public function rules() |
||
135 | { |
||
136 | $rules = [ |
||
137 | [['author_id', 'responsible_id'], 'integer'], |
||
138 | [['subject', 'message'], 'required', 'on' => ['create']], |
||
139 | [['subject'], 'string', 'min' => 3], |
||
140 | [['id'], 'required', 'on' => ['answer', 'update-answer', 'open', 'close']], |
||
141 | [['recipient_id'], 'required', 'when' => function () { |
||
142 | return Yii::$app->user->can('support'); |
||
143 | }, 'on' => 'create'], |
||
144 | [ |
||
145 | [ |
||
146 | 'topics', |
||
147 | 'state', |
||
148 | 'priority', |
||
149 | 'responsible', |
||
150 | 'recipient_id', |
||
151 | 'watchers', |
||
152 | 'spent', 'spent_hours', |
||
153 | 'file_ids', |
||
154 | ], |
||
155 | 'safe', |
||
156 | 'on' => 'create', |
||
157 | ], |
||
158 | [ |
||
159 | [ |
||
160 | 'message', |
||
161 | 'topics', 'state', 'priority', |
||
162 | 'responsible', 'recipient_id', |
||
163 | 'watchers', 'add_watchers', 'del_watchers', 'watcher_ids', |
||
164 | 'is_private', |
||
165 | 'file_ids', |
||
166 | 'spent', 'spent_hours', |
||
167 | ], |
||
168 | 'safe', |
||
169 | 'on' => 'answer', |
||
170 | ], |
||
171 | [['state'], 'safe', 'on' => ['close', 'open']], |
||
172 | // only client-side validation. Answer is actually possible without a message, |
||
173 | // but does not make any sense. |
||
174 | [['message'], 'required', 'on' => ['answer'], 'when' => function () { |
||
175 | return false; |
||
176 | }], |
||
177 | [['id'], 'integer', 'on' => 'answer'], |
||
178 | [['file'], 'file', 'maxFiles' => 15], |
||
179 | [['lastanswer', 'create_time', 'recipient'], 'safe'], |
||
180 | [['author', 'author_seller'], 'safe', 'when' => Yii::$app->user->can('support')], |
||
181 | ]; |
||
182 | |||
183 | return $rules; |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * {@inheritdoc} |
||
188 | */ |
||
189 | public function attributeLabels() |
||
190 | { |
||
191 | return $this->mergeAttributeLabels([ |
||
192 | 'author' => Yii::t('hipanel:ticket', 'Author'), |
||
193 | 'author_id' => Yii::t('hipanel:ticket', 'Author'), |
||
194 | 'recipient' => Yii::t('hipanel:ticket', 'Recipient'), |
||
195 | 'is_private' => Yii::t('hipanel:ticket', 'Make private'), |
||
196 | 'responsible' => Yii::t('hipanel:ticket', 'Assignee'), |
||
197 | 'responsible_id' => Yii::t('hipanel:ticket', 'Assignee'), |
||
198 | 'spent' => Yii::t('hipanel:ticket', 'Spent time'), |
||
199 | 'create_time' => Yii::t('hipanel:ticket', 'Created'), |
||
200 | 'a_reply_time' => Yii::t('hipanel:ticket', 'a_reply_time'), |
||
201 | 'file' => Yii::t('hipanel:ticket', 'Files'), |
||
202 | 'lastanswer' => Yii::t('hipanel:ticket', 'Last answer'), |
||
203 | 'author_seller' => Yii::t('hipanel:ticket', 'Seller'), |
||
204 | 'watcher_ids' => Yii::t('hipanel:ticket', 'Watchers'), |
||
205 | ]); |
||
206 | } |
||
207 | |||
208 | public function getClient() |
||
209 | { |
||
210 | return $this->author; |
||
211 | } |
||
212 | |||
213 | public function getClient_id() |
||
214 | { |
||
215 | return $this->author_id; |
||
216 | } |
||
217 | |||
218 | public function getSeller() |
||
219 | { |
||
220 | return $this->author_seller; |
||
221 | } |
||
222 | |||
223 | public function getSeller_id() |
||
224 | { |
||
225 | return $this->author_seller_id; |
||
226 | } |
||
227 | |||
228 | public function getThreadUrl() |
||
229 | { |
||
230 | return ['@ticket/view', 'id' => $this->id]; |
||
231 | } |
||
232 | |||
233 | public static function parseMessage($message) |
||
234 | { |
||
235 | $message = str_replace(["\n\r", "\r\r", "\r\n"], "\n", $message); // "\n\n", |
||
236 | $message = Markdown::process($message); |
||
237 | $message = HtmlPurifier::process($message, [ |
||
238 | 'HTML.SafeObject' => true, // Allow safe HTML entities |
||
239 | 'Core.EscapeInvalidTags' => true, // Escape not allowed tags instead of stripping them |
||
240 | 'Core.LexerImpl' => 'DirectLex', // Do not try to close unclosed tags |
||
241 | ]); |
||
242 | |||
243 | return $message; |
||
244 | } |
||
245 | |||
246 | public function prepareSpentTime() |
||
247 | { |
||
248 | list($this->spent_hours, $this->spent) = explode(':', $this->spent, 2); |
||
249 | } |
||
250 | |||
251 | public function prepareTopic() |
||
252 | { |
||
253 | $this->topics = is_array($this->topics) ? implode(',', $this->topics) : $this->topics; |
||
254 | } |
||
255 | |||
256 | public function getWatchersLogin() |
||
257 | { |
||
258 | $results = []; |
||
259 | foreach ((array)$this->watchers as $id => $watcher) { |
||
260 | list($login, $email) = explode(' ', $watcher); |
||
261 | $results[$id] = $login; |
||
262 | } |
||
263 | |||
264 | return $results; |
||
265 | } |
||
266 | |||
267 | public function xFormater(array $items) |
||
268 | { |
||
269 | $result = []; |
||
270 | foreach ($items as $id => $label) { |
||
271 | $object = new stdClass(); |
||
272 | $object->value = $id; |
||
273 | $object->text = $label; |
||
274 | $result[] = $object; |
||
275 | } |
||
276 | |||
277 | return $result; |
||
278 | } |
||
279 | |||
280 | public function getAnswers() |
||
281 | { |
||
282 | // TODO: redo API in order to have different `Thread` and `ThreadMessage` models |
||
283 | return $this->hasMany(Answer::class, ['id' => 'id'])->joinWith('files')->indexBy('answer_id'); |
||
284 | } |
||
285 | |||
286 | /** |
||
287 | * Returns array of client types, that can be set as responsible for the thread. |
||
288 | * |
||
289 | * @return array |
||
290 | */ |
||
291 | public static function getResponsibleClientTypes() |
||
292 | { |
||
293 | return [Client::TYPE_SELLER, Client::TYPE_ADMIN, Client::TYPE_MANAGER, Client::TYPE_OWNER]; |
||
294 | } |
||
295 | |||
296 | /** |
||
297 | * @param integer $id |
||
298 | * @param bool $throwOnError whether to throw an exception when answer is not found in thread |
||
299 | * @throws NotFoundHttpException |
||
300 | * @return Answer |
||
301 | */ |
||
302 | public function getAnswer($id, $throwOnError = true) |
||
303 | { |
||
304 | if (!isset($this->answers[$id]) && $throwOnError) { |
||
305 | throw new NotFoundHttpException('Answer does not belong to this model'); |
||
306 | } |
||
307 | |||
308 | return $this->answers[$id]; |
||
309 | } |
||
310 | |||
311 | public function getMaxAnswerId() |
||
312 | { |
||
313 | if ($this->isRelationPopulated('answers')) { |
||
314 | return max(array_keys($this->answers)); |
||
315 | } |
||
316 | |||
317 | return $this->id; |
||
318 | } |
||
319 | |||
320 | public function scenarioActions() |
||
321 | { |
||
322 | return [ |
||
323 | 'open' => 'answer', |
||
324 | 'close' => 'answer', |
||
325 | ]; |
||
326 | } |
||
327 | |||
328 | public function canSetSpent() |
||
329 | { |
||
330 | if (isset(Yii::$app->params['ticket.canSetSpent']) && is_callable(Yii::$app->params['ticket.canSetSpent'])) { |
||
331 | return call_user_func(Yii::$app->params['ticket.canSetSpent'], $this); |
||
332 | } |
||
333 | |||
334 | return true; |
||
335 | } |
||
336 | |||
337 | public function isOpen() |
||
338 | { |
||
339 | return $this->state === self::STATE_OPEN; |
||
340 | } |
||
341 | |||
342 | public function isHighPriority(): bool |
||
343 | { |
||
344 | return $this->priority === self::PRIORITY_HIGH; |
||
345 | } |
||
346 | |||
347 | public function isUserAssigned(): bool |
||
348 | { |
||
349 | return $this->responsible_id === Yii::$app->getUser()->getId(); |
||
350 | } |
||
351 | } |
||
352 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.