Completed
Push — master ( e0a519...eabd41 )
by Julito
119:58 queued 99:23
created

Database::update()   C

Complexity

Conditions 11
Paths 14

Size

Total Lines 46
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 25
c 1
b 0
f 0
nc 14
nop 4
dl 0
loc 46
rs 5.2653

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Doctrine\Common\Annotations\AnnotationRegistry;
5
use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\Driver\Statement;
7
use Doctrine\DBAL\Types\Type;
8
use Doctrine\ORM\EntityManager;
9
use Symfony\Component\Debug\ExceptionHandler;
10
11
/**
12
 * Class Database.
13
 */
14
class Database
15
{
16
    /**
17
     * @var EntityManager
18
     */
19
    private static $em;
20
    private static $connection;
21
22
    /**
23
     * Only used by the installer.
24
     *
25
     * @param array  $params
26
     * @param string $entityRootPath
27
     *
28
     * @throws \Doctrine\ORM\ORMException
29
     */
30
    public function connect(
31
        $params = [],
32
        $entityRootPath = ''
33
    ) {
34
        $config = self::getDoctrineConfig($entityRootPath);
35
        $config->setAutoGenerateProxyClasses(true);
36
37
        $config->setEntityNamespaces(
38
            [
39
                'ChamiloUserBundle' => 'Chamilo\UserBundle\Entity',
40
                'ChamiloCoreBundle' => 'Chamilo\CoreBundle\Entity',
41
                'ChamiloCourseBundle' => 'Chamilo\CourseBundle\Entity',
42
                'ChamiloSkillBundle' => 'Chamilo\SkillBundle\Entity',
43
                'ChamiloTicketBundle' => 'Chamilo\TicketBundle\Entity',
44
                'ChamiloPluginBundle' => 'Chamilo\PluginBundle\Entity',
45
            ]
46
        );
47
48
        $params['charset'] = 'utf8';
49
        $entityManager = EntityManager::create($params, $config);
50
        $connection = $entityManager->getConnection();
51
52
        $sysPath = !empty($sysPath) ? $sysPath : api_get_path(SYS_PATH);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sysPath seems to never exist and therefore empty should always be true.
Loading history...
53
        AnnotationRegistry::registerFile(
54
            $sysPath."vendor/symfony/doctrine-bridge/Validator/Constraints/UniqueEntity.php"
55
        );
56
57
        // Registering gedmo extensions
58
        AnnotationRegistry::registerAutoloadNamespace(
59
            'Gedmo\Mapping\Annotation',
60
            $sysPath."vendor/gedmo/doctrine-extensions/lib"
61
        );
62
63
        $this->setConnection($connection);
64
        $this->setManager($entityManager);
65
    }
66
67
    /**
68
     * @param EntityManager $em
69
     */
70
    public static function setManager($em)
71
    {
72
        self::$em = $em;
73
    }
74
75
    /**
76
     * @param Connection $connection
77
     */
78
    public static function setConnection(Connection $connection)
79
    {
80
        self::$connection = $connection;
81
    }
82
83
    /**
84
     * @return Connection
85
     */
86
    public function getConnection()
87
    {
88
        return self::$connection;
89
    }
90
91
    /**
92
     * @return EntityManager
93
     */
94
    public static function getManager()
95
    {
96
        return self::$em;
97
    }
98
99
    /**
100
     * Returns the name of the main database.
101
     *
102
     * @return string
103
     */
104
    public static function get_main_database()
105
    {
106
        return self::getManager()->getConnection()->getDatabase();
107
    }
108
109
    /**
110
     * Get main table.
111
     *
112
     * @param string $table
113
     *
114
     * @return string
115
     */
116
    public static function get_main_table($table)
117
    {
118
        return $table;
119
    }
120
121
    /**
122
     * Get course table.
123
     *
124
     * @param string $table
125
     *
126
     * @return string
127
     */
128
    public static function get_course_table($table)
129
    {
130
        return DB_COURSE_PREFIX.$table;
131
    }
132
133
    /**
134
     * Counts the number of rows in a table.
135
     *
136
     * @param string $table The table of which the rows should be counted
137
     *
138
     * @return int the number of rows in the given table
139
     *
140
     * @deprecated
141
     */
142
    public static function count_rows($table)
143
    {
144
        $obj = self::fetch_object(self::query("SELECT COUNT(*) AS n FROM $table"));
145
146
        return $obj->n;
147
    }
148
149
    /**
150
     * Returns the number of affected rows in the last database operation.
151
     *
152
     * @param Statement $result
153
     *
154
     * @return int
155
     */
156
    public static function affected_rows(Statement $result)
157
    {
158
        return $result->rowCount();
159
    }
160
161
    /**
162
     * Escape MySQL wildchars _ and % in LIKE search.
163
     *
164
     * @param string $text The string to escape
165
     *
166
     * @return string The escaped string
167
     */
168
    public static function escape_sql_wildcards($text)
169
    {
170
        $text = api_preg_replace("/_/", "\_", $text);
171
        $text = api_preg_replace("/%/", "\%", $text);
172
173
        return $text;
174
    }
175
176
    /**
177
     * Escapes a string to insert into the database as text.
178
     *
179
     * @param string $string
180
     *
181
     * @return string
182
     */
183
    public static function escape_string($string)
184
    {
185
        $string = self::getManager()->getConnection()->quote($string);
186
        // The quote method from PDO also adds quotes around the string, which
187
        // is not how the legacy mysql_real_escape_string() was used in
188
        // Chamilo, so we need to remove the quotes around. Using trim will
189
        // remove more than one quote if they are sequenced, generating
190
        // broken queries and SQL injection risks
191
        return substr($string, 1, -1);
192
    }
193
194
    /**
195
     * Gets the array from a SQL result (as returned by Database::query).
196
     *
197
     * @param Statement $result
198
     * @param string    $option Optional: "ASSOC","NUM" or "BOTH"
199
     *
200
     * @return array|mixed
201
     */
202
    public static function fetch_array(Statement $result, $option = 'BOTH')
203
    {
204
        if ($result === false) {
205
            return [];
206
        }
207
208
        return $result->fetch(self::customOptionToDoctrineOption($option));
209
    }
210
211
    /**
212
     * Gets an associative array from a SQL result (as returned by Database::query).
213
     *
214
     * @param Statement $result
215
     *
216
     * @return array
217
     */
218
    public static function fetch_assoc(Statement $result)
219
    {
220
        return $result->fetch(PDO::FETCH_ASSOC);
221
    }
222
223
    /**
224
     * Gets the next row of the result of the SQL query
225
     * (as returned by Database::query) in an object form.
226
     *
227
     * @param Statement $result
228
     *
229
     * @return mixed
230
     */
231
    public static function fetch_object(Statement $result)
232
    {
233
        return $result->fetch(PDO::FETCH_OBJ);
234
    }
235
236
    /**
237
     * Gets the array from a SQL result (as returned by Database::query)
238
     * help achieving database independence.
239
     *
240
     * @param Statement $result
241
     *
242
     * @return mixed
243
     */
244
    public static function fetch_row(Statement $result)
245
    {
246
        if ($result === false) {
247
            return [];
248
        }
249
250
        return $result->fetch(PDO::FETCH_NUM);
251
    }
252
253
    /**
254
     * Frees all the memory associated with the provided result identifier.
255
     *
256
     * @return bool|null Returns TRUE on success or FALSE on failure.
257
     *                   Notes: Use this method if you are concerned about how much memory is being
258
     *                   used for queries that return large result sets.
259
     *                   Anyway, all associated result memory is automatically freed at the end of the script's execution.
260
     */
261
    public static function free_result(Statement $result)
262
    {
263
        $result->closeCursor();
264
    }
265
266
    /**
267
     * Gets the ID of the last item inserted into the database.
268
     *
269
     * @return string
270
     */
271
    public static function insert_id()
272
    {
273
        return self::getManager()->getConnection()->lastInsertId();
274
    }
275
276
    /**
277
     * @param Statement $result
278
     *
279
     * @return int
280
     */
281
    public static function num_rows(Statement $result)
282
    {
283
        if ($result === false) {
284
            return 0;
285
        }
286
287
        return $result->rowCount();
288
    }
289
290
    /**
291
     * Acts as the relative *_result() function of most DB drivers and fetches a
292
     * specific line and a field.
293
     *
294
     * @param Statement $resource
295
     * @param int       $row
296
     * @param string    $field
297
     *
298
     * @return mixed
299
     */
300
    public static function result(Statement $resource, $row, $field = '')
301
    {
302
        if ($resource->rowCount() > 0) {
303
            $result = $resource->fetchAll(PDO::FETCH_BOTH);
304
305
            return $result[$row][$field];
306
        }
307
    }
308
309
    /**
310
     * @param string $query
311
     *
312
     * @return Statement
313
     */
314
    public static function query($query)
315
    {
316
        $connection = self::getManager()->getConnection();
317
        $result = null;
318
        try {
319
            $result = $connection->executeQuery($query);
320
        } catch (Exception $e) {
321
            self::handleError($e);
322
        }
323
324
        return $result;
325
    }
326
327
    /**
328
     * @param Exception $e
329
     */
330
    public static function handleError($e)
331
    {
332
        $debug = api_get_setting('server_type') == 'test';
333
        if ($debug) {
334
            // We use Symfony exception handler for better error information
335
            $handler = new ExceptionHandler();
336
            $handler->handle($e);
337
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
338
        } else {
339
            error_log($e->getMessage());
340
            api_not_allowed(false, get_lang('GeneralError'));
341
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
342
        }
343
    }
344
345
    /**
346
     * @param string $option
347
     *
348
     * @return int
349
     */
350
    public static function customOptionToDoctrineOption($option)
351
    {
352
        switch ($option) {
353
            case 'ASSOC':
354
                return PDO::FETCH_ASSOC;
355
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
356
            case 'NUM':
357
                return PDO::FETCH_NUM;
358
                break;
359
            case 'BOTH':
360
            default:
361
                return PDO::FETCH_BOTH;
362
                break;
363
        }
364
    }
365
366
    /**
367
     * Stores a query result into an array.
368
     *
369
     * @author Olivier Brouckaert
370
     *
371
     * @param Statement $result - the return value of the query
372
     * @param string    $option BOTH, ASSOC, or NUM
373
     *
374
     * @return array - the value returned by the query
375
     */
376
    public static function store_result(Statement $result, $option = 'BOTH')
377
    {
378
        return $result->fetchAll(self::customOptionToDoctrineOption($option));
379
    }
380
381
    /**
382
     * Database insert.
383
     *
384
     * @param string $table_name
385
     * @param array  $attributes
386
     * @param bool   $show_query
387
     *
388
     * @return false|int
389
     */
390
    public static function insert($table_name, $attributes, $show_query = false)
391
    {
392
        if (empty($attributes) || empty($table_name)) {
393
            return false;
394
        }
395
396
        $params = array_keys($attributes);
397
398
        if (!empty($params)) {
399
            $sql = 'INSERT INTO '.$table_name.' ('.implode(',', $params).')
400
                    VALUES (:'.implode(', :', $params).')';
401
402
            if ($show_query) {
403
                var_dump($sql);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($sql) looks like debug code. Are you sure you do not want to remove it?
Loading history...
404
                error_log($sql);
405
            }
406
407
            $result = false;
408
            try {
409
                $statement = self::getConnection()->prepare($sql);
0 ignored issues
show
Bug Best Practice introduced by
The method Database::getConnection() is not static, but was called statically. ( Ignorable by Annotation )

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

409
                $statement = self::/** @scrutinizer ignore-call */ getConnection()->prepare($sql);
Loading history...
410
                $result = $statement->execute($attributes);
411
            } catch (Exception $e) {
412
                self::handleError($e);
413
            }
414
415
            if ($result) {
416
                return (int) self::getManager()->getConnection()->lastInsertId();
417
            }
418
        }
419
420
        return false;
421
    }
422
423
    /**
424
     * @param string $tableName       use Database::get_main_table
425
     * @param array  $attributes      Values to updates
426
     *                                Example: $params['name'] = 'Julio'; $params['lastname'] = 'Montoya';
427
     * @param array  $whereConditions where conditions i.e array('id = ?' =>'4')
428
     * @param bool   $showQuery
429
     *
430
     * @return bool|int
431
     */
432
    public static function update(
433
        $tableName,
434
        $attributes,
435
        $whereConditions = [],
436
        $showQuery = false
437
    ) {
438
        if (!empty($tableName) && !empty($attributes)) {
439
            $updateSql = '';
440
            $count = 1;
441
442
            foreach ($attributes as $key => $value) {
443
                if ($showQuery) {
444
                    echo $key.': '.$value.PHP_EOL;
445
                }
446
                $updateSql .= "$key = :$key ";
447
                if ($count < count($attributes)) {
448
                    $updateSql .= ', ';
449
                }
450
                $count++;
451
            }
452
453
            if (!empty($updateSql)) {
454
                // Parsing and cleaning the where conditions
455
                $whereReturn = self::parse_where_conditions($whereConditions);
456
                $sql = "UPDATE $tableName SET $updateSql $whereReturn ";
457
458
                try {
459
                    $statement = self::getManager()->getConnection()->prepare($sql);
460
                    $result = $statement->execute($attributes);
461
                } catch (Exception $e) {
462
                    self::handleError($e);
463
                }
464
465
                if ($showQuery) {
466
                    var_dump($sql);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($sql) looks like debug code. Are you sure you do not want to remove it?
Loading history...
467
                    var_dump($attributes);
468
                    var_dump($whereConditions);
469
                }
470
471
                if ($result && $statement) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.
Loading history...
472
                    return $statement->rowCount();
473
                }
474
            }
475
        }
476
477
        return false;
478
    }
