Passed
Push — master ( a52eb2...005dc8 )
by Julito
15:26 queued 06:31
created

Database::get_main_database()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Doctrine\Common\Annotations\AnnotationRegistry;
6
use Doctrine\Common\EventManager;
7
use Doctrine\DBAL\Connection;
8
use Doctrine\DBAL\Driver\Statement;
9
use Doctrine\DBAL\Types\Type;
10
use Doctrine\ORM\Configuration;
11
use Doctrine\ORM\EntityManager;
12
13
class Database
14
{
15
    /**
16
     * @var EntityManager
17
     */
18
    private static $em;
19
    private static $connection;
20
21
    /**
22
     * Setup doctrine only for the installation.
23
     *
24
     * @param array  $params
25
     * @param string $entityRootPath
26
     */
27
    public function connect($params = [], $entityRootPath = '')
28
    {
29
        $config = self::getDoctrineConfig($entityRootPath);
30
        $config->setAutoGenerateProxyClasses(true);
31
        $config->setEntityNamespaces(
32
            [
33
                'ChamiloCoreBundle' => 'Chamilo\CoreBundle\Entity',
34
                'ChamiloCourseBundle' => 'Chamilo\CourseBundle\Entity',
35
            ]
36
        );
37
38
        $params['charset'] = 'utf8';
39
        $sysPath = api_get_path(SYMFONY_SYS_PATH);
40
41
        $cache = new Doctrine\Common\Cache\ArrayCache();
42
        // standard annotation reader
43
        $annotationReader = new Doctrine\Common\Annotations\AnnotationReader();
44
        $cachedAnnotationReader = new Doctrine\Common\Annotations\CachedReader(
45
            $annotationReader, // use reader
46
            $cache // and a cache driver
47
        );
48
49
        $evm = new EventManager();
50
        $timestampableListener = new Gedmo\Timestampable\TimestampableListener();
51
        $timestampableListener->setAnnotationReader($cachedAnnotationReader);
52
        $evm->addEventSubscriber($timestampableListener);
53
54
        $driverChain = new \Doctrine\Persistence\Mapping\Driver\MappingDriverChain();
55
        // load superclass metadata mapping only, into driver chain
56
        // also registers Gedmo annotations.NOTE: you can personalize it
57
        Gedmo\DoctrineExtensions::registerAbstractMappingIntoDriverChainORM(
58
            $driverChain, // our metadata driver chain, to hook into
59
            $cachedAnnotationReader // our cached annotation reader
60
        );
61
62
        AnnotationRegistry::registerLoader(
63
            function ($class) use ($sysPath) {
64
                $file = str_replace("\\", DIRECTORY_SEPARATOR, $class).".php";
65
                $file = str_replace('Symfony/Component/Validator', '', $file);
66
                $file = str_replace('Symfony\Component\Validator', '', $file);
67
                $file = str_replace('Symfony/Component/Serializer', '', $file);
68
69
                $fileToInclude = $sysPath.'vendor/symfony/validator/'.$file;
70
71
                if (file_exists($fileToInclude)) {
72
                    // file exists makes sure that the loader fails silently
73
                    require_once $fileToInclude;
74
75
                    return true;
76
                }
77
78
                $fileToInclude = $sysPath.'vendor/symfony/validator/Constraints/'.$file;
79
                if (file_exists($fileToInclude)) {
80
                    // file exists makes sure that the loader fails silently
81
                    require_once $fileToInclude;
82
83
                    return true;
84
                }
85
86
                $fileToInclude = $sysPath.'vendor/symfony/serializer/'.$file;
87
88
                if (file_exists($fileToInclude)) {
89
                    // file exists makes sure that the loader fails silently
90
                    require_once $fileToInclude;
91
92
                    return true;
93
                }
94
            }
95
        );
96
97
        AnnotationRegistry::registerFile(
98
            $sysPath.'vendor/api-platform/core/src/Annotation/ApiResource.php'
99
        );
100
        AnnotationRegistry::registerFile(
101
            $sysPath.'vendor/api-platform/core/src/Annotation/ApiFilter.php'
102
        );
103
        AnnotationRegistry::registerFile(
104
            $sysPath.'vendor/api-platform/core/src/Annotation/ApiProperty.php'
105
        );
106
        AnnotationRegistry::registerFile(
107
            $sysPath.'vendor/api-platform/core/src/Annotation/ApiSubresource.php'
108
        );
109
110
        $entityManager = EntityManager::create($params, $config, $evm);
111
112
        if (false === Type::hasType('uuid')) {
113
            Type::addType('uuid', \Symfony\Bridge\Doctrine\Types\UuidType::class);
114
        }
115
116
        $connection = $entityManager->getConnection();
117
        AnnotationRegistry::registerFile(
118
            $sysPath.'vendor/symfony/doctrine-bridge/Validator/Constraints/UniqueEntity.php'
119
        );
120
121
        $this->setConnection($connection);
122
        $this->setManager($entityManager);
123
    }
124
125
    /**
126
     * @param EntityManager $em
127
     */
128
    public static function setManager($em)
129
    {
130
        self::$em = $em;
131
    }
132
133
    public static function setConnection(Connection $connection)
134
    {
135
        self::$connection = $connection;
136
    }
137
138
    /**
139
     * @return Connection
140
     */
141
    public static function getConnection()
142
    {
143
        return self::$connection;
144
    }
145
146
    /**
147
     * @return EntityManager
148
     */
149
    public static function getManager()
150
    {
151
        return self::$em;
152
    }
153
154
    /**
155
     * Returns the name of the main database.
156
     *
157
     * @return string
158
     */
159
    public static function get_main_database()
160
    {
161
        return self::getManager()->getConnection()->getDatabase();
162
    }
163
164
    /**
165
     * Get main table.
166
     *
167
     * @param string $table
168
     *
169
     * @return string
170
     */
171
    public static function get_main_table($table)
172
    {
173
        return $table;
174
    }
175
176
    /**
177
     * Get course table.
178
     *
179
     * @param string $table
180
     *
181
     * @return string
182
     */
183
    public static function get_course_table($table)
184
    {
185
        return DB_COURSE_PREFIX.$table;
186
    }
187
188
    /**
189
     * Counts the number of rows in a table.
190
     *
191
     * @param string $table The table of which the rows should be counted
192
     *
193
     * @return int the number of rows in the given table
194
     *
195
     * @deprecated
196
     */
197
    public static function count_rows($table)
198
    {
199
        $obj = self::fetch_object(self::query("SELECT COUNT(*) AS n FROM $table"));
200
201
        return $obj->n;
202
    }
203
204
    /**
205
     * Returns the number of affected rows in the last database operation.
206
     *
207
     * @return int
208
     */
209
    public static function affected_rows($result)
210
    {
211
        return $result->rowCount();
212
    }
213
214
    /**
215
     * Escapes a string to insert into the database as text.
216
     *
217
     * @param string $string
218
     *
219
     * @return string
220
     */
221
    public static function escape_string($string)
222
    {
223
        $string = self::getManager()->getConnection()->quote($string);
224
        // The quote method from PDO also adds quotes around the string, which
225
        // is not how the legacy mysql_real_escape_string() was used in
226
        // Chamilo, so we need to remove the quotes around. Using trim will
227
        // remove more than one quote if they are sequenced, generating
228
        // broken queries and SQL injection risks
229
        return substr($string, 1, -1);
230
    }
231
232
    /**
233
     * Gets the array from a SQL result (as returned by Database::query).
234
     *
235
     * @param string $option Optional: "ASSOC","NUM" or "BOTH"
236
     *
237
     * @return array|mixed
238
     */
239
    public static function fetch_array($result, $option = 'BOTH')
240
    {
241
        if (false === $result) {
242
            return [];
243
        }
244
245
        return $result->fetch(self::customOptionToDoctrineOption($option));
246
    }
247
248
    /**
249
     * Gets an associative array from a SQL result (as returned by Database::query).
250
     *
251
     * @return array
252
     */
253
    public static function fetch_assoc($result)
254
    {
255
        return $result->fetch(PDO::FETCH_ASSOC);
256
    }
257
258
    /**
259
     * Gets the next row of the result of the SQL query
260
     * (as returned by Database::query) in an object form.
261
     *
262
     * @return mixed
263
     */
264
    public static function fetch_object($result)
265
    {
266
        return $result->fetch(PDO::FETCH_OBJ);
267
    }
268
269
    /**
270
     * Gets the array from a SQL result (as returned by Database::query)
271
     * help achieving database independence.
272
     *
273
     * @return mixed
274
     */
275
    public static function fetch_row($result)
276
    {
277
        if (false === $result) {
278
            return [];
279
        }
280
281
        return $result->fetch(PDO::FETCH_NUM);
282
    }
283
284
    /**
285
     * Gets the ID of the last item inserted into the database.
286
     *
287
     * @return string
288
     */
289
    public static function insert_id()
290
    {
291
        return self::getManager()->getConnection()->lastInsertId();
292
    }
293
294
    /**
295
     * @return int
296
     */
297
    public static function num_rows($result)
298
    {
299
        if (false === $result) {
300
            return 0;
301
        }
302
303
        return $result->rowCount();
304
    }
305
306
    /**
307
     * Acts as the relative *_result() function of most DB drivers and fetches a
308
     * specific line and a field.
309
     *
310
     * @param int    $row
311
     * @param string $field
312
     *
313
     * @return mixed
314
     */
315
    public static function result($resource, $row, $field = '')
316
    {
317
        if ($resource->rowCount() > 0) {
318
            $result = $resource->fetchAll(PDO::FETCH_BOTH);
319
320
            return $result[$row][$field];
321
        }
322
323
        return false;
324
    }
325
326
    /**
327
     * @param string $query
328
     *
329
     * @return Statement
330
     */
331
    public static function query($query)
332
    {
333
        $connection = self::getManager()->getConnection();
334
        $result = null;
335
        try {
336
            $result = $connection->executeQuery($query);
337
        } catch (Exception $e) {
338
            self::handleError($e);
339
        }
340
341
        return $result;
342
    }
343
344
    /**
345
     * @param Exception $e
346
     */
347
    public static function handleError($e)
348
    {
349
        $debug = 'test' === api_get_setting('server_type');
350
        if ($debug) {
351
            throw $e;
352
            exit;
0 ignored issues
show
Unused Code introduced by
ExitNode 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...
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...
353
        } else {
354
            error_log($e->getMessage());
355
            api_not_allowed(false, get_lang('An error has occurred. Please contact your system administrator.'));
356
            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...
357
        }
358
    }
359
360
    /**
361
     * @param string $option
362
     *
363
     * @return int
364
     */
365
    public static function customOptionToDoctrineOption($option)
366
    {
367
        switch ($option) {
368
            case 'ASSOC':
369
                return PDO::FETCH_ASSOC;
370
                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...
371
            case 'NUM':
372
                return PDO::FETCH_NUM;
373
                break;
374
            case 'BOTH':
375
            default:
376
                return PDO::FETCH_BOTH;
377
                break;
378
        }
379
    }
380
381
    /**
382
     * Stores a query result into an array.
383
     *
384
     * @author Olivier Brouckaert
385
     *
386
     * @param Statement $result - the return value of the query
387
     * @param string    $option BOTH, ASSOC, or NUM
388
     *
389
     * @return array - the value returned by the query
390
     */
391
    public static function store_result($result, $option = 'BOTH')
392
    {
393
        return $result->fetchAll(self::customOptionToDoctrineOption($option));
394
    }
395
396
    /**
397
     * Database insert.
398
     *
399
     * @param string $table_name
400
     * @param array  $attributes
401
     * @param bool   $show_query
402
     *
403
     * @return false|int
404
     */
405
    public static function insert($table_name, $attributes, $show_query = false)
406
    {
407
        if (empty($attributes) || empty($table_name)) {
408
            return false;
409
        }
410
411
        $params = array_keys($attributes);
412
413
        if (!empty($params)) {
414
            $sql = 'INSERT INTO '.$table_name.' ('.implode(',', $params).')
415
                    VALUES (:'.implode(', :', $params).')';
416
417
            if ($show_query) {
418
                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...
419
                error_log($sql);
420
            }
421
422
            $result = false;
423
            try {
424
                $statement = self::getConnection()->prepare($sql);
425
                $result = $statement->execute($attributes);
426
            } catch (Exception $e) {
427
                self::handleError($e);
428
            }
429
430
            if ($result) {
431
                return (int) self::getManager()->getConnection()->lastInsertId();
432
            }
433
        }
434
435
        return false;
436
    }
437
438
    /**
439
     * @param string $tableName       use Database::get_main_table
440
     * @param array  $attributes      Values to updates
441
     *                                Example: $params['name'] = 'Julio'; $params['lastname'] = 'Montoya';
442
     * @param array  $whereConditions where conditions i.e array('id = ?' =>'4')
443
     * @param bool   $showQuery
444
     *
445
     * @return bool|int
446
     */
447
    public static function update(
448
        $tableName,
449
        $attributes,
450
        $whereConditions = [],
451
        $showQuery = false
452
    ) {
453
        if (!empty($tableName) && !empty($attributes)) {
454
            $updateSql = '';
455
            $count = 1;
456
457
            foreach ($attributes as $key => $value) {
458
                if ($showQuery) {
459
                    echo $key.': '.$value.PHP_EOL;
460
                }
461
                $updateSql .= "$key = :$key ";
462
                if ($count < count($attributes)) {
463
                    $updateSql .= ', ';
464
                }
465
                $count++;
466
            }
467
468
            if (!empty($updateSql)) {
469
                // Parsing and cleaning the where conditions
470
                $whereReturn = self::parse_where_conditions($whereConditions);
471
                $sql = "UPDATE $tableName SET $updateSql $whereReturn ";
472
473
                try {
474
                    $statement = self::getManager()->getConnection()->prepare($sql);
475
                    $result = $statement->execute($attributes);
476
                } catch (Exception $e) {
477
                    self::handleError($e);
478
                }
479
480
                if ($showQuery) {
481
                    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...
482
                    var_dump($attributes);
483
                    var_dump($whereConditions);
484
                }
485
486
                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...
487
                    return $statement->rowCount();
488
                }
489
            }
490
        }
491
492
        return false;
493
    }
