----------------------------------------------------------------------------------------------------------------------- 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 - http://camendesign.com/code/files/0.2/camendesign-0.2.zip *//* • publish.php //this script (it sits above webroot normally) > data //the articles in the website > blog //a folder for each ‘content type’ in the site. blog, art, tweet &c. • some-article //a file (no file extension) containing just the raw HTML of the article • ... > files //enclosures and images used in articles go in a “files” subfolder ... + server //the CSS/images for the site, copied to ‘/upload’ before publishing by a shell script + templates //HTML templates used to create each page in the site + upload //the output folder of this script, creating a whole site to upload */ //text mode! header ('Content-type: text/plain'); ob_start (); //this site is all UTF-8, so any string manipulation should be done multi-byte if possible mb_internal_encoding ('UTF-8'); /* ======================================================================================================================= 1. compress CSS ======================================================================================================================= */ 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 (array_diff ( //ignore the `files` subfolder (for enclosures), and misc filesystem files scandir ("./data/$type/"), array ('.', '..', '.DS_Store', 'Thumbs.db', 'files') ) 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 ($meta, true); //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" : "\n"). //the entry's HTML "}\n\n".$content ); } //add this entry to the list, including the content and other information to produce the final HTML array_push ($data_list, array_merge ( array ('type' => $type, 'file' => "$type/$file_name", 'content' => $content), $meta )); //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_list, create_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_list, create_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", 1 - (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 ($data, 5, true) 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, //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>1 ? $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_list, create_function ('$v', 'global $entry;return ($v["type"]==$entry["type"]);') )); //find the index of this article $index = array_reduce ($data, create_function ( '$a,$v', 'static $c;$c++;return ($v["date"]=='.$entry['date'].'?$c:$a);' )) - 1; //save the file file_put_contents ( "./upload/${entry['file']}.xhtml", gzencode (templatePage ( //html filter url title templateEntry ($entry), $entry['type'], $entry['file'], ' · '.rssTitle ($entry['title']), //next, prev article links isset ($data[$index+1]) ? replaceTemplateTagArray (loadTemplate ('nav-prev-entry'), array ( 'HREF' => '/'.$data[$index+1]['file'], 'TYPE' => $entry['type'] )) : '', isset ($data[$index-1]) ? replaceTemplateTagArray (loadTemplate ('nav-next-entry'), array ( 'HREF' => '/'.$data[$index-1]['file'], '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 url title next, prev article links loadTemplate ('pages/error-404'), '', 'http-404', ' · 404', '', ''), 9) ); file_put_contents ('./upload/http-410.xhtml', gzencode (templatePage ( //html filter url title next, prev article links loadTemplate ('pages/error-410'), '', 'http-410', ' · 410', '', ''), 9) ); msg ("\t\t\t[done]\n"); /* ======================================================================================================================= 6. RSS feeds ======================================================================================================================= */ msg ('* Creating RSS feeds...'); 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). limit to 10 items $data = array_slice ($filter ? array_filter ($data_list, create_function ( '$v', 'global $filter;return ($v["type"]==$filter||(isset($v["tags"])?in_array($filter,$v["tags"]):false));' )) : $data_list, 0, 10); //sort the data by updated date usort ($data, create_function ('$a,$b', 'return $a["updated"]<$b["updated"]?+1:-1;')); $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'] : '') ), 'URL' => 'http://camendesign.com/'.$item['file'], 'DESCRIPTION' => htmlspecialchars (formatContent ( (isset ($item['enclosure']) ? templateEnclosure ($item['enclosure'], $item['type']) : ''). $item['content'], true), ENT_NOQUOTES), 'DATE' => gmdate ('r', mktime ( (integer) substr ($item['updated'], 8, 2), (integer) substr ($item['updated'], 10, 2), 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' : '', 'CATEGORIES' => isset ($item['tags']) ? array_reduce ( array_merge (array ($item['type']), $item['tags']), create_function ('$a,$b', '$a.="\n\t\t$b";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/" : ''), 'DATE' => gmdate ('r'), 'CATEGORY' => $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'], 10, 2), 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['file'], 'MONTH' => date ('M', $unix_when), //“A short textual representation of a month, three letters” '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 `