Importing bits and pieces
This is the initial import based on a few files I had around.
This commit is contained in:
commit
871d28cd16
20 changed files with 1994 additions and 0 deletions
6
includes/box/package.inc.php
Normal file
6
includes/box/package.inc.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
$package[ 'requires' ][] = 'core';
|
||||
$package[ 'files' ][] = 'view';
|
||||
$package[ 'views' ][] = 'box';
|
||||
$package[ 'extras' ][] = 'BoxButton';
|
124
includes/box/view.inc.php
Normal file
124
includes/box/view.inc.php
Normal file
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
class BoxButton
|
||||
{
|
||||
protected $title;
|
||||
protected $URL;
|
||||
protected $id;
|
||||
protected $class;
|
||||
protected $style;
|
||||
|
||||
protected function __construct( $title , $URL )
|
||||
{
|
||||
$this->title = $title;
|
||||
$this->URL = $URL;
|
||||
}
|
||||
|
||||
public function setID( $id )
|
||||
{
|
||||
$this->id = $id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setClass( $class )
|
||||
{
|
||||
$this->class = $class;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setStyle( $style )
|
||||
{
|
||||
$this->style = $style;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render( )
|
||||
{
|
||||
return HTML::make( 'a' )
|
||||
->setAttribute( 'title' , HTML::from( $this->title ) )
|
||||
->setAttribute( 'href' , $this->URL )
|
||||
->setAttribute( 'class' ,
|
||||
'box-button' . ( ( $this->class === null )
|
||||
? '' : ( ' ' . $this->class ) ) )
|
||||
->setAttribute( 'style' , $this->style )
|
||||
->setAttribute( 'id' , $this->id )
|
||||
->appendElement( HTML::make( 'span' )
|
||||
->appendText( $this->title ) );
|
||||
}
|
||||
|
||||
|
||||
public static function create( $title , $URL )
|
||||
{
|
||||
return new BoxButton( $title , $URL );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class View_Box
|
||||
implements View
|
||||
{
|
||||
protected $title;
|
||||
protected $class;
|
||||
protected $id;
|
||||
|
||||
protected $buttons = array( );
|
||||
protected $contents;
|
||||
|
||||
|
||||
public function __construct( $title , View $contents )
|
||||
{
|
||||
$this->title = $title;
|
||||
$this->contents = $contents;
|
||||
}
|
||||
|
||||
|
||||
public function setClass( $class )
|
||||
{
|
||||
$this->class = $class;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function setID( $id )
|
||||
{
|
||||
$this->id = $id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function addButton( BoxButton $button )
|
||||
{
|
||||
array_push( $this->buttons , $button );
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function render( )
|
||||
{
|
||||
$box = HTML::make( 'div' )
|
||||
->setAttribute( 'class' , 'box' . ( is_null( $this->class ) ? '' : " {$this->class}" ) )
|
||||
->setAttribute( 'id' , $this->id )
|
||||
->appendText( "\n" );
|
||||
|
||||
if ( ! is_null( $this->title ) ) {
|
||||
$box->appendElement( HTML::make( 'h2' )
|
||||
->setAttribute( 'class' , 'box-title' )
|
||||
->appendText( $this->title ) );
|
||||
}
|
||||
|
||||
if ( ! empty( $this->buttons )) {
|
||||
$buttons = HTML::make( 'div' )
|
||||
->setAttribute( 'class' , 'box-buttons' );
|
||||
foreach ( $this->buttons as $button ) {
|
||||
$buttons->appendElement( $button->render( ) );
|
||||
}
|
||||
$box->appendElement( $buttons );
|
||||
}
|
||||
|
||||
$box->appendElement( HTML::make( 'div' )
|
||||
->setAttribute( 'class' , 'box-contents' )
|
||||
->append( $this->contents->render( ) ) );
|
||||
|
||||
return $box;
|
||||
}
|
||||
}
|
7
includes/config-sample.inc.php
Normal file
7
includes/config-sample.inc.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
$config[ 'core' ][ 'db' ][ 'name' ] = '...';
|
||||
$config[ 'core' ][ 'db' ][ 'user' ] = '...';
|
||||
$config[ 'core' ][ 'db' ][ 'password' ] = '...';
|
||||
|
||||
$config[ 'core' ][ 'pages' ][ 'baseTitle' ] = '...';
|
44
includes/core/controller.inc.php
Normal file
44
includes/core/controller.inc.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
class ParameterException extends Exception { }
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
|
||||
protected final function getParameter( $name , $method = null )
|
||||
{
|
||||
if ( $method === null ) {
|
||||
try {
|
||||
return $this->getParameter( $name , 'POST' );
|
||||
} catch ( ParameterException $e ) {
|
||||
return $this->getParameter( $name , 'GET' );
|
||||
}
|
||||
}
|
||||
|
||||
$from = '_' . $method;
|
||||
global $$from;
|
||||
if ( ! array_key_exists( $name , $$from ) ) {
|
||||
throw new ParameterException( "$name/$method" );
|
||||
}
|
||||
return ${$from}[ $name ];
|
||||
}
|
||||
|
||||
public abstract function handle( Page $page );
|
||||
}
|
||||
|
||||
|
||||
final class Ctrl_Simple
|
||||
extends Controller
|
||||
{
|
||||
private $view;
|
||||
|
||||
public function __construct( View $view )
|
||||
{
|
||||
$this->view = $view;
|
||||
}
|
||||
|
||||
public function handle( Page $page )
|
||||
{
|
||||
return $this->view;
|
||||
}
|
||||
}
|
20
includes/core/dao.inc.php
Normal file
20
includes/core/dao.inc.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
abstract class DAO
|
||||
{
|
||||
private $database;
|
||||
|
||||
public final function setDatabase( Database $database )
|
||||
{
|
||||
if ( $this->database !== null ) {
|
||||
throw new Exception( "trying to change DAO database" );
|
||||
}
|
||||
$this->database = $database;
|
||||
}
|
||||
|
||||
protected final function query( $query , $prepare = false )
|
||||
{
|
||||
return $this->database->query( $query , $prepare );
|
||||
}
|
||||
}
|
||||
|
133
includes/core/database.inc.php
Normal file
133
includes/core/database.inc.php
Normal file
|
@ -0,0 +1,133 @@
|
|||
<?php
|
||||
|
||||
class DatabaseError extends RuntimeException { }
|
||||
|
||||
final class Database
|
||||
implements PackageAware
|
||||
{
|
||||
|
||||
private $connection;
|
||||
private $package;
|
||||
private $queries = array( );
|
||||
|
||||
public function setPackage( Package $package )
|
||||
{
|
||||
if ( $this->package !== null ) {
|
||||
throw new Exception( 'trying to call setPackage() twice' );
|
||||
}
|
||||
$this->package = $package;
|
||||
}
|
||||
|
||||
|
||||
public function query( $query , $prepare = false )
|
||||
{
|
||||
if ( ! $this->connection ) {
|
||||
$this->connect( );
|
||||
}
|
||||
if ( ! array_key_exists( $query , $this->queries )
|
||||
|| ( $prepare && ! $this->queries[ $query ]->prepared( ) ) ) {
|
||||
$this->queries[ $query ] = new DBQuery( $this->connection , $query , $prepare );
|
||||
}
|
||||
return $this->queries[ $query ];
|
||||
}
|
||||
|
||||
public function commit( )
|
||||
{
|
||||
if ( ! $this->connection ) {
|
||||
return;
|
||||
}
|
||||
if ( ! @pg_query( 'COMMIT' ) ) {
|
||||
throw new DatabaseError( 'COMMIT: ' . pg_last_error( ) );
|
||||
}
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
public function connect( )
|
||||
{
|
||||
$host = $this->package->config( 'db/host' , 'localhost' );
|
||||
$port = $this->package->config( 'db/port' );
|
||||
$name = $this->package->config( 'db/name' , null , true );
|
||||
$user = $this->package->config( 'db/user' );
|
||||
$pass = $this->package->config( 'db/password' );
|
||||
|
||||
$cString = array( );
|
||||
$cString[] = "host=$host";
|
||||
if ( $port !== null ) {
|
||||
$cString[] = "port=$port";
|
||||
}
|
||||
$cString[] = "dbname=$name";
|
||||
if ( $user !== null ) {
|
||||
$cString[] = "user=$user";
|
||||
}
|
||||
if ( $pass !== null ) {
|
||||
$cString[] = "password=$pass";
|
||||
}
|
||||
|
||||
$this->connection = pg_connect( join( ' ' , $cString ) );
|
||||
if ( ! $this->connection ) {
|
||||
throw new DatabaseError( 'connection failed' );
|
||||
}
|
||||
|
||||
if ( ! @pg_query( $this->connection , 'BEGIN TRANSACTION' ) ) {
|
||||
throw new DatabaseError( 'BEGIN TRANSACTION: ' . pg_last_error( ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class DBQuery
|
||||
{
|
||||
private static $lastStatementID = 0;
|
||||
|
||||
private $connection;
|
||||
private $query;
|
||||
private $statement;
|
||||
|
||||
|
||||
public function __construct( $connection , $query , $prepare = false )
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->query = $query;
|
||||
if ( $prepare ) {
|
||||
$this->statement = 'prep_stmt_' . ( ++ DBQuery::$lastStatementID );
|
||||
if ( ! pg_prepare( $connection , $this->statement , $query ) ) {
|
||||
throw new Exception( "unable to prepare statement '$query': " . pg_last_error( ) );
|
||||
}
|
||||
} else {
|
||||
$this->statement = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct( )
|
||||
{
|
||||
if ( $this->statement !== null ) {
|
||||
if ( ! pg_query( 'DEALLOCATE ' . $this->statement ) ) {
|
||||
throw new Exception( "unable to deallocate statement: " . pg_last_error( ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function execute( )
|
||||
{
|
||||
$arguments = func_get_args( );
|
||||
$result = array( );
|
||||
|
||||
if ( $this->statement !== null ) {
|
||||
$pgResult = pg_execute( $this->connection , $this->statement , $arguments );
|
||||
} else {
|
||||
$pgResult = pg_query_params( $this->connection , $this->query , $arguments );
|
||||
}
|
||||
|
||||
if ( ! $pgResult ) {
|
||||
throw new Exception( "query \"{$this->query}\" failed: " . pg_last_error( ) );
|
||||
}
|
||||
|
||||
while ( $row = pg_fetch_object( $pgResult ) ) {
|
||||
array_push( $result , $row );
|
||||
}
|
||||
pg_free_result( $pgResult );
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
26
includes/core/package.inc.php
Normal file
26
includes/core/package.inc.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
$package[ 'extras' ][] = 'Controller';
|
||||
$package[ 'extras' ][] = 'DAO';
|
||||
$package[ 'extras' ][] = 'DatabaseError';
|
||||
$package[ 'extras' ][] = 'DBQuery';
|
||||
$package[ 'extras' ][] = 'HTML';
|
||||
$package[ 'extras' ][] = 'HTMLPage';
|
||||
$package[ 'extras' ][] = 'Page';
|
||||
$package[ 'extras' ][] = 'ParameterException';
|
||||
$package[ 'extras' ][] = 'URLMapper';
|
||||
$package[ 'extras' ][] = 'View';
|
||||
|
||||
$package[ 'files' ][] = 'controller';
|
||||
$package[ 'files' ][] = 'dao';
|
||||
$package[ 'files' ][] = 'database';
|
||||
$package[ 'files' ][] = 'page';
|
||||
$package[ 'files' ][] = 'urls';
|
||||
$package[ 'files' ][] = 'view';
|
||||
|
||||
$package[ 'pages' ][] = 'basic';
|
||||
|
||||
$package[ 'ctrls' ][] = 'session';
|
||||
$package[ 'ctrls' ][] = 'simple';
|
||||
|
||||
$package[ 'singletons' ][] = 'Database';
|
208
includes/core/page.inc.php
Normal file
208
includes/core/page.inc.php
Normal file
|
@ -0,0 +1,208 @@
|
|||
<?php
|
||||
|
||||
|
||||
abstract class Page
|
||||
{
|
||||
private $baseURL;
|
||||
private $controllers = array( );
|
||||
protected $views = array( );
|
||||
|
||||
|
||||
public function __construct( )
|
||||
{
|
||||
$this->baseURL = dirname( $_SERVER[ 'SCRIPT_NAME' ] );
|
||||
}
|
||||
|
||||
public final function addController( Controller $controller )
|
||||
{
|
||||
array_push( $this->controllers , $controller );
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public final function addView( View $view )
|
||||
{
|
||||
array_push( $this->controllers , new Ctrl_Simple( $view ) );
|
||||
return $this;
|
||||
}
|
||||
|
||||
public final function getBaseURL( )
|
||||
{
|
||||
return $this->baseURL;
|
||||
}
|
||||
|
||||
|
||||
protected abstract function render( );
|
||||
|
||||
protected function handleControllerValue( $rc )
|
||||
{
|
||||
$rv = false;
|
||||
if ( is_a( $rc , 'View' ) ) {
|
||||
array_push( $this->views , $rc );
|
||||
} elseif ( is_a( $rc , 'Controller' ) ) {
|
||||
$rv = $this->executeController( $rc );
|
||||
} elseif ( is_array( $rc ) ) {
|
||||
foreach ( $rc as $rcItem ) {
|
||||
if ( $this->handleControllerValue( $rcItem ) ) {
|
||||
$rv = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} elseif ( ! is_null( $rc ) ) {
|
||||
header( "Location: $rc" );
|
||||
$rv = true;
|
||||
}
|
||||
return $rv;
|
||||
}
|
||||
|
||||
protected function executeController( Controller $controller )
|
||||
{
|
||||
return $this->handleControllerValue( $controller->handle( $this ) );
|
||||
}
|
||||
|
||||
public final function handle( )
|
||||
{
|
||||
$mustDraw = true;
|
||||
foreach ( $this->controllers as $controller ) {
|
||||
if ( $this->executeController( $controller ) ) {
|
||||
$mustDraw = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( $mustDraw ) {
|
||||
$this->render( );
|
||||
}
|
||||
Loader::Singleton( 'Database' )->commit( );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
interface PathAware
|
||||
{
|
||||
|
||||
public function setExtraPath( $path );
|
||||
|
||||
}
|
||||
|
||||
|
||||
abstract class HTMLPage
|
||||
extends Page
|
||||
implements PackageAware
|
||||
{
|
||||
protected $title;
|
||||
protected $package;
|
||||
|
||||
public function __construct( )
|
||||
{
|
||||
parent::__construct( );
|
||||
}
|
||||
|
||||
public function setPackage( Package $package )
|
||||
{
|
||||
if ( $this->package !== null ) {
|
||||
throw new Exception( 'trying to call setPackage() twice' );
|
||||
}
|
||||
$this->package = $package;
|
||||
}
|
||||
|
||||
protected abstract function getMenu( );
|
||||
|
||||
|
||||
private function renderMenu( )
|
||||
{
|
||||
$menu = $this->getMenu( );
|
||||
if ( empty( $menu ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$html = HTML::make( 'ul' )
|
||||
->setAttribute( 'class' , 'page-menu' );
|
||||
|
||||
foreach ( $menu as $link => $title ) {
|
||||
$html->appendElement( HTML::make( 'li' )
|
||||
->appendElement( HTML::make( 'a' )
|
||||
->setAttribute( 'href' , $link )
|
||||
->setAttribute( 'title' , HTML::from( $title ) )
|
||||
->appendText( $title ) ) );
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
|
||||
public function setTitle( $title )
|
||||
{
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
protected function getHead( $title )
|
||||
{
|
||||
return HTML::make( 'head' )
|
||||
->appendElement( HTML::make( 'title' )
|
||||
->appendText( $title ) )
|
||||
->appendElement( HTML::make( 'meta' )
|
||||
->setAttribute( 'http-equiv' , 'content-type' )
|
||||
->setAttribute( 'content' , 'text/html;charset=UTF-8' ) )
|
||||
->appendElement( HTML::make( 'style' )
|
||||
->setAttribute( 'id' , 'main-style' )
|
||||
->setAttribute( 'class' , 'css-style' )
|
||||
->appendText( "\n@import url('" . $this->getBaseURL( )
|
||||
. "/style.css?1');\n" ) );
|
||||
}
|
||||
|
||||
|
||||
protected function getBody( $title )
|
||||
{
|
||||
$menu = $this->renderMenu( );
|
||||
$container = HTML::make( 'div' )
|
||||
->setAttribute( 'class' , 'page-container' );
|
||||
|
||||
$t = HTML::make( 'h1' )->appendText( $title );
|
||||
if ( is_null( $menu ) ) {
|
||||
$t->setAttribute( 'class' , 'no-menu' );
|
||||
}
|
||||
$container->appendElement( $t );
|
||||
|
||||
if ( !is_null( $menu ) ) {
|
||||
$container->append( $menu );
|
||||
}
|
||||
|
||||
foreach ( $this->views as $view ) {
|
||||
$container->append( $view->render( ) );
|
||||
}
|
||||
|
||||
return HTML::make( 'body' )->appendElement( $container );
|
||||
}
|
||||
|
||||
|
||||
public function render( )
|
||||
{
|
||||
$baseTitle = $this->package->config( 'pages/baseTitle' , null , false );
|
||||
if ( $baseTitle === null ) {
|
||||
$baseTitle = Loader::PackageConfig( 'core' )->get( 'pages/baseTitle' , '' , true );
|
||||
}
|
||||
$title = is_null( $this->title ) ? '' : ( ' - ' . $this->title );
|
||||
$pTitle = is_null( $this->title ) ? $baseTitle : $this->title;
|
||||
$title = $baseTitle . $title;
|
||||
|
||||
header( 'Content-type: text/html; charset=utf-8' );
|
||||
echo HTML::make( 'html' )
|
||||
->appendElement( $this->getHead( $title ) )
|
||||
->appendElement( $this->getBody( $pTitle ) )
|
||||
->getCode( );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Page_Basic
|
||||
extends HTMLPage
|
||||
{
|
||||
|
||||
protected function getMenu( )
|
||||
{
|
||||
return array( );
|
||||
}
|
||||
|
||||
}
|
103
includes/core/urls.inc.php
Normal file
103
includes/core/urls.inc.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
|
||||
final class URLMapperException extends Exception {}
|
||||
|
||||
|
||||
final class URLMapper
|
||||
implements PackageAware
|
||||
{
|
||||
private $package;
|
||||
private $prefix;
|
||||
private $configBase;
|
||||
|
||||
public function __construct( $prefix = null )
|
||||
{
|
||||
$this->prefix = $prefix;
|
||||
$this->configBase = ( $prefix == null ) ? 'urls' : $prefix;
|
||||
}
|
||||
|
||||
public function setPackage( Package $package )
|
||||
{
|
||||
if ( $this->package !== null ) {
|
||||
throw new Exception( 'trying to call setPackage() twice' );
|
||||
}
|
||||
$this->package = $package;
|
||||
}
|
||||
|
||||
|
||||
public function fromPathInfo( )
|
||||
{
|
||||
if ( array_key_exists( 'PATH_INFO' , $_SERVER ) ) {
|
||||
$path = $_SERVER[ 'PATH_INFO' ];
|
||||
} else {
|
||||
$path = '/' . $this->package->config( $this->configBase . '/default' , 'home' );
|
||||
}
|
||||
|
||||
$this->fromPath( $path );
|
||||
}
|
||||
|
||||
public function fromPath( $path )
|
||||
{
|
||||
if ( ! preg_match( '/^(\/[a-z0-9]+)+$/' , $path ) ) {
|
||||
$this->showPageNotFound( );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->prefix == null ) {
|
||||
$path = substr( $path , 1 );
|
||||
} else {
|
||||
$path = $this->prefix . $path;
|
||||
}
|
||||
try {
|
||||
$this->showPageFor( $path );
|
||||
} catch ( URLMapperException $e ) {
|
||||
$this->showPageNotFound( $path );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function showPageNotFound( $requestPath )
|
||||
{
|
||||
$path = $this->package->config( $this->configBase . '/errors/404', 'errors/404' );
|
||||
$this->showPageFor( $path . '/' . $requestPath , false );
|
||||
}
|
||||
|
||||
|
||||
private function showPageFor( $path )
|
||||
{
|
||||
$split = split( '/' , $path );
|
||||
$extras = array( );
|
||||
while ( !empty( $split ) ) {
|
||||
$name = join( '_' , $split );
|
||||
try {
|
||||
$page = Loader::Page( $name );
|
||||
break;
|
||||
} catch ( LoaderException $e ) {
|
||||
array_unshift( $extras , array_pop( $split ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $split ) ) {
|
||||
throw new URLMapperException( $path );
|
||||
}
|
||||
|
||||
$this->handlePage( $path , $page , join( '/' , $extras ) );
|
||||
}
|
||||
|
||||
|
||||
private function handlePage( $requestPath , Page $page , $extraPath = '' , $pathFailure = true )
|
||||
{
|
||||
if ( $page instanceof PathAware ) {
|
||||
$success = $page->setExtraPath( $extraPath );
|
||||
} else {
|
||||
$success = ( $extraPath === '' );
|
||||
}
|
||||
|
||||
if ( $pathFailure && !$success ) {
|
||||
$this->showPageNotFound( $requestPath );
|
||||
} else {
|
||||
$page->handle( );
|
||||
}
|
||||
}
|
||||
}
|
117
includes/core/view.inc.php
Normal file
117
includes/core/view.inc.php
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
interface View
|
||||
{
|
||||
public function render( );
|
||||
}
|
||||
|
||||
|
||||
final class HTML
|
||||
{
|
||||
protected $tag;
|
||||
protected $attributes = array( );
|
||||
protected $contents = array( );
|
||||
protected $cached;
|
||||
|
||||
|
||||
public function __construct( $tag )
|
||||
{
|
||||
$this->tag = $tag;
|
||||
}
|
||||
|
||||
|
||||
public function setAttribute( $attribute , $value )
|
||||
{
|
||||
if ( $value !== null ) {
|
||||
$this->attributes[ $attribute ] = $value;
|
||||
$this->cached = null;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function appendText( $text )
|
||||
{
|
||||
assert( is_scalar( $text ) );
|
||||
array_push( $this->contents , HTML::from( $text ) );
|
||||
$this->cached = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function appendElement( HTML $element )
|
||||
{
|
||||
array_push( $this->contents , $element );
|
||||
$this->cached = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function appendRaw( $text )
|
||||
{
|
||||
assert( is_scalar( $text ) );
|
||||
array_push( $this->contents , $text );
|
||||
$this->cached = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function append( $auto )
|
||||
{
|
||||
if ( is_array( $auto ) ) {
|
||||
foreach ( $auto as $element ) {
|
||||
$this->append( $element );
|
||||
}
|
||||
} elseif ( is_scalar( $auto ) ) {
|
||||
$this->appendRaw( $auto );
|
||||
} else {
|
||||
$this->appendElement( $auto );
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getCode( )
|
||||
{
|
||||
if ( $this->cached !== null ) {
|
||||
return $this->cached;
|
||||
}
|
||||
$code = '<' . $this->tag;
|
||||
|
||||
if ( ! empty( $this->attributes ) ) {
|
||||
$attrs = array( );
|
||||
foreach ( $this->attributes as $name => $value ) {
|
||||
array_push( $attrs , $name . '="' . $value . '"' );
|
||||
}
|
||||
$code .= ' ' . join( ' ' , $attrs );
|
||||
}
|
||||
|
||||
if ( empty( $this->contents ) ) {
|
||||
$code .= ' />';
|
||||
} else {
|
||||
$code .= '>';
|
||||
foreach ( $this->contents as $item ) {
|
||||
if ( is_scalar( $item ) ) {
|
||||
$code .= $item;
|
||||
} else {
|
||||
$code .= $item->getCode( );
|
||||
}
|
||||
}
|
||||
$code .= '</' . $this->tag . '>';
|
||||
}
|
||||
|
||||
return ( $this->cached = $code );
|
||||
}
|
||||
|
||||
|
||||
public static function make( $tag )
|
||||
{
|
||||
return new HTML( $tag );
|
||||
}
|
||||
|
||||
|
||||
public static function from( $text )
|
||||
{
|
||||
return htmlentities( $text , ENT_COMPAT , 'UTF-8' );
|
||||
}
|
||||
}
|
73
includes/form/ctrl.inc.php
Normal file
73
includes/form/ctrl.inc.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
class Ctrl_Form
|
||||
extends Controller
|
||||
{
|
||||
protected $form;
|
||||
|
||||
public function __construct( Form $form )
|
||||
{
|
||||
$this->form = $form;
|
||||
}
|
||||
|
||||
|
||||
protected function getValues( )
|
||||
{
|
||||
$success = true;
|
||||
|
||||
foreach ( $this->form->fields( ) as $field ) {
|
||||
if ( $field === null ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$value = $this->getParameter( $field->name( ) , $this->form->method( ) );
|
||||
} catch ( ParameterException $e ) {
|
||||
$value = null;
|
||||
}
|
||||
$field->setFormValue( $value );
|
||||
$vResult = $field->validate( );
|
||||
$success = $success && $vResult;
|
||||
}
|
||||
|
||||
return $success;
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected function applyFormControllers( $page )
|
||||
{
|
||||
foreach ( $this->form->controllers( ) as $controller ) {
|
||||
$result = $controller->handle( $page );
|
||||
if ( $result === null ) {
|
||||
continue;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public function handle( Page $page )
|
||||
{
|
||||
try {
|
||||
$this->getParameter( $this->form->name( ) . '-submit' );
|
||||
} catch ( ParameterException $e ) {
|
||||
return $this->form->view( );
|
||||
}
|
||||
|
||||
if ( ! $this->getValues( ) ) {
|
||||
return $this->form->view( );
|
||||
}
|
||||
|
||||
$cResult = $this->applyFormControllers( $page );
|
||||
if ( $cResult === null ) {
|
||||
return $this->form->view( );
|
||||
}
|
||||
if ( $cResult ) {
|
||||
return $this->form->successURL( );
|
||||
}
|
||||
return $this->form->cancelURL( );
|
||||
}
|
||||
|
||||
}
|
164
includes/form/field.inc.php
Normal file
164
includes/form/field.inc.php
Normal file
|
@ -0,0 +1,164 @@
|
|||
<?php
|
||||
|
||||
interface FieldValidator
|
||||
{
|
||||
public function validate( $value );
|
||||
}
|
||||
|
||||
|
||||
interface FieldModifier
|
||||
{
|
||||
public function replace( $value );
|
||||
}
|
||||
|
||||
|
||||
final class Field
|
||||
{
|
||||
const MissingError = 'Mandatory';
|
||||
|
||||
private $name;
|
||||
|
||||
private $type;
|
||||
private $options = array( );
|
||||
|
||||
private $description;
|
||||
private $mandatory = true;
|
||||
|
||||
private $valueDefault;
|
||||
private $valueForm;
|
||||
|
||||
private $validator;
|
||||
private $modifier;
|
||||
|
||||
private $errors = array( );
|
||||
|
||||
|
||||
public function __construct( $name , $type )
|
||||
{
|
||||
$this->type = $type;
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
|
||||
public function addOption( $value , $text , $disabled = false )
|
||||
{
|
||||
assert( $this->type === 'select' );
|
||||
assert( ! array_key_exists( $value, $this->options ) );
|
||||
$obj = new stdClass( );
|
||||
$obj->text = $text;
|
||||
$obj->disabled = $disabled;
|
||||
$this->options[ $value ] = $obj;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function options( )
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
|
||||
public function name( )
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function type( )
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
|
||||
public function setDescription( $description )
|
||||
{
|
||||
$this->description = $description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function description( )
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
|
||||
public function setMandatory( $mandatory )
|
||||
{
|
||||
$this->mandatory = $mandatory;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function mandatory( )
|
||||
{
|
||||
return $this->mandatory;
|
||||
}
|
||||
|
||||
|
||||
public function setModifier( FieldModifier $modifier )
|
||||
{
|
||||
$this->modifier = $modifier;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function setValidator( FieldValidator $validator )
|
||||
{
|
||||
$this->validator = $validator;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function setDefaultValue( $default )
|
||||
{
|
||||
$this->valueDefault = $default;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setFormValue( $form )
|
||||
{
|
||||
if ( $this->modifier !== null ) {
|
||||
$form = $this->modifier->replace( $form );
|
||||
}
|
||||
$this->valueForm = $form;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function value( )
|
||||
{
|
||||
return is_null( $this->valueForm )
|
||||
? $this->valueDefault
|
||||
: $this->valueForm;
|
||||
}
|
||||
|
||||
|
||||
public function putError( $error )
|
||||
{
|
||||
$this->errors[ $error ] = 1;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function errors( )
|
||||
{
|
||||
return array_keys( $this->errors );
|
||||
}
|
||||
|
||||
|
||||
public function validate( )
|
||||
{
|
||||
$value = $this->value( );
|
||||
if ( $this->mandatory && ( $value === null || $value == '' ) ) {
|
||||
$this->putError( Loader::Text( Field::MissingError ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $this->validator !== null ) {
|
||||
$errors = $this->validator->validate( $value );
|
||||
if ( is_array( $errors ) ) {
|
||||
foreach ( $errors as $error ) {
|
||||
$this->putError( $error );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
168
includes/form/form.inc.php
Normal file
168
includes/form/form.inc.php
Normal file
|
@ -0,0 +1,168 @@
|
|||
<?php
|
||||
|
||||
|
||||
final class Form
|
||||
{
|
||||
|
||||
private $buttonTitle;
|
||||
private $name;
|
||||
private $title;
|
||||
|
||||
private $action;
|
||||
private $method;
|
||||
|
||||
private $cancelURL;
|
||||
private $successURL;
|
||||
|
||||
private $fields = array( );
|
||||
|
||||
private $controllers = array( );
|
||||
|
||||
|
||||
public function __construct( $buttonTitle , $name , $title = null )
|
||||
{
|
||||
$this->buttonTitle = $buttonTitle;
|
||||
$this->title = $title;
|
||||
$this->name = is_null( $name ) ? 'the-form' : $name;
|
||||
$this->action = '?';
|
||||
$this->method = 'POST';
|
||||
}
|
||||
|
||||
|
||||
public function buttonTitle( )
|
||||
{
|
||||
return $this->buttonTitle;
|
||||
}
|
||||
|
||||
public function name( )
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function title( )
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function action( )
|
||||
{
|
||||
return $this->action;
|
||||
}
|
||||
|
||||
public function method( )
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
public function cancelURL( )
|
||||
{
|
||||
return $this->cancelURL;
|
||||
}
|
||||
|
||||
public function successURL( )
|
||||
{
|
||||
return $this->successURL;
|
||||
}
|
||||
|
||||
public function fields( )
|
||||
{
|
||||
return array_values( $this->fields );
|
||||
}
|
||||
|
||||
public function field( $name )
|
||||
{
|
||||
assert( array_key_exists( $name , $this->fields ) );
|
||||
return $this->fields[ $name ];
|
||||
}
|
||||
|
||||
public function controllers( )
|
||||
{
|
||||
return $this->controllers;
|
||||
}
|
||||
|
||||
|
||||
public function setAction( $action )
|
||||
{
|
||||
$this->action = $action;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMethod( $method )
|
||||
{
|
||||
$this->method = $method;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setURL( $url )
|
||||
{
|
||||
$this->cancelURL = $this->successURL = $url;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCancelURL( $url )
|
||||
{
|
||||
$this->cancelURL = $url;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setSuccessURL( $url )
|
||||
{
|
||||
$this->successURL = $url;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function addField( Field $field )
|
||||
{
|
||||
assert( ! array_key_exists( $field->name( ) , $this->fields ) );
|
||||
$this->fields[ $field->name( ) ] = $field;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function addSeparator( )
|
||||
{
|
||||
$i = 0;
|
||||
while ( array_key_exists( "sep$i" , $this->fields ) ) {
|
||||
$i ++;
|
||||
}
|
||||
$this->fields[ "sep$i" ] = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function addController( Controller $controller )
|
||||
{
|
||||
if ( is_a( $controller , 'FormAware' ) ) {
|
||||
$controller->setForm( $this );
|
||||
}
|
||||
array_push( $this->controllers , $controller );
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function controller( )
|
||||
{
|
||||
return Loader::Ctrl( 'form' , $this );
|
||||
}
|
||||
|
||||
|
||||
public function view( )
|
||||
{
|
||||
$box = Loader::View( 'box' , $this->title , Loader::View( 'form' , $this ) )
|
||||
->setClass( 'form' );
|
||||
|
||||
if ( $this->cancelURL !== null ) {
|
||||
$box->addButton( BoxButton::create( Loader::Text( 'Cancel' ) , $this->cancelURL )
|
||||
->setClass( 'form-cancel' ) );
|
||||
}
|
||||
|
||||
return $box;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
interface FormAware
|
||||
{
|
||||
public function setForm( Form $form );
|
||||
}
|
26
includes/form/modifiers.inc.php
Normal file
26
includes/form/modifiers.inc.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
class Modifier_TrimString
|
||||
implements FieldModifier
|
||||
{
|
||||
private $removeDuplicateSpaces;
|
||||
|
||||
|
||||
public function __construct( $removeDuplicateSpaces = true )
|
||||
{
|
||||
$this->removeDuplicateSpaces = $removeDuplicateSpaces;
|
||||
}
|
||||
|
||||
|
||||
public function replace( $value )
|
||||
{
|
||||
if ( $value === null ) {
|
||||
return '';
|
||||
}
|
||||
$value = trim( $value );
|
||||
if ( $this->removeDuplicateSpaces ) {
|
||||
$value = preg_replace( '/\s\s+/' , ' ' , $value );
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
24
includes/form/package.inc.php
Normal file
24
includes/form/package.inc.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
$package[ 'requires' ][] = 'box';
|
||||
|
||||
$package[ 'files' ][] = 'field';
|
||||
$package[ 'files' ][] = 'form';
|
||||
$package[ 'files' ][] = 'view';
|
||||
$package[ 'files' ][] = 'ctrl';
|
||||
$package[ 'files' ][] = 'modifiers';
|
||||
$package[ 'files' ][] = 'validators';
|
||||
|
||||
$package[ 'views' ][] = 'form';
|
||||
$package[ 'ctrls' ][] = 'form';
|
||||
|
||||
$package[ 'extras' ][] = 'Field';
|
||||
$package[ 'extras' ][] = 'FieldModifier';
|
||||
$package[ 'extras' ][] = 'FieldValidator';
|
||||
$package[ 'extras' ][] = 'Form';
|
||||
$package[ 'extras' ][] = 'FormAware';
|
||||
|
||||
$package[ 'extras' ][] = 'Modifier_TrimString';
|
||||
$package[ 'extras' ][] = 'Validator_StringLength';
|
||||
$package[ 'extras' ][] = 'Validator_InArray';
|
||||
$package[ 'extras' ][] = 'Validator_IntValue';
|
99
includes/form/validators.inc.php
Normal file
99
includes/form/validators.inc.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
|
||||
class Validator_StringLength
|
||||
implements FieldValidator
|
||||
{
|
||||
protected $errorPrefix;
|
||||
protected $minLength;
|
||||
protected $maxLength;
|
||||
|
||||
public function __construct( $errorPrefix , $minLength = 0 , $maxLength = null )
|
||||
{
|
||||
assert( $maxLength === null || $maxLength >= $minLength );
|
||||
$this->errorPrefix = $errorPrefix;
|
||||
$this->minLength = $minLength;
|
||||
$this->maxLength = $maxLength;
|
||||
}
|
||||
|
||||
|
||||
public function validate( $value )
|
||||
{
|
||||
$len = strlen( $value );
|
||||
if ( $len < $this->minLength ) {
|
||||
$template = Loader::Text( '%1$s is too short (min. %2$d characters)' );
|
||||
return array( sprintf( $template , $this->errorPrefix , $this->minLength ) );
|
||||
}
|
||||
if ( $this->maxLength !== null && $len > $this->maxLength ) {
|
||||
$template = Loader::Text( '%1$s is too long (max. %2$d characters)' );
|
||||
return array( sprintf( $template , $this->errorPrefix , $this->maxLength ) );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Validator_InArray
|
||||
implements FieldValidator
|
||||
{
|
||||
private $values;
|
||||
private $errorText;
|
||||
|
||||
public function __construct( array $values , $errorText )
|
||||
{
|
||||
$this->values = $values;
|
||||
$this->errorText = $errorText;
|
||||
}
|
||||
|
||||
public function validate( $value )
|
||||
{
|
||||
if ( ! ( empty( $this->values ) || in_array( $value , $this->values ) ) ) {
|
||||
return array( $this->errorText );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Validator_IntValue
|
||||
implements FieldValidator
|
||||
{
|
||||
|
||||
private $invalidText;
|
||||
private $minValue;
|
||||
private $minError;
|
||||
private $maxValue;
|
||||
private $maxError;
|
||||
|
||||
public function __construct( $invalidText )
|
||||
{
|
||||
$this->invalidText = $invalidText;
|
||||
}
|
||||
|
||||
public function setMinValue( $minValue , $minError = null )
|
||||
{
|
||||
assert( $this->maxValue === null || $minValue <= $this->maxValue );
|
||||
$this->minValue = $minValue;
|
||||
$this->minError = ( $minError === null ) ? $this->invalidText : $minError;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMaxValue( $maxValue , $maxError = null )
|
||||
{
|
||||
assert( $this->minValue === null || $maxValue >= $this->maxValue );
|
||||
$this->maxValue = $maxValue;
|
||||
$this->maxError = ( $maxError === null ) ? $this->invalidText : $maxError;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function validate( $value )
|
||||
{
|
||||
if ( !is_scalar( $value ) || (int) $value != $value ) {
|
||||
return array( $this->invalidText );
|
||||
} else if ( $this->minValue !== null && $value < $this->minValue ) {
|
||||
return array( $this->minError );
|
||||
} else if ( $this->maxValue !== null && $value > $this->maxValue ) {
|
||||
return array( $this->maxError );
|
||||
}
|
||||
}
|
||||
}
|
184
includes/form/view.inc.php
Normal file
184
includes/form/view.inc.php
Normal file
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
|
||||
class View_Form
|
||||
implements View
|
||||
{
|
||||
protected $form;
|
||||
|
||||
public function __construct( Form $form )
|
||||
{
|
||||
$this->form = $form;
|
||||
}
|
||||
|
||||
|
||||
protected function renderHiddenFields( $prefix )
|
||||
{
|
||||
$result = array( );
|
||||
foreach ( $this->form->fields( ) as $field ) {
|
||||
if ( $field === null || $field->type( ) !== 'hidden' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
array_push( $result , HTML::make( 'input' )
|
||||
->setAttribute( 'type' , 'hidden' )
|
||||
->setAttribute( 'name' , $field->name( ) )
|
||||
->setAttribute( 'value' , HTML::from( $field->value( ) ) )
|
||||
->setAttribute( 'id' , $prefix . $field->name( ) ) );
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
protected function renderPasswordField( $field , $prefix )
|
||||
{
|
||||
return HTML::make( 'input' )
|
||||
->setAttribute( 'type' , 'password' )
|
||||
->setAttribute( 'name' , $field->name( ) )
|
||||
->setAttribute( 'id' , $prefix . 'field' )
|
||||
->setAttribute( 'class' , 'form-text-field' );
|
||||
}
|
||||
|
||||
|
||||
protected function renderTextField( $field , $prefix )
|
||||
{
|
||||
return HTML::make( 'input' )
|
||||
->setAttribute( 'type' , 'text' )
|
||||
->setAttribute( 'name' , $field->name( ) )
|
||||
->setAttribute( 'id' , $prefix . 'field' )
|
||||
->setAttribute( 'class' , 'form-text-field' )
|
||||
->setAttribute( 'value' , HTML::from( $field->value( ) ) );
|
||||
}
|
||||
|
||||
|
||||
protected function renderTextArea( $field , $prefix )
|
||||
{
|
||||
return HTML::make( 'textarea' )
|
||||
->setAttribute( 'name' , $field->name( ) )
|
||||
->setAttribute( 'id' , $prefix . 'field' )
|
||||
->setAttribute( 'class' , 'form-text-field' )
|
||||
->appendText( (string) $field->value( ) );
|
||||
}
|
||||
|
||||
|
||||
protected function renderSelectField( $field , $prefix )
|
||||
{
|
||||
$select = HTML::make( 'select' )
|
||||
->setAttribute( 'name' , $field->name( ) )
|
||||
->setAttribute( 'id' , $prefix . 'field' )
|
||||
->setAttribute( 'class' , 'form-select' );
|
||||
|
||||
$selected = $field->value( );
|
||||
foreach ( $field->options( ) as $value => $obj ) {
|
||||
$option = HTML::make( 'option' )
|
||||
->setAttribute( 'value' , $value )
|
||||
->setAttribute( 'disabled' , $obj->disabled ? 'disabled' : null )
|
||||
->appendText( $obj->text );
|
||||
if ( "$value" === "$selected" ) {
|
||||
$option->setAttribute( 'selected' , 'selected' );
|
||||
}
|
||||
$select->appendElement( $option );
|
||||
}
|
||||
return $select;
|
||||
}
|
||||
|
||||
|
||||
protected function renderField( $field , $prefix )
|
||||
{
|
||||
switch ( $field->type( ) ) {
|
||||
|
||||
case 'password':
|
||||
$result = $this->renderPasswordField( $field , $prefix );
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
$result = $this->renderTextField( $field , $prefix );
|
||||
break;
|
||||
|
||||
case 'textarea':
|
||||
$result = $this->renderTextArea( $field , $prefix );
|
||||
break;
|
||||
|
||||
case 'select':
|
||||
$result = $this->renderSelectField( $field , $prefix );
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception( "field " . $field->name() . " has unknown type " . $field->type() );
|
||||
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
protected function renderVisibleFields( $target , $prefix )
|
||||
{
|
||||
foreach ( $this->form->fields( ) as $field ) {
|
||||
if ( $field === null ) {
|
||||
$target->appendElement( HTML::make( 'hr' ) );
|
||||
continue;
|
||||
}
|
||||
if ( $field->type( ) === 'hidden' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fPrefix = $prefix . $field->name( ) . '-';
|
||||
if ( $field->type( ) === 'html' ) {
|
||||
$target->appendElement( HTML::make( 'dd' )
|
||||
->setAttribute( 'id' , $fPrefix )
|
||||
->setAttribute( 'class' , 'html-section' )
|
||||
->append( $field->value( ) ) );
|
||||
continue;
|
||||
}
|
||||
|
||||
$fClass = 'field' . ( $field->mandatory( ) ? ' mandatory' : '' );
|
||||
$target->appendElement( HTML::make( 'dt' )
|
||||
->setAttribute( 'class' , $fClass )
|
||||
->setAttribute( 'id' , $fPrefix . 'label' )
|
||||
->appendElement( HTML::make( 'label' )
|
||||
->setAttribute( 'for' , $fPrefix . 'field' )
|
||||
->appendText( $field->description( ) ) ) );
|
||||
|
||||
$errors = $field->errors( );
|
||||
if ( !empty( $errors ) ) {
|
||||
foreach ( $errors as $error ) {
|
||||
$target->appendElement( HTML::make( 'dd' )
|
||||
->setAttribute( 'class' , 'form-error' )
|
||||
->appendText( $error ) );
|
||||
}
|
||||
$fClass .= ' erroneous';
|
||||
}
|
||||
|
||||
$target->appendElement( HTML::make( 'dd' )
|
||||
->setAttribute( 'id' , $fPrefix . 'container' )
|
||||
->setAttribute( 'class' , $fClass )
|
||||
->append( $this->renderField( $field , $fPrefix ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function render( )
|
||||
{
|
||||
$name = $this->form->name();
|
||||
$prefix = $name . '-';
|
||||
|
||||
$form = HTML::make( 'form' )
|
||||
->setAttribute( 'name' , $name )
|
||||
->setAttribute( 'id' , $prefix . 'form' )
|
||||
->setAttribute( 'action' , $this->form->action( ) )
|
||||
->setAttribute( 'method' , $this->form->method( ) )
|
||||
->append( $this->renderHiddenFields( $prefix ) )
|
||||
->append( $visibleArea = HTML::make( 'dl' ) );
|
||||
|
||||
$this->renderVisibleFields( $visibleArea , $prefix );
|
||||
$visibleArea->appendElement( HTML::make( 'dt' )
|
||||
->setAttribute( 'class' , 'submit-button' )
|
||||
->setAttribute( 'id' , $prefix . 'submit-container' )
|
||||
->appendElement( HTML::make( 'input' )
|
||||
->setAttribute( 'type' , 'submit' )
|
||||
->setAttribute( 'name' , $prefix . 'submit' )
|
||||
->setAttribute( 'id' , $prefix . 'submit' )
|
||||
->setAttribute( 'value' , $this->form->buttonTitle( ) ) ) );
|
||||
|
||||
return $form;
|
||||
}
|
||||
}
|
456
includes/loader.inc.php
Normal file
456
includes/loader.inc.php
Normal file
|
@ -0,0 +1,456 @@
|
|||
<?php
|
||||
|
||||
|
||||
final class LoaderException extends Exception { }
|
||||
|
||||
|
||||
final class ConfigGetter
|
||||
{
|
||||
private $config;
|
||||
private $package;
|
||||
|
||||
public function __construct( Package $package , $config )
|
||||
{
|
||||
$this->package = $package;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
|
||||
public function get( $path = '' , $default = null , $fail = false )
|
||||
{
|
||||
if ( $path == '' ) {
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
$aPath = explode( '/' , $path );
|
||||
$config = &$this->config;
|
||||
foreach ( $aPath as $name ) {
|
||||
if ( !( is_array( $config ) && array_key_exists( $name , $config ) ) ) {
|
||||
if ( $fail ) {
|
||||
throw new LoaderException( "configuration key '$path' not found for package '"
|
||||
. $this->package->name() . "'" );
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
$config = &$config[ $name ];
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class Package
|
||||
{
|
||||
private $name;
|
||||
|
||||
private $files;
|
||||
private $requires;
|
||||
|
||||
private $ctrls;
|
||||
private $daos;
|
||||
private $extras;
|
||||
private $hooks;
|
||||
private $pages;
|
||||
private $singletons;
|
||||
private $views;
|
||||
|
||||
private $loaded = false;
|
||||
private $config;
|
||||
|
||||
public function __construct( $name , $description , $config )
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
$fields = array( 'files' , 'requires' , 'daos' , 'views' , 'ctrls' , 'extras' , 'singletons' , 'pages' , 'hooks' );
|
||||
foreach ( $fields as $field ) {
|
||||
$this->getField( $description , $field );
|
||||
}
|
||||
if ( empty( $this->files ) ) {
|
||||
throw new LoaderException( "package '{$this->name}': no files" );
|
||||
}
|
||||
|
||||
if ( ! is_array( $config ) ) {
|
||||
$config = array( );
|
||||
}
|
||||
$this->config = new ConfigGetter( $this , $config );
|
||||
}
|
||||
|
||||
|
||||
private function getField( $description , $field )
|
||||
{
|
||||
if ( ! array_key_exists( $field , $description ) ) {
|
||||
$this->$field = array( );
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $description[ $field ];
|
||||
if ( !is_array( $value ) ) {
|
||||
throw new LoaderException( "package '{$this->name}': '$field' must be an array" );
|
||||
}
|
||||
|
||||
foreach ( $value as $item ) {
|
||||
if ( !is_string( $item ) ) {
|
||||
throw new LoaderException( "package '{$this->name}': '$field' contains non-string items" );
|
||||
}
|
||||
}
|
||||
|
||||
$this->$field = $value;
|
||||
}
|
||||
|
||||
|
||||
public function name( )
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function files( )
|
||||
{
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
public function requires( )
|
||||
{
|
||||
return $this->requires;
|
||||
}
|
||||
|
||||
public function daos( )
|
||||
{
|
||||
return $this->daos;
|
||||
}
|
||||
|
||||
public function views( )
|
||||
{
|
||||
return $this->views;
|
||||
}
|
||||
|
||||
public function ctrls( )
|
||||
{
|
||||
return $this->ctrls;
|
||||
}
|
||||
|
||||
public function singletons( )
|
||||
{
|
||||
return $this->singletons;
|
||||
}
|
||||
|
||||
public function extras( )
|
||||
{
|
||||
return $this->extras;
|
||||
}
|
||||
|
||||
public function pages( )
|
||||
{
|
||||
return $this->pages;
|
||||
}
|
||||
|
||||
|
||||
public function loaded( )
|
||||
{
|
||||
return $this->loaded;
|
||||
}
|
||||
|
||||
public function hooks( )
|
||||
{
|
||||
return $this->hooks;
|
||||
}
|
||||
|
||||
public function setLoaded( )
|
||||
{
|
||||
$this->loaded = true;
|
||||
}
|
||||
|
||||
|
||||
public function config( $path = '' , $default = null , $fail = false )
|
||||
{
|
||||
return $this->config->get( $path , $default , $fail );
|
||||
}
|
||||
|
||||
public function getConfigAccess( )
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
interface PackageAware
|
||||
{
|
||||
public function setPackage( Package $package );
|
||||
}
|
||||
|
||||
|
||||
interface TextSource
|
||||
{
|
||||
public function get( $what );
|
||||
}
|
||||
|
||||
|
||||
final class Loader
|
||||
{
|
||||
private static $loader = null;
|
||||
|
||||
private $baseDir;
|
||||
private $config;
|
||||
private $packages = array( );
|
||||
private $items = array(
|
||||
'ctrls' => array( ) ,
|
||||
'daos' => array( ) ,
|
||||
'extras' => array( ) ,
|
||||
'singletons' => array( ) ,
|
||||
'views' => array( ) ,
|
||||
'pages' => array( ) ,
|
||||
);
|
||||
private $loading = array( );
|
||||
private $singletons = array( );
|
||||
private $daos = array( );
|
||||
private $textSource;
|
||||
|
||||
private function __construct( )
|
||||
{
|
||||
$this->baseDir = dirname( __FILE__ );
|
||||
$this->loadConfig( );
|
||||
$this->loadPackageDescriptions( );
|
||||
}
|
||||
|
||||
private function loadConfig( )
|
||||
{
|
||||
$config = array( );
|
||||
@include( $this->baseDir . '/config.inc.php' );
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
private function loadPackageDescriptions( )
|
||||
{
|
||||
if ( !( $dh = opendir( $this->baseDir ) ) ) {
|
||||
throw new LoaderException( "unable to access directory" );
|
||||
}
|
||||
|
||||
while ( ( $entry = readdir( $dh ) ) !== false ) {
|
||||
if ( $entry === '.' || $entry === '..' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = "{$this->baseDir}/$entry";
|
||||
if ( is_dir( $path ) && is_file( "$path/package.inc.php" ) ) {
|
||||
$this->loadDescription( $entry );
|
||||
}
|
||||
}
|
||||
|
||||
closedir( $dh );
|
||||
}
|
||||
|
||||
|
||||
private function loadDescription( $name )
|
||||
{
|
||||
$package = array( );
|
||||
require( $this->baseDir . '/' . $name . '/package.inc.php' );
|
||||
if ( empty( $package ) ) {
|
||||
throw new LoaderException( "package '$name': no information" );
|
||||
}
|
||||
|
||||
if ( ! array_key_exists( $name , $this->config ) ) {
|
||||
$this->config[ $name ] = array( );
|
||||
}
|
||||
|
||||
$package = new Package( $name , $package , $this->config[ $name ] );
|
||||
$this->packages[ $name ] = $package;
|
||||
$this->config[ $name ] = null;
|
||||
|
||||
foreach ( array_keys( $this->items ) as $type ) {
|
||||
$items = $package->$type( );
|
||||
foreach ( $items as $item ) {
|
||||
if ( array_key_exists( $item , $this->items[ $type ] ) ) {
|
||||
$oName = $this->items[ $type ][ $item ];
|
||||
$type = substr( $type , 0 , strlen( $type ) - 1 );
|
||||
throw new LoaderException( "package '$name': conflict with '$oName' on $type '$item'" );
|
||||
}
|
||||
$this->items[ $type ][ $item ] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function loadPackage( $name )
|
||||
{
|
||||
if ( ! array_key_exists( $name , $this->packages ) ) {
|
||||
throw new LoaderException( "Package '$name' not found" );
|
||||
}
|
||||
|
||||
$package = $this->packages[ $name ];
|
||||
if ( $package->loaded( ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( array_key_exists( $name , $this->loading ) ) {
|
||||
throw new LoaderException( "Package '$name': recursive dependencies detected" );
|
||||
}
|
||||
$this->loading[ $name ] = 1;
|
||||
|
||||
foreach ( $package->requires( ) as $dependency ) {
|
||||
$this->loadPackage( $dependency );
|
||||
}
|
||||
|
||||
$dir = $this->baseDir . '/' . $name;
|
||||
foreach ( $package->files( ) as $file ) {
|
||||
require_once( "$dir/$file.inc.php" );
|
||||
}
|
||||
|
||||
unset( $this->loading[ $name ] );
|
||||
$package->setLoaded( );
|
||||
|
||||
$hooks = $package->hooks( );
|
||||
if ( is_array( $hooks ) ) {
|
||||
foreach ( $hooks as $hook ) {
|
||||
$hook( $this , $package );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function findItem( $name , $type )
|
||||
{
|
||||
$rType = $type . 's';
|
||||
if ( ! array_key_exists( $rType , $this->items ) ) {
|
||||
throw new LoaderException( "Invalid type '$type'" );
|
||||
}
|
||||
|
||||
if ( ! array_key_exists( $name , $this->items[ $rType ] ) ) {
|
||||
throw new LoaderException( "Item '$name' of type $type not found" );
|
||||
}
|
||||
|
||||
$package = $this->items[ $rType ][ $name ];
|
||||
$this->loadPackage( $package );
|
||||
return $this->packages[ $package ];
|
||||
}
|
||||
|
||||
|
||||
private function createInstance( $package , $cName , $args )
|
||||
{
|
||||
if ( empty( $args ) ) {
|
||||
$instance = new $cName();
|
||||
} else {
|
||||
$reflection = new ReflectionClass( $cName );
|
||||
$instance = $reflection->newInstanceArgs( $args );
|
||||
}
|
||||
|
||||
if ( is_a( $instance , 'PackageAware' ) ) {
|
||||
$instance->setPackage( $package );
|
||||
}
|
||||
return $instance;
|
||||
}
|
||||
|
||||
|
||||
private function loadAndCreate( $type , $name , $cName , $args )
|
||||
{
|
||||
$package = $this->findItem( $name , $type );
|
||||
return $this->createInstance( $package , $cName , $args );
|
||||
}
|
||||
|
||||
|
||||
private function getSingleton( $name )
|
||||
{
|
||||
if ( ! array_key_exists( $name , $this->singletons ) ) {
|
||||
$this->singletons[ $name ] = $this->loadAndCreate( 'singleton' , $name , $name , array( ) );
|
||||
}
|
||||
return $this->singletons[ $name ];
|
||||
}
|
||||
|
||||
|
||||
private function getDao( $name )
|
||||
{
|
||||
if ( array_key_exists( $name , $this->daos ) ) {
|
||||
return $this->daos[ $name ];
|
||||
}
|
||||
$cName = Loader::convertName( 'DAO' , $name );
|
||||
$instance = $this->loadAndCreate( 'dao' , $name , $cName , array( ) );
|
||||
$instance->setDatabase( Loader::Singleton( 'Database' ) );
|
||||
$this->daos[ $name ] = $instance;
|
||||
return $instance;
|
||||
}
|
||||
|
||||
|
||||
private static function get( )
|
||||
{
|
||||
if ( Loader::$loader === null ) {
|
||||
Loader::$loader = new Loader( );
|
||||
}
|
||||
return Loader::$loader;
|
||||
}
|
||||
|
||||
private static function convertName( $type , $name )
|
||||
{
|
||||
$cName = ucfirst( $type ) . '_';
|
||||
foreach ( explode( '_' , $name ) as $part ) {
|
||||
$cName .= ucfirst( $part );
|
||||
}
|
||||
return $cName;
|
||||
}
|
||||
|
||||
private static function creator( $type , $convert , $args )
|
||||
{
|
||||
$name = array_shift( $args );
|
||||
$cName = $convert ? Loader::convertName( $type , $name ) : $name;
|
||||
return Loader::get( )->loadAndCreate( $type , $name , $cName , $args );
|
||||
}
|
||||
|
||||
|
||||
public static function PackageConfig( $name )
|
||||
{
|
||||
$loader = Loader::get( );
|
||||
$loader->loadPackage( $name );
|
||||
return $loader->packages[ $name ]->getConfigAccess( );
|
||||
}
|
||||
|
||||
|
||||
public static function TextSource( TextSource $source = null )
|
||||
{
|
||||
if ( $source !== null ) {
|
||||
Loader::get( )->textSource = $source;
|
||||
} else {
|
||||
return Loader::get( )->textSource;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function Text( $what )
|
||||
{
|
||||
$source = Loader::get( )->textSource;
|
||||
return $source ? $source->get( $what ) : $what;
|
||||
}
|
||||
|
||||
|
||||
public static function Load( $name , $type = 'extra' )
|
||||
{
|
||||
Loader::get( )->findItem( $name , $type );
|
||||
}
|
||||
|
||||
|
||||
public static function Singleton( $name )
|
||||
{
|
||||
return Loader::get( )->getSingleton( $name );
|
||||
}
|
||||
|
||||
public static function Create( )
|
||||
{
|
||||
return Loader::creator( 'extra' , false , func_get_args( ) );
|
||||
}
|
||||
|
||||
public static function View( )
|
||||
{
|
||||
return Loader::creator( 'view' , true , func_get_args( ) );
|
||||
}
|
||||
|
||||
public static function Ctrl( )
|
||||
{
|
||||
return Loader::creator( 'ctrl' , true , func_get_args( ) );
|
||||
}
|
||||
|
||||
public static function Page( )
|
||||
{
|
||||
return Loader::creator( 'page' , true , func_get_args( ) );
|
||||
}
|
||||
|
||||
public static function DAO( $name )
|
||||
{
|
||||
return Loader::get( )->getDao( $name );
|
||||
}
|
||||
}
|
5
site/.htaccess
Normal file
5
site/.htaccess
Normal file
|
@ -0,0 +1,5 @@
|
|||
RewriteEngine on
|
||||
#RewriteBase /
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.*)$ index.php/$1 [L,QSA]
|
7
site/index.php
Normal file
7
site/index.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
include ( '../includes/loader.inc.php' );
|
||||
Loader::Create( 'URLMapper' , 'site' )->fromPathInfo( );
|
||||
|
||||
|
||||
?>
|
Loading…
Reference in a new issue