Please login or register.

Login with username, password and session length
Advanced search  
Pages: [1] 2 3

Author Topic: Discussions on ideas for a core framework  (Read 4444 times)

nukpana

  • Hero Member
  • *****
  • Karma: 71
  • Posts: 663
Discussions on ideas for a core framework
« on: August 22, 2011, 12:52:22 AM »

This is further discussion to the outline I posted here
Quote
A small framework core with the basics:
- database
 - settings (no view)
 - plugin API, plugins (no view)
 - template (no view)
 - form/validation class
 - controller function - to hook different url methods
 - center function - a central view function to hook to the main column


In all honesty that is it.  Everything else is built up via the template, system layers, then plugins

NOTE: this has been updated here: http://snewscms.com/forum/index.php/topic,10292.msg69200.html#msg69200

Sorry it took me a while to get this post going.... but here it is:

Please note, these are suggestions and not so fully tested code.

1. Database:
First, per PHP, the mysql extension will soon be depreciated. What better way to start with using PDO, even the code is smaller.

Code: [Select]
<?php 

   
// Configure Database - from a configuration file
   
$config_db = array(
      
'dsn'       => 'mysql:dbname=test;host=localhost',
      
'user'      => 'root',
      
'password'   => 'root',
      
'params'   => array()
   );

   function 
db() {
      global 
$config_db;
      
      
$db_conn FALSE;
      try {
         
$db_conn = new PDO(
            
$config_db['dsn'],
            
$config_db['user'],
            
$config_db['password'],
            
$config_db['params']
         );   
      } catch( 
PDOException $e ) {
         die( 
$e->getMessage() );
      }
      return 
$db_conn;      
   }

?>

The code is a textbook sample to the PDO construct, found in the PHP docs, but I am just wrapping it in a function call.  We can see some cool uses for it right away.

Instead of:
Code: [Select]
$query = mysql_query($sql);
$result = mysql_result($query);
while ($r = mysql_fetch_array($result)) {

It can be simply:
Code: [Select]
foreach ( db()->query($sql) as $r ) {
1a. Database Schema
Ok I posted something similar previously, but my idea is this:

Code: [Select]
-- mySQL

CREATE TABLE addons (
   name VARCHAR(255) NOT NULL,
   type VARCHAR(255) NOT NULL, -- plugins, template

   INDEX (type),
   UNIQUE (name)
) ENGINE = InnoDB;

CREATE TABLE settings (
   name VARCHAR(255) NOT NULL,
   value VARCHAR(255) NOT NULL,
   INDEX (name),
   UNIQUE (name)
) ENGINE = InnoDB;

CREATE TABLE users (
   id INT( NOT NULL AUTO_INCREMENT,
   email VARCHAR(255) NOT NULL,
   password VARCHAR(255) NOT NULL,
   
   PRIMARY KEY (id),
   UNIQUE (email)
) ENGINE = InnoDB;

CREATE TABLE meta (
   id INT(11) AUTO_INCREMENT,
   name VARCHAR(255) NOT NULL,
   uri VARCHAR(255) NOT NULL,
   date DATETIME NOT NULL,
   seq INT(1) NOT NULL DEFAULT '1',    -- sequence
   status INT(1) NOT NULL DEFAULT '1',
   tag VARCHAR(255) NOT NULL,

   PRIMARY KEY (id),
   INDEX (
      uri,
      status,
      tag
   )
) ENGINE = InnoDB;

   CREATE TABLE meta_assoc (
      meta_id INT(11) NOT NULL,
      parent INT(11) DEFAULT NULL,
      INDEX (
         meta_id,
         parent
      )
   ) ENGINE = InnoDB;

   ALTER TABLE meta_assoc
      ADD CONSTRAINT meta_assoc_ibfk_1
         FOREIGN KEY (meta_id)
         REFERENCES meta (id)
            ON DELETE CASCADE
            ON UPDATE CASCADE;
         
   CREATE TABLE content (
      meta_id INT(11) NOT NULL,
      description TEXT DEFAULT NULL,
      text LONGTEXT NOT NULL,
      INDEX (meta_id)
   ) ENGINE = InnoDB;     

   ALTER TABLE content
      ADD CONSTRAINT content_ibfk_1
         FOREIGN KEY (meta_id)
         REFERENCES meta (id)
            ON DELETE CASCADE
            ON UPDATE CASCADE;   

First this should be sort of self explanatory.

I am using INNODB to allow FOREIGN KEY references to other tables if need be.

The addon table is almost identical to my plugin and template switcher.
mods.

Settings table, is almost identical to current sNews settings, with an UNIQUE index so we don't get duplicates.

The heart of the app would be the meta table.  This would hold (sub/)categories/articles/extra/etc. that would be tagged as such.

Any hierarchy would be done with the meta_assoc table. Here you can see the INNODB working - so if we delete category 1, any association in the meta_assoc table would also be deleted per the constraint.

Likewise to the meta_assoc table, the content table would hold the main content of the said meta data.

The user table is seperate from the settings table as like default sNews SU.  Here we can set the stage for an MU environment and add other tables for roles in the same way the meta_assoc and content tables are laid out.

2. Settings
I stated earlier: Settings (no view). What this means is we are setting up only the base code to work with the CRUD of the Settings DB table.  There is no user view associated here:
Code: [Select]
<?php

   
class Settings {
      private static 
$_data = array();
   
      static function 
init() {
         
$sql 'SELECT name, value FROM settings';
         foreach ( 
db()->query($sql) as $r ) {
            
self::$_data[$r['name']] = $r['value'];
         }
      }
   
      static function 
get$key ) {
         if( 
self::exists($key) ) {
            return 
self::$_data[$key];
         }
      }
      
      static function 
set$key$value ) {
         if( 
self::exists($key) ) {
            
$sql "UPDATE settings 
               SET value = '"
.$value."' 
               WHERE name = '"
.$key."'";
         } else {
            
$sql "INSERT 
               INTO settings 
               VALUES('"
.$key."', '".$value."')
               LIMIT 1"
;         
         }
         
db()->query($sql);
      }

      static function 
delete$key ) {
         if( 
self::exists($key) ) {
            
db()->query("DELETE 
               FROM settings 
               WHERE name = '"
.$key."'"
            
);
         }
      }

      static function 
exists$key ) {
         return 
array_key_exists($keyself::$_data);
      }   
   } 
   
   
Settings::init();

?>

Here we are using a a Static Class that can be used like so:

Code: [Select]
<?php

Settings
::set('website_title''My cool website!');
Settings::get('website_title');
Settings::delete('website_title');

/// put it in a wrapper function if you want:
function s($var) {
  return 
Settings::get($var); 
}
echo 
s('website_title');

?>


3. Plugin, Template
This is something I posted previously, but I will do again.  This is just the plugin code, and the template code is similar to the link I noted above.  Again, unfinished:

Registry class
Code: [Select]
<?php

   
class Registry {
      private static 
$_data = array();

      static function 
get$name ) {
         if( isset( 
self::$_data[$name] ) ) {   
            return 
self::$_data[$name];
         }
      }
      static function 
set$name$value '' ) {
         
self::$_data[$name] = $value;
      }   
   }   
   
?>


Main Addon Class:
Code: [Select]
<?php

   
class AddOns {
      private static 
$_data = array();
   
      static function 
init() {
         
$sql 'SELECT type, name FROM addons';
         foreach ( 
db()->query($sql) as $r ) {
            
self::$_data[$r['type']][] = $r['name'];
         }
      }
   
      static function 
get$type ) {
         if( 
array_key_exists($typeself::$_data) ) {
            return 
self::$_data[$type];
         }
      }
      
      static function 
add$name$type ) {
         if( !
self::exists($name$type) ) {
            
db()->query("INSERT 
               INTO addons 
               VALUES('"
.$name."', '".$type."')
               LIMIT 1"
            
);
         }
      }

      static function 
delete$name$type ) {
         if( 
self::exists($name$type) ) {
            
db()->query("DELETE 
               FROM addons 
               WHERE name = '"
.$name."' 
                  AND type = '"
.$type."'"
            
);
         }
      }

      static function 
exists$name$type ) {
         return 
array_search($nameself::get($type)) === FALSE
            
FALSE
            
TRUE
      }         
   } 
   
   
AddOns::init();   

?>


Plugin Class:
Code: [Select]
<?php

   define
('EXT_PATH''ext/');   
   
   class 
Plugins {
      private static 
$_active,
                   
$_list;

      static function 
init() {
         
self::$_active AddOns::get('plugin')
            ? 
AddOns::get('plugin')
            : array();

         if( empty(
self::$_active) ) return;
         
         foreach( 
self::$_active as $p ) {
            
$file EXT_PATH $p '.php';
            if( 
file_exists($file) ) {
               include(
$file);
            }
         }
      }

      static function 
is_active$name ) {   
         
$state FALSE;
         if( 
in_array($nameself::$_active) ) {
            
$state TRUE;
         }   
         return 
$state;      
      }

      static function 
get$name ) {
         if( 
$name == 'all' ) {
            return 
self::all_plugins();
         } else {
            if( isset( 
self::$_list[$name] ) ) {
               return 
self::$_list[$name];
            }
         }
      }

      private static function 
all_plugins() {
         
$dir opendir(EXT_PATH);
         if (
$dir) {
            while (
false !== ($file readdir($dir))) {
               if(    
preg_match('|\.php$|'$file
                  && !
is_dir(EXT_PATH $file
               ) { 
                  
$file substr
                     
$file
                     
0
                     
strpos$file'.php' 
                  );
                  
self::$_list[$file] = self::data(
                     
EXT_PATH $file '.php'
                  
);
                  if( 
self::is_active($file) ) {
                     
self::$_list[$file]['active'] = TRUE;
                  }
               }
            }
         }
         return 
self::$_list;
      }

      private static function 
data$file ) {
         
$file implode(''file$file ) );

         
$class = new ReflectionClass('Plugin');
         
$methods $class->getMethods();

         foreach( 
$methods as $k => $v ) {
            
preg_match("|".$v->name."\('(.*)\'\)|i"$file$n[$v->name]);

            if( !empty( 
$n[$v->name][1] ) && $v->name != 'add' ) {
               
$array[$v->name] = strip_tags$n[$v->name][1] );
            }
         }
         return 
$array;
      }

   } 
   
   
Plugins::init();   
   
   
Registry::set('ext'
      array(
         
'ext' => array(),
         
'hook'=> array()
      )
   );

   function 
ext() {
      return 
Registry::get('ext');
   }
   
   
// Plugin class - for developers
   
class Plugin {
      private 
$_name,         
            
$_data = array(),   
            
$_hook = array(),   
            
$_ext;         

      function 
__construct() {
         
$this->_ext ext();
         
$this->_name calling_file_name();
      }
      
      function 
name$name ) {
         if( empty(
$name) ) {
            
$name $this->_name;
         } 
         
$this->_data['name'] = $name;
      }

      function 
add$hook$callback ) {
         
$this->_hook[$hook][] = $callback;   
      }

      function 
author$name ) {
         if (!empty(
$name)) {
            
$this->_data['author'] = $name;
         }
      }
   
      function 
description$str ) {
         if (!empty(
$str)) {
            
$this->_data['description'] = $str;
         }
      }   
   
      function 
commit() {
         
$this->_ext['ext'][$this->_name] = $this->_data;
         foreach (
$this->_hook as $hook => $c) {
            foreach( 
$c as $callback ) {
               
$this->_ext['hook'][$hook][] = $callback;
            }
         }
         
// Add plugin information to the registry
         
Registry::set('ext'$this->_ext);
      }
   }

   function 
exec_plugin$hook$string '' ) {
      
$ext ext();
      if( isset( 
$ext['hook'][$hook] ) ) {
         foreach( 
$ext['hook'][$hook] as $func ) {
            
$string call_user_func($func$string);
         }
      }
      return 
$string;
   }

   function 
calling_file_name() {
      
$backtrace debug_backtrace();
      
$paths explode('/'$backtrace[1]['file']);
      
$last count($paths) - 1;
      if (
end($paths)) {
         return 
str_replace('.php''',  $paths[$last]);
      }         
   }   

?>


4. Form, Validation code.
I did finish any test code but my thinking was 2 separated class that handled a Form and Validation. I think a base class can allow any form to be made and then through plugins, new item, or validation can be added without altering the initial code.  I didn't like any of the tests I made, so I am reluctant to post any code but here was an older (very very bad) test:

