Completed
Push — master ( 2fdc96...4f1f24 )
by Damian
12:09
created

MySQLDatabase::now()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
/**
4
 * MySQL connector class.
5
 *
6
 * Supported indexes for {@link requireTable()}:
7
 *
8
 * @package framework
9
 * @subpackage model
10
 */
11
class MySQLDatabase extends SS_Database {
12
13
	/**
14
	 * Default connection charset (may be overridden in $databaseConfig)
15
	 *
16
	 * @config
17
	 * @var String
18
	 */
19
	private static $connection_charset = null;
20
21
	public function connect($parameters) {
22
		// Ensure that driver is available (required by PDO)
23
		if(empty($parameters['driver'])) {
24
			$parameters['driver'] = $this->getDatabaseServer();
25
		}
26
27
		// Set charset
28
		if( empty($parameters['charset'])
29
			&& ($charset = Config::inst()->get('MySQLDatabase', 'connection_charset'))
30
		) {
31
			$parameters['charset'] = $charset;
32
		}
33
34
		// Set collation
35
		if( empty($parameters['collation'])
36
			&& ($collation = Config::inst()->get('MySQLDatabase', 'connection_collation'))
37
		) {
38
			$parameters['collation'] = $collation;
39
		}
40
41
		// Notify connector of parameters
42
		$this->connector->connect($parameters);
43
44
		// This is important!
45
		$this->setSQLMode('ANSI');
46
47
		if (isset($parameters['timezone'])) {
48
			$this->selectTimezone($parameters['timezone']);
49
		}
50
51
		// SS_Database subclass maintains responsibility for selecting database
52
		// once connected in order to correctly handle schema queries about
53
		// existence of database, error handling at the correct level, etc
54
		if (!empty($parameters['database'])) {
55
			$this->selectDatabase($parameters['database'], false, false);
56
		}
57
	}
58
59
	/**
60
	 * Sets the SQL mode
61
	 *
62
	 * @param string $mode Connection mode
63
	 */
64
	public function setSQLMode($mode) {
65
		if (empty($mode)) return;
66
		$this->preparedQuery("SET sql_mode = ?", array($mode));
67
	}
68
69
	/**
70
	 * Sets the system timezone for the database connection
71
	 *
72
	 * @param string $timezone
73
	 */
74
	public function selectTimezone($timezone) {
75
		if (empty($timezone)) return;
76
		$this->preparedQuery("SET SESSION time_zone = ?", array($timezone));
77
	}
78
79
	public function supportsCollations() {
80
		return true;
81
	}
82
83
	public function supportsTimezoneOverride() {
84
		return true;
85
	}
86
87
	public function getDatabaseServer() {
88
		return "mysql";
89
	}
90
91
	/**
92
	 * The core search engine, used by this class and its subclasses to do fun stuff.
93
	 * Searches both SiteTree and File.
94
	 *
95
	 * @param string $keywords Keywords as a string.
96
	 */
97
	public function searchEngine($classesToSearch, $keywords, $start, $pageLength, $sortBy = "Relevance DESC",
98
		$extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false
99
	) {
100
		if (!class_exists('SiteTree'))
101
				throw new Exception('MySQLDatabase->searchEngine() requires "SiteTree" class');
102
		if (!class_exists('File'))
103
				throw new Exception('MySQLDatabase->searchEngine() requires "File" class');
104
105
		$keywords = $this->escapeString($keywords);
106
		$htmlEntityKeywords = htmlentities($keywords, ENT_NOQUOTES, 'UTF-8');
107
108
		$extraFilters = array('SiteTree' => '', 'File' => '');
109
110
		if ($booleanSearch) $boolean = "IN BOOLEAN MODE";
111
112
		if ($extraFilter) {
113
			$extraFilters['SiteTree'] = " AND $extraFilter";
114
115
			if ($alternativeFileFilter)
116
					$extraFilters['File'] = " AND $alternativeFileFilter";
117
			else $extraFilters['File'] = $extraFilters['SiteTree'];
118
		}
119
120
		// Always ensure that only pages with ShowInSearch = 1 can be searched
121
		$extraFilters['SiteTree'] .= " AND ShowInSearch <> 0";
122
123
		// File.ShowInSearch was added later, keep the database driver backwards compatible
124
		// by checking for its existence first
125
		$fields = $this->fieldList('File');
0 ignored issues
show
Deprecated Code introduced by
The method SS_Database::fieldList() has been deprecated with message: since version 4.0 Use DB::field_list instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
126
		if (array_key_exists('ShowInSearch', $fields))
127
				$extraFilters['File'] .= " AND ShowInSearch <> 0";
128
129
		$limit = $start . ", " . (int) $pageLength;
130
131
		$notMatch = $invertedMatch
132
				? "NOT "
133
				: "";
134
		if ($keywords) {
135
			$match['SiteTree'] = "
0 ignored issues
show
Coding Style Comprehensibility introduced by
$match was never initialized. Although not strictly required by PHP, it is generally a good practice to add $match = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
136
				MATCH (Title, MenuTitle, Content, MetaDescription) AGAINST ('$keywords' $boolean)
0 ignored issues
show
Bug introduced by
The variable $boolean does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
137
				+ MATCH (Title, MenuTitle, Content, MetaDescription) AGAINST ('$htmlEntityKeywords' $boolean)
138
			";
139
			$match['File'] = "MATCH (Name, Title) AGAINST ('$keywords' $boolean) AND ClassName = 'File'";
140
141
			// We make the relevance search by converting a boolean mode search into a normal one
142
			$relevanceKeywords = str_replace(array('*', '+', '-'), '', $keywords);
143
			$htmlEntityRelevanceKeywords = str_replace(array('*', '+', '-'), '', $htmlEntityKeywords);
144
			$relevance['SiteTree'] = "MATCH (Title, MenuTitle, Content, MetaDescription) "
0 ignored issues
show
Coding Style Comprehensibility introduced by
$relevance was never initialized. Although not strictly required by PHP, it is generally a good practice to add $relevance = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
145
					. "AGAINST ('$relevanceKeywords') "
146
					. "+ MATCH (Title, MenuTitle, Content, MetaDescription) AGAINST ('$htmlEntityRelevanceKeywords')";
147
			$relevance['File'] = "MATCH (Name, Title) AGAINST ('$relevanceKeywords')";
148
		} else {
149
			$relevance['SiteTree'] = $relevance['File'] = 1;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$relevance was never initialized. Although not strictly required by PHP, it is generally a good practice to add $relevance = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
150
			$match['SiteTree'] = $match['File'] = "1 = 1";
0 ignored issues
show
Coding Style Comprehensibility introduced by
$match was never initialized. Although not strictly required by PHP, it is generally a good practice to add $match = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
151
		}
152
153
		// Generate initial DataLists and base table names
154
		$lists = array();
155
		$baseClasses = array('SiteTree' => '', 'File' => '');
156
		foreach ($classesToSearch as $class) {
157
			$lists[$class] = DataList::create($class)->where($notMatch . $match[$class] . $extraFilters[$class], "");
0 ignored issues
show
Unused Code introduced by
The call to DataList::where() has too many arguments starting with ''.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
158
			$baseClasses[$class] = '"' . $class . '"';
159
		}
160
161
		$charset = Config::inst()->get('MySQLDatabase', 'charset');
162
163
		// Make column selection lists
164
		$select = array(
165
			'SiteTree' => array(
166
				"ClassName", "$baseClasses[SiteTree].\"ID\"", "ParentID",
167
				"Title", "MenuTitle", "URLSegment", "Content",
168
				"LastEdited", "Created",
169
				"Name" => "_{$charset}''",
170
				"Relevance" => $relevance['SiteTree'], "CanViewType"
171
			),
172
			'File' => array(
173
				"ClassName", "$baseClasses[File].\"ID\"", "ParentID" => "_{$charset}''",
174
				"Title", "MenuTitle" => "_{$charset}''", "URLSegment" => "_{$charset}''", "Content",
175
				"LastEdited", "Created",
176
				"Name",
177
				"Relevance" => $relevance['File'], "CanViewType" => "NULL"
178
			),
179
		);
180
181
		// Process and combine queries
182
		$querySQLs = array();
183
		$queryParameters = array();
184
		$totalCount = 0;
185
		foreach ($lists as $class => $list) {
186
			$query = $list->dataQuery()->query();
187
188
			// There's no need to do all that joining
189
			$query->setFrom(array(str_replace(array('"', '`'), '', $baseClasses[$class]) => $baseClasses[$class]));
190
			$query->setSelect($select[$class]);
191
			$query->setOrderBy(array());
192
193
			$querySQLs[] = $query->sql($parameters);
0 ignored issues
show
Bug introduced by
The variable $parameters does not exist. Did you mean $queryParameters?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
194
			$queryParameters = array_merge($queryParameters, $parameters);
0 ignored issues
show
Bug introduced by
The variable $parameters does not exist. Did you mean $queryParameters?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
195
196
			$totalCount += $query->unlimitedRowCount();
197
		}
198
		$fullQuery = implode(" UNION ", $querySQLs) . " ORDER BY $sortBy LIMIT $limit";
199
200
		// Get records
201
		$records = $this->preparedQuery($fullQuery, $queryParameters);
202
203
		$objects = array();
204
205
		foreach ($records as $record) {
206
			$objects[] = new $record['ClassName']($record);
207
		}
208
209
		$list = new PaginatedList(new ArrayList($objects));
210
		$list->setPageStart($start);
211
		$list->setPageLength($pageLength);
212
		$list->setTotalItems($totalCount);
213
214
		// The list has already been limited by the query above
215
		$list->setLimitItems(false);
216
217
		return $list;
218
	}
219
220
	public function supportsTransactions() {
221
		return true;
222
	}
223
224
	public function transactionStart($transactionMode = false, $sessionCharacteristics = false) {
225
		// This sets the isolation level for the NEXT transaction, not the current one.
226
		if ($transactionMode) {
227
			$this->query('SET TRANSACTION ' . $transactionMode);
228
		}
229
230
		$this->query('START TRANSACTION');
231
232
		if ($sessionCharacteristics) {
233
			$this->query('SET SESSION TRANSACTION ' . $sessionCharacteristics);
234
		}
235
	}
236
237
	public function transactionSavepoint($savepoint) {
238
		$this->query("SAVEPOINT $savepoint");
239
	}
240
241
	public function transactionRollback($savepoint = false) {
242
		if ($savepoint) {
243
			$this->query('ROLLBACK TO ' . $savepoint);
244
		} else {
245
			$this->query('ROLLBACK');
246
		}
247
	}
248
249
	public function transactionEnd($chain = false) {
250
		$this->query('COMMIT AND ' . ($chain ? '' : 'NO ') . 'CHAIN');
251
	}
252
253
	public function comparisonClause($field, $value, $exact = false, $negate = false, $caseSensitive = null,
254
		$parameterised = false
255
	) {
256
		if ($exact && $caseSensitive === null) {
257
			$comp = ($negate) ? '!=' : '=';
258
		} else {
259
			$comp = ($caseSensitive) ? 'LIKE BINARY' : 'LIKE';
260
			if ($negate) $comp = 'NOT ' . $comp;
261
		}
262
263
		if($parameterised) {
264
			return sprintf("%s %s ?", $field, $comp);
265
		} else {
266
			return sprintf("%s %s '%s'", $field, $comp, $value);
267
		}
268
	}
269
270
	public function formattedDatetimeClause($date, $format) {
271
		preg_match_all('/%(.)/', $format, $matches);
272
		foreach ($matches[1] as $match)
273
			if (array_search($match, array('Y', 'm', 'd', 'H', 'i', 's', 'U')) === false) {
274
				user_error('formattedDatetimeClause(): unsupported format character %' . $match, E_USER_WARNING);
275
			}
276
277
		if (preg_match('/^now$/i', $date)) {
278
			$date = "NOW()";
279
		} else if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i', $date)) {
280
			$date = "'$date'";
281
		}
282
283
		if ($format == '%U') return "UNIX_TIMESTAMP($date)";
284
285
		return "DATE_FORMAT($date, '$format')";
286
	}
