/gitblog.php
PHP | 3690 lines | 2795 code | 463 blank | 432 comment | 463 complexity | 35d614f6e11a05757beeebfdfbce7c9c MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- <?
- error_reporting(E_ALL);
- $gb_time_started = microtime(true);
- date_default_timezone_set(@date_default_timezone_get());
- /**
- * Configuration.
- *
- * These values can be overridden in gb-config.php (or somewhere else for that matter).
- */
- class gb {
- /** URL prefix for tags */
- static public $tags_prefix = 'tags/';
- /** URL prefix for categories */
- static public $categories_prefix = 'category/';
- /** URL prefix for the feed */
- static public $feed_prefix = 'feed';
- /**
- * URL prefix (strftime pattern).
- * Need to specify at least year and month. Day, time and so on is optional.
- * Changing this parameter does not affect the cache.
- */
- static public $posts_prefix = '%Y/%m/';
-
- /** URL prefix for pages */
- static public $pages_prefix = '';
- /** Number of posts per page. */
- static public $posts_pagesize = 10;
-
- /** Enables fuzzy URI matching of posts */
- static public $posts_fuzzy_lookup = true;
-
- /** URL to gitblog index _relative_ to gb::$site_url */
- static public $index_prefix = 'index.php';
- /** 'PATH_INFO' or any other string which will then be matched in $_GET[string] */
- static public $request_query = 'PATH_INFO';
-
- /**
- * When this query string key is set and the client is authorized,
- * the same effect as setting $version_query_key to "work" is achieved.
- */
- static public $preview_query_key = 'preview';
-
- /**
- * When this query string key is set and the client is authorized, the
- * specified version of a viewed post is displayed rather than the live
- * version.
- */
- static public $version_query_key = 'version';
-
- /**
- * When this query string key is set and gb::$is_preview is true, the
- * object specified by pathspec is loaded. This overrides parsing the URI
- * and is needed in cases where there are multiple posts with the same
- * name but with different file extensions (content types).
- */
- static public $pathspec_query_key = 'pathspec';
-
- /**
- * Log messages of priority >=$log_filter will be sent to syslog.
- * Disable logging by setting this to -1.
- * See the "Logging" section in gitblog.php for more information.
- */
- static public $log_filter = LOG_NOTICE;
-
- # --------------------------------------------------------------------------
- # The following are by default set in the gb-config.php file.
- # See gb-config.php for detailed documentation.
-
- /** Site title */
- static public $site_title = null;
-
- /** Site description */
- static public $site_description =
- 'Change this fancy description by editing gb::$site_description in gb-config.php';
-
- /** Shared secret */
- static public $secret = '';
-
- # --------------------------------------------------------------------------
- # Constants
-
- static public $version = '0.1.6';
-
- /** Absolute path to the gitblog directory */
- static public $dir;
-
-
- /** Absolute path to the site root */
- static public $site_dir;
-
- /** Absolute URL to the site root, not including gb::$index_prefix */
- static public $site_url;
-
- /** Absolute URL path (i.e. starts with a slash) to the site root */
- static public $site_path;
-
-
- /** Absolute path to current theme. Available when running a theme. */
- static public $theme_dir;
-
- /** Absolute URL to current theme. Available when running a theme. */
- static public $theme_url;
-
-
- static public $content_cache_fnext = '.content';
- static public $comments_cache_fnext = '.comments';
- static public $index_cache_fnext = '.index';
-
- /**
- * The strftime pattern used to build posts cachename.
- *
- * The granularity of this date is the "bottleneck", or "limiter", for
- * $posts_prefix. If you specify "%Y", $posts_prefix can define patterns with
- * granularity ranging from year to second. But if you set this parameter to
- * "%Y/%m/%d-" the minimum granularity of $posts_prefix goes up to day, which
- * means that this: $posts_prefix = '%Y/%m/' will not work, as day is
- * missing. However this: $posts_prefix = '%y-%m-%e/' and
- * $posts_prefix = '%y/%m/%e/%H/%M/' works fine, as they both have a
- * granularity of one day or more.
- *
- * It's recommended not to alter this value. The only viable case where
- * altering this is if you are posting many many posts every day, thus adding
- * day ($posts_cn_pattern = '%Y/%m/%d-') would give a slight file system
- * performance improvement on most file systems.
- */
- static public $posts_cn_pattern = '%Y/%m-';
-
- # --------------------------------------------------------------------------
- # The following are used at runtime.
-
- static public $title;
-
- static public $is_404 = false;
- static public $is_page = false;
- static public $is_post = false;
- static public $is_posts = false;
- static public $is_search = false;
- static public $is_tags = false;
- static public $is_categories = false;
- static public $is_feed = false;
-
- /**
- * Preview mode -- work content is loaded rather than live versions.
- *
- * This is automatically set to true by the request handler (end of this
- * file) when all of the following are true:
- *
- * - gb::$preview_query_key is set in the query string (i.e. "?preview")
- * - Client is authorized (gb::$authorized is non-false)
- */
- static public $is_preview = false;
-
- /**
- * A universal list of error messages (simple strings) which occured during
- * the current request handling.
- *
- * Themes should take care of this and display these error messages where
- * appropriate.
- */
- static public $errors = array();
-
- /** True if some part of gitblog (inside the gitblog directory) is the initial invoker */
- static public $is_internal_call = false;
-
- /** Contains the site.json structure or null if not loaded */
- static public $site_state = null;
-
- # --------------------------------------------------------------------------
- # Logging
- static public $log_open = false;
- static public $log_cb = null;
-
- /**
- * Send a message to syslog.
- *
- * INT CONSTANT DESCRIPTION
- * ---- ----------- ----------------------------------
- * 0 LOG_EMERG system is unusable
- * 1 LOG_ALERT action must be taken immediately
- * 2 LOG_CRIT critical conditions
- * 3 LOG_ERR error conditions
- * 4 LOG_WARNING warning conditions
- * 5 LOG_NOTICE normal, but significant, condition
- * 6 LOG_INFO informational message
- * 7 LOG_DEBUG debug-level message
- */
- static function log(/* [$priority,] $fmt [mixed ..] */) {
- $vargs = func_get_args();
- $priority = count($vargs) === 1 || !is_int($vargs[0]) ? LOG_NOTICE : array_shift($vargs);
- return self::vlog($priority, $vargs);
- }
-
- static function vlog($priority, $vargs, $btoffset=1, $prefix=null) {
- if ($priority > self::$log_filter)
- return true;
- if ($prefix === null) {
- $bt = debug_backtrace();
- while (!isset($bt[$btoffset]) && $btoffset >= 0)
- $btoffset--;
- $bt = isset($bt[$btoffset]) ? $bt[$btoffset] : $bt[$btoffset-1];
- $prefix = '['.(isset($bt['file']) ? gb_relpath(gb::$site_dir, $bt['file']).':'.$bt['line'] : '?').'] ';
- }
- $msg = $prefix;
- if(count($vargs) > 1) {
- $fmt = array_shift($vargs);
- $msg .= vsprintf($fmt, $vargs);
- }
- elseif ($vargs) {
- $msg .= $vargs[0];
- }
- if (!self::$log_open && !self::openlog() && $priority < LOG_WARNING) {
- trigger_error($msg, E_USER_ERROR);
- return $msg;
- }
- if (self::$log_cb) {
- $fnc = self::$log_cb;
- $fnc($priority, $msg);
- }
- if (syslog($priority, $msg))
- return $msg;
- return error_log($msg, 4) ? $msg : false;
- }
-
- static function openlog($ident=null, $options=LOG_PID, $facility=LOG_USER) {
- if ($ident === null) {
- $u = parse_url(gb::$site_url);
- $ident = 'gitblog.'.isset($u['host']) ? $u['host'] .'.' : '';
- if (isset($u['path']))
- $ident .= str_replace('/', '.', trim($u['path'],'/'));
- }
- self::$log_open = openlog($ident, $options, $facility);
- return self::$log_open;
- }
-
- # --------------------------------------------------------------------------
- # Info about the Request
-
- static protected $current_url = null;
-
- static function url_to($part=null, $htmlsafe=true) {
- $s = gb::$site_url.self::$index_prefix;
- if ($part) {
- if ($part{0} === '/') {
- $s .= strlen($part) > 1 ? substr($part, 1) : '';
- }
- else {
- $v = $part.'_prefix';
- $s .= self::$$v;
- }
- }
- return $htmlsafe ? h($s) : $s;
- }
-
- static function url() {
- if (self::$current_url === null) {
- $u = new GBURL();
- $u->secure = isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on');
- $u->scheme = $u->secure ? 'https' : 'http';
- $u->host = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] :
- (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost');
- if(($p = strpos($u->host,':')) !== false) {
- $u->port = intval(substr($u->host, $p+1));
- $u->host = substr($u->host, 0, $p);
- }
- elseif(isset($_SERVER['SERVER_PORT'])) {
- $u->port = intval($_SERVER['SERVER_PORT']);
- }
- else {
- $u->port = $u->secure ? 443 : 80;
- }
- $u->query = $_GET;
- $u->path = $u->query ? substr(@$_SERVER['REQUEST_URI'], 0, strpos($_SERVER['REQUEST_URI'],'?'))
- : rtrim(@$_SERVER['REQUEST_URI'],'?');
- self::$current_url = $u;
- }
- return self::$current_url;
- }
-
- static function referrer_url($fallback_on_http_referer=false) {
- $dest = isset($_REQUEST['gb-referrer']) ? $_REQUEST['gb-referrer']
- : (isset($_REQUEST['referrer']) ? $_REQUEST['referrer'] : false);
- if ($fallback_on_http_referer && $dest === false && isset($_SERVER['HTTP_REFERER']))
- $dest = $_SERVER['HTTP_REFERER'];
- if ($dest) {
- $dest = new GBURL($dest);
- unset($dest['gb-error']);
- return $dest;
- }
- return false;
- }
-
- # --------------------------------------------------------------------------
- # Admin authentication
-
- static public $authorized = null;
- static public $_authenticators = null;
-
- static function authenticator($context='gb-admin') {
- if (self::$_authenticators === null)
- self::$_authenticators = array();
- elseif (isset(self::$_authenticators[$context]))
- return self::$_authenticators[$context];
- $users = array();
- foreach (GBUser::find() as $email => $account) {
- # only include actual users
- if (strpos($email, '@') !== false)
- $users[$email] = $account->passhash;
- }
- $chap = new CHAP($users, $context);
- self::$_authenticators[$context] = $chap;
- return $chap;
- }
-
- static function deauthorize($redirect=true, $context='gb-admin') {
- $old_authorized = self::$authorized;
- self::$authorized = null;
- if (self::authenticator($context)->deauthorize()) {
- if ($old_authorized)
- self::log('client deauthorized: '.$old_authorized->email);
- gb::event('client-deauthorized', $old_authorized);
- }
- if ($redirect) {
- header('HTTP/1.1 303 See Other');
- header('Location: '.(isset($_REQUEST['referrer']) ? $_REQUEST['referrer'] : gb::$site_url));
- exit(0);
- }
- }
-
- static function authenticate($force=true, $context='gb-admin') {
- $auth = self::authenticator($context);
- self::$authorized = null;
- if (($authed = $auth->authenticate())) {
- self::$authorized = GBUser::find($authed);
- return self::$authorized;
- }
- elseif ($force) {
- $url = gb_admin::$url . 'helpers/authorize.php?referrer='.urlencode(self::url());
- header('HTTP/1.1 303 See Other');
- header('Location: '.$url);
- exit('<html><body>See Other <a href="'.$url.'"></a></body></html>');
- }
- return $authed;
- }
-
- # --------------------------------------------------------------------------
- # Plugins
-
- static public $plugins_loaded = array();
-
- static function plugin_check_enabled($context, $name) {
- $plugin_config = self::data('plugins');
- if (!isset($plugin_config[$context]))
- return false;
- $name = str_replace(array('-', '.'), '_', $name);
- foreach ($plugin_config[$context] as $path) {
- $plugin_name = str_replace(array('-', '.'), '_', substr(basename($path), 0, -4));
- if ($plugin_name == $name);
- return true;
- }
- return false;
- }
-
- static function load_plugins($context) {
- $plugin_config = self::data('plugins');
- if (!isset($plugin_config[$context]))
- return;
- $plugins = $plugin_config[$context];
-
- if (!is_array($plugins))
- return;
-
- # load plugins
- foreach ($plugins as $path) {
- if (!$path)
- continue;
-
- # expand gitblog plugins
- if ($path{0} !== '/')
- $path = gb::$dir . '/plugins/'.$path;
-
- # get loadstate
- $loadstate = null;
- if (isset(self::$plugins_loaded[$path]))
- $loadstate = self::$plugins_loaded[$path];
-
- # check loadstate
- if ($loadstate === null) {
- # load if not loaded
- require $path;
- }
- elseif (in_array($context, $loadstate, true)) {
- # already loaded and inited in this context
- continue;
- }
-
- # call name_plugin::init($context)
- $name = str_replace(array('-', '.'), '_', substr(basename($path), 0, -4)); # assume .xxx
- $did_init = call_user_func(array($name.'_plugin', 'init'), $context);
- if ($loadstate === null)
- self::$plugins_loaded[$path] = $did_init ? array($context) : array();
- elseif ($did_init)
- self::$plugins_loaded[$path][] = $context;
- }
- }
-
- /** A JSONDict */
- static public $settings = null;
- # initialized after the gb class
-
- # --------------------------------------------------------------------------
- # Events
-
- static public $events = array();
-
- /** Register $callable for receiving $event s */
- static function observe($event, $callable) {
- if(isset(self::$events[$event]))
- self::$events[$event][] = $callable;
- else
- self::$events[$event] = array($callable);
- }
-
- /** Dispatch an event, optionally with arguments. */
- static function event(/* $event [, $arg ..] */ ) {
- $args = func_get_args();
- $event = array_shift($args);
- if(isset(self::$events[$event])) {
- foreach(self::$events[$event] as $callable) {
- if (call_user_func_array($callable, $args) === true)
- break;
- }
- }
- }
-
- /** Unregister $callable from receiving $event s */
- static function stop_observing($callable, $event=null) {
- if($event !== null) {
- if(isset(self::$events[$event])) {
- $a =& self::$events[$event];
- if(($i = array_search($callable, $a)) !== false) {
- unset($a[$i]);
- if(!$a)
- unset(self::$events[$event]);
- return true;
- }
- }
- }
- else {
- foreach(self::$events as $n => $a) {
- if(($i = array_search($callable, $a)) !== false) {
- unset(self::$events[$n][$i]);
- if(!self::$events[$n])
- unset(self::$events[$n]);
- return true;
- }
- }
- }
- return false;
- }
-
- # --------------------------------------------------------------------------
- # Filters
-
- static public $filters = array();
-
- /**
- * Add a filter
- *
- * Lower number for $priority means earlier execution of $func.
- *
- * If $func returns boolean FALSE the filter chain is broken, not applying
- * any more filter after the one returning FALSE. Returning anything else
- * have no effect.
- */
- static function add_filter($tag, $func, $priority=100) {
- if (!isset(self::$filters[$tag]))
- self::$filters[$tag] = array($priority => array($func));
- elseif (!isset(self::$filters[$tag][$priority]))
- self::$filters[$tag][$priority] = array($func);
- else
- self::$filters[$tag][$priority][] = $func;
- }
-
- /** Apply filters for $tag on $value */
- static function filter($tag, $value/*, [arg ..] */) {
- $vargs = func_get_args();
- $tag = array_shift($vargs);
- if (!isset(self::$filters[$tag]))
- return $value;
- $a = self::$filters[$tag];
- if ($a === null)
- return $value;
- ksort($a, SORT_NUMERIC);
- foreach ($a as $funcs) {
- foreach ($funcs as $func) {
- $value = call_user_func_array($func, $vargs);
- $vargs[0] = $value;
- }
- }
- return $vargs[0];
- }
-
- # --------------------------------------------------------------------------
- # defer -- Delayed execution
-
- static public $deferred = null;
- static public $deferred_time_limit = 30;
-
- /**
- * Schedule $callable for delayed execution.
- *
- * $callable will be executed after the response has been sent to the client.
- * This is useful for expensive operations which do not need to send
- * anything to the client.
- *
- * At the first call to defer, deferring will be "activated". This means that
- * output buffering is enabled, keepalive disabled and user-abort is ignored.
- * You can check to see if deferring is enabled by doing a truth check on
- * gb::$deferred. The event "did-activate-deferring" is also posted.
- *
- * Use deferring wth caution.
- *
- * A good example of when delayed execution is a good idea, is how the
- * email-notification plugin defers the mail action (this is actually part of
- * GBMail but this plugin makes good use of it).
- *
- * Events:
- *
- * - "did-activate-deferring"
- * Posted when defer is activated.
- *
- */
- static function defer($callable /* [$arg, .. ] */) {
- if (self::$deferred === null) {
- if (headers_sent())
- return false;
- ob_start();
- header('Transfer-Encoding: identity');
- header('Connection: close');
- self::$deferred = array();
- register_shutdown_function(array('gb','run_deferred'));
- ignore_user_abort(true);
- gb::event('did-activate-deferring');
- }
- self::$deferred[] = array($callable, array_slice(func_get_args(), 1));
- return true;
- }
-
- static function run_deferred() {
- try {
- # allow for self::$deferred_time_limit more seconds of processing
- global $gb_time_started;
- $time_spent = time()-$gb_time_started;
- @set_time_limit(self::$deferred_time_limit + $time_spent);
-
- if (headers_sent()) {
- # issue warning if output already started
- gb::log(LOG_WARNING,
- 'defer: output already started -- using interleaved execution');
- }
- else {
- # tell client the request is done
- $size = ob_get_length();
- header('Content-Length: '.$size);
- ob_end_flush();
- }
-
- # flush any pending output
- flush();
-
- # call deferred code
- foreach (self::$deferred as $f) {
- try {
- call_user_func_array($f[0], $f[1]);
- }
- catch (Exception $e) {
- gb::log(LOG_ERR, 'deferred %s failed with %s: %s',
- gb_strlimit(json_encode($f),40), get_class($e), $e->__toString());
- }
- }
- }
- catch (Exception $e) {
- gb::log(LOG_ERR, 'run_deferred failed with %s: %s', get_class($e), $e->__toString());
- }
- }
-
- # --------------------------------------------------------------------------
- # data -- arbitrary key-value storage
-
- static public $data_store_class = 'JSONDict';
- static public $data_stores = array();
-
- static function data($name, $default=null) {
- if (isset(self::$data_stores[$name]))
- return self::$data_stores[$name];
- $cls = self::$data_store_class;
- $store = new $cls($name);
- self::$data_stores[$name] = $store;
- if ($default && !is_array($store->storage()->get()))
- $store->storage()->set($default);
- return $store;
- }
-
- # --------------------------------------------------------------------------
- # reading object indices
-
- static public $object_indices = array();
-
- static function index($name, $fallback=null) {
- if (isset(self::$object_indices[$name]))
- return self::$object_indices[$name];
- if ($fallback !== null) {
- $obj = @unserialize(file_get_contents(self::index_path($name)));
- if ($obj === false)
- return $fallback;
- }
- else
- $obj = unserialize(file_get_contents(self::index_path($name)));
- self::$object_indices[$name] = $obj;
- return $obj;
- }
-
- static function index_cachename($name) {
- return $name.'.index';
- }
-
- static function index_path($name) {
- return gb::$site_dir.'/.git/info/gitblog/'.self::index_cachename($name);
- }
-
- # --------------------------------------------------------------------------
- # GitBlog
-
- static public $rebuilders = array();
-
- /** Execute a command inside a shell */
- static function shell($cmd, $input=null, $cwd=null, $env=null) {
- #var_dump($cmd);
- # start process
- $ps = gb_popen($cmd, $cwd, $env === null ? $_ENV : $env);
- if (!$ps)
- return null;
- # stdin
- if ($input)
- fwrite($ps['pipes'][0], $input);
- fclose($ps['pipes'][0]);
- # stdout
- $output = stream_get_contents($ps['pipes'][1]);
- fclose($ps['pipes'][1]);
- # stderr
- $errors = stream_get_contents($ps['pipes'][2]);
- fclose($ps['pipes'][2]);
- # wait and return
- return array(proc_close($ps['handle']), $output, $errors);
- }
-
- /** Glob with PCRE skip filter which defaults to skipping directories. */
- static function glob($pattern, $skip='/\/$/') {
- foreach (glob($pattern, GLOB_MARK|GLOB_BRACE) as $path)
- if ( ($skip && !preg_match($skip, $path)) || !$skip )
- return $path;
- return null;
- }
-
- static function pathToTheme($file='') {
- return gb::$site_dir.'/theme/'.$file;
- }
-
- static function tags($indexname='tags-by-popularity') {
- return gb::index($indexname);
- }
-
- static function categories($indexname='category-to-objs') {
- return gb::index($indexname);
- }
-
- static function urlToTags($tags) {
- return gb::$site_url . gb::$index_prefix . gb::$tags_prefix
- . implode(',', array_map('urlencode', $tags));
- }
-
- static function urlToTag($tag) {
- return gb::$site_url . gb::$index_prefix . gb::$tags_prefix
- . urlencode($tag);
- }
-
- static function urlToCategories($categories) {
- return gb::$site_url . gb::$index_prefix . gb::$categories_prefix
- . implode(',', array_map('urlencode', $categories));
- }
-
- static function urlToCategory($category) {
- return gb::$site_url . gb::$index_prefix . gb::$categories_prefix
- . urlencode($category);
- }
-
- static function init($add_sample_content=true, $shared='true', $theme='default', $mkdirmode=0775) {
- # sanity check
- $themedir = gb::$dir.'/themes/'.$theme;
- if (!is_dir($themedir)) {
- throw new InvalidArgumentException(
- 'no theme named '.$theme.' ('.$themedir.'not found or not a directory)');
- }
-
- # git init
- git::init(null, null, $shared);
-
- # Create empty standard directories
- mkdir(gb::$site_dir.'/content/posts', $mkdirmode, true);
- chmod(gb::$site_dir.'/content', $mkdirmode);
- chmod(gb::$site_dir.'/content/posts', $mkdirmode);
- mkdir(gb::$site_dir.'/content/pages', $mkdirmode);
- chmod(gb::$site_dir.'/content/pages', $mkdirmode);
- mkdir(gb::$site_dir.'/data', $mkdirmode);
- chmod(gb::$site_dir.'/data', $mkdirmode);
-
- # Create hooks and set basic config
- gb_maint::repair_repo_setup();
-
- # Copy default data sets
- $data_skeleton_dir = gb::$dir.'/skeleton/data';
- foreach (scandir($data_skeleton_dir) as $name) {
- if ($name{0} !== '.') {
- $path = $data_skeleton_dir.'/'.$name;
- if (is_file($path)) {
- copy($path, gb::$site_dir.'/data/'.$name);
- chmod(gb::$site_dir.'/data/'.$name, 0664);
- }
- }
- }
-
- # Copy .gitignore
- copy(gb::$dir.'/skeleton/gitignore', gb::$site_dir.'/.gitignore');
- chmod(gb::$site_dir.'/.gitignore', 0664);
- git::add('.gitignore');
-
- # Copy theme
- $lnname = gb::$site_dir.'/index.php';
- $lntarget = gb_relpath($lnname, $themedir.'/index.php');
- symlink($lntarget, $lnname) or exit($lntarget);
- git::add('index.php');
-
- # Add gb-config.php (might been added already, might be missing and/or
- # might be ignored by custom .gitignore -- doesn't really matter)
- git::add('gb-config.php', false);
-
- # Add sample content
- if ($add_sample_content) {
- # Copy example "about" page
- copy(gb::$dir.'/skeleton/content/pages/about.html', gb::$site_dir.'/content/pages/about.html');
- chmod(gb::$site_dir.'/content/pages/about.html', 0664);
- git::add('content/pages/about.html');
-
- # Copy example "about/intro" snippet
- mkdir(gb::$site_dir.'/content/pages/about', $mkdirmode);
- chmod(gb::$site_dir.'/content/pages/about', $mkdirmode);
- copy(gb::$dir.'/skeleton/content/pages/about/intro.html', gb::$site_dir.'/content/pages/about/intro.html');
- chmod(gb::$site_dir.'/content/pages/about/intro.html', 0664);
- git::add('content/pages/about/intro.html');
-
- # Copy example "hello world" post
- $s = file_get_contents(gb::$dir.'/skeleton/content/posts/0000-00-00-hello-world.html');
- $s = preg_replace('/published:.+/', 'published: '.date('H:i:s O'), $s);
- $name = 'content/posts/'.gmdate('Y/m-d').'-hello-world.html';
- $path = gb::$site_dir.'/'.$name;
- @mkdir(dirname($path), 0775, true);
- chmod(dirname($path), 0775);
- $s = str_replace('0000/00-00-hello-world.html', basename(dirname($name)).'/'.basename($name), $s);
- file_put_contents($path, $s);
- chmod($path, 0664);
- git::add($name);
- }
-
- return true;
- }
-
- static function version_parse($s) {
- if (is_int($s))
- return $s;
- $v = array_map('intval', explode('.', $s));
- if (count($v) < 3)
- return 0;
- return ($v[0] << 16) + ($v[1] << 8) + $v[2];
- }
- static function version_format($v) {
- return sprintf('%d.%d.%d', $v >> 16, ($v & 0x00ff00) >> 8, $v & 0x0000ff);
- }
-
- /** Load the site state */
- static function load_site_state() {
- $path = self::$site_dir.'/data/site.json';
- $data = @file_get_contents($path);
- if ($data === false) {
- # version <= 0.1.3 ?
- if (is_readable(gb::$site_dir.'/site.json'))
- gb::$site_state = @json_decode(file_get_contents(gb::$site_dir.'/site.json'), true);
- return gb::$site_state !== null;
- }
- gb::$site_state = json_decode($data, true);
- if (gb::$site_state === null || is_string(gb::$site_state)) {
- self::log(LOG_WARNING, 'syntax error in site.json -- moved to site.json.broken and creating new');
- if (!rename($path, $path.'.broken'))
- self::log(LOG_WARNING, 'failed to move "%s" to "%s"', $path, $path.'.broken');
- gb::$site_state = null;
- return false;
- }
- return true;
- }
-
- /**
- * Verify integrity of the site, automatically taking any actions to restore
- * it if broken.
- *
- * Return values:
- * 0 Nothing done (everything is probably OK).
- * -1 Error (the error has been logged through trigger_error).
- * 1 gitblog cache was updated.
- * 2 gitdir is missing and need to be created (git init).
- * 3 upgrade performed
- */
- static function verify_integrity() {
- $r = 0;
- if (!is_dir(gb::$site_dir.'/.git/info/gitblog')) {
- if (!is_dir(gb::$site_dir.'/.git')) {
- # 2: no repo/not initialized
- return 2;
- }
- # 1: gitblog cache updated
- gb_maint::sync_site_state();
- GBRebuilder::rebuild(true);
- return 1;
- }
-
- # load site.json
- $r = self::load_site_state();
-
- # check site state
- if ( $r === false
- || !isset(gb::$site_state['url'])
- || !gb::$site_state['url']
- || (
- gb::$site_state['url'] !== gb::$site_url
- && strpos(gb::$site_url, '://localhost') === false
- && strpos(gb::$site_url, '://127.0.0.1') === false
- )
- )
- {
- return gb_maint::sync_site_state() === false ? -1 : 0;
- }
- elseif (gb::$site_state['version'] !== gb::$version) {
- return gb_maint::upgrade(gb::$site_state['version']) ? 0 : -1;
- }
- elseif (gb::$site_state['posts_pagesize'] !== gb::$posts_pagesize) {
- gb_maint::sync_site_state();
- GBRebuilder::rebuild(true);
- return 1;
- }
-
- return 0;
- }
-
- static function verify_config() {
- if (!gb::$secret || strlen(gb::$secret) < 62) {
- header('HTTP/1.1 503 Service Unavailable');
- header('Content-Type: text/plain; charset=utf-8');
- exit("\n\ngb::\$secret is not set or too short.\n\nPlease edit your gb-config.php file.\n");
- }
- }
-
- static function verify() {
- if (self::verify_integrity() === 2) {
- header("Location: ".gb::$site_url."gitblog/admin/setup.php");
- exit(0);
- }
- gb::verify_config();
- }
- }
- #------------------------------------------------------------------------------
- # Initialize constants
- gb::$dir = dirname(__FILE__);
- ini_set('include_path', ini_get('include_path') . ':' . gb::$dir . '/lib');
- if (gb::$request_query === 'PATH_INFO')
- gb::$index_prefix = rtrim(gb::$index_prefix, '/').'/';
- $u = dirname($_SERVER['SCRIPT_NAME']);
- $s = dirname($_SERVER['SCRIPT_FILENAME']);
- if (substr($_SERVER['SCRIPT_FILENAME'], -20) === '/gitblog/gitblog.php')
- exit('you can not run gitblog.php directly');
- gb::$is_internal_call = ((strpos($s, '/gitblog/') !== false || substr($s, -8) === '/gitblog')
- && (strpos(realpath($s), realpath(gb::$dir)) === 0));
- # gb::$site_dir
- if (isset($gb_site_dir)) {
- gb::$site_dir = $gb_site_dir;
- unset($gb_site_dir);
- }
- else {
- if (gb::$is_internal_call) {
- # confirmed: inside gitblog -- back up to before the gitblog dir and
- # assume that's the site dir.
- $max = 20;
- while($s !== '/' && $max--) {
- if (substr($s, -7) === 'gitblog') {
- $s = dirname($s);
- $u = dirname($u);
- break;
- }
- $s = dirname($s);
- $u = dirname($u);
- }
- }
- gb::$site_dir = realpath($s);
- }
- # gb::$site_path -- must end in a slash ("/").
- if (isset($gb_site_path)) {
- gb::$site_path = $gb_site_path;
- unset($gb_site_path);
- }
- else {
- gb::$site_path = ($u === '/' ? $u : $u.'/');
- }
- # gb::$site_url -- URL to the base of the site. Must end in a slash ("/").
- if (isset($gb_site_url)) {
- gb::$site_url = $gb_site_url;
- unset($gb_site_url);
- }
- else {
- gb::$site_url = (isset($_SERVER['HTTPS']) ? 'https://' : 'http://')
- .$_SERVER['SERVER_NAME']
- .($_SERVER['SERVER_PORT'] !== '80' && $_SERVER['SERVER_PORT'] !== '443' ? ':'.$_SERVER['SERVER_PORT'] : '')
- .gb::$site_path;
- }
- # only set the following when called externally
- if (!gb::$is_internal_call) {
-
- # gb::$theme_dir
- if (isset($gb_theme_dir)) {
- gb::$theme_dir = $gb_theme_dir;
- unset($gb_theme_dir);
- }
- else {
- $bt = debug_backtrace();
- gb::$theme_dir = dirname($bt[0]['file']);
- }
-
- # gb::$theme_url
- if (isset($gb_theme_url)) {
- gb::$theme_url = $gb_theme_url;
- unset($gb_theme_url);
- }
- else {
- $relpath = gb_relpath(gb::$site_dir, gb::$theme_dir);
- if ($relpath === '' || $relpath === '.') {
- gb::$theme_url = gb::$site_url;
- }
- elseif ($relpath{0} === '.' || $relpath{0} === '/') {
- $uplevels = $max_uplevels = 0;
- if ($relpath{0} === '/') {
- $uplevels = 1;
- }
- if ($relpath{0} === '.') {
- function _empty($x) { return empty($x); }
- $max_uplevels = count(explode('/',trim(parse_url(gb::$site_url, PHP_URL_PATH), '/')));
- $uplevels = count(array_filter(explode('../', $relpath), '_empty'));
- }
- if ($uplevels > $max_uplevels) {
- trigger_error('gb::$theme_url could not be deduced since the theme you are '.
- 'using ('.gb::$theme_dir.') is not reachable from '.gb::$site_url.
- '. You need to manually define $gb_theme_url before including gitblog.php',
- E_USER_ERROR);
- }
- }
- else {
- gb::$theme_url = gb::$site_url . $relpath . '/';
- }
- }
- }
- unset($s);
- unset($u);
- #------------------------------------------------------------------------------
- # Define error handler which throws PHPException s
- function gb_throw_php_error($errno, $errstr, $errfile=null, $errline=-1, $errcontext=null) {
- if(error_reporting() === 0)
- return;
- try { gb::vlog(LOG_WARNING, array($errstr), 2); } catch (Exception $e) {}
- if ($errstr)
- $errstr = html_entity_decode(strip_tags($errstr), ENT_QUOTES, 'UTF-8');
- throw new PHPException($errstr, $errno, $errfile, $errline);
- }
- set_error_handler('gb_throw_php_error', E_ALL);
- #------------------------------------------------------------------------------
- # Load configuration
- if (file_exists(gb::$site_dir.'/gb-config.php'))
- include gb::$site_dir.'/gb-config.php';
- # no config? -- read defaults
- if (gb::$site_title === null) {
- require gb::$dir.'/skeleton/gb-config.php';
- }
- #------------------------------------------------------------------------------
- # Setup autoload and exception handler
- # Lazy class loader
- function __autoload($classname) {
- require $classname . '.php';
- }
- function gb_exception_handler($e) {
- if (ini_get('html_errors')) {
- if (headers_sent())
- $msg = GBException::formatHTMLBlock($e);
- else
- $msg = GBException::formatHTMLDocument($e);
- }
- else
- $msg = GBException::format($e, true, false, null, 0);
- exit($msg);
- }
- set_exception_handler('gb_exception_handler');
- # PATH patches: macports git. todo: move to admin/setup.php
- $_ENV['PATH'] .= ':/opt/local/bin';
- #------------------------------------------------------------------------------
- # Utilities
- # These classes and functions are used in >=90% of all use cases -- that's why
- # they are defined inline here in gitblog.php and not put in lazy files inside
- # lib/.
- /** Dictionary backed by a JSONStore */
- class JSONDict implements ArrayAccess, Countable {
- public $file;
- public $skeleton_file;
- public $cache;
- public $storage;
-
- function __construct($name_or_path, $is_path=false, $skeleton_file=null) {
- $this->file = ($is_path === false) ? gb::$site_dir.'/data/'.$name_or_path.'.json' : $name_or_path;
- $this->cache = null;
- $this->storage = null;
- $this->skeleton_file = $skeleton_file;
- }
-
- /** Retrieve the underlying JSONStore storage */
- function storage() {
- if ($this->storage === null)
- $this->storage = new JSONStore($this->file, $this->skeleton_file);
- return $this->storage;
- }
-
- /**
- * Higher level GET operation able to read deep values, which keys are
- * separated by $sep.
- */
- function get($key, $default=null, $sep='/') {
- if (!$sep) {
- if (($value = $this->offsetGet($key)) === null)
- return $default;
- return $value;
- }
- $keys = explode($sep, trim($key,$sep));
- if (($count = count($keys)) < 2) {
- if (($value = $this->offsetGet($key)) === null)
- return $default;
- return $value;
- }
- $value = $this->offsetGet($keys[0]);
- for ($i=1; $i<$count; $i++) {
- $key = $keys[$i];
- if (!is_array($value) || !isset($value[$key]))
- return $default;
- $value = $value[$key];
- }
- return $value;
- }
-
- /**
- * Higher level PUT operation able to set deep values, which keys are
- * separated by $sep.
- */
- function put($key, $value, $sep='/') {
- $temp_tx = false;
- $keys = explode($sep, trim($key, $sep));
- if (($count = count($keys)) < 2)
- return $this->offsetSet($key, $value);
-
- $this->cache === null;
- $storage = $this->storage();
- if (!$storage->transactionActive()) {
- $storage->begin();
- $temp_tx = true;
- }
- try {
- $storage->get(); # make sure $storage->data is loaded
-
- # two-key optimisation
- if ($count === 2) {
- $key1 = $keys[0];
- $d =& $storage->data[$key1];
- if (!isset($d))
- $d = array($keys[1] => $value);
- elseif (!is_array($d))
- $d = array($storage->data[$key1], $keys[1] => $value);
- else
- $d[$keys[1]] = $value;
- }
- else {
- $patch = null;
- $n = array();
- $leaf_key = array_pop($keys);
- $eroot = null;
- $e = $storage->data;
- $ef = true;
-
- # build patch
- foreach ($keys as $key) {
- $n[$key] = array();
- if ($patch === null) {
- $patch =& $n;
- $eroot =& $e;
- }
- if ($ef !== false) {
- if (isset($e[$key]) && is_array($e[$key]))
- $e =& $e[$key];
- else
- $ef = false;
- }
- $n =& $n[$key];
- }
-
- # apply
- if ($ef !== false) {
- # quick patch (simply replace or set value)
- if (!is_array($e))
- $e = array($leaf_key => $value);
- else
- $e[$leaf_key] = $value;
- $storage->data = $eroot;
- }
- else {
- # merge patch
- $n[$leaf_key] = $value;
- $storage->data = array_merge_recursive($storage->data, $patch);
- }
- }
-
- # commit changes
- $this->cache = $storage->data;
- if ($temp_tx === true)
- $storage->commit();
- }
- catch (Exception $e) {
- if ($temp_tx === true)
- $storage->rollback();
- throw $e;
- }
- }
-
- function offsetGet($k) {
- if ($this->cache === null)
- $this->cache = $this->storage()->get();
- return isset($this->cache[$k]) ? $this->cache[$k] : null;
- }
-
- function offsetSet($k, $v) {
- $this->storage()->set($k, $v);
- $this->cache = null; # will be reloaded at next call to get
- }
-
- function offsetExists($k) {
- if ($this->cache === null)
- $this->cache = $this->storage()->get();
- return isset($this->cache[$k]);
- }
-
- function offsetUnset($k) {
- $this->storage()->set($k, null);
- $this->cache = null; # will be reloaded at next call to get
- }
-
- function count() {
- if ($this->cache === null)
- $this->cache = $this->storage()->get();
- return count($this->cache);
- }
-
- function toJSON() {
- $json = trim(file_get_contents($this->file));
- return (!$json || $json{0} !== '{') ? '{}' : $json;
- }
-
- function __toString() {
- if ($this->cache === null)
- $this->cache = $this->storage()->get();
- return var_export($this->cache ,1);
- }
- }
- class GBURL implements ArrayAccess, Countable {
- public $scheme;
- public $host;
- public $secure;
- public $port;
- public $path = '/';
- public $query;
- public $fragment;
-
- static function parse($str) {
- return new self($str);
- }
-
- function __construct($url=null) {
- if ($url !== null) {
- $p = @parse_url($url);
- if ($p === false)
- throw new InvalidArgumentException('unable to parse URL '.var_export($url,1));
- foreach ($p as $k => $v) {
- if ($k === 'query')
- parse_str($v, $this->query);
- else
- $this->$k = $v;
- }
- $this->secure = $this->scheme === 'https';
- if ($this->port === null)
- $this->port = $this->scheme === 'https' ? 443 : ($this->scheme === 'http' ? 80 : null);
- }
- }
-
- function __toString() {
- return $this->toString();
- }
-
- function toString($scheme=true, $host=true, $port=true, $path=true, $query=true, $fragment=true) {
- $s = '';
-
- if ($scheme !== false) {
- if ($scheme === true) {
- if ($this->scheme)
- $s = $this->scheme . '://';
- }
- else
- $s = $scheme . '://';
- }
-
- if ($host !== false) {
- if ($host === true)
- $s .= $this->host;
- else
- $s .= $host;
-
- if ($port === true && $this->port !== null && (
- ($this->secure === true && $this->port !== 443)
- || ($this->secure === false && $this->port !== 80)
- ))
- $s .= ':' . $this->port;
- elseif ($port !== true && $port !== false)
- $s .= ':' . $port;
- }
-
- if ($path !== false) {
- if ($path === true)
- $s .= $this->path;
- else
- $s .= $path;
- }
-
- if ($query === true && $this->query) {
- if (($query = is_string($this->query) ? $this->query : http_build_query($this->query)))
- $s .= '?'.$query;
- }
- elseif ($query !== true && $query !== false && $query)
- $s .= '?'.(is_string($query) ? $query : http_build_query($query));
-
- if ($fragment === true && $this->fragment)
- $s .= '#'.$this->fragment;
- elseif ($fragment !== true && $fragment !== false && $fragment)
- $s .= '#'.$fragment;
-
- return $s;
- }
- function __sleep() {
- $this->query = http_build_query($this->query);
- return get_object_vars($this);
- }
-
- function __wakeup() {
- $v = $this->query;
- $this->query = array();
- parse_str($v, $this->query);
- }
-
- # ArrayAccess
- function offsetGet($k) { return $this->query[$k]; }
- function offsetSet($k, $v) { $this->query[$k] = $v; }
- function offsetExists($k) { return isset($this->query[$k]); }
- function offsetUnset($k) { unset($this->query[$k]); }
-
- # Countable
- function count() { return count($this->query); }
- }
- /** Human-readable representation of $var */
- function r($var) {
- return var_export($var, true);
- }
- /** Ture $path is an absolute path */
- function gb_isabspath($path) {
- return ($path && $path{0} === '/');
- }
- /** Boiler plate popen */
- function gb_popen($cmd, $cwd=null, $env=null) {
- $fds = array(array("pipe", "r"), array("pipe", "w"), array("pipe", "w"));
- $ps = proc_open($cmd, $fds, $pipes, $cwd, $env);
- if (!is_resource($ps)) {
- trigger_error('gb_popen('.var_export($cmd,1).') failed in '.__FILE__.':'.__LINE__);
- return null;
- }
- return array('handle'=>$ps, 'pipes'=>$pipes);
- }
- /** Sort GBContent objects on published, descending */
- function gb_sortfunc_cobj_date_published_r(GBContent $a, GBContent $b) {
- return $b->published->time - $a->published->time;
- }
- function gb_sortfunc_cobj_date_modified_r(GBContent $a, GBContent $b) {
- return $b->modified->time - $a->modified->time;
- }
- /** path w/o extension */
- function gb_filenoext($path) {
- $p = strrpos($path, '.', strrpos($path, '/'));
- return $p > 0 ? substr($path, 0, $p) : $path;
- }
- /** split path into array("path w/o extension", "extension") */
- function gb_fnsplit($path) {
- $p = strrpos($path, '.', strrpos($path, '/'));
- return array($p > 0 ? substr($path, 0, $p) : $path,
- $p !== false ? substr($path, $p+1) : '');
- }
- /**
- * Commit id of current gitblog head. Used for URLs which should be
- * cached in relation to gitblog versions.
- */
- function gb_headid() {
- if (gb::$site_state !== null && @isset(gb::$site_state['gitblog']) && @isset(gb::$site_state['gitblog']['head']))
- return gb::$site_state['gitblog']['head'];
- return null;
- }
- /** Like readline, but acts on a byte array. Keeps state with $p */
- function gb_sreadline(&$p, $str, $sep="\n") {
- if ($p === null)
- $p = 0;
- $i = strpos($str, $sep, $p);
- if ($i === false)
- return null;
- #echo "p=$p i=$i i-p=".($i-$p)."\n";
- $line = substr($str, $p, $i-$p);
- $p = $i + 1;
- return $line;
- }
- /** Evaluate an escaped UTF-8 sequence, like the ones generated by git */
- function gb_utf8_unescape($s) {
- eval('$s = "'.$s.'";');
- return $s;
- }
- function gb_normalize_git_name($name) {
- return ($name && $name{0} === '"') ? gb_utf8_unescape(substr($name, 1, -1)) : $name;
- }
- /** Normalize $time (any format strtotime can handle) to a ISO timestamp. */
- function gb_strtoisotime($time) {
- $d = new DateTime($time);
- return $d->format('c');
- }
- function gb_mkutctime($st) {
- return gmmktime($st['tm_hour'], $st['tm_min'], $st['tm_sec'],
- $st['tm_mon']+1, ($st['tm_mday'] === 0) ? 1 : $st['tm_mday'], 1900+$st['tm_year']);
- }
- function gb_format_duration($seconds, $format='%H:%M:%S.') {
- $i = intval($seconds);
- return gmstrftime($format, $i).sprintf('%03d', round($seconds*1000.0)-($i*1000));
- }
- function gb_hash($data) {
- return base_convert(hash_hmac('sha1', $data, gb::$secret), 16, 36);
- }
- function gb_flush() {
- if (gb::$deferred === null)
- flush();
- }
- /**
- * Calculate relative path.
- *
- * Example cases:
- *
- * /var/gitblog/site/theme, /var/gitblog/gitblog/themes/default => "../gitblog/themes/default"
- * /var/gitblog/gitblog/themes/default, /var/gitblog/site/theme => "../../site/theme"
- * /var/gitblog/site/theme, /etc/gitblog/gitblog/themes/default => "/etc/gitblog/gitblog/themes/default"
- * /var/gitblog, gitblog/themes/default => "gitblog/themes/default"
- * /var/gitblog/site/theme, /var/gitblog/site/theme => ""
- */
- function gb_relpath($from, $to) {
- if ($from === $to)
- return '.';
- $fromv = explode('/', trim($from,'/'));
- $tov = explode('/', trim($to,'/'));
- $len = min(count($fromv), count($tov));
- $r = array();
- $likes = $back = 0;
-
- for (; $likes<$len; $likes++)
- if ($fromv[$likes] != $tov[$likes])
- break;
-
- if ((!$likes) && $to{0} === '/')
- return $to;
-
- if ($likes) {
- array_pop($fromv);
- $back = count($fromv) - $likes;
- for ($x=0; $x<$back; $x++)
- $r[] = '..';
- $r = array_merge($r, array_slice($tov, $likes));
- }
- else {
- $r = $tov;
- }
-
- return implode('/', $r);
- }
- function gb_hms_from_time($ts) {
- $p = date('his', $ts);
- return (intval($p{0}.$p{1})*60*60) + (intval($p{2}.$p{3})*60) + intval($p{4}.$p{5});
- }
- if (function_exists('mb_substr')) {
- function gb_strlimit($str, $limit=20, $ellipsis='…') {
- if (mb_strlen($str, 'utf-8') > $limit)
- return rtrim(mb_substr($str,0,$limit-mb_strlen($ellipsis, 'utf-8'), 'utf-8')).$ellipsis;
- return $str;
- }
- }
- else {
- function gb_strlimit($str, $limit=20, $ellipsis='…') {
- if (strlen($str) > $limit)
- return rtrim(substr($str,0,$limit-strlen($ellipsis))).$ellipsis;
- return $str;
- }
- }
- function gb_strbool($s, $empty_is_true=false) {
- $s = strtoupper($s);
- return ( $s === 'TRUE' || $s === 'YES' || $s === '1' || $s === 'ON' ||
- ($s === '' && $empty_is_true) );
- }
- function gb_strtodomid($s) {
- return trim(preg_replace('/[^A-Za-z0-9_-]+/m', '-', $s), '-');
- }
- function gb_tokenize_html($html) {
- return preg_split('/(<.*>|\[.*\])/Us', $html, -1,
- PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
- }
- #------------------------------------------------------------------------------
- class GBDateTime {
- public $time;
- public $offset;
-
- function __construct($time=null, $offset=null) {
- if ($time === null || is_int($time)) {
- $this->time = ($time === null) ? time() : $time;
- $this->offset = ($offset === null) ? self::localTimezoneOffset() : $offset;
- }
- else {
- $st = date_parse($time);
- if (isset($st['zone']) && $st['zone'] !== 0)
- $this->offset = -$st['zone']*60;
- if (isset($st['is_dst']) && $st['is_dst'] === true)
- $this->offset += 3600;
- $this->time = gmmktime($st['hour'], $st['minute'], $st['second'],
- $st['month'], $st['day'], $st['year']);
- if ($this->offset !== null)
- $this->time -= $this->offset;
- else
- $this->offset = 0;
- }
- }
-
- function format($format='%FT%H:%M:%S%z') {
- return strftime($format, $this->time);
- }
- function utcformat($format='%FT%H:%M:%SZ') {
- return gmstrftime($format, $this->time);
- }
-
- function origformat($format='%FT%H:%M:%S', $tzformat='H:i') {
- return gmstrftime($format, $this->time + $this->offset)
- . ($tzformat ? self::formatTimezoneOffset($this->offset, $tzformat) : '');
- }
-
- function condensed($ranges=array(86400=>'%H:%M', 31536000=>'%b %e'), $compared_to=null) {
- if ($compared_to === null)
- $diff = time() - $this->time;
- elseif (is_int($compared_to))
- $diff = $compared_to - $this->time;
- else
- $diff = $compared_to->time - $this->time;
- ksort($ranges);
- $default_format = isset($ranges[0]) ? array_shift($ranges) : '%Y-%m-%d';
- # 1, 4, 129
- foreach ($ranges as $threshold => $format) {
- #printf('test %d vs %d (%s, %s)', $diff, $threshold, $format, $this);
- if ($diff < $threshold)
- return $this->origformat($format, false);
- }
- return $this->origformat($default_format, false);
- }
-
- /** Relative age */
- function age($threshold=null, $yformat=null, $absformat=null, $suffix=null,
- $compared_to=null, $momentago=null, $prefix=null)
- {
- if ($threshold === null) $threshold = 2592000; # 30 days
- if ($yformat === null) $yformat='%B %e';
- if ($absformat === null) $absformat='%B %e, %Y';
- if ($suffix === null) $suffix=' ago';
- if ($prefix === null) $prefix='';
- if ($momentago === null) $momentago='A second';
-
- if ($compared_to === null)
- $diff = time() - $this->time;
- elseif (is_int($compared_to))
- $diff = $compared_to - $this->time;
- else
- $diff = $compared_to->time - $this->time;
-
- if ($diff < 0)
- $diff = -$diff;
-
- if ($diff >= $threshold)
- return $this->origformat($diff < 31536000 ? $yformat : $absformat, false);
-
- if ($diff < 5)
- return $prefix.$momentago.$suffix;
- elseif ($diff < 50)
- return $prefix.$diff.' '.($diff === 1 ? 'second' : 'seconds').$suffix;
- elseif ($diff < 3000) {
- $diff = (int)round($diff / 60);
- return $prefix.$diff.' '.($diff === 1 ? 'minute' : 'minutes').$suffix;
- }
- elseif ($diff < 83600) {
- $diff = (int)round($diff / 3600);
- return $prefix.$diff.' '.($diff === 1 ? 'hour' : 'hours').$suffix;
- }
- elseif ($diff < 604800) {
- $diff = (int)round($diff / 86400);
- return $prefix.$diff.' '.($diff === 1 ? 'day' : 'days').$suffix;
- }
- elseif ($diff < 2628000) {
- $diff = (int)round($diff / 604800);
- return $prefix.$diff.' '.($diff === 1 ? 'week' : 'weeks').$suffix;
- }
- elseif ($diff < 31536000) {
- $diff = (int)round($diff / 2628000);
- return $prefix.$diff.' '.($diff === 1 ? 'month' : 'months').$suffix;
- }
- $diff = (int)round($diff / 31536000);
- return $prefix.$diff.' '.($diff === 1 ? 'year' : 'years').$suffix;
- }
-
- /**
- * The offset for timezones west of UTC is always negative, and for those
- * east of UTC is always positive.
- */
- static function localTimezoneOffset() {
- $tod = gettimeofday();
- return -($tod['minuteswest']*60);
- }
-
- static function formatTimezoneOffset($offset, $format='H:i') {
- return ($offset < 0) ? '-'.gmdate($format, -$offset) : '+'.gmdate($format, $offset);
- }
-
- function __toString() {
- return $this->origformat();
- }
-
- function __sleep() {
- #$this->d = gmstrftime('%FT%TZ', $this->time);
- return array('time', 'offset');
- }
-
- function __wakeup() {
- #$this->time = gb_mkutctime(strptime($this->d, '%FT%TZ'));
- #unset($this->d);
- }
-
- static function __set_state($state) {
- if (is_array($state)) {
- $o = new self;
- foreach ($state as $k => $v)
- $o->$k = $v;
- return $o;
- }
- return new self($state);
- }
-
- function reintrepretTimezone($tzoffset) {
- $gmts = $this->offset === 0 ? $this->time : strtotime($this->utcformat());
- $ds = gmstrftime('%FT%H:%M:%S', $gmts+$tzoffset) . self::formatTimezoneOffset($tzoffset);
- return new GBDateTime($ds);
- }
-
- function mergeString($s, $adjustTimezone=false) {
- $t = date_parse($s);
- $ds = '';
- if ($t['hour'] !== false)
- $ds = sprintf('%02d:%02d:%02d', $t['hour'],$t['minute'],$t['second']);
- else
- $ds = $this->utcformat('%T');
- $tzoffset = 0;
- if (isset($t['zone'])) {
- $tzoffset = -($t['zone']*60);
- $ds .= self::formatTimezoneOffset($tzoffset);
- }
- else {
- $ds .= self::formatTimezoneOffset($this->offset);
- }
-
- if ($adjustTimezone)
- $default = explode('-',gmstrftime('%F', strtotime($this->utcformat('%F'))+$tzoffset));
- else
- $default = explode('-',$this->utcformat('%F'));
-
- $ds = (($t['year'] !== false) ? $t['year'] : $default[0]). '-'
- . (($t['month'] !== false) ? $t['month'] : $default[1]). '-'
- . (($t['day'] !== false) ? $t['day'] : $default[2])
- . 'T' . $ds;
-
- return new GBDateTime($ds);
- }
- }
- # -----------------------------------------------------------------------------
- # Content (posts, pages, etc)
- class GBAuthor {
- public $name;
- public $email;
-
- function __construct($name=null, $email=null) {
- $this->name = $name;
- $this->email = $email;
- }
-
- static function parse($gitauthor) {
- $gitauthor = trim($gitauthor);
- $p = strpos($gitauthor, '<');
- $name = '';
- $email = '';
- if ($p === 0) {
- $email = trim($gitauthor, '<>');
- }
- elseif ($p === false) {
- if (strpos($gitauthor, '@') !== false)
- $email = $gitauthor;
- else
- $name = $gitauthor;
- }
- else {
- $name = rtrim(substr($gitauthor, 0, $p));
- $email = trim(substr($gitauthor, $p+1), '<>');
- }
- return new self($name, $email);
- }
-
- static function __set_state($state) {
- if (is_array($state)) {
- $o = new self;
- foreach ($state as $k => $v)
- $o->$k = $v;
- return $o;
- }
- elseif (is_object($state) && $state instanceof self) {
- return $state;
- }
- else {
- return self::parse($state);
- }
- }
-
- static function gitFormat($author, $fallback=null) {…
Large files files are truncated, but you can click here to view the full file