Code: [Select]
<?
   class Form {
      private $_form;

      function __construct( $form_name ) {
         $this->_form = $form_name;
      }

      function form_name() {
         return $this->_form;
      }

      function render( $form, $data ) {
         $form = exec_ext($this->_form .'_render', $form);
         if( !empty($data) ) {
                        // Undefined code I was testing
         //   $form = set_form_values($form, $data);
         }
         return $form;
      }
   }

   class Validation {
      private $_form,
            $_fields = array(),
            $_errors = array();

      function __construct( $form_name ) {
         $this->_form = $form_name;
      }
   
      function add_validation( $field, $callback ) {
         $this->_fields[] = array(
            'field' => $field,
            'callback' => $callback
         );
      }   

      function validate( $array ) {
         exec_ext( $this->_form . '_validation_array', $this );

         foreach( $this->_fields as $v ) {

            if( is_callable($v['callback']) ) {
               $fields = $v['callback']( $array[$v['field']] );
               
               if(   isset($fields['validation']) &&
                  $fields['validation'] === FALSE
               ) {
                  $this->_errors[$v['field']][] = $fields['view'];
               }               
            }
         }     
      }

      function has_errors() {
         if( count($this->_errors) > 0 ) {
            return TRUE;
         }
      }
   
      function get_errors() {
         return $this->_errors;
      }   
   }

   $f = new Form('testform');
        $f->render('form HTML', $_POST);
   if( isset($_POST['submit']) ) {
      $val = new Validation($f->form_name());
      $val->add_validation('field','callback');
      $val->validate( $_POST );
   
      if( $val->has_errors() ) {
         $errors = $val->get_errors();
                        // Do something with the errors
                 }
       }

?>

5. Controller code
This would be the code that would parse any URL and pass the results to the template.  This would be similar to the MainQuery & Globals section of sNews 1.7 - with the exception that this would be driven by plugins ie the whole function could look like:

Code: [Select]
fn controller() {
   return array(exec_ext('controller'));
}

// Usage could be:
$values = controller();
loadTemplate($values);

So doing this wouldn't lock a user into one style of URL, but the values passed would have to be the same for use in the template.

6. A new Center function.
Could probably be as simple as the above controller code. Being the only core code, that I think would be needed in the template.   This would be the main content view for private and public viewings so all code could hook up there.
« Last Edit: June 18, 2012, 03:55:40 AM by nukpana »
Logged

philmoz

  • High flyer
  • ULTIMATE member
  • ******
  • Karma: 161
  • Posts: 1988
    • fiddle 'n fly
Re: Discussions on ideas for a core framework
« Reply #1 on: September 16, 2011, 10:10:21 PM »

prior to this post (only just), I started looking at PDO, and agree that it is the way to go.

anyone wanting a tutorial for near newbies, this one isn't too bad.
http://net.tutsplus.com/tutorials/php/why-you-should-be-using-phps-pdo-for-database-access/

and youtube
http://www.youtube.com/watch?v=dF8hoPj-1bc
« Last Edit: September 16, 2011, 10:28:42 PM by philmoz »
Logged
Of all the things I have lost, it is my mind that I miss the most.

Fred K

  • Still trying to learn stuff
  • ULTIMATE member
  • ******
  • Karma: 130
  • Posts: 2728
    • Personal
Re: Discussions on ideas for a core framework
« Reply #2 on: September 17, 2011, 02:21:09 AM »

sheesh, this is so over my head it's not even funny... however, a word on the db setup. I was catching up on daringfireball a while back and came across this post which links to this post which, to cut to the chase, would mean the following line after each table block:

); ENGINE=InnoDB DEFAULT CHARSET=uf8;

note to self: stay off the anisette! it's the devil! :P
« Last Edit: September 17, 2011, 07:01:58 PM by Fred K »
Logged

nukpana

  • Hero Member
  • *****
  • Karma: 71
  • Posts: 663
Re: Discussions on ideas for a core framework
« Reply #3 on: May 16, 2012, 12:59:31 PM »

Wow, so alot of time has passed here.  I am still plugging away at this, slowly, very slowly but surely.

Here is some (not fully tested) updated code to see where my head has been at. Warning, it is more OOP than the original post(OP). I will follow the outline noted in the OP.

1. Database
 - Config.ini.
Code: [Select]
; Configuration File

[database]
; Example: mysql:host=localhost;dbname=testdb
dsn = "mysql:host=localhost;dbname=test2"
; Database User
user = "root"
; Database Password
pass = "root"

[paths]
languages = "usr/lang/"
extensions = "usr/ext/"
templates = "usr/tpl/"

The config parser and usage in the db function in the OP:
Code: [Select]
<?php

   
/**
    * Get values from config.ini based on section/key
    *
    * @param string $section
    * @param string $key
    * @staticvar array $array
    * @return string
    */
   
function get_ini$section$key ) {
      static 
$array;
      if( !
$array ) {
         
$array parse_ini_file('config.ini'TRUE);
      }
      if( isset(
$array[$section][$key]) ) {
         return 
$array[$section][$key];
      }
   }

   
/**
    * PDO database connection wrapper
    *
    * @uses function get_ini
    * @return object on success, dies on failure
    */
   
function db() {
      
$db_conn FALSE;
      try {
         
$db_conn = new PDO(
            
get_ini('database''dsn'),
            
get_ini('database''user'),
            
get_ini('database''pass')
         );
      } catch( 
PDOException $e ) {
         die( 
$e->getMessage() );
      }
      return 
$db_conn;
   }

?>

2. Settings. Hasn't changed, with the exception of passing the db function through the Settings::init() method.
Code: (Snippet) [Select]
<?php

   
/**
    * Initializing Settings construct
    */       
   
Settings::init(db());

   
/**
    * Setting CRUD class
    *
    * @final
    */
   
final class Settings {
      
/**
       * Property to house key/values from database
       *
       * @static
       * @property array $_data
       */
      
private static $_data = array();
      
      
/**    
       * Property for database object
       *
       * @static
       * @property object $_db       
       */         
      
private static $_db;

      
/**
       * False construct to build the self::$_data array from database
       * 
       * @static
       * @param object $db
       * @uses self::$_data       
       * @uses self::$_db
       */      
      
static function init$db ) {
         
self::$_db $db;
         
$sql '
            SELECT name, value 
            FROM settings
         '
;
         foreach ( 
self::$_db->query($sql) as $r ) {
            
self::$_data[$r['name']] = $r['value'];
         }
      }


The addons table is gone, replaced by a plugins table with one column.
Code: [Select]
CREATE TABLE plugins (
   name VARCHAR(255) NOT NULL,
   UNIQUE (name)
) ENGINE = InnoDB;

3. Plugin, Template
Big changes here. The Registry hasn't changed, but the AddOn static class changed to a abstract class.  This is to allow internal core functions to share the "template" class functionality
Code: [Select]
<?php

   
/**
    * Addon class
    * Property/Method comments show suggested usage
    *
    * @abstract
    */
   
abstract class AddOn {
      
/**
       * Property to note the active addon
       *
       * @property bool $_active
       */
      
protected $_active;
      
/**
       * Array list of all addons
       *
       * @property array $_list
       */
      
protected $_list = array();

      
/**
       * Loads active addons
       *
       * @abstract
       */
      
abstract function load();

      
/**
       * Get list of all addons in/active
       *
       * @abstract
       * @uses self::$_active
       * @uses self::$_list
       * @uses self::data()
       * @return array
       */
      
abstract function get();

      
/**
       * Process addon de/activate routine
       *
       * @abstract
       * @param string $str
       * @uses self::set()
       * @uses self::delete() optional
       */
      
abstract function process($str);

      
/**
       * Process data from addon file ie. attributes
       *
       * @abstract
       * @param string $file
       * @return array
       */
      
abstract protected function data($file);

      
/**
       * Set the active addon
       *
       * @abstract
       * @param string $addon
       */
      
abstract protected function set($addon);
   }

   
/**
    * Wrapper function to call AddOn::load()
    *
    * @uses AddOn instance
    * @param object $addon
    */
   
function addon_init(AddOn $addon) {
      
$addon->load();
   }

   
/**
    * Wrapper function to call AddOn::get()
    *
    * @uses AddOn instance
    * @param object $addon
    * @return array
    */
   
function addon_get(AddOn $addon) {
      return 
$addon->get();
   }

   
/**
    * Wrapper function to call AddOn::get()
    *
    * @uses AddOn instance
    * @param object $addon
    * @param string $str
    */
   
function addon_process(AddOn $addon$str) {
      
$addon->process($str);
   }

?>


Plugins look similar, but as stated above, the addon table is gone, so the CRUD functionality is now in the plugin file:
Code: [Select]
<?php

   
/**
    * Extensions
    */   

   /**
    * Define extension path constant
    * 
    * @name EXT_PATH
    * @uses get_ini()    
    */   
   
define(
      
'EXT_PATH'
      
get_ini('paths''extensions')
   );

   
/**
    * Set a blank extension Registry 
    * 
    * @name ext
    */
   
Registry::set('ext'
      array(
         
'ext' => array(),
         
'hook'=> array()
      )
   );

   
/**
    * Wrapper for the loaded extension array
    * 
    * @return array
    */   
   
function ext() {
      return 
Registry::get('ext');
   }

   
/**
    * Extension CRUD class
    *
    * @final
    */   
   
final class ExtCRUD {
      
/**
       * Property to house key/values from database
       *
       * @static
       * @property array $_data
       */
      
private static $_data = array();
      
      
/**    
       * Property for database object
       *
       * @static
       * @property object $_db       
       */         
      
private static $_db;

      
/**
       * False construct to build the self::$_data array from database
       * 
       * @static
       * @param object $db
       * @uses self::$_data       
       * @uses self::$_db
       */      
      
static function init$db ) {
         
self::$_db $db;
         
$sql 'SELECT name FROM plugins';
         foreach ( 
self::$_db->query($sql) as $r ) {
            
self::$_data[] = $r['name'];
         }
      }

      
/**
       * Return self::$_data array
       * 
       * @static
       * @uses self::$_data
       * @return array
       */      
      
static function get() {
         return 
self::$_data;
      }

      
/**
       * Add key into Plugins db table, if it doesn't exist
       * 
       * @static
       * @param string $name
       * @uses self::exists()
       * @uses self::$_db       
       */         
      
static function add$name ) {
         if( !
self::exists($name) ) {
            
$sth self::$_db->query("
               INSERT INTO plugins 
               VALUES(:name)
               LIMIT 1
            "
);
            
$sth->bindParam(':name'$namePDO::PARAM_STR);
            
$sth->execute();            
         }
      }

      
/**
       * Deletes key from Plugins db table, if it exists
       * 
       * @static
       * @param string $name
       * @uses self::exists()
       * @uses self::$_db             
       */   
      
static function delete$name  ) {
         if( 
self::exists($name) ) {
            
$sth self::$_db->query("
               DELETE 
               FROM plugins
               WHERE name = :name 
            "
);
            
$sth->bindParam(':name'$namePDO::PARAM_STR);
            
$sth->execute();            
         }
      }

      
/**
       * Checks if key exists in self::$_data array
       * 
       * @static
       * @param string $name
       * @return bool 
       * @uses self::$_data
       */   
      
static function exists$name ) {
         return 
in_array($nameself::$_data); 
      }      
   }
   
   
/**
    * Initializing Extension CRUD construct
    */      
   
ExtCRUD::init(db());

   
/**
    * Extension internal class
    * 
    * @uses AddOn
    * @final
    */   
   
final class Extension extends AddOn {
      
/**
       * Loads the parent::$_active property 
       *
       * @final
       * @uses parent::$_active as array
       * @uses ExtCRUD::get()
       */      
      
function __construct() {
         
$this->_active ExtCRUD::get();
      }
      
      
/**
       * Loads the active extensions 
       * Updates the Registry through the Ext::commit() method
       *
       * @final
       * @uses parent::$_active
       * @uses constant EXT_PATH
       * @uses Registry::set()
       */         
      
final function load() {
         foreach( 
$this->_active as $p ) {
            
$file EXT_PATH $p '.php';

            if( 
file_exists$file ) ) {
               require( 
$file );
               
               if( 
class_exists($p) ) {
                  
$i = new $p;
                  
$i->commit();
               }               
            }
         }      
      } 

      
/**
       * Get array list of available extensions
       *
       * @final
       * @uses parent::$_active
       * @uses parent::$_list   
       * @uses self::data()
       * @uses constant EXT_PATH           
       * @return array
       */   
      
final function get() {
         
$dir opendir(EXT_PATH);
         if (
$dir) {
            while (
false !== ($file readdir($dir))) {
               if(    
preg_match('|\.php$|'$file
                  && !
is_dir(EXT_PATH $file
               ) { 
                  
$file substr($file0strpos($file'.php'));
                  
                  
$this->_list[$file] = $this->data(
                     
EXT_PATH $file '.php'
                  
);
                  
                  if( 
in_array($file$this->_active) ) {
                     
$this->_list[$file]['active'] = TRUE;
                  }
               }
            }
         }
         return 
$this->_list;
      }
         
      
/**
       * Parse called methods from Ext subclasses into array
       *
       * @final
       * @param string $file
       * @return array
       */               
      
final protected function data$file ) {
         
$array = array();
         
         
$file implode(''file$file ) );
   
         
$methods get_class_methods('Ext');      
         foreach( 
$methods as $k => $method ) {
            
preg_match("|".$method."\('(.*)\'\)|i"$file$n[$method]);

            if( !empty(
$n[$method][1]) && $method != 'add' ) {
               
$array[$method] = strip_tags($n[$method][1]);
            }
         }
         return 
$array;
      }

      