479
480
    /**
481
     * Experimental useful database finder.
482
     *
483
     * @todo lot of stuff to do here
484
     * @todo known issues, it doesn't work when using LIKE conditions
485
     *
486
     * @example array('where'=> array('course_code LIKE "?%"'))
487
     * @example array('where'=> array('type = ? AND category = ?' => array('setting', 'Plugins'))
488
     * @example array('where'=> array('name = "Julio" AND lastname = "montoya"'))
489
     *
490
     * @param array  $columns
491
     * @param string $table_name
492
     * @param array  $conditions
493
     * @param string $type_result
494
     * @param string $option
495
     * @param bool   $debug
496
     *
497
     * @return array
498
     */
499
    public static function select(
500
        $columns,
501
        $table_name,
502
        $conditions = [],
503
        $type_result = 'all',
504
        $option = 'ASSOC',
505
        $debug = false
506
    ) {
507
        $conditions = self::parse_conditions($conditions);
508
509
        //@todo we could do a describe here to check the columns ...
510
        if (is_array($columns)) {
511
            $clean_columns = implode(',', $columns);
512
        } else {
513
            if ($columns == '*') {
514
                $clean_columns = '*';
515
            } else {
516
                $clean_columns = (string) $columns;
517
            }
518
        }
519
520
        $sql = "SELECT $clean_columns FROM $table_name $conditions";
521
        if ($debug) {
522
            var_dump($sql);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($sql) looks like debug code. Are you sure you do not want to remove it?
Loading history...
523
        }
524
        $result = self::query($sql);
525
        $array = [];
526
527
        if ($type_result === 'all') {
528
            while ($row = self::fetch_array($result, $option)) {
529
                if (isset($row['id'])) {
530
                    $array[$row['id']] = $row;
531
                } else {
532
                    $array[] = $row;
533
                }
534
            }
535
        } else {
536
            $array = self::fetch_array($result, $option);
537
        }
538
539
        return $array;
540
    }
