<?php //written by kroc camen of camen design
/* =======================================================================================================================
   hello, just a heads-up that all of the code on this site is now yours to use anyway you want anywhere you want, enjoy!
   a more detailed and fun description of the licencing is here: <camendesign.com/blog/when_its_better>
   -----------------------------------------------------------------------------------------------------------------------
   this script generates the website by templating the contents of a data directory containing HTML files for each of the
   articles in the site. this script itself is not run live on the server, instead it produces a folder containing the
   website, that is uploaded. in order to use this script, you’ll need to download the zip file containing the necessary
   folder layout and template files - <camendesign.com/code/files/0.2/camendesign-0.2.zip>
*/
date_default_timezone_set ('Europe/London');

//text mode!
header ('Content-type: text/plain');
ob_start ();

//“ReMarkable” is my Markdown-like syntax for writing in plain text and converting to HTML
//see the ReMarkable folder for documentation, or the website <camendesign.com/code/remarkable>
include "ReMarkable/remarkable.php";


/* =======================================================================================================================
   1. compress CSS
   ======================================================================================================================= */
//compress the CSS file to save bandwidth for both me and the user,
//the .htaccess serves “.csz” as “text/css” with gzip encoding (done here)
msg ('* Compressing CSS file...');
file_put_contents ('./upload/design/design.csz'gzencode (file_get_contents ('./upload/design/design.css'), 9));
msg ("\t\t\t[done]\n");


/* =======================================================================================================================
   2. index data
   ======================================================================================================================= */
msg ('* Indexing data...');

/* get a list of the various content-types from the folders in the site (blog | photo | tweet &c.)
   ----------------------------------------------------------------------------------------------------------------------- */
//`array_fill_keys` is used to flip the array into `"type"=>0` format so we can clock up the counts of each type
$data_types array_fill_keys (
//get the list of directory contents and strip out files and “.”, “..”
array_filter (scandir ('./data/'), create_function ('$v''return (strpos($v,".")===false);')),
0);

/* generate a list of all of the content in the site, the date => and the meta/content:
   ----------------------------------------------------------------------------------------------------------------------- */
$data_list $data_tags = array ();

//get each entry in each content-type folder
foreach ($data_types as $type => &$count) foreach (preg_grep ('/\.rem$/'scandir ("./data/$type/")) as $file_name) {
//open the file and read the header and HTML
list ($meta$content) = explode ("\n\n"file_get_contents ("./data/$type/$file_name"), 2);
//the header is a JSON object containing the meta-information (date, tags, enclosure &c.)
$meta json_decode ($metatrue);

//if the JSON did not decode, there must be a typo - exit here
if (!$meta) die ("\n\n! JSON header error in: '$type/$file_name'\n\n");

//update information:
$modified false;
//if the file has no date: it’s new. give it a date
if (!isset ($meta['date']))    {$meta['date']    = date ('YmdHi'time ()); $modified true;}
//if the file has no updated meta, add it. the entry will be pushed to the top of the RSS feed
if (!isset ($meta['updated'])) {$meta['updated'] = date ('YmdHi'time ()); $modified true;}
//save the file if the date/updated fields have been changed
if ($modified) {
//of course, I could have just used `json_encode` here, but then it wouldn’t be tidy
file_put_contents ("./data/$type/$file_name",
"{\t\"date\"\t\t:\t${meta['date']},\n".
"\t\"updated\"\t:\t${meta['updated']},\n".
"\t\"title\"\t\t:\t\"".str_replace('"''\"'$meta['title'])."\"".
//optional stuff
(isset ($meta['licence'])   ? ",\n\t\"licence\"\t:\t\"${meta['licence']}\"" '').
(isset ($meta['tags'])      ? ",\n\t\"tags\"\t\t:\t[\"".implode ('", "'$meta['tags'])."\"]" '').
(isset ($meta['enclosure']) ? ",\n\t\"enclosure\"\t:\t\"${meta['enclosure']}\"\n" '').
(isset ($meta['url'])       ? ",\n\t\"src\"\t:\t\"${meta['url']}\"\n" "\n").
//the entry’s content
"}\n\n".$content
);
}

//add this entry to the list, including the content and other information to produce the final HTML
array_push ($data_listarray_merge ($meta, array (
'type'    => $type,
'file'    => "$type/$file_name",
'name'   => "$type/".substr ($file_name0, -4),
'title'   => reMarkable ('# '.$meta['title'].' #'),
'content' => formatContent ($content)
)));

//add to the count for each of the tags
if (isset ($meta['tags'])) foreach ($meta['tags'] as $tag) @$data_tags[$tag]++;

//add to the count for this content-type
$count++;
}
arsort ($data_types); //sort content-types by count, descending (for tag-cloud)
arsort ($data_tags); //sort tags by count, descending (also for tag-cloud)
//sort entries by date, descending (for home page)
usort ($data_listcreate_function ('$a,$b''return $a["date"]<$b["date"]?+1:-1;'));