/**
       * Set an active plugin
       * 
       * @final
       * @param string $addon
       * @uses ExtCRUD::set()
       */   
      
final protected function set$addon ) {
         
ExtCRUD::set$addon );
      }
      
      
/**
       * Delete an active plugin
       * 
       * @final
       * @param string $addon
       * @uses ExtCRUD::delete()
       */         
      
final protected function delete$addon ) {
         
ExtCRUD::delete$addon );
      }   

      
/**
       * Process de/activate request string
       * 
       * @final
       *
       * @param string $str
       * @example
       *** activate_testplugin
       *** deactive_testplugin
       *
       * @uses self::set()
       * @uses self::delete()
       */         
      
final function process$str ) {
         switch (
true) {
            case (
strpos($str'activate') === ):
               
$this->set(substr($str'9'));
            break;
            case (
strpos($str'deactivate') === ):
               
$this->delete(substr($str'11'));
            break;
         }
      }         
   }   
   
   
/**
    * Initialise AddOn::load()
    *
    * @uses AddOn instance
    * @param object $addon
    */      
   
addon_init(new Extension);

   
/**
    * Extension API class
    * 
    * @abstract
    *
    * @example : Filename must match class name
    *
    * File name - TestPlugin.php
    * 
    * class TestPlugin extends Ext {
    *       function main() {
    *          $this->add('hook', 'callback');
    *      }
    * }
    *
    */   
   
abstract class Ext {
      
/**    
       * Property for plugin name
       *
       * @property string $_name       
       */   
      
private $_name;
      
      
/**    
       * Property for plugin data
       *
       * @property array $_data       
       */         
      
private $_data = array();   

      
/**    
       * Property for hook/callback data
       *
       * @property array $_hook    
       */   
      
private $_hook = array();
      
      
/**    
       * Property for extension registry wrapper
       *
       * @property array $_ext
       */         
      
private $_ext;         

      
/**
       * Loads the self::$_ext & self::$_name properties 
       *
       * @final
       * @uses ext()
       */   
      
final function __construct() {
         
$this->_ext  ext();
         
$this->_name get_class($this);
      }

      
/**
       * Method to register plugin and call inner methods
       *
       * @abstract
       */               
      
abstract function main();

      
/**
       * Sets plugin visible name
       *
       * @final
       * @param string $name
       * @uses self::$_name if empty
       * @uses self::$_data
       */   
      
final function name$name ) {
         if( empty(
$name) ) {
            
$name $this->_name;
         } 
         
$this->_data['name'] = $name;
      }

      
/**
       * Sets plugin author
       *
       * @final
       * @param string $name
       * @uses self::$_data
       */         
      
final function author$name ) {
         if (!empty(
$name)) {
            
$this->_data['author'] = $name;
         }
      }

      
/**
       * Sets plugin website
       *
       * @final
       * @param string $url
       * @uses self::$_data
       */   
      
final function website$url ) {
         if (!empty(
$url)) {
            
$this->_data['website'] = $url;
         }
      }

      
/**
       * Sets plugin description
       *
       * @final
       * @param string $str
       * @uses self::$_data
       */      
      
final function description$str ) {
         if (!empty(
$str)) {
            
$this->_data['description'] = $str;
         }
      }            
         
      
/**
       * Sets hook/callback for plugin
       *
       * @final
       * @param string $hook
       * $param string $callback
       * $param int $priority
       * @uses self::$_hook
       */               
      
final function add$hook$callback$priority 10 ) {
         
$this->_hook[$hook][$priority][] = $callback;   
      }

      
/**
       * Sets plugin dependencies
       * Sets error key is dependent plugin does not exist
       *
       * @final
       * @uses self::$_data
       * @uses self::$_ext
       */   
      
final function req() {
         
$this->_data['requires'] = func_get_args();

         foreach( 
func_get_args() as $dep ) {
            if( !isset(
$this->_ext['ext'][$dep]) ) {
               
$this->_data['req_error'] = TRUE;
            }
         }         
      }

      
/**
       * Commit plugin to the Extension Registry
       * Plugin will not be added if dependencies are not met
       * Called by Extension::load(), user doesn't need to initate.
       * 
       * @final
       * @uses self::$_data
       * @uses self::$_ext
       * @uses self::$_ext
       * @uses Registry::set()
       */         
      
final function commit() {
         
$this->main();
         
         if(    !isset(
$this->_data['req_error']) 
            || 
$this->_data['req_error'] === FALSE 
         
) {

            
$this->_ext['ext'][$this->_name] = $this->_data;


            
ksort_recursive($this->_hook);
            foreach (
$this->_hook as $hook => $calls) {
               foreach( 
$calls as $call ) {
                  foreach( 
$call as $func ) {
                     
$this->_ext['hook'][$hook][] = $func;
                  }
               }
            }
         }
         
Registry::set('ext'$this->_ext);
      }      
   }

   
/**
    * Ext::commit() wrapper function
    * Helpful for internal extensions
    * 
    * @final
    * @uses Ext instance
    * @param object $ext
    */      
   
function ext_commit(Ext $ext) {
      
$ext->commit();
   }   
   
   
/**
    * Function to match hook with callbacks
    * 
    * @example
    * echo exec_ext('hook');
    *
    * @example with string:
    * echo exec_ext('hook', 'Hello World');
    * Echos empty string, if a callback is not set    
    *
    * @uses ext()
    * @param string $hook
    * @param mixed $var
    * @return mixed
    */         
   
function exec_ext$hook$var NULL ) {
      
$ext ext();
      if( isset(
$ext['hook'][$hook]) ) { 
         foreach( 
$ext['hook'][$hook] as $func ) {   
            
$var $func($var);
         }
      }
      return 
$var;
   }

?>


The template function follows suit, but smaller.

Using the AddOn template, I added Languages to the core functionality - again not finished, but a start. See the comments for usage
Code: [Select]
<?php

   
/**
    * Languages
    */   

   /**
    * Define language path constant
    * 
    * @name LANG_PATH
    * @uses get_ini()    
    */   
   
define(
      
'LANG_PATH'
      
get_ini('paths''languages')
   );

   
/**
    * Set a blank language Registry 
    * 
    * @name lang
    */      
   
Registry::set('lang', array());

   
/**
    * Wrapper for the loaded language array
    * 
    * @return array
    */   
   
function lang() {
      return 
Registry::get('lang');
   }   

   
/**
    * Language internal class
    * 
    * @uses AddOn
    * @final
    */   
   
final class Language extends AddOn {
      
/**
       * Loads the parent::$_active property 
       *
       * @final
       * @uses parent::$_active as string
       * @uses Settings::get()
       */      
      
final function __construct() {
         
$this->_active Settings::get('language');
      }

      
/**
       * Loads the active language & sets it to the Registry
       *
       * @final
       * @uses parent::$_active
       * @uses constant LANG_PATH
       * @uses Registry::set()
       */         
      
final function load() {
         
$array = array();
         
$file LANG_PATH $this->_active '.php';
         if( 
file_exists($file) ) {
            
$array = require $file;
         }
         
Registry::set('lang'$array);
      }    

      
/**
       * Get array list of available languages
       *
       * @final
       * @uses parent::$_active
       * @uses parent::$_list   
       * @uses constant LANG_PATH           
       * @return array
       */         
      
final function get() {
         
$dir opendir(LANG_PATH);
         if (
$dir) {
            while (
false !== ($file readdir($dir))) {
               if (
                     
$file != '.' && 
                     
$file != '..' && 
                     
file_exists(LANG_PATH $file)
               ) {
                  
$filename basename($file'.php');
                  
$this->_list[$filename] = array();
                  
                  if( 
$filename === $this->_active ) {
                     
$this->_list[$filename]['active'] = TRUE;
                  }
               }
            }
         }
         return 
$this->_list;      
      }

      
/**
       * Unused
       *
       * @final
       * @todo?
       */   
      
final protected function data$file ) {}

      
/**
       * Set the active language
       * 
       * @final
       * @param string $addon
       * @uses Setting::set()
       */         
      
final protected function set$addon ) {
         
Settings::set'language'$addon );
      }

      
/**
       * Process the active language
       * 
       * @final
       * @param string $str
       * @uses self::set()
       */            
      
final function process$str ) {
         
$this->set$str );
      }               
   }

   
/**
    * Initialise AddOn::load()
    *
    * @uses AddOn instance
    * @param object $addon
    */   
   
addon_init(new Language);
   
   
/**
    * Function to translate strings
    * Uses strtr to translate characters or replace substrings
    * @link http://php.net/manual/en/function.strtr.php
    * 
    * @example Basic:
    * echo __('Hello World'); # Hello World
    *
    * @example Placeholder:
    * echo __('Hello @user', array('@user' => 'you')); # Hello you
    *
    * @example Do not translate placeholder - Spanish translation example
    * return array('Hello @user'' => 'Hola @user'); 
    *
    * @uses value()
    * @uses lang()
    * @param string $string
    * @param array $args
    */      
   
function __$string, array $args NULL ) {
   
      
$string value($stringlang())
         ? 
value($stringlang())
         : 
$string;

      return 
$args === null
         
$string
         
strtr($string$args);
   }

?>


4. Form, Validation. .... nothing at the moment.

5. Controller. Completely unfinished (like everything else....) but getting there:

Urls can utilize pretty urls with or without mod_rewrite:
http://localhost/cms/admin/user/1/edit      - With rewrite
http://localhost/cms/?/admin/user/1/edit   - Without rewrite

We can also utilize REST format as shown above...
http://microformats.org/wiki/rest/urls
http://framework.zend.com/manual/en/zend.controller.router.html

I am using parts based on this router class... I am trying to verify if it's ok to use in whole or part.
So far my usage is successful, but I haven't fully tested it out.
http://brandonwamboldt.ca/my-php-router-class-825/

For now...

Preparing Uri...
Code: [Select]
<?php

/**
 * Remove index.php, ?/, and duplicate slashes from $_SERVER['REQUEST_URI']
 * Based on http://brandonwamboldt.ca/my-php-router-class-825/
 * Router::__get_clean_url()
 *
 * @return string
 */
function _prepare_uri() {
$uri $_SERVER['REQUEST_URI'];
$uri str_replace(
dirname($_SERVER['PHP_SELF']),
'',
$uri
);
$uri str_replace('index.php'''$uri);
$uri str_replace('?/'''$uri);
$uri preg_replace'/\/+/''/'$uri );
$uri ltrim$uri'/' );

return $uri;
}

function parse_uri($component null) {
$array = array();

$uri _prepare_uri();
$uri parse_url($uri);

if( isset($uri['path']) ) {
$uri['path'] = trim($uri['path'], '/');
$array['path'] = explode('/'$uri['path']);
}
if( isset($uri['query']) ) {
parse_str($uri['query'], $array['query']);
}

if( isset($array[$component]) ) {
return $array[$component];
} else {
return $array;
}
}

function uri_part($num) {
return value($numparse_uri('path'));
}

Then the router:
Code: [Select]
<?php

/**
 * Router
 */


/**
 * Route class based from:
 * @link http://brandonwamboldt.ca/my-php-router-class-825/
 *
 * @example
 * Route::add('/', 'fn'); # function 'fn' called when matched 
 *
 * @example with mock pretty url/query string & numeric capture
 * URL - http://localhost/cms/?/admin/page/1444
 * Route::add('/admin/page/<#num>', 'fn2');
 * function fn2( $var ) {
 *  echo $var['num'];
 * }  
 * 
 * @final
 */

final class Route {
/**
 * Property to house route array
 *
 * @access private
 * @static
 * @property array $_routes
 */

private static $_routes = array();

/**
 * Wrapper method to pass self::$_route property
 * 
 * @static
 * @uses self::$_routes
 */

static function get_routes() {
return self::$_routes;
}

/**
 * Sets route/callback
 *
 * @final
 * @param string $route
 * $param string $callback
 * $param int $priority
 * @uses self::$_routes
 * @uses self::_prepare_route();
 */

static function add$route$callback$priority 10 ) {
$route self::_prepare_route($route);
self::$_routes[$route][$priority][] = $callback;
}

/**
 * Prepares route regex
 * Modified from http://brandonwamboldt.ca/my-php-router-class-825/
 * Router::route()
 *
 * @final
 * @param string $route
 * @return string
 */

private static function _prepare_route$route ) {
$route ltrim$route'/' );

// Custom, format: <:var_name|regex>
$route preg_replace
'/\<\:(.*?)\|(.*?)\>/'
'(?P<\1>\2)'
$route 
);

// Alphanumeric, format: <:var_name>
$route preg_replace
'/\<\:(.*?)\>/'
'(?P<\1>[A-Za-z0-9\-\_]+)'
$route 
);

