if (preg_match("/^insert/i", $x)) { print mysql_affected_rows() . " INSERTED\n \n"; } } } } return $res; } require_once dirname(__FILE__) . "/../../common/connect/applyCredentials.php"; require_once dirname(__FILE__) . "/../../common/php/dbMySqlWrappers.php"; require_once dirname(__FILE__) . "/../../common/php/saveRecord.php"; require_once dirname(__FILE__) . "/../../common/php/utilsTitleMask.php"; // 26/3/14 Functions to index record being saved using Elastic Search (Lucene) require_once dirname(__FILE__) . "/../../records/index/elasticSearchFunctions.php"; if (!is_logged_in()) { jsonError("no logged-in user"); } $_REQUEST = json_decode(@$_POST["data"] ? $_POST["data"] : base64_decode(@$_GET["data"]), true); mysql_connection_overwrite(DATABASE); mysql_query("start transaction"); $out = saveRecord(@$_REQUEST["id"], @$_REQUEST["type"], @$_REQUEST["url"], @$_REQUEST["notes"], @$_REQUEST["group"], @$_REQUEST["vis"], @$_REQUEST["bookmark"], @$_REQUEST["pnotes"], @$_REQUEST["rating"], @$_REQUEST["tags"], @$_REQUEST["wgTags"], @$_REQUEST["detail"], @$_REQUEST["-notify"], @$_REQUEST["+notify"], @$_REQUEST["-comment"], @$_REQUEST["comment"], @$_REQUEST["+comment"]); mysql_query("commit"); // 26/3/14 Add record to index in Elastic Search (Lucene) updateRecordIndexEntry(HEURIST_DBNAME, @$_REQUEST["type"], @$_REQUEST["id"]); print json_format($out); function jsonError($message) { mysql_query("rollback"); print "{\"error\":\"" . addslashes($message) . "\"}"; exit(0); }
/** * Main method that parses POST and update details for given record ID * * @param int $recID */ function updateRecord($recID, $rtyID = null) { // Update the given record. // This is non-trivial: so that the versioning stuff (achive_*) works properly // we need to separate this into updates, inserts and deletes. // We get the currect record details and compare them against the post // if the details id is in the post[dtyID][dtlID] then compare the values $recID = intval($recID); // Check that the user has permissions to edit it. $res = mysql_query("select * from Records" . " left join sysUsrGrpLinks on ugl_GroupID=rec_OwnerUGrpID" . " left join defRecTypes on rty_ID=rec_RecTypeID" . " where rec_ID={$recID} and (! rec_OwnerUGrpID or rec_OwnerUGrpID=" . get_user_id() . " or ugl_UserID=" . get_user_id() . ")"); if (mysql_num_rows($res) == 0) { $res = mysql_query("select grp.ugr_Name from Records, " . USERS_DATABASE . ".sysUGrps grp where rec_ID={$recID} and grp.ugr_ID=rec_OwnerUGrpID"); $grpName = mysql_fetch_row($res); $grpName = $grpName[0]; print '({ error: "\\nSorry - you can\'t edit this record.\\nYou aren\'t in the ' . slash($grpName) . ' workgroup" })'; return; } $record = mysql_fetch_assoc($res); // Upload any files submitted ... (doesn't have to take place right now, but may as well) uploadFiles(); //Artem: it does not work here - since we uploaded files at once // Get the existing records details and compare them to the incoming data $recDetails = getRecordDetails($recID); // find UPDATES - everything that is in current record and has a post value is treated as an update $recDetailUpdates = array(); foreach ($recDetails as $dtyID => $dtlIDs) { $eltName = "type:" . $dtyID; $skipEltName = "_type:" . $dtyID; if (@$_REQUEST[$skipEltName] && is_array($_REQUEST[$skipEltName])) { // handle any _type post meant for ignore, need to remove from recDetails foreach (@$_REQUEST[$skipEltName] as $codedDtlID => $ingnoreVal) { if (!preg_match("/^bd:\\d+\$/", $codedDtlID)) { continue; } $dtlID = substr($codedDtlID, 3); // everything after "bd:" unset($_REQUEST[$skipEltName][$codedDtlID]); // remove data from post submission if (count($_REQUEST[$skipEltName]) == 0) { // if nothing left in post dtyID then remove it also unset($_REQUEST[$skipEltName]); } unset($recDetails[$dtyID][$dtlID]); // remove data from local reflection of the database } } if (!(@$_REQUEST[$eltName] && is_array($_REQUEST[$eltName]))) { // element wasn't in POST: ignore it -this could be a non-rectype detail unset($recDetails[$dtyID]); // remove from details so it's not deleted continue; } if (count($_REQUEST[$eltName]) == 0) { // element was in POST but without content: values have been deleted client-side (need to be deleted in DB so leave POST) continue; } $bdInputHandler = getInputHandlerForType($dtyID); //returns the particular handler (processor) for given field type foreach ($dtlIDs as $dtlID => $val) { $eltID = "bd:" . $dtlID; $val = @$_REQUEST[$eltName][$eltID]; if (!$bdInputHandler->inputOK($val, $dtyID, $rtyID)) { continue; // faulty input ... ignore } $toadd = $bdInputHandler->convertPostToMysql($val); if ($toadd == null) { continue; } $recDetailUpdates[$dtlID] = $toadd; $recDetailUpdates[$dtlID]["dtl_DetailTypeID"] = $dtyID; /* @TODO Since this function is utilized in (email)import we need to add verification of values according to detail type at the first for terms (enumeration field type) */ unset($_REQUEST[$eltName][$eltID]); // remove data from post submission if (count($_REQUEST[$eltName]) == 0) { // if nothing left in post dtyID then remove it also unset($_REQUEST[$eltName]); } unset($recDetails[$dtyID][$dtlID]); // remove data from local reflection of the database } } // find DELETES // Anything left in recDetails now represents recDetails rows that need to be deleted $bibDetailDeletes = array(); foreach ($recDetails as $dtyID => $dtlIDs) { foreach ($dtlIDs as $dtlID => $val) { array_push($bibDetailDeletes, $dtlID); } } // find INSERTS // Try to insert anything left in POST as new recDetails rows $bibDetailInserts = array(); foreach ($_REQUEST as $eltName => $bds) { // if not properly formatted or empty or an empty array then skip it if (!preg_match("/^type:\\d+\$/", $eltName) || !$_REQUEST[$eltName] || count($_REQUEST[$eltName]) == 0) { continue; } $dtyID = substr($eltName, 5); $bdInputHandler = getInputHandlerForType($dtyID); foreach ($bds as $eltID => $val) { if (!$bdInputHandler->inputOK($val, $dtyID, $rtyID)) { continue; // faulty input ... ignore } $newBibDetail = $bdInputHandler->convertPostToMysql($val); $newBibDetail["dtl_DetailTypeID"] = $dtyID; $newBibDetail["dtl_RecID"] = $recID; array_push($bibDetailInserts, $newBibDetail); unset($_REQUEST[$eltName][$eltID]); // remove data from post submission } } // Anything left in POST now is stuff that we have no intention of inserting ... ignore it // We now have: // - $recDetailUpdates: an assoc. array of dtl_ID => column values to be updated in recDetails // - $bibDetailInserts: an array of column values to be inserted into recDetails // - $bibDetailDeletes: an array of dtl_ID values corresponding to rows to be deleted from recDetails // Commence versioning ... mysql_query("start transaction"); $recUpdates = array("rec_Modified" => array("now()"), "rec_FlagTemporary" => 0); $recUpdates["rec_ScratchPad"] = $_REQUEST["notes"]; if (intval(@$_REQUEST["rectype"])) { $recUpdates["rec_RecTypeID"] = intval($_REQUEST["rectype"]); } if (array_key_exists("rec_url", $_REQUEST)) { $recUpdates["rec_URL"] = $_REQUEST["rec_url"]; } $owner = $record['rec_OwnerUGrpID']; if (is_admin() || is_admin('group', $owner) || $owner == get_user_id()) { // must be grpAdmin or record owner to changes ownership or visibility if (array_key_exists("rec_owner", $_REQUEST)) { $recUpdates["rec_OwnerUGrpID"] = $_REQUEST["rec_owner"]; } if (array_key_exists("rec_visibility", $_REQUEST)) { $recUpdates["rec_NonOwnerVisibility"] = $_REQUEST["rec_visibility"]; } else { if ($record['rec_NonOwnerVisibility'] == 'public' && HEURIST_PUBLIC_TO_PENDING) { $recUpdates["rec_NonOwnerVisibility"] = 'pending'; } } } mysql__update("Records", "rec_ID={$recID}", $recUpdates); $biblioUpdated = mysql_affected_rows() > 0 ? true : false; $updatedRowCount = 0; foreach ($recDetailUpdates as $bdID => $vals) { mysql__update("recDetails", "dtl_ID={$bdID} and dtl_RecID={$recID}", $vals); if (mysql_affected_rows() > 0) { ++$updatedRowCount; } } $insertedRowCount = 0; foreach ($bibDetailInserts as $vals) { mysql__insert("recDetails", $vals); if (mysql_affected_rows() > 0) { ++$insertedRowCount; } } $deletedRowCount = 0; if ($bibDetailDeletes) { mysql_query("delete from recDetails where dtl_ID in (" . join($bibDetailDeletes, ",") . ") and dtl_RecID={$recID}"); if (mysql_affected_rows() > 0) { $deletedRowCount = mysql_affected_rows(); } } // eliminate any duplicated lines $notesIn = explode("\n", str_replace("\r", "", $_REQUEST["notes"])); $notesOut = ""; $notesMap = array(); for ($i = 0; $i < count($notesIn); ++$i) { if (!@$notesMap[$notesIn[$i]] || !$notesIn[$i]) { // preserve blank lines $notesOut .= $notesIn[$i] . "\n"; $notesMap[$notesIn[$i]] = true; } } $_REQUEST["notes"] = preg_replace("/\n\n+/", "\n", $notesOut); if ($updatedRowCount > 0 || $insertedRowCount > 0 || $deletedRowCount > 0 || $biblioUpdated) { /* something changed: update the records title and commit all changes */ $new_title = fill_title_mask($record["rty_TitleMask"], $record["rec_ID"], $record["rec_RecTypeID"]); mysql_query("update Records\n set rec_Title = '" . mysql_real_escape_string($new_title) . "'\n where rec_ID = {$recID}"); mysql_query("commit"); // Update memcached's copy of record (if it is cached) updateCachedRecord($recID); updateRecordIndexEntry(DATABASE, $record["rec_RecTypeID"], $recID); // TODO: Doesn't properly update Elasticsearch return true; } else { /* nothing changed: rollback the transaction so we don't get false versioning */ mysql_query("rollback"); return false; } }
/** * Rebuild the index for a specified record type * @param $dbName The name of the Heurist databasem, excluding prefix * @param * @returns 0 = OK, any other = error */ function buildIndexForRectype($dbName, $recTypeID) { global $elasticSearch; if ($elasticSearch) { //print "buildIndexForRectype: indexing record type $recTypeID for $dbName<br />"; deleteIndexForRectype($dbName, $recTypeID); // clear the existing index $query = "Select rec_ID from Records where rec_RecTypeID = {$recTypeID}"; $res = mysql_query($query); if ($res) { while ($row = mysql_fetch_array($res)) { // fetch records $code = updateRecordIndexEntry($dbName, $recTypeID, $row[0]); if ($code != 0) { //print "<br />ERROR while updating record index; code = $code, dbName = $dbname, rectypeID = $recTypeID, row = " + $row[0] + "<br />"; return $code; // curl error code } } return 0; } } return -1; }
function saveRecord($recordID, $rectype, $url, $notes, $wg, $vis, $personalised, $pnotes, $rating, $tags, $wgTags, $details, $notifyREMOVE, $notifyADD, $commentREMOVE, $commentMOD, $commentADD, &$nonces = null, &$retitleRecs = null, $modeImport = 0) { global $msgInfoSaveRec; $msgInfoSaveRec = array(); // reset the message array mysql_query("start transaction"); // $log = " saving record ($recordID) "; $recordID = intval($recordID); $wg = intval($wg); if (!is_logged_in()) { errSaveRec("It is not possible to save record if not logged in, record save aborted"); return $msgInfoSaveRec; } else { if ($wg != null && intval($wg) > 0 && intval($wg) != get_user_id()) { // non-member saves are not allowed $uquery = "select * from " . USERS_DATABASE . ".sysUsrGrpLinks where ugl_UserID=" . get_user_id() . " and ugl_GroupID=" . $wg; $res = mysql_query($uquery); if (mysql_num_rows($res) < 1) { errSaveRec("Current user " . get_user_id() . " is not a member of required workgroup " . $wg . ", record save aborted "); return $msgInfoSaveRec; } } } $rectype = intval($rectype); if ($recordID && !$rectype) { errSaveRec("cannot change existing record to private note, record save aborted"); return $msgInfoSaveRec; } $rectypeName = null; $res = mysql_query("select rty_Name from defRecTypes where rty_ID=" . $rectype); if ($res) { $row = mysql_fetch_row($res); if ($row) { $rectypeName = $row[0]; } } if (!$rectypeName) { errSaveRec("record type #{$rectype} is not valid"); return $msgInfoSaveRec; } if ($vis) { $vis = strtolower(str_replace('"', "", $vis)); $isvalid = in_array(strtolower($vis), array('hidden', 'viewable', 'pending', 'public')); if ($isvalid == false) { $vis = null; } } $now = date('Y-m-d H:i:s'); $wg = $wg >= 0 ? $wg : get_user_id(); // public records data if (!$recordID || $recordID < 0) { //new record // $log .= "- inserting record "; $recheader = array("rec_RecTypeID" => $rectype, "rec_URL" => $url, "rec_ScratchPad" => $notes, "rec_OwnerUGrpID" => $wg, "rec_NonOwnerVisibility" => $vis ? $vis : "viewable", "rec_AddedByUGrpID" => get_user_id(), "rec_Added" => $now, "rec_Modified" => $now, "rec_AddedByImport" => $modeImport > 0 ? 1 : 0); if ($recordID < 0) { $recordID = abs($recordID); $recheader["rec_ID"] = $recordID; } mysql__insert("Records", $recheader); if (mysql_error()) { errSaveRec("Database record insert error - " . mysql_error() . " " . print_r($recheader, true)); return $msgInfoSaveRec; } $recordID = mysql_insert_id(); } else { $res = checkPermission($recordID, $wg); if ($res !== true) { errSaveRec($res); return $msgInfoSaveRec; } // $log .= "- updating record "; mysql__update("Records", "rec_ID={$recordID}", array("rec_RecTypeID" => $rectype, "rec_URL" => $url, "rec_ScratchPad" => $notes, "rec_OwnerUGrpID" => $wg, "rec_NonOwnerVisibility" => $vis ? $vis : "viewable", "rec_FlagTemporary" => 0, "rec_Modified" => $now)); updateRecordIndexEntry(DATABASE, $rectype, $recordID); // TODO: Doesn't properly update Elasticsearch if (mysql_error()) { errSaveRec("Database record update error - " . mysql_error()); return $msgInfoSaveRec; } } // public recDetails data if ($details) { // $log .= "- inserting details "; $dtlIDsByAction = doDetailInsertion($recordID, $details, $rectype, $wg, $nonces, $retitleRecs, $modeImport); if (@$dtlIDsByAction['error']) { array_push($msgInfoSaveRec['error'], $dtlIDsByAction['error']); return $msgInfoSaveRec; } } // check that all the required fields are present $res = mysql_query("select rst_ID, rst_DetailTypeID, rst_DisplayName" . " from defRecStructure" . " left join recDetails on dtl_RecID={$recordID} and rst_DetailTypeID=dtl_DetailTypeID" . " where rst_RecTypeID={$rectype} and rst_RequirementType='required' and dtl_ID is null"); if (mysql_num_rows($res) > 0) { // $log .= "- testing missing detatils "; $missed = ""; while ($row = mysql_fetch_row($res)) { //ij asked to remove$conceptCode = getDetailTypeConceptID($row[1]); $missed = $missed . $row[2]; //ij asked to remove ." (Code:".$conceptCode.") "; } // at least one missing field if ($missed) { $msg = "Missing data for Required field(s) in '{$rectypeName}'. You may need to make fields optional. Missing data: " . $missed; if ($modeImport == 2) { warnSaveRec($msg); } else { errSaveRec($msg); return $msgInfoSaveRec; } } } mysql_query("commit"); // if we get to here we have a valid save of the core record. // calculate title, do an update // $log .= "- filling titlemask "; $mask = mysql__select_array("defRecTypes", "rty_TitleMask", "rty_ID={$rectype}"); $mask = $mask[0]; $title = fill_title_mask($mask, $recordID, $rectype); if ($title) { mysql_query("update Records set rec_Title = '" . mysql_real_escape_string($title) . "' where rec_ID = {$recordID}"); } // Update memcache: we can do this here since it's only the public data that we cache. updateCachedRecord($recordID); updateRecordIndexEntry(USERS_DATABASE, $rectype, $recordID); // private data $bkmk = @mysql_fetch_row(mysql_query("select bkm_ID from usrBookmarks where bkm_UGrpID=" . get_user_id() . " and bkm_recID=" . $recordID)); $bkm_ID = @$bkmk[0]; if ($personalised) { if (!$bkm_ID) { // Record is not yet bookmarked, but we want it to be mysql_query("insert into usrBookmarks (bkm_Added,bkm_Modified,bkm_UGrpID,bkm_recID) values (now(),now()," . get_user_id() . ",{$recordID})"); if (mysql_error()) { warnSaveRec("trying to create a bookmark - database error - " . mysql_error()); } else { $bkm_ID = mysql_insert_id(); } } // $log .= "- updating bookmark "; mysql__update("usrBookmarks", "bkm_ID={$bkm_ID}", array("bkm_Rating" => $rating, "bkm_Modified" => date('Y-m-d H:i:s'))); //WARNING tags is assumed to be a complete replacement list for personal tags on this record. doTagInsertion($recordID, $bkm_ID, $tags); } else { if ($bkm_ID) { // Record is bookmarked, but the user doesn't want it to be // $log .= "- deleting bookmark "; $query = "delete usrBookmarks, usrRecTagLinks " . "from usrBookmarks left join usrRecTagLinks on rtl_RecID = bkm_recID " . "left join usrTags on tag_ID = rtl_TagID " . "where bkm_ID={$bkm_ID} and bkm_recID={$recordID} and bkm_UGrpID = tag_UGrpID and bkm_UGrpID=" . get_user_id(); mysql_query($query); if (mysql_error()) { warnSaveRec("database error while removing bookmark- " . mysql_error()); } //saw TODO: add code to remove other personal data reminders, personal notes (woots), etc. } } doWgTagInsertion($recordID, $wgTags); if ($notifyREMOVE || $notifyADD) { $notifyIDs = handleNotifications($recordID, $notifyREMOVE, $notifyADD); } if ($commentREMOVE || $commentMOD || $commentADD) { $commentIDs = handleComments($recordID, $commentREMOVE, $commentMOD, $commentADD); } $rval = array("bibID" => $recordID, "bkmkID" => $bkm_ID, "modified" => $now); if ($title) { $rval["title"] = $title; } if (@$dtlIDsByAction) { $rval["detail"] = $dtlIDsByAction; } if (@$notifyIDs) { $rval["notify"] = $notifyIDs; } if (@$commentIDs) { $rval["comment"] = $commentIDs; } if (@$msgInfoSaveRec['warning']) { $rval["warning"] = $msgInfoSaveRec['warning']; } if (@$msgInfoSaveRec['error']) { //should never get here with error set $rval["error"] = $msgInfoSaveRec['error']; } else { //$rval["usageCount"] = updateRecTypeUsageCount(); } return $rval; }