Example #1
0
 public static function getShardInfo($shardID)
 {
     if (!$shardID) {
         throw new Exception('$shardID not provided');
     }
     if (isset(self::$shardInfo[$shardID])) {
         return self::$shardInfo[$shardID];
     }
     $cacheKey = 'shardInfo_' . $shardID;
     $shardInfo = Z_Core::$MC->get($cacheKey);
     if ($shardInfo) {
         self::$shardInfo[$shardID] = $shardInfo;
         return $shardInfo;
     }
     $sql = "SELECT address, port, db,\n\t\t\t\tCASE\n\t\t\t\t\tWHEN shardHosts.state='up' THEN shards.state\n\t\t\t\t\tWHEN shardHosts.state='readonly' THEN\n\t\t\t\t\t\tIF(shards.state='down', 'down', 'readonly')\n\t\t\t\t\tWHEN shardHosts.state='down' THEN 'down'\n\t\t\t\tEND AS state, shardHostID\n\t\t\t\tFROM shards JOIN shardHosts USING (shardHostID) WHERE shardID=?";
     $shardInfo = Zotero_DB::rowQuery($sql, $shardID);
     if (!$shardInfo) {
         throw new Exception("Shard {$shardID} not found");
     }
     self::$shardInfo[$shardID] = $shardInfo;
     Z_Core::$MC->set($cacheKey, $shardInfo, 60);
     return $shardInfo;
 }
Example #2
0
 public static function getUserValues($userID)
 {
     $sql = "SELECT quota, UNIX_TIMESTAMP(expiration) AS expiration FROM storageAccounts WHERE userID=?";
     return Zotero_DB::rowQuery($sql, $userID);
 }
Example #3
0
 /**
  * Get the result of a queued process for a given sync session
  *
  * If no result, return false
  * If success, return array('timestamp' => "123456789")
  * If error, return array('xmldata' => "<data ...", 'exception' => Exception)
  */
 public static function getSessionUploadResult($sessionID)
 {
     Zotero_DB::beginTransaction();
     $sql = "SELECT UNIX_TIMESTAMP(finished) AS finished, xmldata, errorCode, errorMessage\n\t\t\t\tFROM syncUploadQueue WHERE sessionID=?";
     $row = Zotero_DB::rowQuery($sql, $sessionID);
     if (!$row) {
         Zotero_DB::commit();
         throw new Exception("Queued upload not found for session");
     }
     if (is_null($row['finished'])) {
         Zotero_DB::beginTransaction();
         return false;
     }
     $sql = "DELETE FROM syncUploadQueue WHERE sessionID=?";
     Zotero_DB::query($sql, $sessionID);
     Zotero_DB::commit();
     // Success
     if (is_null($row['errorCode'])) {
         return array('timestamp' => $row['finished']);
     }
     $e = @unserialize($row['errorMessage']);
     return array('timestamp' => $row['finished'], 'xmldata' => $row['xmldata'], 'exception' => $e);
 }
Example #4
0
 private function load()
 {
     //Z_Core::debug("Loading data for search $this->id");
     if (!$this->libraryID) {
         throw new Exception("Library ID not set");
     }
     if (!$this->id && !$this->key) {
         throw new Exception("ID or key not set");
     }
     $shardID = Zotero_Shards::getByLibraryID($this->libraryID);
     $sql = "SELECT searchID AS id, searchName AS name, dateAdded, dateModified, libraryID, `key`,\n\t\t\t\tMAX(searchConditionID) AS maxSearchConditionID FROM savedSearches\n\t\t\t\tLEFT JOIN savedSearchConditions USING (searchID) WHERE ";
     if ($this->id) {
         $sql .= "searchID=?";
         $params = $this->id;
     } else {
         $sql .= "libraryID=? AND `key`=?";
         $params = array($this->libraryID, $this->key);
     }
     $sql .= " GROUP BY searchID";
     $data = Zotero_DB::rowQuery($sql, $params, $shardID);
     $this->loaded = true;
     if (!$data) {
         return;
     }
     foreach ($data as $key => $val) {
         $this->{$key} = $val;
     }
     $sql = "SELECT * FROM savedSearchConditions\n\t\t\t\tWHERE searchID=? ORDER BY searchConditionID";
     $conditions = Zotero_DB::query($sql, $this->id, $shardID);
     foreach ($conditions as $condition) {
         /*
         if (!Zotero.SearchConditions.get(condition)){
         	Zotero.debug("Invalid saved search condition '"
         		+ condition + "' -- skipping", 2);
         	continue;
         }
         */
         $searchConditionID = $condition['searchConditionID'];
         $this->conditions[$searchConditionID] = array('id' => $searchConditionID, 'condition' => $condition['condition'], 'mode' => $condition['mode'], 'operator' => $condition['operator'], 'value' => $condition['value'], 'required' => $condition['required']);
     }
 }