// Numeric, format: <#var_name>
$route preg_replace
'/\<\#(.*?)\>/'
'(?P<\1>[0-9]+)'
$route 
);

// Wildcard (INCLUDING dir separators), format: <*var_name>
$route preg_replace
'/\<\*(.*?)\>/'
'(?P<\1>.+)'
$route 
);

// Wildcard (EXCLUDING dir separators), format: <!var_name>
$route preg_replace
'/\<\!(.*?)\>/'
'(?P<\1>[^\/]+)'
$route 
);

// Add regex for a full match or no match
$route '#^' $route '$#';

return $route;
}
}

/**
 * Router class 
 * Prioritizes and calls callback on route match
 * 
 * @final
 */

class Router {
/**
 * Property to house Route::get_routes()
 *
 * @access private
 * @property array $_routes
 */

private $_routes = array();

/**
 * Property to house cleaned url
 *
 * @access private
 * @property string $_routes
 */

private $_uri;

/**
 * Load the self::$_routes property
 *
 * @uses self::$_routes
 * @uses Route::get_routes()
 */

function __construct$uri ) {
$this->_routes Route::get_routes();
$this->_uri $uri;
}

/**
 * Gets clean uri
 *
 * @uses self::_clean_uri()
 * @return string
 */

private function _get_url() {
return $this->_uri;
}

/**
 * Process the routes
 * 
 * @uses self::$_routes
 * @uses self::_get_url()
 */

function run() {
ksort_recursive($this->_routes);

foreach( $this->_routes as $route => $calls ) {
foreach( $calls as $call ) {
foreach( $call as $func ) {
if( preg_match(
$route
$this->_get_url(),
$params

) {
$func($params);
}
}
}
}
}
}

class Rte extends Ext {
function main() {
$this->add('script_end''init_router');
}
}

function init_router() {
$router = new Router(_prepare_uri());
$router->run();
}

ext_commit(new Rte);

?>


Usage example excerpt from the admin/index.php:
Code: [Select]
<?php

if( 
uri_part(1) ) {
require('sys/admin/'ucfirst(uri_part(1)) . '.php');
}

switch( true ) {
case( uri_part(3) ):
Route::add(
'/admin/'uri_part(1) .'/<#id>/<:action>',
ucfirst(uri_part(1)) . 'Controller'
);
break;
case( uri_part(2) ):
Route::add(
'/admin/'uri_part(1) .'/<#id>',
ucfirst(uri_part(1)) . 'Controller'
);
break;
case( uri_part(1) ):
Route::add(
'/admin/'uri_part(1),
ucfirst(uri_part(1)) . 'Controller'
);
break;
.........

404s, headers, etc... haven't gotten that far....

End
Logged

philmoz

  • High flyer
  • ULTIMATE member
  • ******
  • Karma: 161
  • Posts: 1988
    • fiddle 'n fly
Re: Discussions on ideas for a core framework
« Reply #4 on: May 16, 2012, 02:08:10 PM »

Jason, your thoughts on a file registry table.

Just an idea, but any file within web directory would be registered. Any errant file that appears would not be parsed or included - just in case server is compromised and file injections made.
So, all required files, all plugin files, all interface uploaded files (images, pdfs etc)
Logged
Of all the things I have lost, it is my mind that I miss the most.

nukpana

  • Hero Member
  • *****
  • Karma: 71
  • Posts: 663
Re: Discussions on ideas for a core framework
« Reply #5 on: May 17, 2012, 04:16:52 AM »

Jason, your thoughts on a file registry table.

Just an idea, but any file within web directory would be registered. Any errant file that appears would not be parsed or included - just in case server is compromised and file injections made.
So, all required files, all plugin files, all interface uploaded files (images, pdfs etc)
I would like to do some research first, but my initial thoughts - I don't think it is a good idea.... but it depends on the application.  In context, for a CMS like sNews or for this thread, I am not liking it too much.

A quick search shows most of the basic security precautions are from user input:
http://php.net/manual/en/security.filesystem.php
http://www.ibm.com/developerworks/opensource/library/os-php-secure-apps/index.html
http://www.phpmoot.com/filesystem-security-with-php/

Now, where this can work - if any of the files have been corrupted ie they don't have the same signature as originally issued. 

So the thinking would be the file or file set would be md5'd from the author prior to release.  Then a key would have to come from a author which would be installed into the database. On each start-up the files that are registered would be checked if exists, then md5'd, then checked against the db key.  If it doesn't check out, uninstall and alert a warning.

Code: (Checking or issuing MD5) [Select]
<?php

$files = array(
'file1.php',
'file2.php',
'fldr/file3.php'
);

$file_hash = array();
foreach( $files as $f ) {
if( file_exists($f)) {
$file_hash[] = md5_file($f);
}
}
echo md5(implode(''$file_hash)); 

?>


Code: (Psuedo code) [Select]
<?php

if( $this->_key() !== $this->_file_md5sum() ) {
$this->process('deactivate_' $this->_name);
trigger_error(
htmlentities('Extension keys do not match, possibly malicious script. Extension uninstalled'), 
E_USER_WARNING
);
} else {
# $this->_load();
}

Obviously this would be small at first, but when one installs more plugins, templates, etc, the processing time can increase... what is that load time?  The next idea would be to the authors, it would be an extra step to publish their work, especially template authors - ie WP template that have multiple files, JS, images, etc. 

Again more research to check it out...
Logged

skian

  • Full Member
  • ***
  • Karma: 14
  • Posts: 120
Re: Discussions on ideas for a core framework
« Reply #6 on: May 17, 2012, 10:30:19 AM »

IMHO, plugins should be loaded explicitly by the user (admin) in index.php. This way, regular plugins won't require any modification inside snews.php.

So, in index, you would typically find :
Code: [Select]
<?php
require('snews.php');
include(
'archive-plugin.php'); // plugin examples
include('rss-plugin.php'); 
include(
'admin-plugin.php'); 
?>


Each plugin should be in charge to register itself to snews.
Code: [Select]
<?php
register_builtin_page
('/archive'archive_callback);
?>

Logged

philmoz

  • High flyer
  • ULTIMATE member
  • ******
  • Karma: 161
  • Posts: 1988
    • fiddle 'n fly
Re: Discussions on ideas for a core framework
« Reply #7 on: May 17, 2012, 11:25:41 AM »

IMHO, plugins should be loaded explicitly by the user (admin) in index.php. This way, regular plugins won't require any modification inside snews.php.

I would rather see them registered and placed from the backend.
Index.php would do security/login checks, call relevant template which in turn would have placeholders that have assigned items.
They (placeholders) would operate pretty much the way multiple extras crossed with the function insert currently operate in snews1.7

That way, template, once set with placeholders, need never be edited when adding/removing extra bits (plugins etc).
With serious use of caching, overhead would be minimal.

As for file registration, As a site grows, I agree the overheads will become too great if checking was performed evrytime site was loaded. If table had filename and path/directory as data, and admin could be able to set an inspection routine going per directory (say one directory a day) to ensure nothing foreign has arrived. Seize and quarantine routines would move files elsewhere.
... and thinking on it more, would be best as an optional admin plugin ;)
Logged
Of all the things I have lost, it is my mind that I miss the most.

nukpana

  • Hero Member
  • *****
  • Karma: 71
  • Posts: 663
Re: Discussions on ideas for a core framework
« Reply #8 on: May 17, 2012, 12:51:14 PM »

IMHO, plugins should be loaded explicitly by the user (admin) in index.php. This way, regular plugins won't require any modification inside snews.php.

So, in index, you would typically find :
Code: [Select]
<?php
require('snews.php');
include(
'archive-plugin.php'); // plugin examples
include('rss-plugin.php'); 
include(
'admin-plugin.php'); 
?>


Each plugin should be in charge to register itself to snews.
Code: [Select]
<?php
register_builtin_page
('/archive'archive_callback);
?>


While I don't disagree with the first method, most systems de/activate plugins using the backend admin inteface, which is what my ideas have been based towards.

Your second method is almost like the Route::add() method I noted above.  So a Archive plugin using the above ideas could be:
Code: [Select]
<?php

class Archive extends Ext {
function main() { # Using PHP 5.3 closures (anonymous functions)
$this->add('page_controller_hook', function() {
Route::add'/archive''archives' );
});
}

function install() {
db()->exec('
INSTALL INTO meta( name, uri, status, tag )
VALUES( '
Archives', 'archive', 1, 'page')
'
);
}
function uninstall() {
db()->exec('
DELETE FROM meta
WHERE uri = '
archive'
'
);
}
}

function 
archives() {}

« Last Edit: May 17, 2012, 01:22:15 PM by nukpana »
Logged

nukpana

  • Hero Member
  • *****
  • Karma: 71
  • Posts: 663
Re: Discussions on ideas for a core framework
« Reply #9 on: May 17, 2012, 12:59:00 PM »

As for file registration, As a site grows, I agree the overheads will become too great if checking was performed evrytime site was loaded. If table had filename and path/directory as data, and admin could be able to set an inspection routine going per directory (say one directory a day) to ensure nothing foreign has arrived. Seize and quarantine routines would move files elsewhere.
... and thinking on it more, would be best as an optional admin plugin ;)
I wouldn't as an plugin... something like that may be needed before release of the main script so all addons would have the same checking.  Hacker could focus on the addons without the keys if done as a plugin and it would bne more work for the addon author to add the keys, re-release the addon, then having the user/admin uninstall/reinstall.
Logged

nukpana

  • Hero Member
  • *****
  • Karma: 71
  • Posts: 663
Re: Discussions on ideas for a core framework
« Reply #10 on: May 19, 2012, 05:48:10 AM »

So a Archive plugin using the above ideas could be:
Code: [Select]
<?php

class Archive extends Ext {
function main() { # Using PHP 5.3 closures (anonymous functions)
$this->add('page_controller_hook', function() {
Route::add'/archive''archives' );
});
}

function install() {
db()->exec('
INSTALL INTO meta( name, uri, status, tag )
VALUES( '
Archives', 'archive', 1, 'page')
'
);
}
function uninstall() {
db()->exec('
DELETE FROM meta
WHERE uri = '
archive'
'
);
}
}

function 
archives() {}

I would like to go a bit further into the install/uninstall method of plugins.

So using this example of Archives, with the current system it would need to be part of the Pages array. Of course, this would be in the database. So, when the admin activates the plugin either by an include or through a backend, there has to be an install method that gets triggered - on the flipside an uninstall method, when a plugin is deactivated.

Since this thread is going a very OOP route, I will use the Observer pattern.
http://en.wikipedia.org/wiki/Observer_pattern
Code: [Select]
<?php

class 
Observable {
private $_event,
$_observers = array();

final function createEvent$event ) {
$this->_event = (string) $event;
$this->_notify();
}

final function getEvent() {
return $this->_event;
}

final function attachObserver $observer ) {
$this->_observers[] = $observer;
}

final private function _notify() {
foreach($this->_observers as $observer) {
$observer->update$this );
}
}
}

interface Observer {
function updateObservable $subject );
}

?>

What this is doing is the object can be observable (class Class extends Observable) and set events happening within it (Observable::setEvent).  It will also register other classes (Observable::attach) to observe the events happening (Observable::getEvent) and then react to the event (Observable::notify -> Observer::update).

The Observer interface is just a template, where the class that implements the template sets the update function, but the parameter has to be an instance of the Observable class.  You will see an example later on.

My edit is the setEvent/getEvent and having the notification automatically be added to the setEvent. One thing is the event key would probably be static/dynamic as shown below so all listeners don't react, unless that is what you want....

Now using a basic version of the extension code above, we can see the Observer/Observable in play:
Code: [Select]
<?php

final class 
Extension extends Observable {
protected $_active;

function __construct() {
$this->_active = array(
'TestOne',
'TestTwo'
);
}

final function load() {
foreach( $this->_active as $p ) {
if( class_exists($p) ) {
$i = new $p;
$i->commit();
$this->attach($i);  #from Observable
}
}


final protected function set$addon ) {
echo '<br>setting ' $addon;
$this->createEvent('installed_' $addon); #from Observable
}

final protected function delete$addon ) {
echo '<br>deleting' $addon;
$this->createEvent('uninstalled_' $addon); #from Observable
}

final function process$str ) {
switch (true) {
case (strpos($str'activate') === ):
$this->set(substr($str'9'));
break;
case (strpos($str'deactivate') === ):
$this->delete(substr($str'11'));
break;
}
}
}

abstract class Ext implements Observer {
private $_name;

final function __construct() {
$this->_name get_class($this);
}

abstract function main();

final function commit() {
$this->main();
}

final function updateObservable $subject ) {
switch( $subject->getEvent() ) {
case( 'installed_' $this->_name ):
if( method_exists($this'install') ) {
$this->install();
}
break;
case( 'uninstalled_' $this->_name  ):
if( method_exists($this'uninstall') ) {
$this->uninstall();
}
break;
}
}
}

?>

