database.lib.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. use Doctrine\Common\Annotations\AnnotationRegistry;
  4. use Doctrine\DBAL\Connection;
  5. use Doctrine\DBAL\Driver\Statement;
  6. use Doctrine\DBAL\Types\Type;
  7. use Doctrine\ORM\EntityManager;
  8. /**
  9. * Class Database
  10. */
  11. class Database
  12. {
  13. /**
  14. * @var EntityManager
  15. */
  16. private static $em;
  17. private static $connection;
  18. public static $utcDateTimeClass;
  19. /**
  20. * @param EntityManager $em
  21. */
  22. public function setManager($em)
  23. {
  24. self::$em = $em;
  25. }
  26. /**
  27. * @param Connection $connection
  28. */
  29. public function setConnection(Connection $connection)
  30. {
  31. self::$connection = $connection;
  32. }
  33. /**
  34. * @return Connection
  35. */
  36. public function getConnection()
  37. {
  38. return self::$connection;
  39. }
  40. /**
  41. * @return EntityManager
  42. */
  43. public static function getManager()
  44. {
  45. return self::$em;
  46. }
  47. /**
  48. * Returns the name of the main database.
  49. *
  50. * @return string
  51. */
  52. public static function get_main_database()
  53. {
  54. return self::getManager()->getConnection()->getDatabase();
  55. }
  56. /**
  57. * Get main table
  58. *
  59. * @param string $table
  60. *
  61. * @return mixed
  62. */
  63. public static function get_main_table($table)
  64. {
  65. return $table;
  66. }
  67. /**
  68. * Get course table
  69. *
  70. * @param string $table
  71. *
  72. * @return string
  73. */
  74. public static function get_course_table($table)
  75. {
  76. return DB_COURSE_PREFIX.$table;
  77. }
  78. /**
  79. * Counts the number of rows in a table
  80. * @param string $table The table of which the rows should be counted
  81. *
  82. * @return int The number of rows in the given table.
  83. * @deprecated
  84. */
  85. public static function count_rows($table)
  86. {
  87. $obj = self::fetch_object(self::query("SELECT COUNT(*) AS n FROM $table"));
  88. return $obj->n;
  89. }
  90. /**
  91. * Returns the number of affected rows in the last database operation.
  92. * @param Statement $result
  93. *
  94. * @return int
  95. */
  96. public static function affected_rows(Statement $result)
  97. {
  98. return $result->rowCount();
  99. }
  100. /**
  101. * @return string
  102. */
  103. public static function getUTCDateTimeTypeClass()
  104. {
  105. return isset(self::$utcDateTimeClass) ? self::$utcDateTimeClass :
  106. 'Chamilo\CoreBundle\DoctrineExtensions\DBAL\Types\UTCDateTimeType';
  107. }
  108. /**
  109. * Connect to the database sets the entity manager.
  110. *
  111. * @param array $params
  112. * @param string $sysPath
  113. * @param string $entityRootPath
  114. *
  115. * @throws \Doctrine\ORM\ORMException
  116. */
  117. public function connect($params = array(), $sysPath = '', $entityRootPath = '')
  118. {
  119. $config = self::getDoctrineConfig($entityRootPath);
  120. $config->setEntityNamespaces(
  121. array(
  122. 'ChamiloUserBundle' => 'Chamilo\UserBundle\Entity',
  123. 'ChamiloCoreBundle' => 'Chamilo\CoreBundle\Entity',
  124. 'ChamiloCourseBundle' => 'Chamilo\CourseBundle\Entity'
  125. )
  126. );
  127. $params['charset'] = 'utf8';
  128. $entityManager = EntityManager::create($params, $config);
  129. $sysPath = !empty($sysPath) ? $sysPath : api_get_path(SYS_PATH);
  130. // Registering Constraints
  131. AnnotationRegistry::registerAutoloadNamespace(
  132. 'Symfony\Component\Validator\Constraint',
  133. $sysPath."vendor/symfony/validator"
  134. );
  135. AnnotationRegistry::registerFile(
  136. $sysPath."vendor/symfony/doctrine-bridge/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php"
  137. );
  138. // Registering gedmo extensions
  139. AnnotationRegistry::registerAutoloadNamespace(
  140. 'Gedmo\Mapping\Annotation',
  141. $sysPath."vendor/gedmo/doctrine-extensions/lib"
  142. );
  143. Type::overrideType(
  144. Type::DATETIME,
  145. self::getUTCDateTimeTypeClass()
  146. );
  147. $listener = new \Gedmo\Timestampable\TimestampableListener();
  148. $entityManager->getEventManager()->addEventSubscriber($listener);
  149. $listener = new \Gedmo\Tree\TreeListener();
  150. $entityManager->getEventManager()->addEventSubscriber($listener);
  151. $listener = new \Gedmo\Sortable\SortableListener();
  152. $entityManager->getEventManager()->addEventSubscriber($listener);
  153. $connection = $entityManager->getConnection();
  154. $this->setConnection($connection);
  155. $this->setManager($entityManager);
  156. }
  157. /**
  158. * Escape MySQL wildchars _ and % in LIKE search
  159. * @param string $text The string to escape
  160. *
  161. * @return string The escaped string
  162. */
  163. public static function escape_sql_wildcards($text)
  164. {
  165. $text = api_preg_replace("/_/", "\_", $text);
  166. $text = api_preg_replace("/%/", "\%", $text);
  167. return $text;
  168. }
  169. /**
  170. * Escapes a string to insert into the database as text
  171. *
  172. * @param string $string
  173. *
  174. * @return string
  175. */
  176. public static function escape_string($string)
  177. {
  178. $string = self::getManager()->getConnection()->quote($string);
  179. return trim($string, "'");
  180. }
  181. /**
  182. * Gets the array from a SQL result (as returned by Database::query)
  183. *
  184. * @param Statement $result
  185. * @param string $option Optional: "ASSOC","NUM" or "BOTH"
  186. *
  187. * @return array|mixed
  188. */
  189. public static function fetch_array(Statement $result, $option = 'BOTH')
  190. {
  191. if ($result === false) {
  192. return array();
  193. }
  194. return $result->fetch(self::customOptionToDoctrineOption($option));
  195. }
  196. /**
  197. * Gets an associative array from a SQL result (as returned by Database::query).
  198. *
  199. * @param Statement $result
  200. *
  201. * @return array
  202. */
  203. public static function fetch_assoc(Statement $result)
  204. {
  205. return $result->fetch(PDO::FETCH_ASSOC);
  206. }
  207. /**
  208. * Gets the next row of the result of the SQL query
  209. * (as returned by Database::query) in an object form
  210. *
  211. * @param Statement $result
  212. *
  213. * @return mixed
  214. */
  215. public static function fetch_object(Statement $result)
  216. {
  217. return $result->fetch(PDO::FETCH_OBJ);
  218. }
  219. /**
  220. * Gets the array from a SQL result (as returned by Database::query)
  221. * help achieving database independence
  222. *
  223. * @param Statement $result
  224. *
  225. * @return mixed
  226. */
  227. public static function fetch_row(Statement $result)
  228. {
  229. return $result->fetch(PDO::FETCH_NUM);
  230. }
  231. /**
  232. * Frees all the memory associated with the provided result identifier.
  233. * @return bool Returns TRUE on success or FALSE on failure.
  234. * Notes: Use this method if you are concerned about how much memory is being used for queries that return large result sets.
  235. * Anyway, all associated result memory is automatically freed at the end of the script's execution.
  236. */
  237. public static function free_result(Statement $result)
  238. {
  239. $result->closeCursor();
  240. }
  241. /**
  242. * Gets the ID of the last item inserted into the database
  243. *
  244. * @return string
  245. */
  246. public static function insert_id()
  247. {
  248. return self::getManager()->getConnection()->lastInsertId();
  249. }
  250. /**
  251. * @param Statement $result
  252. *
  253. * @return int
  254. */
  255. public static function num_rows(Statement $result)
  256. {
  257. return $result->rowCount();
  258. }
  259. /**
  260. * Acts as the relative *_result() function of most DB drivers and fetches a
  261. * specific line and a field
  262. *
  263. * @param Statement $resource
  264. * @param int $row
  265. * @param string $field
  266. *
  267. * @return mixed
  268. */
  269. public static function result(Statement $resource, $row, $field = '')
  270. {
  271. if ($resource->rowCount() > 0) {
  272. $result = $resource->fetchAll(PDO::FETCH_BOTH);
  273. return $result[$row][$field];
  274. }
  275. }
  276. /**
  277. * @param string $query
  278. *
  279. * @return Statement
  280. *
  281. * @throws \Doctrine\DBAL\DBALException
  282. */
  283. public static function query($query)
  284. {
  285. $connection = self::getManager()->getConnection();
  286. if (api_get_setting('server_type') == 'test') {
  287. $result = $connection->executeQuery($query);
  288. } else {
  289. try {
  290. $result = $connection->executeQuery($query);
  291. } catch (Exception $e) {
  292. api_not_allowed(false, get_lang('GeneralError'));
  293. //error_log($e->getMessage());
  294. exit;
  295. }
  296. }
  297. return $result;
  298. }
  299. /**
  300. * @param string $option
  301. *
  302. * @return int
  303. */
  304. public static function customOptionToDoctrineOption($option)
  305. {
  306. switch ($option) {
  307. case 'ASSOC':
  308. return PDO::FETCH_ASSOC;
  309. break;
  310. case 'NUM':
  311. return PDO::FETCH_NUM;
  312. break;
  313. case 'BOTH':
  314. default:
  315. return PDO::FETCH_BOTH;
  316. break;
  317. }
  318. }
  319. /**
  320. * Stores a query result into an array.
  321. *
  322. * @author Olivier Brouckaert
  323. * @param Statement $result - the return value of the query
  324. * @param string $option BOTH, ASSOC, or NUM
  325. *
  326. * @return array - the value returned by the query
  327. */
  328. public static function store_result(Statement $result, $option = 'BOTH')
  329. {
  330. return $result->fetchAll(self::customOptionToDoctrineOption($option));
  331. }
  332. /**
  333. * Database insert
  334. * @param string $table_name
  335. * @param array $attributes
  336. * @param bool $show_query
  337. *
  338. * @return bool|int
  339. */
  340. public static function insert($table_name, $attributes, $show_query = false)
  341. {
  342. if (empty($attributes) || empty($table_name)) {
  343. return false;
  344. }
  345. $params = array_keys($attributes);
  346. if (!empty($params)) {
  347. $sql = 'INSERT INTO '.$table_name.' ('.implode(',', $params).')
  348. VALUES (:'.implode(', :' ,$params).')';
  349. $statement = self::getManager()->getConnection()->prepare($sql);
  350. $result = $statement->execute($attributes);
  351. if ($show_query) {
  352. var_dump($sql);
  353. error_log($sql);
  354. }
  355. if ($result) {
  356. return self::getManager()->getConnection()->lastInsertId();
  357. }
  358. }
  359. return false;
  360. }
  361. /**
  362. * @param string $table_name use Database::get_main_table
  363. * @param array $attributes Values to updates
  364. * Example: $params['name'] = 'Julio'; $params['lastname'] = 'Montoya';
  365. * @param array $where_conditions where conditions i.e array('id = ?' =>'4')
  366. * @param bool $show_query
  367. *
  368. * @return bool|int
  369. */
  370. public static function update(
  371. $table_name,
  372. $attributes,
  373. $where_conditions = array(),
  374. $show_query = false
  375. ) {
  376. if (!empty($table_name) && !empty($attributes)) {
  377. $update_sql = '';
  378. //Cleaning attributes
  379. $count = 1;
  380. foreach ($attributes as $key => $value) {
  381. $update_sql .= "$key = :$key ";
  382. if ($count < count($attributes)) {
  383. $update_sql.=', ';
  384. }
  385. $count++;
  386. }
  387. if (!empty($update_sql)) {
  388. //Parsing and cleaning the where conditions
  389. $where_return = self::parse_where_conditions($where_conditions);
  390. $sql = "UPDATE $table_name SET $update_sql $where_return ";
  391. $statement = self::getManager()->getConnection()->prepare($sql);
  392. $result = $statement->execute($attributes);
  393. if ($show_query) {
  394. var_dump($sql);
  395. }
  396. if ($result) {
  397. return $statement->rowCount();
  398. }
  399. }
  400. }
  401. return false;
  402. }
  403. /**
  404. * Experimental useful database finder
  405. * @todo lot of stuff to do here
  406. * @todo known issues, it doesn't work when using LIKE conditions
  407. * @example array('where'=> array('course_code LIKE "?%"'))
  408. * @example array('where'=> array('type = ? AND category = ?' => array('setting', 'Plugins'))
  409. * @example array('where'=> array('name = "Julio" AND lastname = "montoya"'))
  410. */
  411. public static function select($columns, $table_name, $conditions = array(), $type_result = 'all', $option = 'ASSOC')
  412. {
  413. $conditions = self::parse_conditions($conditions);
  414. //@todo we could do a describe here to check the columns ...
  415. if (is_array($columns)) {
  416. $clean_columns = implode(',', $columns);
  417. } else {
  418. if ($columns == '*') {
  419. $clean_columns = '*';
  420. } else {
  421. $clean_columns = (string)$columns;
  422. }
  423. }
  424. $sql = "SELECT $clean_columns FROM $table_name $conditions";
  425. $result = self::query($sql);
  426. $array = array();
  427. if ($type_result == 'all') {
  428. while ($row = self::fetch_array($result, $option)) {
  429. if (isset($row['id'])) {
  430. $array[$row['id']] = $row;
  431. } else {
  432. $array[] = $row;
  433. }
  434. }
  435. } else {
  436. $array = self::fetch_array($result, $option);
  437. }
  438. return $array;
  439. }
  440. /**
  441. * Parses WHERE/ORDER conditions i.e array('where'=>array('id = ?' =>'4'), 'order'=>'id DESC'))
  442. * @todo known issues, it doesn't work when using
  443. * LIKE conditions example: array('where'=>array('course_code LIKE "?%"'))
  444. * @param array $conditions
  445. */
  446. public static function parse_conditions($conditions)
  447. {
  448. if (empty($conditions)) {
  449. return '';
  450. }
  451. $return_value = $where_return = '';
  452. foreach ($conditions as $type_condition => $condition_data) {
  453. if ($condition_data == false) {
  454. continue;
  455. }
  456. $type_condition = strtolower($type_condition);
  457. switch ($type_condition) {
  458. case 'where':
  459. foreach ($condition_data as $condition => $value_array) {
  460. if (is_array($value_array)) {
  461. $clean_values = array();
  462. foreach($value_array as $item) {
  463. $item = Database::escape_string($item);
  464. $clean_values[]= $item;
  465. }
  466. } else {
  467. $value_array = Database::escape_string($value_array);
  468. $clean_values = $value_array;
  469. }
  470. if (!empty($condition) && $clean_values != '') {
  471. $condition = str_replace('%',"'@percentage@'", $condition); //replace "%"
  472. $condition = str_replace("'?'","%s", $condition);
  473. $condition = str_replace("?","%s", $condition);
  474. $condition = str_replace("@%s@","@-@", $condition);
  475. $condition = str_replace("%s","'%s'", $condition);
  476. $condition = str_replace("@-@","@%s@", $condition);
  477. // Treat conditions as string
  478. $condition = vsprintf($condition, $clean_values);
  479. $condition = str_replace('@percentage@','%', $condition); //replace "%"
  480. $where_return .= $condition;
  481. }
  482. }
  483. if (!empty($where_return)) {
  484. $return_value = " WHERE $where_return" ;
  485. }
  486. break;
  487. case 'order':
  488. $order_array = $condition_data;
  489. if (!empty($order_array)) {
  490. // 'order' => 'id desc, name desc'
  491. $order_array = self::escape_string($order_array, null, false);
  492. $new_order_array = explode(',', $order_array);
  493. $temp_value = array();
  494. foreach($new_order_array as $element) {
  495. $element = explode(' ', $element);
  496. $element = array_filter($element);
  497. $element = array_values($element);
  498. if (!empty($element[1])) {
  499. $element[1] = strtolower($element[1]);
  500. $order = 'DESC';
  501. if (in_array($element[1], array('desc', 'asc'))) {
  502. $order = $element[1];
  503. }
  504. $temp_value[]= $element[0].' '.$order.' ';
  505. } else {
  506. //by default DESC
  507. $temp_value[]= $element[0].' DESC ';
  508. }
  509. }
  510. if (!empty($temp_value)) {
  511. $return_value .= ' ORDER BY '.implode(', ', $temp_value);
  512. } else {
  513. //$return_value .= '';
  514. }
  515. }
  516. break;
  517. case 'limit':
  518. $limit_array = explode(',', $condition_data);
  519. if (!empty($limit_array)) {
  520. if (count($limit_array) > 1) {
  521. $return_value .= ' LIMIT '.intval($limit_array[0]).' , '.intval($limit_array[1]);
  522. } else {
  523. $return_value .= ' LIMIT '.intval($limit_array[0]);
  524. }
  525. }
  526. break;
  527. }
  528. }
  529. return $return_value;
  530. }
  531. /**
  532. * @param array $conditions
  533. *
  534. * @return string
  535. */
  536. public static function parse_where_conditions($conditions)
  537. {
  538. return self::parse_conditions(array('where' => $conditions));
  539. }
  540. /**
  541. * @param string $table_name
  542. * @param array $where_conditions
  543. * @param bool $show_query
  544. *
  545. * @return int
  546. */
  547. public static function delete($table_name, $where_conditions, $show_query = false)
  548. {
  549. $where_return = self::parse_where_conditions($where_conditions);
  550. $sql = "DELETE FROM $table_name $where_return ";
  551. if ($show_query) { echo $sql; echo '<br />'; }
  552. $result = self::query($sql);
  553. $affected_rows = self::affected_rows($result);
  554. //@todo should return affected_rows for
  555. return $affected_rows;
  556. }
  557. /**
  558. * Get Doctrine configuration
  559. * @param string $path
  560. *
  561. * @return \Doctrine\ORM\Configuration
  562. */
  563. public static function getDoctrineConfig($path)
  564. {
  565. $isDevMode = true;
  566. $isSimpleMode = false;
  567. $proxyDir = null;
  568. $cache = null;
  569. $path = !empty($path) ? $path : api_get_path(SYS_PATH);
  570. $paths = array(
  571. $path.'src/Chamilo/CoreBundle/Entity',
  572. $path.'src/Chamilo/UserBundle/Entity',
  573. $path.'src/Chamilo/CourseBundle/Entity'
  574. );
  575. /*$doctrineCache = api_get_path(SYS_ARCHIVE_PATH).'doctrine/';
  576. if (!is_dir($doctrineCache)) {
  577. mkdir($doctrineCache, api_get_permissions_for_new_directories(), true);
  578. }*/
  579. return \Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration(
  580. $paths,
  581. $isDevMode,
  582. $proxyDir,
  583. $cache,
  584. $isSimpleMode
  585. );
  586. }
  587. }