494
495
    /**
496
     * Experimental useful database finder.
497
     *
498
     * @param mixed|array $columns
499
     * @param string      $table_name
500
     * @param array       $conditions
501
     * @param string      $type_result
502
     * @param string      $option
503
     * @param bool        $debug
504
     *
505
     * @return array
506
     *
507
     * @todo    lot of stuff to do here
508
     * @todo    known issues, it doesn't work when using LIKE conditions
509
     *
510
     * @example array('where'=> array('course_code LIKE "?%"'))
511
     * @example array('where'=> array('type = ? AND category = ?' => array('setting', 'Plugins'))
512
     * @example array('where'=> array('name = "Julio" AND lastname = "montoya"'))
513
     */
514
    public static function select(
515
        $columns,
516
        $table_name,
517
        $conditions = [],
518
        $type_result = 'all',
519
        $option = 'ASSOC',
520
        $debug = false
521
    ) {
522
        if ($type_result === 'count') {
523
            $conditions['LIMIT'] = null;
524
            $conditions['limit'] = null;
525
        }
526
        $conditions = self::parse_conditions($conditions);
527
528
        //@todo we could do a describe here to check the columns ...
529
        if (is_array($columns)) {
530
            $clean_columns = implode(',', $columns);
531
        } else {
532
            if ('*' === $columns) {
533
                $clean_columns = '*';
534
            } else {
535
                $clean_columns = (string) $columns;
536
            }
537
        }
538
539
        if ($type_result === 'count') {
540
            $clean_columns = ' count(*) count ';
541
        }
542
        $sql = "SELECT $clean_columns FROM $table_name $conditions";
543
        if ($debug) {
544
            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...
545
        }
546
        $result = self::query($sql);
547
        if ($type_result === 'count') {
548
            $row = self::fetch_array($result, $option);
549
            if ($row) {
550
                return (int) $row['count'];
551
            }
552
553
            return 0;
554
        }
555
        $array = [];
556
557
        if ('all' === $type_result) {
558
            while ($row = self::fetch_array($result, $option)) {
559
                if (isset($row['id'])) {
560
                    $array[$row['id']] = $row;
561
                } else {
562
                    $array[] = $row;
563
                }
564
            }
565
        } else {
566
            $array = self::fetch_array($result, $option);
567
        }
568
569
        return $array;
570
    }