So what is happening here is the Extension class inherits the Observable functions.  When we load the plugin classes, we are attaching the plugins ($this->attach($i)) to listen to the events called - shown in the Extension::set  ($this->createEvent('installed_' . $addon);) & Extension::delete ($this->createEvent('uninstalled_' . $addon);)

Now, for the plugin author, the Ext class uses the Observer interface and uses the update method to react to the de/activate event to call a un/install routine.  The un/install methods are optional so if they require some un/install routine, they would add it.

Now in action:
Code: [Select]
<?php

class 
TestOne extends Ext {
function main() {}
function install() {
echo '<br>TestOne installing';
}
function uninstall() {
echo '<br>TestOne uninstalling';
}
}

class TestTwo extends Ext {
function main() {}
function install() {
echo '<br>TestTwo installing';
}
function uninstall() {
echo '<br>TestTwo uninstalling';
}
}

$e = new Extension;
$e->load();
if( isset($_GET['a']) ) {
$e->process($_GET['a']);
}

?>
Pretty similar to the previous post example? So the "plugin" classes  now have the install/unisntall methods.  The Extension class gets called and loads the plugins, and as shown will attached each plugin as a listener to the Extension's events.

Now, if I point my browser to : http://localhost/observer_test.php?a=activate_TestOne
The following will echo:
Quote
setting TestOne
TestOne installing
Likewise for uninstalling:
http://localhost/observer_test.php?a=deactivate_TestTwo
Quote
deletingTestTwo
TestTwo uninstalling

If you recall the echoing of "setting..." & "deleting..." comes from the Extension::set & Extension::delete methods.  As soon as they were called by the Extension::process method, they also created the event, which prompted the listener, TestOne & TestTwo to react to the given event key.
« Last Edit: May 19, 2012, 02:11:18 PM by nukpana »
Logged

nukpana

  • Hero Member
  • *****
  • Karma: 71
  • Posts: 663
Re: Discussions on ideas for a core framework
« Reply #11 on: May 28, 2012, 07:17:47 PM »

Memorial Day weekend here in the states and it's been busy (Wife had a race & I built a robot with Arduino, for starters)... Now some time for this project...

Part 1:
So I really like the ini file as a config and I wanted to use it with plugins and templates as an informational piece, than code.  It also reduces the code used to parse this information.

A sample plugin ini:
Code: [Select]
; Sample plugin.ini

[plugin.information]
name = ""
description = ""
author = ""
website = ""
version = ""

[plugin.dependencies]
requires[] = ""
requires[] = ""
requires[] = ""

[plugin.hooks]
hook_A = "callback"
hook_B[] = "callback_one"
hook_B[] = "callback_two"

Template could be:
Code: [Select]
[template.information]
name = ""
description = ""
author = ""
website = ""
version = ""
screenshot = ""

Now the author doesn't need to put $this->add('hook', callback), unless they need some prioritization or can use 5.3 to do inline functions call.

The code to use all of this is the get_ini function I noted earlier, but done differently:
Code: [Select]
<?php

function 
get_ini$key$file 'config.ini' ) {
$array parse_ini_file($fileTRUE);
if( isset($array[$key]) ) {
return $array[$key];
}
}

?>

Now, we can define the file being used... so:

Templates:
Code: (Snippet of Template class) [Select]
<?php

final function get() {
$dir opendir(TPL_PATH);
if ($dir) {
while (false !== ($file readdir($dir))) {
if( 
   $file != '.' 
&& $file != '..' 
&& file_exists(TPL_PATH'/' .$file '/''index.php')
&& file_exists(TPL_PATH'/' .$file '/''template.ini')
) {
$this->_list[$file] = $this->data(
TPL_PATH'/' .$file '/template.ini'
);
if( $file === $this->_active ) {
$this->_list[$file]['active'] = TRUE;
}
}
}
}
return $this->_list;
}

final protected function data$file ) {
$array = array();
$array['information'] = get_ini
'template.information',  
$file 
);
return strip_tags_deep($array);
}

?>


Plugin classes Ext & Extension in full. I want to show the Extension class getting the information from the ini file, then passing it to the Ext class, where it get processed - the hooks are not implemented, but you can see where we are getting the information from. 

Full Ext & Extension class
Code: [Select]
<?php

abstract class 
Ext implements Observer {
private $_name;
private $_data = array();
private $_hook = array();
private $_ext;

final function __construct() {
$this->_ext  ext();
$this->_name get_class($this);
$this->_data value(
$this->_name,
addon_get(new Extension)
);
}

abstract function main();

final function add$hook$callback$priority 10 ) {
$this->_hook[$hook][$priority][] = $callback;
}

function register_admin_module() {
$this->_data['admin_module'][] = $this->_name;
}

final private function _check_requirements() {
if( isset($this->_data['dependencies']['requires']) ) {
$requirement $this->_data['dependencies']['requires'];
foreach( $requirement as $dep ) {
if( !isset($this->_ext['ext'][$dep]) ) {
$this->_data['req_error'] = TRUE;
}
}
}
}

final function commit() {
$this->_check_requirements();
$this->main();

if(    !isset($this->_data['req_error']) 
|| $this->_data['req_error'] === FALSE 
) {

$this->_ext['ext'][$this->_name] = $this->_data;


foreach ($this->_hook as $hook => $calls) {
foreach( $calls as $k => $call ) {
foreach( $call as $func ) {
$this->_ext['hook'][$hook][$k] = $func;
}
}
}
ksort_recursive($this->_ext['hook']);
}
Registry::set('ext'$this->_ext);
}

final function updateObservable $subject ) {
switch( $subject->getEvent() ) {
case( 'activated_' $this->_name ):
if( method_exists($this'install') ) {
$this->install();
}
break;
case( 'deactivated_' $this->_name  ):
if( method_exists($this'uninstall') ) {
$this->uninstall();
}
break;
}
}
}

function ext_commit(Ext $ext) {
$ext->commit();

// Log this transaction
$e ext();
$e['ext'][get_class($ext)]['internal_commit'] = TRUE
Registry::set('ext'$e);
}

final class Extension extends AddOn {

function __construct() {
$this->_active ExtCRUD::get();
}

final function load() {
foreach( $this->_active as $p ) {

$file EXT_PATH'/' .$p'/index.php';

if( file_exists$file ) ) {
require( $file );

if( class_exists($p) ) {

$i = new $p;
$i->commit();
// Attach the observer
$this->attach($i);
}
}
}


final function get() {
$dir opendir(EXT_PATH);
if( $dir ) {
while( false !== ($file readdir($dir)) ) {
if( 
   $file != '.' 
&& $file != '..' 
&& file_exists(EXT_PATH'/' .$file'/index.php')
&& file_exists(EXT_PATH'/' .$file'/plugin.ini')
) {

$this->_list[$file] = $this->data(
EXT_PATH'/' .$file'/plugin.ini'
);

if( in_array($file$this->_active) ) {
$this->_list[$file]['active'] = TRUE;
}

}
}
}
return $this->_list;
}

final protected function data$file ) {
$array = array();
$array['information'] = get_ini'plugin.information',  $file );
$array['dependencies']= get_ini'plugin.dependencies'$file );
$array['hooks'   = get_ini'plugin.hooks' $file );
return strip_tags_deep($array);
}

final protected function set$addon ) {
ExtCRUD::set$addon );
$this->createEvent('activated_' $addon);
}

final protected function delete$addon ) {
ExtCRUD::delete$addon );
$this->createEvent('deactivated_' $addon);
}

final function process$str ) {
switch (true) {
case (strpos($str'activate') === ):
$this->set(substr($str'9'));
break;
case (strpos($str'deactivate') === ):
$this->delete(substr($str'11'));
break;
}
}
}

?>

Side Note:
Also note, I implemented the Observer class in the above post and a check in ext_commit when an internal plugin commits itself, without going through the normal function. Here you can do a simple print_r(ext()) and see what plugins are registering themselves to run. Example:

Code: (print_r(ext()) example) [Select]
Array
(
    [ext] => Array
        (
            [getLanguages] => Array
                (
                    [information] => Array
                        (
                            [name] => getLanguages
                            [description] => Collects all translated string and puts them in an array
                            [author] => Me
                            [version] => 0.1
                        )

                    [dependencies] =>
                    [hooks] => Array
                        (
                            [script_end] => language_array
                        )

                    [active] => 1
                )

            [Login] => Array
                (
                    [admin_module] => Array
                        (
                            [0] => Login
                        )

                    [internal_commit] => 1
                )

            [Logout] => Array
                (
                    [admin_module] => Array
                        (
                            [0] => Logout
                        )

                    [internal_commit] => 1
                )

            [Post] => Array
                (
                    [admin_module] => Array
                        (
                            [0] => Post
                        )

                    [internal_commit] => 1
                )

        )

    [hook] => Array
        (
            [admin_menu] => Array
                (
                    [10] => addPostToAdminMenu
                    [999] => addLogoutToAdminMenu
                )

        )

)

Side Note 2
If you look at the print_r break down, you notice Login, Logout and Post (Pages) are plugins. They are like plugins where each is a separate section ie  Logout below - it has the plugin class block that defines itself as a plugin.  I added a method to register admin top level modules, then I have each module handle it's own REST parsing so..:

When the url looks like: http://localhost/testCMS/?/admin it will go to this function first to delegate where it needs to go:
Code: [Select]
<?php

function 
AdminController() {
$modules registered_admin_modules();
if( uri_part(1) ) {
if( ADMIN ) {
if( isset($modules[uri_part(1)]) ) {
$fn ucfirst(uri_part(1)) . 'Controller';
$fn();
}
} else {
LoginController();
}
}
}

?>


Code: [Select]
<?php

class 
Logout extends Ext {
function main() {
$this->register_admin_module();

$this->add('admin_menu''addLogoutToAdminMenu'999);
}
}

ext_commit(new Logout);


function addLogoutToAdminMenu$array ) {
$array[] = array(
'url' => build_url('admin''logout'),
'title' => __('Logout')
);
return $array;
}


function LogoutController() {
logout();
}

function logout() {
    session_destroy();
echo '<meta http-equiv="refresh" content="1; url='build_url() .'">';
}

?>


Part 2 - Router:
So I redid the Router to the below.  My testing didn't prove well for the functions I posted in an earlier post.  This is almost a straight copy/past to the linked class with the exception of removing methods and adding some minor things.  Still testing it though....
Code: [Select]
<?php

// Modified from http://brandonwamboldt.ca/my-php-router-class-825/
final class Router {
private $_callback,
$_default_route NULL,
$_params = array(),
$_routes = array(),
$_uri;

function __construct$uri ) {
$this->_uri $uri;
}

function default_route$callback ) {
$this->_default_route $callback;
}

function run() {
$matched_route FALSE;
ksort$this->_routes );
foreach ( $this->_routes as $priority => $routes ) {
foreach ( $routes as $route => $callback ) {
if ( preg_match$route$this->_uri$matches ) ) {

$matched_route TRUE;
$params NULL;
foreach ( $matches as $key => $match ) {
if ( is_string$key ) ) {
$params[$key] = $match;
}
}
$this->_params $params;
$this->_callback $callback;
}
}
}
if(    $matched_route === FALSE 
&& $this->_default_route !== NULL 
) {
$this->_callback $this->_default_route;
}
}

    function dispatch() {
        $fn $this->_callback;
        $fn$this->_params );
    }
    function execute() {
        $this->run();
        $this->dispatch();
    }
    
    function route$route$callback$priority 10 ) {
$route ltrim$route'/' );

// Custom, format: <:var_name|regex>
$route preg_replace
'/\<\:(.*?)\|(.*?)\>/'
'(?P<\1>\2)'
$route 
);

// Alphanumeric, format: <:var_name>
$route preg_replace
'/\<\:(.*?)\>/'
'(?P<\1>[A-Za-z0-9\-\_]+)'
$route 
);

// Numeric, format: <#var_name>
$route preg_replace
'/\<\#(.*?)\>/'
'(?P<\1>[0-9]+)'
$route 
);

// Wildcard (INCLUDING dir separators), format: <*var_name>
$route preg_replace
'/\<\*(.*?)\>/'
'(?P<\1>.+)'
$route 
);

// Wildcard (EXCLUDING dir separators), format: <!var_name>
$route preg_replace
'/\<\!(.*?)\>/'
'(?P<\1>[^\/]+)'
$route 
);

// Add regex for a full match or no match
$route '#^' $route '$#';

        // Add the route to our routing array
        $this->_routes[$priority][$route] = $callback;
        return TRUE;
}
}

function add_route($route$callback$priority 10$default_route NULL) {
$rtr = new Router(_prepare_uri());
$rtr->route($route$callback$priority);
$rtr->default_route($default_route);
$rtr->execute();
}

?>

An example - Login function:
Code: [Select]
<?php <?php

class 
Login extends Ext {
function main() {
$this->register_admin_module();
}
}
ext_commit(new Login);

