Passed
Push — master ( 5e2840...d88203 )
by Julito
07:41
created

Database::count_rows()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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