Fossil

Check-in [f7938ebd]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:More infrastructure for wiki and ticket moderation.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | moderation
Files: files | file ages | folders
SHA1: f7938ebd00ee6d47fa7684c44de4be68dc778a58
User & Date: drh 2012-11-01 13:11:52
Context
2012-11-01
14:34
Wiki moderation now appears to be working. check-in: b7ccf110 user: drh tags: moderation
13:11
More infrastructure for wiki and ticket moderation. check-in: f7938ebd user: drh tags: moderation
2012-10-31
23:07
Improvements to the display of Wiki and Ticket changes. check-in: ba0ae3b2 user: drh tags: moderation
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/info.c.

1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
....
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678





1679
1680
1681
1682
1683
1684
1685
**
** Show the details of a ticket change control artifact.
*/
void tinfo_page(void){
  int rid;
  char *zDate;
  const char *zUuid;
  char zTktName[20];
  Manifest *pTktChng;

  login_check_credentials();
  if( !g.perm.RdTkt ){ login_needed(); return; }
  rid = name_to_rid_www("name");
  if( rid==0 ){ fossil_redirect_home(); }
  zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
................................................................................
  }
  pTktChng = manifest_get(rid, CFTYPE_TICKET);
  if( pTktChng==0 ){
    fossil_redirect_home();
  }
  style_header("Ticket Change Details");
  zDate = db_text(0, "SELECT datetime(%.12f)", pTktChng->rDate);
  memcpy(zTktName, pTktChng->zTicketUuid, 10);
  zTktName[10] = 0;
  if( g.perm.Hyperlink ){
    @ <h2>Changes to ticket
    @ %z(href("%R/tktview/%s",pTktChng->zTicketUuid))%s(zTktName)</a></h2>
    @
    @ <p>By %h(pTktChng->zUser) on %s(zDate).
    style_submenu_element("Raw", "Raw", "%R/artifact/%T", zUuid);
    style_submenu_element("History", "History", 
             "%R/tkthistory/%s", pTktChng->zTicketUuid);
  }else{
    @ <h2>Changes to ticket %s(zTktName)</h2>
    @
    @ <p>By %h(pTktChng->zUser) on %s(zDate).
    @ </p>
  }
  @
  @ <ol>
  free(zDate);
  ticket_output_change_artifact(pTktChng);
  manifest_destroy(pTktChng);





  style_footer();
}


/*
** WEBPAGE: info
** URL: info/ARTIFACTID







|







 







|
|


|











<
<



>
>
>
>
>







1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
....
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673


1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
**
** Show the details of a ticket change control artifact.
*/
void tinfo_page(void){
  int rid;
  char *zDate;
  const char *zUuid;
  char zTktName[UUID_SIZE+1];
  Manifest *pTktChng;

  login_check_credentials();
  if( !g.perm.RdTkt ){ login_needed(); return; }
  rid = name_to_rid_www("name");
  if( rid==0 ){ fossil_redirect_home(); }
  zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
................................................................................
  }
  pTktChng = manifest_get(rid, CFTYPE_TICKET);
  if( pTktChng==0 ){
    fossil_redirect_home();
  }
  style_header("Ticket Change Details");
  zDate = db_text(0, "SELECT datetime(%.12f)", pTktChng->rDate);
  memcpy(zTktName, pTktChng->zTicketUuid, UUID_SIZE);
  zTktName[UUID_SIZE] = 0;
  if( g.perm.Hyperlink ){
    @ <h2>Changes to ticket
    @ %z(href("%R/tktview/%s",zTktName))%s(zTktName)</a></h2>
    @
    @ <p>By %h(pTktChng->zUser) on %s(zDate).
    style_submenu_element("Raw", "Raw", "%R/artifact/%T", zUuid);
    style_submenu_element("History", "History", 
             "%R/tkthistory/%s", pTktChng->zTicketUuid);
  }else{
    @ <h2>Changes to ticket %s(zTktName)</h2>
    @
    @ <p>By %h(pTktChng->zUser) on %s(zDate).
    @ </p>
  }


  free(zDate);
  ticket_output_change_artifact(pTktChng);
  manifest_destroy(pTktChng);
  if( g.perm.Setup ){
    @
    @ <p>These changes are implemented by artifact
    @ %z(href("%R/artifact/%s",zUuid))%s(zUuid)</a> (%d(rid)).</p>
  }
  style_footer();
}


