From ef6e9ef26be31a47d116c130ef0b3cf5eb4628db Mon Sep 17 00:00:00 2001
From: Hypolite Petovan <hypolite@mrpetovan.com>
Date: Sun, 5 Jan 2020 17:22:07 -0500
Subject: [PATCH] Add DBA::collapseCondition method

- Update Database->update for use with DBA::collapseCondition
---
 src/Database/DBA.php      | 125 +++++++++++++++++++++++---------------
 src/Database/Database.php |  13 ++--
 2 files changed, 83 insertions(+), 55 deletions(-)

diff --git a/src/Database/DBA.php b/src/Database/DBA.php
index 1cf10ba4ad..9c607638b9 100644
--- a/src/Database/DBA.php
+++ b/src/Database/DBA.php
@@ -529,65 +529,94 @@ class DBA
 	 */
 	public static function buildCondition(array &$condition = [])
 	{
+		$condition = self::collapseCondition($condition);
+		
 		$condition_string = '';
 		if (count($condition) > 0) {
-			reset($condition);
-			$first_key = key($condition);
-			if (is_int($first_key)) {
-				$condition_string = " WHERE (" . array_shift($condition) . ")";
-			} else {
-				$new_values = [];
-				$condition_string = "";
-				foreach ($condition as $field => $value) {
-					if ($condition_string != "") {
-						$condition_string .= " AND ";
-					}
-					if (is_array($value)) {
-						if (count($value)) {
-							/* Workaround for MySQL Bug #64791.
-							 * Never mix data types inside any IN() condition.
-							 * In case of mixed types, cast all as string.
-							 * Logic needs to be consistent with DBA::p() data types.
-							 */
-							$is_int = false;
-							$is_alpha = false;
-							foreach ($value as $single_value) {
-								if (is_int($single_value)) {
-									$is_int = true;
-								} else {
-									$is_alpha = true;
-								}
-							}
+			$condition_string = " WHERE (" . array_shift($condition) . ")";
+		}
 
-							if ($is_int && $is_alpha) {
-								foreach ($value as &$ref) {
-									if (is_int($ref)) {
-										$ref = (string)$ref;
-									}
-								}
-								unset($ref); //Prevent accidental re-use.
-							}
+		return $condition_string;
+	}
+
+	/**
+	 * Collapse an associative array condition into a SQL string + parameters condition array.
+	 *
+	 * ['uid' => 1, 'network' => ['dspr', 'apub']]
+	 *
+	 * gets transformed into
+	 *
+	 * ["`uid` = ? AND `network` IN (?, ?)", 1, 'dspr', 'apub']
+	 *
+	 * @param array $condition
+	 * @return array
+	 */
+	public static function collapseCondition(array $condition)
+	{
+		// Ensures an always true condition is returned
+		if (count($condition) < 1) {
+			return ['1'];
+		}
+
+		reset($condition);
+		$first_key = key($condition);
+
+		if (is_int($first_key)) {
+			// Already collapsed
+			return $condition;
+		}
+
+		$values = [];
+		$condition_string = "";
+		foreach ($condition as $field => $value) {
+			if ($condition_string != "") {
+				$condition_string .= " AND ";
+			}
 
-							$new_values = array_merge($new_values, array_values($value));
-							$placeholders = substr(str_repeat("?, ", count($value)), 0, -2);
-							$condition_string .= self::quoteIdentifier($field) . " IN (" . $placeholders . ")";
+			if (is_array($value)) {
+				if (count($value)) {
+					/* Workaround for MySQL Bug #64791.
+					 * Never mix data types inside any IN() condition.
+					 * In case of mixed types, cast all as string.
+					 * Logic needs to be consistent with DBA::p() data types.
+					 */
+					$is_int = false;
+					$is_alpha = false;
+					foreach ($value as $single_value) {
+						if (is_int($single_value)) {
+							$is_int = true;
 						} else {
-							// Empty value array isn't supported by IN and is logically equivalent to no match
-							$condition_string .= "FALSE";
+							$is_alpha = true;
+						}
+					}
+
+					if ($is_int && $is_alpha) {
+						foreach ($value as &$ref) {
+							if (is_int($ref)) {
+								$ref = (string)$ref;
+							}
 						}
-					} elseif (is_null($value)) {
-						$condition_string .= self::quoteIdentifier($field) . " IS NULL";
-					} else {
-						$new_values[$field] = $value;
-						$condition_string .= self::quoteIdentifier($field) . " = ?";
+						unset($ref); //Prevent accidental re-use.
 					}
+
+					$values = array_merge($values, array_values($value));
+					$placeholders = substr(str_repeat("?, ", count($value)), 0, -2);
+					$condition_string .= self::quoteIdentifier($field) . " IN (" . $placeholders . ")";
+				} else {
+					// Empty value array isn't supported by IN and is logically equivalent to no match
+					$condition_string .= "FALSE";
 				}
-				$condition_string = " WHERE (" . $condition_string . ")";
-				$condition = $new_values;
+			} elseif (is_null($value)) {
+				$condition_string .= self::quoteIdentifier($field) . " IS NULL";
+			} else {
+				$values[$field] = $value;
+				$condition_string .= self::quoteIdentifier($field) . " = ?";
 			}
 		}
 
-		return $condition_string;
+		$condition = array_merge([$condition_string], array_values($values));
+
+		return $condition;
 	}
 
 	/**
diff --git a/src/Database/Database.php b/src/Database/Database.php
index 1dd3524ed6..bd295fc9e4 100644
--- a/src/Database/Database.php
+++ b/src/Database/Database.php
@@ -1327,10 +1327,6 @@ class Database
 			return false;
 		}
 
-		$table_string = DBA::buildTableString($table);
-
-		$condition_string = DBA::buildCondition($condition);
-
 		if (is_bool($old_fields)) {
 			$do_insert = $old_fields;
 
@@ -1361,13 +1357,16 @@ class Database
 			return true;
 		}
 
+		$table_string = DBA::buildTableString($table);
+
+		$condition_string = DBA::buildCondition($condition);
+
 		$sql = "UPDATE " . $table_string . " SET "
 			. implode(" = ?, ", array_map([DBA::class, 'quoteIdentifier'], array_keys($fields))) . " = ?"
 			. $condition_string;
 
-		$params1 = array_values($fields);
-		$params2 = array_values($condition);
-		$params  = array_merge_recursive($params1, $params2);
+		// Combines the updated fields parameter values with the condition parameter values
+		$params  = array_merge(array_values($fields), $condition);
 
 		return $this->e($sql, $params);
 	}
-- 
2.39.5