Issues (14)

Security Analysis    no request data  

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

  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.
  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.
  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.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  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.
  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.
  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.
  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.
  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.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  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.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
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.

QueryBuilder.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Brouzie\Sphinxy;
4
5
class QueryBuilder
6
{
7
    const TYPE_INSERT = 1;
8
    const TYPE_SELECT = 2;
9
    const TYPE_UPDATE = 3;
10
    const TYPE_DELETE = 4;
11
    const TYPE_REPLACE = 5;
12
13
    /**
14
     * @var Connection
15
     */
16
    private $conn;
17
18
    private $type;
19
20
    private $sqlParts = array(
21
        'select' => array(),
22
        'from' => array(),
23
        'where' => array(),
24
        'groupBy' => array(),
25
        'groupByLimit' => null,
26
        'withinGroupOrderBy' => array(),
27
        'orderBy' => array(),
28
        'facet' => array(),
29
        'resultSetNames' => array(0),
30
        'set' => array(),
31
        'values' => array(),
32
        'options' => array(),
33
        'firstResult' => 0,
34
        'maxResults' => null,
35
    );
36
37
    private static $multipleParts = array(
38
        'select' => true,
39
        'from' => true,
40
        'where' => true,
41
        'groupBy' => true,
42
        'groupByLimit' => false,
43
        'withinGroupOrderBy' => true,
44
        'orderBy' => true,
45
        'facet' => true,
46
        'resultSetNames' => true,
47
        'set' => true,
48
        'values' => true,
49
        'options' => true,
50
        'firstResult' => false,
51
        'maxResults' => false,
52
    );
53
54
    private $isDirty = true;
55
56
    private $sql;
57
58
    private $parameters = array();
59
60
    private $parametersCounter = 0;
61
62 81
    public function __construct(Connection $conn)
63
    {
64 81
        $this->conn = $conn;
65 81
    }
66
67
    public function getEscaper()
68
    {
69
        return $this->conn->getEscaper();
70
    }
71
72 60
    public function select($select = null)
73
    {
74 60
        $this->type = self::TYPE_SELECT;
75 60
        if (null === $select) {
76
            return $this;
77
        }
78
79 60
        return $this->add('select', (array) $select);
80
    }
81
82 9
    public function addSelect($select)
83
    {
84 9
        $this->type = self::TYPE_SELECT;
85
86 9
        return $this->add('select', (array) $select, true);
87
    }
88
89 6
    public function update($index)
90
    {
91 6
        $this->type = self::TYPE_UPDATE;
92
93 6
        return $this->add('from', array('table' => $index));
94
    }
95
96 6
    public function insert($index)
97
    {
98 6
        $this->type = self::TYPE_INSERT;
99
100 6
        return $this->add('from', array('table' => $index));
101
    }
102
103 3
    public function replace($index)
104
    {
105 3
        $this->type = self::TYPE_REPLACE;
106
107 3
        return $this->add('from', array('table' => $index));
108
    }
109
110 3
    public function delete($index)
111
    {
112 3
        $this->type = self::TYPE_DELETE;
113
114 3
        return $this->add('from', array('table' => $index));
115
    }
116
117 6
    public function set($key, $value)
118
    {
119 6
        return $this->add('set', compact('key', 'value'), true);
120
    }
121
122 9
    public function values(array $values)
123
    {
124 9
        return $this->add('values', $values);
125
    }
126
127 3
    public function addValues(array $values)
128
    {
129 3
        return $this->add('values', $values, true);
130
    }
131
132 60
    public function from($index)
133
    {
134 60
        return $this->add('from', array('table' => $index));
135
    }
136
137 3
    public function addFrom($index)
138
    {
139 3
        return $this->add('from', array('table' => $index), true);
140
    }
141
142 39
    public function where($where)
143
    {
144 39
        return $this->add('where', $where);
145
    }
146
147 9
    public function andWhere($where)
148
    {
149 9
        return $this->add('where', $where, true);
150
    }
151
152 15
    public function groupBy($groupBy, $limit = null)
153
    {
154 10
        return $this
155 15
            ->add('groupBy', $groupBy)
156 15
            ->add('groupByLimit', $limit);
157
    }
158
159 3
    public function addGroupBy($groupBy)
160
    {
161 3
        return $this->add('groupBy', $groupBy, true);
162
    }
163
164 6
    public function withinGroupOrderBy($order, $direction = null)
165
    {
166 6
        return $this->add('withinGroupOrderBy', compact('order', 'direction'));
167
    }
168
169 3
    public function addWithinGroupOrderBy($order, $direction = null)
170
    {
171 3
        return $this->add('withinGroupOrderBy', compact('order', 'direction'), true);
172
    }
173
174
    /**
175
     * @param string|array $facet 'column1', or array('column1', 'column1') or array('column1' => 'column_alias', 'column2')
176
     * @param string $by
177
     * @param string $order
178
     * @param string $direction
179
     * @param int $limit
180
     * @param int $skip
181
     *
182
     * @return $this
183
     */
184 3
    public function facet($facet, $by = null, $order = null, $direction = null, $limit = null, $skip = 0)
185
    {
186 3
        $facet = (array) $facet;
187
188 3
        return $this->add('facet', compact('facet', 'by', 'order', 'direction', 'limit', 'skip'), true);
189
    }
190
191
    public function nameResultSet($name)
192
    {
193
        return $this->add('resultSetNames', $name, true);
194
    }
195
196 9
    public function orderBy($order, $direction = null)
197
    {
198 9
        return $this->add('orderBy', compact('order', 'direction'));
199
    }
200
201 6
    public function addOrderBy($order, $direction = null)
202
    {
203 6
        return $this->add('orderBy', compact('order', 'direction'), true);
204
    }
205
206 6
    public function setOption($name, $value)
207
    {
208 6
        return $this->add('options', compact('name', 'value'), true);
209
    }
210
211 6
    public function setMaxResults($limit)
212
    {
213 6
        return $this->add('maxResults', $limit);
214
    }
215
216 3
    public function setFirstResult($skip)
217
    {
218 3
        return $this->add('firstResult', $skip);
219
    }
220
221
    public function merge(self $qb)
0 ignored issues
show
The parameter $qb is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
222
    {
223
        //TODO: делать или не делать?
224
        // ...
225
    }
226
227 9
    public function setParameter($parameter, $value)
228
    {
229 9
        $this->parameters[$parameter] = $value;
230
231 9
        return $this;
232
    }
233
234
    /**
235
     * Creates a new named parameter and bind the value $value to it.
236
     *
237
     * @param string $value
238
     * @param string $prefix the name to bind with
239
     *
240
     * @return string the placeholder name used
241
     */
242 6
    public function createParameter($value, $prefix = 'gen_')
243
    {
244 6
        $prefix = preg_replace('/[^a-z0-9_]/ui', '_', $prefix);
245 6
        $prefix .= ++$this->parametersCounter;
246 6
        $this->setParameter($prefix, $value);
247
248 6
        return ':'.$prefix;
249
    }
250
251 9
    public function getParameters()
252
    {
253 9
        return $this->parameters;
254
    }
255
256
    public function execute()
257
    {
258
        return $this->conn->executeUpdate($this->getSql(), $this->parameters);
259
    }
260
261
    public function getResult()
262
    {
263
        return $this->conn->executeQuery($this->getSql(), $this->parameters);
264
    }
265
266
    public function getMultiResult()
267
    {
268
        return $this->conn->executeMultiQuery($this->getSql(), $this->parameters, array(), $this->sqlParts['resultSetNames']);
269
    }
270
271 78
    public function getSql()
272
    {
273 78
        if (!$this->isDirty) {
274
            return $this->sql;
275
        }
276
277 78
        switch ($this->type) {
278 78
            case self::TYPE_SELECT:
279 60
                $this->sql = $this->buildSqlForSelect();
280 60
                break;
281
282 18
            case self::TYPE_INSERT:
283 16
            case self::TYPE_REPLACE:
284 9
                $this->sql = $this->buildSqlForInsert();
285 9
                break;
286
287 9
            case self::TYPE_UPDATE:
288 6
                $this->sql = $this->buildSqlForUpdate();
289 6
                break;
290
291 3
            case self::TYPE_DELETE:
292 3
                $this->sql = $this->buildSqlForDelete();
293 3
                break;
294 52
        }
295
296 78
        $this->isDirty = false;
297
298 78
        return $this->sql;
299
    }
300
301
    /**
302
     * Either appends to or replaces a single, generic query part.
303
     *
304
     * @param string $sqlPartName
305
     * @param string|array $sqlPart
306
     * @param bool $append
307
     *
308
     * @return $this this QueryBuilder instance
309
     */
310 78
    protected function add($sqlPartName, $sqlPart, $append = false)
311
    {
312 78
        $this->isDirty = true;
313
314 78
        if (self::$multipleParts[$sqlPartName]) {
315 78
            if ($append) {
316 45
                $this->sqlParts[$sqlPartName][] = $sqlPart;
317 30
            } else {
318 78
                $this->sqlParts[$sqlPartName] = array($sqlPart);
319
            }
320 52
        } else {
321 21
            $this->sqlParts[$sqlPartName] = $sqlPart;
322
        }
323
324 78
        return $this;
325
    }
326
327 60
    protected function buildSqlForSelect()
328
    {
329 60
        $select = call_user_func_array('array_merge', $this->sqlParts['select']);
330 60
        $query = 'SELECT '.implode(', ', $select).' FROM ';
331
332 60
        $fromParts = array();
333 60
        foreach ($this->sqlParts['from'] as $from) {
334 60
            $table = $from['table'];
335 60
            if ($table instanceof static) {
336 3
                $fromParts[] = '('.$table->getSql().')';
337 3
                foreach ($table->getParameters() as $parameter => $value) {
338 3
                    $this->setParameter($parameter, $value);
339 2
                }
340 2
            } else {
341 60
                $fromParts[] = $table;
342
            }
343 40
        }
344
345 60
        $query .= implode(', ', $fromParts)
346 60
            .$this->buildWherePart()
347 60
            .$this->buildGroupByPart()
348 60
            .$this->buildOrderByPart();
349
350
        //TODO: inject limit, skip as parameters for better caching? Or just move caching to upper layer
351 60
        if ($this->sqlParts['maxResults']) {
352 6
            $query .= ' LIMIT '.(int) $this->sqlParts['firstResult'].', '.(int) $this->sqlParts['maxResults'];
353 4
        }
354
355 60
        $query .= $this->buildOptionsPart()
356 60
            .$this->buildFacetPart();
357
358 60
        return $query;
359
    }
360
361 9
    protected function buildSqlForInsert()
362
    {
363 9
        $columns = array();
364 9
        $valuesParts = array();
365 9
        foreach ($this->sqlParts['values'] as $value) {
366
            //TODO: check columns
367 9
            $columns = array_keys($value);
368 9
            $valuesParts[] = '('.implode(', ', $value).')';
369 6
        }
370
371 9
        $index = current($this->sqlParts['from'])['table'];
372 9
        $query = ($this->type === self::TYPE_REPLACE ? 'REPLACE' : 'INSERT')
373 9
            .' INTO '.$index
374 9
            .' ('.implode(', ', $columns).') VALUES '.implode(', ', $valuesParts);
375
376 9
        return $query;
377
    }
378
379 6
    protected function buildSqlForUpdate()
380
    {
381 6
        $index = current($this->sqlParts['from'])['table'];
382 6
        $setParts = array();
383 6
        foreach ($this->sqlParts['set'] as $setPart) {
384 6
            $setParts[] = $setPart['key'].' = '.$setPart['value'];
385 4
        }
386
387 6
        $query = 'UPDATE '.$index.' SET '.implode(', ', $setParts).$this->buildWherePart();
388
389 6
        return $query;
390
    }
391
392 3
    protected function buildSqlForDelete()
393
    {
394 3
        $index = current($this->sqlParts['from'])['table'];
395 3
        $query = 'DELETE FROM '.$index.$this->buildWherePart();
396
397 3
        return $query;
398
    }
399
400 69
    protected function buildWherePart()
401
    {
402 69
        if (!$this->sqlParts['where']) {
403 33
            return '';
404
        }
405
406 39
        return ' WHERE '.implode(' AND ', $this->sqlParts['where']);
407
    }
408
409 60
    protected function buildGroupByPart()
410
    {
411 60
        if (!$this->sqlParts['groupBy']) {
412 45
            return '';
413
        }
414
415 15
        $sql = ' GROUP'.($this->sqlParts['groupByLimit'] ? ' '.$this->sqlParts['groupByLimit'] : '')
416 15
            .' BY '.implode(', ', $this->sqlParts['groupBy']);
417
418 15
        if (!$this->sqlParts['withinGroupOrderBy']) {
419 9
            return $sql;
420
        }
421
422 6
        $orderByParts = array();
423 6 View Code Duplication
        foreach ($this->sqlParts['withinGroupOrderBy'] as $orderBy) {
0 ignored issues
show
This code seems to be duplicated across your project.

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.

Loading history...
424 6
            $orderByParts[] = $orderBy['order'].$this->getDirection($orderBy['order'], $orderBy['direction']);
425 4
        }
426
427 6
        return $sql.' WITHIN GROUP ORDER BY '.implode(', ', $orderByParts);
428
    }
429
430 60
    protected function buildOrderByPart()
431
    {
432 60
        if (!$this->sqlParts['orderBy']) {
433 51
            return '';
434
        }
435
436 9
        $orderByParts = array();
437 9 View Code Duplication
        foreach ($this->sqlParts['orderBy'] as $orderBy) {
0 ignored issues
show
This code seems to be duplicated across your project.

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.

Loading history...
438 9
            $orderByParts[] = $orderBy['order'].$this->getDirection($orderBy['order'], $orderBy['direction']);
439 6
        }
440
441 9
        return ' ORDER BY '.implode(', ', $orderByParts);
442
    }
443
444 60
    protected function buildOptionsPart()
445
    {
446 60
        if (!$this->sqlParts['options']) {
447 54
            return '';
448
        }
449
450 6
        $optionsParts = array();
451 6
        foreach ($this->sqlParts['options'] as $option) {
452 6
            $optionsParts[] = $option['name'].' = '.$option['value'];
453 4
        }
454
455 6
        return ' OPTION '.implode(', ', $optionsParts);
456
    }
457
458
    /**
459
     * Build FACET {expr_list} [BY {expr_list}] [ORDER BY {expr | FACET()} {ASC | DESC}] [LIMIT [offset,] count].
460
     *
461
     * @return string
462
     */
463 60
    protected function buildFacetPart()
464
    {
465 60
        if (!$this->sqlParts['facet']) {
466 57
            return '';
467
        }
468
469 3
        $facetParts = array();
470 3
        foreach ($this->sqlParts['facet'] as $facet) {
471 3
            $facetExpressions = array();
472 3
            foreach ($facet['facet'] as $key => $facetExpr) {
473 3
                if (is_int($key)) {
474 3
                    $facetExpressions[] = $facetExpr;
475 2
                } else {
476 3
                    $facetExpressions[] = $key.' AS '.$facetExpr;
477
                }
478 2
            }
479 3
            $facetPart = 'FACET '.implode(', ', $facetExpressions);
480 3
            if ($facet['by']) {
481 3
                $facetPart .= ' BY '.$facet['by'];
482 2
            }
483 3
            if ($facet['order']) {
484 3
                $facetPart .= ' ORDER BY '.$facet['order'].$this->getDirection($facet['order'], $facet['direction']);
485 2
            }
486 3
            if ($facet['limit']) {
487
                $facetPart .= ' LIMIT '.(int) $facet['skip'].', '.(int) $facet['limit'];
488
            }
489
490 3
            $facetParts[] = $facetPart;
491 2
        }
492
493 3
        return ' '.implode(' ', $facetParts);
494
    }
495
496 18
    protected function getDirection($order, $direction)
497
    {
498 18
        if (strtoupper($direction) === 'DESC') {
499 18
            return ' DESC';
500
        }
501
502 15
        if (null === $direction && strtoupper($order) === 'RAND()') {
503 3
            return '';
504
        }
505
506 15
        return ' ASC';
507
    }
508
}
509