Example #5
0
 private function load()
 {
     $libraryID = $this->libraryID;
     $id = $this->id;
     $key = $this->key;
     Z_Core::debug("Loading data for search " . ($id ? $id : $key));
     if (!$libraryID) {
         throw new Exception("Library ID not set");
     }
     if (!$id && !$key) {
         throw new Exception("ID or key not set");
     }
     $shardID = Zotero_Shards::getByLibraryID($libraryID);
     $sql = "SELECT searchID AS id, searchName AS name, dateAdded,\n\t\t\t\tdateModified, libraryID, `key`, version\n\t\t\t\tFROM savedSearches WHERE ";
     if ($id) {
         $sql .= "searchID=?";
         $params = $id;
     } else {
         $sql .= "libraryID=? AND `key`=?";
         $params = array($libraryID, $key);
     }
     $sql .= " GROUP BY searchID";
     $data = Zotero_DB::rowQuery($sql, $params, $shardID);
     $this->loaded = true;
     if (!$data) {
         return;
     }
     foreach ($data as $key => $val) {
         $this->{$key} = $val;
     }
     $sql = "SELECT * FROM savedSearchConditions\n\t\t\t\tWHERE searchID=? ORDER BY searchConditionID";
     $conditions = Zotero_DB::query($sql, $this->id, $shardID);
     foreach ($conditions as $condition) {
         $searchConditionID = $condition['searchConditionID'];
         $this->conditions[$searchConditionID] = array('id' => $searchConditionID, 'condition' => $condition['condition'], 'mode' => $condition['mode'], 'operator' => $condition['operator'], 'value' => $condition['value'], 'required' => $condition['required']);
     }
 }
Example #6
0
 /**
  * Get library version from the database
  */
 public static function getVersion($libraryID)
 {
     // Default empty library
     if ($libraryID === 0) {
         return 0;
     }
     $sql = "SELECT version FROM shardLibraries WHERE libraryID=?";
     $version = Zotero_DB::valueQuery($sql, $libraryID, Zotero_Shards::getByLibraryID($libraryID));
     // TEMP: Remove after classic sync, and use shardLibraries only for version info?
     if (!$version || $version == 1) {
         $shardID = Zotero_Shards::getByLibraryID($libraryID);
         $readOnly = Zotero_DB::isReadOnly($shardID);
         $sql = "SELECT lastUpdated, version FROM libraries WHERE libraryID=?";
         $row = Zotero_DB::rowQuery($sql, $libraryID);
         $sql = "UPDATE shardLibraries SET version=?, lastUpdated=? WHERE libraryID=?";
         Zotero_DB::query($sql, array($row['version'], $row['lastUpdated'], $libraryID), $shardID, ['writeInReadMode' => true]);
         $sql = "SELECT IFNULL(IF(MAX(version)=0, 1, MAX(version)), 1) FROM items WHERE libraryID=?";
         $version = Zotero_DB::valueQuery($sql, $libraryID, $shardID);
         $sql = "UPDATE shardLibraries SET version=? WHERE libraryID=?";
         Zotero_DB::query($sql, [$version, $libraryID], $shardID, ['writeInReadMode' => true]);
     }
     // Store original version for use by getOriginalVersion()
     if (!isset(self::$originalVersions[$libraryID])) {
         self::$originalVersions[$libraryID] = $version;
     }
     return $version;
 }
