mirror of https://github.com/acanas/swad-core.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
562 lines
20 KiB
C
562 lines
20 KiB
C
// swad_timeline_publication.c: social timeline publications
|
|
|
|
/*
|
|
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-2023 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
/*****************************************************************************/
|
|
/*********************************** Headers *********************************/
|
|
/*****************************************************************************/
|
|
|
|
#include <stdlib.h> // For malloc and free
|
|
#include <string.h> // For string functions
|
|
|
|
#include "swad_error.h"
|
|
#include "swad_follow.h"
|
|
#include "swad_follow_database.h"
|
|
#include "swad_global.h"
|
|
#include "swad_parameter.h"
|
|
#include "swad_profile.h"
|
|
#include "swad_profile_database.h"
|
|
#include "swad_timeline.h"
|
|
#include "swad_timeline_database.h"
|
|
#include "swad_timeline_note.h"
|
|
#include "swad_timeline_publication.h"
|
|
|
|
/*****************************************************************************/
|
|
/***************************** Private prototypes ****************************/
|
|
/*****************************************************************************/
|
|
|
|
static void TmlPub_InitializeRangeOfPubs (Tml_WhatToGet_t WhatToGet,
|
|
struct TmlPub_RangePubsToGet *RangePubsToGet);
|
|
|
|
static unsigned TmlPub_GetMaxPubsToGet (const struct Tml_Timeline *Timeline);
|
|
|
|
static void TmlPub_UpdateFirstLastPubCodesIntoSession (const struct Tml_Timeline *Timeline);
|
|
|
|
static struct TmlPub_Publication *TmlPub_SelectTheMostRecentPub (const struct TmlPub_SubQueries *SubQueries);
|
|
|
|
static TmlPub_Type_t TmlPub_GetPubTypeFromStr (const char *Str);
|
|
|
|
/*****************************************************************************/
|
|
/*************** Get list of pubications to show in timeline *****************/
|
|
/*****************************************************************************/
|
|
/*
|
|
_ ______________________
|
|
/ |______________________| Tml_GET_ONLY_NEW_PUBS
|
|
New < |______________________| automatically from time to time
|
|
\_|______________________| (AJAX)
|
|
_|_See_new_activity_(3)_|
|
|
/ |______________________| Tml_GET_RECENT_TIMELINE
|
|
| |______________________| user clicks on action menu
|
|
Recent < |______________________| or after editing timeline
|
|
| |______________________|
|
|
\_|______________________|
|
|
_|_______See_more_______|
|
|
/ |______________________| Tml_GET_ONLY_OLD_PUBS
|
|
| |______________________| user clicks on bottom link
|
|
Old < |______________________| (AJAX)
|
|
| |______________________|
|
|
\_|______________________|
|
|
*/
|
|
void TmlPub_GetListPubsToShowInTimeline (struct Tml_Timeline *Timeline)
|
|
{
|
|
struct TmlPub_SubQueries SubQueries;
|
|
struct TmlPub_RangePubsToGet RangePubsToGet;
|
|
unsigned MaxPubsToGet = TmlPub_GetMaxPubsToGet (Timeline);
|
|
unsigned NumPub;
|
|
struct TmlPub_Publication *Pub;
|
|
|
|
/***** Initialize range of publications *****/
|
|
TmlPub_InitializeRangeOfPubs (Timeline->WhatToGet,&RangePubsToGet);
|
|
|
|
/***** Clear timeline for this session in database *****/
|
|
if (Timeline->WhatToGet == Tml_GET_REC_PUBS)
|
|
Tml_DB_ClearTimelineNotesOfSessionFromDB ();
|
|
|
|
/***** Create temporary table with all notes visible in timeline *****/
|
|
Tml_DB_CreateTmpTableTimeline (Timeline->WhatToGet);
|
|
|
|
/***** Create subqueries *****/
|
|
/* Create subquery with potential publishers */
|
|
Tml_DB_CreateSubQueryPublishers (Timeline->UsrOrGbl,Timeline->Who,
|
|
&SubQueries.Publishers.Table,
|
|
SubQueries.Publishers.SubQuery);
|
|
|
|
/* Create subquery with bottom range of publications to get from tml_pubs.
|
|
Bottom pub. code remains unchanged in all iterations of the loop. */
|
|
Tml_DB_CreateSubQueryRange (TmlPub_BOTTOM,
|
|
RangePubsToGet.Bottom,
|
|
SubQueries.Range.Bottom);
|
|
|
|
/***** Initialize list of publications *****/
|
|
/* Chained list of publications:
|
|
|
|
Timeline->Pubs.Top Pub #0
|
|
______ ______ Pub #1
|
|
|______|------>|______| ______ Pub #2
|
|
|______| -> |______| ______ Pub #3
|
|
|______| / |______| ->|______| ______
|
|
|______| / |______| / |______| ->|______|
|
|
|_Next_|-- |______| / |______| // |______|
|
|
more recent |_Next_|-- |______| // |______|
|
|
______ |_Next_|--/ |______|
|
|
|______|---------------------------------------------- |_NULL_|
|
|
older
|
|
Timeline->Pubs.Bottom
|
|
|
|
*/
|
|
Timeline->Pubs.Top =
|
|
Timeline->Pubs.Bottom = NULL;
|
|
|
|
/***** Get the publications in timeline *****/
|
|
/* With the current approach, we select one by one
|
|
the publications and notes in a loop.
|
|
In each iteration:
|
|
we get the most recent publication (original, shared or comment)
|
|
of every set of publications corresponding to the same note,
|
|
checking that the note is not already retrieved.
|
|
After getting a publication, its note code is saved
|
|
in order to not get it again.
|
|
|
|
As an alternative, we tried to get the maximum PubCod,
|
|
i.e more recent publication (original, shared or comment),
|
|
of every set of publications corresponding to the same note:
|
|
SELECT MAX(PubCod) AS NewestPubCod
|
|
FROM tml_pubs ...
|
|
GROUP BY NotCod
|
|
ORDER BY NewestPubCod DESC
|
|
LIMIT 10
|
|
but this query is slow (several seconds) with a big table.
|
|
*/
|
|
for (NumPub = 0;
|
|
NumPub < MaxPubsToGet;
|
|
NumPub++)
|
|
{
|
|
/* Create subquery with top range of publications to get from tml_pubs
|
|
In each iteration of this loop, top publication code is changed to a lower value */
|
|
Tml_DB_CreateSubQueryRange (TmlPub_TOP,
|
|
RangePubsToGet.Top,
|
|
SubQueries.Range.Top);
|
|
|
|
/* Select the most recent publication from tml_pubs */
|
|
Pub = TmlPub_SelectTheMostRecentPub (&SubQueries);
|
|
|
|
/* Chain the previous publication with the current one */
|
|
if (NumPub == 0)
|
|
Timeline->Pubs.Top = Pub; // Pointer to top publication
|
|
else
|
|
Timeline->Pubs.Bottom->Next = Pub; // Chain the previous publication with the current one
|
|
Timeline->Pubs.Bottom = Pub; // Update pointer to bottom publication
|
|
|
|
if (Pub == NULL) // Nothing got ==> abort loop
|
|
break; // Last publication
|
|
|
|
/* Insert note in temporary tables with visible notes.
|
|
These tables will be used to not get notes already shown */
|
|
Tml_DB_InsertNoteInTimeline (Pub->NotCod);
|
|
|
|
/* Narrow the range for the next iteration */
|
|
RangePubsToGet.Top = Pub->PubCod;
|
|
}
|
|
|
|
/***** Update first (oldest) and last (more recent) publication codes
|
|
into session for next refresh *****/
|
|
TmlPub_UpdateFirstLastPubCodesIntoSession (Timeline);
|
|
|
|
/***** Drop temporary tables *****/
|
|
/* Drop temporary table with visible notes in timeline */
|
|
Tml_DB_DropTmpTableTimeline ();
|
|
|
|
/* Drop temporary table with me and users I follow */
|
|
if (Timeline->UsrOrGbl == TmlUsr_TIMELINE_GBL && // Show the global timeline
|
|
Timeline->Who == Usr_WHO_FOLLOWED) // Show the timeline of the users I follow
|
|
Fol_DB_DropTmpTableMeAndUsrsIFollow ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*************** Get list of pubications to show in timeline *****************/
|
|
/*****************************************************************************/
|
|
|
|
static void TmlPub_InitializeRangeOfPubs (Tml_WhatToGet_t WhatToGet,
|
|
struct TmlPub_RangePubsToGet *RangePubsToGet)
|
|
{
|
|
/* tml_pubs
|
|
_____ 0
|
|
|_____|11
|
|
|_____|10
|
|
_|_____| 9 <-- RangePubsToGet.Top
|
|
Get / |_____| 8
|
|
pubs | |_____| 7
|
|
from < |_____| 6
|
|
this | |_____| 5
|
|
range \_|_____| 4
|
|
|_____| 3 <-- RangePubsToGet.Bottom
|
|
|_____| 2
|
|
|_____| 1
|
|
0 */
|
|
switch (WhatToGet)
|
|
{
|
|
case Tml_GET_NEW_PUBS: // Get the publications (without limit)
|
|
// newer than last pub. code
|
|
/* Via AJAX automatically from time to time */
|
|
RangePubsToGet->Top = 0;
|
|
/* _ _____ 0 <-- RangePubsToGet.Top = +infinite
|
|
Get / |_____|11
|
|
these < |_____|10
|
|
pubs \_|_____| 9
|
|
/ |_____| 8 <-- RangePubsToGet.Bottom = last pub. code
|
|
Pubs | |_____| 7
|
|
already < |_____| 6
|
|
shown | |_____| 5
|
|
| |_____| 4
|
|
. |_____| .
|
|
. |_____| .
|
|
. |_____| .
|
|
*/
|
|
RangePubsToGet->Bottom = Tml_DB_GetPubCodFromSession (TmlPub_LAST);
|
|
break;
|
|
case Tml_GET_REC_PUBS: // Get some limited recent publications
|
|
/* First query to get initial timeline shown
|
|
==> no notes yet in current timeline table */
|
|
RangePubsToGet->Top = 0;
|
|
/* _ _____ 0 <-- RangePubsToGet.Top = +infinite
|
|
/ |_____| 8
|
|
Get | |_____| 7
|
|
pubs < |_____| 6
|
|
from | |_____| 5
|
|
all . |_____| 4
|
|
range . |_____| 3
|
|
. |_____| 2
|
|
|_____| 1
|
|
0 <-- RangePubsToGet.Bottom = -infinite */
|
|
RangePubsToGet->Bottom = 0;
|
|
break;
|
|
case Tml_GET_OLD_PUBS: // Get some limited publications
|
|
// older than first pub. code
|
|
/* Via AJAX when I click in link to get old publications */
|
|
RangePubsToGet->Top = Tml_DB_GetPubCodFromSession (TmlPub_FIRST);
|
|
/* _____
|
|
. |_____| .
|
|
. |_____| .
|
|
. |_____| .
|
|
Pubs | |_____| 8
|
|
already < |_____| 7
|
|
shown | |_____| 6
|
|
| |_____| 5
|
|
Get \_|_____| 4 <-- RangePubsToGet.Top = first pub. code
|
|
pubs / |_____| 3
|
|
from < |_____| 2
|
|
this \_|_____| 1
|
|
rage 0 <-- RangePubsToGet.Bottom = -infinite */
|
|
RangePubsToGet->Bottom = 0;
|
|
break;
|
|
default: // Not reached
|
|
RangePubsToGet->Top =
|
|
RangePubsToGet->Bottom = 0; // Initialized to avoid warning
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********* Get maximum number of publications to get from database ***********/
|
|
/*****************************************************************************/
|
|
|
|
static unsigned TmlPub_GetMaxPubsToGet (const struct Tml_Timeline *Timeline)
|
|
{
|
|
static const unsigned MaxPubsToGet[Tml_NUM_WHAT_TO_GET] =
|
|
{
|
|
[Tml_GET_NEW_PUBS] = TmlPub_MAX_NEW_PUBS_TO_GET_AND_SHOW,
|
|
[Tml_GET_REC_PUBS] = TmlPub_MAX_REC_PUBS_TO_GET_AND_SHOW,
|
|
[Tml_GET_OLD_PUBS] = TmlPub_MAX_OLD_PUBS_TO_GET_AND_SHOW,
|
|
};
|
|
|
|
return MaxPubsToGet[Timeline->WhatToGet];
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************* Update first (oldest) and last (more recent) ***************/
|
|
/************* publication codes into session for next refresh ***************/
|
|
/*****************************************************************************/
|
|
|
|
static void TmlPub_UpdateFirstLastPubCodesIntoSession (const struct Tml_Timeline *Timeline)
|
|
{
|
|
long FirstPubCod;
|
|
|
|
switch (Timeline->WhatToGet)
|
|
{
|
|
case Tml_GET_NEW_PUBS: // Get only new publications
|
|
Tml_DB_UpdateLastPubCodInSession ();
|
|
break;
|
|
case Tml_GET_REC_PUBS: // Get recent publications
|
|
case Tml_GET_OLD_PUBS: // Get only old publications
|
|
// The oldest publication code retrieved and shown
|
|
FirstPubCod = Timeline->Pubs.Bottom ? Timeline->Pubs.Bottom->PubCod :
|
|
0;
|
|
if (Timeline->WhatToGet == Tml_GET_REC_PUBS)
|
|
Tml_DB_UpdateFirstLastPubCodsInSession (FirstPubCod);
|
|
else
|
|
Tml_DB_UpdateFirstPubCodInSession (FirstPubCod);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************** Free chained list of publications in timeline ****************/
|
|
/*****************************************************************************/
|
|
|
|
void TmlPub_FreeListPubs (struct Tml_Timeline *Timeline)
|
|
{
|
|
struct TmlPub_Publication *Pub;
|
|
struct TmlPub_Publication *Next;
|
|
|
|
/***** Go over the list freeing memory *****/
|
|
for (Pub = Timeline->Pubs.Top;
|
|
Pub;
|
|
Pub = Next)
|
|
{
|
|
/* Save a copy of pointer to next element before freeing it */
|
|
Next = Pub->Next;
|
|
|
|
/* Free memory used for this publication */
|
|
free (Pub);
|
|
}
|
|
|
|
/***** Reset pointers to top and bottom elements *****/
|
|
Timeline->Pubs.Top =
|
|
Timeline->Pubs.Bottom = NULL;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************** Select the most recent publication from tml_pubs *************/
|
|
/*****************************************************************************/
|
|
|
|
static struct TmlPub_Publication *TmlPub_SelectTheMostRecentPub (const struct TmlPub_SubQueries *SubQueries)
|
|
{
|
|
MYSQL_RES *mysql_res;
|
|
struct TmlPub_Publication *Pub;
|
|
|
|
/***** Select the most recent publication from database *****/
|
|
if (Tml_DB_SelectTheMostRecentPub (&mysql_res,SubQueries) == 1)
|
|
{
|
|
/* Allocate space for publication */
|
|
if ((Pub = malloc (sizeof (*Pub))) == NULL)
|
|
Err_NotEnoughMemoryExit ();
|
|
|
|
/* Get data of publication */
|
|
TmlPub_GetPubDataFromRow (mysql_res,Pub);
|
|
Pub->Next = NULL;
|
|
}
|
|
else
|
|
Pub = NULL;
|
|
|
|
/***** Free structure that stores the query result *****/
|
|
DB_FreeMySQLResult (&mysql_res);
|
|
|
|
return Pub;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************* Show new publications in timeline ***********************/
|
|
/*****************************************************************************/
|
|
// The publications are inserted as list elements of a hidden list
|
|
|
|
void TmlPub_InsertNewPubsInTimeline (struct Tml_Timeline *Timeline)
|
|
{
|
|
struct TmlPub_Publication *Pub;
|
|
struct TmlNot_Note Not;
|
|
|
|
/***** List new publications in timeline *****/
|
|
for (Pub = Timeline->Pubs.Top;
|
|
Pub;
|
|
Pub = Pub->Next)
|
|
{
|
|
/* Get data of note */
|
|
Not.NotCod = Pub->NotCod;
|
|
TmlNot_GetNoteDataByCod (&Not);
|
|
|
|
/* Write note */
|
|
HTM_LI_Begin ("class=\"Tml_WIDTH Tml_SEP Tml_NEW_PUB_%s\""
|
|
" data-note-code=\"%ld\"", // Note code to be read later...
|
|
The_GetSuffix (), // ...from JavaScript...
|
|
Not.NotCod); // ...to avoid repeating notes
|
|
TmlNot_CheckAndWriteNoteWithTopMsg (Timeline,&Not,
|
|
TmlPub_GetTopMessage (Pub->Type),
|
|
Pub->PublisherCod);
|
|
HTM_LI_End ();
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********************* Show old publications in timeline *********************/
|
|
/*****************************************************************************/
|
|
// The publications are inserted as list elements of a hidden list
|
|
|
|
void TmlPub_ShowOldPubsInTimeline (struct Tml_Timeline *Timeline)
|
|
{
|
|
struct TmlPub_Publication *Pub;
|
|
struct TmlNot_Note Not;
|
|
|
|
/***** List old publications in timeline *****/
|
|
for (Pub = Timeline->Pubs.Top;
|
|
Pub;
|
|
Pub = Pub->Next)
|
|
{
|
|
/* Get data of note */
|
|
Not.NotCod = Pub->NotCod;
|
|
TmlNot_GetNoteDataByCod (&Not);
|
|
|
|
/* Write note */
|
|
HTM_LI_Begin ("class=\"Tml_WIDTH Tml_SEP\"");
|
|
TmlNot_CheckAndWriteNoteWithTopMsg (Timeline,&Not,
|
|
TmlPub_GetTopMessage (Pub->Type),
|
|
Pub->PublisherCod);
|
|
HTM_LI_End ();
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************* Get a top message given the type of publication ***************/
|
|
/*****************************************************************************/
|
|
|
|
Tml_TopMessage_t TmlPub_GetTopMessage (TmlPub_Type_t PubType)
|
|
{
|
|
static const Tml_TopMessage_t TopMessages[TmlPub_NUM_PUB_TYPES] =
|
|
{
|
|
[TmlPub_SHARED_NOTE ] = Tml_TOP_MESSAGE_SHARED,
|
|
[TmlPub_COMMENT_TO_NOTE] = Tml_TOP_MESSAGE_COMMENTED,
|
|
};
|
|
|
|
return TopMessages[PubType];
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************** Put link to view new publications in timeline *************/
|
|
/*****************************************************************************/
|
|
|
|
void TmlPub_PutLinkToViewNewPubs (void)
|
|
{
|
|
extern const char *Txt_See_new_activity;
|
|
|
|
/***** Link to view (show hidden) new publications *****/
|
|
/* Begin container */
|
|
// div is hidden. When new notes arrive to the client via AJAX, div is shown
|
|
HTM_DIV_Begin ("id=\"view_new_container\""
|
|
" class=\"Tml_WIDTH Tml_SEP BG_HIGHLIGHT\""
|
|
" style=\"display:none;\"");
|
|
|
|
/* Begin anchor */
|
|
HTM_A_Begin ("href=\"\" class=\"FORM_IN_%s BOLD\""
|
|
" onclick=\"moveNewTimelineToTimeline();return false;\"",
|
|
The_GetSuffix ());
|
|
|
|
/* Text */
|
|
HTM_TxtF ("%s (",Txt_See_new_activity);
|
|
HTM_SPAN_Begin ("id=\"view_new_count\"");
|
|
HTM_Unsigned (0);
|
|
HTM_SPAN_End ();
|
|
HTM_Txt (")");
|
|
|
|
/* End anchor */
|
|
HTM_A_End ();
|
|
|
|
/* End container */
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************** Put link to view old publications in timeline *************/
|
|
/*****************************************************************************/
|
|
|
|
void TmlPub_PutLinkToViewOldPubs (void)
|
|
{
|
|
extern const char *Txt_See_more;
|
|
|
|
/***** Begin container *****/
|
|
HTM_DIV_Begin ("id=\"view_old_pubs_container\""
|
|
" class=\"Tml_WIDTH Tml_SEP BG_HIGHLIGHT\"");
|
|
|
|
/***** Put button to refresh *****/
|
|
HTM_BUTTON_Begin (Txt_See_more,
|
|
"class=\"BT_LINK FORM_IN_%s BOLD\""
|
|
" onclick=\"refreshOldTimeline();return false;\"",
|
|
The_GetSuffix ());
|
|
Ico_PutIconTextUpdate (Txt_See_more);
|
|
HTM_BUTTON_End ();
|
|
|
|
/***** End container *****/
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************** Get data of publication using its code ********************/
|
|
/*****************************************************************************/
|
|
|
|
void TmlPub_GetPubDataFromRow (MYSQL_RES *mysql_res,
|
|
struct TmlPub_Publication *Pub)
|
|
{
|
|
MYSQL_ROW row;
|
|
|
|
/***** Get next row from result *****/
|
|
row = mysql_fetch_row (mysql_res);
|
|
/*
|
|
row[0]: PubCod
|
|
row[1]: NotCod
|
|
row[2]: PublisherCod
|
|
row[3]: PubType
|
|
*/
|
|
/***** Get code of publication (row[0]), code of note (row[1]),
|
|
publisher's code (row[2]) and type of publication (row[3]) *****/
|
|
Pub->PubCod = Str_ConvertStrCodToLongCod (row[0]);
|
|
Pub->NotCod = Str_ConvertStrCodToLongCod (row[1]);
|
|
Pub->PublisherCod = Str_ConvertStrCodToLongCod (row[2]);
|
|
Pub->Type = TmlPub_GetPubTypeFromStr (row[3]);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******* Get publication type from string number coming from database ********/
|
|
/*****************************************************************************/
|
|
|
|
static TmlPub_Type_t TmlPub_GetPubTypeFromStr (const char *Str)
|
|
{
|
|
unsigned UnsignedNum;
|
|
|
|
/***** Get publication type from string *****/
|
|
if (sscanf (Str,"%u",&UnsignedNum) == 1)
|
|
if (UnsignedNum < TmlPub_NUM_PUB_TYPES)
|
|
return (TmlPub_Type_t) UnsignedNum;
|
|
|
|
return TmlPub_UNKNOWN;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********************* Publish note/comment in timeline **********************/
|
|
/*****************************************************************************/
|
|
// Pub->PubCod is set by the function
|
|
|
|
void TmlPub_PublishPubInTimeline (struct TmlPub_Publication *Pub)
|
|
{
|
|
/***** Publish note in timeline *****/
|
|
Pub->PubCod = Tml_DB_CreateNewPub (Pub);
|
|
|
|
/***** Increment number of publications in user's figures *****/
|
|
Prf_DB_IncrementNumTimelinePubsUsr (Pub->PublisherCod);
|
|
}
|