예제 #1
0
// Helper function.
// $user: The user whose changes are being processed.
// $recipients: The users who opted to receive online notifications of any contributors.
void changes_process_identifiers (Webserver_Request * request,
                                  string user,
                                  vector <string> recipients,
                                  string bible,
                                  int book, int chapter,
                                  int oldId, int newId,
                                  string & email)
{
  if (oldId != 0) {
    recipients = filter_string_array_diff (recipients, {user});
    Database_Modifications database_modifications;
    string stylesheet = Database_Config_Bible::getExportStylesheet (bible);
    Database_Modifications_Text old_chapter_text = database_modifications.getUserChapter (user, bible, book, chapter, oldId);
    string old_chapter_usfm = old_chapter_text.oldtext;
    Database_Modifications_Text new_chapter_text = database_modifications.getUserChapter (user, bible, book, chapter, newId);
    string new_chapter_usfm = new_chapter_text.newtext;
    //int timestamp = database_modifications.getUserTimestamp (user, bible, book, chapter, newId);
    vector <int> old_verse_numbers = usfm_get_verse_numbers (old_chapter_usfm);
    vector <int> new_verse_numbers = usfm_get_verse_numbers (new_chapter_usfm);
    vector <int> verses = old_verse_numbers;
    verses.insert (verses.end (), new_verse_numbers.begin (), new_verse_numbers.end ());
    verses = array_unique (verses);
    sort (verses.begin(), verses.end());
    for (auto verse : verses) {
      string old_verse_usfm = usfm_get_verse_text (old_chapter_usfm, verse);
      string new_verse_usfm = usfm_get_verse_text (new_chapter_usfm, verse);
      if (old_verse_usfm != new_verse_usfm) {
        Filter_Text filter_text_old = Filter_Text (bible);
        Filter_Text filter_text_new = Filter_Text (bible);
        filter_text_old.html_text_standard = new Html_Text (translate("Bible"));
        filter_text_new.html_text_standard = new Html_Text (translate("Bible"));
        filter_text_old.text_text = new Text_Text ();
        filter_text_new.text_text = new Text_Text ();
        filter_text_old.addUsfmCode (old_verse_usfm);
        filter_text_new.addUsfmCode (new_verse_usfm);
        filter_text_old.run (stylesheet);
        filter_text_new.run (stylesheet);
        string old_html = filter_text_old.html_text_standard->getInnerHtml ();
        string new_html = filter_text_new.html_text_standard->getInnerHtml ();
        string old_text = filter_text_old.text_text->get ();
        string new_text = filter_text_new.text_text->get ();
        if (old_text != new_text) {
          string modification = filter_diff_diff (old_text, new_text);
          email += "<div>";
          email += filter_passage_display (book, chapter, convert_to_string (verse));
          email += " ";
          email += modification;
          email += "</div>";
          if (request->database_config_user()->getUserUserChangesNotificationsOnline (user)) {
            database_modifications.recordNotification ({user}, changes_personal_category (), bible, book, chapter, verse, old_html, modification, new_html);
          }
          for (auto recipient : recipients) {
            if (recipient == user) continue;
            database_modifications.recordNotification ({recipient}, user, bible, book, chapter, verse, old_html, modification, new_html);
          }
        }
      }
    }
  }
}
예제 #2
0
string xrefs_translate (void * webserver_request)
{
  Webserver_Request * request = (Webserver_Request *) webserver_request;
  
  
  string sourceBible = request->database_config_user()->getSourceXrefBible ();
  string targetBible = request->database_config_user()->getTargetXrefBible ();
  
  
  // Save book / abbreviation pair.
  if (request->post.count ("save")) {
    string fullname = request->post ["fullname"];
    string abbreviation = request->post ["abbreviation"];
    string abbreviations = Database_Config_Bible::getBookAbbreviations (targetBible);
    abbreviations = filter_abbreviations_add (abbreviations, fullname, abbreviation);
    Database_Config_Bible::setBookAbbreviations (targetBible, abbreviations);
  }
  
  
  string abbrevs = Database_Config_Bible::getBookAbbreviations (sourceBible);
  vector <pair <int, string> > sourceAbbreviations = filter_abbreviations_read (abbrevs);
  vector <int> sourceBooks;
  for (auto element : sourceAbbreviations) sourceBooks.push_back (element.first);

  
  abbrevs = Database_Config_Bible::getBookAbbreviations (targetBible);
  vector <pair <int, string> > targetAbbreviations = filter_abbreviations_read (abbrevs);
  vector <int> targetBooks;
  for (auto element : targetAbbreviations) targetBooks.push_back (element.first);
  
  
  vector <int> unknown_books = filter_string_array_diff (sourceBooks, targetBooks);
  unknown_books = array_unique (unknown_books);
  
  
  if (unknown_books.empty ()) {
    redirect_browser (request, xrefs_clear_url ());
    return "";
  }


  string page;
  Assets_Header header = Assets_Header (translate("Cross references"), webserver_request);
  page = header.run ();
  Assets_View view;
  
  
  view.set_variable ("remaining", convert_to_string (unknown_books.size () - 1));
                      
  
  string bookname = Database_Books::getEnglishFromId (unknown_books [0]);
  view.set_variable ("bookname", bookname);
  
  
  page += view.render ("xrefs", "translate");
  page += Assets_Page::footer ();
  return page;
}
예제 #3
0
파일: logic.cpp 프로젝트: alerque/bibledit
// Maintains the database with note assignees.
// $force: Force maintenance.
void notes_logic_maintain_note_assignees (bool force)
{
  Database_NoteAssignment database_noteassignment;
  Webserver_Request webserver_request;
  
  Database_Users database_users;
  vector <string> users = database_users.getUsers ();

  // If even one user's assignees are absent, force rebuilding them for all users.
  for (auto & user : users) {
    if (!database_noteassignment.exists (user)) force = true;
  }
  if (!force) return;

  Database_Bibles database_bibles;
  vector <string> bibles = database_bibles.getBibles ();
  
  // A user can assign notes to other users
  // who have access to the Bibles the user has access to.
  for (auto & user : users) {

    vector <string> assignees;
    
    for (auto & bible : bibles) {
      
      // Continue with this Bible if the user has access to it.
      if (access_bible_read (&webserver_request, bible, user)) {

        for (auto & assignee : users) {
          if (access_bible_read (&webserver_request, bible, assignee)) {
            assignees.push_back (assignee);
          }
        }
      }
    }

    assignees = array_unique (assignees);
    database_noteassignment.assignees (user, assignees);
  }
}
예제 #4
0
string search_search2 (void * webserver_request)
{
    Webserver_Request * request = (Webserver_Request *) webserver_request;


    Database_Volatile database_volatile = Database_Volatile ();


    string siteUrl = config_logic_site_url ();


    string bible = request->database_config_user()->getBible ();
    if (request->query.count ("bible")) bible = request->query ["bible"];


    bool hit_is_set = request->query.count ("h");
    bool query_is_set = request->query.count ("q");
    int identifier = convert_to_int (request->query ["i"]);
    string query = request->query ["q"];
    string hit = request->query ["h"];


    // Get one search hit.
    if (hit_is_set) {


        // Retrieve the search parameters from the volatile database.
        string query = database_volatile.getValue (identifier, "query");
        //bool casesensitive = convert_to_bool (database_volatile.getValue (identifier, "casesensitive"));
        bool plaintext = convert_to_bool (database_volatile.getValue (identifier, "plaintext"));


        // Get the Bible and passage for this identifier.
        Passage details = Passage::from_text (hit);
        string bible = details.bible;
        int book = details.book;
        int chapter = details.chapter;
        string verse = details.verse;


        // Get the plain text or USFM.
        string text;
        if (plaintext) {
            text = search_logic_get_bible_verse_text (bible, book, chapter, convert_to_int (verse));
        } else {
            text = search_logic_get_bible_verse_usfm (bible, book, chapter, convert_to_int (verse));
        }


        // Format it.
        string link = filter_passage_link_for_opening_editor_at (book, chapter, verse);
        text =  filter_string_markup_words ({query}, text);
        string output = "<div>" + link + " " + text + "</div>";


        // Output to browser.
        return output;
    }


    // Perform the initial search.
    if (query_is_set) {


        // Get extra search parameters and store them all in the volatile database.
        bool casesensitive = (request->query ["c"] == "true");
        bool plaintext = (request->query ["p"] == "true");
        bool currentbook = (request->query ["b"] == "true");
        string sharing = request->query ["s"];
        database_volatile.setValue (identifier, "query", query);
        database_volatile.setValue (identifier, "casesensitive", convert_to_string (casesensitive));
        database_volatile.setValue (identifier, "plaintext", convert_to_string (plaintext));


        // Deal with case sensitivity.
        // Deal with whether to search the plain text, or the raw USFM.
        // Fetch the initial set of hits.
        vector <Passage> passages;
        if (plaintext) {
            if (casesensitive) {
                passages = search_logic_search_bible_text_case_sensitive (bible, query);
            } else {
                passages = search_logic_search_bible_text (bible, query);
            }
        } else {
            if (casesensitive) {
                passages = search_logic_search_bible_usfm_case_sensitive (bible, query);
            } else {
                passages = search_logic_search_bible_usfm (bible, query);
            }
        }


        // Deal with possible searching in the current book only.
        if (currentbook) {
            int book = Ipc_Focus::getBook (request);
            vector <Passage> bookpassages;
            for (auto & passage : passages) {
                if (book == passage.book) {
                    bookpassages.push_back (passage);
                }
            }
            passages = bookpassages;
        }


        // Deal with how to share the results.
        vector <string> hits;
        for (auto & passage : passages) {
            hits.push_back (passage.to_text ());
        }
        if (sharing != "load") {
            vector <string> loaded_hits = filter_string_explode (database_volatile.getValue (identifier, "hits"), '\n');
            if (sharing == "add") {
                hits.insert (hits.end(), loaded_hits.begin(), loaded_hits.end());
            }
            if (sharing == "remove") {
                hits = filter_string_array_diff (loaded_hits, hits);
            }
            if (sharing == "intersect") {
                hits = array_intersect (loaded_hits, hits);
            }
            hits = array_unique (hits);
        }


        // Generate one string from the hits.
        string output = filter_string_implode (hits, "\n");


        // Store search hits in the volatile database.
        database_volatile.setValue (identifier, "hits", output);


        // Output results.
        return output;
    }


    // Build the advanced search page.
    string page;
    Assets_Header header = Assets_Header (translate("Search"), request);
    header.setNavigator ();
    header.addBreadCrumb (menu_logic_search_menu (), menu_logic_search_text ());
    page = header.run ();
    Assets_View view;
    view.set_variable ("bible", bible);
    string script = "var searchBible = \"" + bible + "\";";
    view.set_variable ("script", script);
    page += view.render ("search", "search2");
    page += Assets_Page::footer ();
    return page;
}
예제 #5
0
파일: strong.cpp 프로젝트: alerque/bibledit
string search_strong (void * webserver_request)
{
  Webserver_Request * request = (Webserver_Request *) webserver_request;


  Database_Kjv database_kjv = Database_Kjv ();
  
  
  string bible = request->database_config_user()->getBible ();
  if (request->query.count ("b")) {
    bible = request->query ["b"];
  }
  
  
  if (request->query.count ("load")) {

    int book = Ipc_Focus::getBook (request);
    int chapter = Ipc_Focus::getChapter (request);
    int verse = Ipc_Focus::getVerse (request);
    
    // Get Strong's numbers, plus English snippets.
    string html = "<table>\n";
    vector <Database_Kjv_Item> details = database_kjv.getVerse (book, chapter, verse);
    for (auto & detail : details) {
      string strong = detail.strong;
      string english = detail.english;
      html += "<tr><td><a href=\"" + strong + "\">" + strong + "</a></td><td>" + english + "</td></tr>\n";
    }
    html += "</table>\n";
    
    return html;
  }
  
  
  if (request->query.count ("strong")) {
    
    string strong = request->query ["strong"];
    strong = filter_string_trim (strong);
    
    vector <int> passages;
    
    vector <Passage> details = database_kjv.searchStrong (strong);
    
    for (auto & passage : details) {
      int i_passage = filter_passage_to_integer (passage);
      passages.push_back (i_passage);
    }
    
    passages = array_unique (passages);
    sort (passages.begin(), passages.end());
    
    string output;
    for (auto & passage : passages) {
      if (!output.empty()) output.append ("\n");
      output.append (convert_to_string (passage));
    }
    return output;
  }
  
  
  if (request->query.count ("id")) {
    int id = convert_to_int (request->query ["id"]);
    
    // Get the and passage for this identifier.
    Passage passage = filter_integer_to_passage (id);
    int book = passage.book;
    int chapter = passage.chapter;
    string verse = passage.verse;
    
    // Get the plain text.
    string text = search_logic_get_bible_verse_text (bible, book, chapter, convert_to_int (verse));
    
    // Format it.
    string link = filter_passage_link_for_opening_editor_at (book, chapter, verse);
    string output = "<div>" + link + " " + text + "</div>";
    
    // Output to browser.
    return output;
  }
  
  
  string page;
  
  Assets_Header header = Assets_Header (translate("Search"), request);
  header.setNavigator ();
  header.addBreadCrumb (menu_logic_search_menu (), menu_logic_search_text ());
  page = header.run ();
  
  Assets_View view;
  
  view.set_variable ("bible", bible);
  
  string script = "var searchBible = \"" + bible + "\";";
  view.set_variable ("script", script);

  page += view.render ("search", "strong");
  
  page += Assets_Page::footer ();
  
  return page;
}
예제 #6
0
void changes_modifications ()
{
  Database_Logs::log ("Change notifications: Generating", Filter_Roles::translator ());

  
  // Notifications are not available to clients for the duration of processing them.
  config_globals_change_notifications_available = false;
  
  
  // Data objects.
  Webserver_Request request;
  Database_Modifications database_modifications;


  // Check on the health of the modifications database and (re)create it if needed.
  if (!database_modifications.healthy ()) database_modifications.erase ();
  database_modifications.create ();
  
  
  // Create online change notifications for users who made changes in Bibles
  // through the web editor or through a client.
  // It runs before the team changes.
  // This produces the desired order of the notifications in the GUI.
  // At the same time, produce change statistics per user.
  
  // Get the users who will receive the changes entered by the contributors.
  vector <string> recipients;
  {
    vector <string> users = request.database_users ()->getUsers ();
    for (auto & user : users) {
      if (request.database_config_user ()->getContributorChangesNotificationsOnline (user)) {
        recipients.push_back (user);
      }
    }
  }

  // Storage for the statistics.
  map <string, int> user_change_statistics;
  float modification_time_total = 0;
  int modification_time_count = 0;
  
  vector <string> users = database_modifications.getUserUsernames ();
  if (!users.empty ()) Database_Logs::log ("Change notifications: Per user", Filter_Roles::translator ());
  for (auto user : users) {

    // Total changes made by this user.
    int change_count = 0;
    
    // Go through the Bibles changed by the current user.
    vector <string> bibles = database_modifications.getUserBibles (user);
    for (auto bible : bibles) {
      
      // Body of the email to be sent.
      string email = "<p>" + translate("You have entered the changes below in a Bible editor.") + " " + translate ("You may check if it made its way into the Bible text.") + "</p>";
      size_t empty_email_length = email.length ();
      
      // Go through the books in that Bible.
      vector <int> books = database_modifications.getUserBooks (user, bible);
      for (auto book : books) {
        
        // Go through the chapters in that book.
        vector <int> chapters = database_modifications.getUserChapters (user, bible, book);
        for (auto chapter : chapters) {
          
          // Get the sets of identifiers for that chapter, and set some variables.
          vector <Database_Modifications_Id> IdSets = database_modifications.getUserIdentifiers (user, bible, book, chapter);
          //int referenceOldId = 0;
          int referenceNewId = 0;
          int newId = 0;
          int lastNewId = 0;
          bool restart = true;
          
          // Go through the sets of identifiers.
          for (auto IdSet : IdSets) {
            
            int oldId = IdSet.oldid;
            newId = IdSet.newid;
            
            if (restart) {
              changes_process_identifiers (&request, user, recipients, bible, book, chapter, referenceNewId, newId, email, change_count, modification_time_total, modification_time_count);
              //referenceOldId = oldId;
              referenceNewId = newId;
              lastNewId = newId;
              restart = false;
              continue;
            }
            
            if (oldId == lastNewId) {
              lastNewId = newId;
            } else {
              restart = true;
            }
          }
          
          // Process the last set of identifiers.
          changes_process_identifiers (&request, user, recipients, bible, book, chapter, referenceNewId, newId, email, change_count, modification_time_total, modification_time_count);
          
        }
      }

      // Check whether there's any email to be sent.
      if (email.length () != empty_email_length) {
        // Send the user email with the user's personal changes if the user opted to receive it.
        if (request.database_config_user()->getUserUserChangesNotification (user)) {
          string subject = translate("Changes you entered in") + " " + bible;
          if (!client_logic_client_enabled ()) email_schedule (user, subject, email);
        }
      }
    }
    
    // Store change statistics for this user.
    user_change_statistics [user] = change_count;

    // Clear the user's changes in the database.
    database_modifications.clearUserUser (user);
    
    
    // Clear checksum cache.
    request.database_config_user ()->setUserChangeNotificationsChecksum (user, "");
  }
  
  
  // Generate the notifications, online and by email,
  // for the changes in the Bibles entered by anyone since the previous notifications were generated.
  vector <string> bibles = database_modifications.getTeamDiffBibles ();
  for (auto bible : bibles) {
    
    
    string stylesheet = Database_Config_Bible::getExportStylesheet (bible);
    
    
    vector <string> changeNotificationUsers;
    vector <string> users = request.database_users ()->getUsers ();
    for (auto user : users) {
      if (access_bible_read (&request, bible, user)) {
        if (request.database_config_user()->getUserGenerateChangeNotifications (user)) {
          changeNotificationUsers.push_back (user);
        }
      }
    }
    users.clear ();
    
    
    // The number of changes processed so far for this Bible.
    int processedChangesCount = 0;
    
    
    // The files get stored at http://site.org:<port>/revisions/<Bible>/<date>
    int seconds = filter_date_seconds_since_epoch ();
    string timepath;
    timepath.append (convert_to_string (filter_date_numerical_year (seconds)));
    timepath.append ("-");
    timepath.append (filter_string_fill (convert_to_string (filter_date_numerical_month (seconds)), 2, '0'));
    timepath.append ("-");
    timepath.append (filter_string_fill (convert_to_string (filter_date_numerical_month_day (seconds)), 2, '0'));
    timepath.append (" ");
    timepath.append (filter_string_fill (convert_to_string (filter_date_numerical_hour (seconds)), 2, '0'));
    timepath.append (":");
    timepath.append (filter_string_fill (convert_to_string (filter_date_numerical_minute (seconds)), 2, '0'));
    timepath.append (":");
    timepath.append (filter_string_fill (convert_to_string (filter_date_numerical_second (seconds)), 2, '0'));
    string directory = filter_url_create_root_path ("revisions", bible, timepath);
    filter_url_mkdir (directory);
    
    
    // Produce the USFM and html files.
    filter_diff_produce_verse_level (bible, directory);
    
    
    // Create online page with changed verses.
    string versesoutputfile = filter_url_create_path (directory, "changed_verses.html");
    filter_diff_run_file (filter_url_create_path (directory, "verses_old.txt"), filter_url_create_path (directory, "verses_new.txt"), versesoutputfile);
    
    
    // Storage for body of the email with the changes.
    vector <string> email_changes;
    
    
    // Generate the online change notifications.
    vector <int> books = database_modifications.getTeamDiffBooks (bible);
    for (auto book : books) {
      vector <int> chapters = database_modifications.getTeamDiffChapters (bible, book);
      for (auto chapter : chapters) {
        Database_Logs::log ("Change notifications: " + bible + " " + filter_passage_display (book, chapter, ""), Filter_Roles::translator ());
        string old_chapter_usfm = database_modifications.getTeamDiff (bible, book, chapter);
        string new_chapter_usfm = request.database_bibles()->getChapter (bible, book, chapter);
        vector <int> old_verse_numbers = usfm_get_verse_numbers (old_chapter_usfm);
        vector <int> new_verse_numbers = usfm_get_verse_numbers (new_chapter_usfm);
        vector <int> verses = old_verse_numbers;
        verses.insert (verses.end (), new_verse_numbers.begin (), new_verse_numbers.end ());
        verses = array_unique (verses);
        sort (verses.begin (), verses.end());
        for (auto verse : verses) {
          string old_verse_usfm = usfm_get_verse_text (old_chapter_usfm, verse);
          string new_verse_usfm = usfm_get_verse_text (new_chapter_usfm, verse);
          if (old_verse_usfm != new_verse_usfm) {
            processedChangesCount++;
            // In case of too many change notifications, processing them would take too much time, so take a few shortcuts.
            string old_html = "<p>" + old_verse_usfm + "</p>";
            string new_html = "<p>" + new_verse_usfm + "</p>";
            string old_text = old_verse_usfm;
            string new_text = new_verse_usfm;
            if (processedChangesCount < 800) {
              Filter_Text filter_text_old = Filter_Text (bible);
              Filter_Text filter_text_new = Filter_Text (bible);
              filter_text_old.html_text_standard = new Html_Text ("");
              filter_text_new.html_text_standard = new Html_Text ("");
              filter_text_old.text_text = new Text_Text ();
              filter_text_new.text_text = new Text_Text ();
              filter_text_old.addUsfmCode (old_verse_usfm);
              filter_text_new.addUsfmCode (new_verse_usfm);
              filter_text_old.run (stylesheet);
              filter_text_new.run (stylesheet);
              old_html = filter_text_old.html_text_standard->getInnerHtml ();
              new_html = filter_text_new.html_text_standard->getInnerHtml ();
              old_text = filter_text_old.text_text->get ();
              new_text = filter_text_new.text_text->get ();
            }
            string modification = filter_diff_diff (old_text, new_text);
            database_modifications.recordNotification (changeNotificationUsers, changes_bible_category (), bible, book, chapter, verse, old_html, modification, new_html);
            string passage = filter_passage_display (book, chapter, convert_to_string (verse))   + ": ";
            if (old_text != new_text) {
              email_changes.push_back (passage  + modification);
            } else {
              email_changes.push_back (translate ("The following passage has no change in the text.") + " " + translate ("The change is in the formatting only.") + " " + translate ("The USFM code is given for reference."));
              email_changes.push_back (passage);
              email_changes.push_back (translate ("Old code:") + " " + old_verse_usfm);
              email_changes.push_back (translate ("New code:") + " " + new_verse_usfm);
            }
          }
        }
        // Delete the diff data for this chapter, for two reasons:
        // 1. New diffs for this chapter can be stored straightaway.
        // 2. In case of large amounts of diff data, and this function gets killed,
        //    then the next time it runs again, it will continue from where it was killed.
        database_modifications.deleteTeamDiffChapter (bible, book, chapter);
      }
    }
    
    
    // Email the changes to the subscribed users.
    if (!email_changes.empty ()) {
      string body;
      for (auto & line : email_changes) {
        body.append ("<div>");
        body.append (line);
        body.append ("</div>\n");
      }
      string subject = translate("Recent changes:") + " " + bible;
      vector <string> users = request.database_users ()->getUsers ();
      for (auto & user : users) {
        if (request.database_config_user()->getUserBibleChangesNotification (user)) {
          if (access_bible_read (&request, bible, user)) {
            if (!client_logic_client_enabled ()) {
              email_schedule (user, subject, body);
            }
          }
        }
      }
    }
    
    
  }

  
  // Index the data and remove expired notifications.
  Database_Logs::log ("Change notifications: Indexing", Filter_Roles::translator ());
  database_modifications.indexTrimAllNotifications ();

  
  // Remove expired downloadable revisions.
  string directory = filter_url_create_root_path ("revisions");
  int now = filter_date_seconds_since_epoch ();
  bibles = filter_url_scandir (directory);
  for (auto &bible : bibles) {
    string folder = filter_url_create_path (directory, bible);
    int time = filter_url_file_modification_time (folder);
    int days = (now - time) / 86400;
    if (days > 31) {
      filter_url_rmdir (folder);
    } else {
      vector <string> revisions = filter_url_scandir (folder);
      for (auto & revision : revisions) {
        string path = filter_url_create_path (folder, revision);
        int time = filter_url_file_modification_time (path);
        int days = (now - time) / 86400;
        if (days > 31) {
          filter_url_rmdir (path);
          Database_Logs::log ("Removing expired downloadable revision notification: " + bible + " " + revision, Filter_Roles::translator ());
        }
      }
    }
  }
  
  
  // Clear checksum caches.
  users = request.database_users ()->getUsers ();
  for (auto user : users) {
    request.database_config_user ()->setUserChangeNotificationsChecksum (user, "");
  }
  
  
  // Vacuum the modifications index, as it might have been updated.
  database_modifications.vacuum ();
  
  
  // Make the notifications available again to clients.
  config_globals_change_notifications_available = true;

  
  // Store the statistics in the database.
  if (modification_time_count) {
    // Take average timestamp of all timestamps.
    int timestamp = round (modification_time_total / modification_time_count);
    for (auto & element : user_change_statistics) {
      // Store dated change statistics per user.
      string user = element.first;
      int count = element.second;
      Database_Statistics::store_changes (timestamp, user, count);
    }
  }
  

  Database_Logs::log ("Change notifications: Ready", Filter_Roles::translator ());
}