⌈⌋ ⎇ branch:  freshcode


Check-in [108362e99b]

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

Overview
Comment:Add basic /meta forum, separate handler and database structure.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 108362e99b840518ae63379697a48dab9c2df071
User & Date: mario 2014-08-11 16:13:43
Context
2014-08-11
16:15
Use non-autoupdating autoloader. check-in: 23550cee89 user: mario tags: trunk, 0.6.5
16:13
Add basic /meta forum, separate handler and database structure. check-in: 108362e99b user: mario tags: trunk
16:12
Split Browser Projects by Tag into /names and /tags links. check-in: 7b214ee870 user: mario tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to .htaccess.

34
35
36
37
38
39
40
41

42
43
44
45
46
47
48
49
RewriteRule  ^projects/([\w-_]+)/releases/(\w+)\.json$  index.php?page=api&name=$1&api=version_%1&id=$2 [L,NS,QSA]
RewriteCond  %{REQUEST_METHOD}  ^(GET|PUT|POST|PUSH)$
RewriteRule  ^projects/([\w-_]+)/urls\.json$  index.php?page=api&name=$1&api=urls [L,NS,QSA] 


#-- Page dispatching
RewriteRule  ^$                 index.php?page=index    [L,NS,QSA]
RewriteRule  ^(projects|submit|search|flag|names?|tags?|feed|login|links|forum|rc|admin|drchangelog)\b/?(\w+(?:[-_]\w+)*)?(?:\.(json|atom|rss))?/?$   index.php?page=$1&name=$2&ext=$3   [L,NS,QSA]


#-- Deny direct invocations
RewriteRule  ^freshcode\.db.*$  -                       [F]
RewriteRule  ^\.                -                       [F]
RewriteCond  %{ENV:REDIRECT_STATUS}  !200
RewriteRule  ^\w+\.php(|/.*)$   -                       [F,L,NS]









|
>








34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
RewriteRule  ^projects/([\w-_]+)/releases/(\w+)\.json$  index.php?page=api&name=$1&api=version_%1&id=$2 [L,NS,QSA]
RewriteCond  %{REQUEST_METHOD}  ^(GET|PUT|POST|PUSH)$
RewriteRule  ^projects/([\w-_]+)/urls\.json$  index.php?page=api&name=$1&api=urls [L,NS,QSA] 


#-- Page dispatching
RewriteRule  ^$                 index.php?page=index    [L,NS,QSA]
RewriteRule  ^(projects|submit|search|flag|names?|tags?|feed|login|links|rc|admin|drchangelog)\b/?(\w+(?:[-_]\w+)*)?(?:\.(json|atom|rss))?/?$   index.php?page=$1&name=$2&ext=$3   [L,NS,QSA]
RewriteRule  ^(forum|meta)\b/?(\w+)?/?$   page_forum.php?name=$2   [L,NS,QSA]

#-- Deny direct invocations
RewriteRule  ^freshcode\.db.*$  -                       [F]
RewriteRule  ^\.                -                       [F]
RewriteCond  %{ENV:REDIRECT_STATUS}  !200
RewriteRule  ^\w+\.php(|/.*)$   -                       [F,L,NS]


Added forum.css.













































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/**
 * api: css
 * type: stylesheet
 * title: meta forum layout
 * description: Minimalistic forum style
 * version: 0.1
 *
 * Posts are just presented in a nested list,
 * and left-side title banner sticks out.
 *
 */


/**
 * Google fonts
 *
 */