function LoginController() {
switch( true ) {
case( uri_part(2) ):
add_route('admin/login/process''process_login');
break;
default:
login();
break;
}
}

function login() {
if( !ADMIN ) {
?>

<h2>Login</h2>
<form method="post" action="<?php echo build_url('admin''login''process'); ?>">
<p>
<?php echo __('Email'); ?>:
<input type="text" name="email" />
</p>
<p>
<?php echo __('Password'); ?>:
<input type="password" name="password" />
</p>
<p>
<input type="submit" name="submit" value="<?php echo __('Login'); ?>">
</p>
</form>
<?php
}
}

function process_login() {
if( isset($_POST['submit']) ) {
$sth db()->query("SELECT * FROM users");
$sth->execute();
$r $sth->fetch(PDO::FETCH_ASSOC);
if(    hash('whirlpool'$_POST['email']) === $r['email'
&& hash('whirlpool'$_POST['password']) === $r['password']
) {
$_SESSION['Logged_In'] = "True";
echo '<meta http-equiv="refresh" content="0; url='.build_url().'">';
} else {
echo '<meta http-equiv="refresh" content="0; url='.build_url('admin''login').'">';
}
}


?>

Example 2:
Code: [Select]
<?php


function PostController() {
switch( true ) {
# /admin/post/1/edit
case( uri_part(3) ):
add_route(
'/admin/post/<#id>/<:action>',
'process_post'
);
break;

# /admin/post/1
case( uri_part(2) ):
switch( true ) {
case( is_numeric(uri_part(2)) ):
add_route(
'/admin/post/<#id>',
'edit_post'
);
break;
case( uri_part(2) == 'process' ):
add_route(
'/admin/post/process',
'process_post'
);
break;
# http://localhost/testCMS/?/admin/post/all
# http://localhost/testCMS/?/admin/post/6-10
default:
add_route(
'/admin/post/<:id>',
'get_posts'
);
break;
}
break;

# /admin/post
case( uri_part(1) ):
add_route('/admin/post''new_post');
break;
}
}

?>

Part 3 Url rewrite & non rewrite.


I noted here, #6 controller regarding the url structure.  Well I recall having a discussion many moons ago that a switch, if done, should be in a config file. 

So I did it.

The main config would include this entry:
Code: [Select]
; Rewrite - change to TRUE if mod_rewrite is installed
; removes ?/ from internal urls ie:
; http://localhost/cms/?/admin/post/2/edit rewrite.url = FALSE
; http://localhost/cms/admin/post/2/edit rewrite.url = TRUE
rewrite.url = "FALSE";

Then build_url would be this:
Code: [Select]
<?php

function 
build_url() {
$str  'http://' $_SERVER['HTTP_HOST'];
$str .= str_replace(
'index.php'
''
$_SERVER['SCRIPT_NAME']
);
$qs get_ini('rewrite.url') === TRUE
''
'?/';
$args func_get_args();
if( !empty($args) ) {
$args implode('/'$args);
return $str $qs $args;
} else {
return $str;
}
}

?>
« Last Edit: May 28, 2012, 09:57:52 PM by nukpana »
Logged

nukpana

  • Hero Member
  • *****
  • Karma: 71
  • Posts: 663
Re: Discussions on ideas for a core framework
« Reply #12 on: June 07, 2012, 06:10:14 AM »

The validation base code is pretty much done - unfinished, but closer to finished than before.  This code is close to the idea I had in the OP.  It also was influenced by the Zend Validator classes as well.  Anyway, take a look:
Code: [Select]
<?php

abstract class 
Validator {
protected $_errorMsg 'undefined';

    function getError() {
     return $this->_errorMsg;
    }

abstract function validate($value);
}

class Validate {
private $_data = array(),
$_errors = array();

function addValidator($fieldValidator $obj) {
$this->_data[$field][] = $obj;
return $this;
}

function isValid($array) {
$valid true;

foreach( $this->_data as $field => $objects ) {
foreach($objects as $i => $obj) {
if( !$obj->validate($array[$field]) ) {
$valid false;
$this->_errors[] = array(
'field' => $field,
'error' => $obj->getError()
);
break;
}
}
}
return $valid;
}

function getErrors() {
return $this->_errors;
}
}


We have 2 classes, one to base our validators from and the other is the main class bringing everything together.

Validator is the base abstract class.  Our classes that extend this class only needs the validate function, but the errorMsg variable is there to let the user know why the value is not being good.

Validate is used when the form is submitted.  addValidation takes the field's value and applies it to the Validator sub class.  addValidation returns $this to allow chaining. isValid will check each field against the set validators.  It will stop when an error occurs and calls the errorMessage when it is called.

Using the Validator, we can define our error message and the validate function like so:
Code: [Select]
<?php

class 
IsBlank extends Validator {
protected $_errorMsg 'Sorry, you left this blank';

function validate($value) {
return (!empty($value) || $value !== '')
true
false;
}
}

class IsEmail extends Validator {
protected $_errorMsg 'Sorry, the email is invalid';

function validate($value) {
return filter_var($valueFILTER_VALIDATE_EMAIL);
}
}


Let's use a form...
Code: [Select]
<form method="post" action="testValidator01.php">
<p>Name: <input type="text" name="name" value="<?php echo isset($_POST['name']) ? $_POST['name'] : ''?>"></p>
<p>Email: <input type="text" name="email" value="<?php echo isset($_POST['email']) ? $_POST['email'] : ''?>"></p>
<input type="submit" name="submit" value="Submit">
</form>

And validate it on submit:
Code: [Select]
<?php

if( isset(
$_POST['submit']) ) {
$validation = new Validate;
$validation->addValidator('name', new IsBlank);

$validation->addValidator('email',new IsBlank)
   ->addValidator('email',new IsEmail);

if( !$validation->isValid($_POST) ) {
print_r($validation->getErrors());
} else {
echo 'Ok to go!';
}
}
So going back a bit, we can see the chaining in the addValidator routine in the email field.  IsBlank gets checked first, then IsEmail. If IsBlank catches an error, the isValid method will stop at that error and put the error and field name in an error to be returned later (getErrors())
Logged

nukpana

  • Hero Member
  • *****
  • Karma: 71
  • Posts: 663
Re: Discussions on ideas for a core framework
« Reply #13 on: June 07, 2012, 10:50:22 AM »

And here is the updated framework code:

Code: [Select]
<?php 

error_reporting(-1);

session_start();

#-----------------------------------------------------------------------------------
#---------- Helper Functions -------------------------------------------------------
#-----------------------------------------------------------------------------------

function get_ini$key$file 'config.ini' ) {
$array parse_ini_file($fileTRUE);
if( isset($array[$key]) ) return $array[$key];
}

function db() {
$db_conn FALSE;
try {
$db_conn = new PDO(
get_ini('database.dsn'),
get_ini('database.user'),
get_ini('database.pass')
);
} catch( PDOException $e ) {
die( $e->getMessage() );
}
return $db_conn;
}

// Modified version of http://php.net/manual/en/function.ksort.php#105399
function ksort_recursive(array &$array) {
ksort($array);
foreach( $array as &$a ) {
if( is_array($a) ) ksort_recursive($a);
}
}

// From http://us3.php.net/manual/en/function.stripslashes.php Example #2
function stripslashes_deep($value) {
return is_array($value)
array_map('stripslashes_deep'$value)
stripslashes($value);
}

// From http://www.php.net/manual/en/function.strip-tags.php#62705
function strip_tags_deep($value) {
return is_array($value
array_map('strip_tags_deep'$value
strip_tags($value);
}

// Based from https://wiki.php.net/rfc/functionarraydereferencing
function value($key$array) {
if( isset($array[$key]) ) return $array[$key];
}

// Remove index.php, and duplicate slashes from $_SERVER['REQUEST_URI']
// Based on http://brandonwamboldt.ca/my-php-router-class-825/
function _prepare_uri() {
$uri $_SERVER['REQUEST_URI'];
$uri str_replacedirname($_SERVER['PHP_SELF']), ''$uri );
$uri str_replace('index.php'''$uri);
$uri str_replace('?/'''$uri);
$uri preg_replace'/\/+/''/'$uri );
$uri ltrim$uri'/' );

return $uri;
}

function parse_uri($component null) {
$array = array();

$uri _prepare_uri();
$uri parse_url($uri);

if( isset($uri['path']) ) {
$uri['path'] = trim($uri['path'], '/');
$array['path'] = explode('/'$uri['path']);
}
if( isset($uri['query']) ) {
parse_str($uri['query'], $array['query']);
}

return isset($array[$component])
$array[$component]
$array;
}

function uri_part($num) {
return value($numparse_uri('path'));
}

function uri_str($key) {
return value($keyparse_uri('query'));
}

function build_url() {
$str  'http://' $_SERVER['HTTP_HOST'];
$str .= str_replace'index.php'''$_SERVER['SCRIPT_NAME'] );
$qs get_ini('rewrite.url') === TRUE
''
'?/';
$args func_get_args();

if( !empty($args) ) {
$args implode('/'$args);
return $str $qs $args;
} else {
return $str;
}
}

#-----------------------------------------------------------------------------------
#---------- Router Class -----------------------------------------------------------
#-----------------------------------------------------------------------------------

// Modified from http://brandonwamboldt.ca/my-php-router-class-825/
final class Router {
private $_callback,
 $_params = array(),
 $_routes = array(),
 $_uri;

function __construct$uri ) {
$this->_uri $uri;
}

    function route$route$callback ) {
$route ltrim$route'/' );

// Custom, format: <:var_name|regex>
$route preg_replace'/\<\:(.*?)\|(.*?)\>/''(?P<\1>\2)'$route );

// Alphanumeric, format: <:var_name>
$route preg_replace'/\<\:(.*?)\>/''(?P<\1>[A-Za-z0-9\-\_]+)'$route );

// Numeric, format: <#var_name>
$route preg_replace'/\<\#(.*?)\>/''(?P<\1>[0-9]+)'$route );

// Wildcard (INCLUDING dir separators), format: <*var_name>
$route preg_replace'/\<\*(.*?)\>/''(?P<\1>.+)'$route );

// Wildcard (EXCLUDING dir separators), format: <!var_name>
$route preg_replace'/\<\!(.*?)\>/', '(?P<\1>[^\/]+)'$route );

// Add regex for a full match or no match
$route '#^' $route '$#';

        // Add the route to our routing array
        $this->_routes[$route] = $callback;
}

    function execute() {
foreach ( $this->_routes as $route => $callback ) {
if ( preg_match$route$this->_uri$matches ) ) {
foreach ( $matches as $key => $match ) {
if ( is_string$key ) ) {
$this->_params[$key] = $match;
}
}
$this->_callback $callback;
}
}
    if ( $this->_callback === NULL) return;

    $fn $this->_callback;
    $fn$this->_params );
    }
}

function add_route($route$callback) {
$rtr = new Router(_prepare_uri());
$rtr->route($route$callback);
$rtr->execute();
}

#-----------------------------------------------------------------------------------
#---------- Registry Class ---------------------------------------------------------
#-----------------------------------------------------------------------------------

final class Registry {
private static $_data = array();

static function get$key ) {
if( isset(self::$_data[$key]) ) {
return self::$_data[$key];
}
}

static function set$key$value '' ) {
self::$_data[$key] = $value;
}
}

#-----------------------------------------------------------------------------------
#---------- Observable & Observer classes ------------------------------------------
#-----------------------------------------------------------------------------------

// Observable & Observer classes
class Observable {
private $_event,
 $_observers = array();

final function createEvent$event ) {
$this->_event = (string) $event;
$this->_notify();
}

final function getEvent() {
return $this->_event;
}

final function attachObserver $observer ) {
$i array_search($observer$this->_observers);
if ($i === false) {
$this->_observers[] = $observer;
}
}

final private function _notify() {
foreach($this->_observers as $observer) {
$observer->update$this );
}
}
}

interface Observer {
function updateObservable $subject );
}

#-----------------------------------------------------------------------------------
#---------- AddOn Template ---------------------------------------------------------
#-----------------------------------------------------------------------------------

// AddOn class template & helper functions
abstract class AddOn extends Observable {
protected $_active,
  $_list = array();

abstract function load();
abstract function get();
abstract function process($str);

abstract protected function set($addon);
}

function addon_init(AddOn $addon) {
$addon->load();
}

function addon_get(AddOn $addon) {
return $addon->get();
}

function addon_process(AddOn $addon$str) {
$addon->process($str);
}

#-----------------------------------------------------------------------------------
#---------- Settings CRUD Class ----------------------------------------------------
#-----------------------------------------------------------------------------------

final class Settings {
private static $_data = array(),
   $_db;

static function init$db ) {
self::$_db $db;
$sql 'SELECT name, value FROM settings';
foreach ( self::$_db->query($sql) as $r ) {
self::$_data[$r['name']] = $r['value'];
}
}

static function get$key ) {
if( self::exists($key) ) {
return self::$_data[$key];
}
}

