1: <?php
2: /**
3: * You may not change or alter any portion of this comment or credits
4: * of supporting developers from this source code or any supporting source code
5: * which is considered copyrighted (c) material of the original comment or credit authors.
6: *
7: * This program is distributed in the hope that it will be useful,
8: * but WITHOUT ANY WARRANTY; without even the implied warranty of
9: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10: */
11:
12: namespace Xoops\Core\Database;
13:
14: use Doctrine\DBAL\Configuration;
15: use Doctrine\DBAL\Driver;
16: use Doctrine\DBAL\Cache\QueryCacheProfile;
17: use Doctrine\Common\EventManager;
18:
19: /**
20: * Connection wrapper for Doctrine DBAL Connection
21: *
22: * @category Xoops\Core\Database\Connection
23: * @package Connection
24: * @author readheadedrod <redheadedrod@hotmail.com>
25: * @author Richard Griffith <richard@geekwright.com>
26: * @copyright 2013-2015 XOOPS Project (http://xoops.org)
27: * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
28: * @version Release: 2.6
29: * @link http://xoops.org
30: * @since 2.6.0
31: */
32: class Connection extends \Doctrine\DBAL\Connection
33: {
34: /**
35: * @var bool $safe true means it is safe to write to database
36: * removed allowedWebChanges as unnecessary. Using this instead.
37: */
38: protected static $safe;
39:
40:
41: /**
42: * @var bool $force true means force writing to database even if safe is not true.
43: */
44: protected static $force;
45:
46: /**
47: * @var bool $transactionActive true means a transaction is in process.
48: */
49: protected static $transactionActive;
50:
51:
52: /**
53: * this is a public setter for the safe variable
54: *
55: * @param bool $safe set safe to true if safe to write data to database
56: *
57: * @return void
58: */
59: public static function setSafe($safe = true)
60: {
61: if (is_bool($safe)) {
62: self::$safe = $safe;
63: }
64: }
65:
66: /**
67: * this is a public getter for the safe variable
68: *
69: * @return boolean
70: */
71: public static function getSafe()
72: {
73: return self::$safe;
74: }
75:
76: /**
77: * this is a public setter for the $force variable
78: *
79: * @param bool $force when true will force a write to database when safe is false.
80: *
81: * @return void
82: */
83: public static function setForce($force = false)
84: {
85: if (is_bool($force)) {
86: self::$force = $force;
87: }
88: }
89:
90: /**
91: * this is a public getter for the $force variable
92: *
93: * @return boolean
94: */
95: public static function getForce()
96: {
97: return self::$force;
98: }
99:
100: /**
101: * Initializes a new instance of the Connection class.
102: *
103: * This sets up necessary variables before calling parent constructor
104: *
105: * @param array $params Parameters for the driver
106: * @param Driver $driver The driver to use
107: * @param Configuration $config The connection configuration
108: * @param EventManager $eventManager Event manager to use
109: */
110: public function __construct(
111: array $params,
112: Driver $driver,
113: Configuration $config = null,
114: EventManager $eventManager = null
115: ) {
116: if (!defined('XOOPS_DB_PROXY') || ($_SERVER['REQUEST_METHOD'] !== 'GET') || (php_sapi_name() === 'cli')) {
117: self::setSafe(true);
118: } else {
119: self::setSafe(false);
120: }
121: self::setForce(false);
122: self::$transactionActive = false;
123: try {
124: parent::__construct($params, $driver, $config, $eventManager);
125: } catch (\Exception $e) {
126: // We are dead in the water. This exception may contain very sensitive
127: // information and cannot be allowed to be displayed as is.
128: //\Xoops::getInstance()->events()->triggerEvent('core.exception', $e);
129: trigger_error("Cannot get database connection", E_USER_ERROR);
130: }
131:
132: }
133:
134: /**
135: * Prepend the prefix.'_' to the given tablename
136: * If tablename is empty, just return the prefix.
137: *
138: * @param string $tablename tablename
139: *
140: * @return string prefixed tablename, or prefix if tablename is empty
141: */
142: public static function prefix($tablename = '')
143: {
144: $prefix = \XoopsBaseConfig::get('db-prefix');
145: if ($tablename != '') {
146: return $prefix . '_' . $tablename;
147: } else {
148: return $prefix;
149: }
150: }
151:
152: /**
153: * Inserts a table row with specified data.
154: *
155: * Adds prefix to the name of the table then passes to normal function.
156: *
157: * @param string $tableName The name of the table to insert data into.
158: * @param array $data An associative array containing column-value pairs.
159: * @param array $types Types of the inserted data.
160: *
161: * @return integer The number of affected rows.
162: */
163: public function insertPrefix($tableName, array $data, array $types = array())
164: {
165: $tableName = $this->prefix($tableName);
166: return $this->insert($tableName, $data, $types);
167: }
168:
169:
170: /**
171: * Executes an SQL UPDATE statement on a table.
172: *
173: * Adds prefix to the name of the table then passes to normal function.
174: *
175: * @param string $tableName The name of the table to update.
176: * @param array $data The data to update
177: * @param array $identifier The update criteria.
178: * An associative array containing column-value pairs.
179: * @param array $types Types of the merged $data and
180: * $identifier arrays in that order.
181: *
182: * @return integer The number of affected rows.
183: */
184: public function updatePrefix($tableName, array $data, array $identifier, array $types = array())
185: {
186: $tableName = $this->prefix($tableName);
187: return $this->update($tableName, $data, $identifier, $types);
188: }
189:
190: /**
191: * Executes an SQL DELETE statement on a table.
192: *
193: * Adds prefix to the name of the table then passes to delete function.
194: *
195: * @param string $tableName The name of the table on which to delete.
196: * @param array $identifier The deletion criteria.
197: * An associative array containing column-value pairs.
198: *
199: * @return integer The number of affected rows.
200: *
201: */
202: public function deletePrefix($tableName, array $identifier)
203: {
204: $tableName = $this->prefix($tableName);
205: return $this->delete($tableName, $identifier);
206: }
207:
208: /**
209: * Executes an, optionally parametrized, SQL query.
210: *
211: * If the query is parametrized, a prepared statement is used.
212: * If an SQLLogger is configured, the execution is logged.
213: *
214: * @param string $query The SQL query to execute.
215: * @param array $params The parameters to bind to the query, if any.
216: * @param array $types The types the previous parameters are in.
217: * @param QueryCacheProfile $qcp The query Cache profile
218: *
219: * @return \Doctrine\DBAL\Driver\Statement The executed statement.
220: *
221: * @internal PERF: Directly prepares a driver statement, not a wrapper.
222: */
223: public function executeQuery(
224: $query,
225: array $params = array(),
226: $types = array(),
227: QueryCacheProfile $qcp = null
228: ) {
229: return parent::executeQuery($query, $params, $types, $qcp);
230: }
231:
232: /**
233: * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
234: * and returns the number of affected rows.
235: *
236: * This method supports PDO binding types as well as DBAL mapping types.
237: *
238: * This over ridding process checks to make sure it is safe to do these.
239: * If force is active then it will over ride the safe setting.
240: *
241: * @param string $query The SQL query.
242: * @param array $params The query parameters.
243: * @param array $types The parameter types.
244: *
245: * @return integer The number of affected rows.
246: *
247: * @internal PERF: Directly prepares a driver statement, not a wrapper.
248: *
249: * @todo build a better exception catching system.
250: */
251: public function executeUpdate($query, array $params = array(), array $types = array())
252: {
253: $events = \Xoops::getInstance()->events();
254: if (self::getSafe() || self::getForce()) {
255: if (!self::$transactionActive) {
256: self::setForce(false);
257: };
258: $events->triggerEvent('core.database.query.start');
259: try {
260: $result = parent::executeUpdate($query, $params, $types);
261: } catch (\Exception $e) {
262: $events->triggerEvent('core.exception', $e);
263: $result = 0;
264: }
265: $events->triggerEvent('core.database.query.end');
266: } else {
267: //$events->triggerEvent('core.database.query.failure', (array('Not safe:')));
268: return (int) 0;
269: }
270: if ($result != 0) {
271: //$events->triggerEvent('core.database.query.success', (array($query)));
272: return (int) $result;
273: } else {
274: //$events->triggerEvent('core.database.query.failure', (array($query)));
275: return (int) 0;
276: }
277: }
278:
279: /**
280: * Starts a transaction by suspending auto-commit mode.
281: *
282: * @return void
283: */
284: public function beginTransaction()
285: {
286: self::$transactionActive = true;
287: parent::beginTransaction();
288: }
289:
290: /**
291: * Commits the current transaction.
292: *
293: * @return void
294: */
295: public function commit()
296: {
297: self::$transactionActive = false;
298: self::setForce(false);
299: parent::commit();
300: }
301:
302: /**
303: * rolls back the current transaction.
304: *
305: * @return void
306: */
307: public function rollBack()
308: {
309: self::$transactionActive = false;
310: self::setForce(false);
311: parent::rollBack();
312: }
313:
314: /**
315: * perform a safe query if allowed
316: * can receive variable number of arguments
317: *
318: * @return mixed returns the value received or null if nothing received.
319: *
320: * @todo add error report for using non select while not safe.
321: * @todo need to check if doctrine allows more than one query to be sent.
322: * This code assumes only one query is sent and anything else sent is not
323: * a query. This will have to be readdressed if this is wrong.
324: *
325: */
326: public function query()
327: {
328: $events = \Xoops::getInstance()->events();
329: if (!self::getSafe() && !self::getForce()) {
330: $sql = ltrim(func_get_arg(0));
331: if (!self::getSafe() && strtolower(substr($sql, 0, 6))!== 'select') {
332: // $events->triggerEvent('core.database.query.failure', (array('Not safe:')));
333: return null;
334: }
335: }
336: self::setForce(false); // resets $force back to false
337: $events->triggerEvent('core.database.query.start');
338: try {
339: $result = call_user_func_array(array('parent', 'query'), func_get_args());
340: } catch (\Exception $e) {
341: $events->triggerEvent('core.exception', $e);
342: $result=null;
343: }
344: $events->triggerEvent('core.database.query.end');
345: if ($result) {
346: //$events->triggerEvent('core.database.query.success', (array('')));
347: return $result;
348: } else {
349: //$events->triggerEvent('core.database.query.failure', (array('')));
350: return null;
351: }
352: }
353:
354: /**
355: * perform queries from SQL dump file in a batch
356: *
357: * @param string $file file path to an SQL dump file
358: *
359: * @return bool FALSE if failed reading SQL file or
360: * TRUE if the file has been read and queries executed
361: */
362: public function queryFromFile($file)
363: {
364: if (false !== ($fp = fopen($file, 'r'))) {
365: $sql_queries = trim(fread($fp, filesize($file)));
366: \SqlUtility::splitMySqlFile($pieces, $sql_queries);
367: foreach ($pieces as $query) {
368: $prefixed_query = \SqlUtility::prefixQuery(trim($query), $this->prefix());
369: if ($prefixed_query != false) {
370: $this->query($prefixed_query[0]);
371: }
372: }
373: return true;
374: }
375: return false;
376: }
377:
378: /**
379: * Duplicates original xoops quote.
380: * Name changed to not confuse normal usage of quote.
381: *
382: * @param string $input test to convert
383: *
384: * @return mixed converted text
385: */
386: public function quoteSlash($input)
387: {
388: return $this->quote($input);
389: }
390:
391:
392: /**
393: * Create a new instance of a SQL query builder.
394: *
395: * @return QueryBuilder
396: */
397: public function createXoopsQueryBuilder()
398: {
399: return new QueryBuilder($this);
400: }
401: }
402: