Completed
Push — master ( 4fc9f8...d0e06e )
by Julito
12:04
created

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