// swad_timeline_note.c: social timeline notes /* SWAD (Shared Workspace At a Distance), is a web platform developed at the University of Granada (Spain), and used to support university teaching. This file is part of SWAD core. Copyright (C) 1999-2024 Antonio Caņas Vargas This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General 3 License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ /*****************************************************************************/ /*********************************** Headers *********************************/ /*****************************************************************************/ #define _GNU_SOURCE // For asprintf #include // For PATH_MAX #include // For asprintf #include // For free #include "swad_action_list.h" #include "swad_alert.h" #include "swad_box.h" #include "swad_browser_database.h" #include "swad_call_for_exam.h" #include "swad_course.h" #include "swad_error.h" #include "swad_forum.h" #include "swad_global.h" #include "swad_hierarchy.h" #include "swad_notice.h" #include "swad_notification_database.h" #include "swad_parameter_code.h" #include "swad_photo.h" #include "swad_profile.h" #include "swad_timeline.h" #include "swad_timeline_database.h" #include "swad_timeline_form.h" #include "swad_timeline_publication.h" /*****************************************************************************/ /************** External global variables from others modules ****************/ /*****************************************************************************/ extern struct Globals Gbl; /*****************************************************************************/ /**************************** Private constants ******************************/ /*****************************************************************************/ static const TmlNot_Type_t TmlNot_NoteType[Brw_NUM_TYPES_FILE_BROWSER] = { [Brw_ADMI_DOC_INS] = TmlNot_INS_DOC_PUB_FILE, [Brw_ADMI_SHR_INS] = TmlNot_INS_SHA_PUB_FILE, [Brw_ADMI_DOC_CTR] = TmlNot_CTR_DOC_PUB_FILE, [Brw_ADMI_SHR_CTR] = TmlNot_CTR_SHA_PUB_FILE, [Brw_ADMI_DOC_DEG] = TmlNot_DEG_DOC_PUB_FILE, [Brw_ADMI_SHR_DEG] = TmlNot_DEG_SHA_PUB_FILE, [Brw_ADMI_DOC_CRS] = TmlNot_CRS_DOC_PUB_FILE, [Brw_ADMI_SHR_CRS] = TmlNot_CRS_SHA_PUB_FILE, }; /*****************************************************************************/ /***************************** Private prototypes ****************************/ /*****************************************************************************/ static void TmlNot_WriteTopMessage (Tml_TopMessage_t TopMessage,long PublisherCod); static void TmlNot_WriteNote (const struct Tml_Timeline *Timeline, const struct TmlNot_Note *Not); static void TmlNot_WriteAuthorTimeAndContent (const struct TmlNot_Note *Not, const struct Usr_Data *UsrDat); static void TmlNot_WriteContent (const struct TmlNot_Note *Not); static void TmlNot_GetAndWriteNoPost (const struct TmlNot_Note *Not); static void TmlNot_GetLocationInHierarchy (const struct TmlNot_Note *Not, struct Hie_Node Hie[Hie_NUM_LEVELS], struct For_Forum *Forum, char ForumName[For_MAX_BYTES_FORUM_NAME + 1]); static void TmlNot_WriteLocationInHierarchy (const struct TmlNot_Note *Not, struct Hie_Node Hie[Hie_NUM_LEVELS], const char ForumName[For_MAX_BYTES_FORUM_NAME + 1]); static void TmlNot_PutFormGoToAction (const struct TmlNot_Note *Not, const struct For_Forums *Forums); static void TmlNot_WriteButtonsAndComms (const struct Tml_Timeline *Timeline, const struct TmlNot_Note *Not, const struct Usr_Data *UsrDat); static void TmlNot_WriteButtonToAddAComm (const struct Tml_Timeline *Timeline, const struct TmlNot_Note *Not, const char IdNewComm[Frm_MAX_BYTES_ID + 1]); static void TmlNot_WriteFavShaRemAndComms (const struct Tml_Timeline *Timeline, const struct TmlNot_Note *Not, const struct Usr_Data *UsrDat); static void TmlNot_WriteFavShaRem (const struct Tml_Timeline *Timeline, const struct TmlNot_Note *Not, const struct Usr_Data *UsrDat); static void TmlNot_PutFormToRemoveNote (const struct Tml_Timeline *Timeline, long NotCod); static void TmlNot_ReqRemNote (struct Tml_Timeline *Timeline); static void TmlNot_PutParsRemoveNote (void *Timeline); static void TmlNot_RemoveNote (void); static void TmlNot_RemoveNoteMediaAndDBEntries (struct TmlNot_Note *Not); static void TmlNot_GetNoteDataFromRow (MYSQL_RES *mysql_res, struct TmlNot_Note *Not); static TmlNot_Type_t TmlNot_GetNoteTypeFromStr (const char *Str); static void TmlNot_ResetNote (struct TmlNot_Note *Not); /*****************************************************************************/ /****************** Show highlighted note above timeline *********************/ /*****************************************************************************/ void TmlNot_ShowHighlightedNote (struct Tml_Timeline *Timeline, struct TmlNot_Note *Not) { struct Usr_Data PublisherDat; Ntf_NotifyEvent_t NotifyEvent; static const Tml_TopMessage_t TopMessages[Ntf_NUM_NOTIFY_EVENTS] = { [Ntf_EVENT_TML_COMMENT] = Tml_TOP_MESSAGE_COMMENTED, [Ntf_EVENT_TML_FAV ] = Tml_TOP_MESSAGE_FAVED, [Ntf_EVENT_TML_SHARE ] = Tml_TOP_MESSAGE_SHARED, [Ntf_EVENT_TML_MENTION] = Tml_TOP_MESSAGE_MENTIONED, }; /***** Get other parameters *****/ /* Get the publisher who did the action (publishing, commenting, faving, sharing, mentioning) */ Usr_GetParOtherUsrCodEncrypted (&PublisherDat); /* Get what he/she did */ NotifyEvent = Ntf_GetParNotifyEvent (); /***** Get data of the note *****/ TmlNot_GetNoteDataByCod (Not); /***** Show the note highlighted *****/ /* Begin box */ Box_BoxBegin (NULL,NULL,NULL,NULL,Box_CLOSABLE); /* Begin container */ HTM_DIV_Begin ("class=\"Tml_WIDTH Tml_NEW_PUB_%s\"",The_GetSuffix ()); /* Check and write note with top message */ TmlNot_CheckAndWriteNoteWithTopMsg (Timeline,Not, TopMessages[NotifyEvent], PublisherDat.UsrCod); /* End container */ HTM_DIV_End (); /* End box */ Box_BoxEnd (); HTM_BR (); } /*****************************************************************************/ /****************** Check and write note with top message ********************/ /*****************************************************************************/ void TmlNot_CheckAndWriteNoteWithTopMsg (const struct Tml_Timeline *Timeline, const struct TmlNot_Note *Not, Tml_TopMessage_t TopMessage, long PublisherCod) // Who did the action (publication, commenting, faving, sharing, mentioning) { /* ___________________________________________ | | \ | Top message: | > top message |___________________________________________| / | _____ | | | \ \ || | | Author's name | Date-time | | | ||Auth.| |______________________|___________| | | ||photo| | | | author's | ||_____| | | > name, time | | | Note | | and content | | | content | | | | | | | | | |__________________________________| / | | | | | | \ | | | Favs | Shared |Remove| | > note | |_____________|_____________|______| | | |________| | | | | | List | | buttons | | Comment| of | > and | | icon | comments | | comments | |________|__________________________________| | | | | | | | Form to write new comment | | | |__________________________________| / / */ /***** Trivial check: codes *****/ if (Not->NotCod <= 0 || Not->UsrCod <= 0 || Not->Type == TmlNot_UNKNOWN) { Ale_ShowAlert (Ale_ERROR,"Error in note."); return; } /***** Write sharer/commenter if distinct to author *****/ if (TopMessage != Tml_TOP_MESSAGE_NONE) TmlNot_WriteTopMessage (TopMessage,PublisherCod); /***** Write note *****/ TmlNot_WriteNote (Timeline,Not); } /*****************************************************************************/ /*************** Write sharer/commenter if distinct to author ****************/ /*****************************************************************************/ static void TmlNot_WriteTopMessage (Tml_TopMessage_t TopMessage,long PublisherCod) { extern const char *Txt_TIMELINE_NOTE_TOP_MESSAGES[Tml_NUM_TOP_MESSAGES]; struct Usr_Data PublisherDat; /***** Initialize structure with user's data *****/ Usr_UsrDataConstructor (&PublisherDat); /***** Get user's data *****/ PublisherDat.UsrCod = PublisherCod; if (Usr_ChkUsrCodAndGetAllUsrDataFromUsrCod (&PublisherDat, // Really we only need EncryptedUsrCod and FullName Usr_DONT_GET_PREFS, Usr_DONT_GET_ROLE_IN_CRS)) { /***** Begin container *****/ HTM_DIV_Begin ("class=\"Tml_TOP_CONT Tml_TOP_PUBLISHER Tml_WIDTH\""); /***** Show publisher's name inside form to go to user's public profile *****/ TmlNot_WriteAuthorName (&PublisherDat,"BT_LINK"); /***** Show action made *****/ HTM_TxtF (" %s:",Txt_TIMELINE_NOTE_TOP_MESSAGES[TopMessage]); /***** End container *****/ HTM_DIV_End (); } /***** Free memory used for user's data *****/ Usr_UsrDataDestructor (&PublisherDat); } /*****************************************************************************/ /********************************* Show note *********************************/ /*****************************************************************************/ static void TmlNot_WriteNote (const struct Tml_Timeline *Timeline, const struct TmlNot_Note *Not) { struct Usr_Data UsrDat; // Author of the note /***** Get author data *****/ Usr_UsrDataConstructor (&UsrDat); UsrDat.UsrCod = Not->UsrCod; Usr_ChkUsrCodAndGetAllUsrDataFromUsrCod (&UsrDat, Usr_DONT_GET_PREFS, Usr_DONT_GET_ROLE_IN_CRS); /***** Left top: author's photo *****/ TmlNot_ShowAuthorPhoto (&UsrDat); /***** Right top: author's name, time, and content *****/ TmlNot_WriteAuthorTimeAndContent (Not,&UsrDat); /***** Bottom: buttons and comments *****/ TmlNot_WriteButtonsAndComms (Timeline,Not,&UsrDat); /***** Free memory used for author's data *****/ Usr_UsrDataDestructor (&UsrDat); } /*****************************************************************************/ /*********************** Show photo of author of a note **********************/ /*****************************************************************************/ void TmlNot_ShowAuthorPhoto (struct Usr_Data *UsrDat) { static const char *ClassPhoto[PhoSha_NUM_SHAPES] = { [PhoSha_SHAPE_CIRCLE ] = "PHOTOC45x60", [PhoSha_SHAPE_ELLIPSE ] = "PHOTOE45x60", [PhoSha_SHAPE_OVAL ] = "PHOTOO45x60", [PhoSha_SHAPE_RECTANGLE] = "PHOTOR45x60", }; /***** Begin container *****/ HTM_DIV_Begin ("class=\"Tml_LEFT_PHOTO\""); /***** Photo *****/ Pho_ShowUsrPhotoIfAllowed (UsrDat, ClassPhoto[Gbl.Prefs.PhotoShape],Pho_ZOOM); /***** End container *****/ HTM_DIV_End (); } /*****************************************************************************/ /**** Write top right part of a note: author's name, time and note content ***/ /*****************************************************************************/ static void TmlNot_WriteAuthorTimeAndContent (const struct TmlNot_Note *Not, const struct Usr_Data *UsrDat) { char *Class; /***** Begin top container *****/ HTM_DIV_Begin ("class=\"Tml_RIGHT_CONT Tml_RIGHT_WIDTH\""); /***** Write author's full name *****/ if (asprintf (&Class,"Tml_RIGHT_AUTHOR Tml_RIGHT_AUTHOR_WIDTH BT_LINK DAT_STRONG_%s BOLD", The_GetSuffix ()) < 0) Err_NotEnoughMemoryExit (); TmlNot_WriteAuthorName (UsrDat,Class); free (Class); /***** Write date and time *****/ Tml_WriteDateTime (Not->DateTimeUTC); /***** Write content of the note *****/ TmlNot_WriteContent (Not); /***** End top container *****/ HTM_DIV_End (); } /*****************************************************************************/ /*************** Write name and nickname of author of a note *****************/ /*****************************************************************************/ void TmlNot_WriteAuthorName (const struct Usr_Data *UsrDat, const char *Class) { extern const char *Txt_My_public_profile; extern const char *Txt_Another_user_s_profile; const char *Title[Usr_NUM_ME_OR_OTHER] = { [Usr_ME ] = Txt_My_public_profile, [Usr_OTHER] = Txt_Another_user_s_profile, }; /***** Show user's name inside form to go to user's public profile *****/ /* Begin form */ Frm_BeginForm (ActSeeOthPubPrf); Usr_PutParUsrCodEncrypted (UsrDat->EnUsrCod); /* Author's name */ HTM_BUTTON_Submit_Begin (Title[Usr_ItsMe (UsrDat->UsrCod)], "class=\"%s\"",Class); HTM_Txt (UsrDat->FullName); HTM_BUTTON_End (); /* End form */ Frm_EndForm (); } /*****************************************************************************/ /*********************** Get and write a note content ************************/ /*****************************************************************************/ static void TmlNot_WriteContent (const struct TmlNot_Note *Not) { if (Not->Type == TmlNot_POST) // It's a post TmlPst_GetAndWritePost (Not->Cod); else // Not a post TmlNot_GetAndWriteNoPost (Not); } /*****************************************************************************/ /***************** Get and write a note which is not a post ******************/ /*****************************************************************************/ static void TmlNot_GetAndWriteNoPost (const struct TmlNot_Note *Not) { struct Hie_Node Hie[Hie_NUM_LEVELS]; struct For_Forums Forums; char ForumName[For_MAX_BYTES_FORUM_NAME + 1]; char SummaryStr[Ntf_MAX_BYTES_SUMMARY + 1]; /***** Reset forums *****/ For_ResetForums (&Forums); /***** Get location in hierarchy *****/ if (!Not->Unavailable) TmlNot_GetLocationInHierarchy (Not,Hie,&Forums.Forum,ForumName); /***** Write note type *****/ TmlNot_PutFormGoToAction (Not,&Forums); /***** Write location in hierarchy *****/ if (!Not->Unavailable) TmlNot_WriteLocationInHierarchy (Not,Hie,ForumName); /***** Get and write note summary *****/ /* Get note summary */ TmlNot_GetNoteSummary (Not,SummaryStr); /* Write note summary */ HTM_DIV_Begin ("class=\"Tml_TXT Tml_TXT_%s\"",The_GetSuffix ()); HTM_Txt (SummaryStr); HTM_DIV_End (); } /*****************************************************************************/ /************************ Get location in hierarchy **************************/ /*****************************************************************************/ static void TmlNot_GetLocationInHierarchy (const struct TmlNot_Note *Not, struct Hie_Node Hie[Hie_NUM_LEVELS], struct For_Forum *Forum, char ForumName[For_MAX_BYTES_FORUM_NAME + 1]) { extern bool (*Hie_GetDataByCod[Hie_NUM_LEVELS]) (struct Hie_Node *Node); /***** Initialize location in hierarchy *****/ Hie[Hie_CTY].HieCod = Hie[Hie_INS].HieCod = Hie[Hie_CTR].HieCod = Hie[Hie_DEG].HieCod = Hie[Hie_CRS].HieCod = -1L; /***** Get location in hierarchy *****/ switch (Not->Type) { case TmlNot_INS_DOC_PUB_FILE: case TmlNot_INS_SHA_PUB_FILE: /* Get institution data */ Hie[Hie_INS].HieCod = Not->HieCod; Hie_GetDataByCod[Hie_INS] (&Hie[Hie_INS]); break; case TmlNot_CTR_DOC_PUB_FILE: case TmlNot_CTR_SHA_PUB_FILE: /* Get center data */ Hie[Hie_CTR].HieCod = Not->HieCod; Hie_GetDataByCod[Hie_CTR] (&Hie[Hie_CTR]); break; case TmlNot_DEG_DOC_PUB_FILE: case TmlNot_DEG_SHA_PUB_FILE: /* Get degree data */ Hie[Hie_DEG].HieCod = Not->HieCod; Hie_GetDataByCod[Hie_DEG] (&Hie[Hie_DEG]); break; case TmlNot_CRS_DOC_PUB_FILE: case TmlNot_CRS_SHA_PUB_FILE: case TmlNot_CALL_FOR_EXAM: case TmlNot_NOTICE: /* Get course data */ Hie[Hie_CRS].HieCod = Not->HieCod; Hie_GetDataByCod[Hie_CRS] (&Hie[Hie_CRS]); break; case TmlNot_FORUM_POST: /* Get forum type of the post */ For_GetThreadForumTypeAndHieCodOfAPost (Not->Cod,Forum); /* Set forum name in recipient's language */ For_SetForumName (Forum,ForumName,Gbl.Prefs.Language,false); break; default: break; } } /*****************************************************************************/ /*********************** Write location in hierarchy *************************/ /*****************************************************************************/ static void TmlNot_WriteLocationInHierarchy (const struct TmlNot_Note *Not, struct Hie_Node Hie[Hie_NUM_LEVELS], const char ForumName[For_MAX_BYTES_FORUM_NAME + 1]) { extern const char *Txt_HIERARCHY_SINGUL_Abc[Hie_NUM_LEVELS]; extern const char *Txt_Forum; /***** Begin container *****/ HTM_DIV_Begin ("class=\"Tml_LOC\""); /***** Write location *****/ switch (Not->Type) { case TmlNot_INS_DOC_PUB_FILE: case TmlNot_INS_SHA_PUB_FILE: /* Write location (institution) in hierarchy */ HTM_TxtF ("%s: %s", Txt_HIERARCHY_SINGUL_Abc[Hie_INS],Hie[Hie_INS].ShrtName); break; case TmlNot_CTR_DOC_PUB_FILE: case TmlNot_CTR_SHA_PUB_FILE: /* Write location (center) in hierarchy */ HTM_TxtF ("%s: %s", Txt_HIERARCHY_SINGUL_Abc[Hie_CTR],Hie[Hie_CTR].ShrtName); break; case TmlNot_DEG_DOC_PUB_FILE: case TmlNot_DEG_SHA_PUB_FILE: /* Write location (degree) in hierarchy */ HTM_TxtF ("%s: %s", Txt_HIERARCHY_SINGUL_Abc[Hie_DEG],Hie[Hie_DEG].ShrtName); break; case TmlNot_CRS_DOC_PUB_FILE: case TmlNot_CRS_SHA_PUB_FILE: case TmlNot_CALL_FOR_EXAM: case TmlNot_NOTICE: /* Write location (course) in hierarchy */ HTM_TxtF ("%s: %s", Txt_HIERARCHY_SINGUL_Abc[Hie_CRS],Hie[Hie_CRS].ShrtName); break; case TmlNot_FORUM_POST: /* Write forum name */ HTM_TxtF ("%s: %s",Txt_Forum,ForumName); break; default: break; } /***** End container *****/ HTM_DIV_End (); } /*****************************************************************************/ /************* Put form to go to an action depending on the note *************/ /*****************************************************************************/ static void TmlNot_PutFormGoToAction (const struct TmlNot_Note *Not, const struct For_Forums *Forums) { extern Act_Action_t For_ActionsSeeFor[For_NUM_TYPES_FORUM]; extern const char *Txt_TIMELINE_NOTE[Tml_NOT_NUM_NOTE_TYPES]; extern const char *Txt_not_available; char *Anchor = NULL; static Act_Action_t Tml_DefaultActions[Tml_NOT_NUM_NOTE_TYPES] = { [TmlNot_UNKNOWN ] = ActUnk, /* Start tab */ [TmlNot_POST ] = ActUnk, // action not used /* Institution tab */ [TmlNot_INS_DOC_PUB_FILE ] = ActReqDatSeeDocIns, [TmlNot_INS_SHA_PUB_FILE ] = ActReqDatShaIns, /* Center tab */ [TmlNot_CTR_DOC_PUB_FILE ] = ActReqDatSeeDocCtr, [TmlNot_CTR_SHA_PUB_FILE ] = ActReqDatShaCtr, /* Degree tab */ [TmlNot_DEG_DOC_PUB_FILE ] = ActReqDatSeeDocDeg, [TmlNot_DEG_SHA_PUB_FILE ] = ActReqDatShaDeg, /* Course tab */ [TmlNot_CRS_DOC_PUB_FILE ] = ActReqDatSeeDocCrs, [TmlNot_CRS_SHA_PUB_FILE ] = ActReqDatShaCrs, /* Assessment tab */ [TmlNot_CALL_FOR_EXAM ] = ActSeeOneCfe, /* Users tab */ /* Messages tab */ [TmlNot_NOTICE ] = ActSeeOneNot, [TmlNot_FORUM_POST ] = ActSeeFor, /* Analytics tab */ /* Profile tab */ }; static const char *Tml_Icons[Tml_NOT_NUM_NOTE_TYPES] = { [TmlNot_UNKNOWN ] = NULL, /* Start tab */ [TmlNot_POST ] = NULL, // icon not used /* Institution tab */ [TmlNot_INS_DOC_PUB_FILE ] = "file.svg", [TmlNot_INS_SHA_PUB_FILE ] = "file.svg", /* Center tab */ [TmlNot_CTR_DOC_PUB_FILE ] = "file.svg", [TmlNot_CTR_SHA_PUB_FILE ] = "file.svg", /* Degree tab */ [TmlNot_DEG_DOC_PUB_FILE ] = "file.svg", [TmlNot_DEG_SHA_PUB_FILE ] = "file.svg", /* Course tab */ [TmlNot_CRS_DOC_PUB_FILE ] = "file.svg", [TmlNot_CRS_SHA_PUB_FILE ] = "file.svg", /* Assessment tab */ [TmlNot_CALL_FOR_EXAM ] = "bullhorn.svg", /* Users tab */ /* Messages tab */ [TmlNot_NOTICE ] = "sticky-note.svg", [TmlNot_FORUM_POST ] = "comments.svg", /* Analytics tab */ /* Profile tab */ }; if (Not->Unavailable || // File/notice... pointed by this note is unavailable Frm_CheckIfInside ()) // Inside another form { /***** Do not put form *****/ /* Begin container */ HTM_DIV_Begin ("class=\"Tml_FORM_OFF\""); /* Text ("not available") */ HTM_Txt (Txt_TIMELINE_NOTE[Not->Type]); if (Not->Unavailable) HTM_TxtF (" (%s)",Txt_not_available); /* End container */ HTM_DIV_End (); } else // Not inside another form { /***** Begin container *****/ HTM_DIV_Begin ("class=\"Tml_FORM\""); /***** Begin form with parameters depending on the type of note *****/ switch (Not->Type) { case TmlNot_INS_DOC_PUB_FILE: case TmlNot_INS_SHA_PUB_FILE: Frm_BeginForm (Tml_DefaultActions[Not->Type]); ParCod_PutPar (ParCod_Fil,Not->Cod); if (Not->HieCod != Gbl.Hierarchy.Node[Hie_INS].HieCod) // Not the current institution ParCod_PutPar (ParCod_Ins,Not->HieCod); // Go to another institution break; case TmlNot_CTR_DOC_PUB_FILE: case TmlNot_CTR_SHA_PUB_FILE: Frm_BeginForm (Tml_DefaultActions[Not->Type]); ParCod_PutPar (ParCod_Fil,Not->Cod); if (Not->HieCod != Gbl.Hierarchy.Node[Hie_CTR].HieCod) // Not the current center ParCod_PutPar (ParCod_Ctr,Not->HieCod); // Go to another center break; case TmlNot_DEG_DOC_PUB_FILE: case TmlNot_DEG_SHA_PUB_FILE: Frm_BeginForm (Tml_DefaultActions[Not->Type]); ParCod_PutPar (ParCod_Fil,Not->Cod); if (Not->HieCod != Gbl.Hierarchy.Node[Hie_DEG].HieCod) // Not the current degree ParCod_PutPar (ParCod_Deg,Not->HieCod); // Go to another degree break; case TmlNot_CRS_DOC_PUB_FILE: case TmlNot_CRS_SHA_PUB_FILE: Frm_BeginForm (Tml_DefaultActions[Not->Type]); ParCod_PutPar (ParCod_Fil,Not->Cod); if (Not->HieCod != Gbl.Hierarchy.Node[Hie_CRS].HieCod) // Not the current course ParCod_PutPar (ParCod_Crs,Not->HieCod); // Go to another course break; case TmlNot_CALL_FOR_EXAM: Frm_SetAnchorStr (Not->Cod,&Anchor); Frm_BeginFormAnchor (Tml_DefaultActions[Not->Type], Anchor); // Locate on this specific exam Frm_FreeAnchorStr (&Anchor); ParCod_PutPar (ParCod_Exa,Not->Cod); if (Not->HieCod != Gbl.Hierarchy.Node[Hie_CRS].HieCod) // Not the current course ParCod_PutPar (ParCod_Crs,Not->HieCod); // Go to another course break; case TmlNot_POST: // Not applicable return; case TmlNot_FORUM_POST: Frm_BeginForm (For_ActionsSeeFor[Forums->Forum.Type]); For_PutAllParsForum (1, // Page of threads = first 1, // Page of posts = first Forums->ForumSet, Forums->ThreadsOrder, Forums->Forum.HieCod, Forums->Thread.Selected, -1L); if (Not->HieCod != Gbl.Hierarchy.Node[Hie_CRS].HieCod) // Not the current course ParCod_PutPar (ParCod_Crs,Not->HieCod); // Go to another course break; case TmlNot_NOTICE: Frm_SetAnchorStr (Not->Cod,&Anchor); Frm_BeginFormAnchor (Tml_DefaultActions[Not->Type],Anchor); Frm_FreeAnchorStr (&Anchor); ParCod_PutPar (ParCod_Not,Not->Cod); if (Not->HieCod != Gbl.Hierarchy.Node[Hie_CRS].HieCod) // Not the current course ParCod_PutPar (ParCod_Crs,Not->HieCod); // Go to another course break; default: // Not applicable return; } /***** Icon and link to go to action *****/ /* Begin button */ HTM_BUTTON_Submit_Begin (Txt_TIMELINE_NOTE[Not->Type], "class=\"BT_LINK FORM_IN_%s ICO_HIGHLIGHT\"", The_GetSuffix ()); /* Icon and text */ Ico_PutIcon (Tml_Icons[Not->Type],Ico_BLACK, Txt_TIMELINE_NOTE[Not->Type],"CONTEXT_ICOx16"); HTM_NBSPTxt (Txt_TIMELINE_NOTE[Not->Type]); /* End button */ HTM_BUTTON_End (); /***** End form *****/ Frm_EndForm (); /***** End container *****/ HTM_DIV_End (); } } /*****************************************************************************/ /********************** Get note summary and content *************************/ /*****************************************************************************/ void TmlNot_GetNoteSummary (const struct TmlNot_Note *Not, char SummaryStr[Ntf_MAX_BYTES_SUMMARY + 1]) { SummaryStr[0] = '\0'; switch (Not->Type) { case TmlNot_UNKNOWN: break; case TmlNot_INS_DOC_PUB_FILE: case TmlNot_INS_SHA_PUB_FILE: case TmlNot_CTR_DOC_PUB_FILE: case TmlNot_CTR_SHA_PUB_FILE: case TmlNot_DEG_DOC_PUB_FILE: case TmlNot_DEG_SHA_PUB_FILE: case TmlNot_CRS_DOC_PUB_FILE: case TmlNot_CRS_SHA_PUB_FILE: Brw_GetSummaryAndContentOfFile (SummaryStr,NULL,Not->Cod, Ntf_DONT_GET_CONTENT); break; case TmlNot_CALL_FOR_EXAM: Cfe_GetSummaryAndContentCallForExam (SummaryStr,NULL,Not->Cod, Ntf_DONT_GET_CONTENT); break; case TmlNot_POST: // Not applicable break; case TmlNot_FORUM_POST: For_GetSummaryAndContentForumPst (SummaryStr,NULL,Not->Cod, Ntf_DONT_GET_CONTENT); break; case TmlNot_NOTICE: Not_GetSummaryAndContentNotice (SummaryStr,NULL,Not->Cod, Ntf_DONT_GET_CONTENT); break; } } /*****************************************************************************/ /************************ Write bottom part of a note ************************/ /*****************************************************************************/ static void TmlNot_WriteButtonsAndComms (const struct Tml_Timeline *Timeline, const struct TmlNot_Note *Not, const struct Usr_Data *UsrDat) // Author { char IdNewComm[Frm_MAX_BYTES_ID + 1]; /***** Create unique id for new comment *****/ Frm_SetUniqueId (IdNewComm); /***** Left: button to add a comment *****/ TmlNot_WriteButtonToAddAComm (Timeline,Not,IdNewComm); /***** Right: write favs, shared and remove buttons, and comments *****/ TmlNot_WriteFavShaRemAndComms (Timeline,Not,UsrDat); /***** Put hidden form to write a new comment *****/ TmlCom_PutPhotoAndFormToWriteNewComm (Timeline,Not->NotCod,IdNewComm); } /*****************************************************************************/ /********************** Write button to add a comment ************************/ /*****************************************************************************/ static void TmlNot_WriteButtonToAddAComm (const struct Tml_Timeline *Timeline, const struct TmlNot_Note *Not, const char IdNewComm[Frm_MAX_BYTES_ID + 1]) { /***** Begin container *****/ HTM_DIV_Begin ("class=\"Tml_BOTTOM_LEFT\""); /***** Button to add a comment *****/ if (Not->Unavailable) // Unavailable notes can not be commented TmlCom_PutIconCommDisabled (); else TmlCom_PutIconToToggleComm (Timeline,IdNewComm); /***** End container *****/ HTM_DIV_End (); } /*****************************************************************************/ /******* Write favs, shared and remove buttons, and comments of a note *******/ /*****************************************************************************/ static void TmlNot_WriteFavShaRemAndComms (const struct Tml_Timeline *Timeline, const struct TmlNot_Note *Not, const struct Usr_Data *UsrDat) // Author { /***** Begin container *****/ HTM_DIV_Begin ("class=\"Tml_BOTTOM_RIGHT Tml_RIGHT_WIDTH\""); /***** Write favs, shared and remove buttons int the foot of a note *****/ TmlNot_WriteFavShaRem (Timeline,Not,UsrDat); /***** Comments *****/ TmlCom_WriteCommsInNote (Timeline,Not); /***** End container *****/ HTM_DIV_End (); } /*****************************************************************************/ /******* Write favs, shared and remove buttons in the foot of a note *********/ /*****************************************************************************/ static void TmlNot_WriteFavShaRem (const struct Tml_Timeline *Timeline, const struct TmlNot_Note *Not, const struct Usr_Data *UsrDat) // Author { static unsigned NumDiv = 0; // Used to create unique div id for fav and shared const char *UniqueNameEncrypted = Cry_GetUniqueNameEncrypted (); NumDiv++; /***** Begin foot container *****/ HTM_DIV_Begin ("class=\"Tml_FOOT Tml_RIGHT_WIDTH\""); /***** Foot column 1: fav zone *****/ HTM_DIV_Begin ("id=\"fav_not_%s_%u\"" " class=\"Tml_FAV_NOT Tml_FAV_NOT_WIDTH\"", UniqueNameEncrypted,NumDiv); TmlUsr_PutIconFavSha (TmlUsr_FAV_UNF_NOTE, Not->NotCod,Not->UsrCod,Not->NumFavs, TmlUsr_SHOW_FEW_USRS); HTM_DIV_End (); /***** Foot column 2: share zone *****/ HTM_DIV_Begin ("id=\"sha_not_%s_%u\"" " class=\"Tml_SHA_NOT Tml_SHA_NOT_WIDTH\"", UniqueNameEncrypted,NumDiv); TmlUsr_PutIconFavSha (TmlUsr_SHA_UNS_NOTE, Not->NotCod,Not->UsrCod,Not->NumShared, TmlUsr_SHOW_FEW_USRS); HTM_DIV_End (); /***** Foot column 3: icon to remove this note *****/ HTM_DIV_Begin ("class=\"Tml_REM\""); if (Usr_ItsMe (UsrDat->UsrCod) == Usr_ME) // I am the author TmlNot_PutFormToRemoveNote (Timeline,Not->NotCod); HTM_DIV_End (); /***** End foot container *****/ HTM_DIV_End (); } /*****************************************************************************/ /**************************** Form to remove note ****************************/ /*****************************************************************************/ static void TmlNot_PutFormToRemoveNote (const struct Tml_Timeline *Timeline, long NotCod) { extern Act_Action_t TmlFrm_ActionUsr[TmlFrm_NUM_ACTIONS]; extern Act_Action_t TmlFrm_ActionGbl[TmlFrm_NUM_ACTIONS]; /***** Form to remove publication *****/ /* Begin form */ TmlFrm_BeginForm (Timeline,TmlFrm_REQ_REM_NOTE); ParCod_PutPar (ParCod_Not,NotCod); /* Icon to remove */ Ico_PutIconLink ("trash.svg",Ico_RED, Gbl.Usrs.Other.UsrDat.UsrCod > 0 ? TmlFrm_ActionUsr[TmlFrm_REQ_REM_NOTE] : TmlFrm_ActionGbl[TmlFrm_REQ_REM_NOTE]); /* End form */ TmlFrm_EndForm (); } /*****************************************************************************/ /***************** Store and publish a note into database ********************/ /*****************************************************************************/ void TmlNot_StoreAndPublishNote (TmlNot_Type_t NoteType,long Cod) { struct TmlPub_Publication Pub; TmlNot_StoreAndPublishNoteInternal (NoteType,Cod,&Pub); } void TmlNot_StoreAndPublishNoteInternal (TmlNot_Type_t NoteType,long Cod, struct TmlPub_Publication *Pub) { static Hie_Level_t Level[Tml_NOT_NUM_NOTE_TYPES] = { [TmlNot_INS_DOC_PUB_FILE] = Hie_INS, [TmlNot_INS_SHA_PUB_FILE] = Hie_INS, [TmlNot_CTR_DOC_PUB_FILE] = Hie_CTR, [TmlNot_CTR_SHA_PUB_FILE] = Hie_CTR, [TmlNot_DEG_DOC_PUB_FILE] = Hie_DEG, [TmlNot_DEG_SHA_PUB_FILE] = Hie_DEG, [TmlNot_CRS_DOC_PUB_FILE] = Hie_CRS, [TmlNot_CRS_SHA_PUB_FILE] = Hie_CRS, [TmlNot_CALL_FOR_EXAM ] = Hie_CRS, [TmlNot_NOTICE ] = Hie_CRS, }; long HieCod; // Hierarchy code (institution/center/degree/course) if (Level[NoteType]) HieCod = Gbl.Hierarchy.Node[Level[NoteType]].HieCod; else HieCod = -1L; /***** Publish note in timeline *****/ Pub->PublisherCod = Gbl.Usrs.Me.UsrDat.UsrCod; Pub->NotCod = Tml_DB_CreateNewNote (NoteType,Cod,Pub->PublisherCod,HieCod); Pub->Type = TmlPub_ORIGINAL_NOTE; TmlPub_PublishPubInTimeline (Pub); } /*****************************************************************************/ /****************** Mark notes of one file as unavailable ********************/ /*****************************************************************************/ void TmlNot_MarkNoteOneFileAsUnavailable (const char *Path) { extern const Brw_FileBrowser_t Brw_DB_FileBrowserForDB_files[Brw_NUM_TYPES_FILE_BROWSER]; Brw_FileBrowser_t FileBrowser = Brw_DB_FileBrowserForDB_files[Gbl.FileBrowser.Type]; long FilCod; if (TmlNot_NoteType[FileBrowser]) /***** Get file code *****/ if ((FilCod = Brw_DB_GetFilCodByPath (Path, true)) > 0) // Only public files /***** Mark possible note as unavailable *****/ Tml_DB_MarkNoteAsUnavailable (TmlNot_NoteType[FileBrowser],FilCod); } /*****************************************************************************/ /***** Mark possible notes involving children of a folder as unavailable *****/ /*****************************************************************************/ void TmlNot_MarkNotesChildrenOfFolderAsUnavailable (const char *Path) { extern const Brw_FileBrowser_t Brw_DB_FileBrowserForDB_files[Brw_NUM_TYPES_FILE_BROWSER]; Brw_FileBrowser_t FileBrowser = Brw_DB_FileBrowserForDB_files[Gbl.FileBrowser.Type]; if (TmlNot_NoteType[FileBrowser]) Tml_DB_MarkNotesChildrenOfFolderAsUnavailable (TmlNot_NoteType[FileBrowser],FileBrowser, Brw_GetCodForFileBrowser (), Path); } /*****************************************************************************/ /*********************** Request the removal of a note ***********************/ /*****************************************************************************/ void TmlNot_ReqRemNoteUsr (void) { struct Tml_Timeline Timeline; /***** Reset timeline context *****/ Tml_ResetTimeline (&Timeline); /***** Get user whom profile is displayed *****/ Usr_GetParOtherUsrCodEncryptedAndGetUsrData (); /***** Show user's profile *****/ Prf_ShowUsrProfile (&Gbl.Usrs.Other.UsrDat); /***** Begin section *****/ HTM_SECTION_Begin (Tml_TIMELINE_SECTION_ID); /***** Request the removal of note *****/ TmlNot_ReqRemNote (&Timeline); /***** Write timeline again (user) *****/ Tml_ShowTimelineUsr (&Timeline); /***** End section *****/ HTM_SECTION_End (); } void TmlNot_ReqRemNoteGbl (void) { struct Tml_Timeline Timeline; /***** Initialize timeline *****/ Tml_InitTimelineGbl (&Timeline); /***** Request the removal of note *****/ TmlNot_ReqRemNote (&Timeline); /***** Write timeline again (global) *****/ Tml_ShowNoteAndTimelineGbl (&Timeline); } static void TmlNot_ReqRemNote (struct Tml_Timeline *Timeline) { extern const char *Txt_Do_you_really_want_to_remove_the_following_post; struct TmlNot_Note Not; /***** Get data of note *****/ Not.NotCod = ParCod_GetAndCheckPar (ParCod_Not); TmlNot_GetNoteDataByCod (&Not); /***** Do some checks *****/ if (TmlUsr_CheckIfICanRemove (Not.NotCod,Not.UsrCod) == Usr_CAN_NOT) return; /***** Show question and button to remove note *****/ /* Begin alert */ TmlFrm_BeginAlertRemove (Txt_Do_you_really_want_to_remove_the_following_post); /* Show note */ Box_BoxBegin (NULL,NULL,NULL,NULL,Box_CLOSABLE); HTM_DIV_Begin ("class=\"Tml_WIDTH\""); TmlNot_CheckAndWriteNoteWithTopMsg (Timeline,&Not, Tml_TOP_MESSAGE_NONE, -1L); HTM_DIV_End (); Box_BoxEnd (); HTM_BR (); /* End alert */ Timeline->NotCod = Not.NotCod; // Note to be removed TmlFrm_EndAlertRemove (Timeline,TmlFrm_REM_NOTE,TmlNot_PutParsRemoveNote); } /*****************************************************************************/ /********************* Put parameters to remove a note ***********************/ /*****************************************************************************/ static void TmlNot_PutParsRemoveNote (void *Timeline) { if (Timeline) { if (Gbl.Usrs.Other.UsrDat.UsrCod > 0) // User's timeline Usr_PutParOtherUsrCodEncrypted (Gbl.Usrs.Other.UsrDat.EnUsrCod); else // Global timeline Usr_PutParWho (((struct Tml_Timeline *) Timeline)->Who); ParCod_PutPar (ParCod_Not,((struct Tml_Timeline *) Timeline)->NotCod); } } /*****************************************************************************/ /******************************* Remove a note *******************************/ /*****************************************************************************/ void TmlNot_RemoveNoteUsr (void) { struct Tml_Timeline Timeline; /***** Reset timeline context *****/ Tml_ResetTimeline (&Timeline); /***** Get user whom profile is displayed *****/ Usr_GetParOtherUsrCodEncryptedAndGetUsrData (); /***** Show user's profile *****/ Prf_ShowUsrProfile (&Gbl.Usrs.Other.UsrDat); /***** Begin section *****/ HTM_SECTION_Begin (Tml_TIMELINE_SECTION_ID); /***** Remove a note *****/ TmlNot_RemoveNote (); /***** Write updated timeline after removing (user) *****/ Tml_ShowTimelineUsr (&Timeline); /***** End section *****/ HTM_SECTION_End (); } void TmlNot_RemoveNoteGbl (void) { struct Tml_Timeline Timeline; /***** Initialize timeline *****/ Tml_InitTimelineGbl (&Timeline); /***** Remove a note *****/ TmlNot_RemoveNote (); /***** Write updated timeline after removing (global) *****/ Tml_ShowNoteAndTimelineGbl (&Timeline); } static void TmlNot_RemoveNote (void) { extern const char *Txt_The_post_no_longer_exists; extern const char *Txt_TIMELINE_Post_removed; struct TmlNot_Note Not; /***** Get data of note *****/ Not.NotCod = ParCod_GetAndCheckPar (ParCod_Not); TmlNot_GetNoteDataByCod (&Not); /***** Trivial check 1: note code should be > 0 *****/ if (Not.NotCod <= 0) { Ale_ShowAlert (Ale_WARNING,Txt_The_post_no_longer_exists); return; } /***** Trivial check 2: Am I the author of this note? *****/ if (Usr_ItsMe (Not.UsrCod) == Usr_OTHER) { Err_NoPermission (); return; } /***** Delete note from database *****/ TmlNot_RemoveNoteMediaAndDBEntries (&Not); /***** Reset note *****/ TmlNot_ResetNote (&Not); /***** Message of success *****/ Ale_ShowAlert (Ale_SUCCESS,Txt_TIMELINE_Post_removed); } /*****************************************************************************/ /*********************** Remove a note from database *************************/ /*****************************************************************************/ static void TmlNot_RemoveNoteMediaAndDBEntries (struct TmlNot_Note *Not) { MYSQL_RES *mysql_res; long PubCod; unsigned long NumComms; unsigned long NumComm; long MedCod; /***** Remove comments associated to this note *****/ /* Get comments of this note */ NumComms = Tml_DB_GetComms (Not->NotCod,&mysql_res); /* For each comment... */ for (NumComm = 0; NumComm < NumComms; NumComm++) { /* Get code of comment **/ PubCod = DB_GetNextCode (mysql_res); /* Remove media associated to comment and delete comment from database */ TmlCom_RemoveCommMediaAndDBEntries (PubCod); } /* Free structure that stores the query result */ DB_FreeMySQLResult (&mysql_res); /***** Remove media associated to post *****/ if (Not->Type == TmlNot_POST) if ((MedCod = Tml_DB_GetMedCodFromPost (Not->Cod)) > 0) Med_RemoveMedia (MedCod); /***** Mark possible notifications on the publications of this note as removed *****/ if ((PubCod = Tml_DB_GetPubCodOfOriginalNote (Not->NotCod)) > 0) { Ntf_DB_MarkNotifAsRemoved (Ntf_EVENT_TML_FAV ,PubCod); Ntf_DB_MarkNotifAsRemoved (Ntf_EVENT_TML_SHARE ,PubCod); Ntf_DB_MarkNotifAsRemoved (Ntf_EVENT_TML_MENTION,PubCod); } /***** Remove favs for this note *****/ Tml_DB_RemoveNoteFavs (Not->NotCod); /***** Remove all publications of this note *****/ Tml_DB_RemoveNotePubs (Not->NotCod); /***** Remove note *****/ Tml_DB_RemoveNote (Not->NotCod); if (Not->Type == TmlNot_POST) /***** Remove post *****/ Tml_DB_RemovePost (Not->Cod); } /*****************************************************************************/ /************************ Get data of note from row **************************/ /*****************************************************************************/ static void TmlNot_GetNoteDataFromRow (MYSQL_RES *mysql_res, struct TmlNot_Note *Not) { MYSQL_ROW row; /***** Get next row from result *****/ row = mysql_fetch_row (mysql_res); /* row[0]: NotCod row[1]: NoteType row[2]: Cod row[3]: UsrCod row[4]: HieCod row[5]: Unavailable row[6]: UNIX_TIMESTAMP(TimeNote) */ /***** Get code (row[0]) *****/ Not->NotCod = Str_ConvertStrCodToLongCod (row[0]); /***** Get note type (row[1]) *****/ Not->Type = TmlNot_GetNoteTypeFromStr (row[1]); /***** Get file/post... code (row[2]), (from) user code (row[3]) and hierarchy code (row[4]) *****/ Not->Cod = Str_ConvertStrCodToLongCod (row[2]); Not->UsrCod = Str_ConvertStrCodToLongCod (row[3]); Not->HieCod = Str_ConvertStrCodToLongCod (row[4]); /***** File/post... unavailable (row[5]) *****/ Not->Unavailable = (row[5][0] == 'Y'); /***** Get time of the note (row[6]) *****/ Not->DateTimeUTC = Dat_GetUNIXTimeFromStr (row[6]); /***** Get number of times this note has been shared and favourited *****/ Not->NumShared = Tml_DB_GetNumSharers (Not->NotCod,Not->UsrCod); Not->NumFavs = Tml_DB_GetNumFavers (TmlUsr_FAV_UNF_NOTE, Not->NotCod,Not->UsrCod); } /*****************************************************************************/ /********* Get note type from string number coming from database *************/ /*****************************************************************************/ static TmlNot_Type_t TmlNot_GetNoteTypeFromStr (const char *Str) { unsigned UnsignedNum; if (sscanf (Str,"%u",&UnsignedNum) == 1) if (UnsignedNum < Tml_NOT_NUM_NOTE_TYPES) return (TmlNot_Type_t) UnsignedNum; return TmlNot_UNKNOWN; } /*****************************************************************************/ /*************************** Reset fields of note ****************************/ /*****************************************************************************/ static void TmlNot_ResetNote (struct TmlNot_Note *Not) { Not->NotCod = -1L; Not->Type = TmlNot_UNKNOWN; Not->UsrCod = -1L; Not->HieCod = -1L; Not->Cod = -1L; Not->Unavailable = false; Not->DateTimeUTC = (time_t) 0; Not->NumShared = 0; } /*****************************************************************************/ /******************** Get data of note using its code ************************/ /*****************************************************************************/ void TmlNot_GetNoteDataByCod (struct TmlNot_Note *Not) { MYSQL_RES *mysql_res; /***** Trivial check: note code should be > 0 *****/ if (Not->NotCod <= 0) { /* Reset fields of note */ TmlNot_ResetNote (Not); return; } /***** Get data of note from database *****/ if (Tml_DB_GetNoteDataByCod (Not->NotCod,&mysql_res)) TmlNot_GetNoteDataFromRow (mysql_res,Not); else /* Reset fields of note */ TmlNot_ResetNote (Not); /***** Free structure that stores the query result *****/ DB_FreeMySQLResult (&mysql_res); }