function save_json($cj, $actor, $send)
 {
     global $Conf, $Me, $Now;
     $inserting = !$this->contactId;
     $old_roles = $this->roles;
     $old_email = $this->email;
     $different_email = strtolower($cj->email) !== strtolower((string) $old_email);
     $cu = new Contact_Update($inserting, $different_email);
     $aupapers = null;
     if ($different_email) {
         $aupapers = self::email_authored_papers($cj->email, $cj);
     }
     // check whether this user is changing themselves
     $changing_other = false;
     if (self::contactdb() && $Me && (strcasecmp($this->email, $Me->email) != 0 || $Me->is_actas_user())) {
         $changing_other = true;
     }
     // Main fields
     foreach (array("firstName", "lastName", "email", "affiliation", "collaborators", "preferredEmail", "country") as $k) {
         if (isset($cj->{$k})) {
             $this->_save_assign_field($k, $cj->{$k}, $cu);
         }
     }
     if (isset($cj->phone)) {
         $this->_save_assign_field("voicePhoneNumber", $cj->phone, $cu);
     }
     $this->_save_assign_field("unaccentedName", Text::unaccented_name($this->firstName, $this->lastName), $cu);
     self::set_sorter($this);
     // Disabled
     $disabled = $this->disabled ? 1 : 0;
     if (isset($cj->disabled)) {
         $disabled = $cj->disabled ? 1 : 0;
     }
     if (($this->disabled ? 1 : 0) !== $disabled || !$this->contactId) {
         $cu->qv["disabled"] = $this->disabled = $disabled;
     }
     // Data
     $old_datastr = $this->data_str();
     $data = (object) array();
     foreach (array("address", "city", "state", "zip") as $k) {
         if (isset($cj->{$k}) && ($x = $cj->{$k})) {
             while (is_array($x) && $x[count($x) - 1] === "") {
                 array_pop($x);
             }
             $data->{$k} = $x ?: null;
         }
     }
     $this->merge_data($data);
     $datastr = $this->data_str();
     if ($datastr !== $old_datastr) {
         $cu->qv["data"] = $datastr;
     }
     // Changes to the above fields also change the updateTime.
     if (count($cu->qv)) {
         $cu->qv["updateTime"] = $this->updateTime = $Now;
     }
     // Follow
     if (isset($cj->follow)) {
         $w = 0;
         if (get($cj->follow, "reviews")) {
             $w |= WATCH_COMMENT;
         }
         if (get($cj->follow, "allreviews")) {
             $w |= WATCH_ALLCOMMENTS;
         }
         if (get($cj->follow, "allfinal")) {
             $w |= WATCHTYPE_FINAL_SUBMIT << WATCHSHIFT_ALL;
         }
         $this->_save_assign_field("defaultWatch", $w, $cu);
     }
     // Tags
     if (isset($cj->tags)) {
         $tags = array();
         foreach ($cj->tags as $t) {
             list($tag, $value) = TagInfo::split_index($t);
             if (strcasecmp($tag, "pc") != 0) {
                 $tags[$tag] = $tag . "#" . ($value ?: 0);
             }
         }
         ksort($tags);
         $t = count($tags) ? " " . join(" ", $tags) . " " : "";
         $this->_save_assign_field("contactTags", $t, $cu);
     }
     // If inserting, set initial password and creation time
     if ($inserting) {
         $cu->qv["creationTime"] = $this->creationTime = $Now;
         $this->_create_password(self::contactdb_find_by_email($this->email), $cu);
     }
     // Initial save
     if (count($cu->qv)) {
         // always true if $inserting
         $q = ($inserting ? "insert into" : "update") . " ContactInfo set " . join("=?, ", array_keys($cu->qv)) . "=?" . ($inserting ? "" : " where contactId={$this->contactId}");
         if (!($result = Dbl::qe_apply($Conf->dblink, $q, array_values($cu->qv)))) {
             return $result;
         }
         if ($inserting) {
             $this->contactId = $this->cid = (int) $result->insert_id;
         }
         Dbl::free($result);
     }
     // Topics
     if (isset($cj->topics)) {
         $tf = array();
         foreach ($cj->topics as $k => $v) {
             $tf[] = "({$this->contactId},{$k},{$v})";
         }
         $Conf->qe("delete from TopicInterest where contactId={$this->contactId}");
         if (count($tf)) {
             $Conf->qe("insert into TopicInterest (contactId,topicId,interest) values " . join(",", $tf));
         }
     }
     // Roles
     $roles = 0;
     if (isset($cj->roles)) {
         $roles = self::parse_roles_json($cj->roles);
         if ($roles !== $old_roles) {
             $this->save_roles($roles, $actor);
         }
     }
     // Update authorship
     if ($aupapers) {
         $this->save_authored_papers($aupapers);
     }
     // Update contact database
     $cdbu = $this->contactDbId ? $this : $this->contactdb_user_;
     if ($different_email) {
         $cdbu = null;
     }
     if (($cdb = self::contactdb()) && (!$cdbu || count($cu->cdb_uqv))) {
         $qv = [];
         if (!$cdbu) {
             $q = "insert into ContactInfo set firstName=?, lastName=?, email=?, affiliation=?, country=?, collaborators=?";
             $qv = array($this->firstName, $this->lastName, $this->email, $this->affiliation, $this->country, $this->collaborators);
             if ($this->password !== "" && ($this->password[0] !== " " || $this->password[1] === "\$")) {
                 $q .= ", password=?";
                 $qv[] = $this->password;
             }
             $q .= " on duplicate key update ";
         } else {
             $q = "update ContactInfo set ";
         }
         if (count($cu->cdb_uqv) && $changing_other) {
             $q .= join(", ", array_map(function ($k) {
                 return "{$k}=if(coalesce({$k},'')='',?,{$k})";
             }, array_keys($cu->cdb_uqv)));
         } else {
             if (count($cu->cdb_uqv)) {
                 $q .= join("=?, ", array_keys($cu->cdb_uqv)) . "=?";
             } else {
                 $q .= "firstName=firstName";
             }
         }
         if (count($cu->cdb_uqv)) {
             $q .= ", updateTime={$Now}";
         }
         $qv = array_merge($qv, array_values($cu->cdb_uqv));
         if ($cdbu) {
             $q .= " where contactDbId=" . $cdbu->contactDbId;
         }
         $result = Dbl::ql_apply($cdb, $q, $qv);
         Dbl::free($result);
         $this->contactdb_user_ = false;
     }
     // Password
     if (isset($cj->new_password)) {
         $this->change_password(get($cj, "old_password"), $cj->new_password, 0);
     }
     // Beware PC cache
     if (($roles | $old_roles) & Contact::ROLE_PCLIKE) {
         $Conf->invalidateCaches(array("pc" => 1));
     }
     // Mark creation and activity
     if ($inserting) {
         if ($send && !$this->disabled) {
             $this->sendAccountInfo("create", false);
         }
         $type = $this->disabled ? "disabled " : "";
         if ($Me && $Me->has_email() && $Me->email !== $this->email) {
             $Conf->log("Created {$type}account ({$Me->email})", $this);
         } else {
             $Conf->log("Created {$type}account", $this);
         }
     }
     $actor = $actor ?: $Me;
     if ($actor && $this->contactId == $actor->contactId) {
         $this->mark_activity();
     }
     return true;
 }
 private function invariantq($q, $args = [])
 {
     $result = Dbl::ql_apply($this->dblink, $q, $args);
     if ($result) {
         self::$invariant_row = $result->fetch_row();
         $result->close();
         return !!self::$invariant_row;
     } else {
         return null;
     }
 }