msg ("\t\t\t\t[done]\n");


/* =======================================================================================================================
   3. create index pages
   ======================================================================================================================= */
msg ("* Creating Index pages:\n");
foreach (
array_merge (array (''), array_keys ($data_types), array_keys ($data_tags)) as $filter) {
//filter the list to just entries for a particular tag (or all entries if no filter)
$data $filter array_filter ($data_listcreate_function (
'$v''global $filter;return ($v["type"]==$filter||(isset($v["tags"])?in_array($filter,$v["tags"]):false));'
)) : $data_list;

msg 
("\t\"$filter\"\t\t\t".str_repeat("\t"- (int) (strlen ("\"$filter\"") / 8)));

//create the folder if it doesn’t exist
if ($filter && !file_exists ("./upload/$filter/")) mkdir ("./upload/$filter/"0777);

//split into pages
foreach ($pages array_chunk ($data5true) as $page => $page_data) {
//generate a page of content
$content ''; foreach ($page_data as $entry$content .= templateEntry ($entry);

//save the file
file_put_contents (
'./upload/'.($filter "$filter/" '').($page+1).'.xhtml',
gzencode (templatePage (
$content$filter, array (),
//url
($filter "$filter/" '').($page+1),
//title
($filter " · $filter" '').($page ' · page '.($page+1) : ''),
//next page link
($page != count ($pages)-1) ? replaceTemplateTag (loadTemplate ('nav-next-page'), 'HREF',
'/'.($filter "$filter/" '').($page+2)
) : '',
//previous page link
$page replaceTemplateTag (loadTemplate ('nav-prev-page'), 'HREF',
'/'.($filter "$filter/" '').($page>$page '')
) : ''
), 9)
);
}
msg (count ($pages)."\t[done]\n");
}


/* =======================================================================================================================
   4. create permalink pages
   ======================================================================================================================= */
msg ('* Creating Permalink pages...');
foreach (
$data_list as &$entry) {
//for this article’s type, find the previous/next articles
$data array_values (array_filter (
$data_listcreate_function ('$v''global $entry;return ($v["type"]==$entry["type"]);')
));
//find the index of this article
$index array_reduce ($datacreate_function (
'$a,$v''static $c;$c++;return ($v["date"]=='.$entry['date'].'?$c:$a);'
)) - 1;

//save the file
file_put_contents (
"./upload/${entry['name']}.xhtml",
gzencode (templatePage (
//html
templateEntry ($entry),
//filter        tags            url             title
$entry['type'], $entry['tags'], $entry['name'], ' · '.rssTitle ($entry['title']),
//next, prev article links
isset ($data[$index+1]) ? replaceTemplateTagArray (loadTemplate ('nav-prev-entry'), array (
'HREF' => '/'.$data[$index+1]['name'],
'TYPE' => $entry['type']
)) : '',
isset ($data[$index-1]) ? replaceTemplateTagArray (loadTemplate ('nav-next-entry'), array (
'HREF' => '/'.$data[$index-1]['name'],
'TYPE' => $entry['type']
)) : ''
), 9)
);
}
msg ("\t\t".count ($data_list)."\t[done]\n");


/* =======================================================================================================================
   5. create 404/410 page
   ======================================================================================================================= */
msg ('* Creating 404/410 pages...');
file_put_contents ('./upload/http-404.xhtml'gzencode (templatePage (
//html                            filter  tags      url         title     next, prev article links
loadTemplate ('pages/error-404'), '',     array (), 'http-404'' · 404''',   ''), 9)
);
file_put_contents ('./upload/http-410.xhtml'gzencode (templatePage (
//html                            filter  tags      url         title     next, prev article links
loadTemplate ('pages/error-410'), '',     array (), 'http-410'' · 410''',   ''), 9)
);
msg ("\t\t\t[done]\n");


/* =======================================================================================================================
   6. create projects page
   ======================================================================================================================= */
msg ('* Creating Projects page...');
file_put_contents ('./upload/projects.xhtml'gzencode (templatePage (
//html                                                    
reMarkable (file_get_contents ("./server/projects.rem"), 0125'./data'),
//filter  tags      url         title          next, prev
'',       array (), 'projects'' · projects''',   ''
), 9));
msg ("\t\t\t[done]\n");


/* =======================================================================================================================
   7. RSS feeds
   ======================================================================================================================= */
msg ('* Creating RSS feeds...');
foreach (
array_merge (array (''), array_keys ($data_types), array_keys ($data_tags)) as $filter) {
//sort the data by updated date
usort ($data_listcreate_function ('$a,$b''return $a["updated"]<$b["updated"]?+1:-1;'));

//filter the list to just entries for a particular tag (or all entries if no filter). limit to 10 items
$data array_slice ($filter array_filter ($data_listcreate_function (
'$v''global $filter;return ($v["type"]==$filter||(isset($v["tags"])?in_array($filter,$v["tags"]):false));'
)) : $data_list010);

$content 
'';
foreach ($data as $item$content .= replaceTemplateTagArray (loadTemplate ('rss/rss-item'), array (
//if a title is not given, use the enclosure filename if available
'TITLE'       => "[${item['type']}] ".($item['title']
rssTitle ($item['title'])
: (isset ($item['enclosure']) ? $item['enclosure'] : '')
),
//if an external URL is provided (link content-type), link to that instead
'URL'         => isset ($item['url']) ? $item['url'] : 'http://camendesign.com/'.$item['name'],
'DESCRIPTION' => htmlspecialchars (preg_replace (
'/(href|src)="\//''$1="http://camendesign.com/',
(isset ($item['enclosure']) ? templateEnclosure ($item['enclosure'], $item['name']) : '').
$item['content']
), ENT_NOQUOTES),
'DATE'        => gmdate ('r'mktime (
(integer) substr ($item['updated'], 8,  2),
(integer) substr ($item['updated'], 102), 0,
(integer) substr ($item['updated'], 4,  2),
(integer) substr ($item['updated'], 6,  2),
(integer) substr ($item['updated'], 0,  4)
)),
'ENCLOSURE'   => isset ($item['enclosure'])
"\n\t\t<enclosure url=\"http://camendesign.com/${item['name']}/".
   str_replace('%2F''/'urlencode ($item['enclosure'])).'" '.
   'length="'.filesize ("./data/${item['name']}/${item['enclosure']}").'" '.
   'type="'.mimeType (substr(strrchr($item['enclosure'], '.'), 1)).'" />'
'',
'CATEGORIES'  => isset ($item['tags']) ? array_reduce (
array_merge (array ($item['type']), $item['tags']),
create_function ('$a,$b''$a.="\n\t\t<category>$b</category>";return $a;')
) : ''
));

//save the file
file_put_contents (
'./upload/'.($filter "$filter/rss" 'rss').'.rsz',
gzencode (replaceTemplateTagArray (loadTemplate ('rss/rss'), array (
'DOMAIN'   => 'http://camendesign.com/'.($filter "$filter/" ''),
'ROOT'     => 'http://camendesign.com/',
'DATE'     => gmdate ('r'),
'CATEGORY' => $filter "<category>$filter</category>" '',
'TITLE'    => $filter " · $filter" '',
'ITEMS'    => $content
)), 9)
);
}
msg ("\t\t\t".count (array_merge (array (''), array_keys ($data_types), array_keys ($data_tags)))."\t[done]\n");



/* / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / */

//log a message to the screen
function msg ($s_message) {
echo ($s_message); ob_flush (); flush ();
}

//template the HTML for a content-entry (blog, tweet, art &c.)
function templateEntry (&$a_meta) {
//convert timestamp for the date formatting functions below
$unix_when mktime (
(integer) substr ($a_meta['date'], 8,  2),
(integer) substr ($a_meta['date'], 102), 0,
(integer) substr ($a_meta['date'], 4,  2),
(integer) substr ($a_meta['date'], 6,  2),
(integer) substr ($a_meta['date'], 0,  4)
);

//this shows the full density of the template system (effectively it is one line)
$html replaceTemplateTagArray (loadTemplate ('entry'), array (
/* --- time ---------------------------------------------------------------------------------------------- */
'HREF'  => '/'.$a_meta['name'],
'MON'   => date ('M'$unix_when), //“A short textual representation of a month, three letters”
'MONTH' => date ('F'$unix_when), //“A full textual representation of a month”
'DAY'   => date ('j'$unix_when), //“Day of the month without leading zeros”
'TH'    => date ('S'$unix_when), //“English ordinal suffix for the day of the month, 2 characters”
'YEAR'  => date ('y'$unix_when), //“A two digit representation of a year”
'DATE'  => date ('c'$unix_when), //“ISO 8601 date” (for `datetime` attribute of `<time>`)
'HOUR'  => date ('g'$unix_when), //“12-hour format of an hour without leading zeros”
'MINS'  => date ('i'$unix_when), //“Minutes with leading zeros”
'AMPM'  => date ('a'$unix_when), //“Lowercase Ante meridiem and Post meridiem”
/* --- content ------------------------------------------------------------------------------------------- */
'TYPE'  => $a_meta['type'],
//load the template for that content type; blogs are shaped different from tweets and so on
'ENTRY' => replaceTemplateTag (loadTemplate ("content-types/${a_meta['type']}"),
'CONTENT'$a_meta['content']
),
//title has to be given after content as the `TITLE` template tag is a part of the content template,
//and not a part of the entry template
'TITLE' => $a_meta['title'],
/* --- optional extras ----------------------------------------------------------------------------------- */
'LICENCE'   => isset ($a_meta['licence']) ? loadTemplate ('licences/'.$a_meta['licence']) : '',
'ENCLOSURE' => isset ($a_meta['enclosure']) ? templateEnclosure ($a_meta['enclosure'], $a_meta['name']) : '',
'URL'       => @$a_meta['url'],
/* --- tags ---------------------------------------------------------------------------------------------- */
'CONTENT_TYPE' => replaceTemplateTagArray (
loadTemplate ('entry-tag'),
array ('TAG' => $a_meta['type'], 'HREF' => "/${a_meta['type']}/")
),
//the list of user-tags aside the content-entry
'TAGS' => isset ($a_meta['tags']) ? array_reduce (
//template each tag and concenate into a list
$a_meta['tags'], create_function (
'$a,$b''return $a.=repeatTemplate("'.addslashes (loadTemplate ('entry-tag')).
'",array("TAG"=>$b,"HREF"=>"/$b/"));'
)
) : ''
));
return $html;
}

//get the HTML for an enclosure
function templateEnclosure ($s_filename$s_path) {
//load the enclosure’s template
$html loadTemplate ('content-types/'.reset (explode ('/'$s_path)).'.enclosure');

$enclosure_path 
"/$s_path/";
$enclosure_file $enclosure_path.$s_filename;

//an image enclosure gets a preview image
if ($image_size getimagesize ("./data$enclosure_file")) {
//is the image a 32-bit PNG (has transparency)
$is_alpha = ($image_size[2] == IMAGETYPE_PNG)
ord (file_get_contents ("./data$enclosure_file"falsenull251)) & 4
false
;
//decide the preview file’s file type:
//* a JPG always has a JPG preview
//* a PNG has a JPG preview unless it has transparency, resulting in a PNG preview
$enclosure_preview $image_size[0]<=640
$s_filename
pathinfo ($s_filenamePATHINFO_FILENAME).'_preview.'.($is_alpha 'png' 'jpg')
;
//if a preview file is required, and it does not exist, create it...
if (
$enclosure_preview != $s_filename &&
!file_exists ("./data$enclosure_path$enclosure_preview")
) {
switch ($image_size[2]) {
case IMAGETYPE_JPEG$image imagecreatefromjpeg ("./data$enclosure_file"); break;
case IMAGETYPE_PNG:  $image imagecreatefrompng  ("./data$enclosure_file"); break;
}
//scale the height according to ratio
$image_height 640 * ($image_size[1] / $image_size[0]);
$image_preview imagecreatetruecolor (640$image_height);
//preserve transparency on PNGs
if ($is_alphaimagealphablending ($image_previewfalse);
//resize the image
imagecopyresampled (
$image_preview$image0000,
640$image_height$image_size[0], $image_size[1]
);
imagedestroy ($image);

//save the preview image:
if ($is_alpha) {
//if transparent, preview must be PNG,
imagesavealpha ($image_previewtrue);
imagepng ($image_preview"./data$enclosure_path$enclosure_preview");
} else {
//otherwise, use a JPG preview instead for better filesize
//resized PNGs are very large due to added colours by anti-alias
//why 80? <ebrueggeman.com/article_php_image_optimization.php>
imagejpeg ($image_preview"./data$enclosure_path$enclosure_preview"80);
}
imagedestroy ($image_preview);
}

//template the preview HTML
$image_size getimagesize ("./data$enclosure_path$enclosure_preview");
replaceTemplateTagArray ($html, array (
'ENCLOSURE_PREVIEW' => $enclosure_path.str_replace('%2F''/'rawurlencode ($enclosure_preview)),
'ENCLOSURE_WIDTH'   => $image_size[0],
'ENCLOSURE_HEIGHT'  => $image_size[1]
));
}

//insert regular enclosure information
return replaceTemplateTagArray ($html, array (
'ENCLOSURE_NAME' => end (explode ('/'htmlspecialchars ($s_filename))),
'ENCLOSURE_HREF' => $enclosure_path.str_replace('%2F''/'rawurlencode ($s_filename)),
'ENCLOSURE_MIME' => mimeType (end (explode ('.'$s_filename))),
//an inline way to format a file size :)
'ENCLOSURE_SIZE' => array_reduce (
array (' B'' KB'' MB'), create_function ('$a,$b',
'return is_numeric($a)?($a>=1024?$a/1024:number_format($a,strlen($b)-2).$b):$a;'
), filesize ("./data$enclosure_path$s_filename")
)
));
}

//generate a whole page's HTML
function templatePage ($s_content$s_filter$a_tags$s_url$s_title$s_next$s_prev) {
//load the base template for the pages - see </design/templates/html>
$html loadTemplate ('html');

replaceTemplateTagArray 
($html, array (
//the “html” link on the page links to the same page, plus “.xhtml” on the end
'HTML_URL'   => "/$s_url.xhtml",
'HEAD_TITLE' => $s_title,
//if viewing a tag, include the rss feed for that
'HEAD_RSS'   => $s_filter replaceTemplateTagArray (loadTemplate ('head'),
array ('HREF'  => "/$s_filter/rss"'TITLE' => $s_filter)
) : ''
));

/* --- tag-cloud ------------------------------------------------------------------------------------------------- */
global $data_types$data_tags;

$tagcloud_tag   
loadTemplate ('nav-tagcloud-tag');
$tagcloud_types '';
$tagcloud_tags  '';
$on loadTemplate ('nav-tagcloud-tag-on');

replaceTemplateTag ($html'ALL_ON', !$s_filter && $s_url !== 'projects' $on '');
replaceTemplateTag ($html'PROJECTS_ON'$s_url == 'projects' $on '');

foreach 
($data_types as $type => $count$tagcloud_types .= repeatTemplate ($tagcloud_tag, array (
'TAG'   => $type,
'HREF'  => "/$type/",
'COUNT' => $count,
'ON'    => $s_filter == $type $on ''
));

foreach ($data_tags as $tag => $count$tagcloud_tags .= repeatTemplate ($tagcloud_tag, array (
'TAG'   => $tag,
'HREF'  => "/$tag/",
'COUNT' => $count,
'ON'    => ($s_filter == $tag || @in_array ($tag$a_tags)) ? $on ''
));

replaceTemplateTag ($html'TAG_CLOUD_TYPES'$tagcloud_types);
replaceTemplateTag ($html'TAG_CLOUD_TAGS',  $tagcloud_tags);

replaceTemplateTagArray 
($html, array ('PREV' => $s_prev'NEXT' => $s_next));

replaceTemplateTag 
($html'CONTENT'$s_content);

return $html
;
}


/* === templating ======================================================================================================== */
//the simplicity of the next four functions hides their fundamental power. with them you can separate the PHP and HTML 100%
//they will scale from the smallest site like this one, up to an entrprise app or a heavy traffic web2.0 website
//learn them, use them; you will not look back

function loadTemplate ($s_template_name) {
return file_get_contents ("./templates/$s_template_name");
}

function 
replaceTemplateTag (&$s_template$s_tag$s_content) {
$s_template str_replace ("&__${s_tag}__"$s_content $s_template);
return $s_template;
}

function 
replaceTemplateTagArray (&$s_template$a_values) {
foreach ($a_values as $key=>$value$s_template replaceTemplateTag ($s_template$key$value);
return $s_template;
}

function 
repeatTemplate ($s_template$a_values) {
return replaceTemplateTagArray ($s_template$a_values);
}


/* ======================================================================================================================= */

//this is a piece of crap, because my elegant hack to get the mime-type from apache wouldn’t work on my live server :[
function mimeType ($s_extension) {
switch (strtolower ($s_extension)) {
//images
case 'gif': return 'image/gif'; break;
case 'jpeg': case 'jpg': return 'image/jpeg'; break;
case 'png': return 'image/png'; break;
//code
case 'asp': return 'text/asp'; break;
case 'css': return 'text/css'; break;
case 'html': return 'text/html'; break;
case 'js': return 'text/javascript'; break;
case 'php': return 'application/x-httpd-php'; break;
//documents
case 'pdf': return 'application/pdf'; break;
case 'txt': return 'text/plain'; break;
//downloads
case 'exe': case 'dmg': return 'application/octet-stream'; break;
case 'sh': return 'application/x-sh'; break;
case 'zip': return 'application/zip'; break;
default: return ''; break;
}
}

//convert an HTML string to a text representation for titles that don’t allow html (e.g. rss, `<title>`)
function rssTitle ($s_string) {
//replace some html tags with text equivalents. this is done so that formatting in the titles can be conveyed to
//people in their rss readers, where an `<em>` might stress a word in a particulary important way &c.
$s_string str_replace (
array ('<strong>''</strong>''<em>''</em>''<q>''</q>''<code>''</code>''<br />'),
array ('*'       '*'        '_'   '_'    '“'  '”'   '`'     '`',       ' '     ),
$s_string
);
//strip out any remaining html tags
return strip_tags ($s_string);
}

//prepare the raw html from the database for display
function formatContent ($s_content$b_absoluteurls false) {
$s_content reMarkable ($s_content0125'./data');

//convert PRE blocks
while (preg_match (
'/<pre>(?:<code>)?(?!\n<code>)(.*?)(?:<\/code>)?<\/pre>/s'$s_content$matchPREG_OFFSET_CAPTURE
)) $s_content substr_replace ($s_content,
"<pre>\n".array_reduce (
//split into lines
explode ("\n"preg_replace (
//retain syntax markup tags
'/&lt;(\/?)(samp|var|dfn|i)&gt;/''<$1$2>'$match[1][0]
)),
create_function ('$a,$v','$a.="<code>$v</code>\n";return $a;')
)."</pre>",
$match[0][1], strlen ($match[0][0])
);
return $s_content;
}

/* ==================================================================================================== code is art === */ ?>