287
288
	public function datetimeIntervalClause($date, $interval) {
289
		$interval = preg_replace('/(year|month|day|hour|minute|second)s/i', '$1', $interval);
290
291
		if (preg_match('/^now$/i', $date)) {
292
			$date = "NOW()";
293
		} else if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i', $date)) {
294
			$date = "'$date'";
295
		}
296
297
		return "$date + INTERVAL $interval";
298
	}
299
300
	public function datetimeDifferenceClause($date1, $date2) {
301
		// First date format
302
		if (preg_match('/^now$/i', $date1)) {
303
			$date1 = "NOW()";
304
		} else if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i', $date1)) {
305
			$date1 = "'$date1'";
306
		}
307
		// Second date format
308
		if (preg_match('/^now$/i', $date2)) {
309
			$date2 = "NOW()";
310
		} else if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i', $date2)) {
311
			$date2 = "'$date2'";
312
		}
313
314
		return "UNIX_TIMESTAMP($date1) - UNIX_TIMESTAMP($date2)";
315
	}
316
317
	public function supportsLocks() {
318
		return true;
319
	}
320
321
	public function canLock($name) {
322
		$id = $this->getLockIdentifier($name);
323
		return (bool) $this->query(sprintf("SELECT IS_FREE_LOCK('%s')", $id))->value();
324
	}
325
326
	public function getLock($name, $timeout = 5) {
327
		$id = $this->getLockIdentifier($name);
328
329
		// MySQL auto-releases existing locks on subsequent GET_LOCK() calls,
330
		// in contrast to PostgreSQL and SQL Server who stack the locks.
331
		return (bool) $this->query(sprintf("SELECT GET_LOCK('%s', %d)", $id, $timeout))->value();
332
	}
333
334
	public function releaseLock($name) {
335
		$id = $this->getLockIdentifier($name);
336
		return (bool) $this->query(sprintf("SELECT RELEASE_LOCK('%s')", $id))->value();
337
	}
338
339
	protected function getLockIdentifier($name) {
340
		// Prefix with database name
341
		$dbName = $this->connector->getSelectedDatabase() ;
342
		return $this->escapeString("{$dbName}_{$name}");
343
	}
344
345
	public function now() {
346
		// MySQL uses NOW() to return the current date/time.
347
		return 'NOW()';
348
	}
349
350
	public function random() {
351
		return 'RAND()';
352
	}
353
}
354