/*
** WEBPAGE: info
** URL: info/ARTIFACTID

Changes to src/moderate.c.

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

40

41

















































42




43






















































** This file contains code used to deal with moderator actions for
** Wiki and Tickets.
*/
#include "config.h"
#include "moderate.h"
#include <assert.h>


/*
** Create a table to represent pending moderation requests, if the
** table does not already exist.
*/
void moderation_table_create(void){
  db_multi_exec(
     "CREATE TABLE IF NOT EXISTS modreq("
     "  mreqid INTEGER PRIMARY KEY,"  /* Unique ID for the request */
     "  objid INT UNIQUE,"            /* Record pending approval */
     "  ctime DATETIME,"              /* Julian day number */
     "  user TEXT,"                   /* Name of user submitter */
     "  ipaddr TEXT,"                 /* IP address of submitter */
     "  mtype TEXT,"                  /* 't', 'w', 'at', or 'aw' */
     "  afile INT,"                   /* File being attached, or NULL */

     "  aid ANY"                      /* TicketId or Wiki Name */

     ");"

















































  );




}





























































<






|
<
|
<
<
<
<
<
>
|
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
18
19
20
21
22
23
24

25
26
27
28
29
30
31

32





33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
** This file contains code used to deal with moderator actions for
** Wiki and Tickets.
*/
#include "config.h"
#include "moderate.h"
#include <assert.h>


/*
** Create a table to represent pending moderation requests, if the
** table does not already exist.
*/
void moderation_table_create(void){
  db_multi_exec(
     "CREATE TABLE IF NOT EXISTS modreq(\n"

     "  objid INTEGER PRIMARY KEY,\n"        /* Record pending approval */





     "  parent INTEGER REFERENCES modreq,\n" /* Parent record */
     "  tktid TEXT\n"                        /* Associated ticket id */
     ");\n"
  );
}

/*
** Return TRUE if the modreq table exists
*/
int moderation_table_exists(void){
  static int modreqExists = -1;
  if( modreqExists<0 ){
    modreqExists = db_exists("SELECT 1 FROM %s.sqlite_master"
                             " WHERE name='modreq'", db_name("repository"));
  }
  return modreqExists;
}

/*
** Return TRUE if the object specified is being held for moderation.
*/
int moderation_pending(int rid){
  static Stmt q;
  int rc;
  if( rid==0 || !moderation_table_exists() ) return 0;
  db_static_prepare(&q, "SELECT 1 FROM modreq WHERE objid=:objid");
  db_bind_int(&q, ":objid", rid);
  rc = db_step(&q)==SQLITE_ROW;
  db_reset(&q);
  return rc;
}

/*
** Delete a moderation item given by rid
*/
void moderation_disapprove(int rid){
  Stmt q;
  char *zTktid;
  if( !moderation_pending(rid) ) return;
  if( content_is_private(rid) ){
    db_prepare(&q, "SELECT rid FROM delta WHERE srcid=%d", rid);
    while( db_step(&q)==SQLITE_ROW ){
      int ridUser = db_column_int(&q, 0);
      content_undelta(ridUser);
    }
    db_finalize(&q);
    db_multi_exec(
      "DELETE FROM blob WHERE rid=%d;"
      "DELETE FROM delta WHERE rid=%d;"
      "DELETE FROM event WHERE objid=%d;"
      "DELETE FROM tagxref WHERE rid=%d;"
      "DELETE FROM private WHERE rid=%d;",
      rid, rid, rid, rid, rid
    );
    zTktid = db_text(0, "SELECT tktid FROm modreq WHERE objid=%d", rid);
    if( zTktid ){
      ticket_rebuild_entry(zTktid);
      fossil_free(zTktid);
    }
  }
  db_prepare(&q, "SELECT objid FROM modreq WHERE parent=%d AND "
                 "UNION SELECT parent FROM modreq WHERE objid=%d",
                 rid, rid);
  while( db_step(&q)==SQLITE_ROW ){
    int other = db_column_int(&q, 0);
    if( other==rid ) continue;
    moderation_approve(other);
  }
  db_finalize(&q);
  db_multi_exec("DELETE FROM modreq WHERE objid=%d", rid);
}

