Completed
Push — master ( 255be6...90a7a9 )
by Julito
22:42
created

Database::connect()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 36
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 20
nc 2
nop 2
dl 0
loc 36
rs 8.8571
c 0
b 0
f 0
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
10
/**
11
 * Class Database.
12
 */
13
class Database
14
{
15
    /**
16
     * @var EntityManager
17
     */
18
    private static $em;
19
    private static $connection;
20
21
    /**
22
     * Only used by the installer.
23
     *
24
     * @param array  $params
25
     * @param string $entityRootPath
26
     *
27
     * @throws \Doctrine\ORM\ORMException
28
     */
29
    public function connect(
30
        $params = [],
31
        $entityRootPath = ''
32
    ) {
33
        $config = self::getDoctrineConfig($entityRootPath);
34
        $config->setAutoGenerateProxyClasses(true);
35
36
        $config->setEntityNamespaces(
37
            [
38
                'ChamiloUserBundle' => 'Chamilo\UserBundle\Entity',
39
                'ChamiloCoreBundle' => 'Chamilo\CoreBundle\Entity',
40
                'ChamiloCourseBundle' => 'Chamilo\CourseBundle\Entity',
41
                'ChamiloSkillBundle' => 'Chamilo\SkillBundle\Entity',
42
                'ChamiloTicketBundle' => 'Chamilo\TicketBundle\Entity',
43
                'ChamiloPluginBundle' => 'Chamilo\PluginBundle\Entity',
44
            ]
45
        );
46
47
        $params['charset'] = 'utf8';
48
        $entityManager = EntityManager::create($params, $config);
49
        $connection = $entityManager->getConnection();
50
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
            error_log($e->getMessage());
322
            api_not_allowed(false, get_lang('GeneralError'));
323
        } finally {
324
            return $result;
325
        }
326
327
        return $result;
0 ignored issues
show
Unused Code introduced by
return $result is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
328
    }
329
330
    /**
331
     * @param string $option
332
     *
333
     * @return int
334
     */
335
    public static function customOptionToDoctrineOption($option)
336
    {
337
        switch ($option) {
338
            case 'ASSOC':
339
                return PDO::FETCH_ASSOC;
340
                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...
341
            case 'NUM':
342
                return PDO::FETCH_NUM;
343
                break;
344
            case 'BOTH':
345
            default:
346
                return PDO::FETCH_BOTH;
347
                break;
348
        }
349
    }
350
351
    /**
352
     * Stores a query result into an array.
353
     *
354
     * @author Olivier Brouckaert
355
     *
356
     * @param Statement $result - the return value of the query
357
     * @param string    $option BOTH, ASSOC, or NUM
358
     *
359
     * @return array - the value returned by the query
360
     */
361
    public static function store_result(Statement $result, $option = 'BOTH')
362
    {
363
        return $result->fetchAll(self::customOptionToDoctrineOption($option));
364
    }
365
366
    /**
367
     * Database insert.
368
     *
369
     * @param string $table_name
370
     * @param array  $attributes
371
     * @param bool   $show_query
372
     *
373
     * @return false|int
374
     */
375
    public static function insert($table_name, $attributes, $show_query = false)
376
    {
377
        if (empty($attributes) || empty($table_name)) {
378
            return false;
379
        }
380
381
        $params = array_keys($attributes);
382
383
        if (!empty($params)) {
384
            $sql = 'INSERT INTO '.$table_name.' ('.implode(',', $params).')
385
                    VALUES (:'.implode(', :', $params).')';
386
387
            $statement = self::getManager()->getConnection()->prepare($sql);
388
            $result = $statement->execute($attributes);
389
390
            if ($show_query) {
391
                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...
392
                error_log($sql);
393
            }
394
395
            if ($result) {
396
                return (int) self::getManager()->getConnection()->lastInsertId();
397
            }
398
        }
399
400
        return false;
401
    }
402
403
    /**
404
     * @param string $tableName       use Database::get_main_table
405
     * @param array  $attributes      Values to updates
406
     *                                Example: $params['name'] = 'Julio'; $params['lastname'] = 'Montoya';
407
     * @param array  $whereConditions where conditions i.e array('id = ?' =>'4')
408
     * @param bool   $showQuery
409
     *
410
     * @return bool|int
411
     */
