// This function compares the $newtext with the $oldtext. // It returns an empty string if the difference is below the limit set for the Bible. // It returns a message specifying the difference if it exceeds that limit. string usfm_save_is_safe (void * webserver_request, string oldtext, string newtext, bool chapter) { // Two texts are equal: safe. if (newtext == oldtext) return ""; Webserver_Request * request = (Webserver_Request *) webserver_request; // Allowed percentage difference. int allowed_percentage = request->database_config_user ()->getEditingAllowedDifferenceVerse (); if (chapter) allowed_percentage = request->database_config_user ()->getEditingAllowedDifferenceChapter (); // The length of the new text should not differ more than a set percentage from the old text. float existingLength = oldtext.length(); float newLength = newtext.length (); int percentage = 100 * (newLength - existingLength) / existingLength; percentage = abs (percentage); if (percentage > 100) percentage = 100; if (percentage > allowed_percentage) { Database_Logs::log ("The text was not saved for safety reasons. The length differs " + convert_to_string (percentage) + "% from the existing text. Make smaller changes and save more often. Or relax the restriction in the Bible's editing settings."); Database_Logs::log (newtext); return translate ("Text length differs too much"); } // The new text should be at least a set percentage similar to the old text. percentage = filter_diff_similarity (oldtext, newtext); if (percentage < (100 - allowed_percentage)) { Database_Logs::log ("The text was not saved for safety reasons. The new text is " + convert_to_string (percentage) + "% similar to the existing text. Make smaller changes and save more often. Or relax the restriction in the Bible's editing settings."); Database_Logs::log (newtext); return translate ("Text content differs too much"); } // Safety checks have passed. return ""; }
// Send the named $desktop to a $user name. void workspace_send (void * webserver_request, string desktop, string user) { Webserver_Request * request = (Webserver_Request *) webserver_request; // Save current active desktop. string active_desktop = request->database_config_user()->getActiveWorkspace (); // Retrieve settings for the $desktop of the current user. request->database_config_user()->setActiveWorkspace (desktop); map <int, string> urls = workspace_get_urls (webserver_request, false); map <int, string> widths = workspace_get_widths (webserver_request); map <int, string> heights = workspace_get_heights (webserver_request); string entire_width = workspace_get_entire_width (webserver_request); // Restore current active desktop. request->database_config_user()->setActiveWorkspace (active_desktop); // New webserver request object for the destination user. Webserver_Request destination_request; destination_request.session_logic ()->setUsername (user); // Save desktop for destination user. active_desktop = destination_request.database_config_user()->getActiveWorkspace (); destination_request.database_config_user()->setActiveWorkspace (desktop); // Copy source desktop to destination. workspace_set_urls (&destination_request, urls); workspace_set_widths (&destination_request, widths); workspace_set_heights (&destination_request, heights); workspace_set_entire_width (&destination_request, entire_width); // Restore desktop for the destination user. request->database_config_user()->setActiveWorkspace (active_desktop); }
string editone_preview (void * webserver_request) { Webserver_Request * request = (Webserver_Request *) webserver_request; bool touch = request->session_logic ()->touchEnabled (); string page; Assets_Header header = Assets_Header (translate("Preview"), request); header.setNavigator (); header.setEditorStylesheet (); if (touch) header.jQueryTouchOn (); header.addBreadCrumb (menu_logic_translate_menu (), menu_logic_translate_text ()); header.refresh (5, "index"); page = header.run (); Assets_View view; // Get active Bible, and check read access to it. // If needed, change Bible to one it has read access to. string bible = access_bible_clamp (request, request->database_config_user()->getBible ()); string cls = Filter_Css::getClass (bible); string font = Fonts_Logic::getTextFont (bible); int direction = Database_Config_Bible::getTextDirection (bible); int lineheight = Database_Config_Bible::getLineHeight (bible); int letterspacing = Database_Config_Bible::getLetterSpacing (bible); view.set_variable ("custom_class", cls); view.set_variable ("custom_css", Filter_Css::getCss (cls, Fonts_Logic::getFontPath (font), direction, lineheight, letterspacing)); int book = Ipc_Focus::getBook (webserver_request); int chapter = Ipc_Focus::getChapter (webserver_request); //int verse = Ipc_Focus::getVerse (webserver_request); string stylesheet = request->database_config_user()->getStylesheet (); string usfm = request->database_bibles()->getChapter (bible, book, chapter); Editor_Usfm2Html editor_usfm2html; editor_usfm2html.load (usfm); editor_usfm2html.stylesheet (stylesheet); editor_usfm2html.run (); string html = editor_usfm2html.get (); view.set_variable ("html", html); page += view.render ("editone", "preview"); page += Assets_Page::footer (); return page; }
string basic_index (void * webserver_request) { Webserver_Request * request = (Webserver_Request *) webserver_request; Assets_Header header = Assets_Header ("Settings", webserver_request); string page = header.run (); Assets_View view; if (request->query.count ("changebible")) { string changebible = request->query ["changebible"]; if (changebible == "") { Dialog_List dialog_list = Dialog_List ("index", translate("Select which Bible to make the active one for editing"), "", ""); vector <string> bibles = access_bible_bibles (request); for (auto & bible : bibles) { dialog_list.add_row (bible, "changebible", bible); } page += dialog_list.run(); return page; } else { request->database_config_user()->setBible (changebible); // Going to another Bible, ensure that the focused book exists there. int book = Ipc_Focus::getBook (request); vector <int> books = request->database_bibles()->getBooks (changebible); if (find (books.begin(), books.end(), book) == books.end()) { if (!books.empty ()) book = books [0]; else book = 0; Ipc_Focus::set (request, book, 1, 1); } } } string bible = access_bible_clamp (request, request->database_config_user()->getBible ()); view.set_variable ("bible", bible); #ifdef CLIENT_PREPARED view.enable_zone ("client"); if (client_logic_client_enabled ()) { view.enable_zone ("connected"); } #else view.enable_zone ("cloud"); #endif page += view.render ("basic", "index"); page += Assets_Page::footer (); return page; }
string edit_load (void * webserver_request) { Webserver_Request * request = (Webserver_Request *) webserver_request; string bible = request->query ["bible"]; int book = convert_to_int (request->query ["book"]); int chapter = convert_to_int (request->query ["chapter"]); // Store a copy of the USFM loaded in the editor for later reference. storeLoadedUsfm (webserver_request, bible, book, chapter, "edit"); string stylesheet = request->database_config_user()->getStylesheet (); string usfm = request->database_bibles()->getChapter (bible, book, chapter); Editor_Usfm2Html editor_usfm2html; editor_usfm2html.load (usfm); editor_usfm2html.stylesheet (stylesheet); editor_usfm2html.run (); string html = editor_usfm2html.get (); // To make editing empty verses easier, convert spaces to non-breaking spaces, so they appear in the editor. if (usfm_contains_empty_verses (usfm)) { string search = "<span> </span>"; string replace = "<span>" + unicode_non_breaking_space_entity () + "</span>"; html = filter_string_str_replace (search, replace, html); } string user = request->session_logic ()->currentUser (); bool write = access_bible_book_write (webserver_request, user, bible, book); return Checksum_Logic::send (html, write); }
void client_index_enable_client (void * webserver_request, string username, string password, int level) { Webserver_Request * request = (Webserver_Request *) webserver_request; // Enable client mode upon a successful connection. client_logic_enable_client (true); // Remove all users from the database, and add the current one. client_index_remove_all_users (request); request->database_users ()->add_user (username, password, level, ""); // Update the username and the level in the current session. request->session_logic ()->setUsername (username); request->session_logic ()->currentLevel (true); // Clear all pending note actions and Bible actions and settings updates. Database_NoteActions database_noteactions; Database_BibleActions database_bibleactions; database_noteactions.clear (); database_noteactions.create (); database_bibleactions.clear (); database_bibleactions.create (); request->session_logic ()->setUsername (username); request->database_config_user()->setUpdatedSettings ({}); Database_Config_General::setUnsentBibleDataTime (0); Database_Config_General::setUnreceivedBibleDataTime (filter_date_seconds_since_epoch ()); // Set it to repeat sync every so often. if (Database_Config_General::getRepeatSendReceive () == 0) { Database_Config_General::setRepeatSendReceive (2); } // Schedule a sync operation straight-away. sendreceive_queue_sync (-1); }
string public_create (void * webserver_request) { Webserver_Request * request = (Webserver_Request *) webserver_request; Database_Notes database_notes (webserver_request); Notes_Logic notes_logic = Notes_Logic (webserver_request); string page; Assets_Header header = Assets_Header (translate("Create note"), request); page += header.run (); Assets_View view; string bible = request->database_config_user()->getBible (); int book = Ipc_Focus::getBook (webserver_request); int chapter = Ipc_Focus::getChapter (webserver_request); int verse = Ipc_Focus::getVerse (webserver_request); string chapter_usfm = request->database_bibles()->getChapter (bible, book, chapter); string verse_usfm = usfm_get_verse_text (chapter_usfm, verse); string stylesheet = Database_Config_Bible::getExportStylesheet (bible); Filter_Text filter_text = Filter_Text (bible); filter_text.html_text_standard = new Html_Text (bible); filter_text.addUsfmCode (verse_usfm); filter_text.run (stylesheet); string versetext = filter_text.html_text_standard->getInnerHtml (); view.set_variable ("versetext", versetext); if (request->post.count ("submit")) { string summary = filter_string_trim (request->post["summary"]); if (summary.empty ()) summary = translate ("Feedback"); string contents = "<p>" + versetext + "</p>" + filter_string_trim (request->post["contents"]); int identifier = notes_logic.createNote (bible, book, chapter, verse, summary, contents, false); // A note created by a public user is made public to all. database_notes.setPublic (identifier, true); // Subscribe the user to the note. // Then the user receives email about any updates made on this note. database_notes.subscribe (identifier); // Go to the main public notes page. redirect_browser (request, public_index_url ()); return ""; } if (request->post.count ("cancel")) { redirect_browser (request, public_index_url ()); return ""; } string passage = filter_passage_display (book, chapter, convert_to_string (verse)); view.set_variable ("passage", passage); page += view.render ("public", "create"); page += Assets_Page::footer (); return page; }
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; }
// Store updated workspace settings for sending to the cloud. void workspace_cache_for_cloud (void * webserver_request, bool urls, bool widths, bool heights) { #ifdef HAVE_CLIENT // For a client, store the setting for sending to the server. Webserver_Request * request = (Webserver_Request *) webserver_request; if (urls) request->database_config_user()->addUpdatedSetting (Sync_Logic::settings_send_workspace_urls); if (widths) request->database_config_user()->addUpdatedSetting (Sync_Logic::settings_send_workspace_widths); if (heights) request->database_config_user()->addUpdatedSetting (Sync_Logic::settings_send_workspace_heights); #else (void) webserver_request; (void) urls; (void) widths; (void) heights; #endif }
string workspace_get_active_name (void * webserver_request) { Webserver_Request * request = (Webserver_Request *) webserver_request; string workspace = request->database_config_user()->getActiveWorkspace (); if (workspace.empty ()) { workspace = workspace_get_default_name (); } return workspace; }
// handleEmailComment - handles an email received from from with subject subject and body body. // Returns true if the mail was processed, else false. // The email is considered to have been processed if it comments on an existing Consultation Note. bool Notes_Logic::handleEmailComment (string from, string subject, string body) { // Check whether the Consultation Notes Identifier in the subject exists in the notes database. // The CNID looks like: (CNID123456789) size_t pos = subject.find ("(CNID"); if (pos == string::npos) return false; subject = subject.substr (pos + 5); pos = subject.find (")"); if (pos == string::npos) return false; subject = subject.substr (0, pos); // Webserver request. Webserver_Request * request = (Webserver_Request *) webserver_request; // At this stage, the subject contains an identifier. // Check that the identifier is an existing Consultation Note. int identifier = convert_to_int (subject); Database_Notes database_notes (webserver_request); if (!database_notes.identifierExists (identifier)) return false; // Check that the from address of the email belongs to an existing user. // Or else use the obfuscated email address as the user name. string username; from = filter_string_extract_email (from); if (request->database_users()->emailExists (from)) { username = request->database_users()->getEmailToUser (from); } else { username = from; username = filter_string_str_replace ("@", " ", username); username = filter_string_str_replace (".", " ", username); } // Clean the email's body. string year = convert_to_string (filter_date_numerical_year (filter_date_seconds_since_epoch ())); string sender = Database_Config_General::getSiteMailName(); body = filter_string_extract_body (body, year, sender); // Remove any new lines from the body. This cleans up the email considerably, // because some emails that get posted would otherwise look untidy, // when the many new lines take up a lot of space. body = filter_string_str_replace ("\n", " ", body); // Make comment on the consultation note. string sessionuser = request->session_logic ()->currentUser (); request->session_logic ()->setUsername (username); addComment (identifier, body); request->session_logic ()->setUsername (sessionuser); // Mail confirmation to the username. if (request->database_config_user()->getUserNotifyMeOfMyPosts (username)) { Database_Mail database_mail = Database_Mail (webserver_request); string subject = translate("Your comment was posted"); subject.append (" [CNID"); subject.append (convert_to_string (identifier)); subject.append ("]"); database_mail.send (username, subject, body); } // Log operation. Database_Logs::log ("Comment posted: " + body); // Job done. return true; }
void workspace_create_defaults (void * webserver_request) { Webserver_Request * request = (Webserver_Request *) webserver_request; // Save current active desktop. string desktop = request->database_config_user()->getActiveWorkspace (); // Create or update the default desktops. vector <string> names = workspace_get_default_names (); for (unsigned int i = 0; i < names.size (); i++) { request->database_config_user()->setActiveWorkspace (names [i]); int bench = i + 1; workspace_set_urls (request, workspace_get_default_urls (bench)); workspace_set_widths (request, workspace_get_default_widths (bench)); workspace_set_heights (request, workspace_get_default_heights (bench)); } // Restore current active desktop. request->database_config_user()->setActiveWorkspace (desktop); }
string edit_focus (void * webserver_request) { Webserver_Request * request = (Webserver_Request *) webserver_request; // Get Bible: If an empty Bible is given, bail out. string bible = request->query ["bible"]; if (bible.empty ()) return ""; // Get book: If no book is given: Bail out. int book = convert_to_int (request->query ["book"]); if (!book) return ""; // Get chapter. int chapter = convert_to_int (request->query ["chapter"]); string stylesheet = request->database_config_user()->getStylesheet (); string usfm = request->database_bibles()->getChapter (bible, book, chapter); int verse = Ipc_Focus::getVerse (request); Editor_Usfm2Html editor_usfm2html; editor_usfm2html.load (usfm); editor_usfm2html.stylesheet (stylesheet); editor_usfm2html.run (); int startingOffset = 0; int endingOffset = 0; // To deal with a combined verse, go through the offsets, and pick the correct one. for (auto element : editor_usfm2html.verseStartOffsets) { int vs = element.first; int offset = element.second; if (vs <= verse) startingOffset = offset; if (endingOffset == 0) { if (vs > verse) { endingOffset = offset; } } } if (verse) { startingOffset += convert_to_string (verse).length () + 1; } if (endingOffset) { endingOffset--; } else { endingOffset = editor_usfm2html.textLength; } string data = convert_to_string (startingOffset); data.append ("\n"); data.append (convert_to_string (endingOffset)); return data; }
// Copy desktop $source to $destination void workspace_copy (void * webserver_request, string source, string destination) { Webserver_Request * request = (Webserver_Request *) webserver_request; // Save current active desktop. string active_desktop = request->database_config_user()->getActiveWorkspace (); // Copy source desktop to destination. request->database_config_user()->setActiveWorkspace (source); map <int, string> urls = workspace_get_urls (webserver_request, false); map <int, string> widths = workspace_get_widths (webserver_request); map <int, string> heights = workspace_get_heights (webserver_request); string entire_width = workspace_get_entire_width (webserver_request); request->database_config_user()->setActiveWorkspace (destination); workspace_set_urls (webserver_request, urls); workspace_set_widths (webserver_request, widths); workspace_set_heights (webserver_request, heights); workspace_set_entire_width (webserver_request, entire_width); // Restore current active desktop. request->database_config_user()->setActiveWorkspace (active_desktop); }
void demo_create_sample_workbenches (void * webserver_request) { Webserver_Request * request = (Webserver_Request *) webserver_request; map <int, string> urls; map <int, string> widths; for (int i = 0; i < 15; i++) { string url; string width; if (i == 0) { url = editusfm_index_url (); width = "45%"; } if (i == 1) { url = resource_index_url (); width = "45%"; } urls [i] = url; widths [i] = width; } map <int, string> row_heights = { make_pair (0, "90%"), make_pair (1, ""), make_pair (2, "") }; request->database_config_user()->setActiveWorkbench ("USFM"); workbench_set_urls (request, urls); workbench_set_widths (request, widths); workbench_set_heights (request, row_heights); urls[0] = edit_index_url (); urls[1] = resource_index_url (); request->database_config_user()->setActiveWorkbench (demo_workbench ()); workbench_set_urls (request, urls); workbench_set_widths (request, widths); workbench_set_heights (request, row_heights); }
// Returns whether the interface is supposed to be in basic mode. // When the mode was flipped, this used to expire after some hours. // But there may be people working on a tablet, // who would want to permanently switch to advanced mode, without this mode to expire. // Therefore the mode flip switch is now permanent, till flipped again. bool config_logic_basic_mode (void * webserver_request) { Webserver_Request * request = (Webserver_Request *) webserver_request; // When this is a touch-enabled device, the basic mode will be on. bool basic_mode = request->session_logic ()->touchEnabled (); // Check whether to flip basic <> advanced mode. if (request->database_config_user ()->getFlipInterfaceMode ()) { basic_mode = !basic_mode; } return basic_mode; }
// This orders the workspacees. // It takes the order as in array $workspacees. void workspace_reorder (void * webserver_request, const vector <string> & workspacees) { // The order of the workspacees is taken from the URLs. // Widths and heights are not considered for the order. Webserver_Request * request = (Webserver_Request *) webserver_request; // Retrieve the old order of the workspacees, plus their details. string rawvalue = request->database_config_user()->getWorkspaceURLs (); vector <string> oldlines = filter_string_explode (rawvalue, '\n'); // Create vector with the sorted workspace definitions. vector <string> newlines; for (auto & workspace : workspacees) { for (auto & line : oldlines) { if (line.find (workspace + "_") == 0) { newlines.push_back (line); line.clear (); } } } // Add any extra ones. for (auto & line : oldlines) { if (!line.empty ()) { newlines.push_back (line); } } // Save everything. rawvalue = filter_string_implode (newlines, "\n"); request->database_config_user()->setWorkspaceURLs (rawvalue); // Schedule for sending to Cloud. workspace_cache_for_cloud (request, true, false, false); }
void config_logic_swipe_enabled (void * webserver_request, string & script) { Webserver_Request * request = (Webserver_Request *) webserver_request; string true_false = "false"; if (request->session_logic ()->touchEnabled ()) { if (request->database_config_user ()->getSwipeActionsAvailable ()) { true_false = "true"; } } script.append ("\n"); script.append ("var swipe_operations = "); script.append (true_false); script.append (";"); }
string search_replace2 (void * webserver_request) { // Build the advanced replace page. Webserver_Request * request = (Webserver_Request *) webserver_request; string bible = request->database_config_user()->getBible (); string page; Assets_Header header = Assets_Header (translate("Replace"), request); 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", "replace2"); page += Assets_Page::footer (); return page; }
string index_index (void * webserver_request) { Webserver_Request * request = (Webserver_Request *) webserver_request; Assets_Header header = Assets_Header ("Bibledit", webserver_request); if (config_logic_demo_enabled ()) { // The demo, when there's no active menu, forwards to a the active workbench. if (request->query.empty ()) { header.refresh (5, "/" + workbench_index_url ()); } } // Mode toggle: basic <> advanced. string mode = request->query ["mode"]; if (!mode.empty ()) { int flip = false; if (mode == "basic") { if (!request->session_logic ()->touchEnabled ()) { flip = true; } } if (mode == "advanced") { if (request->session_logic ()->touchEnabled ()) { flip = true; } } request->database_config_user ()->setFlipInterfaceMode (flip); } // Normally a page does not show the expanded main menu. // This is to save space on the screen. // But the home page of Bibledit show the extended main menu. if (request->query.count ("item") == 0) { request->query ["item"] = "main"; } string page = header.run (); Assets_View view; page += view.render ("index", "index"); page += Assets_Page::footer (); return page; }
// Returns the names of the available workspacees. vector <string> workspace_get_names (void * webserver_request) { Webserver_Request * request = (Webserver_Request *) webserver_request; vector <string> workspacees; // The names and the order of the workspacees is taken from the URLs. string rawvalue = request->database_config_user()->getWorkspaceURLs (); vector <string> lines = filter_string_explode (rawvalue, '\n'); for (auto & line : lines) { vector <string> bits = filter_string_explode (line, '_'); if (bits.size() == 3) { if (find (workspacees.begin(), workspacees.end(), bits[0]) == workspacees.end()) { workspacees.push_back (bits[0]); } } } if (workspacees.empty ()) workspacees.push_back (workspace_get_active_name (request)); return workspacees; }
void workspace_set_values (void * webserver_request, int selector, const map <int, string> & values) { // Store values locally, and for a client, store them also for sending to the server. Webserver_Request * request = (Webserver_Request *) webserver_request; string workspace = workspace_get_active_name (request); string rawvalue; if (selector == URLS) rawvalue = request->database_config_user()->getWorkspaceURLs (); if (selector == WIDTHS) rawvalue = request->database_config_user()->getWorkspaceWidths (); if (selector == HEIGHTS) rawvalue = request->database_config_user()->getWorkspaceHeights (); if (selector == ENTIREWIDTH) rawvalue = request->database_config_user()->getEntireWorkspaceWidths (); vector <string> currentlines = filter_string_explode (rawvalue, '\n'); vector <string> newlines; for (auto & line : currentlines) { if (line.find (workspace + "_") != 0) { newlines.push_back (line); } } for (auto & element : values) { string line = workspace + "_" + convert_to_string (element.first) + "_" + element.second; newlines.push_back (line); } rawvalue = filter_string_implode (newlines, "\n"); if (selector == URLS) { request->database_config_user()->setWorkspaceURLs (rawvalue); workspace_cache_for_cloud (request, true, false, false); } if (selector == WIDTHS) { request->database_config_user()->setWorkspaceWidths (rawvalue); workspace_cache_for_cloud (request, false, true, false); } if (selector == HEIGHTS) { request->database_config_user()->setWorkspaceHeights (rawvalue); workspace_cache_for_cloud (request, false, false, true); } if (selector == ENTIREWIDTH) { request->database_config_user()->setEntireWorkspaceWidths (rawvalue); workspace_cache_for_cloud (request, false, true, false); } }
void workspace_delete (void * webserver_request, string workspace) { Webserver_Request * request = (Webserver_Request *) webserver_request; string rawvalue; vector <string> currentlines; vector <string> newlines; rawvalue = request->database_config_user()->getWorkspaceURLs (); currentlines = filter_string_explode (rawvalue, '\n'); newlines.clear (); for (auto & line : currentlines) { if (line.find (workspace + "_") != 0) newlines.push_back (line); } rawvalue = filter_string_implode (newlines, "\n"); request->database_config_user()->setWorkspaceURLs (rawvalue); rawvalue = request->database_config_user()->getWorkspaceWidths (); currentlines = filter_string_explode (rawvalue, '\n'); newlines.clear (); for (auto & line : currentlines) { if (line.find (workspace + "_") != 0) newlines.push_back (line); } rawvalue = filter_string_implode (newlines, "\n"); request->database_config_user()->setWorkspaceWidths (rawvalue); rawvalue = request->database_config_user()->getWorkspaceHeights (); currentlines = filter_string_explode (rawvalue, '\n'); newlines.clear (); for (auto & line : currentlines) { if (line.find (workspace + "_") != 0) newlines.push_back (line); } rawvalue = filter_string_implode (newlines, "\n"); request->database_config_user()->setWorkspaceHeights (rawvalue); request->database_config_user()->setActiveWorkspace (""); // For a client, store the setting for sending to the server. workspace_cache_for_cloud (request, true, true, true); }
string xrefs_next (void * webserver_request) { Webserver_Request * request = (Webserver_Request *) webserver_request; string bible = request->database_config_user()->getTargetXrefBible (); int currentBook = Ipc_Focus::getBook (webserver_request); int currentChapter = Ipc_Focus::getChapter (webserver_request); Passage currentPassage = Passage ("", currentBook, currentChapter, "1"); int currentLocation = filter_passage_to_integer (currentPassage); vector <int> books = request->database_bibles()->getBooks (bible); for (auto book : books) { vector <int> chapters = request->database_bibles()->getChapters (bible, book); for (auto chapter : chapters) { if (chapter == 0) continue; Passage passage = Passage ("", book, chapter, "1"); int location = filter_passage_to_integer (passage); if (location > currentLocation) { string usfm = request->database_bibles()->getChapter (bible, book, chapter); auto xrefs = usfm_extract_notes (usfm, {"x"}); if (xrefs.empty ()) { Ipc_Focus::set (webserver_request, book, chapter, 1); redirect_browser (request, xrefs_index_url ()); return ""; } } } } redirect_browser (request, xrefs_index_url ()); return ""; }
// handleEmailNew - handles an email received from $from with subject $subject and body $body. // Returns true if the mail was processed, else false. // The email is considered to have been processed if it created a new Consultation Note. bool Notes_Logic::handleEmailNew (string from, string subject, string body) { // Webserver request. Webserver_Request * request = (Webserver_Request *) webserver_request; // Store the original subject. string originalSubject = subject; // Check that the subject indicates that a new consultation note is to be created. size_t pos = unicode_string_casefold (subject).find ("new note"); if (pos == string::npos) return false; // There is a new note. Remove that bit from the subject. if (pos > 0) subject.erase (0, pos + 8); // Clean the subject line. subject = filter_string_trim (subject); subject = filter_string_str_replace (".", " ", subject); subject = filter_string_str_replace (":", " ", subject); subject = filter_string_str_replace (" ", " ", subject); subject = filter_string_str_replace (" ", " ", subject); // Check that the from address of the email belongs to an existing user. from = filter_string_extract_email (from); if (!request->database_users()->emailExists (from)) return false; string username = request->database_users()->getEmailToUser (from); // Extract book, chapter, verse, and note summary from the subject int book = -1; int chapter = -1; int verse = -1; string summary; vector <string> subjectlines = filter_string_explode (subject, ' '); if (!subjectlines.empty()) { book = filter_passage_interpret_book (subjectlines[0]); subjectlines.erase (subjectlines.begin()); } if (!subjectlines.empty()) { chapter = convert_to_int (subjectlines[0]); subjectlines.erase (subjectlines.begin()); } if (!subjectlines.empty()) { verse = convert_to_int (subjectlines[0]); subjectlines.erase (subjectlines.begin()); } summary = filter_string_implode (subjectlines, " "); // Check book, chapter, verse, and summary. Give feedback if there's anything wrong. string noteCheck; if (book <= 0) noteCheck.append (translate("Unknown book")); if (chapter < 0) { noteCheck.append (" "); noteCheck.append (translate("Unknown chapter")); } if (verse < 0) { noteCheck.append (" "); noteCheck.append (translate("Unknown verse")); } if (summary.empty ()) { noteCheck.append (" "); noteCheck.append (translate("Unknown summary")); } // Mail user if the note could not be posted. Database_Mail database_mail = Database_Mail (webserver_request); if (noteCheck != "") { subject = translate("Your new note could not be posted"); database_mail.send (username, subject + ": " + originalSubject, noteCheck); return false; } // Clean the email's body. body = filter_string_extract_body (body); // Post the note. string sessionuser = request->session_logic()->currentUser (); request->session_logic()->setUsername (username); Database_Notes database_notes = Database_Notes(webserver_request); string bible = request->database_config_user()->getBible (); int identifier = database_notes.storeNewNote (bible, book, chapter, verse, summary, body, false); handlerNewNote (identifier); request->session_logic()->setUsername (sessionuser); // Mail confirmation to the username. if (request->database_config_user()->getUserNotifyMeOfMyPosts (username)) { subject = translate("Your new note was posted"); database_mail.send (username, subject + ": " + originalSubject, body); } // Log operation. Database_Logs::log ("New note posted : " + body); // Job done. return true; }
// Cleans and resets the data in the Bibledit installation. void demo_clean_data () { Database_Logs::log ("Cleaning up the demo data"); Webserver_Request request; // Set user to the demo credentials (admin) as this is the user who is always logged-in in a demo installation. request.session_logic ()->setUsername (session_admin_credentials ()); // Delete empty stylesheet that may have been there. request.database_styles()->revokeWriteAccess ("", styles_logic_standard_sheet ()); request.database_styles()->deleteSheet (""); styles_sheets_create_all (); // Set the export stylesheet to "Standard" for all Bibles and the admin. vector <string> bibles = request.database_bibles()->getBibles (); for (auto & bible : bibles) { Database_Config_Bible::setExportStylesheet (bible, styles_logic_standard_sheet ()); } request.database_config_user()->setStylesheet (styles_logic_standard_sheet ()); // Set the site language to "Default" Database_Config_General::setSiteLanguage (""); // Ensure the default users are there. map <string, int> users = { make_pair ("guest", Filter_Roles::guest ()), make_pair ("member", Filter_Roles::member ()), make_pair ("consultant", Filter_Roles::consultant ()), make_pair ("translator", Filter_Roles::translator ()), make_pair ("manager", Filter_Roles::manager ()), make_pair (session_admin_credentials (), Filter_Roles::admin ()) }; for (auto & element : users) { if (!request.database_users ()->usernameExists (element.first)) { request.database_users ()->addNewUser(element.first, element.first, element.second, ""); } request.database_users ()->updateUserLevel (element.first, element.second); } // Create / update sample Bible. demo_create_sample_bible (); // Create sample notes. demo_create_sample_notes (&request); // Create samples for the workbenches. demo_create_sample_workbenches (&request); // Set navigator to John 3:16. Ipc_Focus::set (&request, 43, 3, 16); // Set and/or trim resources to display. // Too many resources crash the demo: Limit the amount. vector <string> resources = request.database_config_user()->getActiveResources (); bool reset_resources = false; if (resources.size () > 25) reset_resources = true; vector <string> defaults = demo_logic_default_resources (); for (auto & name : defaults) { if (!in_array (name, resources)) reset_resources = true; } if (reset_resources) { resources = demo_logic_default_resources (); request.database_config_user()->setActiveResources (resources); } // No flipped basic <> advanded mode. request.database_config_user ()->setFlipInterfaceMode (false); }
// This handles notifications for the users // identifier: the note that is being handled. // notification: the type of action on the consultation note. void Notes_Logic::notifyUsers (int identifier, int notification) { // Take no action in client mode. if (client_logic_client_enabled ()) return; // Data objects. Webserver_Request * request = (Webserver_Request *) webserver_request; Database_Notes database_notes (webserver_request); // This note's Bible. string bible = database_notes.getBible (identifier); // Subscription and assignment not to be used for notes marked for deletion, // because marking notes for deletion is nearly the same as deleting them straightaway. if (notification != notifyMarkNoteForDeletion) { // Whether current user gets subscribed to the note. if (request->database_config_user ()->getSubscribeToConsultationNotesEditedByMe ()) { database_notes.subscribe (identifier); } // Users to get subscribed to the note, or to whom the note is to be assigned. vector <string> users = request->database_users ()->getUsers (); for (const string & user : users) { if (access_bible_read (webserver_request, bible, user)) { if (request->database_config_user ()->getNotifyUserOfAnyConsultationNotesEdits (user)) { database_notes.subscribeUser (identifier, user); } if (request->database_config_user ()->getUserAssignedToConsultationNotesChanges (user)) { database_notes.assignUser (identifier, user); } } } } // The recipients who receive a notification by email. vector <string> recipients; // Subscribers who receive email. vector <string> subscribers = database_notes.getSubscribers (identifier); for (const string & subscriber : subscribers) { if (request->database_config_user ()->getUserSubscribedConsultationNoteNotification (subscriber)) { recipients.push_back (subscriber); } } // Assignees who receive email. vector <string> assignees = database_notes.getAssignees (identifier); for (const string & assignee : assignees) { if (request->database_config_user ()->getUserAssignedConsultationNoteNotification (assignee)) { recipients.push_back (assignee); } } // In case the consultation note is deleted or marked for deletion, // notify only the users with this specific notification set. if ((notification == notifyNoteDelete) || (notification == notifyMarkNoteForDeletion)) { recipients.clear (); vector <string> users = request->database_users ()->getUsers (); for (const auto & user : users) { if (request->database_config_user ()->getUserDeletedConsultationNoteNotification (user)) { if (access_bible_read (webserver_request, bible, user)) { recipients.push_back (user); } } } } // Remove duplicates from the recipients. set <string> unique (recipients.begin (), recipients.end ()); recipients.assign (unique.begin (), unique.end()); // Deal with suppressing mail to the user when he made the update himself. string username = request->session_logic ()->currentUser (); if (request->database_config_user ()->getUserSuppressMailFromYourUpdatesNotes (username)) { recipients.erase (remove (recipients.begin(), recipients.end(), username), recipients.end()); } // Generate the label prefixed to the subject line of the email. string label = translate("General"); switch (notification) { case notifyNoteNew : label = translate("New"); break; case notifyNoteComment : label = translate("Comment"); break; case notifyNoteDelete : label = translate("Deleted"); break; case notifyMarkNoteForDeletion : label = translate("Marked for deletion"); break; } // Optional postponing sending email. bool postpone = false; if (notification == notifyNoteNew) { if (request->database_config_user ()->getPostponeNewNotesMails ()) { postpone = true; } } // Send mail to all recipients. emailUsers (identifier, label, recipients, postpone); }
string resource_get (void * webserver_request) { Webserver_Request * request = (Webserver_Request *) webserver_request; vector <string> bits; string s_resource = request->query["resource"]; string s_book = request->query["book"]; string s_chapter = request->query["chapter"]; string s_verse = request->query["verse"]; if (!s_resource.empty () && !s_book.empty () && !s_chapter.empty () && !s_verse.empty ()) { unsigned int resource = convert_to_int (s_resource); int book = convert_to_int (s_book); int chapter = convert_to_int (s_chapter); int verse = convert_to_int (s_verse); // In JavaScript the resource identifier starts at 1 instead of at 0. resource--; vector <string> resources = request->database_config_user()->getActiveResources (); if (resource <= resources.size ()) { s_resource = resources [resource]; // Handle a divider. if (resource_logic_is_divider (s_resource)) return resource_logic_get_divider (s_resource); string bible = request->database_config_user ()->getBible (); string versification = Database_Config_Bible::getVersificationSystem (bible); Database_Versifications database_versifications; vector <int> chapters = database_versifications.getChapters (versification, book); // Whether to add extra verse numbers, for clarity in case of viewing more than one verse. bool add_verse_numbers = false; int context_before = request->database_config_user ()->getResourceVersesBefore (); if (context_before) add_verse_numbers = true; int context_after = request->database_config_user ()->getResourceVersesAfter (); if (context_after) add_verse_numbers = true; // Context before the focused verse. vector <int> chapters_before; vector <int> verses_before; if (context_before > 0) { for (int ch = chapter - 1; ch <= chapter; ch++) { if (in_array (ch, chapters)) { vector <int> verses = database_versifications.getVerses (versification, book, ch); for (size_t vs = 0; vs < verses.size (); vs++) { int vs2 = verses [vs]; if ((ch < chapter) || (vs2 < verse)) { if (vs2 > 0) { chapters_before.push_back (ch); verses_before.push_back (verses[vs]); } } } } } while ((int)chapters_before.size () > context_before) { chapters_before.erase (chapters_before.begin ()); verses_before.erase (verses_before.begin ()); } } for (unsigned int i = 0; i < chapters_before.size (); i++) { bits.push_back (resource_logic_get_html (request, s_resource, book, chapters_before[i], verses_before[i], add_verse_numbers)); } // Focused verse. bits.push_back (resource_logic_get_html (request, s_resource, book, chapter, verse, add_verse_numbers)); // Context after the focused verse. vector <int> chapters_after; vector <int> verses_after; if (context_after > 0) { for (int ch = chapter; ch <= chapter + 1; ch++) { if (in_array (ch, chapters)) { vector <int> verses = database_versifications.getVerses (versification, book, ch); for (size_t vs = 0; vs < verses.size (); vs++) { int vs2 = verses [vs]; if ((ch > chapter) || (vs2 > verse)) { if (vs2 > 0) { chapters_after.push_back (ch); verses_after.push_back (verses[vs]); } } } } } while ((int)chapters_after.size () > context_after) { chapters_after.pop_back (); verses_after.pop_back (); } } for (unsigned int i = 0; i < chapters_after.size (); i++) { bits.push_back (resource_logic_get_html (request, s_resource, book, chapters_after[i], verses_after[i], add_verse_numbers)); } } } string page = filter_string_implode (bits, ""); // <br> return page; }
string editone_load (void * webserver_request) { Webserver_Request * request = (Webserver_Request *) webserver_request; string bible = request->query ["bible"]; int book = convert_to_int (request->query ["book"]); int chapter = convert_to_int (request->query ["chapter"]); int verse = convert_to_int (request->query ["verse"]); string part = request->query ["part"]; string stylesheet = request->database_config_user()->getStylesheet (); string chapter_usfm = request->database_bibles()->getChapter (bible, book, chapter); string focused_verse_usfm = usfm_get_verse_text (chapter_usfm, verse); vector <int> verses = usfm_get_verse_numbers (chapter_usfm); int highest_verse = 0; if (!verses.empty ()) highest_verse = verses.back (); string prefix_usfm = usfm_get_verse_range_text (chapter_usfm, 0, verse - 1, focused_verse_usfm); string suffix_usfm = usfm_get_verse_range_text (chapter_usfm, verse + 1, highest_verse, focused_verse_usfm); // Last paragraph style of the prefix: To be used for the starting visual style for the focused verse. string prefix_paragraph_style; string prefix_html; if (!prefix_usfm.empty ()) { Editor_Usfm2Html editor_usfm2html; editor_usfm2html.load (prefix_usfm); editor_usfm2html.stylesheet (stylesheet); editor_usfm2html.run (); prefix_html = editor_usfm2html.get (); prefix_paragraph_style = editor_usfm2html.currentParagraphStyle; } // Last paragraph style of the focused verse: For the starting visual style of the suffix. string focused_verse_paragraph_style; string focused_verse_html; if (!focused_verse_usfm.empty ()) { Editor_Usfm2Html editor_usfm2html; editor_usfm2html.load (focused_verse_usfm); editor_usfm2html.stylesheet (stylesheet); editor_usfm2html.run (); focused_verse_html = editor_usfm2html.get (); focused_verse_html = editone_load_remove_id_notes (focused_verse_html); focused_verse_paragraph_style = editor_usfm2html.currentParagraphStyle; } // If the first paragraph of the focused verse does not have a paragraph style applied, // apply the last paragraph style of the prefix to the first paragraph of the focused verse. // For example, html like this: // <p><span class="v">7</span><span> </span><span>For Yahweh knows the way of the righteous,</span></p><p class="q2"><span>but the way of the wicked shall perish.</span></p> // ... becomes like this: // <p class="q1"><span class="v">7</span><span /><span>For Yahweh knows the way of the righteous,</span></p><p class="q2"><span>but the way of the wicked shall perish.</span></p> string focused_verse_applied_style; if (!focused_verse_html.empty ()) { if (!prefix_paragraph_style.empty ()) { xml_document document; document.load_string (focused_verse_html.c_str(), parse_ws_pcdata_single); xml_node p_node = document.first_child (); string p_style = p_node.attribute ("class").value (); if (p_style.empty ()) { p_node.append_attribute ("class") = prefix_paragraph_style.c_str (); // Send the applied paragraph style to the browser, // for later use when it saves the modified verse text. focused_verse_applied_style = prefix_paragraph_style; } stringstream output; document.print (output, "", format_raw); focused_verse_html = output.str (); } } string suffix_html; if (!suffix_usfm.empty ()) { Editor_Usfm2Html editor_usfm2html; editor_usfm2html.load (suffix_usfm); editor_usfm2html.stylesheet (stylesheet); editor_usfm2html.run (); suffix_html = editor_usfm2html.get (); suffix_html = editone_load_remove_id_notes (suffix_html); } // If the first paragraph of the suffix does not have a paragraph style applied, // apply the last paragraph style of the focused verse to the first paragraph of the suffix. // For example, html like this: // <p><span class="v">7</span><span> </span><span>For Yahweh knows the way of the righteous,</span></p><p class="q2"><span>but the way of the wicked shall perish.</span></p> // ... becomes like this: // <p class="q1"><span class="v">7</span><span /><span>For Yahweh knows the way of the righteous,</span></p><p class="q2"><span>but the way of the wicked shall perish.</span></p> if (!suffix_html.empty ()) { if (!focused_verse_paragraph_style.empty ()) { xml_document document; document.load_string (suffix_html.c_str(), parse_ws_pcdata_single); xml_node p_node = document.first_child (); string p_style = p_node.attribute ("class").value (); if (p_style.empty ()) { p_node.append_attribute ("class") = focused_verse_paragraph_style.c_str (); } stringstream output; document.print (output, "", format_raw); suffix_html = output.str (); } } string data; data.append (focused_verse_applied_style); data.append ("#_be_#"); data.append (prefix_html); data.append ("#_be_#"); data.append (focused_verse_html); data.append ("#_be_#"); data.append (suffix_html); string user = request->session_logic ()->currentUser (); bool write = access_bible_book_write (webserver_request, user, bible, book); data = Checksum_Logic::send (data, write); return data; }
string sync_changes (void * webserver_request) { Webserver_Request * request = (Webserver_Request *) webserver_request; Sync_Logic sync_logic = Sync_Logic (webserver_request); Database_Modifications database_modifications; // Check on the credentials. if (!sync_logic.credentials_okay ()) return ""; // Bail out if the change notifications are not now available to clients. if (!config_globals_change_notifications_available) { request->response_code = 503; return ""; } // Client makes a prioritized server call: Record the client's IP address. sync_logic.prioritized_ip_address_record (); // Get the relevant parameters the client may have POSTed to us, the server. string user = hex2bin (request->post ["u"]); int action = convert_to_int (request->post ["a"]); int id = convert_to_int (request->post ["i"]); switch (action) { case Sync_Logic::changes_delete_modification: { // The server deletes the change notification. database_modifications.deleteNotification (id); Database_Logs::log ("Client deletes change notification from server: " + convert_to_string (id), Filter_Roles::translator ()); request->database_config_user ()->setChangeNotificationsChecksum (""); return ""; } case Sync_Logic::changes_get_checksum: { // The server responds with the possibly cached total checksum for the user's change notifications. string checksum = request->database_config_user ()->getChangeNotificationsChecksum (); if (checksum.empty ()) { checksum = Sync_Logic::changes_checksum (user); request->database_config_user ()->setChangeNotificationsChecksum (checksum); } return checksum; } case Sync_Logic::changes_get_identifiers: { // The server responds with the identifiers of all the user's change notifications. vector <int> ids = database_modifications.getNotificationIdentifiers (user, false); string response; for (auto & id : ids) { if (!response.empty ()) response.append ("\n"); response.append (convert_to_string (id)); } return response; } case Sync_Logic::changes_get_modification: { // The server responds with the relevant data of the requested modification. vector <string> lines; // category lines.push_back (database_modifications.getNotificationCategory (id)); // bible lines.push_back (database_modifications.getNotificationBible (id)); // book // chapter // verse Passage passage = database_modifications.getNotificationPassage (id); lines.push_back (convert_to_string (passage.book)); lines.push_back (convert_to_string (passage.chapter)); lines.push_back (passage.verse); // oldtext lines.push_back (database_modifications.getNotificationOldText (id)); // modification lines.push_back (database_modifications.getNotificationModification (id)); // newtext lines.push_back (database_modifications.getNotificationNewText (id)); // Result. return filter_string_implode (lines, "\n"); } } // Bad request. // Delay a while to obstruct a flood of bad requests. this_thread::sleep_for (chrono::seconds (1)); request->response_code = 400; return ""; }