571
572
    /**
573
     * Parses WHERE/ORDER conditions i.e array('where'=>array('id = ?' =>'4'), 'order'=>'id DESC').
574
     *
575
     * @todo known issues, it doesn't work when using
576
     * LIKE conditions example: array('where'=>array('course_code LIKE "?%"'))
577
     *
578
     * @param array $conditions
579
     *
580
     * @return string Partial SQL string to add to longer query
581
     */
582
    public static function parse_conditions($conditions)
583
    {
584
        if (empty($conditions)) {
585
            return '';
586
        }
587
        $return_value = $where_return = '';
588
        foreach ($conditions as $type_condition => $condition_data) {
589
            if (false == $condition_data) {
590
                continue;
591
            }
592
            $type_condition = strtolower($type_condition);
593
            switch ($type_condition) {
594
                case 'where':
595
                    foreach ($condition_data as $condition => $value_array) {
596
                        if (is_array($value_array)) {
597
                            $clean_values = [];
598
                            foreach ($value_array as $item) {
599
                                $item = self::escape_string($item);
600
                                $clean_values[] = $item;
601
                            }
602
                        } else {
603
                            $value_array = self::escape_string($value_array);
604
                            $clean_values = [$value_array];
605
                        }
606
607
                        if (!empty($condition) && '' != $clean_values) {
608
                            $condition = str_replace('%', "'@percentage@'", $condition); //replace "%"
609
                            $condition = str_replace("'?'", "%s", $condition);
610
                            $condition = str_replace("?", "%s", $condition);
611
612
                            $condition = str_replace("@%s@", "@-@", $condition);
613
                            $condition = str_replace("%s", "'%s'", $condition);
614
                            $condition = str_replace("@-@", "@%s@", $condition);
615
616
                            // Treat conditions as string
617
                            $condition = vsprintf($condition, $clean_values);
618
                            $condition = str_replace('@percentage@', '%', $condition); //replace "%"
619
                            $where_return .= $condition;
620
                        }
621
                    }
622
623
                    if (!empty($where_return)) {
624
                        $return_value = " WHERE $where_return";
625
                    }
626
                    break;
627
                case 'order':
628
                    $order_array = $condition_data;
629
630
                    if (!empty($order_array)) {
631
                        // 'order' => 'id desc, name desc'
632
                        $order_array = self::escape_string($order_array);
633
                        $new_order_array = explode(',', $order_array);
634
                        $temp_value = [];
635
636
                        foreach ($new_order_array as $element) {
637
                            $element = explode(' ', $element);
638
                            $element = array_filter($element);
639
                            $element = array_values($element);
640
641
                            if (!empty($element[1])) {
642
                                $element[1] = strtolower($element[1]);
643
                                $order = 'DESC';
644
                                if (in_array($element[1], ['desc', 'asc'])) {
645
                                    $order = $element[1];
646
                                }
647
                                $temp_value[] = ' `'.$element[0].'` '.$order.' ';
648
                            } else {
649
                                //by default DESC
650
                                $temp_value[] = ' `'.$element[0].'` DESC ';
651
                            }
652
                        }
653
                        if (!empty($temp_value)) {
654
                            $return_value .= ' ORDER BY '.implode(', ', $temp_value);
655
                        }
656
                    }
657
                    break;
658
                case 'limit':
659
                    $limit_array = explode(',', $condition_data);
660
                    if (!empty($limit_array)) {
661
                        if (count($limit_array) > 1) {
662
                            $return_value .= ' LIMIT '.intval($limit_array[0]).' , '.intval($limit_array[1]);
663
                        } else {
664
                            $return_value .= ' LIMIT '.intval($limit_array[0]);
665
                        }
666
                    }
667
                    break;
668
            }
669
        }
670
671
        return $return_value;
672
    }