412
    public static function update(
413
        $tableName,
414
        $attributes,
415
        $whereConditions = [],
416
        $showQuery = false
417
    ) {
418
        if (!empty($tableName) && !empty($attributes)) {
419
            $updateSql = '';
420
            $count = 1;
421
422
            foreach ($attributes as $key => $value) {
423
                if ($showQuery) {
424
                    echo $key.': '.$value.PHP_EOL;
425
                }
426
                $updateSql .= "$key = :$key ";
427
                if ($count < count($attributes)) {
428
                    $updateSql .= ', ';
429
                }
430
                $count++;
431
            }
432
433
            if (!empty($updateSql)) {
434
                // Parsing and cleaning the where conditions
435
                $whereReturn = self::parse_where_conditions($whereConditions);
436
                $sql = "UPDATE $tableName SET $updateSql $whereReturn ";
437
                $statement = self::getManager()->getConnection()->prepare($sql);
438
                $result = $statement->execute($attributes);
439
440
                if ($showQuery) {
441
                    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...
442
                    var_dump($attributes);
443
                    var_dump($whereConditions);
444
                }
445
446
                if ($result) {
447
                    return $statement->rowCount();
448
                }
449
            }
450
        }
451
452
        return false;
453
    }
454
455
    /**
456
     * Experimental useful database finder.
457
     *
458
     * @todo lot of stuff to do here
459
     * @todo known issues, it doesn't work when using LIKE conditions
460
     *
461
     * @example array('where'=> array('course_code LIKE "?%"'))
462
     * @example array('where'=> array('type = ? AND category = ?' => array('setting', 'Plugins'))
463
     * @example array('where'=> array('name = "Julio" AND lastname = "montoya"'))
464
     *
465
     * @param array  $columns
466
     * @param string $table_name
467
     * @param array  $conditions
468
     * @param string $type_result
469
     * @param string $option
470
     * @param bool   $debug
471
     *
472
     * @return array
473
     */
474
    public static function select(
475
        $columns,
476
        $table_name,
477
        $conditions = [],
478
        $type_result = 'all',
479
        $option = 'ASSOC',
480
        $debug = false
481
    ) {
482
        $conditions = self::parse_conditions($conditions);
483
484
        //@todo we could do a describe here to check the columns ...
485
        if (is_array($columns)) {
486
            $clean_columns = implode(',', $columns);
487
        } else {
488
            if ($columns == '*') {
489
                $clean_columns = '*';
490
            } else {
491
                $clean_columns = (string) $columns;
492
            }
493
        }
494
495
        $sql = "SELECT $clean_columns FROM $table_name $conditions";
496
        if ($debug) {
497
            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...
498
        }
499
        $result = self::query($sql);
500
        $array = [];
501
502
        if ($type_result === 'all') {
503
            while ($row = self::fetch_array($result, $option)) {
504
                if (isset($row['id'])) {
505
                    $array[$row['id']] = $row;
506
                } else {
507
                    $array[] = $row;
508
                }
509
            }
510
        } else {
511
            $array = self::fetch_array($result, $option);
512
        }
513
514
        return $array;
515
    }
516
517
    /**
518
     * Parses WHERE/ORDER conditions i.e array('where'=>array('id = ?' =>'4'), 'order'=>'id DESC').
519
     *
520
     * @todo known issues, it doesn't work when using
521
     * LIKE conditions example: array('where'=>array('course_code LIKE "?%"'))
522
     *
523
     * @param array $conditions
524
     *
525
     * @return string Partial SQL string to add to longer query
526
     */
527
    public static function parse_conditions($conditions)
528
    {
529
        if (empty($conditions)) {
530
            return '';
531
        }
532
        $return_value = $where_return = '';
533
        foreach ($conditions as $type_condition => $condition_data) {
534
            if ($condition_data == false) {
535
                continue;
536
            }
537
            $type_condition = strtolower($type_condition);
538
            switch ($type_condition) {
539
                case 'where':
540
                    foreach ($condition_data as $condition => $value_array) {
541
                        if (is_array($value_array)) {
542
                            $clean_values = [];
543
                            foreach ($value_array as $item) {
544
                                $item = self::escape_string($item);
545
                                $clean_values[] = $item;
546
                            }
547
                        } else {
548
                            $value_array = self::escape_string($value_array);
549
                            $clean_values = $value_array;
550
                        }
551
552
                        if (!empty($condition) && $clean_values != '') {
553
                            $condition = str_replace('%', "'@percentage@'", $condition); //replace "%"
554
                            $condition = str_replace("'?'", "%s", $condition);
555
                            $condition = str_replace("?", "%s", $condition);
556
557
                            $condition = str_replace("@%s@", "@-@", $condition);
558
                            $condition = str_replace("%s", "'%s'", $condition);
559
                            $condition = str_replace("@-@", "@%s@", $condition);
560
561
                            // Treat conditions as string
562
                            $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

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