@import url(http://fonts.googleapis.com/css?family=Hind:400,500,700,300);

/**
 * General
 *
 */
html, body { padding: 0; margin: 0; height: 100%; }
body {
/* font: 400 12pt/16pt Kreon;/
   font: 500 12pt/16pt Raleway;
   font: 500 12pt/16pt Numans;
   font: 500 12pt/16pt Inder;
   font: 400 12pt/16pt Voces;
   font: 400 12pt/16pt Magra;*/
   font: 400 12pt/16pt Hind;
}

/**
 * Title border
 *
 */
#title {
   display: block;
   position: fixed;
   left: 0;
   top: 0;
   background: #222;
   width: 150pt;
   float: left;
   height: 100%;
   min-height: 5000px;
   padding: 0;  margin: 0;
}
h1 {
   transform: rotate(270deg);
   color: #fff;
   font-size: 72pt;
   position: relative;
   top: 400pt;
   white-space: nowrap;
   letter-spacing: -0.025em;
}
h1 { font-weight: 300; }
h1 b { font-weight: 700; }
h1 .red { color: #744; }
h1 .grey { color: #444; }

/**
 * Forum tree
 *
 */
.forum, .forum ul {
   padding: 5pt 0 0 30pt;
   list-style: none;
}
ul.forum {
   padding: 5pt;
   padding-left: 180pt;
   list-style: none;
   padding-bottom: 90pt;
}

/**
 * One post block (wrapped in <li>)
 *
 */
.forum .entry {
   display: block;
   width: 600pt;
/*   background: #eee;
   border: 1px solid #ddd; */
   min-height: 30pt;
   padding-left: 5pt;
   margin-top: 15pt;
}

/**
 * Tag / author / time - left-rotated meta info.
 *
 */
.forum .entry .meta {
}
.forum .entry .meta div {
   /* border: 2px dashed #faa; */
}
.forum .entry .meta div > * {
   font-size: 90%;
   line-height: 90%;
   color: #666;
   padding-right: 5pt;
}
.forum .entry .meta .datetime {
   font-size: 5pt;
   color: #ccc;
}
.forum .entry .meta .category {
   color: #85879f;
   background: #fcf9f1;
   border-radius: 3pt;
   font-size: 105%;
   letter-spacing: 0.1em;
}


/**
 * Post content.
 *
 */
.forum .entry .summary {
   display: block;
   margin: 0;
   font-weight: bold;
   font-size: 108%;
   color: #449;
}
.forum .entry .excerpt {
   color: #777;
}
.forum .entry .excerpt.trimmed {
   display: none;
}
.forum .entry .content.trimmed {
   display: none;
}
.forum .entry .funcs.trimmed {
   opacity: 0.1;
}
.forum .entry .content p {
   padding-top: 0; margin-top: 0;
}

label {
   display: block;
   padding: 5pt;
}

.action {
   margin: 1pt;
   padding: 2pt 10pt;
   color: #33c;
   background: #e7e7fc;
   border: #f0f0f9;
   border-radius: 5pt;
   font-size: 10pt;
   opacity: 0.85;
   cursor: pointer;
}
.action.forum-edit {
   opacity: 0.5;
}
.action:hover {
   opacity: 1.0;
}
.action.forum-edit:hover {
   cursor: s-resize;
}
.action.forum-reply:hover {
   cursor: copy;
}


/**
 * Submit form
 *
 */
form.forum-submit label b {
   display: inline-block;
   width: 80pt;
   text-align: right;
}
form.forum-submit label b select {
   font-weight: 900;
   font-size: 105%;
   text-align: right;
}

form.forum-submit .markup-buttons {
   width: 80pt;
   padding: 5pt;
   text-align: right;
   position: relative;
}
.markup-buttons .action {
   display: inline-block;
   margin: 0 10pt 7pt 0;
   cursor: text;
   padding: 3pt 15pt 1pt 15pt;
   line-height: 1em;
   background: #eef0f7;
   opacity: 0.3;
}
.markup-buttons .action:hover {
   opacity: 1.0;
}

.error, .warning {
   background: #eeddaa;
   border: 2px solid #ddcc99;
   border-radius: 5pt;
   padding: 5pt;
}
.error {
   background: #eebbaa;
}


input, textarea, select {
   font-size: 105%;
   padding: 1.5pt;
   border: 1px solid #bbb;
   border-left: 5pt solid #ccc;
   border-radius: 5pt;
}

Changes to gimmicks.js.

152
153
154
155
156
157
158
159
160
161
162
163
164
165















166
167
168
169
170
171
172
173
        // editing
        if (func == "forum-edit") {
            $target.load("/forum/edit", { "id": id });
        }
        // submit
        if (func == "forum-submit"){
            $target = $target.parent();
            $.post("/forum/submit", $target.serialize(), function(html){
                $target.html(html);
            });
        }
        event.preventDefault();
    });

















});













|






>
>
>
>
>
>
>
>
>
>
>
>
>
>
>