/*
** Approve an object held for moderation.
*/
void moderation_approve(int rid){
  Stmt q;
  if( !moderation_pending(rid) ) return;
  db_multi_exec("DELETE FROM private WHERE rid=%d;", rid);
  db_prepare(&q, "SELECT objid FROM modreq WHERE parent=%d AND "
                 "UNION SELECT parent FROM modreq WHERE objid=%d",
                 rid, rid);
  while( db_step(&q)==SQLITE_ROW ){
    int other = db_column_int(&q, 0);
    if( other==rid ) continue;
    moderation_approve(other);
  }
  db_finalize(&q);
  db_multi_exec("DELETE FROM modreq WHERE objid=%d", rid);
}

/*
** WEBPAGE: modreq
**
** Show all pending moderation request
*/
void modreq_page(void){
  Blob sql;
  Stmt q;

  login_check_credentials();
  if( !g.perm.RdWiki && !g.perm.RdTkt ){ login_needed(); return; }
  style_header("Pending Moderation Requests");
  blob_init(&sql, timeline_query_for_www(), -1);
  blob_appendf(&sql,
      " AND event.objid IN (SELECT objid FROM modreq)"
      " ORDER BY event.mtime DESC"
  );
  db_prepare(&q, blob_str(&sql));
  www_print_timeline(&q, 0, 0, 0, 0);
  db_finalize(&q);
  style_footer();
}

Changes to src/style.c.

928
929
930
931
932
933
934
935
936
937





938
939
940
941
942
943
944
    @   background-color: #ffc8c8;
  },
  { "span.diffhr",
    "suppressed lines in a diff",
    @   color: #0000ff;
  },
  { "span.diffln",
    "line nubmers in a diff",
    @   color: #a0a0a0;
  },





  { 0,
    0,
    0
  }
};

