<?php
namespace Tlf;
/**
* A (somewhat) minimal layer on top of Liaison & Phad to make them much easier to setup.
*/
class Server {
/** set TRUE to stop deliver() from delivering */
static public $IS_TLFSERV_CLI = false;
/**
* if true, print error messages & always recompile everything
* See init() for initialization.
*/
public bool $debug = false;
/**
* Set false before calling init() if you want to implement your own /generic-error-page/ route
*/
public bool $enable_error_route = true;
/** array of phad objects */
public array $phads = [];
/** environment object */
public \Env $env;
/** Liaison object */
public \Lia $lia;
/** The most recently set Phad object. See $this->phads for list of all phad instances */
public \Phad $phad;
/** The main server package */
public \Lia\Package $main;
public function __construct(){}
/**
* Initialize liaison.
* @note This COULD be in the constructor, but then file_route() & php_route() would have a lot of overhead before running
*
* @param $debug true to force debug mode, false to force no debug mode, null to use default (yes for localhost, no for non-localhost)
*/
public function init($debug=null){
// i could separate this into an init() function so the fast routes are faster
$this->env = new \Env();
if ($debug===null&&$this->env->is_bool(['host.local']))$debug = true;
else if ($debug===null)$debug = false;
$this->debug = $debug;
if ($debug)$this->env->showErrors();
$lia = $this->lia = new \Lia();
// add the main server package (default addons)
$server_dir = \Lia\Package\Server::main_dir();
$this->main = $server = new \Lia\Package\Server($lia, 'server', $server_dir);
$lia->set('lia:server.cache.dir', $server_dir.'/cache/');
// some basic configs
$lia->set('lia:server.router.varDelim', '\\.\\/\\:'); //default includes a hyphen, which is dumb as hek
$lia->set('lia:server.cache.dir',$server->dir.'/cache/');
// if ($debug){
// $lia->set('lia:server.resources.recompileJsAfter', 0);
// $lia->set('lia:server.resources.recompileCssAfter', 0);
// $lia->set('lia:server.resources.useCache', true);
// // $lia->set('lia:blog.use_cache', false);
// $lia->set('lia:server.resources.forceRecompile', true);
// } else {
// $lia->set('lia:server.resources.useCache', true);
// // $lia->set('lia:server.blog.use_cache', true);
// }
if ($debug!==false&&$this->env->is_bool(['host.local'])
&&substr($_SERVER['REQUEST_URI'],-1)=='/'
||$_SERVER['REQUEST_METHOD']=='POST'
){
$lia->cache->delete_all_cache_files();
}
// route to error page
if ($this->enable_error_route&&$_SERVER['REQUEST_URI']=='/generic-error-page/'){ // save a small amount of overhead when this isn't the requested page
$lia->addon('lia:server.seo')->html['meta-noindex'] = ['<meta name="robots" content="noindex" />'];
if (!$lia->addon('lia:server.router')->has_static_route('/generic-error-page/')){
$lia->addRoute('/generic-error-page/',function($route,$response){
$response->content = "Unknown Error. Please return to the <a href=\"/\">Home Page</a> or try again. If this persists, contact the website owner.";
});
}
}
}
/**
* Load environment settings file
* @param $file Absolute path to the json file
*/
public function load_env(string $file){
$this->env->load($file);
}
public function set_cache_dir(string $dir){
// $this->lia->set('server.cache.dir', $dir);
// $this->lia->set('lia:cache.dir', $dir);
}
/**
* To disable the theme which generally provides a full HTML doc
*/
public function disable_theme(){
$server->addon('lia:server.server')->useTheme = false;
}
/** quickly route to a file in $dir */
public function file_route($dir){
\Lia\FastFileRouter::file($dir);
}
/** quickly route to a script in $dir */
public function php_route($dir){
\Lia\FastFileRouter::php($dir);
}
/** Check if the request uri starts with the given string
* @param $url_prefix something like `/blog/`
*/
public function is_request_to(string $url_prefix){
return substr($_SERVER['REQUEST_URI'],0,strlen($url_prefix))
== $url_prefix;
}
/**
* Add a standard Liaison server package for the given dir
*/
public function addServer($dir,$name=null, $url_prefix='/'){
$name = $name ?? uniqid();
$app = new \Lia\Package\Server($this->lia, $name, $dir, $url_prefix);
$app->base_url = $url_prefix;
$app->dir = $dir;
return $app;
}
/**
* Get a pdo object with a mysql connection that's defined in your env settings file
* The settings must define:
* mysql.host, mysql.dbname, mysql.user, & mysql.password
*/
public function env_pdo(){
$env = $this->env;
$pdo = new \PDO(
'mysql:host='.$env->get('mysql.host').';dbname='.$env->get('mysql.dbname'),
$env->get('mysql.user'), $env->get('mysql.password')
);
return $pdo;
}
public function phad_item(string $item, array $args = []){
foreach ($this->phads as $phad){
if ($phad->has_item($item)){
$item = $phad->item($item, $args);
foreach ($item->resource_files()['css'] as $f){
$this->lia->addResourceFile($f);
}
foreach ($item->resource_files()['js'] as $f){
$this->lia->addResourceFile($f);
}
return $item;
}
}
throw new \Exception("Phad item '$item' not found");
}
/**
* @param $dir the base dir. In it, you MUST have dirs 'phad', 'cache', and 'sitemap'
* @param $pdo a pdo object, connected to a database
*/
public function enable_phad(string $dir, \PDO $pdo, $phad_class='Phad', $route_prefix=''){
$lia = $this->lia;
$helper = new \Tlf\Server\Helper();
$options = [
'item_dir'=>$dir.'/phad/',
'cache_dir'=>$dir.'/cache/',
'sitemap_dir'=>$dir.'/sitemap/',
'pdo' => $pdo,// a pdo object
'router' => $lia->addon('lia:server.router'),
'throw_on_query_failure'=>$this->debug,
'force_compile'=>$this->debug,
];
$phad = $phad_class::main($options);
$phad->route_prefix = $route_prefix;
$phad->global_phad_args['lia'] = $this->lia;
$lia->set('phad', $phad);
$lia->addMethod('phad', [$this, 'phad_item']);
$this->phads[] = $phad;
$phad->filters['markdown'] = [$helper, 'markdown_to_html'];
$phad->integration->setup_liaison_routes($lia);
$phad->integration->setup_liaison_route($lia, '/sitemap.xml', $phad->sitemap_dir.'/sitemap.xml');
$phad->handlers['user_has_role'] =
function(string $role){
return true;
}
;
$phad->handlers['can_read_row'] =
function(array $ItemRow,object $ItemInfo,string $ItemName){
return true;
};
$this->phad = $phad;
return $phad;
}
/**
* Run `$lia->deliver()` & automatically handle errors
*/
public function deliver(){
if (static::$IS_TLFSERV_CLI)return;
$debug = $this->debug;
try {
if ($debug && $_SERVER['REQUEST_URI']=='/debug/'){
$this->debug();
exit;
}
$env = $this->env;
try {
$this->lia->deliver();
} catch (\Exception $e){
\Lia\ExceptionCatcher::throw($e,$debug);
}
} catch (\Throwable $e){
if ($debug){
echo nl2br("\n\n---------------------------------------\n\nError:\n");
throw $e;
} else {
ob_get_clean();
try {
$host = $_SERVER['HTTP_HOST'];
$file = $_SERVER['DOCUMENT_ROOT'].'/cache/generic-error-page.html';
if (is_file($file)){
$i=0;
while(ob_get_level()>0&&$i++<10){
ob_end_clean();
}
echo file_get_contents($file);
}
if ($debug){
throw new \Exception("Error page not found at $file");
} else {
exit;
}
} catch (\Throwable $e){
echo "There was an error. Try returning to the "
.'<a href="/">Home Page</a> at '.$host;
exit;
}
}
}
}
public function debug(){
$lia = $this->lia;
$router = $lia->addon('lia:server.router');
$routes = $router->routeMap;
print_r(array_keys($routes['POST']));
exit;
}
/**
* 1. Save the current page to analytics
* 2. Enable the analytics route, if phad is enabled
*/
public function enable_analytics(\PDO $pdo){
$url = @$_SERVER['REQUEST_URI'];
$pos = strpos($url, '?');
$url = str_replace(['<','>'],'-',$url);
if ($pos!==false)$url = substr($url,0,$pos);
// $ip_hash = md5(@$_SERVER["REMOTE_ADDR"]);
$ip_hash = 'na';
$datetime = new \DateTime();
$year_month = $datetime->format("Y-m")."-01";
$end = substr($url,-4);
// var_dump($end);
if (substr($url,-4)=='.css'||substr($url,-3)=='.js'){
} else {
$stmt = $pdo->prepare(
'INSERT INTO tlf_analytics (`url`, `ip_hash`, `year_month`)
VALUES (:url, :ip_hash, :year_month)
');
if ($stmt==false){
// print_r($pdo->errorInfo());
// exit;
return;
}
// echo 'execute analytics';
// exit;
$stmt->execute( ['url'=>$url,'ip_hash'=>$ip_hash, 'year_month'=>$year_month]);
}
if (!isset($this->phad)||!is_object($this->phad))return;
$args = [
'pdo'=>$pdo,
];
$item = $this->phad->item_from_file(__DIR__.'/AnalyticsView.php', $args);
$this->lia->addRoute(
'/analytics/',
function($route,$response) use ($item){
$response->content = $item->html();
}
);
}
/**
* Initialize your analytics table. Recommend using a local sqlite database.
*/
public function init_analytics_table(\PDO $pdo){
$pdo->exec(
'CREATE TABLE IF NOT EXISTS `tlf_analytics`(
`id` int AUTO_INCREMENT,
`url` varchar(255) NOT NULL,
`ip_hash` varchar(255) NOT NULL,
`year_month` varchar(12) NOT NULL,
PRIMARY KEY(`id`)
);
'
);
print_r($pdo->errorInfo());
// exit;
}
}