Example #7
0
 /**
  * Get the result of a queued process for a given sync session
  *
  * If no result, return false
  * If success, return array('timestamp' => "123456789")
  * If error, return array('xmldata' => "<data ...", 'exception' => Exception)
  */
 public static function getSessionUploadResult($sessionID)
 {
     Zotero_DB::beginTransaction();
     $sql = "SELECT UNIX_TIMESTAMP(finished) AS finished, xmldata, errorCode, errorMessage\n\t\t\t\tFROM syncUploadQueue WHERE sessionID=?";
     $row = Zotero_DB::rowQuery($sql, $sessionID);
     if (!$row) {
         Zotero_DB::commit();
         throw new Exception("Queued upload not found for session");
     }
     if (is_null($row['finished'])) {
         Zotero_DB::beginTransaction();
         return false;
     }
     $sql = "DELETE FROM syncUploadQueue WHERE sessionID=?";
     Zotero_DB::query($sql, $sessionID);
     Zotero_DB::commit();
     // Success
     if (is_null($row['errorCode'])) {
         return array('timestamp' => $row['finished']);
     }
     $e = @unserialize($row['errorMessage']);
     // In case it's not a valid exception for some reason, make one
     //
     // TODO: This can probably be removed after the transition
     if (!$e instanceof Exception) {
         $e = new Exception($row['errorMessage'], $row['errorCode']);
     }
     return array('timestamp' => $row['finished'], 'xmldata' => $row['xmldata'], 'exception' => $e);
 }
Example #8
0
 private function load($allowFail = false)
 {
     if (!$this->libraryID) {
         throw new Exception("Library ID not set");
     }
     if (!$this->id) {
         throw new Exception("ID not set");
     }
     //Z_Core::debug("Loading data for relation $this->id");
     $sql = "SELECT relationID AS id, libraryID, subject, object, predicate FROM relations " . "WHERE relationID=?";
     $data = Zotero_DB::rowQuery($sql, $this->id, Zotero_Shards::getByLibraryID($this->libraryID));
     $this->loaded = true;
     if (!$data) {
         return;
     }
     foreach ($data as $key => $val) {
         $this->{$key} = $val;
     }
 }
Example #9
0
 private function load($allowFail = false)
 {
     if (!$this->libraryID) {
         throw new Exception("Library ID not set");
     }
     if (!$this->id) {
         throw new Exception("ID not set");
     }
     //Z_Core::debug("Loading data for relation $this->id");
     $sql = "SELECT * FROM relations WHERE relationID=?";
     $data = Zotero_DB::rowQuery($sql, $this->id, Zotero_Shards::getByLibraryID($this->libraryID));
     $this->loaded = true;
     if (!$data) {
         return;
     }
     foreach ($data as $key => $val) {
         // TEMP
         if ($key == 'serverDateModifiedMS') {
             continue;
         }
         if ($key == 'relationID') {
             $this->id = $val;
             continue;
         }
         $this->{$key} = $val;
     }
 }