static function set$key$value ) {
if( self::exists($key) ) {
$sql "UPDATE settings 
SET value = :value 
WHERE name = :key"
;
} else {
$sql "INSERT INTO settings 
VALUES(:key, :value)
LIMIT 1"
;
}
$sth self::$_db->prepare($sql);
$sth->bindParam(':key'$keyPDO::PARAM_STR);
$sth->bindParam(':value'$valuePDO::PARAM_STR);
$sth->execute();
}

static function delete$key ) {
if( self::exists($key) ) {
$sth self::$_db->query("
DELETE 
FROM settings 
WHERE name = :key
"
);
$sth->bindParam(':key'$keyPDO::PARAM_STR);
$sth->execute();
}
}

static function exists$key ) {
return array_key_exists($keyself::$_data);
}
}
// initialize settings
Settings::init(db());

#-----------------------------------------------------------------------------------
#---------- Language Helper --------------------------------------------------------
#-----------------------------------------------------------------------------------

define'LANG_PATH'get_ini('path.languages') );

Registry::set('lang', array());

function lang() {
return Registry::get('lang');
}

final class Languages extends AddOn {

final function __construct() {
$this->_active Settings::get('language');
}

final function load() {
$array = array();
$file LANG_PATH .'/'$this->_active '.php';
if( file_exists($file) ) {
$array = require $file;
}
Registry::set('lang'$array);


final function get() {
$dir opendir(LANG_PATH);
if ($dir) {
while (false !== ($file readdir($dir))) {
if (
$file != '.' && 
$file != '..' && 
file_exists(LANG_PATH .'/'$file)
) {
$filename basename($file'.php');
$this->_list[$filename] = array();

if( $filename === $this->_active ) {
$this->_list[$filename]['active'] = TRUE;
}
}
}
}
return $this->_list;
}

final protected function set$addon ) {
Settings::set'language'$addon );
}

final function process$str ) {
$this->set$str );
}
}
// initialize languages
addon_init(new Languages);

// Function to translate strings 
// Uses http://php.net/manual/en/function.strtr.php
// Do not translate placeholder
function __$string, array $args NULL ) {
$string value($stringlang())
value($stringlang())
$string;

return $args === null
$string
strtr($string$args);
}

#-----------------------------------------------------------------------------------
#---------- Plugin Helper ----------------------------------------------------------
#-----------------------------------------------------------------------------------

// Extensions/Plugins 
define'EXT_PATH'get_ini('path.extensions') );

Registry::set('ext'
array(
'ext' => array(),
'hook'=> array()
)
);

function ext() {
return Registry::get('ext');
}

final class Extension {
private static $_data = array();
private static $_db;

static function init$db ) {
self::$_db $db;
$sql 'SELECT name FROM plugins';
foreach ( self::$_db->query($sql) as $r ) {
self::$_data[] = $r['name'];
}
}

static function get() {
return self::$_data;
}

static function add$name ) {
if( !self::exists($name) ) {
$sth self::$_db->query("
INSERT INTO plugins 
VALUES(:name)
LIMIT 1
"
);
$sth->bindParam(':name'$namePDO::PARAM_STR);
$sth->execute();
}
}

static function delete$name  ) {
if( self::exists($name) ) {
$sth self::$_db->query("
DELETE 
FROM plugins
WHERE name = :name 
"
);
$sth->bindParam(':name'$namePDO::PARAM_STR);
$sth->execute();
}
}

static function exists$name ) {
return in_array($nameself::$_data); 
}
}
Extension::init(db());

abstract class Ext implements Observer {
protected $_name;
protected $_data = array();
private $_hook = array();
private $_ext;

final function __construct() {
$this->_ext  ext();
$this->_name get_class($this);
$this->_data value(
$this->_name,
addon_get(new Extensions)
);
}

abstract function main();

final function add$hook$callback$priority 10 ) {
$this->_hook[$hook][$priority][] = $callback;
}

final private function _check_requirements() {
if( isset($this->_data['dependencies']['requires']) ) {
$requirement $this->_data['dependencies']['requires'];
foreach( $requirement as $dep ) {
if( !isset($this->_ext['ext'][$dep]) ) {
$this->_data['req_error'] = TRUE;
}
}
}
}

final function commit() {

$this->_check_requirements();
$this->main();

if(    !isset($this->_data['req_error']) 
|| $this->_data['req_error'] === FALSE 
) {

$this->_ext['ext'][$this->_name] = $this->_data;

if( isset($this->_data['hooks']) ) {
foreach( $this->_data['hooks'] as $hook => $calls ) {
if( is_array($calls) ) {
foreach( $calls as $k => $func ) {
$this->_ext['hook'][$hook][10][] = $func;
}
} else {
$this->_ext['hook'][$hook][10][] = $calls;
}
}
}

foreach ($this->_hook as $hook => $calls) {
foreach( $calls as $k => $call ) {
foreach( $call as $func ) {
$this->_ext['hook'][$hook][$k][] = $func;
}
}
}
ksort_recursive($this->_ext['hook']);
}
Registry::set('ext'$this->_ext);
}

final function updateObservable $subject ) {
switch( $subject->getEvent() ) {
case( 'activated_' $this->_name ):
if( method_exists($this'install') ) {
$this->install();
}
break;
case( 'deactivated_' $this->_name  ):
if( method_exists($this'uninstall') ) {
$this->uninstall();
}
break;
}
}
}

function ext_commit(Ext $ext) {
$ext->commit();

// Log this transaction
$e ext();
$e['ext'][get_class($ext)]['internal_commit'] = TRUE
Registry::set('ext'$e);
}

final class Extensions extends AddOn {

function __construct() {
$this->_active Extension::get();
}

final function load() {
foreach( $this->_active as $p ) {

$file EXT_PATH'/' .$p'/index.php';

if( file_exists$file ) ) {
require( $file );

if( class_exists($p) ) {

$i = new $p;
$i->commit();
// Attach the observer
$this->attach($i);
}
}
}


final function get() {
$dir opendir(EXT_PATH);
if( $dir ) {
while( false !== ($file readdir($dir)) ) {
if( 
   $file != '.' 
&& $file != '..' 
&& file_exists(EXT_PATH'/' .$file'/index.php')
&& file_exists(EXT_PATH'/' .$file'/plugin.ini')
) {

$this->_list[$file] = $this->data(
EXT_PATH'/' .$file'/plugin.ini'
);

if( in_array($file$this->_active) ) {
$this->_list[$file]['active'] = TRUE;
}
}
}
}
return $this->_list;
}

final protected function data$file ) {
$array = array();
$array['information'] = get_ini'plugin.information', $file );
$array['dependencies']= get_ini'plugin.dependencies', $file );
$array['hooks'   = get_ini'plugin.hooks' $file );
return strip_tags_deep($array);
}

final protected function set$addon ) {
Extension::set$addon );
$this->createEvent('activated_' $addon);
}

final protected function delete$addon ) {
Extension::delete$addon );
$this->createEvent('deactivated_' $addon);
}

final function process$str ) {
switch (true) {
case (strpos($str'activate') === ):
$this->set(substr($str'9'));
break;
case (strpos($str'deactivate') === ):
$this->delete(substr($str'11'));
break;
}
}
}
addon_init(new Extensions);

function exec_ext$hook$var NULL ) {
$ext ext();

if( isset($ext['hook'][$hook]) ) { 
foreach( $ext['hook'][$hook] as $k => $calls ) {
foreach( $calls as $func ) {
$var $func($var);
}
}
}
return $var;
}

#-----------------------------------------------------------------------------------
#---------- Template Helper --------------------------------------------------------
#-----------------------------------------------------------------------------------

// Templates
define'TPL_PATH', get_ini('path.templates') );

final class Templates extends AddOn {
final function __construct() {
$this->_active Settings::get('template');
}

final function load() {
$file TPL_PATH .'/'$this->_active '/index.php';
if( file_exists($file) ) require($file);


final function get() {
$dir opendir(TPL_PATH);
if( $dir ) {
while( false !== ($file readdir($dir)) ) {
if( 
   $file != '.' 
&& $file != '..' 
&& file_exists(TPL_PATH'/' .$file '/''index.php')
&& file_exists(TPL_PATH'/' .$file '/''template.ini')
) {
$this->_list[$file] = $this->data(
TPL_PATH'/' .$file '/template.ini'
);
if( $file === $this->_active ) {
$this->_list[$file]['active'] = TRUE;
}
}
}
}
return $this->_list;
}

final protected function data$file ) {
$array = array();
$array['information'] = get_ini
'template.information',  
$file 
);
return strip_tags_deep($array);
}

final protected function set$addon ) {
Settings::set'template'$addon );
}

final function process$str ) {
if( strpos($str'activate') === ) {
$this->set(substr($str'9'));
}
}
}

#-----------------------------------------------------------------------------------
#---------- Form Validator Class ---------------------------------------------------
#-----------------------------------------------------------------------------------

abstract class Validator {
protected $_errorMsg 'undefined';

    function getError() {
     return $this->_errorMsg;
    }

abstract function validate($value);
}

class Validate {
private $_data = array(),
$_errors = array();

function addValidator($fieldValidator $obj) {
$this->_data[$field][] = $obj;
return $this;
}

function isValid($array) {
$valid true;

foreach( $this->_data as $field => $objects ) {
foreach($objects as $i => $obj) {
if( !$obj->validate($array[$field]) ) {

$valid false;
$this->_errors[] = array(
'field' => $field,
'error' => $obj->getError()
);
break;
}
}
}
return $valid;
}

function getErrors() {
return $this->_errors;
}
}

I removed the admin code from the Ext class, so the core admin functions look like:
Code: [Select]
<?php

#-----------------------------------------------------------------------------------
#---------- Admin System -----------------------------------------------------------
#-----------------------------------------------------------------------------------

define('ADMIN', isset($_SESSION['Logged_In']));

abstract class AdminModule extends Ext {
function register_admin_module() {
$this->_data['admin_module'][] = $this->_name;
}
}

function registered_admin_modules() {
$modules = array();
foreach( value('ext'ext()) as $ext ) {
if( isset($ext['admin_module'][0]) ) {
$modules[] = strtolower(
$ext['admin_module'][0]
);
}
}
$modules array_flip($modules);
return $modules;
}

// Incomplete
function AdminController() {
$modules registered_admin_modules();
if( uri_part(1) ) {
if( ADMIN ) {
if( isset($modules[uri_part(1)]) ) {
$fn ucfirst(uri_part(1)) . 'Controller';
$fn();
}
} else {
LoginController();
}
}
}


I am rethinking the database - redoing the meta/content tables to just the single function/single table ie (pages table, categories table, etc)... but for now, off to bed!
« Last Edit: June 10, 2012, 02:31:26 AM by nukpana »
Logged

nukpana

  • Hero Member
  • *****
  • Karma: 71
  • Posts: 663
Re: Discussions on ideas for a core framework
« Reply #14 on: June 16, 2012, 12:15:43 AM »

A weekly update to this. Mind you, this is still very much concept/alpha state code ie. Don't use in production or anything.

Database:
mySQL:
Code: [Select]
CREATE TABLE plugins (
name VARCHAR(255) NOT NULL,
UNIQUE (name)
) ENGINE = InnoDB;

CREATE TABLE settings (
name VARCHAR(255) NOT NULL,
value VARCHAR(255) NOT NULL,
INDEX (name),
UNIQUE (name)
) ENGINE = InnoDB;

-- Example
INSERT INTO settings VALUES('template', 'Default');

CREATE TABLE users (
id INT(8) NOT NULL AUTO_INCREMENT,
email VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,

PRIMARY KEY (id),
UNIQUE (email)
) ENGINE = InnoDB;

CREATE TABLE pages (
id INT(11) AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
uri VARCHAR(255) NOT NULL,
status INT(1) NOT NULL DEFAULT '1',
seq INT(5) NOT NULL DEFAULT '1', -- sequence
text LONGTEXT NOT NULL,

PRIMARY KEY (id),
INDEX (
uri,
status,
seq
),
UNIQUE (uri)
) ENGINE = InnoDB;

Core: - Not much has changed. I am using this as my test index.php hence the first 3 lines.  The Extension initialization starts later due to admin functions needing to be available...
Code: ("Test Index.php") [Select]
<?php

error_reporting(-1);

session_start();

define('ADMIN', isset($_SESSION['Logged_In']));

#-------------------------------------------------------------------------------
#---------- Helper Functions ---------------------------------------------------
#-------------------------------------------------------------------------------

function get_ini$key$file 'config.ini' ) {
$array parse_ini_file($fileTRUE);
if( isset($array[$key]) ) return $array[$key];
}

function db() {
$db_conn FALSE;
try {
$db_conn = new PDO(
get_ini('database.dsn'),
get_ini('database.user'),
get_ini('database.pass')
);
} catch( PDOException $e ) {
die( $e->getMessage() );
}
return $db_conn;
}

