diff --git a/swad_changelog.h b/swad_changelog.h index 9a2fa6b9f..7515e5481 100644 --- a/swad_changelog.h +++ b/swad_changelog.h @@ -121,19 +121,20 @@ // TODO: When receiving a new post, create first the publishing, then the post // TODO: Change PstCod to PubCod in social_posts, removing AUTO_INCREMENT // TODO: View highlighted social note when clicking in timeline notification -// TODO: Mark timeline notifications as removed when post/comment are removed or fav/share are undone +// TODO: Mark timeline notifications as removed when unfav/unshared? /*****************************************************************************/ /****************************** Public constants *****************************/ /*****************************************************************************/ -#define Log_PLATFORM_VERSION "SWAD 15.126.1 (2016-01-24)" +#define Log_PLATFORM_VERSION "SWAD 15.127 (2016-01-24)" #define CSS_FILE "swad15.121.7.css" #define JS_FILE "swad15.121.7.js" // Number of lines (includes comments but not blank lines) has been got with the following command: // nl swad*.c swad*.h css/swad*.css py/swad*.py js/swad*.js soap/swad*.h sql/swad*.sql | tail -1 /* + Version 15.127: Jan 24, 2016 New function to count the number of @nicknames in a text and store it in social publishing. (194825 lines) Version 15.126.1: Jan 24, 2016 Optimization in code to insert links. (194736 lines) Version 15.126: Jan 24, 2016 In any text where URL is replaced by anchor, now @nickname is also replaced to link to user's profile. (194727 lines) 2 changes necessary in database: diff --git a/swad_exam.c b/swad_exam.c index b4f3de867..0f28001c0 100644 --- a/swad_exam.c +++ b/swad_exam.c @@ -279,6 +279,7 @@ void Exa_ReceiveExamAnnouncement (void) long ExaCod; bool NewExamAnnouncement; unsigned NumUsrsToBeNotifiedByEMail; + struct SocialPublishing SocPub; /***** Allocate memory for the exam announcement *****/ Exa_AllocMemExamAnnouncement (); @@ -306,7 +307,7 @@ void Exa_ReceiveExamAnnouncement (void) Ntf_ShowAlertNumUsrsToBeNotifiedByEMail (NumUsrsToBeNotifiedByEMail); /***** Create a new social note about the new exam announcement *****/ - Soc_StoreAndPublishSocialNote (Soc_NOTE_EXAM_ANNOUNCEMENT,ExaCod); + Soc_StoreAndPublishSocialNote (Soc_NOTE_EXAM_ANNOUNCEMENT,ExaCod,&SocPub); /***** Show exam announcement *****/ Exa_ListExamAnnouncementsEdit (); diff --git a/swad_file_browser.c b/swad_file_browser.c index d9624a182..6e660dac5 100644 --- a/swad_file_browser.c +++ b/swad_file_browser.c @@ -9642,6 +9642,7 @@ void Brw_ChgFileMetadata (void) bool PublicFileBeforeEdition; bool PublicFileAfterEdition; Brw_License_t License; + struct SocialPublishing SocPub; /***** Get parameters related to file browser *****/ Brw_GetParAndInitFileBrowser (); @@ -9707,28 +9708,28 @@ void Brw_ChgFileMetadata (void) switch (Gbl.FileBrowser.Type) { case Brw_ADMI_DOCUM_INS: - Soc_StoreAndPublishSocialNote (Soc_NOTE_INS_DOC_PUB_FILE,FileMetadata.FilCod); + Soc_StoreAndPublishSocialNote (Soc_NOTE_INS_DOC_PUB_FILE,FileMetadata.FilCod,&SocPub); break; case Brw_ADMI_SHARE_INS: - Soc_StoreAndPublishSocialNote (Soc_NOTE_INS_SHA_PUB_FILE,FileMetadata.FilCod); + Soc_StoreAndPublishSocialNote (Soc_NOTE_INS_SHA_PUB_FILE,FileMetadata.FilCod,&SocPub); break; case Brw_ADMI_DOCUM_CTR: - Soc_StoreAndPublishSocialNote (Soc_NOTE_CTR_DOC_PUB_FILE,FileMetadata.FilCod); + Soc_StoreAndPublishSocialNote (Soc_NOTE_CTR_DOC_PUB_FILE,FileMetadata.FilCod,&SocPub); break; case Brw_ADMI_SHARE_CTR: - Soc_StoreAndPublishSocialNote (Soc_NOTE_CTR_SHA_PUB_FILE,FileMetadata.FilCod); + Soc_StoreAndPublishSocialNote (Soc_NOTE_CTR_SHA_PUB_FILE,FileMetadata.FilCod,&SocPub); break; case Brw_ADMI_DOCUM_DEG: - Soc_StoreAndPublishSocialNote (Soc_NOTE_DEG_DOC_PUB_FILE,FileMetadata.FilCod); + Soc_StoreAndPublishSocialNote (Soc_NOTE_DEG_DOC_PUB_FILE,FileMetadata.FilCod,&SocPub); break; case Brw_ADMI_SHARE_DEG: - Soc_StoreAndPublishSocialNote (Soc_NOTE_DEG_SHA_PUB_FILE,FileMetadata.FilCod); + Soc_StoreAndPublishSocialNote (Soc_NOTE_DEG_SHA_PUB_FILE,FileMetadata.FilCod,&SocPub); break; case Brw_ADMI_DOCUM_CRS: - Soc_StoreAndPublishSocialNote (Soc_NOTE_CRS_DOC_PUB_FILE,FileMetadata.FilCod); + Soc_StoreAndPublishSocialNote (Soc_NOTE_CRS_DOC_PUB_FILE,FileMetadata.FilCod,&SocPub); break; case Brw_ADMI_SHARE_CRS: - Soc_StoreAndPublishSocialNote (Soc_NOTE_CRS_SHA_PUB_FILE,FileMetadata.FilCod); + Soc_StoreAndPublishSocialNote (Soc_NOTE_CRS_SHA_PUB_FILE,FileMetadata.FilCod,&SocPub); break; default: break; diff --git a/swad_forum.c b/swad_forum.c index 9c6d09764..2ccb3c59e 100644 --- a/swad_forum.c +++ b/swad_forum.c @@ -3746,6 +3746,7 @@ void For_RecForumPst (void) long ThrCod = 0; long PstCod = 0; unsigned NumUsrsToBeNotifiedByEMail; + struct SocialPublishing SocPub; char Content[Cns_MAX_BYTES_LONG_TEXT+1]; /***** Get order type, degree and course of the forum *****/ @@ -3834,7 +3835,7 @@ void For_RecForumPst (void) { case For_FORUM_GLOBAL_USRS: case For_FORUM_SWAD_USRS: - Soc_StoreAndPublishSocialNote (Soc_NOTE_FORUM_POST,PstCod); + Soc_StoreAndPublishSocialNote (Soc_NOTE_FORUM_POST,PstCod,&SocPub); break; default: break; diff --git a/swad_notice.c b/swad_notice.c index f0f7449ab..c9782e66f 100644 --- a/swad_notice.c +++ b/swad_notice.c @@ -134,6 +134,7 @@ void Not_ReceiveNotice (void) extern const char *Txt_Notice_created; long NotCod; unsigned NumUsrsToBeNotifiedByEMail; + struct SocialPublishing SocPub; char Content[Cns_MAX_BYTES_TEXT+1]; /***** Get the text of the notice *****/ @@ -155,7 +156,7 @@ void Not_ReceiveNotice (void) Ntf_ShowAlertNumUsrsToBeNotifiedByEMail (NumUsrsToBeNotifiedByEMail); /***** Create a new social note about the new notice *****/ - Soc_StoreAndPublishSocialNote (Soc_NOTE_NOTICE,NotCod); + Soc_StoreAndPublishSocialNote (Soc_NOTE_NOTICE,NotCod,&SocPub); } /*****************************************************************************/ diff --git a/swad_social.c b/swad_social.c index 5a92f3e7b..fdda3a2c5 100644 --- a/swad_social.c +++ b/swad_social.c @@ -81,16 +81,6 @@ typedef enum // when user clicks on link at bottom of timeline } Soc_WhatToGetFromTimeline_t; -typedef enum - { - Soc_TOP_MESSAGE_NONE, - Soc_TOP_MESSAGE_SHARED, - Soc_TOP_MESSAGE_UNSHARED, - Soc_TOP_MESSAGE_FAV, - Soc_TOP_MESSAGE_UNFAV, - Soc_TOP_MESSAGE_COMMENTED, - } Soc_TopMessage_t; - static const Act_Action_t Soc_DefaultActions[Soc_NUM_NOTE_TYPES] = { ActUnk, // Soc_NOTE_UNKNOWN @@ -171,16 +161,6 @@ static const char *Soc_Icons[Soc_NUM_NOTE_TYPES] = /****************************** Internal types *******************************/ /*****************************************************************************/ -struct SocialPublishing - { - long PubCod; - long NotCod; - long PublisherCod; // Sharer or writer of a comment - Soc_PubType_t PubType; - time_t DateTimeUTC; - Soc_TopMessage_t TopMessage; // Used to show feedback on the action made - }; - struct SocialNote { long NotCod; @@ -340,6 +320,8 @@ static void Soc_SetUniqueId (char UniqueId[Soc_MAX_LENGTH_ID]); static void Soc_ClearTimelineThisSession (void); static void Soc_AddNotesJustRetrievedToTimelineThisSession (void); +static void Str_AnalyzeTxtAndStoreNotifyEventToMentionedUsrs (long PubCod,const char *Txt); + /*****************************************************************************/ /***** Show social activity (timeline) including all the users I follow ******/ /*****************************************************************************/ @@ -1251,7 +1233,6 @@ static void Soc_WriteSocialNote (const struct SocialNote *SocNot, /* End of right part */ fprintf (Gbl.F.Out,""); - fprintf (Gbl.F.Out,"
"); /* Create unique id for new comment */ @@ -1637,11 +1618,10 @@ static void Soc_GetNoteSummary (const struct SocialNote *SocNot, /*****************************************************************************/ // Return the code of the new note just created -long Soc_StoreAndPublishSocialNote (Soc_NoteType_t NoteType,long Cod) +void Soc_StoreAndPublishSocialNote (Soc_NoteType_t NoteType,long Cod,struct SocialPublishing *SocPub) { char Query[256]; long HieCod; // Hierarchy code (institution/centre/degree/course) - struct SocialPublishing SocPub; switch (NoteType) { @@ -1673,14 +1653,12 @@ long Soc_StoreAndPublishSocialNote (Soc_NoteType_t NoteType,long Cod) " (NoteType,UsrCod,HieCod,Cod,Unavailable,TimeNote)" " VALUES ('%u','%ld','%ld','%ld','N',NOW())", (unsigned) NoteType,Gbl.Usrs.Me.UsrDat.UsrCod,HieCod,Cod); - SocPub.NotCod = DB_QueryINSERTandReturnCode (Query,"can not create new social note"); + SocPub->NotCod = DB_QueryINSERTandReturnCode (Query,"can not create new social note"); /***** Publish social note in timeline *****/ - SocPub.PublisherCod = Gbl.Usrs.Me.UsrDat.UsrCod; - SocPub.PubType = Soc_PUB_ORIGINAL_NOTE; - Soc_PublishSocialNoteInTimeline (&SocPub); - - return SocPub.NotCod; + SocPub->PublisherCod = Gbl.Usrs.Me.UsrDat.UsrCod; + SocPub->PubType = Soc_PUB_ORIGINAL_NOTE; + Soc_PublishSocialNoteInTimeline (SocPub); } /*****************************************************************************/ @@ -1857,9 +1835,6 @@ static void Soc_PublishSocialNoteInTimeline (struct SocialPublishing *SocPub) SocPub->PublisherCod, (unsigned) SocPub->PubType); SocPub->PubCod = DB_QueryINSERTandReturnCode (Query,"can not publish social note"); - - /***** Store notification about the new publishing *****/ - Ntf_StoreNotifyEventsToAllUsrs (Ntf_EVENT_TIMELINE_PUBLISH,SocPub->PubCod); } /*****************************************************************************/ @@ -2002,7 +1977,7 @@ static long Soc_ReceiveSocialPost (void) char Content[Cns_MAX_BYTES_LONG_TEXT+1]; char Query[128+Cns_MAX_BYTES_LONG_TEXT]; long PstCod; - long NotCod; + struct SocialPublishing SocPub; /***** Get the content of the new post *****/ Par_GetParAndChangeFormat ("Content",Content,Cns_MAX_BYTES_LONG_TEXT, @@ -2017,12 +1992,18 @@ static long Soc_ReceiveSocialPost (void) PstCod = DB_QueryINSERTandReturnCode (Query,"can not create post"); /* Insert post in social notes */ - NotCod = Soc_StoreAndPublishSocialNote (Soc_NOTE_SOCIAL_POST,PstCod); + Soc_StoreAndPublishSocialNote (Soc_NOTE_SOCIAL_POST,PstCod,&SocPub); + + /***** Store notifications about the new publishing *****/ + Ntf_StoreNotifyEventsToAllUsrs (Ntf_EVENT_TIMELINE_PUBLISH,SocPub.PubCod); + + /***** Analyze content and store notifications about mentions *****/ + Str_AnalyzeTxtAndStoreNotifyEventToMentionedUsrs (SocPub.PubCod,Content); } else - NotCod = -1L; + SocPub.NotCod = -1L; - return NotCod; + return SocPub.NotCod; } /*****************************************************************************/ @@ -2731,9 +2712,15 @@ static long Soc_ReceiveComment (void) Content); DB_QueryINSERT (Query,"can not store comment content"); - /***** Store notification about the new comment *****/ + /***** Store notifications about the new publishing *****/ + Ntf_StoreNotifyEventsToAllUsrs (Ntf_EVENT_TIMELINE_PUBLISH,SocPub.PubCod); + + /***** Store notifications about the new comment *****/ Ntf_StoreNotifyEventsToAllUsrs (Ntf_EVENT_TIMELINE_COMMENT,SocPub.PubCod); + /***** Analyze content and store notifications about mentions *****/ + Str_AnalyzeTxtAndStoreNotifyEventToMentionedUsrs (SocPub.PubCod,Content); + /***** Show the social note just commented *****/ Soc_WriteSocialNote (&SocNot, Soc_TOP_MESSAGE_COMMENTED,Gbl.Usrs.Me.UsrDat.UsrCod, @@ -2811,6 +2798,9 @@ static long Soc_ShareSocialNote (void) /* Update number of times this social note is shared */ SocNot.NumShared = Soc_UpdateNumTimesANoteHasBeenShared (&SocNot); + + /***** Store notifications about the new publishing *****/ + Ntf_StoreNotifyEventsToAllUsrs (Ntf_EVENT_TIMELINE_PUBLISH,SocPub.PubCod); } } else @@ -4438,3 +4428,89 @@ void Soc_GetNotifSocialPublishing (char *SummaryStr,char **ContentStr,long PubCo if ((*ContentStr = (char *) malloc (strlen (Content)+1)) != NULL) strcpy (*ContentStr,Content); } +/*****************************************************************************/ +/*** Create a notification about mention for any nickname in a publishing ****/ +/*****************************************************************************/ +/* + Example: "The user @rms says..." + ^ ^ + PtrStart ___| |___ PtrEnd + Length = 3 +*/ +static void Str_AnalyzeTxtAndStoreNotifyEventToMentionedUsrs (long PubCod,const char *Txt) + { + const char *Ptr; + bool IsNickname; + struct + { + const char *PtrStart; + const char *PtrEnd; + size_t Length; // Length of the nickname + } Nickname; + struct UsrData UsrDat; + bool CreateNotif; + bool NotifyByEmail; + + /***** Initialize structure with user's data *****/ + Usr_UsrDataConstructor (&UsrDat); + + /***** Find nicknames and create notifications *****/ + for (Ptr = Txt; + *Ptr;) + /* Check if the next char is the start of a nickname */ + if ((int) *Ptr == (int) '@') + { + /* Find nickname end */ + Ptr++; // Points to first character after @ + Nickname.PtrStart = Ptr; + + /* A nick can have digits, letters and '_' */ + for (; + *Ptr; + Ptr++) + if (!((*Ptr >= 'a' && *Ptr <= 'z') || + (*Ptr >= 'A' && *Ptr <= 'Z') || + (*Ptr >= '0' && *Ptr <= '9') || + (*Ptr == '_'))) + break; + + /* Calculate length of this nickname */ + Nickname.PtrEnd = Ptr - 1; + Nickname.Length = (size_t) (Ptr - Nickname.PtrStart); + + /* A nick (without arroba) must have a number of characters + Nck_MIN_LENGTH_NICKNAME_WITHOUT_ARROBA <= Length <= Nck_MAX_LENGTH_NICKNAME_WITHOUT_ARROBA */ + IsNickname = (Nickname.Length >= Nck_MIN_LENGTH_NICKNAME_WITHOUT_ARROBA && + Nickname.Length <= Nck_MAX_LENGTH_NICKNAME_WITHOUT_ARROBA); + + if (IsNickname) + { + /* Copy nickname */ + strncpy (UsrDat.Nickname,Nickname.PtrStart,Nickname.Length); + UsrDat.Nickname[Nickname.Length] = '\0'; + + if ((UsrDat.UsrCod = Nck_GetUsrCodFromNickname (UsrDat.Nickname)) > 0) + if (UsrDat.UsrCod != Gbl.Usrs.Me.UsrDat.UsrCod) // It's not me + { + /* Get user's data */ + Usr_GetAllUsrDataFromUsrCod (&UsrDat); + + /* Create notification for the mentioned user *****/ + CreateNotif = (UsrDat.Prefs.NotifNtfEvents & (1 << Ntf_EVENT_TIMELINE_MENTION)); + if (CreateNotif) + { + NotifyByEmail = (UsrDat.Prefs.EmailNtfEvents & (1 << Ntf_EVENT_TIMELINE_MENTION)); + Ntf_StoreNotifyEventToOneUser (Ntf_EVENT_TIMELINE_MENTION,&UsrDat,PubCod, + (Ntf_Status_t) (NotifyByEmail ? Ntf_STATUS_BIT_EMAIL : + 0)); + } + } + } + } + /* The next char is not the start of a nickname */ + else // Character != '@' + Ptr++; + + /***** Free memory used for user's data *****/ + Usr_UsrDataDestructor (&UsrDat); + } diff --git a/swad_social.h b/swad_social.h index bab196409..dff6a8284 100644 --- a/swad_social.h +++ b/swad_social.h @@ -87,6 +87,26 @@ typedef enum } Soc_NoteType_t; +typedef enum + { + Soc_TOP_MESSAGE_NONE, + Soc_TOP_MESSAGE_SHARED, + Soc_TOP_MESSAGE_UNSHARED, + Soc_TOP_MESSAGE_FAV, + Soc_TOP_MESSAGE_UNFAV, + Soc_TOP_MESSAGE_COMMENTED, + } Soc_TopMessage_t; + +struct SocialPublishing + { + long PubCod; + long NotCod; + long PublisherCod; // Sharer or writer of a comment + Soc_PubType_t PubType; + time_t DateTimeUTC; + Soc_TopMessage_t TopMessage; // Used to show feedback on the action made + }; + /*****************************************************************************/ /****************************** Public prototypes ****************************/ /*****************************************************************************/ @@ -99,7 +119,7 @@ void Soc_RefreshNewTimelineGbl (void); void Soc_RefreshOldTimelineGbl (void); void Soc_RefreshOldTimelineUsr (void); -long Soc_StoreAndPublishSocialNote (Soc_NoteType_t NoteType,long Cod); +void Soc_StoreAndPublishSocialNote (Soc_NoteType_t NoteType,long Cod,struct SocialPublishing *SocPub); void Soc_MarkSocialNoteAsUnavailableUsingNotCod (long NotCod); void Soc_MarkSocialNoteAsUnavailableUsingNoteTypeAndCod (Soc_NoteType_t NoteType,long Cod); void Soc_MarkSocialNoteOneFileAsUnavailable (const char *Path); diff --git a/swad_string.c b/swad_string.c index b0e947b2d..cae20aba7 100644 --- a/swad_string.c +++ b/swad_string.c @@ -213,7 +213,7 @@ void Str_InsertLinks (char *Txt,unsigned long MaxLength,size_t MaxCharsURLOnScre } /* Calculate length of this URL */ - Links[NumLinks].NumActualBytes = (size_t) (Links[NumLinks].PtrEnd - Links[NumLinks].PtrStart) + 1; + Links[NumLinks].NumActualBytes = (size_t) (Links[NumLinks].PtrEnd + 1 - Links[NumLinks].PtrStart); if (Links[NumLinks].NumActualBytes <= MaxCharsURLOnScreen) LinksTotalLength += Links[NumLinks].NumActualBytes; else // If URL is too long to be displayed ==> short it @@ -260,7 +260,7 @@ void Str_InsertLinks (char *Txt,unsigned long MaxLength,size_t MaxCharsURLOnScre /* Calculate length of this nickname */ Links[NumLinks].PtrEnd = PtrSrc - 1; - Links[NumLinks].NumActualBytes = (size_t) (Links[NumLinks].PtrEnd - Links[NumLinks].PtrStart) + 1; + Links[NumLinks].NumActualBytes = (size_t) (PtrSrc - Links[NumLinks].PtrStart); /* A nick (without arroba) must have a number of characters Nck_MIN_LENGTH_NICKNAME_WITHOUT_ARROBA <= Length <= Nck_MAX_LENGTH_NICKNAME_WITHOUT_ARROBA */ @@ -285,7 +285,7 @@ void Str_InsertLinks (char *Txt,unsigned long MaxLength,size_t MaxCharsURLOnScre } } /* The next char is not the start of URL or nickname */ - else // Character != 'h' + else // Character distinct to 'h' or '@' PtrSrc++; /***** If there are one or more links (URLs or nicknames) in text *****/