152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
        // editing
        if (func == "forum-edit") {
            $target.load("/forum/edit", { "id": id });
        }
        // submit
        if (func == "forum-submit"){
            $target = $target.parent();
            $.post("/forum/submit", $target.find("form").serialize(), function(html){
                $target.html(html);
            });
        }
        event.preventDefault();
    });

    // Markup
    $(".forum").delegate(".action.markup", "click", function(){
        var $ta = $(this).parent().parent().parent().find("textarea");
        var content = $ta.val();
        var x = $ta[0].selectionStart;
        var y = $ta[0].selectionEnd;
        var before = $(this).data("before");
        var after = $(this).data("after");
        if (y) {
            $ta.val(content.substr(0, x) + before + content.substr(x, y-x) + after + content.substr(y, content.length - y));
        }
        else {
            $ta.val(content + before + "..." + after);
        }
    });

});






Changes to index.php.

1
2
3

4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
<?php
/**
 * api: php

 * title: Freshcode.club
 * description: FLOSS software release tracking website
 * version: 0.6.0
 * author: mario
 * license: AGPL
 * 
 * Implements a freshmeat/freecode-like directory for open source
 * release publishing / tracking.
 *
 */


#-- init
include("config.php");


#-- dispatch
switch ($page = $_GET->id["page"]) {




    case "index":
    case "projects":
    case "feed":
    case "forum":
    case "links":
    case "tags":
    case "names":
    case "search":
    case "rc":
    case "drchangelog":
    case "login":
        include("page_$page.php");
        break;






    case "flag":
    case "submit":
        if ((LOGIN_REQUIRED or $page === "flag") and empty($_SESSION["openid"])) {
            exit(include("page_login.php"));
        }
        include("page_$page.php");



>


|
















>
>
>



<


<






>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
<?php
/**
 * api: php
 * type: main
 * title: Freshcode.club
 * description: FLOSS software release tracking website
 * version: 0.6.5
 * author: mario
 * license: AGPL
 * 
 * Implements a freshmeat/freecode-like directory for open source
 * release publishing / tracking.
 *
 */


#-- init
include("config.php");


#-- dispatch
switch ($page = $_GET->id["page"]) {

    case "name":
    case "names":
        $page = "names";
    case "index":
    case "projects":
    case "feed":

    case "links":
    case "tags":

    case "search":
    case "rc":
    case "drchangelog":
    case "login":
        include("page_$page.php");
        break;

    case "forum":
    case "meta":
        include("page_forum.php");
        break;

    case "flag":
    case "submit":
        if ((LOGIN_REQUIRED or $page === "flag") and empty($_SESSION["openid"])) {
            exit(include("page_login.php"));
        }
        include("page_$page.php");

Added lib/forum.php.























































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
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
345
346
347
<?php
/**
 * api: php
 * type: handler
 * title: Follow The Thread
 * description: Straightforward threaded discussion forum
 * version: 0.2
 * category: discussion
 * depends: HTMLPurifier
 * config:
 *    <var name="forum_cfg[categories]" type="list" default="discussion,documentation" help="Comma-separated list of thread classifiers"/>
 *
 *
 * Implements a minimalistic web forum.
 * Primary goals are:
 *   → Threaded discussions in place of bulletin board blabber.
 *   → Contemporary security over restriction gimmicks.
 *   → Usability instead of UI featuritis.
 *   → Open access in lieu of accounteritis.
 *
 * Single table database:
 *    [id] INT PRIMARY KEY NOT NULL UNIQUE,
 *    [pid] INT NOT NULL DEFAULT(0) REFERENCES [forum] ([id]),
 *    [gid] INT NOT NULL REFERENCES [forum] ([id]),
 *    [tag] VARCHAR (0, 32) NOT NULL DEFAULT('discussion'),
 *    [summary] VARCHAR (0, 200) NOT NULL,
 *    [source] TEXT,
 *    [html] TEXT,
 *    [excerpt] TEXT,
 *    [author] VARCHAR (0, 80),
 *    [miniature] TEXT,
 *    [t_published] INT NOT NULL,
 *    [edit_token] VARCHAR (16, 64)
 *
 * Templating
 * Behaviour
 * Configuration
 *
 *
 *
 */


/**
 * Decorative classification/grouping of threads.
 *
 */
global $forum_cfg;
$forum_cfg["categories"] = "discussion,projects,announcement,code,documentation,autoupdate";



/**
 * Callbacks, dispatcher and handler.
 *
 */
class forum {


    /**
     * Can be set externally, depending on application logic.
     *
     */
    var $is_admin = 0;
    var $can_edit = 1;



    /**
     * NOP
     *
     */
    function __construct() {
    }


    /**
     * Show forum listing
     *
     */
    function index($page=0) {
    
        // Fetch thread groups (attached to root gid=0)
        $entries = db("
            SELECT *
              FROM forum
             WHERE gid
                IN ( SELECT id
                       FROM forum
                      WHERE pid = 0
                   ORDER BY t_published DESC
                      LIMIT 50
                     OFFSET ?*50 )
          ORDER BY gid DESC,
                   t_published ASC
        ", $page);
        
        // Iterate over groups
        $last = 0;
        $group = array();
        foreach ($entries as $e) {
            if ($e["gid"] != $last) {
                $this->show_thread($group);
                $group = array();
            }
            $group[] = $e;
            $last = $e["gid"];
        }
        $this->show_thread($group);
    }


    /**
     * Iterate over grouped entry list
     * and recursively output posts.
     *
     */
    function show_thread($group, $pid=0) {
    
        #-- find available parent ids
        $parents = array_column($group, "pid");
    
        #-- step throuh
        foreach ($group as $entry) {
        
            #-- show if associated
            if ($entry["pid"] == $pid) {
                $entry["miniature"] or $entry["miniature"] = "/img/user.png";
                include("template/forum_entry.php");
                
                #-- Nest its children
                if (in_array($entry["id"], $parents)) {
                    print "    <ul>\n";
                    $this->show_thread($group, $entry["id"]);
                    print "    </ul>\n";
                }

                print "       </li>\n";
            }
        }
    }

    
    
    /**
     * Load a single entry.
     *
     */
    function entry($id) {
    }



    /**
     * Accept POST input and populate new forum post.
     * Adds an reply if ?pid= is not zero.
     * Doubles as edit function if ?id= is present.
     *
     */
    function submit($INSERT="INSERT") {
    
        #-- Prepare some fields
        $data = array(
            "id" => NULL,
            "pid" => $_POST->int["pid"],
            "gid" => NULL,
            "author" => $_POST->text->length…30->html["author"],
            "miniature" => $_POST->text->length…200->html["image"],
            "tag" => $_POST->text->length…20->html["tag"],
            "summary" => $_POST->text->length…120->html["summary"],
            "source" => $_POST->nocontrol->length…12000["source"],
            "html" => "",
            "excerpt" => "",
            "t_published" => time(),
            "edit_token" => $this->edit_token(),
        );

        #-- Source to HTML
        $data = $this->prepare_output($data);
        
        #-- Reject too minor submisions
        if (strlen("$data[source]$data[summary]") < 100) {
            exit("<p class=warning>Your post was a little too coarse. Please elaborate to keep discussions going.</p>");
        }

        #-- Edit
        if ($id = $_POST->int["id"]) {
            $prev = $this->edit_keep($this->edit_entry($id));
            $data = array_merge($data, $prev);
            $INSERT = "REPLACE";
#            var_dump($INSERT, $data, $prev);
        }
        #-- Reply
        elseif ($data["pid"]) {
            $data["gid"] = $this->group_id($data["pid"]);
        }

        /**
         * Store entry
         *  → Find maximum ID
         *  → Use as new id, and group id if new
         *  → Else keep previous pid and gid/id
         *
         * $data and $ids are split up before, so the :? and ::
         * placeholders don't consume all fields.
         *
         */
        $ids = array_splice($data, 0, 3);  // extract id,pid,gid
        $ok = db("
            $INSERT INTO forum (id, pid, gid, :?)
            VALUES (
                IFNULL(:id, (SELECT IFNULL(MAX(id), 0) + 1 AS id FROM forum)),
                IFNULL(:pid, 0),
                COALESCE(:gid, :id, (SELECT IFNULL(MAX(id), 0) + 1 AS id FROM forum)),
                ::
            );
        ", $data, $data, $ids);
        
        #-- return rendered
        if ($ok) {
            $data["id"] = db()->lastInsertId();
            $data["pid"] = 0;
            $this->show_thread([$data], 0);
        }
    }


    #-- Editing timeout / permission
    function edit_permission($prev) {
        return
            $this->is_admin
        or
            $_COOKIE->name["edit_token"] == $prev["edit_token"]
        and
            $prev["t_published"] + 48*3600 > time();
    }


    #-- Retrieves or sets edit_token
    function edit_token() {
        if (empty($token = $_COOKIE->name["edit_token"])) {
            setcookie("edit_token", $token = sha1(serialize($_SERVER)), time()+7*24*3600);
        }
        return $token;
    }


    /**
     * Retrieve post entry, check edit permission, and/or set defaults
     *
     *  → For forum/edit requests fetches the existing post content.
     *  → Also checks editing permissions (token),
     *    or e.g. existing OpenID logon ($this->can_edit is set from main site).
     *
     */
    function edit_entry($id) {
        if ($prev = db("SELECT * FROM forum WHERE id=?", $id)->fetch()) {

            if (!$this->can_edit) {
                exit("<p class=warning>You aren't logged in on the main site. Please associate an OpenID account.</p>");
            }
            if (!$this->edit_permission($prev)) {
                exit("<p class=warning>Entry not editable. (The edit token does not match, or the article is too old for editing.)</p>");
            }
            
            return $prev;
        }
        exit("<p class=error>Post #$id does not exist.</p>");
    }

    
    #-- Unsets a few fields for replying.
    function edit_keep($prev) {
        $copy = array_flip(str_getcsv("id,pid,gid,miniature,t_published,edit_token"));
        return array_intersect_key($prev, $copy);
    }


    #-- Copy thread/group id from parent post etc.
    function group_id($parent_id) {
        if ($prev = db("SELECT gid FROM forum WHERE id = ?", $parent_id)) {
            return $prev->gid;
        }
        return 0;
    }    


    #-- Convert Markdown/BB source into HTML, create excerpt, prepare user avatar
    function prepare_output($data) {

        #-- Content
        define("HTMLPURIFIER_PREFIX", "phar://lib/htmlpurifier.phar/standalone/");
        $md = new Parsedown();
        $data["html"] = input::purify($md->parse($data["source"]));
        $data["excerpt"] = input::html(substr(strip_tags($data["html"]), 0, 320));
        
        #-- Author
        if (strlen($data["miniature"]) and $type = $_POST->in_array("img_type", "gravatar,identicon,monsterid,wavatar,retro"))
        {
            $data["miniature"] = "http://www.gravatar.com/avatar/"
                               . md5($data["miniature"])
                               . ".jpeg?s=16" . ($type == "gravatar" ? "" : "&d=$type");
        }
        return $data;
    }




    /**
     * Output submit <form>
     *
     *  → Provides a few blank fields.
     *  → Injects cookie defaults if fresh submission / not an edit.
     *  → Escapes previous content for edit_form() calls.
     *
     */
    function submit_form($pid=0, $id=0, $data=array()) {
        global $forum_cfg;

        extract(array_merge(
            array_fill_keys(str_getcsv("author,miniature,tag,summary,source"), ""),
            $data ? array() : $_COOKIE->list->text["author,miniature"],
            array_map("htmlspecialchars", $data)
        ));

        include("template/forum_submit_form.php");
    }


    /**
     * Show editing <form> instead with prefilled previous data.
     *
     *  → Retrieves previous post content, checks permissions at that.
     *  → Outputs edit form (replied for AJAX $.load() request)
     *
     */
    function edit_form($pid=0, $id=0) {
        $data = $this->edit_entry($id);
        $this->submit_form(0, 0, $data);
    }

}




Added page_forum.php.

































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
<?php
/**
 * api: freshcode
 * type: main
 * title: meta/forum
 * description: Simple threaded discussion / documentation forum.
 * version: 0.2
 *
 * Distinct layout from main site and harbours its own dispatcher.
 * Editing/post features. CSS is melted in, as there's no subpaging.
 *
 */


#-- custom config
include_once("./shared.phar");  // autoloader
define("INPUT_QUIET", 1) and
include_once("lib/input.php");  // input filter
define("HTTP_HOST", $_SERVER->id["HTTP_HOST"]);
include_once("lib/deferred_openid_session.php");  // auth+session
include_once("aux.php");        // utility functions
include_once("config.local.php");
include_once("lib/db.php");     // database API
db(new PDO("sqlite:forum.db")); // separate storage


#-- set up forum handling
$f = new forum();
$f->is_admin = in_array($_SESSION["openid"], $moderator_ids);


#-- dispatch functions
switch ($name = $_GET->id["name"]) {

    case "submit":
        exit( $f->submit() );

    case "post":
        exit( $f->submit_form($_REQUEST->int["pid"], 0) );

    case "edit":
        exit( $f->edit_form(0, $_REQUEST->int["id"]) );

    case "index":
    case "":
    default:
        // handled below per default
}
   
?>
<!DOCTYPE html>
<html>
<head>
    <title>freshcode.club forum</title>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src=gimmicks.js></script>
    <meta charset=UTF-8>
    <?= "<style>\n"
      . file_get_contents("forum.css")
      . "</style>";
    ?>
</head>
<body>
<div id=title>
   <h1><b>fresh</b>(code)<b class=red>.</b><span class=grey>club</span></h1>
</div>
<br>
<ul class=forum>

   <li>
      <div class=entry>
         <a class="action forum-new" data-id=0>New Thread</a>
      </div>
   </li>
   <?php
      $f->index();
    ?>
</ul>
</body>
</html>

Added template/forum_entry.php.













































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php
/**
 * api: FTD
 * title: Single forum post
 * description:
 *
 */

$_ = "trim";

print <<<HTML

   <li>
      <article class=entry>

          <h6 class=summary>$entry[summary]
             <span class="funcs trimmed">
                <a class="action forum-edit" data-id=$entry[id] title="Edit">Ed</a>
                <a class="action forum-reply" data-id=$entry[id] title="Reply">Re</a>
             </span>
          </h6>

          <aside class=meta><div>
             <b class=category>$entry[tag]</b>
             <i class=author>
                 <img align=top src="$entry[miniature]" width=16 height=16>
                 $entry[author]
             </i>
             <var class=datetime>{$_(strftime("%Y-%m-%d - %H:%M",$entry["t_published"]))}</var>
          </div></aside>

          <div class="excerpt">$entry[excerpt]</div>
          <div class="content trimmed">$entry[html]</div>

      </article>

HTML;

Added template/forum_submit_form.php.

















































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
<?php
/**
 * api: ftt
 * type: template
 * title: Post submit/edit form
 * description: Outputs input form for new / reply / editing forum posts.
 *
 *
 */


?>

<!-- faux -->
<form action=spex method=POST style="displaY: None">
   <input type="hidden" name="back" value="index" />
   <input type="hidden" name="mode" value="posting" />
   <input type="hidden" name="id" value="0" />
   <input type="hidden" name="posting_mode" value="0" />
   <input type="text" size="40" name="name" value="" maxlength="40">
   <input type="text" size="40" name="email" value="">
   <input type="text" size="40" name="homepage" value="">
   <input id="subject" type="text" size="50" name="subject" value="">
   <textarea cols="80" rows="21" name="comment"></textarea>
   <input type="submit" name="save_entry" value="OK - Submit" title="Save entry">
</form>

<!-- actual -->
<form class=forum-submit action=none style="display: run-in">

   <label>
       <b>Author</b>
       <input name=author placeholder=your-name size=50 value="<?=$author?>">
   </label>

   <label>
       <b><select name=img_type><option>gravatar<option>identicon<option>monsterid<option>wavatar<option>retro</select></b>
       <input name=image type=email placeholder="you@example.com" size=50 value="<?=$miniature?>">
   </label>

   <label>
       <b>Category</b>
       <select name=tag><?=form_select_options($forum_cfg["categories"], $tag)?></select>
   </label>

   <label>
       <b>Summary</b>
       <input name=summary placeholder="..." size=60 value="<?=$summary?>">
   </label>

   <label>
       <span style="position: absolute">
          <div class="markup-buttons">
           <a class="action markup" style="font-style: italic" data-before="*" data-after="*">italic</a>
           <a class="action markup" style="font-weight: bold" data-before="**" data-after="**">bold</a>
           <a class="action markup" style="text-decoration: underline" data-before="[" data-after="](http://example.org/)">link</a>
           <a class="action markup" style="" data-before="`" data-after="`">{code}</a>
           <a class="action markup" style="" data-before="\n  *  " data-after="">• list</a>
          </div>
       </span>
       <b>Message</b>
       <textarea name=source cols=55 rows=12><?=$source?></textarea>
   </label>

   <input type=hidden name=id value="<?=$id?>">
   <input type=hidden name=pid value="<?=$pid?>">

   <button class="action forum-submit">Follow The Thread</button>
   <br>

</form>