Example #10
0
 /**
  * Make sure we have a valid session
  */
 private function sessionCheck()
 {
     if (empty($_REQUEST['sessionid'])) {
         $this->error(403, 'NO_SESSION_ID', "Session ID not provided");
     }
     if (!preg_match('/^[a-f0-9]{32}$/', $_REQUEST['sessionid'])) {
         $this->error($this->apiVersion >= 9 ? 403 : 500, 'INVALID_SESSION_ID', "Invalid session ID");
     }
     $sessionID = $_REQUEST['sessionid'];
     $session = Z_Core::$MC->get("syncSession_{$sessionID}");
     $userID = $session ? $session['userID'] : null;
     // TEMP: can switch to just $session
     $ipAddress = isset($session['ipAddress']) ? $session['ipAddress'] : null;
     if (!$userID) {
         $sql = "SELECT userid, (UNIX_TIMESTAMP(NOW())-UNIX_TIMESTAMP(timestamp)) AS age,\n\t\t\t\t\tINET_NTOA(ipAddress) AS ipAddress FROM sessions WHERE sessionID=?";
         $session = Zotero_DB::rowQuery($sql, $sessionID);
         if (!$session) {
             $this->error($this->apiVersion >= 9 ? 403 : 500, 'INVALID_SESSION_ID', "Invalid session ID");
         }
         if ($session['age'] > $this->sessionLifetime) {
             $this->error($this->apiVersion >= 9 ? 403 : 500, 'SESSION_TIMED_OUT', "Session timed out");
         }
         $userID = $session['userid'];
         $ipAddress = $session['ipAddress'];
     }
     $updated = Z_Core::$MC->set("syncSession_{$sessionID}", array('sessionID' => $sessionID, 'userID' => $userID, 'ipAddress' => $ipAddress), $this->sessionLifetime - 1200);
     // Every 20 minutes, update the timestamp in the DB
     if (!Z_Core::$MC->get("syncSession_" . $sessionID . "_dbUpdated")) {
         $sql = "UPDATE sessions SET timestamp=NOW() WHERE sessionID=?";
         Zotero_DB::query($sql, $sessionID);
         Z_Core::$MC->set("syncSession_" . $sessionID . "_dbUpdated", true, 1200);
     }
     $this->sessionID = $sessionID;
     $this->userID = $userID;
     $this->userLibraryID = Zotero_Users::getLibraryIDFromUserID($userID);
     $this->ipAddress = $ipAddress;
 }
 private function load()
 {
     Z_Core::debug("Loading data for collection {$this->_id}");
     $libraryID = $this->_libraryID;
     $id = $this->_id;
     $key = $this->_key;
     if (!$libraryID) {
         throw new Exception("Library ID not set");
     }
     if (!$id && !$key) {
         throw new Exception("ID or key not set");
     }
     // Cache collection data for the entire library
     if (true) {
         if ($id) {
             Z_Core::debug("Loading data for collection {$libraryID}/{$id}");
             $row = Zotero_Collections::getPrimaryDataByID($libraryID, $id);
         } else {
             Z_Core::debug("Loading data for collection {$libraryID}/{$key}");
             $row = Zotero_Collections::getPrimaryDataByKey($libraryID, $key);
         }
         $this->loaded = true;
         if (!$row) {
             return;
         }
         if ($row['libraryID'] != $libraryID) {
             throw new Exception("libraryID {$row['libraryID']} != {$this->libraryID}");
         }
         foreach ($row as $key => $val) {
             $field = '_' . $key;
             $this->{$field} = $val;
         }
     } else {
         // Use cached check for existence if possible
         if ($libraryID && $key) {
             if (!Zotero_Collections::existsByLibraryAndKey($libraryID, $key)) {
                 $this->loaded = true;
                 return;
             }
         }
         $sql = Zotero_Collections::getPrimaryDataSQL();
         if ($id) {
             $sql .= "collectionID=?";
             $params = $id;
         } else {
             $sql .= "libraryID=? AND `key`=?";
             $params = array($libraryID, $key);
         }
         $data = Zotero_DB::rowQuery($sql, $params, Zotero_Shards::getByLibraryID($libraryID));
         $this->loaded = true;
         if (!$data) {
             return;
         }
         foreach ($data as $key => $val) {
             $field = '_' . $key;
             $this->{$field} = $val;
         }
     }
 }
Example #12
0
 /**
  * Build object from database
  */
 public function loadPrimaryData($reload = false, $failOnMissing = false)
 {
     if (!$this->identified) {
         return;
     }
     if ($this->loaded['primaryData'] && !$reload) {
         return;
     }
     $libraryID = $this->_libraryID;
     $id = $this->_id;
     $key = $this->_key;
     $objectsClass = $this->objectsClass;
     $columns = [];
     $join = [];
     $where = [];
     if (!$this->_id && !$this->_key) {
         throw new Exception("id or key must be set to load primary data");
     }
     $primaryFields = $objectsClass::$primaryFields;
     $idField = $objectsClass::$idColumn;
     foreach ($primaryFields as $field) {
         // If field not already set
         if ($field == $idField || $this->{'_' . $field} === null || $reload) {
             $columns[] = $objectsClass::getPrimaryDataSQLPart($field) . " AS `{$field}`";
         }
     }
     if (!$columns) {
         return;
     }
     /*if ($id) {
     			Z_Core::debug("Loading data for item $libraryID/$id");
     			$row = Zotero_Items::getPrimaryDataByID($libraryID, $id);
     		}
     		else {
     			Z_Core::debug("Loading data for item $libraryID/$key");
     			$row = Zotero_Items::getPrimaryDataByKey($libraryID, $key);
     		}*/
     // This should match Zotero.*.primaryDataSQL, but without
     // necessarily including all columns
     $sql = "SELECT " . join(", ", $columns) . $objectsClass::$primaryDataSQLFrom;
     if ($id) {
         $sql .= " AND O.{$idField}=? ";
         $params = $id;
     } else {
         $sql .= " AND O.key=? AND O.libraryID=? ";
         $params = [$key, $libraryID];
     }
     $sql .= sizeOf($where) ? ' AND ' . join(' AND ', $where) : '';
     $row = Zotero_DB::rowQuery($sql, $params, Zotero_Shards::getByLibraryID($libraryID));
     if (!$row) {
         if ($failOnMissing) {
             throw new Exception($this->ObjectType . " " . ($id ? $id : $libraryID . "/" . $key) . " not found");
         }
         $this->clearChanged('primaryData');
         // If object doesn't exist, mark all data types as loaded
         $this->markAllDataTypeLoadStates(true);
         return;
     }
     $this->loadFromRow($row, $reload);
 }