/*







|


>
>
>
>
>







928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
    @   background-color: #ffc8c8;
  },
  { "span.diffhr",
    "suppressed lines in a diff",
    @   color: #0000ff;
  },
  { "span.diffln",
    "line numbers in a diff",
    @   color: #a0a0a0;
  },
  { "span.modpending",
    "Moderation Pending message on timelin",
    @   color: #b03800;
    @   font-style: italic;
  },
  { 0,
    0,
    0
  }
};

/*

Changes to src/timeline.c.

234
235
236
237
238
239
240

241


242

243
244
245
246
247
248
249
...
318
319
320
321
322
323
324



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
...
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
    const char *zDate = db_column_text(pQuery, 2);
    const char *zType = db_column_text(pQuery, 7);
    const char *zUser = db_column_text(pQuery, 4);
    const char *zTagList = db_column_text(pQuery, 8);
    int tagid = db_column_int(pQuery, 9);
    const char *zBr = 0;      /* Branch */
    int commentColumn = 3;    /* Column containing comment text */

    char zTime[8];


    if( tagid ){

      if( tagid==prevTagid ){
        if( tmFlags & TIMELINE_BRIEF ){
          suppressCnt++;
          continue;
        }else{
          commentColumn = 10;
        }
................................................................................
      @ <td class="timelineTableCell" style="background-color: %h(zBgClr);">
    }else{
      @ <td class="timelineTableCell">
    }
    if( pGraph && zType[0]!='c' ){
      @ &bull;
    }



    if( zType[0]=='c' ){
      hyperlink_to_uuid(zUuid);
      if( isLeaf ){
        if( db_exists("SELECT 1 FROM tagxref"
                      " WHERE rid=%d AND tagid=%d AND tagtype>0",
                      rid, TAG_CLOSED) ){
          @ <span class="timelineLeaf">Closed-Leaf:</span>
        }else{
          @ <span class="timelineLeaf">Leaf:</span>
        }
      }
    }else if( zType[0]=='e' && tagid ){
      hyperlink_to_event_tagid(tagid);
    }else if( (tmFlags & TIMELINE_ARTID)!=0 ){
      hyperlink_to_uuid(zUuid);
    }
    db_column_blob(pQuery, commentColumn, &comment);
    if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
      Blob truncated;
      blob_zero(&truncated);
................................................................................
      char *zLink = mprintf("%R/timeline?u=%h&c=%t&nd", zUser, zDate);
      @ (user: %z(href("%z",zLink))%h(zUser)</a>%s(zTagList?",":"\051")
    }else{
      @ (user: %h(zUser)%s(zTagList?",":"\051")
    }

    /* Generate a "detail" link for tags. */
    if( zType[0]=='g' && g.perm.Hyperlink ){
      @ [%z(href("%R/info/%S",zUuid))details</a>]
    }

    /* Generate the "tags: TAGLIST" at the end of the comment, together
    ** with hyperlinks to the tag list.
    */
    if( zTagList ){







>

>
>

>







 







>
>
>












|







 







|







234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
...
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
...
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
    const char *zDate = db_column_text(pQuery, 2);
    const char *zType = db_column_text(pQuery, 7);
    const char *zUser = db_column_text(pQuery, 4);
    const char *zTagList = db_column_text(pQuery, 8);
    int tagid = db_column_int(pQuery, 9);
    const char *zBr = 0;      /* Branch */
    int commentColumn = 3;    /* Column containing comment text */
    int modPending;           /* Pending moderation */
    char zTime[8];

    modPending =  moderation_pending(rid);
    if( tagid ){
      if( modPending ) tagid = -tagid;
      if( tagid==prevTagid ){
        if( tmFlags & TIMELINE_BRIEF ){
          suppressCnt++;
          continue;
        }else{
          commentColumn = 10;
        }
................................................................................
      @ <td class="timelineTableCell" style="background-color: %h(zBgClr);">
    }else{
      @ <td class="timelineTableCell">
    }
    if( pGraph && zType[0]!='c' ){
      @ &bull;
    }
    if( modPending ){
      @ <span class="modpending">(Pending Moderation)</span>
    }
    if( zType[0]=='c' ){
      hyperlink_to_uuid(zUuid);
      if( isLeaf ){
        if( db_exists("SELECT 1 FROM tagxref"
                      " WHERE rid=%d AND tagid=%d AND tagtype>0",
                      rid, TAG_CLOSED) ){
          @ <span class="timelineLeaf">Closed-Leaf:</span>
        }else{
          @ <span class="timelineLeaf">Leaf:</span>
        }
      }
    }else if( zType[0]=='e' && tagid ){
      hyperlink_to_event_tagid(tagid<0?-tagid:tagid);
    }else if( (tmFlags & TIMELINE_ARTID)!=0 ){
      hyperlink_to_uuid(zUuid);
    }
    db_column_blob(pQuery, commentColumn, &comment);
    if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
      Blob truncated;
      blob_zero(&truncated);
................................................................................
      char *zLink = mprintf("%R/timeline?u=%h&c=%t&nd", zUser, zDate);
      @ (user: %z(href("%z",zLink))%h(zUser)</a>%s(zTagList?",":"\051")
    }else{
      @ (user: %h(zUser)%s(zTagList?",":"\051")
    }

    /* Generate a "detail" link for tags. */
    if( (zType[0]=='g' || zType[0]=='w' || zType[0]=='t') && g.perm.Hyperlink ){
      @ [%z(href("%R/info/%S",zUuid))details</a>]
    }

    /* Generate the "tags: TAGLIST" at the end of the comment, together
    ** with hyperlinks to the tag list.
    */
    if( zTagList ){