Issues (299)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  Header Injection
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

class/AnswerHandler.php (11 issues)

1
<?php declare(strict_types=1);
2
3
namespace XoopsModules\Smartfaq;
4
5
/**
6
 * Module: SmartFAQ
7
 * Author: The SmartFactory <www.smartfactory.ca>
8
 * Licence: GNU
9
 */
10
11
use Criteria;
12
use CriteriaCompo;
13
use CriteriaElement;
14
use XoopsDatabase;
15
use XoopsModules\Smartfaq;
16
use XoopsObject;
17
use XoopsPersistableObjectHandler;
18
19
/**
20
 * Answers handler class.
21
 * This class is responsible for providing data access mechanisms to the data source
22
 * of Answer class objects.
23
 *
24
 * @author  marcan <[email protected]>
25
 */
26
class AnswerHandler extends XoopsPersistableObjectHandler
27
{
28
    public $helper;
29
30
    /**
31
     * @param \XoopsDatabase|null                $db
32
     * @param \XoopsModules\Smartfaq\Helper|null $helper
33
     */
34
    public function __construct(XoopsDatabase $db = null, \XoopsModules\Smartfaq\Helper $helper = null)
35
    {
36
        /** @var \XoopsModules\Smartfaq\Helper $this ->helper */
37
        if (null === $helper) {
38
            $this->helper = \XoopsModules\Smartfaq\Helper::getInstance();
0 ignored issues
show
The property helper does not seem to exist on XoopsModules\Smartfaq\Helper.
Loading history...
39
        } else {
40
            $this->helper = $helper;
41
        }
42
        $smartfaqIsAdmin = $this->helper->isUserAdmin();
0 ignored issues
show
The assignment to $smartfaqIsAdmin is dead and can be removed.
Loading history...
43
        parent::__construct($db, 'smartfaq_answers', Answer::class, 'answerid', 'answer');
44
    }
45
46
    /**
47
     * create a new answer
48
     *
49
     * @param bool $isNew flag the new objects as "new"?
50
     * @return object Answer
51
     */
52
    public function create($isNew = true)
53
    {
54
        $answer = new Smartfaq\Answer();
55
        if ($isNew) {
56
            $answer->setNew();
57
        }
58
59
        return $answer;
60
    }
61
62
    /**
63
     * retrieve an answer
64
     *
65
     * @param int  $id answerid of the answer
66
     * @param null $fields
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $fields is correct as it would always require null to be passed?
Loading history...
67
     * @return mixed reference to the <a href='psi_element://sfAnswer'>sfAnswer</a> object, FALSE if failed
68
     */
69
    public function get($id = null, $fields = null)
70
    {
71
        if ((int)$id > 0) {
72
            $sql = 'SELECT * FROM ' . $this->db->prefix('smartfaq_answers') . ' WHERE answerid=' . $id;
73
            if (!$result = $this->db->query($sql)) {
74
                return false;
75
            }
76
77
            $numrows = $this->db->getRowsNum($result);
78
            if (1 == $numrows) {
79
                $answer = new Smartfaq\Answer();
80
                $answer->assignVars($this->db->fetchArray($result));
81
82
                return $answer;
83
            }
84
        }
85
86
        return false;
87
    }
88
89
    /**
90
     * insert a new answer in the database
91
     *
92
     * @param \XoopsObject $object reference to the <a href='psi_element://sfAnswer'>sfAnswer</a> object
93
     * @param bool         $force
94
     * @return bool        FALSE if failed, TRUE if already present and unchanged or successful
95
     */
96
    public function insert(XoopsObject $object, $force = false)
97
    {
98
        if ('xoopsmodules\smartfaq\answer' !== \mb_strtolower(\get_class($object))) {
99
            return false;
100
        }
101
        if (!$object->isDirty()) {
102
            return true;
103
        }
104
        if (!$object->cleanVars()) {
105
            return false;
106
        }
107
108
        foreach ($object->cleanVars as $k => $v) {
109
            ${$k} = $v;
110
        }
111
112
        if ($object->isNew()) {
113
            $sql = \sprintf('INSERT INTO `%s` (answerid, `status`, faqid, answer, uid, datesub, notifypub) VALUES (NULL, %u, %u, %s, %u, %u, %u)', $this->db->prefix('smartfaq_answers'), $status, $faqid, $this->db->quoteString($answer), $uid, \time(), $notifypub);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $faqid seems to be never defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable $status seems to be never defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable $uid seems to be never defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable $notifypub seems to be never defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable $answer seems to be never defined.
Loading history...
114
        } else {
115
            $sql = \sprintf('UPDATE `%s` SET STATUS = %u, faqid = %s, answer = %s, uid = %u, datesub = %u, notifypub = %u WHERE answerid = %u', $this->db->prefix('smartfaq_answers'), $status, $faqid, $this->db->quoteString($answer), $uid, $datesub, $notifypub, $answerid);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $datesub seems to be never defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable $answerid seems to be never defined.
Loading history...
116
        }
117
118
        if ($force) {
119
            $result = $this->db->queryF($sql);
120
        } else {
121
            $result = $this->db->query($sql);
122
        }
123
124
        if (!$result) {
125
            return false;
126
        }
127
128
        if ($object->isNew()) {
129
            $object->assignVar('answerid', $this->db->getInsertId());
130
        } else {
131
            $object->assignVar('answerid', $answerid);
132
        }
133
134
        return true;
135
    }
136
137
    /**
138
     * delete an answer from the database
139
     *
140
     * @param \XoopsObject $object reference to the answer to delete
141
     * @param bool         $force
142
     * @return bool        FALSE if failed.
143
     */
144
    public function delete(XoopsObject $object, $force = false)
145
    {
146
        if ('xoopsmodules\smartfaq\answer' !== \mb_strtolower(\get_class($object))) {
147
            return false;
148
        }
149
        $sql = \sprintf('DELETE FROM `%s` WHERE answerid = %u', $this->db->prefix('smartfaq_answers'), $object->getVar('answerid'));
0 ignored issues
show
It seems like $object->getVar('answerid') can also be of type array and array; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

149
        $sql = \sprintf('DELETE FROM `%s` WHERE answerid = %u', $this->db->prefix('smartfaq_answers'), /** @scrutinizer ignore-type */ $object->getVar('answerid'));
Loading history...
150
151
        //echo "<br>" . $sql . "<br>";
152
153
        if ($force) {
154
            $result = $this->db->queryF($sql);
155
        } else {
156
            $result = $this->db->query($sql);
157
        }
158
        if (!$result) {
159
            return false;
160
        }
161
162
        return true;
163
    }
164
165
    /**
166
     * delete an answer from the database
167
     *
168
     * @param object $faqObj reference to the answer to delete
169
     * @return bool   FALSE if failed.
170
     * @internal param bool $force
171
     */
172
    public function deleteFaqAnswers($faqObj)
173
    {
174
        if ('xoopsmodules\smartfaq\faq' !== \mb_strtolower(\get_class($faqObj))) {
175
            return false;
176
        }
177
        $answers = $this->getAllAnswers($faqObj->faqid());
178
        $result  = true;
179
        foreach ($answers as $answer) {
180
            if (!$this->delete($answer)) {
181
                $result = false;
182
            }
183
        }
184
185
        return $result;
186
    }
187
188
    /**
189
     * retrieve answers from the database
190
     *
191
     * @param \CriteriaElement|null $criteria  {@link CriteriaElement} conditions to be met
192
     * @param bool                  $id_as_key use the answerid as key for the array?
193
     * @param bool                  $as_object
194
     * @return array           array of <a href='psi_element://sfAnswer'>sfAnswer</a> objects
195
     */
196
    public function &getObjects(CriteriaElement $criteria = null, $id_as_key = false, $as_object = true)
197
    {
198
        $ret   = [];
199
        $limit = $start = 0;
200
        $sql   = 'SELECT * FROM ' . $this->db->prefix('smartfaq_answers');
201
        if (($criteria instanceof \CriteriaCompo) || ($criteria instanceof \Criteria)) {
202
            $sql .= ' ' . $criteria->renderWhere();
203
            if ('' != $criteria->getSort()) {
204
                $sql .= ' ORDER BY ' . $criteria->getSort() . ' ' . $criteria->getOrder();
205
            }
206
            $limit = $criteria->getLimit();
207
            $start = $criteria->getStart();
208
        }
209
        //echo "<br>" . $sql . "<br>";
210
        $result = $this->db->query($sql, $limit, $start);
211
        if ($this->db->isResultSet($result)) {
212
            while (false !== ($myrow = $this->db->fetchArray($result))) {
213
                $answer = new Smartfaq\Answer();
214
                $answer->assignVars($myrow);
215
                if ($id_as_key) {
216
                    $ret[$myrow['answerid']] = $answer;
217
                } else {
218
                    $ret[] = &$answer;
219
                }
220
                unset($answer);
221
            }
222
        }
223
224
        return $ret;
225
    }
226
227
    /**
228
     * retrieve 1 official answer (for now SmartFAQ only allow 1 official answer...)
229
     *
230
     * @param int $faqid
231
     * @return mixed reference to the <a href='psi_element://sfAnswer'>sfAnswer</a> object, FALSE if failed
232
     */
233
    public function getOfficialAnswer($faqid = 0)
234
    {
235
        $theaAnswers = $this->getAllAnswers($faqid, Constants::SF_AN_STATUS_APPROVED, 1, 0);
236
        $ret         = false;
237
        if (1 == \count($theaAnswers)) {
238
            $ret = $theaAnswers[0];
239
        }
240
241
        return $ret;
242
    }
243
244
    /**
245
     * retrieve all answers
246
     *
247
     * @param int       $faqid
248
     * @param int|array $status
249
     * @param int       $limit
250
     * @param int       $start
251
     * @param string    $sort
252
     * @param string    $order
253
     * @return array  array of <a href='psi_element://sfAnswer'>sfAnswer</a> objects
254
     */
255
    public function getAllAnswers(
256
        $faqid = 0,
257
        $status = -1,
258
        $limit = 0,
259
        $start = 0,
260
        $sort = 'datesub',
261
        $order = 'DESC'
262
    ) {
263
        $hasStatusCriteria = false;
264
        $criteriaStatus    = new CriteriaCompo();
265
        if (\is_array($status)) {
266
            $hasStatusCriteria = true;
267
            foreach ($status as $v) {
268
                $criteriaStatus->add(new Criteria('status', $v), 'OR');
269
            }
270
        } elseif (-1 != $status) {
271
            $hasStatusCriteria = true;
272
            $criteriaStatus->add(new Criteria('status', $status), 'OR');
273
        }
274
        $criteriaFaqid = new Criteria('faqid', $faqid);
275
276
        $criteria = new CriteriaCompo();
277
        $criteria->add($criteriaFaqid);
278
279
        if ($hasStatusCriteria) {
280
            $criteria->add($criteriaStatus);
281
        }
282
283
        $criteria->setSort($sort);
284
        $criteria->setOrder($order);
285
        $criteria->setLimit($limit);
286
        $criteria->setStart($start);
287
        $ret = $this->getObjects($criteria);
288
289
        return $ret;
290
    }
291
292
    /**
293
     * count answers matching a condition
294
     *
295
     * @param \CriteriaElement|null $criteria {@link CriteriaElement} to match
296
     * @return int             count of answers
297
     */
298
    public function getCount(CriteriaElement $criteria = null)
299
    {
300
        $sql = 'SELECT COUNT(*) FROM ' . $this->db->prefix('smartfaq_answers');
301
        if (($criteria instanceof \CriteriaCompo) || ($criteria instanceof \Criteria)) {
302
            $sql .= ' ' . $criteria->renderWhere();
303
        }
304
        $result = $this->db->query($sql);
305
        if (!$result) {
306
            return 0;
307
        }
308
        [$count] = $this->db->fetchRow($result);
309
310
        return $count;
311
    }
312
313
    /**
314
     * count answers matching a condition and group by faq ID
315
     *
316
     * @param object $criteria {@link CriteriaElement} to match
317
     * @return array
318
     */
319
    public function getCountByFAQ($criteria = null)
320
    {
321
        $sql = 'SELECT faqid, COUNT(*) FROM ' . $this->db->prefix('smartfaq_answers');
322
        if (($criteria instanceof \CriteriaCompo) || ($criteria instanceof \Criteria)) {
323
            $sql .= ' ' . $criteria->renderWhere();
324
            $sql .= ' ' . $criteria->getGroupby();
325
        }
326
327
        //echo "<br>$sql<br>";
328
329
        $result = $this->db->query($sql);
330
        if (!$result) {
331
            return [];
332
        }
333
        $ret = [];
334
        while ([$id, $count] = $this->db->fetchRow($result)) {
335
            $ret[$id] = $count;
336
        }
337
338
        return $ret;
339
    }
340
341
    /**
342
     * delete answers matching a set of conditions
343
     *
344
     * @param \CriteriaElement|null $criteria {@link CriteriaElement}
345
     * @param bool                  $force
346
     * @param bool                  $asObject
347
     * @return bool            FALSE if deletion failed
348
     */
349
    public function deleteAll(CriteriaElement $criteria = null, $force = true, $asObject = false)
350
    {
351
        $sql = 'DELETE FROM ' . $this->db->prefix('smartfaq_answers');
352
        if (($criteria instanceof \CriteriaCompo) || ($criteria instanceof \Criteria)) {
353
            $sql .= ' ' . $criteria->renderWhere();
354
        }
355
        if (!$this->db->query($sql)) {
356
            return false;
357
        }
358
359
        return true;
360
    }
361
362
    /**
363
     * Change a value for answers with a certain criteria
364
     *
365
     * @param string                $fieldname  Name of the field
366
     * @param string                $fieldvalue Value to write
367
     * @param \CriteriaElement|null $criteria   {@link CriteriaElement}
368
     * @param bool                  $force
369
     * @return bool
370
     */
371
    public function updateAll($fieldname, $fieldvalue, CriteriaElement $criteria = null, $force = false)
372
    {
373
        $set_clause = \is_numeric($fieldvalue) ? $fieldname . ' = ' . $fieldvalue : $fieldname . ' = ' . $this->db->quoteString($fieldvalue);
374
        $sql        = 'UPDATE ' . $this->db->prefix('smartfaq_answers') . ' SET ' . $set_clause;
375
        if (($criteria instanceof \CriteriaCompo) || ($criteria instanceof \Criteria)) {
376
            $sql .= ' ' . $criteria->renderWhere();
377
        }
378
        //echo "<br>" . $sql . "<br>";
379
        if (!$this->db->queryF($sql)) {
380
            return false;
381
        }
382
383
        return true;
384
    }
385
386
    /**
387
     * @param $faqids
388
     * @return array
389
     */
390
    public function getLastPublishedByFaq($faqids)
391
    {
392
        $ret    = [];
393
        $sql    = 'SELECT faqid, answer, uid, datesub FROM ' . $this->db->prefix('smartfaq_answers') . '
394
               WHERE faqid IN (' . \implode(',', $faqids) . ') AND status = ' . Constants::SF_AN_STATUS_APPROVED . ' GROUP BY faqid';
395
        $result = $this->db->query($sql);
396
        if (!$result) {
397
            return $ret;
398
        }
399
        while (false !== ($row = $this->db->fetchArray($result))) {
400
            $answer = new Smartfaq\Answer();
401
            $answer->assignVars($row);
402
            $ret[$row['faqid']] = &$answer;
403
            unset($answer);
404
        }
405
406
        return $ret;
407
    }
408
}
409