541
542
    /**
543
     * Parses WHERE/ORDER conditions i.e array('where'=>array('id = ?' =>'4'), 'order'=>'id DESC').
544
     *
545
     * @todo known issues, it doesn't work when using
546
     * LIKE conditions example: array('where'=>array('course_code LIKE "?%"'))
547
     *
548
     * @param array $conditions
549
     *
550
     * @return string Partial SQL string to add to longer query
551
     */
552
    public static function parse_conditions($conditions)
553
    {
554
        if (empty($conditions)) {
555
            return '';
556
        }
557
        $return_value = $where_return = '';
558
        foreach ($conditions as $type_condition => $condition_data) {
559
            if ($condition_data == false) {
560
                continue;
561
            }
562
            $type_condition = strtolower($type_condition);
563
            switch ($type_condition) {
564
                case 'where':
565
                    foreach ($condition_data as $condition => $value_array) {
566
                        if (is_array($value_array)) {
567
                            $clean_values = [];
568
                            foreach ($value_array as $item) {
569
                                $item = self::escape_string($item);
570
                                $clean_values[] = $item;
571
                            }
572
                        } else {
573
                            $value_array = self::escape_string($value_array);
574
                            $clean_values = $value_array;
575
                        }
576
577
                        if (!empty($condition) && $clean_values != '') {
578
                            $condition = str_replace('%', "'@percentage@'", $condition); //replace "%"
579
                            $condition = str_replace("'?'", "%s", $condition);
580
                            $condition = str_replace("?", "%s", $condition);
581
582
                            $condition = str_replace("@%s@", "@-@", $condition);
583
                            $condition = str_replace("%s", "'%s'", $condition);
584
                            $condition = str_replace("@-@", "@%s@", $condition);
585
586
                            // Treat conditions as string
587
                            $condition = vsprintf($condition, $clean_values);
0 ignored issues
show
Bug introduced by
It seems like $clean_values can also be of type string; however, parameter $args of vsprintf() does only seem to accept array, 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

587
                            $condition = vsprintf($condition, /** @scrutinizer ignore-type */ $clean_values);
Loading history...
588
                            $condition = str_replace('@percentage@', '%', $condition); //replace "%"
589
                            $where_return .= $condition;
590
                        }
591
                    }
592
593
                    if (!empty($where_return)) {
594
                        $return_value = " WHERE $where_return";
595
                    }
596
                    break;
597
                case 'order':
598
                    $order_array = $condition_data;
599
600
                    if (!empty($order_array)) {
601
                        // 'order' => 'id desc, name desc'
602
                        $order_array = self::escape_string($order_array, null, false);
603
                        $new_order_array = explode(',', $order_array);
604
                        $temp_value = [];
605
606
                        foreach ($new_order_array as $element) {
607
                            $element = explode(' ', $element);
608
                            $element = array_filter($element);
609
                            $element = array_values($element);
610
611
                            if (!empty($element[1])) {
612
                                $element[1] = strtolower($element[1]);
613
                                $order = 'DESC';
614
                                if (in_array($element[1], ['desc', 'asc'])) {
615
                                    $order = $element[1];
616
                                }
617
                                $temp_value[] = $element[0].' '.$order.' ';
618
                            } else {
619
                                //by default DESC
620
                                $temp_value[] = $element[0].' DESC ';
621
                            }
622
                        }
623
                        if (!empty($temp_value)) {
624
                            $return_value .= ' ORDER BY '.implode(', ', $temp_value);
625
                        }
626
                    }
627
                    break;
628
                case 'limit':
629
                    $limit_array = explode(',', $condition_data);
630
                    if (!empty($limit_array)) {
631
                        if (count($limit_array) > 1) {
632
                            $return_value .= ' LIMIT '.intval($limit_array[0]).' , '.intval($limit_array[1]);
633
                        } else {
634
                            $return_value .= ' LIMIT '.intval($limit_array[0]);
635
                        }
636
                    }
637
                    break;
638
            }
639
        }
640
641
        return $return_value;
642
    }
