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 *****/