You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

522 lines
18 KiB

#!/usr/bin/env php
<?php
use Aptoma\Twig\Extension\MarkdownExtension;
use Aptoma\Twig\Extension\MarkdownEngine;
if (PHP_SAPI !== 'cli') {
die();
}
chdir(__DIR__);
$starttime = microtime(true);
require __DIR__ . '/vendor/autoload.php';
if(in_array('--help', $argv) || in_array('-h', $argv)){
echo 'Usage:' . PHP_EOL;
echo ' --run render and switch to new version' . PHP_EOL;
echo ' --no-switch render and don\'t switch to new version' . PHP_EOL;
echo ' --switchonly don\'t render and only switch to new version (use after --no-switch)' . PHP_EOL;
echo ' --rollback switch to previous version' . PHP_EOL;
echo ' --switch-to VER switch to version VER' . PHP_EOL;
echo ' --list-versions list available versions' . PHP_EOL;
echo ' --cleanup N delete old versions but keep the latest N' . PHP_EOL;
die();
}
define('REDIRECT_METHOD_NONE', 0);
define('REDIRECT_METHOD_HTACCESS', 1);
define('REDIRECT_METHOD_PHP', 2);
define('REDIRECT_METHOD_HTML', 3);
define('REDIRECT_METHOD_SYMLINK', 4);
define('HEADER_OVERRIDE_METHOD_NONE', 0);
define('HEADER_OVERRIDE_METHOD_HTACCESS', 1);
define('HEADER_OVERRIDE_METHOD_PHP', 2);
define('IMAGE_RESIZE_METHOD_NONE', 0);
define('IMAGE_RESIZE_METHOD_WIDTH', 1);
define('IMAGE_RESIZE_METHOD_HEIGHT', 2);
define('IMAGE_RESIZE_METHOD_BEST', 3);
require __DIR__ . '/settings.php';
if(! defined('DIR_MODE')){
define('DIR_MODE', 0755);
}
if(! defined('OUTPUT_DIR')){
define('OUTPUT_DIR', __DIR__ . '/versions');
}
define('VERSION_DIR', OUTPUT_DIR . '/' . date('Y-m-d_H-i-s'));
if(! defined('BASE_PATH')){
define('BASE_PATH', 'http://localhost/');
}
if(! defined('THEME')){
define('THEME', 'example');
}
if(! defined('STATIC_FOLDER')){
define('STATIC_FOLDER', 'static');
}
if(! defined('REDIRECT_METHOD')){
define('REDIRECT_METHOD', REDIRECT_METHOD_PHP);
}
if(! defined('HEADER_OVERRIDE_METHOD')){
define('HEADER_OVERRIDE_METHOD', HEADER_OVERRIDE_METHOD_PHP);
}
if(! defined('IMAGE_RESIZE_METHOD')){
define('IMAGE_RESIZE_METHOD', IMAGE_RESIZE_METHOD_BEST);
}
if(!file_exists(__DIR__ . '/templates/' . THEME)){
throw new Exception('Templates folder not found.');
}
if(!file_exists(__DIR__ . '/static/' . THEME)){
throw new Exception('Static folder not found.');
}
$defaultParams = [
'settings' => ['enabled' => true, 'markdown' => false],
'altSlugs' => [],
'variables' => [],
];
if(in_array('--switchonly', $argv)){
if(!file_exists('next')){
throw new Exception('could not switch version. no previous version found.');
}
echo 'switching version' . PHP_EOL;
if(is_link('current')){
rename('current', 'previous');
}
if(!file_exists('current')) {
rename('next', 'current');
}else{
throw new Exceptio('could not switch version. file exists.');
}
die('done.');
}
if(in_array('--rollback', $argv)){
if(!file_exists('previous')){
throw new Exception('could not roll back. no previous version found.');
}
if(is_link('current')){
unlink('current');
}
if(!file_exists('current')){
rename('previous', 'current');
}else{
throw new Exception('could not roll back. file exists.');
}
die('done.');
}
if(in_array('--switch-to', $argv)){
$targetVersion = array_pop($argv);
if(!is_dir(OUTPUT_DIR . '/' . $targetVersion)){
throw new Exception('could not switch version. version ' . $targetVersion . ' not found.');
}
echo 'copying static files' . PHP_EOL;
copyr(__DIR__ . '/static/' . THEME, VERSION_DIR . '/' . STATIC_FOLDER);
if(is_link('current')){
unlink('current');
}
if(!file_exists('current')){
symlink(OUTPUT_DIR . '/' . $targetVersion, 'current');
}else{
throw new Exception('could not switch version. file exists.');
}
die('done.');
}
if(in_array('--list-versions', $argv)){
$versions = glob(OUTPUT_DIR . '/*');
foreach($versions as $version){
if(is_dir($version)){
echo basename($version);
if(realpath('current') == $version) {
echo ' (current)';
} else if (realpath('previous') == $version) {
echo ' (previous)';
} else if (realpath('next') == $version) {
echo ' (next)';
}
echo PHP_EOL;
}
}
die('done.');
}
if(in_array('--cleanup', $argv)){
$count = array_pop($argv);
if(!is_numeric($count)){
throw new Exception('Invalid Argument');
}
$versions = glob(OUTPUT_DIR . '/*');
$totalcount = count($versions);
if($totalcount <= $count){
die('nothing to clean up.');
}
for ($i=0; $i < ($totalcount - $count); $i++) {
if(realpath('current') == $versions[$i]) {
echo 'WARNING! not deleting current version!' . PHP_EOL;
continue;
}
if(realpath('previous') == $versions[$i]) {
echo 'WARNING! deleting previous version!' . PHP_EOL;
unlink('previous');
}
if(realpath('next') == $versions[$i]) {
echo 'WARNING! deleting next version!' . PHP_EOL;
unlink('next');
}
echo 'removing version ' . basename($versions[$i]) . PHP_EOL;
unlinkr($versions[$i]);
}
die('done.');
}
if($argc > 2 || ($argc > 1 && $argv[1] !== '--no-switch' && $argv[1] !== '--run')){
throw new Exception('Invalid Argument');
}
$loader = new Twig_Loader_Filesystem(__DIR__ . '/templates/' . THEME);
$twig = new Twig_Environment($loader, ['debug' => true]);
$markdownengine = new MarkdownEngine\MichelfMarkdownEngine();
$twig->addExtension(new MarkdownExtension($markdownengine));
$twig->addFunction(new Twig_Function('getPageMeta', function ($path = '/', $sort = null, $limit = 0, $offset = 0, $desc = true) use ($defaultParams) {
$tplpath = __DIR__ . '/templates/' . THEME . '/pages' . $path;
if(!file_exists($tplpath)){ throw new Exception('Page ' . $path . ' not found.'); }
$dir_iterator = new RecursiveDirectoryIterator($tplpath);
$iterator = new RecursiveIteratorIterator($dir_iterator);
$sortedresult = [];
foreach($iterator as $file){
if(!$file->isFile() || $file->getExtension() !== 'twig'){
continue;
}
$settingsfile = preg_replace('/\\.twig$/', '.ini', $file->getRealPath());
if(file_exists($settingsfile)){
$data = parse_ini_file($settingsfile, true);
if(!is_array($data)){
echo 'could not parse '.$settingsfile;die();
}
}else{
$data = $defaultParams;
}
if(!array_key_exists('slug', $data['settings'])){
$defaultslug = getDefaultSlug($file);
$data['settings']['slug'] = $defaultslug;
}
$keyparts = explode('.', $sort);
$subdata = $keyparts[0];
$key = $keyparts[1];
if(! array_key_exists($key, $data[$subdata])){
$sortedresult[] = $data;
}else{
$sortedresult[$data[$subdata][$key]] = $data;
}
ksort($sortedresult);
}
if($desc){
$sortedresult = array_reverse($sortedresult);
}
$result = [];
$counter = 0;
$limit += $offset;
foreach($sortedresult as $item){
if(($limit == 0 || $counter < $limit) && ($counter >= $offset || $offset === 0)){
$result[$counter] = $item;
}
$counter++;
}
return $result;
}));
$twig->addFunction(new Twig_Function('dump', function ($input) {
Kint::$enabled_mode = Kint::MODE_RICH;
return d($input);
}));
$twig->addFunction(new Twig_Function('getPageContent', function ($slug, $block = 'entry') use ($twig, $defaultParams) {
$dir_iterator = new RecursiveDirectoryIterator(__DIR__ . '/templates/' . THEME . '/pages/');
$iterator = new RecursiveIteratorIterator($dir_iterator);
foreach($iterator as $templatefile){
if(!$templatefile->isFile() || $templatefile->getExtension() !== 'twig'){
continue;
}
$params = readParams($templatefile, $defaultParams);
$defaultslug = getDefaultSlug($templatefile);
$currentslug = empty($params['settings']['slug']) ? $defaultslug : $params['settings']['slug'];
if($currentslug !== $slug || $params['settings']['enabled'] != true){
continue;
}
$params['settings']['slug'] = $currentslug;
$params['basepath'] = BASE_PATH;
$template = file_get_contents($templatefile->getRealPath());
return $twig->createTemplate($template)->renderBlock($block, $params);
}
throw new Exception('Page ' . $slug . ' not found.');
}));
$twig->addFunction(new Twig_Function('integrityHash', function ($file) {
$urlinfo = parse_url($file);
if(!array_key_exists('scheme', $urlinfo) && substr($file, 0, 2) !== '//'){
$file = __DIR__ . '/static/' . THEME . '/' . $file;
}
if(substr($file, 0, 2) === '//') {
$file = 'https:' . $file;
}
$fileContent = file_get_contents($file);
$hash = hash('sha256', $fileContent, true);
return 'sha256-' . base64_encode($hash);
}));
$twig->addFunction(new Twig_Function('imageresize', function ($image, $size) {
$urlinfo = parse_url($image);
$relativefile = 'imagecache/' . $size . '/' . basename($urlinfo['path']);
$outputfile = __DIR__ . '/static/' . THEME . '/' . $relativefile;
if(file_exists($outputfile)){
return 'static/' . $relativefile;
}
if(!array_key_exists('scheme', $urlinfo) && substr($image, 0, 2) !== '//'){
if(!file_exists(__DIR__ . '/static/' . THEME . '/' . $image)){
throw new Exception('File ' . $image . ' does not exist.');
}
$image = new \Gumlet\ImageResize(__DIR__ . '/static/' . THEME . '/' . $image);
}else{
if(substr($file, 0, 2) === '//') {
$file = 'https:' . $file;
}
$image = \Gumlet\ImageResize::createFromString(file_get_contents($file));
}
if(!is_array(constant('IMAGE_SIZE_' . strtoupper($size)))){
$imagesize = explode(',', constant('IMAGE_SIZE_' . strtoupper($size)));
}
switch(constant('IMAGE_RESIZE_METHOD_' . strtoupper($size))){
case IMAGE_RESIZE_METHOD_NONE:
return $image;
break;
case IMAGE_RESIZE_METHOD_WIDTH:
$image->resizeToWidth($imagesize[0]);
break;
case IMAGE_RESIZE_METHOD_HEIGHT:
$image->resizeToHeight($imagesize[0]);
break;
case IMAGE_RESIZE_METHOD_BEST:
if(count($imagesize) == 1){
$imagesize[1] = $imagesize[0];
}
$image->resizeToBestFit($imagesize[0], $imagesize[1]);
break;
}
mkdirIfNotExists(dirname($outputfile));
$image->save($outputfile);
return 'static/' . $relativefile;
}));
$twig->addFunction(new Twig_Function('php', function ($function, $args = array()) {
return call_user_func_array($function, $args);
}));
echo 'creating version folder ' . VERSION_DIR . PHP_EOL;
mkdirIfNotExists(VERSION_DIR);
$dir_iterator = new RecursiveDirectoryIterator(__DIR__ . '/templates/' . THEME . '/pages/');
$iterator = new RecursiveIteratorIterator($dir_iterator);
foreach ($iterator as $templatefile) {
if(!$templatefile->isFile() || $templatefile->getExtension() !== 'twig'){
continue;
}
echo 'rendering ' . preg_replace('/^' . preg_quote(__DIR__ . '/templates/casper/', '/') . '/', '', $templatefile->getRealPath()) . PHP_EOL;
$template = file_get_contents($templatefile->getRealPath());
$params = readParams($templatefile, $defaultParams);
if($params['settings']['enabled'] != true){
continue;
}
$defaultslug = getDefaultSlug($templatefile);
$slug = !array_key_exists('slug', $params['settings']) ? $defaultslug : $params['settings']['slug'];
handleAltSlugs($params['altSlugs'], $slug, REDIRECT_METHOD);
$params['settings']['slug'] = $slug;
$params['basepath'] = BASE_PATH;
$twigTemplate = $twig->createTemplate($template);
$output = $twigTemplate->render($params);
if($slug === 'index'){
$outputpath = VERSION_DIR;
}else{
$outputpath = VERSION_DIR . '/' . $slug;
}
mkdirIfNotExists($outputpath);
if(array_key_exists('contenttype', $params['settings'])){
if(array_key_exists('headers', $params)){
$params['headers'][] = 'Content-Type: ' . $params['settings']['contenttype'];
}else{
$params['headers'] = ['Content-Type: ' . $params['settings']['contenttype']];
}
}
if(array_key_exists('headers', $params)){
handleHeaderOverride($slug, $params['headers'], HEADER_OVERRIDE_METHOD);
}
file_put_contents($outputpath . '/index.html', $output);
}
if(in_array('--no-switch', $argv)){
echo 'not switching version.' . PHP_EOL;
if(is_link('next')){
unlink('next');
}
if(!file_exists('next')) {
symlink(VERSION_DIR, 'next');
echo 'copying static files' . PHP_EOL;
copyr(__DIR__ . '/static/' . THEME, 'next/' . STATIC_FOLDER);
}else{
throw new Exception('could not create next version. file exists.');
}
}else{
echo 'copying static files' . PHP_EOL;
copyr(__DIR__ . '/static/' . THEME, VERSION_DIR . '/' . STATIC_FOLDER);
echo 'switching version' . PHP_EOL;
if(is_link('current')){
rename('current', 'previous');
}
if(!file_exists('current')) {
symlink(VERSION_DIR, 'current');
}else{
throw new Exception('could not switch version. file exists.');
}
}
echo 'done (' . (number_format((microtime(true) - $starttime) * 1000, 2)) . ' ms).' . PHP_EOL;
function readParams($templatefile, $defaultParams){
$settingsfile = preg_replace('/\\.twig$/', '.ini', $templatefile->getRealPath());
$params = [];
if(file_exists($settingsfile)){
$params = parse_ini_file($settingsfile, true);
}
$params = array_merge($defaultParams, $params);
if(!is_array($params['settings'])){
$params['settings'] = $defaultParams['settings'];
}else{
$params['settings'] = array_merge($defaultParams['settings'], $params['settings']);
}
$params['rendered_at'] = date('Y-m-d H:i:s');
return $params;
}
function getDefaultSlug($templatefile){
if(is_object($templatefile)){
$templatefile = $templatefile->getRealPath();
}
$regex = '/^' . preg_quote(__DIR__ . '/templates/' . THEME . '/pages/', '/') . '(.*)\\.twig$/';
$result = preg_replace($regex, '$1', $templatefile);
return $result;
}
function handleAltSlugs($slugs, $target, $method) {
foreach($slugs as $slug) {
$output = VERSION_DIR . '/' . $slug;
if($target !== '/' && $target !== ''){
$redirectTarget = BASE_PATH . $target;
}else{
$redirectTarget = BASE_PATH;
}
switch($method){
case REDIRECT_METHOD_HTACCESS:
file_put_contents(VERSION_DIR . '/.htaccess', 'Redirect 301 ' . BASE_PATH . $slug . ' ' . $redirectTarget . PHP_EOL, FILE_APPEND);
break;
case REDIRECT_METHOD_PHP:
mkdirIfNotExists($output);
file_put_contents($output . '/index.php', '<?php header("HTTP/1.1 301 Moved Permanently"); header("Location: ' . $redirectTarget . '"); ?>');
break;
case REDIRECT_METHOD_HTML:
mkdirIfNotExists($output);
file_put_contents($output . '/index.html', '<html><head><meta http-equiv="refresh" content="0; url=' . $redirectTarget . '">
<script>window.location.href="' . $redirectTarget . '"</script></head>
<body>Redirecting to <a href="' . $redirectTarget . '">' . htmlentities($redirectTarget) . '</a>...</body></html>');
break;
case REDIRECT_METHOD_SYMLINK:
mkdirIfNotExists(dirname($output));
link($output . '/index.html', VERSION_DIR . '/' . $target);
break;
}
}
}
function handleContentTypeOverride($slug, $contenttype, $method) {
handleHeaderOverride($slug, ['Content-Type: ' . $contenttype], $method);
}
function handleHeaderOverride($slug, $header, $method) {
$outputfile = VERSION_DIR . '/' . $slug;
switch($method){
case HEADER_OVERRIDE_METHOD_HTACCESS:
$outputfile .= '/.htaccess';
$output = '';
foreach($header as $value){
$output .= $value . PHP_EOL;
}
file_put_contents($outputfile, $output . PHP_EOL);
break;
case HEADER_OVERRIDE_METHOD_PHP:
$outputfile .= '/index.php';
$output = '';
foreach($header as $value){
$output .= 'header("' . $value . '", false);';
}
file_put_contents($outputfile, '<?php ' . $output . ' readfile("index.html"); ?>');
break;
}
}
function mkdirIfNotExists($path) {
if(!file_exists($path)){
mkdir($path, DIR_MODE, true);
}
}
function copyr($source, $destination) {
mkdirIfNotExists($destination);
$dir_iterator = new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS);
$iterator = new RecursiveIteratorIterator($dir_iterator, RecursiveIteratorIterator::SELF_FIRST);
foreach($iterator as $item) {
if ($item->isDir()){
mkdirIfNotExists($destination . '/' . $iterator->getSubPathName());
} else {
copy($item, $destination . '/' . $iterator->getSubPathName());
}
}
}
function unlinkr($dir) {
$files = array_diff(scandir($dir), array('.','..'));
foreach ($files as $file) {
if(is_dir($dir . '/' . $file)){
unlinkr($dir . '/' . $file);
}else{
unlink($dir . '/' . $file);
}
}
return rmdir($dir);
}