643
644
    /**
645
     * @param array $conditions
646
     *
647
     * @return string
648
     */
649
    public static function parse_where_conditions($conditions)
650
    {
651
        return self::parse_conditions(['where' => $conditions]);
652
    }
653
654
    /**
655
     * @param string $table_name
656
     * @param array  $where_conditions
657
     * @param bool   $show_query
658
     *
659
     * @return int
660
     */
661
    public static function delete($table_name, $where_conditions, $show_query = false)
662
    {
663
        $where_return = self::parse_where_conditions($where_conditions);
664
        $sql = "DELETE FROM $table_name $where_return ";
665
        if ($show_query) {
666
            echo $sql;
667
            echo '<br />';
668
        }
669
        $result = self::query($sql);
670
        $affected_rows = self::affected_rows($result);
671
        //@todo should return affected_rows for
672
        return $affected_rows;
673
    }
674
675
    /**
676
     * Get Doctrine configuration.
677
     *
678
     * @param string $path
679
     *
680
     * @return \Doctrine\ORM\Configuration
681
     */
682
    public static function getDoctrineConfig($path)
683
    {
684
        $isDevMode = true; // Forces doctrine to use ArrayCache instead of apc/xcache/memcache/redis
685
        $isSimpleMode = false; // related to annotations @Entity
686
        $cache = null;
687
        $path = !empty($path) ? $path : api_get_path(SYS_PATH);
688
689
        $paths = [
690
            //$path.'src/Chamilo/ClassificationBundle/Entity',
691
            //$path.'src/Chamilo/MediaBundle/Entity',
692
            //$path.'src/Chamilo/PageBundle/Entity',
693
            $path.'src/Chamilo/CoreBundle/Entity',
694
            $path.'src/Chamilo/UserBundle/Entity',
695
            $path.'src/Chamilo/CourseBundle/Entity',
696
            $path.'src/Chamilo/TicketBundle/Entity',
697
            $path.'src/Chamilo/SkillBundle/Entity',
698
            $path.'src/Chamilo/PluginBundle/Entity',
699
            //$path.'vendor/sonata-project/user-bundle/Entity',
700
            //$path.'vendor/sonata-project/user-bundle/Model',
701
            //$path.'vendor/friendsofsymfony/user-bundle/FOS/UserBundle/Entity',
702
        ];
703
704
        $proxyDir = $path.'var/cache/';
705
706
        $config = \Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration(
707
            $paths,
708
            $isDevMode,
709
            $proxyDir,
710
            $cache,
711
            $isSimpleMode
712
        );
713
714
        return $config;
715
    }
716
717
    /**
718
     * @param string $table
719
     *
720
     * @return bool
721
     */
722
    public static function tableExists($table)
723
    {
724
        return self::getManager()->getConnection()->getSchemaManager()->tablesExist($table);
725
    }
726
727
    /**
728
     * @param string $table
729
     *
730
     * @return \Doctrine\DBAL\Schema\Column[]
731
     */
732
    public static function listTableColumns($table)
733
    {
734
        return self::getManager()->getConnection()->getSchemaManager()->listTableColumns($table);
735
    }
736
}
737