Check-in Differences
Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From b2cdb48c301b1f54 To abc330bf7cbf428b
2014-08-13
| ||
16:10 | Add db() placeholder documentation ASCII table. check-in: ac49f65add user: mario tags: trunk | |
16:09 | ::http filter was too strict (leading numbers in URLs) check-in: abc330bf7c user: mario tags: trunk | |
16:09 | Add curl()->assert() to be run after ->exec() check-in: 063d94349d user: mario tags: trunk | |
2014-06-30
| ||
01:10 | curl() wrapper check-in: 11fd88164c user: mario tags: trunk, 0.3 | |
01:10 | Populate `tags` table. check-in: b2cdb48c30 user: mario tags: trunk | |
01:07 | Moved utility code to (misnomer) layout_aux. check-in: 1cdf4582fe user: mario tags: trunk | |
Changes to .htaccess.
1 2 3 4 | 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 | + + - - + + + + + + + + - + + + + + + + - - - - + + + + + + + + + + + + + - - - + + + + - - - - + + + + + | # encoding: UTF-8 # api: apache # title: RewriteRules # description: Map paths onto dispatcher script # version: 1.0 # depends: mod_rewrite |
Added aux.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: freshmeat * title: template auxiliary code * description: A few utility functions and data for the templates * version: 0.5 * license: AGPL * * This function asortment prepares some common output. * While a few are parsing helpers or DB query shortcuts. * */ #-- Additional input filters // Project names may be alphanumeric, and contain dashes function proj_name($s) { return preg_replace("/[^a-z0-9-_]+|^[^a-z0-9]+|\W+$|(?<=[-_])[-_]+/", "", strtolower($s)); } // Tags is a comma-separated list, yet sometimes delimited with something else; normalize.. function f_tags($s) { return preg_replace( # exception for "c++" and "c#" ["~[-_.:/]+~", "/(([cflje]#|c\+\+)(?=[\s,-]))?[+#]*/", "/[,;|]+/", "/[^a-z0-9,+#\s-]+/", "/[,\s]+/", "/^\W+|\W+$/"], [ "-", "$1", ",", " ", ", " , "" ], strtolower($s) ); } #-- Template helpers // Wrap tag list into links function wrap_tags($tags, $r="") { foreach (str_getcsv($tags) as $id) { $id = trim($id); $r .= "<a href=\"/search?tag=$id\">$id </a>"; } return $r; } // Return DAY MONTH and TIME or YEAR for older entries function date_fmt($time) { $lastyear = time() - $time > 250*24*3600; return strftime($lastyear ? "%d %b %Y" : "%d %b %H:%M", $time); } /** * Substitute `$version` placeholders in URLs. * * Supported syntax variations: * → $version and $version$ * → %version and %version% * * And for substituting $version-number dots: * → $-$version for which 1.2.3 becomes 1-2-3 * → $_$version for which 2.3.4 becomes 2_3_4 * */ function versioned_url($url, $version) { $rx = "/ ([ \$ % ]) # var syntax ( (.?) \\1 )?+ # substitution prefix (version|Version|VERSION) # 'version' (?= \\1 | \b | _ ) # followed by var syntax, wordbreak, or underscore /x"; // Check for '$version' if (preg_match($rx, $url, $m)) { // Optionally replace dots in version string if (strlen($m[2])) { $version = strtr($version, ["." => $m[3]]); } $url = preg_replace($rx, $version, $url); } return $url; } /** * Convert "url1=, url2=, url3=" list into titled hyperlinks. * */ function proj_links($urls, $entry, $r="") { // unpack and filter $urls = p_key_value($urls, NULL); $urls = array_filter(array_map("input::url", $urls)); // join into HTML list foreach ($urls as $title=>$url) { $title = ucwords($title); $url = versioned_url($url, $entry["version"]); $r .= "→ <a href=\"$url\">$title</a><br>\n"; } return $r; } // Project listing output preparation; // HTML context escapaing, versioned urls, formatted date string function prepare_output(&$entry) { // versioned URLs $entry["download"] = versioned_url($entry["download"], $entry["version"]); // project screenshots if (TRUE or empty($entry["image"])) { if (file_exists($fn = "img/screenshot/$entry[name].jpeg")) { $entry["image"] = "/$fn?" . filemtime($fn); } else { $entry["image"] = "/img/nopreview.png"; } } // $entry["formatted_date"] = date_fmt($entry["t_published"]); // HTML context $entry = array_map("input::_html", $entry); // user image $entry["submitter_img"] = submitter_gravatar($entry["submitter_image"]); } /** * Convert email@xyz to gravatar or identicon, * keep raw URLs, or use default image for empty fields. * */ function submitter_gravatar($img, $size=24) { // capture+strip email if (is_int(strpos($img, "@"))) { $img = "//www.gravatar.com/avatar/" . md5($img) . "?s=$size&d=identicon&r=pg"; } elseif (empty($img)) { $img = "/img/user.png"; } // return html <img> snippet return "<img src=\"$img\" width=$size height=$size class=gravatar>"; } // Social media share links function social_share_links($name, $url) { $c = array("google"=>0, "facebook"=>0, "twitter"=>0, "reddit"=>0, "linkedin"=>0, "stumbleupon"=>0, "delicious"=>0); return <<<HTML <span class=social-share-links> <a href="https://plus.google.com/share?url=$url" title=google+> g﹢ </a> <a href="https://www.facebook.com/sharer/sharer.php?u=$url" title=facebook> fb </a> <a href="https://twitter.com/intent/tweet?url=$url" title=twitter> tw </a> <a href="http://reddit.com/submit?url=$url" title=reddit> rd </a> <a href="https://www.linkedin.com/shareArticle?mini=true&url=$url" title=linkedin> in </a> <a href="https://www.stumbleupon.com/submit?url=$url" title=stumbleupon> su </a> <a href="https://del.icio.us/post?url=$url" title=delicious> dl </a> </span> HTML; } function social_share_count($num) { return empty($num) ? "" : "<var class=social-share-count>$num</var>"; } /** * Write out pseudo pagination links. * This is just appended no matter the actually available entries. * The db() queries themselves handle the LIMIT/OFFSET, depending on a page param. * */ function pagination($page_no, $GET_param="n") { print "<p class=pagination-links> »"; foreach (range($page_no-2, $page_no+9) as $n) if ($n > 0) { print " <a " . ($n==$page_no ? "class=current " : ""). "href=\"?n=$n\">$n</a> "; } print "« </p>"; } /** * Output a list of select <option>s * * - Either accepts a option,value,field list. * - Or an associative array. * */ function form_select_options($names, $value=NULL, $r="") { // Transform comma-separated string into array $map = is_string($names) ? array_combine($names = str_getcsv($names), $names) : $names; // Add currently active value if missing if ($value and !isset($map[$value]) and $value !== NULL) { $map[$value] = $map[$value]; } // Output <option> fields foreach ($map as $id=>$title) { // optgroup if (is_array($title)) { $r .= "<optgroup label=\"$id\">" . form_select_options($title, $value) . "</optgroup>"; } // plain value field else { $r .= "<option" . ($id == $value ? " selected" : "") . " value=\"$id\" title=\"$title\">$id</option>"; } } return $r; } /** * CSRF token generation/verification. * * Is only used for logged-in users though. Here they're mainly to prevent * remotely initiated requests against other users, not general form nonces. */ function csrf($probe=false) { // Tokens are stored in session, reusable, but only for an hour $store = & $_SESSION["csrf"]; foreach ($store as $id=>$time) { if ($time < time()) { unset($store[$id]); } } // Test presence if ($probe) { if (empty($_SESSION["openid"])) { return TRUE; } if ($id = $_REQUEST->name["_ct"]) { #var_dump($id, $store, isset($store[$id])); return isset($store[$id]); } } // Create new entry, output form field for token else { // server ENV already contained Apache unique request id etc. $id = sha1(serialize($_SERVER->__vars)); $store[$id] = time() + 3600; // timeout return "<input type=hidden name=.ct value=$id>"; } } #-- Some string parsing /** * Plain comma-separated list * */ function p_csv($str) { return preg_split("/\s*,\s*/", trim($str)); } /** * Extracts key = value list. * Keys may be wrapped in $, % or [] * Values may not contain spaces * */ function p_key_value($str, $case=CASE_LOWER) { preg_match_all( "@ [[%$]* ([-\w]+) []%$]* \h* [:=>]+ \h* (\S+) (?<![,.;]) @imsx", $str, $m ); $r = array_combine($m[1], $m[2]); return is_int($case) ? array_change_key_case($r, $case) : $r; } ?> |
Deleted config.orig.php.
|
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
|
Added config.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: freshcode * title: Freshcode.club config * description: initialization code * version: 0.5.0 * plugin-register: include_once("$FN"); * * * Automatic and manual dependencies. * Base configuration. * */ // autoloader include("./shared.phar"); // input filter define("INPUT_QUIET", 1); define("INPUT_DIRECT", "raw"); include_once("lib/input.php"); // database include_once("lib/db.php"); db(new PDO("sqlite:freshcode.db")); db()->in_clause = 0; // auth+session define("LOGIN_REQUIRED", 0); define("CAPTCHA_REQUIRED", 0); define("HTTP_HOST", $_SERVER->id["HTTP_HOST"]); include_once("lib/deferred_openid_session.php"); // utility functions include_once("aux.php"); curl::$defaults["useragent"] = "freshcode/0.6 (Linux x86-64; curl) projects-autoupdate/0.5 (screenshots,changelog,regex,xpath) +http://freshcode.club/"; // List of administrative OpenID handles $moderator_ids = array(); include("config.local.php"); ?> |
Added cron.daily/autoupdate.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: freshcode * title: Autoupdate runner * description: Cron job for invoking autoupdates on per-project basis * version: 0.5.0 * depends: curl * author: mario * license: AGPL * * * Each project listing can contain: * `autoupdate_module` = none | regex | github | sourceforge | releases.json * `autoupdate_url` = http://... * `autoupdate_regex` = "version=/.../ \n changes=/.../" * * This module tries to load the mentioned reference URLs, extract version * and changelog, scope/state and download link; then updates the database. * */ // run in cron context chdir(dirname(__DIR__)); include("config.php"); // go $run = new Autoupdate(); $run->all(); #print_r($run->test("github", "youtube-dl")); |
Added cron.daily/news_feeds.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * title: Article feeds * description: Queries a few online resources for article links * version: 0.4 * * Highlights version numbers in news feeds, * and populates templates/feed.*.htm for sidebar display. * */ // switch to webroot chdir(dirname(__DIR__)); #-- RSS $feeds = array( "reddit" => "http://www.reddit.com/r/linux/.rss", "linuxcom" => "http://www.linux.com/news/software?format=feed&type=rss", "linuxgames" => "http://www.linuxgames.com/feed", "sourceforge" => "http://sourceforge.net/directory/release_feed/", "distrowatch" => "http://distrowatch.com/news/dwd.xml", ); $filter = "/Please 'report' off-topic|namelessrom|machomebrew/" ; #-- Traverse and collect entries foreach ($feeds as $name=>$url) { // data $html = ""; $x = file_get_contents($url); $x = preg_replace("/[^\x20-\x7F\s]/", "", $x); $x = simplexml_load_string($x); // append $i = 0; foreach ($x->channel->item as $item) { # pre-filter list($title, $link) = array( htmlspecialchars($item->title), htmlspecialchars($item->link) ); if (empty($title) or empty($link) or preg_match($filter, $title) or preg_match($filter, $link)) { continue; } # per feed switch ($name) { // Extract project base names and version numbers case "sourceforge": if (preg_match("~^(http://sourceforge.net/projects/(\w+))/files/.+?(\d+(\.\d)+).+?/download$~", $item->link, $m)) { $html .= "<a href=\"$m[1]\">$m[2] <em>$m[3]</em></a>\n"; $i++; } break; // Extract project base names and version numbers case "distrowatch": if (preg_match("~^(\d+/\d+)\s(\D+)\s+(.+)$~", $title, $m)) { $html .= "<a href=\"$link\"><small style=color:grey>$m[1]</small> $m[2] <em>$m[3]</em></a>\n"; } break; // Titles as is default: case "reddit": case "linuxcom": case "linuxgames": if (strlen($item->link) and strlen($item->title)) { $title = preg_replace("~(\d+\.[\d-.]+)~", "<em>$0</em>", $title); $html .="<a href=\"$link\">$title</a>\n"; $i++; } break; } if ($i >= 20) { break; } } // save file_put_contents("./template/feed.$name.htm", $html); } |
Added cron.daily/social_links.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: freshcode * title: Social links count * description: Queries api.i-o/links for project homepages * version: 0.1 * * Retrieve social media sharing site links for project homepages. * Stores them in `release`.`social_links` * * Only updates latest DB entry, so a versioned history of link counts * will be retained. (Not that it's needed, ...) * */ // deps chdir(dirname(__DIR__)); include("config.php"); // use "remotely" for implicit caching define("IO_LINKS", "http://api.include-once.org/links/social.ajax.php"); // traverse projects foreach (db("SELECT *, MAX(t_changed) FROM release_versions GROUP BY name ORDER BY t_published DESC") as $project) { // homepage $url = $project["homepage"]; // request counts $counts = curl() ->url(IO_LINKS . "?url=$url") ->UserAgent("cron.daily/social_links (0.1; http://freshcode.club/)") ->exec(); // summarize $counts = json_decode($counts, TRUE); $counts = array_sum($counts); print "$url = $counts\n"; // store $project["social_links"] = $counts; #$project->store("REPLACE"); db("UPDATE release SET social_links=? WHERE :&", $counts, array( "name" => $project["name"], "t_changed" => $project["t_changed"], "t_published" => $project["t_published"], ) ); } |
Added cron.daily/spotlight.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: cron * title: Create random picks for project spotlight * description: Randomly picks out a few projects for the footer * version: 0.1 * * */ chdir(dirname(__DIR__)); include("config.php"); /** * Scan each project, * pick random three. * */ $r = db(" SELECT name, title, SUBSTR(description, 0, 150) AS description, MAX(t_changed) AS t FROM release GROUP BY name ORDER BY random() LIMIT 3; "); // combine into HTML blob $html = ""; foreach ($r as $entry) { $entry = array_map("input::_html", $entry); # $entry["description"] = preg_replace("/\.[^.]*$|[,;][^,;]*$|\S*$/", "", $entry["description"]); $html .= <<<EOF <a class=project-spotlight href="projects/$entry[name]"> <img src="img/screenshot/$entry[name].jpeg" width=120 height=90 alt=$entry[name]> <b> $entry[title] </b> <small class=description>$entry[description]</small> </a> EOF; } // store as template file_put_contents("./template/spotlight.htm", $html); |
Changes to cron.daily/tags.php.
1 2 3 4 5 | 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 | - + - + + + - + - + | <?php /** * api: cron * title: Update `tags` table * description: Splits out tags from according column in project `release`. |
Deleted db.php.
|
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
|
Changes to db.sql.
1 2 | 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 | - + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + - - - + + + + - - + + + + + + - - - + + + + + - - - - - - - + + + + + + + + + + | # # title: freshcode database schema |
Deleted deferred_openid_session.php.
|
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
|
Added doc/cacert.pem.
| 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | -----BEGIN CERTIFICATE----- MIIHPTCCBSWgAwIBAgIBADANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290 IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ 8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6 zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7 w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826 YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAc4w ggHKMB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TCBowYDVR0jBIGbMIGY gBQWtTIb1Mfz4OaO873SsDrusjkY0aF9pHsweTEQMA4GA1UEChMHUm9vdCBDQTEe MBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlDQSBDZXJ0 IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGNhY2Vy dC5vcmeCAQAwDwYDVR0TAQH/BAUwAwEB/zAyBgNVHR8EKzApMCegJaAjhiFodHRw czovL3d3dy5jYWNlcnQub3JnL3Jldm9rZS5jcmwwMAYJYIZIAYb4QgEEBCMWIWh0 dHBzOi8vd3d3LmNhY2VydC5vcmcvcmV2b2tlLmNybDA0BglghkgBhvhCAQgEJxYl aHR0cDovL3d3dy5jYWNlcnQub3JnL2luZGV4LnBocD9pZD0xMDBWBglghkgBhvhC AQ0ESRZHVG8gZ2V0IHlvdXIgb3duIGNlcnRpZmljYXRlIGZvciBGUkVFIGhlYWQg b3ZlciB0byBodHRwOi8vd3d3LmNhY2VydC5vcmcwDQYJKoZIhvcNAQEEBQADggIB ACjH7pyCArpcgBLKNQodgW+JapnM8mgPf6fhjViVPr3yBsOQWqy1YPaZQwGjiHCc nWKdpIevZ1gNMDY75q1I08t0AoZxPuIrA2jxNGJARjtT6ij0rPtmlVOKTV39O9lg 18p5aTuxZZKmxoGCXJzN600BiqXfEVWqFcofN8CCmHBh22p8lqOOLlQ+TyGpkO/c gr/c6EWtTZBzCDyUZbAEmXZ/4rzCahWqlwQ3JNgelE5tDlG+1sSPypZt90Pf6DBl Jzt7u0NDY8RD97LsaMzhGY4i+5jhe1o+ATc7iwiwovOVThrLm82asduycPAtStvY sONvRUgzEv/+PDIqVPfE94rwiCPCR/5kenHA0R6mY7AHfqQv0wGP3J8rtsYIqQ+T SCX8Ev2fQtzzxD72V7DX3WnRBnc0CkvSyqD/HMaMyRa+xMwyN2hzXwj7UfdJUzYF CpUCTPJ5GhD22Dp1nPMd8aINcGeGG7MW9S/lpOt5hvk9C8JzC6WZrG/8Z7jlLwum GCSNe9FINSkYQKyTYOGWhlC0elnYjyELn8+CkcY7v2vcB5G5l1YjqrZslMZIBjzk zk6q5PYvCdxTby78dOs6Y5nCpqyJvKeyRKANihDjbPIky/qbn3BHLt4Ui9SyIAmW omTxJBzcoTWcFbLUvFUufQb1nA5V9FrWk9p2rSVzTMVD -----END CERTIFICATE----- |
Added doc/fc-submit.
| 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 348 349 350 351 352 353 354 355 356 357 358 359 360 361 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | #!/usr/bin/env python3 """ freshcode-submit -- script transactions with the Freshcode.club server """ import sys, re, requests, json, netrc, email.parser, optparse version = "3.0" class Update: "Encapsulate dictionaries describing a project metadata update." def __init__(self): self.name = None self.per_project = {} self.urlassoc = [] self.per_release = {} def __repr__(self): return "Update(" + repr(self.__dict__) + ")" # The Freecode API implementation is sensitive to the kind of HTTP request # method you use. The general rule is: # # Reading records: GET # Adding new records: POST # Updating existing records: PUT # Deleting existing records: DELETE # # From http://help.freecode.com/faqs/api-7/data-api-intro: # 200 OK - Request was successful, the requested content is included # 201 Created - Creation of a new resource was successful # 401 Unauthorized - You need to provide an authentication code with this # request # 403 Forbidden - You don't have permission to access this URI # 404 Not Found - The requested resource was not found # 409 Conflict - The validation of your submitted data failed, please check # the response body for error pointers # 500 Server Error - The request hit a problem and was aborted, please report # as a bug if it persists # 503 Service Unavailable - You hit your API credit limit def RequestWithMethod(method, url, **kwargs): """requests.request is really a drop-in replacement here; with TLS-SNI support""" # Here verify="cacert.pem" would better solve the certificate issue return requests.request(method, url, verify=False, **kwargs) class FreecodeSessionException(Exception): "Carry exception state when a session blows up." def __init__(self, msg): Exception.__init__(self) self.msg = msg class FreecodeSession: "Encapsulate the state of a Freecode API session." server = "https://%s.freshcode.club/" % "api" def __init__(self, auth=None, verbose=0, emit_enable=True): "Initialize Freecode session credentials." self.auth = auth self.verbose = verbose self.emit_enable = emit_enable self.project = None self.permalink = None self.id = None self.project_data = None # If user didn't supply credentials, fetch from ~/.netrc if not self.auth: try: credentials = netrc.netrc() except netrc.NetrcParseError as e: raise FreecodeSessionException("ill-formed .netrc: %s:%s %s" \ % (e.filename, e.lineno, e.msg)) except IOError as e: raise FreecodeSessionException(("missing .netrc file %s" % \ str(e).split()[-1])) ret = credentials.authenticators("freshcode") if not ret: raise FreecodeSessionException("no credentials for Freshcode") _login, self.auth, _password = ret def on_project(self, name): "Select project by Freecode shortname." if self.verbose: print(("Selecting project: %s" % name)) self.project = name pquery = FreecodeSession.server + "projects/%s.json?auth_code=%s" \ % (self.project, self.auth) handle = RequestWithMethod("GET", url=pquery) content = json.loads(handle.text) self.project_data = content['project'] #if self.verbose: # print "Project data: %s" % self.project_data self.permalink = self.project_data['permalink'] self.id = self.project_data['id'] def edit_request(self, url, method="GET", request=None, force=False): "Wrap a JSON object with the auth code and ship it as a request" if request is None: request = {} url = FreecodeSession.server + url data = {"auth_code" : self.auth} data.update(request) data = json.dumps(data) headers = {"Content-Type" : "application/json"} if self.verbose: print(("Request URL:", method, url)) #if self.verbose: # print "Request headers:", headers if self.verbose: print(("Request data:", data)) if self.emit_enable or force: req = RequestWithMethod(method=method, url=url, data=data, headers=headers) if self.verbose: print(req.status_code, req.url, req.headers) content = req.text if self.verbose: print(("Response:", content)) return content def publish_release(self, data): "Add a new release to the current project." if self.verbose: print(("Publishing %s release: %s" % (self.project, repr(data)))) self.edit_request("projects/" + self.permalink + "/releases.json", "POST", {"release": data}) def withdraw_release(self, dversion): "Withdraw a specified release from the current project." if self.verbose: print("Withdrawing %s release: %s" % (self.project, dversion)) releases = self.edit_request("projects/%s/releases/pending.json" \ % self.permalink, force=True) releases = json.loads(releases) for release in releases: properties = release["release"] if properties.get("version") == dversion: vid = properties["id"] break else: raise FreecodeSessionException("couldn't find release %s"%dversion) deletelink = "projects/%s/releases/%s.json" % (self.permalink, vid) self.edit_request(deletelink, "DELETE", {}) def update_core(self, coredata): "Update the core data for a project." if self.verbose: print("Core data update for %s is: %s" % (self.project, coredata)) self.edit_request("projects/" + self.permalink + ".json", "PUT", {"project": coredata}) def update_urls(self, urlassoc): "Update URL list for a project." if self.verbose: print("URL list update for %s is: %s" % (self.project, urlassoc)) # First, get permalinks for all existing URLs uquery = FreecodeSession.server + "projects/%s/urls.json?auth_code=%s" \ % (self.permalink, self.auth) handle = RequestWithMethod("GET", url=uquery) content = json.loads(handle.text) permadict = content['urls'] # Just send the new dict over self.edit_request("projects/%s/urls.json" % (self.permalink), "PUSH", {"urls" : dict(urlassoc)}) class FreecodeMetadataFactory: "Factory class for producing Freecode records in JSON." freecode_field_map = ( ("Project", "P", "name"), # Project ("Summary", "S", "oneliner"), # Project ("Description", "D", "description"), # Project ("License-List", "L", "license_list"), # Project ("Project-Tag-List", "T", "project_tag_list"), # Project ("Version", "v", "version"), # Release ("Changes", "c", "changelog"), # Release ("Hide", "x", "hidden_from_frontpage"), # Release ("Release-Tag-List", "t", "tag_list"), # Release ) # Which attributes have project scope, all others have release scupe projectwide = ('name', 'description', 'oneliner', 'license_list', 'project_tag_list') def __init__(self): self.message_parser = email.parser.Parser() self.argument_parser = optparse.OptionParser( \ usage="usage: %prog [options]") for (msg_field, shortopt, rpc_field) in FreecodeMetadataFactory.freecode_field_map: self.argument_parser.add_option("-" + shortopt, "--" + msg_field.lower(), dest=rpc_field, help="Set the %s field"%msg_field) self.argument_parser.add_option('-q', '--query', dest='query', help='Query metadata for PROJECT',metavar="PROJECT") self.argument_parser.add_option('-d', '--delete', dest='delete', default=False, action='store_true', help='Suppress reading fields from stdin.') self.argument_parser.add_option('-n', '--no-stdin', dest='read', default=True, action='store_false', help='Suppress reading fields from stdin.') self.argument_parser.add_option('-N', '--dryrun', dest='dryrun', default=False, action='store_true', help='Suppress reading fields from stdin.') self.argument_parser.add_option('-V', '--verbose', dest='verbose', default=False, action='store_true', help='Enable verbose debugging.') self.argument_parser.add_option('-?', '--showversion', dest='showversion', default=False, action='store_true', help='Show version and quit.') @staticmethod def header_to_field(hdr): "Map a header name from the job card format to a field." lhdr = hdr.lower().replace("-", "_") for (alias, _shortopt, field) in FreecodeMetadataFactory.freecode_field_map: if lhdr == alias.lower().replace("-", "_").replace("/", "_"): return field raise FreecodeSessionException("Illegal field name %s" % hdr) def getMetadata(self, stream): "Return an Update object describing project and release attributes." data = {} urls = {} (options, _args) = self.argument_parser.parse_args() # Stuff from stdin if present prior_version = data.get("version") if not (options.query or options.showversion) and options.read: message = self.message_parser.parse(stream) for (key, value) in list(message.items()): value = re.sub("\n +", " ", value).strip() if key.endswith("-URL"): key = key.replace("-", " ") urls.update({key[:-4] : value}) else: if key.endswith("List"): value = [x.strip() for x in value.split()] data.update({FreecodeMetadataFactory.header_to_field(key) : value}) if not 'changelog' in data: payload = message.get_payload().strip() if payload: data['changelog'] = payload + "\n" if prior_version and data.get("version") != prior_version: raise FreecodeSessionException("Version conflict on stdin.") # Merge in options from the command line; # they override what's on stdin. controls = ('query', 'delete', 'read', 'dryrun', 'verbose', 'showversion') prior_version = data.get("version") for (key, value) in list(options.__dict__.items()): if key not in controls and value != None: data[key] = value del options.__dict__[key] if prior_version and data.get("version") != prior_version and not options.delete: raise FreecodeSessionException("Version conflict in options.") # Hidden flag special handling if "hidden_from_frontpage" in data: data["hidden_from_frontpage"] = data["hidden_from_frontpage"] in ("Y", "y") # Now merge in the URLs, doing symbol substitution urllist = [] for (label, furl) in list(urls.items()): for (k, v) in list(data.items()): if type(v) == type(""): furl = furl.replace('${' + k + '}', v) urllist.append((label, furl)) # Sort out what things go where update = Update() if options.showversion: pass elif options.query: update.name = options.query else: update.name = data.pop('name') update.urlassoc = urllist for (k, v) in list(data.items()): if k in FreecodeMetadataFactory.projectwide: # Hack to get around a namespace collision if k == "project_release_tag": k = "release_tag" update.per_project[k] = v else: update.per_release[k] = v # Return this return (options, update) if __name__ == "__main__": try: # First, gather update data from stdin and command-line switches factory = FreecodeMetadataFactory() (options, update) = factory.getMetadata(sys.stdin) # Some switches shouldn't be passed to the server query = 'query' in options.__dict__ and options.query verbose = 'verbose' in options.__dict__ and options.verbose delete = 'delete' in options.__dict__ and options.delete dryrun = 'dryrun' in options.__dict__ and options.dryrun showversion = 'showversion' in options.__dict__ and options.showversion if showversion: print("freshcode-submit", version) raise SystemExit(0) # Time to ship the update. # Establish session session = FreecodeSession(verbose=int(verbose), emit_enable=not dryrun) try: session.on_project(update.name) except ValueError as e: print(e) print("freshcode-submit: looks like a server-side problem at freshcode.club; bailing out.", file=sys.stderr) raise SystemExit(1) if options.query: print("Project: %s" % session.project_data["name"]) print("Summary: %s" % session.project_data["oneliner"]) print("Description: %s" % session.project_data["description"].replace("\n", "\n ").rstrip()) print("License-List: %s" % ",".join(session.project_data["license_list"])) print("Project-Tag-List: %s" % ",".join(session.project_data["tag_list"])) for assoc in session.project_data['approved_urls']: #print "Querying", assoc["redirector"] #req = RequestWithMethod(method="HEAD", # url=assoc["redirector"], # data={}, # headers={}) #handle = urllib2.urlopen(req) #print "===" #print handle.info() #print "===" print("%s-URL: %s" % (assoc["label"].replace(" ","-"), assoc["redirector"])) if 'recent_releases' in session.project_data and session.project_data['recent_releases']: most_recent = session.project_data['recent_releases'][0] print("Version: %s" % most_recent['version']) print("Tag-List: %s" % ",".join(most_recent['tag_list'])) if most_recent.get('hidden_from_frontpage'): print("Hide: Y") else: print("Hide: N") print("") print(most_recent["changelog"]) else: # OK, now actually add or delete the release. if update.per_project: session.update_core(update.per_project) if update.urlassoc: session.update_urls(update.urlassoc) if delete: session.withdraw_release(update.per_release['version']) elif update.per_release and list(update.per_release.keys())!=["version"]: session.publish_release(update.per_release) except FreecodeSessionException as e: print("freshcode-submit:", e.msg, file=sys.stderr) sys.exit(1) except requests.exceptions.HTTPError as f: print("freshcode-submit: HTTPError %s" % (f.code), file=sys.stderr) print(f.read(), file=sys.stderr) sys.exit(1) except requests.exceptions.RequestException as f: print("freshcode-submit: URLError %s" % (f.reason,), file=sys.stderr) sys.exit(1) # end |
Added doc/logo.svgz.
cannot compute difference between binary files
Added doc/mktrove.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | #!/usr/bin/php -qC <?php /** * api: cli * title: convert trove.csv * description: generates PHP array of trove categories and tag leaves * * `soffice --headleass --convert-to csv trove.ods` * does not work, so we need it preconverted in trove.csv * * only first 4 columns contain the tree and leaves * */ #-- read in $csv = array_map("str_getcsv", file(__DIR__."/trove.csv")); unset($csv[0]); // remove head // target $TREE = array(); $TMAP = array(); // last tree $last_path = array(); # loop over lines foreach ($csv as $row) { // Cut out only 5 columns (rest of spreadsheet is documentation) $py_trove = $row[5]; $row = array_map("trim", array_slice($row, 0, 5)); // merge current leave parts with last path if ($row = array_filter($row)) { $row = array_slice($last_path, 0, key($row)) + $row; $last_path = $row; } // skip empty else { continue; } $TMAP[] = array($py_trove, implode(" :: ", $row)); // append to what we have $path = array_filter($row); $leaf = array_pop($path); $var = enclose("['", $path, "']"); eval(" \$TREE{$var}[] = '$leaf'; "); # if ($up_var) unset(\$TREE{$up_var}[array_search('$up_leaf', \$TREE{$up_var})]); } // reorder, strip Status and License, then output print var_export54(array( "Topic" => $TREE["Topic"], "Programming Language" => $TREE["Programming Language"], "Environment" => $TREE["Environment"], "Framework" => $TREE["Framework"], "Operating System" => $TREE["Operating System"], "Audience" => $TREE["Audience"], # "Natural" => $TREE["Natural"], )); // save mapping onto py-trove file_put_contents(__DIR__."/trove.pypi-map.csv", json_encode($TMAP, JSON_PRETTY_PRINT)); #-- le functions function enclose($pre, $values, $post) { return $values ? $pre . implode("$post$pre", $values) . $post : ""; } function var_export54($var, $indent="") { switch (gettype($var)) { case "string": return '"' . addcslashes($var, "\\\$\"\r\n\t\v\f") . '"'; case "array": // $indexed = array_keys($var) === range(0, count($var) - 1); $r = []; foreach ($var as $key => $value) { $indexed = is_numeric($key); $r[] = "$indent " . ($indexed ? "" : var_export54($key) . " => ") . var_export54($value, "$indent "); } return "[\n" . implode(",\n", $r) . "\n" . $indent . "]"; case "boolean": return $var ? "TRUE" : "FALSE"; default: return var_export($var, TRUE); } } |
Added doc/trove.ods.
cannot compute difference between binary files
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 freshcode.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 | + + + + + + + + + + + - - + + + + + + | /** * api: css * type: stylesheet * title: freshcode.club layout+style * description: Simulates the late freecode.com layout and looks; well mostly. * version: 0.6.5 * * Centered two-pane layout. The #main section is usually 33% of the screen width, * while the #sidebar floats at the right. They're repositioned only using padding: * to the outer html,body{}. * |
18 19 20 21 22 23 24 | 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 | + + - - + + + + + + + + + + + + + + + + + + + + + + + + + | vertical-align: middle; border: 0; } h2 { font-size: 13pt; } h3 { font-size: 12pt; } h4 { font-size: 11.5pt; } h5 { font-size: 11pt; } a[href=""] { opacity: 0.20; |
50 51 52 53 54 55 56 57 58 | 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 | + + + + - + - + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + | width: 60%; max-width: 70%; background: #fdfdfd; border: 1pt solid #777; } /** * Main action link box, hovering halfway over header box. * */ #tools { margin: 0 150pt; |
110 111 112 113 114 115 116 | 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 | + + - - + + + + + + + + + + + + + + + + + - + - + - + - + - + + + + - + + + + + | display: block; background: linear-gradient(#f7f7f7 40%, #eee 70%, #ddd 100%); } #sidebar section.article-links.trimmed a { height: 1.3em; overflow: hidden; } #sidebar.community-web { font-size: 90%; |
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 | 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 | + + + + + + + + + + + + + + + + + | } #main .login.box #login_url { background: #f7faff; background: linear-gradient(99deg, #888 0%, #a0a0a0 1%, #fc7 3%, #bbb 5%, #fff 8%); padding-left: 33px; } /** * Project listing on frontpage and /projects/xyz * */ #main .project h3 { white-space: nowrap; } #main .project h3 a { color: #000; display: inline-block; max-width: 400pt; overflow: hidden; text-overflow: ellipsis; } #main .project h3 a:hover, #main .project h3 a:hover .version { color: #237; } #main .project .version { font-style: normal; font-weight: 200; } #main .project .links { float: right; } #main .project .links a[href=""] { opacity: 0.15; } #main .project .published_date { font-weight: 200; font-size: 65%; color: #777; position: relative; top: -3.5pt; } #main .project img.preview { padding: 3px; border: 1px solid #eee; box-shadow: 2px 2px 7px 0px #ccc; margin: 3pt; } #main .project .description { padding-bottom: 5pt; border-bottom: 1px solid #ccc; margin-bottom: 3pt; font-size: 95%; |
240 241 242 243 244 245 246 | 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 | + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + + + + + - + + + + + + + + - + + + + + - + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | } #main .project .trimmed div { box-shadow: inset 0 5pt 5pt 0px #ffeecc; height: 5.55em; overflow: hidden; } /** * Shortened view on search/ * */ #main .project.search { clear: both; margin-bottom: 15pt; } #main .project.search h3 { display: inline; margin: 2pt; padding: 2pt; font-size: 95%; } #main .project.search img { border: 0; box-shadow: none; margin: 0 2pt 3pt 0; } #main .project.search small.description { line-height: 80%; } /** * Variation for /projects/xyz detail view. */ |
Added gimmicks.js.
| 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | /** * api: jquery * title: UI behaviour * description: Well, just client-side interface features * version: 0.4 * depends: jquery, jquery-ui * * Collects a few event callbacks to toggle and trigger all the things. * * → Compacted entries (.trimmed class) * * → Trove tags * → Injection to submit_form input box * → Or appending to links on page_tags cloud * * → Action links (green dashed underline) * → Lock entry in submit_form * → Injecting $version placeholder in URLs * → Sidebar box for submit_imports * * → Forum action links * */ // DOM ready $(document).ready(function(){ // Make frontpage #main .project descriptions expandable, by undoing .trimmed; animatedly $(".project .trimmed").one("click", function(){ $(this).animate({"max-height": "20em"}); }); // Likewise for compacted news feeds in #sidebar $(".article-links.trimmed").one("click", function(){ $(this).toggleClass("trimmed"); }); /** * Trove map and tag cloud. * */ // Trove tags add to input#tags field $("#trove_tags.add-tags .option").click(function(){ var $tags = $("#tags"); var prev = $tags.val(); $tags.val(prev + (prev.length ? ", " : "") + $(this).data("tag")); }); // Trove tags highlight in page_tags cloud $("#trove_tags.pick-tags .option").click(function(){ // highlight in trove box $(this).toggleClass("selected"); var tag = $(this).data("tag"); // and in tag cloud $("#tag_cloud a:contains('"+tag+"')").toggleClass("selected"); }); // Append trove[]= selection to any clicked links in tag cloud $("#tag_cloud a").click(function(){ // array from selected tags var tags = $("#trove_tags b.selected, #trove_tags .option.selected").map(function(){ return $(this).data("tag"); }).get(); // append to current link if (tags.length) { this.href += "&trove[]=" + tags.join("&trove[]="); } }); /** * Action links are marked up using <a class="action func-name"> * */ // submit_form: lock entry $(".action.lock-entry").click(function(){ var $lock = $("input[name='lock']"); if (!$lock.val().length && $lock.attr("placeholder")) { $lock.val($lock.attr("placeholder")); } }); // submit_form: apply $version placeholder in URLs $(".action.version-placeholder").click(function(){ var $input = $(this).parent().parent().find("input, textarea").eq(0); var version = $("input[name='version']").val(); if (version.length) { version = new RegExp(version.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"), "g"); $input.val($input.val().replace(version, "$version")); } }); // submit_form: apply $version placeholder in URLs $(".action.submit-import").click(function(){ $("#sidebar section").eq(0).toggle("medium"); $("#sidebar .submit-import").fadeToggle("slow"); }); // Copying some form fields from /submit to /drchangelog $(".action.drchangelog").click(function() { $(this).attr("href", "/drchangelog?autoupdate_module=" + $("form[method='POST']").serialize()); // and let default action proceed }); // Append search field in #tools bar $("#search_q a").click(function() { var q; if (q = $("#search_q input[name=q]").val()) { $(this).attr("href", "/search?q=" + q); } // and let default action proceed }); /** * Forum actions. * */ // Expand forum previews $(".forum .entry").one("click", function(){ $(this).find(".excerpt, .content, .funcs").toggleClass("trimmed"); }); // Post submit button $(".forum").delegate(".action", "click", function(){ // entry/post id var id = $(this).data("id"); var func = this.classList[1]; var $target = $(this).closest(".entry"); // new if (func == "forum-new") { $target.load("/forum/post", { "pid": id }).fadeIn(); } // reply if (func == "forum-reply") { $target.append("<ul><li></li></ul>"); $target.find("ul li").load("/forum/post", { "pid": id }).fadeIn(); } // 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); } }); }); |
Added handler_api.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 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: freshcode * title: Submit API * description: Implements the Freecode JSON Rest API for release updates * version: 0.2 * type: handler * category: API * doc: http://fossil.include-once.org/freshcode/wiki/Freecode+JSON+API * author: mario * license: AGPL * * This utility code emulates the freecode.com API, to support release * submissions via freecode-submit and similar tools. The base features * fit well with the freshcode.club database scheme. * * Our RewriteRules map following Freecode API request paths: * * GET projects/<name>.json query * PUT projects/<name>.json update_core * POST projects/<name>/releases.json publish * GET projects/<name>/releases/<w>.json version_GET, id= * DELETE projects/<name>/releases/<i>.json version_DELETE * * From the ridiculous amount of URL manipulation calls, we just keep: * * GET/PUT projects/<name>/urls.json urls (assoc array) * * * Retrieval requests usually come with an ?auth_code= token. For POST * or PUT access it's part of the JSON response body. Which comes with * varying payloads depending on request type: * * { "auth_code": "pw123", * "project": { * "license_list": "GNU GPL", * "project_tag_list": "kernel,operating-system", * "oneliner": "Linux kernel desc", * "description": "Does this and that.." * } } * * Any crypt(3) password hash in a projects `lock` field will be checked * against the plain auth_code. * * At this point everything went through index.php already; runtime env * thus initialized. Therefore API methods can be invoked directly, which * either retrieve or store project data, and prepare a JSON response. * */ /* @Test @sh @t query wget http://freshcode/projects/linux.json?auth_code=unused -O- ./fc-submit -q linux @t change_core ./fc-submit -P linux -D "new proj" -S "oneliner" -L "GNU GPL" -T "kernel,linux" -n -V @t publish ./fc-submit -P linux -v "3.55.1" -c "Change all the things" -t "major,bugfix" -n -V @t delete ./fc-submit -P linux -v "3.55.1" -d -n -V @t urls wget http://freshcode/projects/linux/urls.json?auth_code=0 -O- */ // Wraps API methods and utility code class FreeCode_API { // Project name var $name; // API method var $api; // HTTP method var $method; // POST/PUT request body var $body; // Optional auth_code (from URL or JSON body) var $auth_code; // Optional revision ID (just used for releases/; either "pending" or t_published timestamp) var $id; // JSON success message var $OK = array("success" => TRUE); /** * Initialize params from RewriteRule args. * */ function __construct() { // URL params $this->name = $_GET->proj_name["name"]; $this->api = $_GET->id->strtolower->in_array("api", "query,update_core,publish,urls,version_get,version_delete"); $this->method = $_SERVER->id->strtoupper["REQUEST_METHOD"]; $this->auth_code = $_REQUEST->text["auth_code"]; $this->id = $_REQUEST->text["id"]; // optional param // Request body is only copied, because it comes with varying payloads (release, project, urls) if ($_SERVER->int["CONTENT_LENGTH"] && $_SERVER->stripos…json->is_int["CONTENT_TYPE"]) { $this->body = json_decode(file_get_contents("php://input"), TRUE); $this->auth_code = $this->body["auth_code"]; } } /** * Invoke API target function. * After retrieving current project data. * */ function dispatch() { // Fetch latest revision if (!$project = new release($this->name)) { $this->error(NULL, "404 No such project", "Invalid Project ID"); } // Run dialed method, then output JSON response. $this->json_exit( $this->{ $this->api ?: "error" }($project) ); } /** * GET project description * ----------------------- * * @unauthorized * * Just returns the current releases project description * and basic fields. * Freecode API clients expect: * → id (something numeric, which we don't have, so just a CRC32 here) * → permalink (=name) * → oneliner (we just extract the first line) * → license_list * → tag_list * → approved_urls * * Also appends extraneous freshcode fields. * */ function query($project) { // Everyone can access this, but only the owner will see private fields $data = $this->auth_filter($project); // Alias some fields for fc-submit, but append our data scheme intact return array( "project" => array( "id" => crc32($data["name"]), "permalink" => $data["name"], "oneliner" => substr($data["description"], 0, 100), "license_list" => p_csv($data["license"]), "tag_list" => p_csv($data["tags"]), "approved_urls" => $this->array_uncolumn(p_key_value($data["urls"], NULL)) ) + $data->getArrayCopy() ); } // Expand associative URLs into [{label:,redirector:},..] list function array_uncolumn($kv, $ind="label", $dat="redirector", $r=array()) { foreach ($kv as $key=>$value) { $r[] = array($ind=>$key, $dat=>$value); } return $r; } /** * PUT project base fields * ----------------------- * * @auth-required * * Whereas the project ->body contains: * → license_list * → project_tag_list * → oneliner (IGNORED) * → description * Additionally we'd accept: * → state * → download (URL) * → homepage * */ function update_core($project) { $core = new input($this->body["project"], "core"); // extract fields $new = array( // standard FC API fields "license" => tags::map_license(p_csv($core->words["license_list"])[0]), "tags" => $core->text->f_tags["project_tag_list"], "description" => $core->text["description"], // additional overrides "homepage" => $core->url->http["homepage"], "download" => $core->url->http["download"], "state" => tags::state_tag($core->name["state"]), ); return $this->insert($project, $new); } /** * POST release/version * -------------------- * * @auth-required * * Here the release body contains: * → version * → changelog * → tag_list * → hidden_from_frontpage * We'd also accept: * → state * → download * */ function publish($project) { $rel = new input($this->body["release"], "rel"); // extract fields $new = array( "version" => $rel->text["version"], "changes" => $rel->text["changelog"], "scope" => tags::scope_tags($rel->text["tag_list"]), "state" => tags::state_tag($rel->text["state"] . $rel->text["tag_list"]), "download" => $rel->url->http["download"], ); $flags = array( "hidden" => $rel->int["hidden_from_frontpage"], ); return $this->insert($project, $new, $flags); } /** * Check for "pending" releases * ---------------------------- * * @unauthorized * * We don't have a pre-approval scheme on Freshcode currently, * so this just returns a history of released versions. * * For the `id` we're just using the `t_published` timestamp. * Thus a "withdraw" request could reference it. * */ function version_GET($project) { assert($this->id === "pending"); // Query release revisions $list = db(" SELECT name, version, t_published, MAX(t_changed) AS t_changed, scope, hidden, changes FROM release WHERE name=? GROUP BY version LIMIT 10", $this->name ); // Assemble oddly nested result array $r = []; foreach ($list as $row) { $r[] = array("release" => array( "id" => $row["t_published"], "version" => $row["version"], "tag_list" => explode(" ", $row["scope"]), "hidden_from_frontpage" => (bool)$row["hidden"], "approved_at" => gmdate(DateTime::ISO8601, $row["t_changed"]), "created_at" => gmdate(DateTime::ISO8601, $row["t_published"]), "changelog" => $row["changes"], )); } return $r; } /** * "Withdraw" a "pending" release * ------------------------------ * * @auth-required * * We're faking two things here. Firstly that the review process * was enabled by default. Secondly that you could delete things. * (The database is designed to be somewhat "immutable", we just * pile up revisions normally.) * * So withdrawing a release just means it gets "hidden" and flagged * for moderator attention. There's also a "delete" flag; but thats * current purpose is terminating a project lifeline (due to VIEW * revision grouping). * * The reasoning being that withdrawn releases are really just * authors making last minute fixes; commonly retracted releases * are just resent later, or with a different changelog. * */ function version_DELETE($project) { // Obviously requires a valid `lock` hash $project = $this->with_permission($project); assert(is_numeric($this->id)); // Hide all entries for revision $r = db([ " UPDATE release ", " SET :, " => ["hidden" => 1, "flag" => 0], " WHERE :& " => ["name" => $this->name, "t_published" => $this->id] ]); return $r ? $this->OK : $this->error(NULL); } /** * URL editing * ----------- * * @auth-required * * Here we deviate from the overflowing Freecode API with individual * URL manipulation. (PUT,POST,DELETE /projects/<name>/urls/targz.json) * That's just double the work for client and API. * * Instead on freshcode there is but one associative URL blob for * reading and updating all URLs at once. * * GET /projects/name/urls.json { "urls" : { "src": "http://..", * "github": "http://.." } } * PUT /projects/name/urls.json { "urls": { "txz":, "doc":.. } } * * Our labels use the tag-form, so incoming labels will be adapted. * ("Tar/BZ2" becomes "tar-bz2".) * * Internally the urls are stored in an INI-style key=value text blob. * (But the API stays somewhat more RESTy with an associative dict.) * */ function urls($project) { /** * For a GET query just mirror "Other URLs" as dict * * @unauthorized * */ if ($this->method == "GET") { return array("urls" => p_key_value($project["urls"], NULL)); } /** * Updates may come as PUT, POST, PUSH request * * @auth-required * */ else { // Extract all $urls = new input($this->body["urls"], "urls"); $urls = $urls->list->url[$urls->keys()]; // Extract homepage and download specifically $urls = array_change_key_case($urls, CASE_LOWER); $new = array_intersect_key($urls, array_flip(str_getcsv("homepage,download"))); $urls = array_diff_key($urls, $new); // Join rest into key=value format $new["urls"] = ""; foreach ($urls as $label => $url) { $label = trim(preg_replace("/\W+/", "-", $label), "-"); $new["urls"] .= "$label = $url\r\n"; } // Update DB $this->insert($project, $new); } } /** * Perform partial update * * @auth-required * */ function insert($project, $new, $flags=[]) { // Write permissions required obviously. $project = $this->with_permission($project); // Add new fields to $project $project->update(array_filter($new, "strlen"), $flags, [], TRUE); // Store or return JSON API error. return $project->store() and (header("Status: 201 Created") + 1) ? $this->OK : $this->error(NULL, "500 Internal Issues", "Database mistake"); } /** * Strip down raw project data for absent auth_code * in read/GET requests. * */ function auth_filter($data) { if (!$this->is_authorized($data)) { unset( $data["lock"], $data["submitter_openid"], $data["submitter"], $data["hidden"], $data["deleted"], $data["flag"], $data["social_links"], $data["autoupdate_regex"], $data["autoupdate_url"], $data["t_changed"] ); } return $data; } /** * Prevent further operations for (write) requests that * actually REQUIRE a valid authorization token. * */ function with_permission($data) { return $this->is_authorized($data) ? $data : $this->error(NULL, "401 Unauthorized", "No matching API auth_token hash. Add a crypt(3) password in your freshcode.club project entries `lock` field, comma-delimited to your OpenID handle. See http://fossil.include-once.org/freshcode/wiki/Freecode+JSON+API"); } /** * The `lock` field usually contains one or more OpenID urls. It's * a comma-delimited field. * * Using the API additionally requires a password hash, as in crypt(3) * or `openssl passwd -1` or PHPs password_hash(), to be present. * * It will simply be compared against the ?auth_code= parameter. * */ function is_authorized($data) { foreach (preg_grep("/^[^:]+$/", p_csv($data["lock"])) as $hash) { if (password_verify($this->auth_code, $hash)) { return TRUE; } } return FALSE; } /** * JSON encode and finish. * */ function json_exit($data) { header("Content-Type2: json/vnd.freecode.com; version=3; charset=UTF-8"); header("Content-Type: application/json"); exit( json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) ); } /** * Bail with error response. * */ function error($data, $http = "503 Unavailable", $json = "unknown method") { header("Status: $http"); $this->json_exit(["error" => "$json"]); } } ?> |
Changes to img/changes.png.
cannot compute difference between binary files
Added img/drchangelog.png.
cannot compute difference between binary files
Deleted img/links/advancescripts.com.jpeg.
cannot compute difference between binary files
Deleted img/links/bigresource.com.jpeg.
cannot compute difference between binary files
Deleted img/links/devscripts.com.jpeg.
cannot compute difference between binary files
Added img/links/distrowatch.com.jpeg.
cannot compute difference between binary files
Deleted img/links/fatscripts.com.jpeg.
cannot compute difference between binary files
Added img/links/findbestopensource.com.jpeg.
cannot compute difference between binary files
Added img/links/freeopensourcesoftware.org.jpeg.
cannot compute difference between binary files
Deleted img/links/hotscripts.com.jpeg.
cannot compute difference between binary files
Added img/links/icewalkers.com.jpeg.
cannot compute difference between binary files
Added img/links/libreprojects.net.jpeg.
cannot compute difference between binary files
Added img/links/linuxappfinder.com.jpeg.
cannot compute difference between binary files
Added img/links/linuxgames.com.jpeg.
cannot compute difference between binary files
Added img/links/linuxsoft.cz.jpeg.
cannot compute difference between binary files
Deleted img/links/needscripts.com.jpeg.
cannot compute difference between binary files
Added img/links/openfontlibrary.org.jpeg.
cannot compute difference between binary files
Added img/links/openhatch.org.jpeg.
cannot compute difference between binary files
Added img/links/opensourcearcade.com.jpeg.
cannot compute difference between binary files
Added img/links/opensourcelinux.org.jpeg.
cannot compute difference between binary files
Added img/links/opensourcelist.org.jpeg.
cannot compute difference between binary files
Added img/links/opensourcescripts.com.jpeg.
cannot compute difference between binary files
Added img/links/osalt.com.jpeg.
cannot compute difference between binary files
Deleted img/links/scripts.com.jpeg.
cannot compute difference between binary files
Deleted img/links/scripts20.com.jpeg.
cannot compute difference between binary files
Added img/links/thechangelog.com.jpeg.
cannot compute difference between binary files
Added img/links/zwodnik.com.jpeg.
cannot compute difference between binary files
Added img/logo.png.
cannot compute difference between binary files
Changes to img/nopreview.png.
cannot compute difference between binary files
Added img/screenshot/.empty.
1 | + | This directory gets populated by cron.daily/screenshots.php |
Added img/user.png.
cannot compute difference between binary files
Changes to index.php.
1 2 3 4 5 | 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 | + - + + + + + + + + + + + + - + + + + + + | <?php /** * api: php * type: main * title: Freshcode.club * description: FLOSS software release tracking website |
Deleted input.php.
|
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
|
Deleted layout_aux.php.
|
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
|
Deleted layout_bottom.php.
|
| - - - - - - - - - - - - - - - - - - - - - - - - - |
|
Deleted layout_header.php.
|
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
|
Added lib/curl.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: php7 * title: fluent curl * description: simple wrapper around curl functions * version: 0.3 * license: Public Domain * * * This simple wrapper class and its factory function allow * compact invocations of curl. You can pass either just an * URL, an option array, a previously wrapped curl() or a * raw curl resource handle. * Methods and options (CURL_ constants) lose their prefix * once wrapped, and can be easily chained: * * curl($url)->followlocation(1)->verbose(1)->exec(); * * Option names are case-insensitive too. The exec() and any * setting retrieval terminate the fluent call. * * * Individual curl() wrappers can also be combined into a * parallel-request curl_multi() instance: * * curl_multi($url1, $url2, $url3)->verbose(1)->exec(); * * Here the handles may be passed as associative array or * indexed list. exec() will wait until all finish, then * directly return an array of result contents. * In-between option calls are applied to all bound handles. * */ # hybrid constructor function curl($url=NULL) { return is_a($url, "curl") ? $url : new curl($url); } /** * Fluent curl wrapper which obviates CURL_ prefixes. * * Makes curl_functions available as methods. Previous constants * are easily accessible through chainable method calls as well. * Only getinfo() options are mapped as virtual properties. * */ class curl { /** * resource / curl handle * */ public $handle = NULL; /** * Setup parameters. * */ static $defaults = array( "returntransfer" => 1, "httpget" => 1, "http200aliases" => array(200, 201, 202, 203), "header" => 0, ); /** * Some checks before returning the resulting content. * */ public $assert = array(); /** * Initialize, where $params is usually just an $url, or an [OPT=>val] list * */ function __construct($params) { // create $this->handle = is_object($params) ? $params : curl_init(); // merge options $params = array_merge( curl::$defaults, array("followlocation" => !ini_get("safe_mode")), is_array($params) ? array_change_key_case($params) : array(), is_string($params) ? array("url" => $params) : array() ); // multiple [url=>$url, post=>$bool, ..] options if (is_array($params)) foreach ($params as $cmd=>$opt) { $this->__call($cmd, array($opt)); } } /** * Most invocations are mapped onto CURL_CONST settings, * while some are just plain function calls: * * @method exec() * @method errno() * @method error() * @method getinfo() */ function __call($opt, $args) { # CURLOPT_*** constant if (defined($const = "CURLOPT_" . strtoupper($opt))) { curl_setopt($this->handle, constant($const), $args[0]); } # curl_action function elseif (function_exists($func = "curl_$opt")) { return call_user_func_array($func, array_merge(array($this->handle), $args)); } # neither exists else { trigger_error("curl: unknown '$opt' option", E_USER_ERROR); } return $this; } /** * Append result-checks such as ["HTTP_CODE" => [200, 201]]. * */ function assert($list) { $this->assert += $list; return $this; } /** * Wrap exec() to test result handle for HTTP or CURL properties. * */ function exec() { $args = func_get_args(); $content = $this->__call("exec", $args); foreach ($this->assert as $option => $values) { if (!in_array($this->$option, $values)) { return NULL; } } return $content; } /** * retrieve constants * */ function __get($opt) { // @implicit error message from constant() for non-existant symbols return curl_getinfo($this->handle, constant($const = "CURLINFO_" . strtoupper($opt))); } } # hybrid constructor function curl_multi($url=NULL) { return new curl_multi($url); } /** * Associative handler for multiple concurrent curl requests. * Not so fluent. * */ class curl_multi { /** * Indexed or associative list * */ public $list = array(); /** * Multi-handle * */ public $mh = NULL; /** * Bind list of curl handles. * * An associative array can be passed here, either of prepared curl() handles, * just URLs, or an curl option array each. * */ function __construct($list) { // optionally get indexed params as list if (func_num_args() >= 2) { $list = func_get_args(); } // auto-wrap curl() handlers $list = array_map("curl", $list); // copy handle list $this->list = $list; // bind actual curl handles $this->mh = curl_multi_init(); foreach ($list as $cw) { curl_multi_add_handle($this->mh, $cw->handle); } } /** * Run until all individual curl() handles are finished. * * Implicitly returns a list of result contents (associative array as well). * */ function exec($timeout=1.50, $t=0.0, $running=1) { // process within timeframe while (($timeout > $t += $timeout/50) and $running) { curl_multi_exec($this->mh, $running); curl_multi_select($this->mh, $timeout/50); } // fetch results, then close handles return $this->close( $this->getcontent() ); } /** * Retrieve results, associative array. * */ function getcontent() { return array_map("curl_multi_getcontent", array_map("current",/*retrieve first property (->handle) each*/ $this->list)); } /** * Close all curl() handles in $list, and the curl-multi wrapper. * */ function close($r=NULL) { $this->__call("close", array()); curl_multi_close($this->mh); return $r; } /** * Applies options on each curl handle. * */ function __call($opt, $args) { foreach ($this->list as $cw) { $cw->__call($opt, $args); } } } ?> |
Added lib/db.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 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * title: PDO wrapper * description: Hybrid db() interface for extended SQL parameterization and result folding * api: php * type: database * version: 0.9.9 * depends: php:pdo * license: Public Domain * author: Mario Salzer * doc: http://fossil.include-once.org/hybrid7/wiki/db * * * QUERY * * Provides simple database queries with enumerated / named parameters. It's * flexible in accepting plain PDO scalar arguments or arrays. Array args get * merged, or transcribed when special placeholders are present: * * $r = db("SELECT * FROM tbl WHERE a>=? AND b IN (??)", $a, array($b, $c)); * * Extended placeholder syntax: * * ?? Interpolation of indexed arrays - useful for IN clauses. * :: Turns associative arrays into a :named, :value, :list. * :? Interpolates key names (ignores values). * * :& Becomes a `name`=:value list, joined by AND - for WHERE clauses. * :| Becomes a `name`=:value list, joined by OR - for WHERE clauses. * :, Becomes a `name`=:value list, joined by , commas - for UPDATEs. * * :* Expression placeholder, where the associated argument should * contain an array ["AND foo IN (??)", $params] - which only * interpolates if $params contains any value. Can be nested. * * Configurable {TOKENS} from db()->tokens[] are also substituted.. * * * RESULT * * The returned result can be accessed as single data row, when fetching just * one: * $result->column * $result["column"] * * Or just traversed row-wise as usual by iteration * * foreach (db("...") as $row) * * Alternatively by object-wrapping (unlike plain PDO->fetchObject() this * hydrates the object using its normal constructor) the result set with: * * foreach ($result->into("ArrayObject") as $row) * * Yet all PDO ->fetch() methods are still available for use on the result obj. * * * CONNECT * * The db() interface binds the global "$db" variable. It ought to be * initialized with: * * db(new PDO(...)); * * It's wrapped PDO handle can also be retrieved with just $pdo = db(); then. * * * RECORD WRAPPER * * There's also a simple table data gateway wrapper implemented here. It * accepts db() queries for single entries, and allows ->save()ing back, or * to ->delete() records. * You should only use it in conjuction with sql2php and its simpler wrappers. * */ /** * Hybrid instantiation / query function. * Couples `$db` in the shared/global scope. * */ function db($sql=NULL, $params=NULL) { #-- shared PDO handle $db = & $GLOBALS["db"]; // Alternatively could be just static to hide it behind db() #-- open database if (is_object($sql)) { // use passed param $db = new db_wrap($sql); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); $db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, is_int(stripos($db->getAttribute(PDO::ATTR_DRIVER_NAME), "mysql"))); $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); $db->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); // save settings $db->tokens = array("PREFIX"=>""); // or reference global $config $db->in_clause = $db->getAttribute(PDO::ATTR_DRIVER_NAME) == "sqlite" and $db->getAttribute(PDO::ATTR_CLIENT_VERSION) < 3.6; } #-- return PDO handle elseif (empty($sql)) { return $db; } #-- just dispatch to the wrapper else { $args = array_slice(func_get_args(), 1); return $db($sql, $args); } } /** * Binds PDO handle, allows original calls and extended placeholder use. * */ class db_wrap { /** * Keep PDO handle. * */ public $pdo = NULL; function __construct($pdo) { $this->pdo = $pdo; } /** * Chain to plain PDO if any other method invoked. * */ function __call($func, $args) { return call_user_func_array(array($this->pdo, $func), $args); } /** * Prepares and executes query after extended placeholders and parameter unpacking. * */ function __invoke($sql, $args=array()) { // $sql may contain associative SQL parts and parameters if (is_array($sql)) { list($sql, $args) = $this->join($sql); } // reject plain strings in SQL if (strpos($sql, "'")) { trigger_error("SQL query contained raw data. DO NOT WANT", E_USER_WARNING); return NULL; } // flatten array arguments and extended placeholders list($sql, $args) = $this->fold($sql, $args); // placeholders if (!empty($this->tokens) && strpos($sql, "{")) { $sql = preg_replace_callback("/\{(\w+)(.*?)\}/", array($this, "token"), $sql); } // older SQLite workaround if (!empty($this->in_clause) && strpos($sql, " IN (")) { // only for ?,?,?,? enum params $sql = preg_replace_callback("/(\S+)\s+IN\s+\(([?,]+)\)/", array($this, "in_clause"), $sql); } // just debug output if (!empty($this->test)) { print json_encode($args)." => " . trim($sql) . "\n"; return; } // run $s = $this->prepare($sql) and $r = $s->execute($args); // wrap return $s && $r ? new db_result($s) : $s; } /** * Expands the extended placeholders and flattens arrays from parameter list. * */ function fold($sql, $args) { // output parameter list $flat_params = array(); #-- flattening sub-arrays (works for ? enumarated and :named params) foreach ($args as $i=>$a) { // subarray that corresponds to special syntax placeholder? if (is_array($a) and preg_match("/ \?\? | : [?:* &,|] /x", $sql, $capture, PREG_OFFSET_CAPTURE)) { list($token, $pos) = current($capture); // placeholder substitution, possibly changing $a params $replace = $this->{self::$expand[$token]}($a); // update SQL string $sql = substr($sql, 0, $pos) . $replace . substr($sql, $pos + strlen($token)); } // unfold into plain parameter list if (is_array($a)) { $flat_params = array_merge($flat_params, $a); } else { $flat_params[] = $a; } } return array($sql, $flat_params); } /** * Syntax expansion callbacks * */ static $expand = array( "??" => "expand_list", ":?" => "expand_keys", "::" => "expand_named", ":," => "expand_assoc_comma", ":&" => "expand_assoc_and", ":|" => "expand_assoc_or", ":*" => "expand_expr", ); // ?? array placeholders function expand_list($a) { return implode(",", array_fill(0, count($a), "?")); } // :? name placeholders, transforms list into enumerated params function expand_keys(&$a) { $enum = array_keys($a) === range(0, count($a) - 1); $r = implode(",", $this->db_identifier($enum ? $a : array_keys($a), "`")); $a = array(); return $r; } // :: becomes :named,:value,:list function expand_named($a) { return ":" . implode(",:", $this->db_identifier(array_keys($a)) ); } // for :, expand COMMA-separated key=:key,bar=:bar associative array function expand_assoc_comma($a, $fill = ", ", $replace=array()) { foreach ($this->db_identifier(array_keys($a)) as $key) { $replace[] = "`$key` = :$key"; } return implode($fill, $replace); } // for :& AND-chained assoc foo=:foo AND bar=:bar function expand_assoc_and($a) { return $this->expand_assoc_comma($a, " AND "); } // for :| OR-chained assoc foo=:foo OR bar=:bar function expand_assoc_or($a) { return $this->expand_assoc_comma($a, " OR "); } /** * While :* holds an optional expression and subvalue list. Which only gets * interpolated if the params list is non-empty. * which may be provided as alternative pairs ["AND :&", $and, "OR :|", $or]. * * While each value list should be a list itself, it's common to just pass * one array param for a single ::/?? extended placeholder. (Which then will * be auto-wrapped.) * */ function expand_expr(&$a) { foreach (array_chunk($a, 2) as $pair) if (list($sql, $args) = $pair) { // substitute subexpression as if it were a regular SQL string if (is_array($args) && count($args)) { // rewrap simple value lists into param-args list $args = array_sum(array_map("is_array", $args)) ? $args : array($args); list ($replace, $a) = $this->fold($sql, $args); return $replace; } } $a = array(); // else replace with nothing and omit current data for flattened $params2 } /** * For readability input SQL may come as associative clause => params list. * ["SELECT ?" => $num, * "FROM :?" => [$tbl], * "WHERE :&" => $match * ] * Which is separated here into keys as $sql string and $args from values. * */ function join($sql_args, $sql="", $args=array()) { foreach ($sql_args as $key=>$val) { // Key itself is not an SQL part if (is_int($key)) { // Value then can be an SQL string, or a param if (is_string($val)) { $sql .= $val; } else { $args[] = $val; } } // Plain SQL => Value else { $sql .= $key . "\n "; $args[] = $val; } } return array($sql, $args); } // This is a restrictive filter function for column/table name identifiers. // Can only be foregone if it's ensured that none of the passed named db() $arg keys originated from http/user input. function db_identifier($as, $wrap="") { return preg_replace(array("/[^\w\d_.]/", "/^|$/"), array("_", $wrap), $as); } // Regex callbacks function token($m) { list($m, $tok, $ext) = $m; return isset($this->token[$tok]) ? $this->token[$tok].$ext : $this->token["$tok$ext"]; } function in_clause($m) { list($m, $key, $vals) = $m; $num = substr_count($vals, "?"); return "($key=" . implode("OR $key=", array_fill(0, $num, "? ")) . ")"; } } /** * Allows traversing result sets as arrays or hydrated objects, * or fetches only first result row on ->column_name accesses. * */ class db_result extends ArrayObject implements IteratorAggregate { protected $results = NULL; function __construct($results) { parent::__construct(array(), 2); $this->results = $results; } // used as PDO statement function __call($func, $args) { return call_user_func_array(array($this->results, $func), $args); } // Single column access function offsetGet($name) { // get first result, transfuse into $this if (is_object($this->results)) { $this->exchangeArray($this->results->fetch()); unset($this->results); } // suffice __get return parent::offsetGet($name); } // Just let PDOStatement handle the Traversable function getIterator() { return isset($this->results) ? $this->results : new ArrayIterator($this); } // Or hydrate specific result objects ourselves function into() { $into = func_get_args() ?: array("ArrayObject", 2); return new db_result_iter($this->results, $into); } } /** * More wrapping for hydrated iteration. * */ class db_result_iter implements Iterator { // Again keep PDOStatement and class specifier protected $results = NULL; protected $into = array(); function __construct($results, $into) { $this->results = $results; $this->into = $into; } // Iterator just fetches and converts on traversal protected $row = NULL; public function current() { list($class, $a2, $a3, $a4, $a5) = array_merge($this->into, [NULL, NULL, NULL, NULL]); return new $class($this->row, $a2); } function valid() { return !empty($this->row = $this->results->fetch()); } // unused for normal `foreach` operation function next() { return NULL; } function rewind() { return NULL; } function key() { return NULL; } } /** * Table data gateway. Don't use directly. * * Keeps ->_meta->table name and ->_meta->fields, * uses extendable tables with [ext] field serialization. * Doesn't cope with table joins. (yet?) * * Allows to ->set() and ->save() record back. */ class db_record /*resembles db_result*/ extends ArrayObject { // this is not purposelessly private, but to not pollute (array) typecasts with decorative data private $_meta; // initialize from db() result or array function __construct($results, $table, $fields, $keys) { // meta $this->_meta = new stdClass(); $this->_meta->table = $table; $this->_meta->fields = array_unique(array_merge(array_keys($fields), array_keys($results))); $this->_meta->keys = $keys; // db query result if (is_array($results)) { $this->_meta->new = 1; // instantiate from defaults or given row values } else { //if (is_string($results)) { // queries are handled in wrapper // $results = db($results); //} $results = $results->fetch(); // just get first result row $this->_meta->new = 0; } // unfold .ext if ($this->_meta->ext = isset($results["ext"])) { $results = array_merge($results, unserialize($results["ext"])); } // copy data // and turn object==array parent::__construct((array)$results, 2); //ArrayObject::ARRAY_AS_PROPS // fluent (hybrid constructor wrapper) return $this; } // set field function set($key, $val) { $this->{$key} = $val; return $this; // fluent } // store table back to DB function save($row=NULL) { // source if (empty($row)) { $row = $this->getArrayCopy(); } else { $row = array_merge($this->getArrayCopy(), is_array($row) ? $row : $row->getArrayCopy()); } // fold .ext if ($this->_meta->ext) { $ext = array(); foreach ($row as $key=>$val) { if (!in_array($key, $this->_meta->fields)) { $ext[$key] = $val; unset($row[$key]); } } $row["ext"] = serialize($ext); } // store if ($this->_meta->new) { db("INSERT INTO {$this->_meta->table} (:?) VALUES (??)", $row, $row); $this->_meta->new = 0; } // update else { $keys = $this->keys($row, 1); db("UPDATE {$this->_meta->table} SET :, WHERE :&", $row, $keys); } return $this; // fluent } // split $keys from $row/$this function keys(&$row, $unset=0) { $keys = array(); foreach ($this->_meta->keys as $key) { $keys[$key] = $row[$key]; if ($unset) unset($row[$key]); } return $keys; } // oh noooes function delete() { db("DELETE FROM {$this->_meta->table} WHERE :&", $this->keys($this)); return $this; // well } } ?> |
Added lib/deferred_openid_session.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: php * title: Session startup * description: Avoids session startup until actual login occured * license: MITL * version: 0.3.1 * * Start $_SESSION only if there's already a session cookie present. * (Prevent needless cookies and tracking ids for not logged-in users.) * * The only handler that initiates any login process is `page_login.php` * */ // Kill off CloudFlare cookie when Do-Not-Track header present if ($_SERVER->has("HTTP_DNT") and $_SERVER->boolean["HTTP_DNT"]) { header("Set-Cookie: __cfduid= ; path=/; domain=.freshcode.club; HttpOnly"); } // Check for pre-existant cookie before defaulting to initiate session store if ($_COOKIE->has("USER")) { session_fresh(); } // just populate placeholders else { $_SESSION["openid"] = ""; $_SESSION["name"] = ""; $_SESSION["csrf"] = array(); } // verify incoming OpenID request if ($_GET->has("openid_mode") and empty($_SESSION["openid"])) { include_once("lib/openid.php"); $openid = new LightOpenID(HTTP_HOST); if ($openid->mode) { if ($openid->validate()) { $_COOKIE->no("USER") and session_fresh(); $_SESSION["openid"] = $openid->identity; $_SESSION["name"] = $openid->getAttributes()["namePerson/friendly"]; } } } // Prevent some session tampering function session_fresh() { // Initiate with current session identifier if ($_COOKIE->has("USER")) { session_id($_COOKIE->id["USER"]); } session_name("USER"); session_set_cookie_params(0, "/", HTTP_HOST, false, true); session_start(); // Security by obscurity: lock client against User-Agent $useragent = $_SERVER->text->length…30["HTTP_USER_AGENT"]; // Security by obscurity: IP subnet lock (or just major route for IPv6) $subnet = $_SERVER->ip->length…6["REMOTE_ADDR"]; // Server-side timeout (7 days) $expire = time() + 7 * 24 * 3600; // New ID for mismatches if (empty($_SESSION["state/client"]) or $_SESSION["state/client"] != $useragent or empty($_SESSION["state/subnet"]) or $_SESSION["state/subnet"] != $subnet or empty($_SESSION["state/expire"]) or $_SESSION["state/expire"] < time() ) { session_destroy(); session_regenerate_id(true); session_start(); } // and Repopulate status fields $_SESSION["state/client"] = $useragent; $_SESSION["state/subnet"] = $subnet; $_SESSION["state/expire"] = $expire; } |
Added lib/diff.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 /* Paul's Simple Diff Algorithm v 0.1 (C) Paul Butler 2007 <http://www.paulbutler.org/> May be used and distributed under the zlib/libpng license. This code is intended for learning purposes; it was written with short code taking priority over performance. It could be used in a practical application, but there are a few ways it could be optimized. Given two arrays, the function diff will return an array of the changes. I won't describe the format of the array, but it will be obvious if you use print_r() on the result of a diff on some test data. htmlDiff is a wrapper for the diff command, it takes two strings and returns the differences in HTML. The tags used are <ins> and <del>, which can easily be styled with CSS. */ class pdiff { // static function diff($old, $new){ $matrix = array(); $maxlen = 0; foreach($old as $oindex => $ovalue){ $nkeys = array_keys($new, $ovalue); foreach($nkeys as $nindex){ $matrix[$oindex][$nindex] = isset($matrix[$oindex - 1][$nindex - 1]) ? $matrix[$oindex - 1][$nindex - 1] + 1 : 1; if($matrix[$oindex][$nindex] > $maxlen){ $maxlen = $matrix[$oindex][$nindex]; $omax = $oindex + 1 - $maxlen; $nmax = $nindex + 1 - $maxlen; } } } if($maxlen == 0) return array(array('d'=>$old, 'i'=>$new)); return array_merge( pdiff::diff(array_slice($old, 0, $omax), array_slice($new, 0, $nmax)), array_slice($new, $nmax, $maxlen), pdiff::diff(array_slice($old, $omax + $maxlen), array_slice($new, $nmax + $maxlen))); } // markup <ins> and <del> between old and new text blob static function htmlDiff($old, $new){ $ret = ''; $diff = pdiff::diff(preg_split("/[\s]+/", $old), preg_split("/[\s]+/", $new)); foreach($diff as $k){ if(is_array($k)) $ret .= (!empty($k['d']) ? "<del>" . input::html(implode(' ',$k['d'])) . "</del> " : ''). (!empty($k['i']) ? "<ins>" . input::html(implode(' ',$k['i'])) . "</ins> " : ''); else $ret .= $k . ' '; } return $ret; } // Just compare word-wise without between three revisions, without honoring order static function triDiff($prev, $curr, $next){ $ret = ''; $prev = preg_split("/[\s]+/", $prev); $curr = preg_split("/[\s]+/", $curr); $next = preg_split("/[\s]+/", $next); foreach($curr as $word){ if (!in_array($word, $prev)) { $ret .= "<ins>$word</ins> "; } elseif (!in_array($word, $next)) { $ret .= "<del>$word</del> "; } else { $ret .= "$word "; } } return $ret; } } ?> |
Added lib/feeder.phar.
cannot compute difference between binary files
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 lib/htmlpurifier.phar.
cannot compute difference between binary files
Added lib/input.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 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: php * title: Input $_REQUEST wrappers * type: interface * description: Encapsulates input variable superglobals for easy sanitization access * version: 2.8 * revision: $Id$ * license: Public Domain * depends: php:filter, php >5.0, html_purifier * config: <const name="INPUT_DIRECT" type="multi" value="disallow" multi="disallow|raw|log" description="filter method for direct $_REQUEST[var] access" /> * <const name="INPUT_QUIET" type="bool" value="0" multi="0=report all|1=no notices|2=no warnings" description="suppress access and behaviour notices" /> * throws: E_USER_NOTICE, E_USER_WARNING, OutOfBoundsException * * Using these object wrappers ensures a single entry point and verification * spot for all user data. They wrap all superglobals and filter HTTP input * through sanitizing methods. For auditing casual and unverified access. * * Standardly they wrap: $_REQUEST, $_GET, $_POST, $_SERVER, $_COOKIE * And provide convenient access to filtered data: * $_GET->int("page_num") // method * $_POST->text["commentfield"] // array syntax * $_REQUEST->text->ascii->strtolower["xyz"] // filter chains * * Available filter methods are: * ->int * ->float * ->boolean * ->name * ->id * ->words * ->text * ->ascii * ->nocontrol * ->spaces * ->q * ->escape * ->regex * ->in_array * ->html * ->purify * ->json * ->length * ->range * ->default * And most useful methods of the php filter extension. * ->email * ->url ->uri ->http * ->ip * ->ipv4->public * PHP string modification functions: * ->urlencode * ->strip_tags * ->htmlentities * ->strtolower * Automatic filter chain handler: * ->array * Fetch multiple variables at once: * ->list["var1,key,name"] * Process multiple entries as array: * ->multi->http_build_query["id,token"] * Possible but should be used very consciously: * ->xss * ->sql * ->mysql * ->log * ->raw * * You can also pre-define a standard filter-chain for all following calls: * $_GET->nocontrol->iconv->utf7->xss->always(); * * Using $__rules[] a set of filter rules can be preset per variable name. * * Parameterized filters can alternatively use the ellipsis … symbol (AltGr+:) * instead of the terminating method access syntax. * $_GET->int->range…0…59["minutes"] * * Some filters are a mixture of sanitizing and validation. Basically * all can also be used independently of the superglobals with their * underscore name, $str = input::_text($str); * * For the superglobals it's also possible to count($_GET); or check with * just $_POST() if there are contents. (Use this in lieu of empty() test.) * There are also the three functions ->has ->no ->keys() for array lookup. * * Defining new filters can be done as methods, or since those are picked * up too, as plain global functions. It's also possible to assign function * aliases or attach closures: * $_POST->_newfilter = function($s) { return modified($s); } * Note that the assigned filter name must have an underscore prefixed. * * -- * * Input validation of course is no substitute for secure application logic, * parameterized sql and proper output encoding. But this methodology is a * good base and streamlines input data handling. * * Filter methods can be bypassed in any number of ways. There's no effort * made at prevention here. But it's recommended to simply use ->raw() when * needed - not all input can be filtered anyway. This way an audit handler * could always attach there - when desired. * * The goal is not to coerce, but encourage security via API *simplicity*. * */ /** * Project-specific identifiers. * * Since `input` is an overly generic name, one might wish to wrap it up if * there's an identifier conflict. (Remember; namespaces aren't taxonomies.) * */ #namespace filter; #use \ArrayObject, \Countable, \Iterator, \OutOfBoundsException; #use \HTMLPurifier; /** * Handler name for direct $_REQUEST["array"] access. * * "raw" = reports with warning, * "disallow" = throws an exception */ defined("INPUT_DIRECT") or define("INPUT_DIRECT", "raw"); /** * Notice suppression. * * 0 = report all, * 1 = no notices, * 2 = ignore non-existant filter */ defined("INPUT_QUIET") or define("INPUT_QUIET", 0); /** * @class Request variable input wrapper. * * The methods are defined with underscore prefix, but supposed to be used without * when invoked on the superglobal arrays: * * @method int int[$field] converts input into integer * @method float float[$field] * @method name name[$field] removes any non-alphanumeric characters * @method id id[$field] alphanumeric string with dots * @method text text[$field] textual data with interpunction * */ class input implements ArrayAccess, Countable, Iterator { /** * Data filtering functions. * * These methods are usually not to be called directly. Instead use * $_GET->filtername["varname"] syntax without preceeding underscore * to access variable content. * * Categories: [e]=escape, [w]=whitelist, [b]=blacklist, [s]=sanitize, [v]=validate * */ /** * @type cast * @sample 22 * * Integer. * */ function _int($data) { return (int)$data; } /** * @type cast * @sample 3.14159 * * Float. * */ function _float($data) { return (float)$data; } /** * @type white, strip * @sample "_foo123" * * Alphanumeric strings. * (e.g. var names, letters and numbers, may contain international letters) * */ function _name($data) { return preg_replace("/\W+/u", "", $data); } /** * @type white, strip * @sample "xvar.1_2.x" * * Identifiers with underscores and dots, * */ function _id($data) { return preg_replace("#(^[^a-z_]+)|[^\w\d_.]+|([^\w_]$)#i", "", $data); } /** * @type white, strip * @sample "tag-name-123" * * Alphanumeric strings with dashes. Commonly used for slugs. * Consecutive dashes and underscores are compacted. * */ function _slug($data) { return preg_replace("/[^\w-]+|^[^a-z]+|[^\w]+$|(?<=[-_])[-_]+/", "", strtolower($s)); } /** * @type white, strip * @sample "Hello - World. Foo + 3, Bar." * * Flow text with whitespace, * with minimal interpunction allowed (optional parameter). * */ function _words($data, $extra="") { return preg_replace("/[^\w\d\s,._\-+$extra]+/u", " ", strip_tags($data)); } /** * [w] * Human-readable text with many special/interpunction characters: * " and ' allowed, but no <, > or \ * */ function _text($data) { return preg_replace("/[^\w\d\s,._\-+?!;:\"\'\/`´()*=#@]+/u", " ", strip_tags($data)); } /** * Acceptable filename characters. * * Alphanumerics and dot (but not as first character). * You should use `->basename` as primary filter anyway. * * @t whitelist * */ function _filename($data) { return preg_replace("/^[.\s]|[^\w._+-]/", "_", $data); } /** * [b] * Filter non-ascii text out. * Does not remove control characters. (Similar to FILTER_FLAG_STRIP_HIGH.) * */ function _ascii($data) { return preg_replace("/[\\200-\\377]+/", "", $data); } /** * [b] * Remove control characters. (Similar to FILTER_FLAG_STRIP_LOW.) * */ function _nocontrol($data) { return preg_replace("/[\\000-\\010\\013\\014\\016-\\037\\177\\377]+/", "", $data); // all except \r \n \t } /** * [e] * Exchange \r \n \t and \f \v \0 for normal spaces. * */ function _spaces($data) { return strtr($data, "\r\n\t\f\v\0", " "); } /** * [x] * Regular expression filter. * * This either extracts (preg_match) data if you have a () capture group, * or functions as filter (pref_replace) if there's a [^ character class. * */ function _regex($data, $rx="", $match=1) { # validating if (strpos($rx, "(")) { if (preg_match($rx, $data, $result)) { return($result[$match]); } } # cropping elseif (strpos($rx, "[^")) { return preg_replace($rx, "", $data); } # replacing else { return preg_replace($rx, $match, $data); } } /** * [w] * Miximum and minimum string length. * */ function _length($data, $max=65535, $cut=NULL) { // just the length limit if (is_null($cut)) { return substr($data, 0, $max); } // require minimum string length, else drop value completely else { return strlen($data) >= $max ? substr($data, 0, $cut) : NULL; } } /** * [w] * Range ensures value is between given minimum and maximum. * (Does not convert to integer itself.) * */ function _range($data, $min, $max) { return ($data > $max) ? $max : (($data < $min) ? $min : $data); } /** * [b] * Fallback value for absent/falsy values. * */ function _default($data, $default) { return empty($data) ? $default : $data; } /** * [w] * Boolean recognizes 1 or 0 and textual values like "false" and "off" or "no". * */ function _boolean($data) { if (empty($data) || $data==="0" || in_array(strtolower($data), array("false", "off", "no", "n", "wrong", "not", "-"))) { return false; } elseif ($data==="1" || in_array(strtolower($data), array("true", "on", "yes", "right", "y", "ok"))) { return true; } else return NULL; } /** * [w] * Ensures field is in array of allowed values. * * Works with arrays, but also string list. If you supply a "list,list,list" then * the comparison is case-insensitive. * */ function _in_array($data, $array) { if (is_array($array) ? in_array($data, $array) : in_array(strtolower($data), explode(",", strtolower($array)))) { return $data; } } ###### filter_var() wrappers ######### /** * [w] * Common case email syntax. * * (Close to RFC2822 but disallows square brackets or double quotes, no verification of TLDs, * doesn't restrict underscores in domain names, ignores i@an and anyone@localhost.) * */ function _email($data, $validate=1) { $data = preg_replace("/[^\w!#$%&'*+/=?_`{|}~@.\[\]\-]/", "", $data); // like filter_var if (!$validate || preg_match("/^(?!\.)[.\w!#$%&'*+/=?^_`{|}~-]+@(?:(?!-)[\w-]{2,}\.)+[\w]{2,6}$/i", trim($data))) { return $data; } } /** * [s] * URI characters. (Actually IRI) * */ function _uri($data) { # we should encode all-non chars return preg_replace("/[^-\w\d\$.+!*'(),{}\|\\~\^\[\]\`<>#%\";\/?:@&=]+/u", "", $data); // same as SANITIZE_URL } /** * [w] * URL syntax * * This is an alias for FILTER_VALIDATE_URL. Beware that it lets a few unwanted schemes * through (file:// and mailto:) and what you'd consider misformed URLs (http://http://whatever). * */ function _url($data) { return filter_var($data, FILTER_VALIDATE_URL); } /** * [v] * More restrictive HTTP/FTP url syntax. * No usernames allowed, no empty port, pathnames/qs/anchor are not checked. * # see also http://internet.ls-la.net/folklore/url-regexpr.html */ function _http($data) { return preg_match("~ (?(DEFINE) (?<byte> 2[0-4]\d |25[0-5] |1\d\d |[1-9]?\d) (?<ip>(?&byte)(\.(?&byte)){3}) (?<hex>[0-9a-fA-F]{1,4}) ) ^ (?<proto>https?|ftps?) :// # (?<user> \w+(:\w+)?@ )? ( (?<host> (?:[a-z\d][a-z\d_\-\$]*\.?)+) |(?<ipv6> \[ (?! [:\w]*::: # ASSERT: no triple ::: colons |(:\w+){8}|(\w+:){8} # not more than 7 : colons |(\w*:){7}\w+\. # not more than 6 : if there's a . | [:\w]*::[:\w]+:: ) # double :: colon must be unique (?= [:\w]*:: # don't count if there is one :: |(\w+:){6}\w+[:.]) # else require six : and one . or : (?: :|(?&hex):)+((?&hex)|(?&ip)|:) \]) # MATCH: combinations of HEX : IP |(?<ipv4> (?&ip) ) ) (?<port> :\d{1,5} )? # the integer isn't optional if port : colon present (unlike FILTER_VALIDATE_URL) (?<path> [/][^?#\s]* )? (?<qury> [?][^#\s]* )? (?<frgm> [#]\S* )? \z~ix", $data, $uu) ? $data : NULL;#(print_r($uu) ? $data : $data) } /** * [w] * IP address * */ function _ip($data) { return filter_var($data, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4|FILTER_FLAG_IPV6) ? $data : NULL; } /** * [w] * IPv4 address * */ function _ipv4($data) { return filter_var($data, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? $data : NULL; } /** * [w] * must be public IP address * */ function _public($data) { return filter_var($data, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE|FILTER_FLAG_NO_RES_RANGE) ? $data : NULL; } ###### format / representation ######### /** * [v] * HTML5 datetime / datetime_local, date, time * */ function _datetime($data) { return preg_match("/^\d{0}\d\d\d\d -(0\d|1[012]) -([012]\d|3[01]) T ([01]\d|2[0-3]) :[0-5]\d :[0-5]\d (Z|[+-]\d\d:\d\d|\.\d\d)$/x", $data) ? $data : NULL; } function _date($data) { return preg_match("/^\d\d\d\d -(0\d|1[012]) -([012]\d|3[01])$/x", $data) ? $data : NULL; } function _time($data) { return preg_match("/^([01]\d|2[0-3]) :[0-5]\d :[0-5]\d (\.\d\d)?$/x", $data) ? $data : NULL; } /** * [v] * HTML5 color * */ function _color($data) { return preg_match("/^#[0-9A-F]{6}$/i", $data) ? strtoupper($data) : NULL; } /** * [w] * Telephone numbers (HTML5 <input type=tel> makes no constraints.) * */ function _tel($data) { $data = preg_replace("#[/.\s]+#", "-", $data); if (preg_match("/^(\+|00)?(-?\d{2,}|\(\d+\)){2,}(-\d{2,}){,3}(\#\d+)?$/", $data)) { return trim($data, "-"); } } /** * [v] * Verify minimum consistency (RFC4627 regex) and decode json data. * */ function _json($data) { if (!preg_match('/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/', preg_replace('/"(\\.|[^"\\])*"/g', '', $data))) { return json_decode($data); } } /** * [v] * XML tree. * */ function _xml($data) { return simplexml_load_string($data); } /** * [w] * Clean html string via HTMLPurifier. * */ function _purify($data) { $h = new HTMLPurifier; return $h->purify( $data ); } /** * [e] * HTML escapes. * * This is actually an output filter. But might be useful to mirror input back into * form fields instantly `<input name=field value="<?= $_GET->html["field"] ?>">` * * @param $data string * @return string */ function _html($data) { return htmlspecialchars($data, ENT_QUOTES, "UTF-8", false); } /** * [b] * Removes residual and certain HTML tags. * WARNING: This is no `strip_tags()` substitute, but purposefully leaves * lonesome < angle brackets > alone. Only strips coherent and well-known * presentational markup. * */ function _strip_markup($data, $with="") { return preg_replace("~(<\s*/?\s*(a|b|i|em|strong|sup|sub|ins|del|br|hr|big|small|font|span|p|div|table|tr|td|ol|ul|li|dl|dd|dt|abbr|tt|code|pre|h[1-6])\b". "(?>[^>\"\']+|\"[^\"]*\"|'[^']*')*>)+~", $with, $data); } /** * [e] * Escape all significant special chars. * */ function _escape($data) { return preg_replace("/[\\\\\[\]\{\}\[\]\'\"\`\´\$\!\&\?\/\>\<\|\*\~\;\^]/", "\\$1", $data); } /** * [e] * Addslashes * */ function _q($data) { return addslashes($data); } /** * [b] * Minimal XSS detection. * Attempts no cleaning, just bails if anything looks suspicious. * * If something is XSS contaminated, it's spam and not worth to process further. * WEAK filters are better than no filters, but you should ultimatively use ->html purifier instead. * */ function _xss($data) { if (preg_match("/[<&>]/", $data)) { // looks remotely like html $html = $data; // remove filler $html = preg_replace("/&#(\d);*/e", "ord('$1')", $html); // escapes $html = preg_replace("/&#x(\w);*/e", "ord(dechex('$1'))", $html); // escapes $html = preg_replace("/[\x00-\x20\"\'\`\´]/", "", $html); // whitespace + control characters, also any quotes $html .= preg_replace("#/\*[^<>]*\*/#", "", $html); // in-JS obfuscation comments // alert patterns if (preg_match("#[<<]/*(\?import|applet|embed|object|script|style|!\[CDATA\[|title|body|link|meta|base|i?frame|frameset|i?layer)#iUu", $html, $uu) or preg_match("#[<>]\w[^>]*(\w{3,}[=:]+(javascript|ecmascript|vbscript|jscript|python|actionscript|livescript):)#iUu", $html, $uu) or preg_match("#[<>]\w[^>]*(on(mouse\w+|key\w+|focus\w*|blur|click|dblclick|reset|select|change|submit|load|unload|error|abort|drag|Abort|Activate|AfterPrint|AfterUpdate|BeforeActivate|BeforeCopy|BeforeCut|BeforeDeactivate|BeforeEditFocus|BeforePaste|BeforePrint|BeforeUnload|Begin|Blur|Bounce|CellChange|Change|Click|ContextMenu|ControlSelect|Copy|Cut|DataAvailable|DataSetChanged|DataSetComplete|DblClick|Deactivate|Drag|DragEnd|DragLeave|DragEnter|DragOver|DragDrop|Drop|End|Error|ErrorUpdate|FilterChange|Finish|Focus|FocusIn|FocusOut|Help|KeyDown|KeyPress|KeyUp|LayoutComplete|Load|LoseCapture|MediaComplete|MediaError|MouseDown|MouseEnter|MouseLeave|MouseMove|MouseOut|MouseOver|MouseUp|MouseWheel|Move|MoveEnd|MoveStart|OutOfSync|Paste|Pause|Progress|PropertyChange|ReadyStateChange|Repeat|Reset|Resize|ResizeEnd|ResizeStart|Resume|Reverse|RowsEnter|RowExit|RowDelete|RowInserted|Scroll|Seek|Select|SelectionChange|SelectStart|Start|Stop|SyncRestored|Submit|TimeError|TrackChange|Unload|URLFlip)[^<\w=>]*[=:]+)[^<>]{3,}#iUu", $html, $uu) or preg_match("#[<>]\w[^>]*(\w{3,}[=:]+(-moz-binding:))#iUu", $html, $uu) or preg_match("#[<>]\w[^>]*(style[=:]+[^<>]*(expression\(|behaviour:|script:))#iUu", $html, $uu)) { $this->_log($data, "DETECTED XSS PATTERN ({$uu['1']}),"); $data = ""; die("input::_xss"); } } return $data; } /** * [w] * Cleans utf-8 from invalid sequences and alternative representations. * (BEWARE: performance drain) * */ function _iconv($data) { return iconv("UTF-8", "UTF-8//IGNORE", $data); } /** * [b] * Few dangerous UTF-7 sequences * (only necessary if output pages don't have a charset specified) * */ function _utf7($data) { return preg_replace("/[+]A(D[w40]|C[IYQU])(-|$)?/", "", $data);; } /** * [e] * Escape for concatenating data into sql query. * (suboptimal, use parameterized queries instead) * */ function _sql($data) { INPUT_QUIET or trigger_error("SQL escaping of input variable '$this->__varname'.", E_USER_NOTICE); return db()->quote($data); // global object } function _mysql($data) { INPUT_QUIET or trigger_error("SQL escaping of input variable '$this->__varname'.", E_USER_NOTICE); return mysql_real_escape_string($data); } ###### pseudo filters ############## /** * [x] * Unfiltered access should obviously be avoided. But it's not always possible, * so this method exists and will just trigger a notice in debug mode. * */ function _raw($data) { INPUT_QUIET or trigger_error("Unfiltered input variable \${$this->__title}['{$this->__varname}'] accessed.", E_USER_NOTICE); return $data; } /** * [x] * Unfiltered access, but logs variable name and value. * */ function _log($data, $reason="manual log") { syslog(LOG_NOTICE, "php7://input:_log@{$_SERVER['SERVER_NAME']} accessing \${$this->__title}['{$this->__varname}'] variable, $reason, content=" . substr($this->_id(json_encode($data)), 0, 48)); return $data; } /** * [b] * Abort with fatal error. (Used as fallback for INPUT_DIRECT access.) * */ function _disallow($data) { throw new OutOfBoundsException("Direct \$_REQUEST[\"$this->__varname\"] is not allowed, add ->filter method, or change INPUT_DIRECT if needed."); } /** * Validate instead of sanitize. * */ function _is($data) { $this->__filter[] = array("is_still", array()); return $data; } function _is_still($data) { return $data === $this->__vars[$this->__varname]; } ###### implementation ################################################ /** * Array data from previous superglobal. * * (It's pointless to make this a priv/proteced attribute, as raw data could be accessed * in any number of ways. Not the least would be to just not use the input filters.) * */ var $__vars = array(); /** * Name of superarray this filter object wraps. * (e.g. "_GET" or "_SERVER") * */ var $__title = ""; /** * Currently accessed array key. * */ var $__varname = ""; /** * Amassed filter list. * Each ->method->chain name will be appended here. Gets automatically reset after * a succesful variable access. * * Each entry is in `array("methodname", array("param1", 2, 3))` format. * */ var $__filter = array(); // filterchain method stack /** * Automatically appended filter list. (Simply combined with current `$__filter`s). * */ var $__always = array(); /** * Currently accessed array keys. * */ var $__rules = array( // pre-defined varname filters // "varname.." => array( array("length",array(256), array("nocontrol",array()) ), ); /** * Initialize object from * * @param array one of $_REQUEST, $_GET or $_POST etc. */ function __construct($_INPUT, $title="") { $this->__vars = $_INPUT; // stripslashes on magic_quotes_gpc might go here, but we have no word if we actually receive a superglobal or if it wasn't already corrected $this->__title = $title; } /** * Sets default filter chain. * These are ALWAYS applied in CONJUNCTION to ->manually->specified->filters. * */ function always() { $this->__always = $this->__filter; $this->__filter = array(); } /** * Normalize array keys (to UPPER case), for e.g. $_SERVER vars. * */ function key_case($case=CASE_UPPER) { $this->__vars = array_change_key_case($this->__vars, $case); } /** * Executes current filter or filter chain on given $varname. * */ function filter($varname, $reset_filter_afterwards=NULL) { // single array [["name"]] becomes varname if (is_array($varname) and count($varname)===1) { $varname = current($varname); } $this->__varname = $varname; // direct/raw access without any ->filtername prior offsetGet[] or plain filter() call if (empty($this->__filter)) { if (isset($this->__rules[$varname])) { $this->__filter = $this->rules[$varname]; // use a predefined filterset } else { $this->__filter = array( INPUT_DIRECT ); // direct access handler } } $first_handler = reset($this->__filter); // retrieve value for selected input variable $data = NULL; if (!is_scalar($varname) or $first_handler == "list" or $first_handler == "multi") { // one of the multiplex handlers supposedly picks it up } elseif (isset($this->__vars[$varname])) { // entry exists $data = $this->__vars[$varname]; } elseif (!INPUT_QUIET) { trigger_error("Undefined input variable \${$this->__title}['{$this->__varname}']", E_USER_NOTICE); // run through filters anyway (for ->log) } // implicit ->array filter handling if (is_array($data) and $first_handler != "array") { array_unshift($this->__filter, "array"); } // apply filters (we're building an ad-hoc merged array here, because ->apply works on the reference, and some filters expect ->__filter to contain the complete current list) $this->__filter = array_merge($this->__filter, $this->__always); $data = $this->apply($data, $this->__filter); // the Traversable array interface resets the filter list after each request, see ->current() if ($reset_filter_afterwards) { $this->__filter = $reset_filter_afterwards; } // done return $data; } /** * Runs list of filters on data. Uses either methods, bound closures, or global functions * if the filter name matches. * * It works on an array reference and with array_shift() because filters are allowed to * modify the list at runtime (->array requires to). * */ function apply($data, &$filterchain) { while ($f = array_shift($filterchain)) { list($filtername, $args) = is_array($f) ? $f : array($f, array()); // an override function name or closure exists if (isset($this->{"_$filtername"})) { $filtername = $this->{"_$filtername"}; } // call _filter method if (is_string($filtername) && method_exists($this, "_$filtername")) { $data = call_user_func_array(array($this, "_$filtername"), array_merge(array($data), (array)$args)); } // ordinary php function, or closure, or rebound method elseif (is_callable($filtername)) { $data = call_user_func_array($filtername, array_merge(array($data), $args)); } else { INPUT_QUIET>=2 or trigger_error("unknown filter '" . (is_scalar($filtername) ? $filtername : "closure") . "', falling back on wiping non-alpha characters from '{$this->__varname}'", E_USER_WARNING); $data = $this->_name($data); } } return $data; } /** * @multiplex * * List data / array value handling. * * This virtual filter hijacks the original filter chain, and applies it * to sub values. * */ function _array($data) { // save + swap out the current filter chain list($multiplex, $this->__filter) = array($this->__filter, array()); // iteratively apply original filter chain on each array entry $data = (array) $data; foreach (array_keys($data) as $i) { $chain = $multiplex; $data[$i] = $this->apply($data[$i], $chain); } return $data; } /** * @multiplex * * Grab a collection of input variables, names delimited by comma. * Implicitly makes it an ->_array() list. * The _array handler is implicitly used for indexed values. _list * and _multi can be used for associative arrays, given a key list. * * @example extract($_GET->list->text["name,title,date,email,comment"]); * @php5.4+ $_GET->list[['key1','key2','key3']]; * * @bugs hacky, improper way to intercept, fetches from $__vars[] directly, * uses $__varname instead of $data, may prevent replays, * main will trigger a notice anyway as VAR1,VAR2,.. is undefined * */ function _list($keys, $pass_array=FALSE) { // get key list if (is_array($this->__varname)) { $keys = $this->__varname; } else { $keys = explode(",", $this->__varname); } // slice out named values from ->__vars $data = array_merge( array_fill_keys($keys, NULL), array_intersect_key($this->__vars, array_flip($keys)) ); // chain to _array multiplex handler if (!$pass_array) { return $this->_array($data); } // process fetched list as array (for user-land functions like http_build_query) else { return $data; } } /** * Processes collection of input variables. * Passes list on as array to subsequent filters. * */ function _multi($keys) { return $this->_list($keys, "process_as_array"); } /** * @hide * * Ordinary method calls are captured here. Any ->method("name") will trigger * the filter and return variable data, just like ->method["name"] would. It * just allows to supply additional method parameters. * */ function __call($filtername, $args) { // can have arguments $this->__filter[] = array($filtername, array_slice($args, 1)); return $this->filter($args[0]); } /** * @hide * * Wrapper to capture ->name->chain accesses. * */ function __get($filtername) { // // we could do some heuristic chaining here, // if the last entry in the ->attrib->attrib list is not a valid method name, // but a valid varname, we should execute the filter chain rather than add. // // Unpack parameterized filter attributes, use U+2022 ellipsis … as delimiter if (strpos($filtername, "…")) { $args = explode("…", $filtername); $filtername = array_shift($args); } else { $args=array(); } // Add filter to list $this->__filter[] = count($args) ? array($filtername, $args) : $filtername; // fluent interface return $this; } /** * @hide ArrayAccess * * Normal $_ARRAY["name"] syntax access is redirected through the filter here. * */ function offsetGet($varname) { // never chains return $this->filter($varname); } /** * @hide ArrayAccess * * Needed for commonplace isset($_POST["var"]) checks. * */ function offsetExists($name) { return isset($this->__vars[$name]); } /** * @hide * Sets value in array. Note that it only works for array["xy"]= top-level * assignments. Subarrays are always retrieved by value (due to filtering) * and cannot be set item-wise. * * @discouraged * Manipulating the input array is indicative for state tampering and thus * throws a notice per default. * */ function offsetSet($name, $value) { INPUT_QUIET or trigger_error("Manipulation of input variable \${$this->__title}['{$this->__varname}']", E_USER_NOTICE); $this->__vars[$name] = $value; } /** * @hide * Removes entry. * * @discouraged * Triggers a notice per default. * */ function offsetUnset($name) { INPUT_QUIET or trigger_error("Removed input variable \${$this->__title}['{$this->__varname}']", E_USER_NOTICE); unset($this->__vars[$name]); } /** * Generic array features. * Due to being reserved words `isset` and `empty` need custom method names here: * * ->has(isset) * ->no(empty) * ->keys() * * Has No Keys seems easy to remember. * */ /** * isset/array_key_exists check; * may list multiple keys to check. * */ function has($args) { $args = (func_num_args() > 1) ? func_get_args() : explode(",", $args); return count(array_intersect_key($this->__vars, array_flip($args))) == count($args); } /** * empty() probing, * Tests if named keys are absent or falsy. * */ function no($args) { $args = (func_num_args() > 1) ? func_get_args() : explode(",", $args); return count(array_filter(array_intersect_key($this->__vars, array_flip($args)))) == 0; } /** * Returns list of all contained keys. * */ function keys() { return array_keys($this->__vars); } /** * @hide Traversable * * Allows to loop over all array entries in a foreach loop. A supplied filterlist * is reapplied for each iteration. * * - Basically all calls are mirrored onto ->__vars` internal array pointer. * */ function current() { return $this->filter(key($this->__vars), /*reset_filter_afterwards=to what it was before*/$this->__filter); } function key() { return key($this->__vars); } function next() { return next($this->__vars); } function rewind() { return reset($this->__vars); } function valid() { if (key($this->__vars) !== NULL) { return TRUE; } else { // also reset the filter list after we're done traversing all entries $this->__filters=array(); return FALSE; } } /** * @hide Countable * */ function count() { return count($this->__vars); } /** * @hide Make filtering functions available as static methods * without underscore prefix for external invocation. */ static function __callStatic($func, $args) { static $filter = "input"; // Also eschew E_STRICT notices if (!is_object($filter)) { $filter = new input(array(), "\$_INLINE_INPUT"); } return isset($filter->{"_$func"}) ? call_user_func_array($filter->{"_$func"}, $args) : call_user_func_array(array($filter, "_$func"), $args); } /** * @hide Allows testing variable presence with e.g. if ( $_POST() ) * Alternatively $_POST("var") is an alias to $_POST["var"]. * */ function __invoke($varname=NULL) { // treat it as variable access if (!is_null($varname)) { return $this->offsetGet($varname); } // do the count() call else { return $this->count(); } } } /** * @autorun * */ $_SERVER = new input($_SERVER, "_SERVER"); $_REQUEST = new input($_REQUEST, "_REQUEST"); $_GET = new input($_GET, "_GET"); $_POST = new input($_POST, "_POST"); $_COOKIE = new input($_COOKIE, "_COOKIE"); #$_SESSION #$_ENV #$_FILES required a special handler ?> |
Added lib/openid.phar.
cannot compute difference between binary files
Deleted logo.png.
cannot compute difference between binary files
Deleted logo.svgz.
cannot compute difference between binary files
Deleted openid.php.
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
|
Added page_admin.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: freshcode * type: page * title: admin interface * description: Showcase user flags and allow to delete or hide project entries/revisions. * version: 0.1 * depends: db * * User flags are collected in a separate `flags` table. Yet each project * entry contains a `flags` counter column as well (this is used by front-end * code to automatically hide too frequently flagged submissions).. * * CREATE TABLE flags * (name TEXT, reason TEXT, note TEXT, submitter_openid TEXT, submitter_ip TEXT); * * The admin page lists from the flags table. Then allows to "delete" certain * revisions, or just mark them as hidden. * */ // Moderator authorization already handled by dispatcher (index.php) include("template/header.php"); ?> <section id=main> <?php $name = $_REQUEST->proj_name["name"]; // Just list flags+projects if (empty($name)) { print "<h3>Flagged entries</h3> <dl>"; // query flags table, but associate data from last release $flags = db("SELECT * FROM flags LEFT JOIN release_versions ON flags.name=release_versions.name"); // just output admin/PROJID links while ($row = $flags->fetch()) { $row = array_map("input::html", $row); print <<<HTML <dt><a href="admin/$row[name]">$row[name]</a> (<em>$row[flag]</em> flags on #$row[t_published])</dt> <dd><b>$row[reason]</b> $row[note] </dd> HTML; } } // Show entry + respond to actions else { /** * Apply actions * * → Actions in `action[field][]=name and action[value][]=value` * → Revisions are lists of `select[t_published][] = t_changed` * */ if ($_POST->has("action")) { // Merge action keys and values $action = $_POST->raw["action"]; $action = array_combine( array_intersect_key($action["field"], $action["value"]), array_intersect_key($action["value"], $action["field"]) ); var_dump($action); // Run trough actions foreach ($action as $field=>$value) if (strlen($field)) { // Update DB for each revision in select[][] foreach ($_POST->raw["select"] as $t_published => $t_changed) { db("UPDATE release SET :? = ? WHERE name=? AND t_published=? AND t_changed IN (??)", array($field), $value, $name, $t_published, $t_changed ); } } // Manually empty `flags` table if ($action["flag"] === 0) { db("DELETE FROM flags WHERE name=?", $name); } } /** * Get all revisions and flags for project name * * */ $entries = db("SELECT * FROM release WHERE name=? ORDER BY t_published ASC, t_changed ASC", $name)->fetchAll(); $flags = db("SELECT * FROM flags WHERE name=?", $name)->fetchAll(); // Start <form> print "<h3>Edit '$name' revisions</h3> Oldest to newest. <form action='admin/$name' method=POST> "; // Show all flagging notes foreach ($flags as $row) { $row = array_map("input::html", $row); print "<li>Flag: <b>$row[reason]</b><br>Note: <em>$row[note]</em><br>By: <u>$row[submitter_openid]</u></li><br>"; } // Print each revision; foreach ($entries as $rev=>$row) { // current, last, and next row $row = array_map("input::html", $row); $last = isset($entries[$rev-1]) ? array_map("input::html", $entries[$rev-1]) : $row; $next = isset($entries[$rev+1]) ? array_map("input::html", $entries[$rev+1]) : $row; // Version header $date = strftime("%Y-%m-%d %T", $row["t_changed"]); print " <br> <table class='rc admin'> <tr> <th> <input type=checkbox name='select[$row[t_published]][]' value='$row[t_changed]'> rev=$rev </th> <th> pub=$row[t_published] chg=$row[t_changed] <small>($date)</small> </th> </tr>"; // Fields foreach ($row as $f=>$v) { $v = pdiff::tridiff($last[$f], $v, $next[$f]); if (in_array($f, ["t_published", "t_changed"])) { $v .= " <small>(" . strftime("%Y-%m-%d %T", $row[$f]) . ")</small>"; } if (in_array($f, ["hidden","flag","deleted","name","t_changed","version"])) { $f = "<em>$f</em>"; } print "<tr><td>$f</td><td>$v</td>"; } print "</table>"; } /** * Print `action` form fields * * → Assocciatively as `action[field][] = dbfield` * and `action[value][] = vakue` * → Applying depends an the field being non-empty * (for unchecked checkboxes they are). * */ print <<<HTML <h4>Actions</h4> <table class=rc> <tr> <td><label> <input type=checkbox name="action[field][0]" value="hidden"> <input type=hidden name="action[value][0]" value="1"> <b>Set hidden</b> </label></td> <td>so the revision will no longer show up on the frontpage. </td> </tr> <tr> <td><label> <input type=checkbox name="action[field][1]" value="deleted"> <input type=hidden name="action[value][1]" value="1"> <b>Mark deleted</b> </label></td> <td> to terminate a project revision line </td> </tr> <tr> <td><label> <input type=checkbox name="action[field][2]" value="flag" checked> <input type=hidden name="action[value][2]" value="0"> <b>Unset flags</b> </label></td> <td> to clear flagging history when finished. </td> </tr> <tr><th>Update field</th><th>with value</th></tr> <tr> <td> <input type=text name="action[field][3]" value=""> </td> <td> <input type=text name="action[value][3]" value="" size=40> </td> </tr> <tr> <td> <input type=text name="action[field][4]" value=""> </td> <td> <input type=text name="action[value][4]" value="" size=40> </td> </tr> </table> <br> <input type=submit value=Apply> <br> <br> HTML; } ?> |
Added page_drchangelog.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * type: page * title: Dr. Changelog * description: Tool to experiment and try out Autoupdate modules * version: 0.1 * license: AfferoLGPL * * Reuses fields from /submit form to start a live check run with * actual Autoupdate modules. * */ $header_add = "<meta name=robots content=noindex>"; include("template/header.php"); ?> <aside id=sidebar> <section> <h5>Know your audience</h5> <small> <p> Whatever source you choose for release announcements, try to keep them <b>user-friendly</b>. </p> <p> End users aren't fond of commit logs. While "merged pull request XY" might be technically highly relevant (for e.g. libraries), it's gibberish to most everyone else.</p> <p> So be careful with the <em>GitHub</em> module in particular. If you're not using githubs /release tool, a commit log may be used still. Only basic filtering is applied.</p> <p> Likewise write <em>Changelogs</em> as <b>summaries</b>. (They're better and more correctly called NEWS or RELEASE-NOTES files actually.)</p> </small> </section> </aside> <section id=main> <?php #-- Output formatted results class TestProject extends ArrayObject { function update($result) { #-- output formatted print "<dl>\n"; foreach ($result as $key=>$value) { print "<dt><b>$key</b></dt>\n<dd>" . input::html($value) . "</dd>\n"; } print "</dl>"; } } // run test if ($_REQUEST->has("test")) { #-- prepare $run = new Autoupdate(); $run->debug = 1; $project = new TestProject(array( "name" => "testproject", "version" => "0.0.0.0.0.0.1", "t_published" => 0, "homepage" => "", "download" => "", "urls" => "", "autoupdate_module" => $_REQUEST->id->in_array("autoupdate_module", "none,release.json,changelog,regex,github"), "autoupdate_url" => $_REQUEST->url["autoupdate_url"], "autoupdate_regex" => $_REQUEST->raw["autoupdate_regex"], )); #-- exec $method = $run->map[$project["autoupdate_module"]]; print "<h3>Results for <em>$method</em> extraction</h3>\n"; $result = $run->$method($project); $result = new TestProject((array)$result); $result->update($result); } // display form else { $data = $_REQUEST->list->html["name,autoupdate_module,autoupdate_url,autoupdate_regex"]; $data["autoupdate_regex"] or $data["autoupdate_regex"] = "\n\nversion = /Version ([\d.]+)/\n\nchanges = http://example.org/news.html\nchanges = $('article pre#release')\nchanges = ~ ((add|fix|change) \V+) ~mix*"; $current_date = strftime("%Y-%m-%d", time()); $select = "form_select_options"; print<<<FORM <style> /** * page-specific layout * */ .autoupdate-alternatives { border-spacing: 5pt; } .autoupdate-alternatives td { padding: 3pt; width: 25%; vertical-align: top; background: #fcfcfc linear-gradient(to bottom, #f7f0e9, #fff); box-shadow: 2px 2px 3px 1px #f9f5f1; border-radius: 10pt; font-size: 95%; } .autoupdate-alternatives td .hidden { position: absolute; display: none; } .autoupdate-alternatives td:hover .hidden { display: block; } .autoupdate-alternatives td .hidden pre { position: relative; top: -30pt; left: -30pt; padding: 7pt; border: 7px solid #111; border-radius: 7pt; background: #f7f7f7; background-image: radial-gradient(circle at 50% 50%, rgb(255,255,255), rgb(244,244,244)); } li { padding: 1.5pt; } </style> <h3>Dr. Changelog</h3> <form action=drchangelog method=POST> <img src=img/drchangelog.png align=right alt="birdy big eyes" title="Don't ask me, I'm just a pictogram."> <p> Freshcode.club can automatically track your software releases. There are <a href="http://fossil.include-once.org/freshcode/wiki/Autoupdate">various alternatives for</a> uncovering them. Try them out. <label> Retrieval method <select name=autoupdate_module> {$select("release.json,changelog,regex,github", $data["autoupdate_module"])} </select> </label> <table class=autoupdate-alternatives><tr> <td> <a href="http://fossil.include-once.org/freshcode/wiki/releases.json"><em>releases.json</em></a> defines a concrete scheme for publishing version and release notes. <span class=hidden><pre> { "version": "1.0.0", "changes": "Fixes and adds lots of new functions ..", "state": "stable", "scope": "major feature", "download": "http://exmpl.org/" } </pre></span> </td> <td>While a <a href="http://fossil.include-once.org/freshcode/wiki/AutoupdateChangelog"><em>Changelog</em></a> text file is likely the easiest, given a coherent format and style. <span class=hidden><pre> 1.0.0 ($current_date) ------------------ * Changes foo and bar. + Adds baz baz. - Some more bugs removed. 0.9.9 (2014-02-27) ------------------ * Now uses Qt5 - Removed all the bugs 0.9.1 (2014-01-20) ------------------ * Initial release with </pre></span> </td> <td><a href="http://fossil.include-once.org/freshcode/wiki/AutoupdateGithub"><em>Github</em></a> extraction prefers <nobr>/releases</nobr> notes. But may as last resort condense a git commit log. <span class=hidden><pre><a href="https://github.com/blog/1547-release-your-software"><img src="https://camo.githubusercontent.com/9f23f54df9e2f69047fb0f9f80b2e33c8339606f/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f32312f3733373136362f62643163623637652d653332392d313165322d393064312d3361656365653930373339662e6a7067" width=400 height=200></a></pre></span> </td> <td>Using <a href="http://fossil.include-once.org/freshcode/wiki/AutoupdateRegex"><em>regex/xpath</em></a> is however the most universal way to extract from project websites. <span class=hidden><pre> <span style=color:gray># load page</span> changes = http://exmpl/news <span style=color:gray># jQuery</span> changes = $("body .release") <span style=color:gray># RegExp</span> version = /Version \d+\.\d+/ </pre></span> </td> </tr></table> </p> <p> <label> Autoupdate URL <input name=autoupdate_url type=url size=80 value="$data[autoupdate_url]" placeholder="https://github.com/user/repo/tags.atom" maxlength=250> </label> Add the URL to your Changelog, releases.json, or GitHub project here. For the regex method this will also be the first page to be extracted from. </p> <p> <h4>Content Scraping</h4> Picking out from your own project website can be surprisingly simple. Define a list for at least <code>version = ...</code> and <code>changes = ...</code> - Add source URLs and apply <a href="http://fossil.include-once.org/freshcode/wiki/AutoupdateRegex"> RegExp, XPath, or jQuery</a> selectors for extraction. <label> Extraction Rules <em>(URLs, Regex, Xpath, jQuery)</em> <textarea cols=67 rows=10 name=autoupdate_regex placeholder="version = /-(\d+\.\d+\.\d+)\.txz/" maxlength=2500>$data[autoupdate_regex]</textarea> <small> <li>Assigning new URLs is only necessary when there's different data to extract from.</li> <li>RegExps like <code>version = /Changes for ([\d.]+)/</code> often match headlines well.</li> <li>A common XPath rule for extracting the first bullet point list is <code>changes = (//ul)[1]/li</code>.</li> <li>While <code>changes = $("section#main article .release")</code> narrows it down for HTML pages.</li> <li>You often can mix extractors, first an XPath/jQuery expression, then a RegExp.</li> <li>Rules for state=, scope= and download= are optional.</li> </small> </label> </p> <p> <input type=submit name=test value=Test-Run> </p> </form> FORM; } include("template/bottom.php"); ?> |
Added page_error.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * type: page * title: Error info * description: Generic error page * version: 0.1 * license: - * * Frontpage. * Just shows the most recent projects and their released versions. * * Shows: * → Recent projects and their released versions. * → Visually trimmed descriptions and changelogs. * → Small boxed tags. * Sidebar: * → Newsfeeds (e.g. linux.com, /r/linux) * HTML: * → RSS/Atom links for update feed comprised of all projects. * */ include("template/header.php"); ?> <section id=main> <?php print "<h2>Error</h2>\n"; print isset($error) ? "<p>$error</p>" : "<p>Some problem occured (entry not accessible etc.)</p>"; include("template/bottom.php"); ?> |
Changes to page_feed.php.
1 2 3 4 5 | 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 | - + - + + + + + + + + + + + + + + + - + - - - - - + + + + + + + + | <?php /** * api: freshcode * title: json feeds * description: exchange protocol and per-project feeds |
43 44 45 46 47 48 49 | 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 | - + - + - + - + + + + - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + + + + + + + + + - + + + + + + + + + - + | function feed_release($row) { return array( "version" => $row["version"], "state" => $row["state"], "scope" => $row["scope"], "changes" => $row["changes"], "download" => versioned_url($row["download"], $row["version"]), |
Changes to page_flag.php.
1 2 3 4 5 6 7 8 9 10 11 12 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | - + - + | <?php /** * type: page * title: Flagging * description: Allows users to flag project listings for moderator attention. * * A submission here will both increase the `release`.`flag` counter, * as well as leave a documentation entry in the `flags` table. * */ |
36 37 38 39 40 41 42 | 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | - + | (reason, note, name, submitter_openid, submitter_ip) VALUES (?,?,?,?,?)", $reason, $note, $name, $_SESSION["openid"], $_SERVER->ip["REMOTE_ADDR"] ); // Increase `release`.`flag` column (just the last entry) db("UPDATE release SET flag=flag+1 |
78 79 80 81 82 83 84 | 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 | - + - + | </label> <label> <input type=radio name=reason value=urls> URLs are no longer working. </label> <label> |
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> |
Changes to page_index.php.
1 2 3 4 5 | 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 | - + + + + + + + + + + + + - - + + + + - - - + + + - - - + + - - - + - - - - - - - - - - - - - - - - - - - - - + + + - - + - + | <?php /** * type: page * title: Project release listing * description: Front page for listing recently submitted projects and their releases |
Changes to page_links.php.
1 2 3 4 | 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 | - + - - - - - + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + - - + + + + + - + + - - + + + + + + + + + + + + + - + + + + + + + + - + - + - - + + + + + - - + + - - - - - + + - - - - + - - - + + + + - + - - + - - + + - - - + + - + - + - - + + + + + + + + - - + + - - - + + - - - + + - - + + - + - - + + + + + - + - - + + + + + - + - - - + + + - - + + + - + | <?php /** * title: Links to other directories * description: Collection/overview of other software tracking / link lists. |
Changes to page_login.php.
12 13 14 15 16 17 18 | 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 | - + - + - + | * */ // initiate verification if ($_POST->has("login_url")) { |
69 70 71 72 73 74 75 | 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | - + - + | // a previous login was already successful else { print "<h3>Already logged in</h3>"; print isset($login_hint) ? "<p>$login_hint</p>" |
Added page_names.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * type: page * title: Browse Projects by Name * description: Alphabetical project lists * version: 0.1 * * Needs some styling, too early for splitting up letter ranges. * */ include("template/header.php"); ?><section id=main><article class=project-name-columns><?php // Letter slicing (AZ or 09) $letters = $_GET->name->length…2->strtolower->default("name", "ae"); $letters = range($letters[0], $letters[1]); // Fetch project names from letter group $names = db(" SELECT DISTINCT name FROM release WHERE substr(name, 1, 1) IN (??) ORDER BY name ", $letters); // Show foreach ($names as $id) { print "<a href=/projects/$id[name]><img src='img/screenshot/$id[name].jpeg' width=100 height=75 align=top> $id[name]</a> <br> "; } ?> </article> <aside class=pagination-links> <a href="names/AE">A-E</a> <a href="names/FH">F-H</a> <a href="names/IN">I-N</a> <a href="names/OT">O-T</a> <a href="names/UZ">U-Z</a> <a href="names/09">0-9</a> </aside> <?php include("template/bottom.php"); ?> |
Changes to page_projects.php.
1 2 3 4 5 6 | 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 | - + - + - - - + + - - + - - - + - - - - + - - - + - - - + - - - - + + - - + - - - - - - - + + + - - + + + + - - + + + - - - - - - - - - - - + + - - + - - - - - - - - - - - - - - - - + + - - + + + + + - - - - - - - - - + - - + + - + - - - + - - + - - - - - + + - - - + + + - - - + + + - + - - + - - - - + | <?php /** * type: page * title: Project detail view * description: List project entry with all URLs and releases * license: AGPL |
Added page_rc.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * type: page * title: Recent Changes * description: Provides a revision diff * version: 0.1 * * Show differences between incremental revisions. * (To detect sneak spam while we're not requiring OpenID logons.) * */ // page header include("template/header.php"); ?><section id=main><?php /** * Fields to inspect/diff. * Using different sets depending on publicness. * */ if (TRUE) { // Public $fields = "name,t_changed,title,version,t_published,license,tags,state,scope,homepage,download,urls,description,changes,submitter"; } if ($_SESSION["openid"]) { // For logged in users $fields .= ",autoupdate_module,autoupdate_url,autoupdate_regex"; } if (in_array($_SESSION["openid"], $moderator_ids)) { // Reveal control/privacy-related fields only to moderators $fields .= ",submitter_image,submitter_openid,lock,hidden,deleted"; } /** * Prepare SQL field aliases * (because sqlite-pdo driver doesn't support it) * * description → crnt.description AS crnt_description * description → prev.description AS prev_description * */ $crnt_fields_alias = preg_replace("/\w+/", "crnt.$0 AS crnt_$0", $fields); $prev_fields_alias = preg_replace("/\w+/", "prev.$0 AS prev_$0", $fields); $prev_fields_empty = preg_replace("/\w+/", " NULL AS prev_$0", $fields); // Also turn CSV list into array $fields = array_diff(str_getcsv($fields), array("t_changed")); /** * Retrieve two consecutive revisions each. * * */ $rc = db(" SELECT $crnt_fields_alias, $prev_fields_alias, MAX(prev.t_changed) FROM release crnt LEFT JOIN release prev ON crnt.name = prev.name WHERE prev.t_changed < crnt.t_changed -- ( SELECT MAX(t_changed) -- FROM release -- WHERE name = crnt.name -- AND t_changed < crnt.t_changed ) GROUP BY crnt.name, crnt.t_changed ORDER BY crnt.t_changed DESC "); /** * Iterate over all results to display differences * */ foreach ($rc as $entry) { #-- Prepare fields $name = $entry["crnt_name"]; $date = strftime("%Y-%m-%d %H:%M:%S", $entry["crnt_t_changed"]); $time_diff = $entry["crnt_t_changed"] - $entry["prev_t_changed"]; #-- Table print "\n\n<table class=rc><tr><th><a href=/projects/$name>$name</a></th><th>$date <small>¤$time_diff</small> <span class=funcs><a href=/submit/$name>edit</a> <a href=/admin/$name>admin</a></span></th></tr>\n"; foreach ($fields as $fn ) { // Diff only if there are differences, obviously if ($entry["prev_$fn"] !== $entry["crnt_$fn"]) { $diff = pdiff::htmlDiff($entry["prev_$fn"], $entry["crnt_$fn"]); print "<tr><td>$fn</td><td class=trimmed>$diff</td></tr>\n"; } } print "</table>\n"; } // page footer include("template/bottom.php"); ?> |
Added page_search.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * type: page * title: Search function * description: Scans packages for description, tags, license, user names * license: AGPL * version 0.2 * * Builds a search query from multiple input params: * → ?user= * → ?tags[]= or ?tag= * → ?trove[]= for ANDed tags * → ?license= * → ?q= for actual text search * */ include("template/header.php"); ?> <section id=main> <?php // Display form if ($_GET->no("tag,tags,trove,user,license,q")) { include("template/search_form.php"); } // Actual search request else { // Wrap search params into arrays $tags = array_filter(array_merge($_GET->array->words["tags"], $_GET->words->p_csv["tag"])); $trove = $_GET->array->words["trove"] and $trove = [$trove, count($trove)]; $user = $_GET->words["user"] and $user = ["$user%"]; $license = $_GET->array->words["license"] and $license = array_filter($license); $search = $_GET->text["q"] and $search = ["%$search%"]; // Run SQL # db()->test = 1; $result = db(" SELECT release.name AS name, title, SUBSTR(description,1,500) AS description, version, image, homepage, download, submitter, submitter_image, release.tags AS tags, license, state, t_published, flag, hidden, deleted, MAX(t_changed) FROM release WHERE NOT deleted AND flag < 5 GROUP BY release.name HAVING 1=1 :* :* :* :* :* ORDER BY t_published DESC, t_changed DESC LIMIT 100 ", // expr :* placeholders only interpolate when inner array contains params [" AND description LIKE ? ", $search], [" AND submitter LIKE ? ", $user], [" AND license IN (??) ", $license], [" AND name IN (SELECT name FROM tags WHERE tag IN (??)) ", $tags], [" AND name IN (SELECT name FROM tags WHERE tag IN (??) GROUP BY name HAVING COUNT(tag) = 1*?) ", $trove] ); // Show sidebar + long project description foreach ($result as $entry) { prepare_output($entry); include("template/search_entry.php"); } } include("template/bottom.php"); ?> |
Changes to page_submit.php.
1 2 3 4 5 6 | 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 | - + - - - + + + - - + + - - - - - - + - - - - - - - - - - - - - - - + + + + - - - - - + - - - - + + + + + + + + + + - - - + - - - - + - - - - - - - + + - - - - + + + - - - - - + - - - - + - - + + + + - + - - + - + + + + - - + - - + - - - + - - + - - - - - - - - - - - + - - - - - - - - - - - - + - - - + + - - + - - - - - - - - - + + + - + - - - - + - + - - - + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - | <?php /** * api: freshcode * type: page * title: Submit/edit project or release * description: Single-page edit form for projects and their releases |
Changes to page_tags.php.
1 2 3 4 5 | 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 | - + - + - - - - + + + - - - - - + + + + - - + - - - - - - - - - - - - + + - - + - - - - + + + + + - - - - - + + + + + - - + + - - - - - - + + + + + + + - - + - - - - - - + + - - - + + - - - + + + + - - - + + + - - + | <?php /** * type: page * title: Tags * description: Tag cloud |
Added release.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: freshcode * title: release/project data wrapper * description: Database scheme / versioned model abstraction for project releases * version: 0.3 * depends: db * license: MITL * * With `release` the database model, its versioning and value constraining are * consolidated somewhat. It's used by submission / autoupdate / and API interfaces. * It's best not to consider this a WebPMVC "model", but table data gateway. * * It can either be instantiated by project $name, fetching the last entry. * Or be populated from a database result array; using db()->into("release") * * * * */ /** * Encases project/release data, keeps fields accessible per array["name"] syntax; * can clean up column formats before ->store()ing it back. * * Adds a couple of static calls to return specific entries object-wrapped, or lists * thereof as plain arrays (because commonly just used for template output). * */ class release extends ArrayObject { /** * Can be instanatiated by project name (latest version will be fetched), * or from a DB result array. * * @return release{} * */ function __construct($namedata, $uu=NULL) { // fetch from DB if (is_string($namedata)) { $namedata = release::latest($namedata); } // unwrap previous AO or release obj if ($namedata instanceof ArrayObject) { $namedata = $namedata->getArrayCopy(); } // populate ArrayObject if (is_array($namedata)) { unset($namedata["_order"]); $this->exchangeArray($namedata); } } /** * Prepare new release submission. * * Merges in flags (hidden, deleted, submitter_*, etc) from latest entry; * but retains t_published associated to `version` if it existed before. * * Filters $newdata to match expected database constraints. For page_submit, * $newdata just equals $_POST, and is already an input{} array object. * * $prefill and $override are used by submission / autoupdate / api callers * to define flags. * */ function update($newdata, $prefill_flags=array(), $override_flags=array(), $partial=FALSE) { // Format constraints via input filter $newdata instanceof input or $newdata = new input($newdata, "\$newdata"); $newdata->_has_urls = array($this, "has_urls"); $newkeys = $newdata->keys(); $newdata->nocontrol->trim->always(); $newdata = array( "name" => $newdata->proj_name ->length…3…33["name"], "homepage" => $newdata->ascii->trim->http ->length…250["homepage"], "download" => $newdata->ascii->trim->url ->length…250["download"], "image" => $newdata->ascii->trim->http ->length…250["image"], "autoupdate_url" => $newdata->ascii->trim->http ->length…250["autoupdate_url"], "title" => $newdata->text ->length…100["title"], "description" => $newdata ->length…2000["description"], "license" => $newdata->words ->length…30["license"], "tags" => $newdata->words->f_tags ->length…150["tags"], "version" => $newdata->words ->length…30["version"], "state" => $newdata->words->strtolower ->length…30["state"], "scope" => $newdata->words->strtolower ->length…30["scope"], "changes" => $newdata->text ->length…2000["changes"], "submitter" => $newdata->text ->length…100["submitter"], "urls" => $newdata->has_urls ->length…2000["urls"], "lock" => $newdata->raw ->length…2000["lock"], "autoupdate_module" => $newdata->id ->length…30["autoupdate_module"], "autoupdate_regex" => $newdata->raw ->length…2000["autoupdate_regex"], ); // Base data for version/t_published lookup, in case we only got partial $newdata $name = $newdata["name"] ?: $this["name"]; $version = in_array("version", $newkeys) ? $newdata["version"] : $this["version"]; // Declare some automatic system flags $auto_flags = array( // Hidden releases are either tagged that way, or have too short of a `changes:` summary "hidden" => intval(is_int(stripos($newdata["scope"], "hidden")) or !empty($prefill_flags["hidden"])), // Increase associated publishing timestamp if hereunto unknown release "t_published" => $this->exists($name, $version) ?: time(), // Whereas the update timestamp is always adapted "t_changed" => time(), ); // Array excerpt if input didn't come from page_submit but Autoupdate or API if ($partial) { $newdata = array_intersect_key($newdata, array_flip($newkeys)); } // Apply some logic filters $this->unpack($newdata); // Merge and apply input $this->exchangeArray(array_merge( $this->getArrayCopy(), // any previous/extraneous control data is kept $prefill_flags, $newdata, $auto_flags, $override_flags )); // chainable call return $this; } /** * Store current data bag into `release` table. * Is to be invoked after ->update(). * */ function store($INSERT="INSERT") { $data = $this->getArrayCopy(); return db("$INSERT INTO release (:?) VALUES (::)", $data, $data) and $this->update_rules(); } /** * Further version management. * */ function update_rules($data) { return // Hide previous empty "" version project entries, if less than two minutes old. db("UPDATE release SET hidden=1 WHERE name=? AND version=? AND t_published < ?", $data["name"], "", time() - 120 ); } /** * Split up fields, * in particular the email out of `submitter`. * */ function unpack(&$newdata) { if (!empty($newdata["submitter"]) and is_int(strpos($newdata["submitter"], "@")) and preg_match($rx = "/[^,;\s]+@[^,;\s]+/", $newdata["submitter"], $match)) { $newdata["submitter_image"] = $match[0]; $newdata["submitter"] = trim(preg_replace($rx, "", $newdata["submitter"]), ", "); } } /** * Retrieve latest published release version. * * @return array */ static function latest($name) { $r = db(" SELECT * FROM release WHERE name = ? ORDER BY t_published DESC, t_changed DESC LIMIT 1", $name ); return $r ? $r->fetch() : array(); } /** * Check for existence of specific release version, * return t_published timestamp if. * * @return int */ static function exists($name, $version) { $r = db(" SELECT t_published FROM release WHERE name=? AND version=?", $name, $version ); $t = $r ? $r->fetchColumn(0) : 0; # print "<b>exists=$t</b>\n"; return intval($t); } /** * Check current login against `lock` field, * which can be a comma-separated list of OpenID handles, or * contain password_hash() literals for API auth. * */ function permission($data, $authwith) { global $moderator_ids; return empty($data["lock"]) or in_array($authwith, array_merge(p_csv($data["lock"]), $moderator_ids)); } /** * Minor data validation: check that `urls` does contain * actual data, not just empty "key= key= key=" lists. * */ function has_urls($str) { return preg_match("/^(\s*[\w-]+\s*=\s*)*$/", $str) ? "" : $str; } } ?> |
Added shared.phar.
cannot compute difference between binary files
Added submit_import.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 348 349 350 351 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: freshcode * title: Import project description * description: Allow DOAP/JSON/etc. import prior manual /submit form intake. * version: 0.5 * * * Checks for uploaded $_FILES or ?import_url= * → Deciphers project name, description, license, tags, etc. * → Passes on extra $data to /submit <form> * */ define("UP_IMPORT_TYPE", "import_via"); define("UP_IMPORT_FILE", "import_file"); define("UP_IMPORT_NAME", "import_name"); /** * Invoked by page_submit itself to populate any empty $data set. * */ class project_import { /** * Evaluate request params, and import data if any. * */ static function fetch($data=NULL) { #-- file upload? if (!empty($_FILES[UP_IMPORT_FILE]["tmp_name"])) { $data = file_get_contents($_FILES[UP_IMPORT_FILE]["tmp_name"]); } #-- import scheme, and project name $type = $_REQUEST->id[UP_IMPORT_TYPE]; $name = $_REQUEST->text[UP_IMPORT_NAME]; if ($type and ($data or $name)) { $i = new self; return (array)@($i->convert($type, $data, $name)); } else { return array(); } } /** * Dispatch to submodules. * */ function convert($type, $data, $name) { #-- switch to fetch methods switch (strtoupper($type)) { case "JSON": return $this->JSON($data); case "PKG-INFO": case "PKGINFO": case "LSM": case "DEBIAN": case "RPMSPEC": return $this->PKG_INFO($data); case "DOAP": return $this->DOAP($data); case "FREECODE": return $this->FREECODE($name); case "SOURCEFORGE": return $this->SOURCEFORGE($name); default: return array(); } } /** * Extract from common JSON formats. * * release.json common.js package.json bower.json composer.json pypi.json * ------------- ------------- ------------- ------------- --------------- ------------- * name name name name name name * version version version version version version * title * description description description description description description * homepage homepage homepage homepage homepage home_page * license licenses* license license* license license * image * state classifiers * download repository repository download_url * urls* repositories repositories release_url * tags keywords keywords keywords keywords keywords * trove classifiers * */ function JSON($data) { // check if it is actually json if ($data = json_decode($data, TRUE)) { // rename a few plain fields $map = array( "name" => "title", // title is commonly absent "screenshot" => "image", "home_page" => "homepage", // pypi "download_url" => "download", // pypi "summary" => "description", // pypi "release_url" => "urls", // pypi ); foreach ($map as $old=>$new) { if (empty($data[$new]) and !empty($data[$old])) { $data[$to] = $data[$from]; } } // complex mapping $map = array( "keywords" => "tags", "classifiers" => "tags", "licenses" => "license", "license" => "license", "repository" => "urls", "repositories" => "urls", "urls" => "urls", ); foreach ($map as $old=>$new) { if (!empty($data[$old])) { switch ($old) { // keywords (common.js, composer.json) become tags case "keywords": $data[$new] = strtolower(join(", ", $data[$old])); break; // Trove classifiers (pypi) case "classifiers": $data[$new] = tags::trove_to_tags($data[$old]); break; // license alias // see spdx.org case "licenses": case "license": while (is_array($data[$old])) { $data[$old] = current($data[$old]); } $data[$new] = tags::map_license($data[$old]); break; // URLs case "repository": $data[$new] = $data[$old]["type"] . "=" . $data[$old]["url"] . "\n"; break; case "repositories": $data[$new] = http_build_query(array_column($data[$old], "url", "type"), "", "\n"); break; case "urls": is_array($data[$old]) and $data[$new] = http_build_query(array_column($data[$old], "url", "packagetype"), "", "\n"); break; } } } // common fields from releases.json are just kept asis $asis = array( "name", "title", "homepage", "description", "license", "tags", "image", "version", "state", "scope", "changes", "download", "urls", "autoupdate_module", "autoupdate_url", "autoupdate_regex", "submitter", "lock", ); // done return( array_filter( array_intersect_key($data, array_flip($asis)), "is_string" ) ); } } /** * Extracts from PKG-INFO and other RFC822-style text files. * * used PKG-INFO LSM Debian RPMSpec * ---- ------------- ------------- ------------ ------- * → Name Title Package Name * → Version Version Version Version * → Description Description Description * → Summary Summary * → Home-Page Primary-Site Homepage URL * Author Author Vendor * → License Coding-Policy Copyright * → Keywords Keywords Section Group * Classifiers * → Platform Platforms * * [1] http://legacy.python.org/dev/peps/pep-0345/ * [2] http://lsm.execpc.com/LSM.README * [3] http://www.debian.org/doc/debian-policy/ch-controlfields.html * [4] http://www.rpm.org/max-rpm/s1-rpm-build-creating-spec-file.html * */ function PKG_INFO($data) { // Simple KEY: VALUE format (value may span multiple lines). preg_match_all("/ ^ %? ([\w-]+): \s* (.+ (?:\R[\v].+$)* ) $ /xm", $data, $uu ) and $data = array_change_key_case(array_combine($uu[1], $uu[2]), CASE_LOWER); // Test if it's PKG-INFO if (!empty($data["description"])) { return array( "title" => $data["name"] ?: $data["title"], "version" => $data["version"], "description" => $data["description"] ?: $data["summary"], "tags" => preg_replace("/[\s,;]+/", ", ", "$data[platform], $data[keywords]"), # "trove-tags" => $data["classifiers"], "homepage" => $data["home-page"] ?: $data["url"] ?: $data["homepage"] ?: $data["primary-site"], "download" => $data["download-url"], "license" => tags::map_license($data["license"] ?: $data["coding-policy"] ?: $data["copyright"]), ); } } /** * Import from DOAP description. * * Would actually require a RDF toolkit, * but for the simple use case here, it's just processed namespace-unaware as xml. * */ function DOAP($data) { if ($x = simplexml_load_string($data)->Project) { $x = array( "name" => strval($x->shortname), "title" => strval($x->name), "description" => strval($x->description ?: $x->shortdesc), "homepage" => strval($x->homepage["resource"]), "download" => strval($x->{'download-page'}["resource"]), "tags" => strval($x->{'programming-language'}) .", ". strval($x->category["resource"]), "license" => tags::map_license(basename(strval($x->license["resource"]))), "version" => strval($x->release->Version->revision), ); return $x; } } /** * Freecodes JSON API is gone, so we have to extract from the project * page itself. * */ function FREECODE($name) { // retrieve if ($html = curl("http://freecode.com/projects/$name")->exec()) { // regex extract to reduce false positives preg_match_all('~ <meta \s+ property="og:title" \s+ content="(?<title>[^"]+)" | <meta \s+ name="keywords" \s+ content="(?<tags>[^"]+)" | class="project-detail"> \s+ <p> (?<description>[^<>]+)</p> | >Licenses< .+? rel="tag"> (?<license>[^<>]+)</a> | >Implementation< .+? rel="tag"> (?<lang>[^<>]+)</a> ~smix', $html, $uu, PREG_SET_ORDER); // join fields if (!empty($uu[0][0])) { $uu = call_user_func_array("array_merge", array_map("array_filter", $uu)); return array( "name" => $name, "title" => $uu["title"], "description" => $uu["description"], "tags" => strtolower((!empty($uu["lang"]) ? "$uu[lang], " : "") . $uu["tags"]), "license" => tags::map_license($uu["license"]), ); } } } /** * Sourceforge still provides a JSON export. * */ function SOURCEFORGE($name) { // retrieve if ($data = json_decode(curl("https://sourceforge.net/rest/p/$name")->exec(), TRUE)) { // custom json extraction return array( "name" => $data["shortname"], "title" => $data["name"], "homepage" => $data["external_homepage"] ?: $data["url"], "description" => $data["short_description"], "image" => $data["screenshots"][0]["thumbnail_url"], "license" => tags::map_license($data["categories"]["license"][0]["fullname"]), "tags" => implode(", ", array_merge( array_column($data["categories"]["language"], "shortname"), array_column($data["categories"]["environment"], "fullname"), array_column($data["categories"]["topic"], "shortname") ) ), "state" => $data["categories"]["developmentstatus"][0]["shortname"] ); } } } #print_r((new project_import)->freecode("firefox")); ?> |
Added tags.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 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * API: freshcode * title: Tags and Trove * description: Provides categorization backend for tree-mapped tags and Trove grouping. * version: 0.2 * type: library * category: taxonomy * doc: http://fossil.include-once.org/freshcode/wiki/Trove+map * license: mixed * * This module provides major tags in a tree, which serves as base for trove categories. * * → Still permits free-form tags. * → Provides for aliasing. * → Only major topic tags end up in trove tree. * → Allows to map licenses from and to tags. * → Handles some HTML and JS output. * * */ /** * Foremost bundles static arrays for tags. * * @static * @dataProvider map * */ class Tags { /** * License monikers and full names. * */ static public $licenses = [ "" => "Unspecified", "Apache" => "Apache License 2.0", "Artistic" => "Artistic license 2.0", "BSDL" => "BSD 3-Clause 'New/Revised' License", "BSDL-2" => "BSD 2-Clause 'Simplified/FreeBSD' License", "CDDL" => "Common Development and Distribution License 1.0", "MITL" => "MIT license", "MPL" => "Mozilla Public License 2.0", "Public Domain" => "Public Domain (no copyright)", "Python" => "Python License", "PHPL" => "PHP License 3.0", "GNU GPL" => "GNU General Public License 2.0", "GNU GPLv3" => "GNU General Public License 3.0", "GNU LGPL" => "GNU Library/Lesser General Public License 2.1", "GNU LGPLv3" => "GNU Library/Lesser General Public License 3.0", "Affero GPL" => "Affero GNU Public License 2.0", "Affero GPLv3" => "GNU Affero General Public License v3", "AFL" => "Academic Free License 3.0", "APL" => "Adaptive Public License", "APSL" => "Apple Public Source License", "AAL" => "Attribution Assurance Licenses", "BSDL-4" => "BSD 4-Clause 'Old' License", "BSL" => "Boost Software License", "CECILL" => "CeCILL License 2.1", "CATOSL" => "Computer Associates Trusted Open Source License 1.1", "CDDL" => "Common Development and Distribution License 1.0", "CPAL" => "Common Public Attribution License 1.0", "CUA" => "CUA Office Public License Version 1.0", "EUDatagrid" => "EU DataGrid Software License", "EPL" => "Eclipse Public License 1.0", "ECL" => "Educational Community License, Version 2.0", "EFL" => "Eiffel Forum License V2.0", "Entessa" => "Entessa Public License", "EUPL" => "European Union Public License, Version 1.1 (EUPL-1.1)", "Fair" => "Fair License", "Frameworx" => "Frameworx License", "HPND" => "Historical Permission Notice and Disclaimer", "IPL" => "IBM Public License 1.0", "IPA" => "IPA Font License", "ISC" => "ISC License", "LPPL" => "LaTeX Project Public License 1.3c", "LPL" => "Lucent Public License Version 1.02", "MirOS" => "MirOS Licence", "MS-RL" => "Microsoft Reciprocal License", "Motosoto" => "Motosoto License", "Multics" => "Multics License", "NASA" => "NASA Open Source Agreement 1.3", "NTP" => "NTP License", "Naumen" => "Naumen Public License", "NGPL" => "Nethack General Public License", "Nokia" => "Nokia Open Source License", "NPOSL" => "Non-Profit Open Software License 3.0", "OCLC" => "OCLC Research Public License 2.0", "OFL" => "Open Font License 1.1", "OGTSL" => "Open Group Test Suite License", "OSL" => "Open Software License 3.0", "PostgreSQL" => "The PostgreSQL License", "CNRI" => "CNRI Python license (CNRI-Python)", "QPL" => "Q Public License", "RPSL" => "RealNetworks Public Source License V1.0", "RPL" => "Reciprocal Public License 1.5", "RSCPL" => "Ricoh Source Code Public License", "SimPL" => "Simple Public License 2.0", "Sleepycat" => "Sleepycat License", "SPL" => "Sun Public License 1.0", "Watcom" => "Sybase Open Watcom Public License 1.0", "NCSA" => "University of Illinois/NCSA Open Source License", "VSL" => "Vovida Software License v. 1.0", "W3C" => "W3C License", "WXwindows" => "wxWindows Library License", "Xnet" => "X.Net License", "ZPL" => "Zope Public License 2.0", "Zlib" => "zlib/libpng license", "Other" => "Other License", "Mixed" => "Multiple Licenses", ]; // todo: Dicuss entry for Commercial/Proprietary code anyhow. // hint: Separation usually works better than prohibition. // (Filtering instead of cleanups) /** * Tag aliases. * */ static public $alias = [ "email" => "e-mail", ]; /** * Tag tree. * */ static public $tree = [ "Topic" => [ "Adaptive Technologies", "Artistic Software", "Communication", "Communication" => [ "BBS", "Chat", "Chat" => [ "ICQ", "Internet Relay Chat", "Skype", "Unix Talk", "XMPP" ], "Conferencing", "Email", "Email" => [ "Address Book", "Email Client", "Email Filter", "Mailing List Server", "Mail Transport Agent", "IMAP", "POP3" ], "Fax", "FIDO", "File Sharing", "Ham Radio", "Internet Phone", "Telephony", "Usenet" ], "Database", "Database" => [ "Database-server", "Front-End" ], "Desktop", "Desktop" => [ "File Manager", "Gnome", "GNUstep", "KDE", "PicoGUI", "Screen Savers", "Window Manager", "Window Manager" => [ "Afterstep", "Applet", "Blackbox", "CTWM", "Enlightenment", "Fluxbox", "FVWM", "IceWM", "MetaCity", "Openbox", "Oroborus", "Sawfish", "Waimea", "Window Maker", "XFCE" ] ], "Documentation", "Education", "Education" => [ "Computer Aided Instruction", "Testing" ], "Game", "Game" => [ "Arcade", "Board Game", "First Person Shooter", "Fortune Cookies", "Multi-User Dungeons", "Puzzle", "Real Time Strategy", "Role-Playing", "Side-Scrolling", "Simulation", "Turn Based Strategy" ], "Home Automation", "Internet", "Internet" => [ "FTP", "Finger", "Log Analysis", "DNS", "Proxy Server", "WAP", "WWW", "WWW" => [ "Browser", "Dynamic Content", "Dynamic Content" => [ "CGI Library", "Message Board", "News/Diary", "Page Counter" ], "HTTP Server", "Indexing/Search", "Session", "Site Management", "Site Management" => [ "Link Checking" ], "WSGI" ] ], "Multimedia", "Multimedia" => [ "Graphics", "Graphics" => [ "3D Modeling", "3D Rendering", "Capture", "Capture" => [ "Digital Camera", "Scanner", "Screen Capture" ], "Editor", "Editor" => [ "Raster-Based", "Vector-Based" ], "Graphics Conversion", "Presentation", "Viewer" ], "Audio", "Audio" => [ "Analysis", "Recording", "CD Audio", "CD Audio" => [ "CD Playing", "CD Ripping", "CD Writing" ], "Conversion", "Editors", "MIDI", "Mixers", "Player", "Player" => [ "MP3" ], "Sound Synthesis", "Speech" ], "Video", "Video" => [ "Capture", "Conversion", "Display", "Non-Linear Editor" ] ], "Office", "Office" => [ "Financial", "Financial" => [ "Accounting", "Investment", "Point-Of-Sale", "Spreadsheet" ], "Groupware", "News/Diary", "Office Suite", "Scheduling" ], "Printing", "Religion", "Scientific", "Scientific" => [ "Artificial Intelligence", "Artificial Life", "Astronomy", "Atmospheric Science", "Bio-Informatics", "Chemistry", "Electronic Design Automation", "GIS", "Human Machine Interfaces", "Image Recognition", "Information Analysis", "Interface Engine", "Mathematics", "Medical Science", "Physics", "Visualization" ], "Security", "Security" => [ "Cryptography" ], "Sociology", "Sociology" => [ "Genealogy", "History" ], "Software Development", "Software Development" => [ "Assembler", "Bug Tracking", "Build Tool", "Code Generator", "Compiler", "Debugger", "Disassembler", "Documentation", "Embedded Systems", "Internationalization", "Interpreter", "Library", "Library" => [ "Application Framework", "Java Library", "Perl Module", "PHP Class", "Pike Module", "pygame", "Python Module", "Ruby Modules", "Tcl Extension" ], "Localization", "Object Brokering", "Object Brokering" => [ "CORBA", "D-Bus", "SOAP" ], "Pre-processor", "Quality Assurance", "Testing", "Testing" => [ "Traffic Generation" ], "User Interfaces", "Version Control", "Widget Set" ], "System", "System" => [ "Archiving", "Archiving" => [ "Backup", "Compression", "Mirroring", "Packaging" ], "Benchmark", "Boot", "Boot" => [ "Init" ], "Clustering", "Console Font", "Distributed Computing", "Emulator", "Filesystem", "Hardware", "Hardware" => [ "Hardware Driver", "Mainframes", "Symmetric Multi-processing" ], "Installation", "Logging", "Monitoring", "Networking", "Networking" => [ "Firewalls", "Monitoring", "Monitoring" => [ "Hardware Watchdog" ], "Time Synchronization" ], "Operating System", "Kernel", "Power (UPS)", "Recovery Tool", "Shells", "Software Distribution", "Systems Administration", "Systems Administration" => [ "Authentication/Directory", "Authentication/Directory" => [ "LDAP", "NIS" ] ] ], "Shell", "Terminal", "Terminal" => [ "Serial", "Telnet", "Terminal Emulator" ], "Text Editor", "Text Editor" => [ "Documentation", "Emacs", "IDE", "Text Processing", "Word Processor" ], "Text Processing", "Text Processing" => [ "Filter", "Font", "General", "Indexing", "Linguistic", "Markup", "Markup" => [ "DocBook", "HTML", "LaTeX", "Markdown", "ReStructuredText", "SGML", "VRML", "Wiki", "XML" ] ], "Utilities" ], "Programming Language" => [ "Ada", "APL", "ASP", "Assembly", "Awk", "Bash", "Basic", "C", "C#", "C++", "Clojure", "Cold Fusion", "Cython", "D", "Delphi", "Dylan", "Eiffel", "Emacs-Lisp", "Erlang", "Euler", "Forth", "Fortran", "Go", "Groovy", "Haskell", "Haxe", "Java", "JavaScript", "Lua", "Lisp", "Logo", "Matlab", "ML", "Modula", "Oberon", "Objective C", "Object Pascal", "OCaml", "Parrot", "Pascal", "Perl", "PHP", "PHP" => [ "HHVM", "Quercus" ], "Pike", "PL/SQL", "PROGRESS", "Prolog", "Python", "Python" => [ "CPython", "IronPython", "Jython", "PyPy", "Stackless" ], "REBOL", "R", "Regex", "Rexx", "Ruby", "Scala", "Scheme", "Simula", "Smalltalk", "SQL", "Tcl", "Unix Shell", "Vala", "YACC", "Zope" ], "Environment" => [ "Console", "Console" => [ "Curses", "Framebuffer", "Newt", "svgalib" ], "Mobile", "MacOS X", "MacOS X" => [ "Aqua", "Carbon", "Cocoa" ], "Daemon", "OpenStack", "Plugin", "Web Environment", "Web Environment" => [ "Buffet", "Mozilla", "ToscaWidgets" ], "Win32", "X11", "X11" => [ "Gnome", "GTK", "KDE", "Qt", "Tk" ], "Wayland" ], "Framework" => [ "C++" => [ "Boost" ], "Groovy" => [ "Grails" ], "Java" => [ "Hibernate", "Spring", "Sinatra", "Struts", "OpenXava" ], "JavaScript" => [ "AngularJS", "extJS", "jQuery", "MooTools", "Prototype", "qooxdoo" ], "Perl" => [ "Mason", "Catalyst" ], "Python" => [ "BFG", "Bob", "Bottle", "Buildout", "Chandler", "CherryPy", "CubicWeb", "Django", "Flask", "IDLE", "IPython", "Opps", "Paste", "Plone", "py2web", "Pylons", "Pyramid", "Review Board", "Setuptools Plugin", "Trac", "Tryton", "TurboGears", "Twisted", "ZODB", "Zope2", "Zope3" ], "PHP" => [ "CakePHP", "Laravel", "Symfony", "Yii", "Zend Framework" ], "Ruby" => [ "Rails" ] ], "Operating System" => [ "BeOS", "Darwin", "MacOS", "MS-DOS", "Windows", "OS2", "Cross-plattform", "PalmOS", "PDA Systems", "POSIX", "AIX", "BSD", "BSD" => [ "FreeBSD", "NetBSD", "OpenBSD" ], "Hurd", "HP-UX", "IRIX", "Linux", "SCO", "Solaris", "QNX", "Unix" ], "Audience" => [ "Customer Service", "Developers", "Education", "End Users", "Financial and Insurance Industry", "Healthcare Industry", "Information Technology", "Legal Industry", "Manufacturing", "Religion", "Science/Research", "System Administrators", "Telecommunications Industry" ], ]; /** * Try to map SPDX.org names onto our license tags, * or find entry in long description; * */ function map_license($id) { // exact find if (isset(tags::$licenses[$id])) { return $id; } // extract moniker and optional version or number if (preg_match_all("/\b[\d.]+\b|\b(?!GNU)\w+\b/", "$id xyDummy", $p)) { list($name, $ver) = @array($p[0][0], $p[0][1]); // close or approximated license description match if ($match = preg_grep("/$name.+?$ver/i", tags::$licenses) or $match = preg_grep("/$name/i", tags::$licenses)) { return key($match); } // or just abbreviation keys if ($match = preg_grep("/{$name}[Lv-]*{$ver[0]}/i", array_keys(tags::$licenses)) or $match = preg_grep("/$name/i", array_keys(tags::$licenses))) { return reset($match); } } } /** * Guess leaves from standard Trove categories * (Does not utilize self::$tree yet!) * */ function trove_to_tags($array, $tags=array()) { preg_match_all("~^Topic :: .+ :: (\w[\w\s/-]+)$~m", implode("\n", (array)$array), $uu); foreach ($uu[1] as $trove) { $tags[] = strtolower( strtr($trove, " /.", "--_") ); } return implode(", ", $tags); } /** * HTML output list of Trove tags. * * Is used in page_submit within the <div class=select id=trove_tags> * * Here everything just wrapped in <span>s, because <select> optgroups * can't be nested, and <ul> breaks out of inline flow text DOM * structure. * */ function trove_select($trove, $level=0, $html="") { // loop through one level foreach ($trove as $key=>$value) { // normalize title into tag-key $tag = is_numeric($key) ? $value : $key; $tag = strtr(strtolower($tag), " /.:-", "-----"); $style = "style='margin-left: {$level}px;'"; // descend into groups if (is_array($value)) { $html .= "<span class=optgroup data-tag=$tag><b class=option data-tag=$tag>$key</b>"; $html .= self::trove_select($value, $level + 10); $html .= "</span>"; } // skip if entry repeated as subgroup elseif (isset($trove[$value])) { #.. } // single tag entry else { $html .= "<span data-tag=$tag class=option>$value</span>"; } } return $html; } /** * Returns just leaves from trove $tree. * */ function leaves() { return iterator_to_array(new RecursiveIteratorIterator(new RecursiveArrayIterator(self::$tree))); } /** * Extract typical release tags. * */ function scope_tags($s) { preg_match_all("/major|minor|bugfix|feature|security|documentation|hidden|cleanup/i", strtolower($s), $uu); return join(" ", $uu[0]); } /** * Extract typical release tags. * */ function state_tag($s) { preg_match_all("/initial|alpha|beta|development|prerelease|stable|mature|historic/i", strtolower($s), $uu); return isset($uu[0][0]) ? $uu[0][0] : ""; } } ?> |
Added template/bottom.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + | </section> <footer id=spotlight> <?php include("template/spotlight.htm"); ?> </footer> <footer id=bottom> <a href="http://fossil.include-once.org/freshcode/wiki/About">About</a> | <a href="http://fossil.include-once.org/freshcode/wiki/Privacy">Privacy / Policy</a> | <a href="http://fossil.include-once.org/freshcode/wiki/Contribute">Contribute</a> | <small> <a href="/login"><i>optional</i> Login</a> </small> <small style=float:right> <span style="display:inline-block; vertical-align:middle;">bookmark<br>freshcode</span> on <?php print social_share_links("freshcode", "http://freshcode.club/"); ?> </small> <br> <small style="font-size:90%"> This is a non-commercial project. <br> All project entries are licensed as CC-BY-SA. There will be <a href="/feed">/atom+json feeds</a>.. </small> </footer> </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> |
Added template/header.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: freshcode * type: template * title: HTML page header * description: Starts <html> and <head>, outputs top bar / menus etc. * version: 0.6.5 * * Optionally injects a `$header_add` list, or allows to override the * page $title. * */ ?> <!DOCTYPE html> <html> <head> <title><?= isset($title) ? $title : "freshcode.club" ?></title> <meta name=version content=0.6.5> <meta charset=UTF-8> <link rel=stylesheet href="/freshcode.css?0.6.5"> <link rel="shortcut icon" href="/img/changes.png"> <base href="//<?= HTTP_HOST ?>/"> <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <!--[if lt IE 9]><script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.2/html5shiv.min.js"></script><![endif]--> <script src="/gimmicks.js"></script> <?php if (isset($header_add)) { print $header_add . "\n"; } ?> </head> <body> <nav id=topbar> Open source software release tracking. <?= is_int(strpos(HTTP_HOST, ".")) ? '<small style="color:#9c7" class=version>[0.6.5 alpha]</small>' : '<b style="color:#c54">[local dev]</b>'; ?> <span style=float:right> <a href="//freshmeat.club/">freshmeat.club</a> | <a href="//freecode.club/">freecode.club</a> | <b><a href="//freshcode.club/">freshcode.club</a></b> </span> </nav> <footer id=logo> <a href="/" title="freshcode.club"><img src="img/logo.png" width=200 height=110 alt=freshcode border=0></a> <div class=empty-box> </div> </footer> <nav id=tools> <a href="/">Home</a> <a href="/submit" class=submit>Submit</a> <span class=submenu> <a href="/names">Browse</a> <a href="/tags" style="left:-5pt;">Projects by Tag</a> </span> <form id=search_q style="display:inline" action=search><input name=q size=5><a href="/search">Search</a></form> <a href="//fossil.include-once.org/freshcode/wiki/About">About</a> <a href="/links">Links</a> <a href="/meta">Meta</a> </nav> |
Added template/index_project.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: include * type: template * title: Frontpage listing * description: Outputs list entry for recent project releases * depends: wrap_tags * * Each project release entry on the frontpage contains * * → Headline with project title, current version, homepage + download button * → Screenshot image * → Description, .trimmed * → Scope and changes, .trimmed * → Short Tag list: license, tags * */ // varexpr callback $_ = "trim"; // Write print <<<HTML <article class=project> <h3> <a href="projects/$entry[name]">$entry[title] <em class=version>$entry[version]</em></a> <span class=links> <span class=published_date>$entry[formatted_date]</span> <a href="$entry[homepage]"><img src="img/home.png" width=20 height=20 border=0 align=middle alt="⛵"></a> <a href="$entry[download]"><img src="img/disk.png" width=20 height=20 border=0 align=middle alt="💾"></a> </span> </h3> <a href="$entry[homepage]"><img class=preview src="$entry[image]" align=right width=120 height=90 border=0></a> <p class="description trimmed">$entry[description]</p> <p class="release-notes trimmed"><b>$entry[scope]:</b> $entry[changes]</p> <p class=tags><img src="img/tag.png" width=30 align=middle height=22 border=0><a class=license>$entry[license]</a>{$_(wrap_tags($entry["tags"]))}</p> </article> HTML; ?> |
Added template/index_sidebar.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * type: template * title: frontpage feeds * description: Outputs #sidebar on frontage, containing template/feed.*.htm * version: 0.4 * * The feed.*.htm files are regularily updated * by cron.daily/newsfeeeds. Thus does not need * further processing here. * */ ?> <aside id=sidebar> <section class="article-links untrimmed"> <h5>Linux.com Software</h5> <?php include("template/feed.linuxcom.htm"); ?> </section> <section class="article-links trimmed"> <h5>reddit<em>/r/linux</em></h5> <?php include("template/feed.reddit.htm"); ?> </section> <section class="article-links trimmed"> <h5>LinuxGames</h5> <?php include("template/feed.linuxgames.htm"); ?> </section> <section class="article-links untrimmed"> <h5>Sourceforge Files</h5> <?php include("template/feed.sourceforge.htm"); ?> </section> <section class="article-links untrimmed"> <h5>DistroWatch</h5> <?php include("template/feed.distrowatch.htm"); ?> </section> </aside> |
Added template/projects_description.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: include * type: template * title: Project description * description: Displays title, version, description, tags, homepage + download button * depends: wrap_tags * * Each projects/ page description contains * * → Headline of project title and current version * → Screenshot image * → Description * → Tag list (tags, license, state) * → [Homepage] and [Download] link buttons * */ $_ = "trim"; print <<<PROJECT <article class=project> <h3> <a href="projects/$entry[name]"> $entry[title] <em class=version>$entry[version]</em> </a> </h3> <a href="$entry[homepage]"> <img class=preview src="$entry[image]" align=right width=120 height=90 border=0> </a> <p class=description style="border:0">$entry[description]</p> <table class=long-tags border=0> <tr> <th>Tags</th> <td>{$_(wrap_tags($entry["tags"]))}</td> </tr> <tr> <th>License</th> <td><a class=license>$entry[license]</a></td> </tr> <tr> <th>State</th> <td><a class=license>$entry[state]</a></td> </tr> </table> <p class=long-links> <a href="$entry[homepage]"><img src="img/home.png" width=20 height=20 border=0 align=middle> Homepage</a> <a href="$entry[download]"><img src="img/disk.png" width=20 height=20 border=0 align=middle> Download</a> </p> </article> PROJECT; ?> |
Added template/projects_release_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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: include * type: template * title: Project version entry * description: Displays a release, with scope and changes * depends: strftime * * Shows versioning history of projects/ * → Date and Version * → Scope and Changes * * This template, obviously, gets iterated over to output all * release entries. * */ print <<<VERSION_ENTRY <div class=release-entry> <span class=version>$entry[version]</span><span class=published_date>{$_(strftime("%d %b %Y %H:%M", $entry["t_published"]))}</span> <span class=release-notes> <b>$entry[scope]:</b> $entry[changes] </span> </div> VERSION_ENTRY; ?> |
Added template/projects_sidebar.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: include * type: template * title: Sidebar links for project * description: Shows project URLs, submitter, submit/ and flag/ link, social bookmarks * depends: proj_links, social_share_count, social_share_links * * Creates #sidebar with four <section>s: * → Project links (homepage, download, other URLs) * → Submitter with gravatar/identicon * → Submission edit button, and flag/ link * → Social sharing links and ★ star count. * */ $_ = "trim"; print <<<SIDEBAR <aside id=sidebar> <section> <h5>Links</h5> <a href="$entry[homepage]"><img src="img/home.png" width=11 height=11> Project Website</a><br> <a href="$entry[download]"><img src="img/disk.png" width=11 height=11> Download</a><br> {$_(proj_links($entry["urls"], $entry))} </section> <section> <h5>Submitted by</h5> <a class=submitter href="/search?user=$entry[submitter]">$entry[submitter_img]$entry[submitter]</a><br> </section> <section style="font-size:90%"> <h5>Manage</h5> You can also help out here by:<br> <a class=long-links href="/submit/$entry[name]" style="display:inline-block; margin: 3pt 1pt;">← Updating infos</a><br> or <a href="/flag/$entry[name]">flagging</a> this entry for moderator attention. </section> <section style="font-size:90%"> <h5>Share project {$_(social_share_count($entry["social_links"]))}</h5> {$_(social_share_links($entry["name"], $entry["homepage"]))} </section> </aside> <section id=main> SIDEBAR; ?> |
Added template/search_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 39 40 41 42 43 44 45 46 47 48 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: include * type: template * title: Search results * description: Display shortened project description * * */ if (!function_exists("smallify")) { function smallify($text, $r="") { $text = explode("\n", wordwrap(input::spaces($text), 15)); foreach ($text as $i=>$line) { $q = 100 - $i; $o = (100 - 1.9*$i) / 100; $r .= "<span style=\"font-size: $q%; opacity: $o;\">" . $line . "</span> "; } return $r; } } $_ = "trim"; print <<<PROJECT <article class="project search"> <h3> <a href="projects/$entry[name]"> $entry[title] <em class=version>$entry[version]</em> </a> </h3> <a href="$entry[homepage]"> <img class=preview src="$entry[image]" align=left width=60 height=45> </a> <small class=description style="border:0">{$_(smallify($entry["description"]))}</small> </article> PROJECT; ?> |
Added template/search_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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: include * type: template * title: Search from * description: Show simple search <form> * * */ $select = "form_select_options"; $licenses = array_slice(tags::$licenses, 1); ?> <h3>Search projects</h3> <form action="/search" method=GET enctype="application/x-www-form-urlencode" accept-encoding=UTF-8> <label> Description <input name=q type=text size=50 style=height:24pt> <small>Search in project titles and descriptions.</small> </label> <label> Tags <input name=tag type=text size=50> <small>Comma-separated list of tags you want to include.</small> </label> <label> Licenses<br> <select name="license[]" multiple size=3> <?php print form_select_options($licenses, NULL); ?> </select> <small>Constrain results to a specific libre / open source / free software licenses.</small> </label> <label> <input type=submit title=Search> </label> </form> |
Added template/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 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * api: freshcode * type: template * title: Project submit/edit form * description: Input fields for project description/release editing. * version: 0.5 * x-func-req: form_select_options * x-var-req: tags::$licenses * * * Expects previous or empty field set in $data. * → * * Also prints out a trivial diversion form for crawlbots. * */ #-- inline placeholders if (!strlen(trim($data["urls"]))) { $data["urls"] = "wiki = \r\ngithub = \r\nrelease-notes = \r\n"; } $select = "form_select_options"; $_ = "trim"; print <<<HTML <span class="PageRank" style="DisplaY: nOne; VisiBility: HiddEN;"> Please bots, submit your recommended link here: <br /> <form action="/submit/pagerank" method="POST"> Name: <input name="name" value="" /> <br/> Email: <input name="email" value="" /> <br/> Website: <input name="link" value="http://" /> <br/> Comment: <textarea name="comment"></textarea> <br/> <input type="submit" name="submit" value="Send" /> </form><hr/> (Real form follows...) </span> <form action="" method=POST enctype="multipart/form-data" accept-encoding=UTF-8 rel=nofollow> <input type=hidden name=is_new value=$is_new> <h3>General Project Info</h3> <p> <label> Project ID <input name=name size=20 placeholder=projectname value="$data[name]" maxlength=33 required pattern="^\w[-_\w]+\w$"> <small>A short moniker which becomes your http://freshcode.club/projects/<var>name</var>.<br> <small>May contain letters, numbers, hyphen or underscore.</small></small> </label> <label> Title <input name=title size=50 placeholder="Awesome Software" value="$data[title]" maxlength=100 required> </label> <label> Homepage <input name=homepage size=50 type=url placeholder="http://project.example.org/" value="$data[homepage]" maxlength=250> </label> <label> Description <textarea cols=55 rows=9 name=description maxlength=1500 required>$data[description]</textarea> <small>Please give a concise roundup of what this software does, what specific features it provides, the intended target audience, or how it compares to similar apps.</small> </label> <label> License <select name=license> {$select(tags::$licenses, $data["license"])} </select> <small>Again note that FLOSS is preferred.</small> </label> <label> Tags<br> <input id=tags name=tags size=50 placeholder="game, desktop, gtk, python" value="$data[tags]" maxlength=150 pattern="^\s*((c\+\+|\w+([-.]\w+)*(\[,;\s]+)?){0,10}\s*$" style="display:inline-block"> <span style="inline-block; height: 0px; overflow: visible; position: absolute;"> <img src=img/addtrove.png with=100 height=150 style="position:relative;top:-150px;"> <span id=trove_tags class=add-tags>{$_(tags::trove_select(tags::$tree))}</span> </span> <small style="width:60%">Categorize your project. Tags can be made up of letters, numbers and dashes. This can include usage context, application type, programming languages, related projects, etc.</small> </label> <label> Image <input type=url name=image size=50 placeholder="http://i.imgur.com/xyzbar.png" value="$data[image]" maxlength=250> <small>Previews will be 120x90 px large. Alternatively a homepage screenshot will appear later.</small> </label> </p> <h3>Release Submission</h3> <p> <label> Version <input name=version size=20 placeholder=2.0.1 value="$data[version]" maxlength=32> <small>Prefer <a href="http://semver.org/">semantic versioning</a> for releases.</small> </label> <label> State <select name=state> {$select("initial,alpha,beta,development,prerelease,stable,mature,historic", $data["state"])} </select> <small>Tells about the stability or target audience of the current release.</small> </label> <label> Scope <br> <select name=scope> {$select("minor feature,minor bugfix,major feature,major bugfix,security,documentation,cleanup,hidden", $data["scope"])} </select> <small>Indicate the significance and primary scope of this release.</small> </label> <label> Changes <textarea cols=60 rows=8 name=changes maxlength=2000>$data[changes]</textarea> <small>Summarize the changes in this release. Documentation additions are as crucial as new features or fixed issues.</small> </label> <label> Download URL <input name=download size=50 type=url placeholder="http://project.example.org/" value="$data[download]" maxlength=250> <small>In particular for the download link one could apply the <a class="action version-placeholder"><b><kbd>\$version</kbd></b> placeholder</a>.</small> </label> <label> Other URLs <textarea cols=60 rows=5 name=urls maxlength=2000>$data[urls]</textarea> <small>An ini-style list of URLs like <code>src = http://foo, deb = http://bar</code>. Use customized label tags, common link names include src / rpm / deb / txz / dvcs / release-notes / forum, etc. Either may contain a <a class="action version-placeholder">\$version placeholder</a> again.</small> </label> </p> <h3>Automatic Release Tracking</h3> <p> <em>You can skip this section.</em> But future release submissions can be automated, with a normalized Changelog, or <var>releases.json</var>, or an extraction ruleset <a href=/drchangelog class="action drchangelog"><img src=img/drchangelog.png width=37 height=37 align=right style="padding:5pt"></a> for your version control system or project homepage. See the <a href="http://fossil.include-once.org/freshcode/wiki/Autoupdate">Autoupdate Howto</a> or <a href=/drchangelog class="action drchangelog">Dr.Changelog</a>. </p> <p> <label> Via <select name=autoupdate_module> {$select("none,release.json,changelog,regex,github,sourceforge", $data["autoupdate_module"])} </select> </label> <label> Autoupdate URL <input name=autoupdate_url type=url size=50 value="$data[autoupdate_url]" placeholder="https://github.com/user/repo/Changelog.md" maxlength=250> <small>This is the primary source for <b>releases.json</b> or a <b>Changelog</b>. It's also initially used for <b>Regex</b> rules in absence of override URLs. GitHub and SourceForge links are usually autodiscovered.</small> </label> <label> Rules <span style="font-weight: 100">(URLs, Regex, XPath, jQuery)</span> <textarea cols=50 rows=3 name=autoupdate_regex placeholder="version = /foo-(\d+\.\d+\.\d+)\.txz/" maxlength=2500>$data[autoupdate_regex]</textarea> <small> <a href="http://fossil.include-once.org/freshcode/wiki/AutoupdateRegex">Regex/Xpath automated updates</a> expect a list of <code>field = ..</code> rules. Define an URL and then associated RegExp, XPath or jQuery selectors for the version= and changes= fields, and optionally for state=, scope= and download=.</small> </label> </p> <h3>Publish</h3> <p> Please proofread again before saving. <label> Submitter <input name=submitter size=50 placeholder="Your Name, optional@example.com" value="$data[submitter]" maxlength=100> <small>List your name or nick name here. Optionally add a gravatar email.</small> </label> <label> Lock Entry <input name=lock size=50 placeholder="$_SESSION[openid]" value="$data[lock]" maxlength=250> <small>Normally all projects can be edited by everyone (WikiStyle). If you commit to yours, you can however <a class="action lock-entry"><b>lock</b> this project</a> against one or multiple OpenID handles (comma-separated, take care to use exact URLs; or <a href="/login">log in</a> beforehand). Or add a password hash for using the submit API. </label> </p> <p> <b>Terms and Conditions</b> <label class=inline><input type=checkbox name="req[os]" value=1 required> It's open source / libre / Free software or pertains BSD/Linux.</label> <label class=inline><input type=checkbox name="req[cc]" value=1 required> Your entry is shareable under the <a href="http://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA</a> license.</label> </p> <p> <input type=submit value="Submit Project/Release"> {$_(csrf())} </p> <p style=margin-bottom:75pt> Thanks for your time and effort! </p> </form> HTML; ?> |
Added template/submit_import.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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | <?php /** * type: template * title: Submit form import sidebar * description: Import function sidebar section. * * Local stylesheet addition for making it slightly less prominent * until hovered over. * */ ?> <style> .submit-import.trimmed { display: none; } </style> <form action="/submit" method=POST enctype="multipart/form-data" class="submit-import trimmed"> <section> <a> <h5>Import</h5> <p> Automatically fill in basic project description <label> From <select name=import_via style="font-size: 125%"><option title="releases.json, common.js, package.json, bower.json, composer.json">JSON<option title="Description of a Project XML">DOAP<option title="Python Package Info">PKG-INFO<option title="Freecode.com project listing">freecode<option title="Sourceforge.net project homepage">sourceforge</select> <small>Which file format or service to use for importing.</small> </label> <label> with Name <input type=text name=import_name placeholder=project-id maxlength=33 pattern="^[\w-_.]+$"> <small>Prior project name on freecode or sourceforge.</small> </label> <label> or File Upload <input type=file name=import_file size=5 placeholder="releases.json"> <small>Upload a project.json or .doap or PKG-INFO summary.</small> </label> <input type=submit value="Import and Edit"> </p> <p><small> But please don't perform mass-imports. When copying from freecode/sourceforge try to bring the description up to date. Edits ensure their reusability under the CC-BY-SA license. </small></p> </a> </section> </form> |
Added template/submit_sidebar.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 /** * type: template * title: Project submit #sidebar * description: Generic advises for project submissions * * */ ?> <aside id=sidebar> <section> <h5>Submit project<br>and/or release</h5> <p> You can submit <em title="Free, Libre, and Open Source Software">FLOSS</em> or <em title="or Solaris/Darwin/Hurd">BSD/Linux</em> software here. It's not required that you're a developer of said project. </p> <p><small> You can always edit the common project information together with a current release. It will show up on the frontpage whenever you update a new version number and a changelog summary. </small></p> <?php if ($is_new) { print "<p>Or <a class='action submit-import' style='color:blue'>import</a> a project..</p>"; } ?> </section> <?php include("template/submit_import.php"); ?> </aside> <section id=main> |