673
674
    /**
675
     * @param array $conditions
676
     *
677
     * @return string
678
     */
679
    public static function parse_where_conditions($conditions)
680
    {
681
        return self::parse_conditions(['where' => $conditions]);
682
    }
683
684
    /**
685
     * @param string $table_name
686
     * @param array  $where_conditions
687
     * @param bool   $show_query
688
     *
689
     * @return int
690
     */
691
    public static function delete($table_name, $where_conditions, $show_query = false)
692
    {
693
        $where_return = self::parse_where_conditions($where_conditions);
694
        $sql = "DELETE FROM $table_name $where_return ";
695
        if ($show_query) {
696
            echo $sql;
697
            echo '<br />';
698
        }
699
        $result = self::query($sql);
700
        $affected_rows = self::affected_rows($result);
701
        //@todo should return affected_rows for
702
        return $affected_rows;
703
    }
704
705
    /**
706
     * Get Doctrine configuration.
707
     *
708
     * @param string $path
709
     *
710
     * @return Configuration
711
     */
712
    public static function getDoctrineConfig($path)
713
    {
714
        $isDevMode = true; // Forces doctrine to use ArrayCache instead of apc/xcache/memcache/redis
715
        $isSimpleMode = false; // related to annotations @Entity
716
        $cache = null;
717
        $path = !empty($path) ? $path : api_get_path(SYMFONY_SYS_PATH);
718
719
        $paths = [
720
            $path.'src/Chamilo/CoreBundle/Entity',
721
            $path.'src/Chamilo/CourseBundle/Entity',
722
        ];
723
724
        $proxyDir = $path.'var/cache/';
725
726
        return \Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration(
727
            $paths,
728
            $isDevMode,
729
            $proxyDir,
730
            $cache,
731
            $isSimpleMode
732
        );
733
    }
734
735
    /**
736
     * @param string $table
737
     *
738
     * @return bool
739
     */
740
    public static function tableExists($table)
741
    {
742
        return self::getManager()->getConnection()->getSchemaManager()->tablesExist($table);
743
    }
744
745
    /**
746
     * @param string $table
747
     *
748
     * @return \Doctrine\DBAL\Schema\Column[]
749
     */
750
    public static function listTableColumns($table)
751
    {
752
        return self::getManager()->getConnection()->getSchemaManager()->listTableColumns($table);
753
    }
754
755
    public static function escapeField($field)
756
    {
757
        return self::escape_string(preg_replace("/[^a-zA-Z0-9_]/", '', $field));
758
    }
759
}
760