// Modified version of http://php.net/manual/en/function.ksort.php#105399
function ksort_recursive(array &$array) {
ksort($array);
foreach( $array as &$a ) {
if( is_array($a) ) ksort_recursive($a);
}
}

// From http://us3.php.net/manual/en/function.stripslashes.php Example #2
function stripslashes_deep($value) {
return is_array($value)
array_map('stripslashes_deep'$value)
stripslashes($value);
}

// From http://www.php.net/manual/en/function.strip-tags.php#62705
function strip_tags_deep($value) {
return is_array($value
array_map('strip_tags_deep'$value
strip_tags($value);
}

// Based from https://wiki.php.net/rfc/functionarraydereferencing
function value($key$array) {
if( isset($array[$key]) ) return $array[$key];
}

// Remove index.php, and duplicate slashes from $_SERVER['REQUEST_URI']
// Based on http://brandonwamboldt.ca/my-php-router-class-825/
function _prepare_uri() {
$uri $_SERVER['REQUEST_URI'];
$uri str_replacedirname($_SERVER['PHP_SELF']), ''$uri );
$uri str_replace('index.php'''$uri);
$uri str_replace('?/'''$uri);
$uri preg_replace'/\/+/''/'$uri );
$uri ltrim$uri'/' );

return $uri;
}

function parse_uri($component null) {
$array = array();

$uri _prepare_uri();
$uri parse_url($uri);

if( isset($uri['path']) ) {
$uri['path'] = trim($uri['path'], '/');
$array['path'] = explode('/'$uri['path']);
}
if( isset($uri['query']) ) {
parse_str($uri['query'], $array['query']);
}

return isset($array[$component])
$array[$component]
$array;
}

function uri_part($num) {
return value($numparse_uri('path'));
}

function uri_qstr($key) {
return value($keyparse_uri('query'));
}

function build_url() {
$str  'http://' $_SERVER['HTTP_HOST'];
$str .= str_replace'index.php'''$_SERVER['SCRIPT_NAME'] );
$qs get_ini('rewrite.url') === TRUE
''
'?/';
$args func_get_args();

if( !empty($args) ) {
$args implode('/'$args);
return $str $qs $args;
} else {
return $str;
}
}

#-------------------------------------------------------------------------------
#---------- Router Class -------------------------------------------------------
#-------------------------------------------------------------------------------

// Modified from http://brandonwamboldt.ca/my-php-router-class-825/
final class Router {
private $_callback,
$_params = array(),
$_routes = array(),
$_uri;

function __construct$uri ) {
$this->_uri $uri;
}

    function route$route$callback ) {
$route ltrim$route'/' );

// Custom, format: <:var_name|regex>
$route preg_replace'/\<\:(.*?)\|(.*?)\>/''(?P<\1>\2)'$route );

// Alphanumeric, format: <:var_name>
$route preg_replace'/\<\:(.*?)\>/''(?P<\1>[A-Za-z0-9\-\_]+)'$route );

// Numeric, format: <#var_name>
$route preg_replace'/\<\#(.*?)\>/''(?P<\1>[0-9]+)'$route );

// Wildcard (INCLUDING dir separators), format: <*var_name>
$route preg_replace'/\<\*(.*?)\>/''(?P<\1>.+)'$route );

// Wildcard (EXCLUDING dir separators), format: <!var_name>
$route preg_replace'/\<\!(.*?)\>/', '(?P<\1>[^\/]+)'$route );

// Add regex for a full match or no match
$route '#^' $route '$#';

        // Add the route to our routing array
        $this->_routes[$route] = $callback;
}

    function execute() {
foreach ( $this->_routes as $route => $callback ) {
if ( preg_match$route$this->_uri$matches ) ) {
foreach ( $matches as $key => $match ) {
if ( is_string$key ) ) {
$this->_params[$key] = $match;
}
}
$this->_callback $callback;
}
}
    if ( $this->_callback === NULL) return;

    $fn $this->_callback;
    $fn$this->_params );
    }
}

function add_route($route$callback) {
$rtr = new Router(_prepare_uri());
$rtr->route($route$callback);
$rtr->execute();
}

#-------------------------------------------------------------------------------
#---------- Registry Class -----------------------------------------------------
#-------------------------------------------------------------------------------

// http://martinfowler.com/eaaCatalog/registry.html
final class Registry {
private static $_data = array();

static function get$key ) {
if( isset(self::$_data[$key]) ) {
return self::$_data[$key];
}
}

static function set$key$value '' ) {
self::$_data[$key] = $value;
}
}

#-------------------------------------------------------------------------------
#---------- Observable & Observer classes --------------------------------------
#-------------------------------------------------------------------------------

// Observable & Observer classes based from:
// http://en.wikipedia.org/wiki/Observer_pattern
// http://phpmaster.com/understanding-the-observer-pattern/
class Observable {
private $_event,
$_observers = array();

final function createEvent$event ) {
$this->_event = (string) $event;
$this->_notify();
}

final function getEvent() {
return $this->_event;
}

final function attachObserver $observer ) {
$i array_search($observer$this->_observers);
if ($i === false) {
$this->_observers[] = $observer;
}
}

final private function _notify() {
foreach($this->_observers as $observer) {
$observer->update$this );
}
}
}

interface Observer {
function updateObservable $subject );
}

#-------------------------------------------------------------------------------
#---------- AddOn Template -----------------------------------------------------
#-------------------------------------------------------------------------------

// AddOn class template & helper functions
abstract class AddOn extends Observable {
protected $_active,
  $_list = array();

abstract function load();
abstract function get();
abstract function process($str);

abstract protected function set($addon);
}

function addon_init(AddOn $addon) {
$addon->load();
}

function addon_get(AddOn $addon) {
return $addon->get();
}

function addon_process(AddOn $addon$str) {
$addon->process($str);
}

#-------------------------------------------------------------------------------
#---------- Settings CRUD Class ------------------------------------------------
#-------------------------------------------------------------------------------

final class Settings {
private static $_data = array(),
   $_db;

static function init$db ) {
self::$_db $db;
$sql 'SELECT name, value FROM settings';
foreach ( self::$_db->query($sql) as $r ) {
self::$_data[$r['name']] = $r['value'];
}
}

static function get$key ) {
if( self::exists($key) ) {
return self::$_data[$key];
}
}

static function set$key$value ) {
if( self::exists($key) ) {
$sql "UPDATE settings 
SET value = :value 
WHERE name = :key"
;
} else {
$sql "INSERT INTO settings 
VALUES(:key, :value)
LIMIT 1"
;
}
$sth self::$_db->prepare($sql);
$sth->bindParam(':key'$keyPDO::PARAM_STR);
$sth->bindParam(':value'$valuePDO::PARAM_STR);
$sth->execute();
}

static function delete$key ) {
if( self::exists($key) ) {
$sth self::$_db->query("
DELETE 
FROM settings 
WHERE name = :key
"
);
$sth->bindParam(':key'$keyPDO::PARAM_STR);
$sth->execute();
}
}

static function exists$key ) {
return array_key_exists($keyself::$_data);
}
}
// initialize settings
Settings::init(db());

#-------------------------------------------------------------------------------
#---------- Language Helper ----------------------------------------------------
#-------------------------------------------------------------------------------

define'LANG_PATH'get_ini('path.languages') );

Registry::set('lang', array());

function lang() {
return Registry::get('lang');
}

final class Languages extends AddOn {

final function __construct() {
$this->_active Settings::get('language');
}

final function load() {
$array = array();
$file LANG_PATH .'/'$this->_active '.php';
if( file_exists($file) ) {
$array = require $file;
}
Registry::set('lang'$array);


final function get() {
$dir opendir(LANG_PATH);
if ($dir) {
while (false !== ($file readdir($dir))) {
if (
$file != '.' && 
$file != '..' && 
file_exists(LANG_PATH .'/'$file)
) {
$filename basename($file'.php');
$this->_list[$filename] = array();

if( $filename === $this->_active ) {
$this->_list[$filename]['active'] = TRUE;
}
}
}
}
return $this->_list;
}

final protected function set$addon ) {
Settings::set'language'$addon );
}

final function process$str ) {
$this->set$str );
}
}
// initialize languages
addon_init(new Languages);

// Function to translate strings 
// Uses http://php.net/manual/en/function.strtr.php
// Do not translate placeholder
function __$string, array $args NULL ) {
$string value($stringlang())
value($stringlang())
$string;

return $args === null
$string
strtr($string$args);
}

#-------------------------------------------------------------------------------
#---------- Plugin Helper ------------------------------------------------------
#-------------------------------------------------------------------------------

// Extensions/Plugins 
define'EXT_PATH'get_ini('path.extensions') );

Registry::set('ext'
array(
'ext' => array(),
'hook'=> array()
)
);

function ext() {
return Registry::get('ext');
}

final class Extension {
private static $_data = array();
private static $_db;

static function init$db ) {
self::$_db $db;
$sql 'SELECT name FROM plugins';
foreach ( self::$_db->query($sql) as $r ) {
self::$_data[] = $r['name'];
}
}

static function get() {
return self::$_data;
}

static function add$name ) {
if( !self::exists($name) ) {
$sth self::$_db->query("
INSERT INTO plugins 
VALUES(:name)
LIMIT 1
"
);
$sth->bindParam(':name'$namePDO::PARAM_STR);
$sth->execute();
}
}

static function delete$name  ) {
if( self::exists($name) ) {
$sth self::$_db->query("
DELETE 
FROM plugins
WHERE name = :name 
"
);
$sth->bindParam(':name'$namePDO::PARAM_STR);
$sth->execute();
}
}

static function exists$name ) {
return in_array($nameself::$_data); 
}
}

abstract class Ext implements Observer {
protected $_name;
protected $_data = array();
private $_hook = array();
private $_ext;

final function __construct() {
$this->_ext  ext();
$this->_name get_class($this);
$this->_data value(
$this->_name,
addon_get(new Extensions)
);
}

abstract function main();

final function add$hook$callback$priority 10 ) {
$this->_hook[$hook][$priority][] = $callback;
}

final private function _check_requirements() {
if( isset($this->_data['dependencies']['requires']) ) {
$requirement $this->_data['dependencies']['requires'];
foreach( $requirement as $dep ) {
if( !isset($this->_ext['ext'][$dep]) ) {
$this->_data['req_error'] = TRUE;
}
}
}
}

final function commit() {

$this->_check_requirements();
$this->main();

if(    !isset($this->_data['req_error']) 
|| $this->_data['req_error'] === FALSE 
) {

$this->_ext['ext'][$this->_name] = $this->_data;

if( isset($this->_data['hooks']) ) {
foreach( $this->_data['hooks'] as $hook => $calls ) {
if( is_array($calls) ) {
foreach( $calls as $k => $func ) {
$this->_ext['hook'][$hook][10][] = $func;
}
} else {
$this->_ext['hook'][$hook][10][] = $calls;
}
}
}

foreach ($this->_hook as $hook => $calls) {
foreach( $calls as $k => $call ) {
foreach( $call as $func ) {
$this->_ext['hook'][$hook][$k][] = $func;
}
}
}
ksort_recursive($this->_ext['hook']);
}
Registry::set('ext'$this->_ext);
}

final function updateObservable $subject ) {
switch( $subject->getEvent() ) {
case( 'activated_' $this->_name ):
if( method_exists($this'install') ) {
$this->install();
}
break;
case( 'deactivated_' $this->_name  ):
if( method_exists($this'uninstall') ) {
$this->uninstall();
}
break;
}
}
}

function ext_commit(Ext $ext) {
$ext->commit();

// Log this transaction
$e ext();
$e['ext'][get_class($ext)]['internal_commit'] = TRUE
Registry::set('ext'$e);
}

final class Extensions extends AddOn {

function __construct() {
$this->_active Extension::get();
}

final function load() {
foreach( $this->_active as $p ) {

$file EXT_PATH'/' .$p'/index.php';

if( file_exists$file ) ) {
require( $file );

if( class_exists($p) ) {

$i = new $p;
$i->commit();
// Attach the observer
$this->attach($i);
}
}
}


final function get() {
$dir opendir(EXT_PATH);
if( $dir ) {
while( false !== ($file readdir($dir)) ) {
if( 
   $file != '.' 
&& $file != '..' 
&& file_exists(EXT_PATH'/' .$file'/index.php')
&& file_exists(EXT_PATH'/' .$file'/plugin.ini')
) {

$this->_list[$file] = $this->data(
EXT_PATH'/' .$file'/plugin.ini'
);

if( in_array($file$this->_active) ) {
$this->_list[$file]['active'] = TRUE;
}
}
}
}
return $this->_list;
}

final protected function data$file ) {
$array = array();
$array['information'] = get_ini'plugin.information', $file );
$array['dependencies']= get_ini'plugin.dependencies', $file );
$array['hooks'   = get_ini'plugin.hooks' $file );
return strip_tags_deep($array);
}

final protected function set$addon ) {
Extension::set$addon );
$this->createEvent('activated_' $addon);
}

final protected function delete$