Example #13
0
 private function load()
 {
     if ($this->id) {
         $sql = "SELECT * FROM `keys` WHERE keyID=?";
         $row = Zotero_DB::rowQuery($sql, $this->id);
     } else {
         if ($this->key) {
             $sql = "SELECT * FROM `keys` WHERE `key`=?";
             $row = Zotero_DB::rowQuery($sql, $this->key);
         }
     }
     if (!$row) {
         return false;
     }
     $this->loadFromRow($row);
     $sql = "SELECT * FROM keyPermissions WHERE keyID=?";
     $rows = Zotero_DB::query($sql, $this->id);
     foreach ($rows as $row) {
         $this->permissions[$row['libraryID']][$row['permission']] = !!$row['granted'];
         if ($row['permission'] == 'library') {
             // Key-based access to library provides file access as well
             $this->permissions[$row['libraryID']]['files'] = !!$row['granted'];
             // Key-based access to group libraries provides note access as well
             if ($row['libraryID'] === 0 || Zotero_Libraries::getType($row['libraryID']) == 'group') {
                 $this->permissions[$row['libraryID']]['notes'] = !!$row['granted'];
             }
         }
     }
 }
Example #14
0
 /**
  * Get the text of an item note
  **/
 public function getNote($sanitized = false, $htmlspecialchars = false)
 {
     if (!$this->isNote() && !$this->isAttachment()) {
         throw new Exception("getNote() can only be called on notes and attachments");
     }
     if (!$this->id) {
         return '';
     }
     // Store access time for later garbage collection
     //$this->noteAccessTime = new Date();
     if ($sanitized) {
         if ($htmlspecialchars) {
             throw new Exception('$sanitized and $htmlspecialchars cannot currently be used together');
         }
         if (is_null($this->noteText)) {
             $sql = "SELECT note, noteSanitized FROM itemNotes WHERE itemID=?";
             $row = Zotero_DB::rowQuery($sql, $this->id, Zotero_Shards::getByLibraryID($this->libraryID));
             if (!$row) {
                 $row = array('note' => '', 'noteSanitized' => '');
             }
             // Empty string means the note is sanitized
             // Null means not yet processed
             if ($row['noteSanitized'] === '') {
                 $this->noteText = $row['note'];
                 $this->noteTextSanitized =& $this->noteText;
             } else {
                 $this->noteText = $row['note'];
                 if (!is_null($row['noteSanitized'])) {
                     $this->noteTextSanitized = $row['noteSanitized'];
                 }
             }
         }
         // DEBUG: Shouldn't be necessary anymore
         if (is_null($this->noteTextSanitized)) {
             $sanitized = Zotero_Notes::sanitize($this->noteText);
             // If sanitized version is the same, use reference
             if ($this->noteText == $sanitized) {
                 $this->noteTextSanitized =& $this->noteText;
             } else {
                 $this->noteTextSanitized = $sanitized;
             }
         }
         return $this->noteTextSanitized;
     }
     if (is_null($this->noteText)) {
         $note = Zotero_Notes::getCachedNote($this->libraryID, $this->id);
         if ($note === false) {
             $sql = "SELECT note FROM itemNotes WHERE itemID=?";
             $note = Zotero_DB::valueQuery($sql, $this->id, Zotero_Shards::getByLibraryID($this->libraryID));
         }
         $this->noteText = $note ? $note : '';
     }
     if ($this->noteText !== '' && $htmlspecialchars) {
         $noteHash = $this->getNoteHash();
         if (!$noteHash) {
             throw new Exception("Note hash is empty");
         }
         $cacheKey = "htmlspecialcharsNote_{$noteHash}";
         $note = Z_Core::$MC->get($cacheKey);
         if ($note === false) {
             $note = htmlspecialchars($this->noteText);
             Z_Core::$MC->set($cacheKey, $note);
         }
         return $note;
     }
     return $this->noteText;
 }
Example #15
0
 private function load()
 {
     $sql = "SELECT * FROM groups WHERE groupID=?";
     $row = Zotero_DB::rowQuery($sql, $this->id);
     if (!$row) {
         return false;
     }
     foreach ($row as $field => $value) {
         switch ($field) {
             case 'groupID':
             case 'slug':
                 continue 2;
         }
         $this->{$field} = $value;
     }
     $sql = "SELECT userID FROM groupUsers WHERE groupID=? AND role='owner'";
     $userID = Zotero_DB::valueQuery($sql, $this->id);
     if (!$userID) {
         throw new Exception("Group {$this->id} doesn't have an owner");
     }
     $this->ownerUserID = $userID;
     $this->loaded = true;
     $this->changed = array();
 }
Example #16
0
	/**
	* Get the text of an item note
	**/
	public function getNote($sanitized=false, $htmlspecialchars=false) {
		if (!$this->isNote() && !$this->isAttachment()) {
			throw new Exception("getNote() can only be called on notes and attachments");
		}
		
		if (!$this->id) {
			return '';
		}
		
		// Store access time for later garbage collection
		//$this->noteAccessTime = new Date();
		
		if ($sanitized) {
			if ($htmlspecialchars) {
				throw new Exception('$sanitized and $htmlspecialchars cannot currently be used together');
			}
			
			if (is_null($this->noteText)) {
				$sql = "SELECT note, noteSanitized FROM itemNotes WHERE itemID=?";
				$row = Zotero_DB::rowQuery($sql, $this->id, Zotero_Shards::getByLibraryID($this->libraryID));
				if (!$row) {
					$row = array('note' => '', 'noteSanitized' => '');
				}
				$this->noteText = $row['note'];
				$this->noteTextSanitized = $row['noteSanitized'];
			}
			// Empty string means the original note is sanitized
			return $this->noteTextSanitized === '' ? $this->noteText : $this->noteTextSanitized;
		}
		
		if (is_null($this->noteText)) {
			$note = Zotero_Notes::getCachedNote($this->libraryID, $this->id);
			if ($note === false) {
				$sql = "SELECT note FROM itemNotes WHERE itemID=?";
				$note = Zotero_DB::valueQuery($sql, $this->id, Zotero_Shards::getByLibraryID($this->libraryID));
			}
			$this->noteText = $note !== false ? $note : '';
		}
		
		if ($this->noteText !== '' && $htmlspecialchars) {
			$noteHash = $this->getNoteHash();
			if ($noteHash) {
				$cacheKey = "htmlspecialcharsNote_$noteHash";
				$note = Z_Core::$MC->get($cacheKey);
				if ($note === false) {
					$note = htmlspecialchars($this->noteText);
					Z_Core::$MC->set($cacheKey, $note);
				}
			}
			else {
				error_log("WARNING: Note hash is empty");
				$note = htmlspecialchars($this->noteText);
			}
			return $note;
		}
		
		return $this->noteText;
	}
 public static function getPrimaryDataByKey($libraryID, $key)
 {
     $type = static::field('object');
     $keyField = static::field('key');
     if (!is_numeric($libraryID)) {
         throw new Exception("Invalid libraryID '{$libraryID}'");
     }
     if ($type == 'relation') {
         if (!preg_match('/[a-f0-9]{32}/', $key)) {
             throw new Exception("Invalid key '{$key}'");
         }
     } else {
         if (!preg_match('/[A-Z0-9]{8}/', $key) && $type != 'setting') {
             throw new Exception("Invalid key '{$key}'");
         }
     }
     if (isset(self::$primaryDataByKey[$type][$libraryID][$key])) {
         return self::$primaryDataByKey[$type][$libraryID][$key];
     }
     $sql = self::getPrimaryDataSQL() . "libraryID=? AND `{$keyField}`=?";
     $row = Zotero_DB::rowQuery($sql, array($libraryID, $key), Zotero_Shards::getByLibraryID($libraryID));
     self::cachePrimaryData($row, $libraryID, $key);
     return $row;
 }