M Demo

M demo massively uses JQuery UI tabs and Hijaxing, and is therefore mostly invisble to Google and the likes.
This page is an unclickable collection of its content.

Click M Demo to see the demo.

M is a zero bootstrap php/MySQL MVC (Model View Controller) framework for rapid WEB development with php and MySQL.
Mcontroller routes and dispatches urls, and enables access to Mmodel, Mview and other facilities.
Mmodel is a collection of facilities to communicate with the MySQL database.
Mview is an extension of the Smarty class http://www.smarty.net/
This demo uses Ajax and Jquery for tabbing and flow.
See Full Documentation
Download
clone
sql error: Can't read dir of './ohadalon_mdemo/' (errno: 20)
show tables from ohadalon_mdemo

Warning: in_array() [function.in-array]: Wrong datatype for second argument in /home/ohadalon/public_html/M/Mmodel.class.php on line 535

Warning: in_array() [function.in-array]: Wrong datatype for second argument in /home/ohadalon/public_html/M/Mmodel.class.php on line 535
Creating table authors from sql/authors.crtable.sql
sql error: Can't create table 'authors' (errno: 20)
CREATE TABLE `authors` (
`id` int(11) NOT NULL auto_increment,
`first` varchar(255) default NULL,
`last` varchar(255) default NULL,
PRIMARY KEY (`id`)
)
Loading authors from sql/authors.data.sql
sql error: Can't find file: './ohadalon_mdemo/authors.frm' (errno: 20)
INSERT INTO authors (id, first, last) VALUES (1,'Isaac','Asimov');
sql error: Can't find file: './ohadalon_mdemo/authors.frm' (errno: 20)
INSERT INTO authors (id, first, last) VALUES (2,'Robert ','Heinlein');
sql error: Can't find file: './ohadalon_mdemo/authors.frm' (errno: 20)
INSERT INTO authors (id, first, last) VALUES (3,'Stanisław','Lem');
sql error: Can't find file: './ohadalon_mdemo/authors.frm' (errno: 20)
INSERT INTO authors (id, first, last) VALUES (4,'Charles','Dickens');
sql error: Can't find file: './ohadalon_mdemo/authors.frm' (errno: 20)
INSERT INTO authors (id, first, last) VALUES (5,'J. R. R. ','Tolkien');
sql error: Can't find file: './ohadalon_mdemo/authors.frm' (errno: 20)
INSERT INTO authors (id, first, last) VALUES (6,'Roald','Dahl');
sql error: Can't find file: './ohadalon_mdemo/authors.frm' (errno: 20)
INSERT INTO authors (id, first, last) VALUES (7,'Douglas','Adams');
sql error: Can't find file: './ohadalon_mdemo/authors.frm' (errno: 20)
show columns from authors
No columns for authors
/home/ohadalon/public_html/M/Mtable.class.php:70: Mtable:__construct: authors: no auto_incrment column
sql error: Can't find file: './ohadalon_mdemo/authors.frm' (errno: 20)
select * from authors order by last,first
First Name Last Name
sql error: Can't find file: './ohadalon_mdemo/authors.frm' (errno: 20)
select * from authors order by last,first

Warning: Invalid argument supplied for foreach() in /home/ohadalon/public_html/Mdemo/everything.php on line 29
New Game See the Source Code

click to play click to play click to play
click to play click to play click to play
click to play click to play click to play
Authors
select * from authors order by last, first
sql error: Can't find file: './ohadalon_mdemo/authors.frm' (errno: 20)
select * from authors order by last, first


Books
select * from books order by title
sql error: Can't find file: './ohadalon_mdemo/books.frm' (errno: 20)
select * from books order by title


Join
select a.*, b.* from authors a, books b where a.id = b.authorId order by a.first, a.last, b.title
sql error: Can't find file: './ohadalon_mdemo/authors.frm' (errno: 20)
select a.*, b.* from authors a, books b where a.id = b.authorId order by a.first, a.last, b.title


Foundation
select a.first, a.last, b.title from authors a, books b where a.id = b.authorId and b.title like '%Foundation%' order by a.first, a.last, b.title
sql error: Can't find file: './ohadalon_mdemo/authors.frm' (errno: 20)
select a.first, a.last, b.title from authors a, books b where a.id = b.authorId and b.title like '%Foundation%' order by a.first, a.last, b.title


The Complete Cartesian Product
select a.*, b.* from authors a, books b order by a.first, a.last, b.title
sql error: Can't find file: './ohadalon_mdemo/authors.frm' (errno: 20)
select a.*, b.* from authors a, books b order by a.first, a.last, b.title


Joins.class.php
<?php
/*------------------------------------------------------------*/
class Joins extends Mcontroller {
    
/*------------------------------------------------------------*/
    
public function index() {
        
$queries = array(
            array(
                
'title' => "Authors",
                
'sql' => "select * from authors order by last, first",
                
'exportFileName' => "Authors",
            ),
            array(
                
'title' => "Books",
                
'sql' => "select * from books order by title",
                
'exportFileName' => "Books",
            ),
            array(
                
'title' => "Join",
                
'sql' => "select a.*, b.* from authors a, books b where a.id = b.authorId order by a.first, a.last, b.title",
                
'exportFileName' => "BooksAndAuthors",
            ),
            array(
                
'title' => "Foundation",
                
'sql' => "select a.first, a.last, b.title from authors a, books b where a.id = b.authorId and b.title like '%Foundation%' order by a.first, a.last, b.title",
                
'exportFileName' => "Foundation",
            ),
            array(
                
'title' => "The Complete Cartesian Product",
                
'sql' => "select a.*, b.* from authors a, books b order by a.first, a.last, b.title",
                
'exportFileName' => "Cartesian",
            ),
        );
        foreach ( 
$queries as $query ) {
            
Mview::msg($query['title'], true);
            
Mview::msg($query['sql']);
            
$this->showRows($query['sql'], true$query['exportFileName']);
            echo 
"<br /><br />\n";
        }
        
$file "Joins.class.php";
        
Mview::msg($file);
        
highlight_file($file);
    }
    
/*------------------------------------------------------------*/
}
/*------------------------------------------------------------*/
Sunday Monday Tuesday Wednsday Thursday Friday Saturday
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

index.php

<?php
/*------------------------------------------------------------*/
/*
/*------------------------------------------------------------*/
session_start();
/*------------------------------------------------------------*/
require_once("mdemoConfig.php");
require_once(
"../M/mfiles.php");
require_once(
"Mdemo.class.php");
/*------------------------------------------------------------*/
/**
 * not part of Mdemo
 * page hits to these pages are counted for statistical purposed
 * no private user information is collected.
 */
$phFile "../tas/PageHit.class.php";
if ( 
file_exists($phFile) ) {
    require_once(
$phFile);
    
$ph = new PageHit;
    
$ph->hit();
}
/*------------------------------------------------------------*/
global $Mview;
global 
$Mmodel;
$Mview = new Mview;
$Mview->assign("M"M_URL);
$Mmodel = new Mmodel;
/*------------------------------------------------------------*/
/**
 * Mdemo extends Mcontroller and so dispatches URLs from here
 * use PATH_INFO=/className/action/otherparts for mod rewrite pretty URLs or just ?className=...&action=...&otherargs
 * Mcontroller extended $this->pathParts() returns array of argumnets to pretty URLs
 */
$Mdemo = new Mdemo;
$Mdemo->control();
/*------------------------------------------------------------*/

Mdemo.class.php

<?php
/*------------------------------------------------------------*/
class Mdemo extends Mcontroller {
    
/*------------------------------------------------------------*/
    /**
     * allow Mcontroller to do all the URL dispatching
     * (this controller is called from index.php)
    /*------------------------------------------------------------*/
    /**
     * if the url has no action specified, than we are just starting.
     * show the frame page with the header, footer and the jquery UI tab center setup.
     */
    
public function index() {
        
$this->Mview->showTpl("mdemo.tpl", array(
            
"M" => M_URL,
        ));
    }
    
/*------------------------------------------------------------*/
}
/*------------------------------------------------------------*/

tpl/mdemo.tpl

<!--
    display all header and footer information
    and let jquery UI tabs Ajax everything else in the center without ever refreshing
-->
{msuShowTpl file="mdemoHead.tpl"}
{msuShowTpl file="mdemoHeader.tpl"}
<div style="font-size:70%;" class="tabs">
    <ul>
        <li><a class="noHijax tabLabel" href="?className=Authors&action=listAuthors"><span>Authors & Books</span></a></li>
        <li><a class="noHijax tabLabel" href="?className=TicTacToe"><span>Tic Tac Toe</span></a></li>
        <li><a class="noHijax tabLabel" href="?className=Joins"><span>Join Tutorial</span></a></li>
        <li><a class="noHijax tabLabel" href="?className=Cal"><span>Calendar</span></a></li>
        <li><a class="noHijax tabLabel" href="?className=ShowSource"><span>See the Source Code</span></a></li>
        <li><a class="noHijax tabLabel" href="?className=Mview&action=showTpl&tpl=admin.tpl"><span>Admin</span></a></li>
    </ul>
</div>
{msuShowTpl file="mdemoFooter.tpl"}
{msuShowTpl file="mdemoFoot.tpl"}

Authors.class.php

<?php
/*------------------------------------------------------------*/
class Authors extends Mtable {
    
/*------------------------------------------------------------*/
    /**
     * an M scaffold is and extension of the class Mtable which in turn extends Mcontroller
     * tell it the name of the table and the default sort order
     */
    
public function __construct() {
        
parent::__construct("authors""last, first");
    }
    
/*------------------------------------------------------------*/
    
public function listAuthors() {
        
/**
         * use Mmodel to get the data from the database
         * and pass the data to Mview to send the ajaxed content
         * back to the browser for display
         * 
         */
        
$this->Mview->showTpl("authorsList.tpl", array(
            
"authors" => $this->Mmodel->getRows("select * from authors order by last,first"),
        ));
    }
    
/*------------------------------------------------------------*/
    
public function listBooks() {
        
/**
         * more the same
         */
        
$authorId $_REQUEST['authorId'];
        
$this->Mview->showTpl("bookList.tpl", array(
            
"books" => $this->Mmodel->getRows("select * from books where authorId = $authorId order by title"),
        ));
    }
    
/*------------------------------------------------------------*/
}
/*------------------------------------------------------------*/

TicTacToe.class.php

<?php
/*------------------------------------------------------------*/
define('YOU''X');
define('ME''O');
/*------------------------------------------------------------*/
class TicTacToe extends Mcontroller {
    
/*------------------------------------------------------------*/
    
private $game;
    
/*------------------------------------------------------------*/
    /**
     * the default action is to start a new game.
     *
     * (this method is called by Mcontroller if no action is
     * specified in the url)
     */
    
public function index() {
        
$this->newGame();
    }
    
/*------------------------------------------------------------*/
    /**
     * to start a new game
     * empty or re-empty the game array
     * by placing null content in all the cells
     */
    
public function newGame() {
        
$this->game = array(
            array(
nullnullnull,),
            array(
nullnullnull,),
            array(
nullnullnull,),
        );
        
$_SESSION['game'] = $this->game;
        
$this->show();
    }
    
/*------------------------------------------------------------*/
    /**
     * a player's move:
     * place the player's chip in the requested spot
     * and make the next move
     */

    
public function play() {
        
$this->game $_SESSION['game'];
        
$x $_REQUEST['x'];
        
$y $_REQUEST['y'];
        
$this->game[$x][$y] = YOU;
        
$this->next();
    }
    
/*------------------------------------------------------------*/
    /**
     * show the source code
     */
    
public function source() {
        
$this->menu();
        
$files = array(
            
"TicTacToe.class.php",
            
"tpl/ticTacToe.menu.tpl",
            
"tpl/ticTacToe.tpl",
        );
        foreach ( 
$files as $file ) {
            
$this->Mview->msg($file);
            
highlight_file($file);
        }
    }
    
/*------------------------------------------------------------*/
    /*------------------------------------------------------------*/
    /*------------------------------------------------------------*/
    /**
     * show the board
     * if the game is over then game buttons are deactivated
     */
    
private function show($gameOver false) {
        
$this->menu();
        
$this->Mview->showTpl("ticTacToe.tpl", array(
            
'game' => $this->game,
            
'active' => ! $gameOver,
        ));
    }
    
/*------------------------------------------------------------*/
    /**
     * put on screen a menu of controls, if its not there already
     */
    
private function menu() {
        static 
$vistied false;
        if ( 
$vistied )
            return;
        
$vistied true;
        
$this->Mview->showTpl("ticTacToe.menu.tpl");
    }
    
/*------------------------------------------------------------*/
    /**
     * what to do next
     * after the player played.
     * check to see if the game is over.
     * if it is, tell win/lose result and show
     * the final board, with buttons deactivated.
     * otherwise, make the move, and again check if the game is over.
     */
    
private function next() {
        
$this->menu();
        if ( 
$this->isWinner(YOU)) {
            
$this->Mview->msg("Contratz. You win. Wanna Play Again?");
            
$this->show(true);
            return;
        }
        if ( 
$this->isTie() ) {
            
$this->Mview->msg("Tie. Wanna Play Again?");
            
$this->show(true);
            return;
        }

        
$this->move();

        if ( 
$this->isWinner(ME)) {
            
$this->Mview->msg(":-(  Wanna Play Again?");
            
$this->show(true);
            return;
        }

        
$_SESSION['game'] = $this->game;
        
$this->show();
    }
    
/*------------------------------------------------------------*/
    /**
     * is the given player ('ME' or 'YOU') a winner on the current state of the board
     * check each of the rows, columns, and diagonals
     */
    
private function isWinner($who) {
        
$m $this->game;
        for(
$i=0;$i<3;$i++) {
            
// rows
            
if ( $this->trioWins($m[$i], $who) )
                return(
true);
            
// columns
            
if ( $this->trioWins(Mutils::arrayColumn($m$i), $who) )
                return(
true);
        }
        
// top-left to bottom-right
        
if ( $this->trioWins(array($m[0][0], $m[1][1], $m[2][2],), $who) )
            return(
true);
        
// top-right to bottom-left
        
if ( $this->trioWins(array($m[0][2], $m[1][1], $m[2][0],), $who) )
            return(
true);
        return(
false);
    }
    
/*------------------------------------------------------------*/
    /**
     * does this array of three values represent a winning for the given player
     * it does if all three values equal the argument
     */
    
private function trioWins($trio$who) {
        for(
$i=0;$i<3;$i++)
            if ( 
$trio[$i] != $who )
                return(
false);
        return(
true);
    }
    
/*------------------------------------------------------------*/
    /**
     * check if the board represents a tie.
     * in fact, do not tell its a tie if there are still moves left
     * (so they can be played anyway, even if to futility),
     * and since this method is only called after winning combinations were checked,
     * it boils down to merely checking to see that all places are filled with chips.
     */
    
private function isTie() {
        
$m $this->game;
        for(
$x=0;$x<3;$x++)
            for(
$y=0;$y<3;$y++)
                if ( 
$m[$x][$y] == null )
                    return(
false);
        return(
true);
    }
    
/*------------------------------------------------------------*/
    /**
     * make a move
     * this is the game strategy
     * first try to see if a win is possible
     * otherwise, block any potential win by the player
     * otherwise, see if you can place a chip in the center
     * otherwise, see if you can place a chip in a corner
     * otherwise, see if you can place a chip on a side
     * all the play functions return true if a chip was placed and false otherwise
     */
    
private function move() {
        
$moveFuncs = array('win''block''center''corner''side');
        foreach ( 
$moveFuncs as $func )
            if ( 
$this->$func() )
                return;
    }
    
/*------------------------------------------------------------*/
    /**
     * try to win by completing a trio with the value ME
     */
    
private function win() {
        return(
$this->complete(ME));
    }
    
/*------------------------------------------------------------*/
    /**
     * try to block the player by completing the players trio
     * in place of the player.
     */
    
private function block() {
        return(
$this->complete(YOU));
    }
    
/*------------------------------------------------------------*/
    /**
     * try to complete a trio that already has two chips of the same kind
     * if the chips are 'ME', then this is a win attempt
     * if the chips are 'YOU' then this is a block attempt
     * since if there are two block cases, the game is lost anyway
     * it is not important which is being blocked
     * the method completeTrio() is called successively with all potential possibilties
     * until a completion is found
     * in which case the chip is placed in the designated place
     * completeTrio is passed a trio in each case
     * and the exact position is 'calculated' from the return value
     */
    
private function complete($who) {
        
$m $this->game;
        for(
$i=0;$i<3;$i++) {
            
// rows
            
if ( ($idx $this->completeTrio($m[$i], $who)) >= ) {
                
$this->game[$i][$idx] = ME;
                return(
true);
            }
            
// columns
            
if ( ($idx $this->completeTrio(Mutils::arrayColumn($m$i), $who)) >= ) {
                
$this->game[$idx][$i] = ME;
                return(
true);
            }
        }
        
// top-left to bottom-right
        
if ( ($idx $this->completeTrio(array($m[0][0], $m[1][1], $m[2][2],), $who)) >= ) {
            
$this->game[$idx][$idx] = ME;
            return(
true);
        }
        
// top-right to bottom-left
        
if ( ($idx $this->completeTrio(array($m[0][2], $m[1][1], $m[2][0],), $who)) >= ) {
            
$this->game[$idx][2-$idx] = ME;
            return(
true);
        }
        return(
false);
    }
    
/*------------------------------------------------------------*/
    /**
     * given an array of three values
     * tell if it can be completed by placing a chip on the only vacant position.
     * return the position (0-2) if so, or -1 if not
     */
    
private function completeTrio($trio$who) {
        
$numComplete 0;
        
$ret null;
        for(
$i=0;$i<3;$i++) {
            if ( 
$trio[$i] != null && $trio[$i] != $who )
                return(-
1);
            if ( 
$trio[$i] == $who )
                
$numComplete++;
            else
                
$ret $i;
        }
        if ( 
$numComplete == )
            return(
$ret);
        return(-
1);
    }
    
/*------------------------------------------------------------*/
    /**
     * try to place a chip in the center of the baord
     */
    
private function center() {
        return(
$this->place(11));
    }
    
/*------------------------------------------------------------*/
    /**
     * try to place a chip in the given position
     */
    
private function place($x$y) {
        if ( 
$this->game[$x][$y] != null )
            return(
false);
        
$this->game[$x][$y] = ME ;
        return(
true);
    }
    
/*------------------------------------------------------------*/
    /**
     * try to place a chip in any corner
     * randomize so that the move is a bit less predictable
     */
    
private function corner() {
        
$corners = array(
            array(
0,0),
            array(
0,2),
            array(
2,0),
            array(
2,2),
        );
        return(
$this->placeAtRandom($corners));
    }
    
/*------------------------------------------------------------*/
    /**
     * place in any of the given list of places at random
     */
    
private function placeAtRandom($places) {
        
shuffle($places);
        foreach ( 
$places as $place )
            if ( 
$this->place($place[0], $place[1]) )
                return(
true);
        return(
false);
    }
    
/*------------------------------------------------------------*/
    /**
     * try to place a chip in any of the sides
     */
    
private function side() {
        
$sides = array(
            array(
0,1),
            array(
1,0),
            array(
1,2),
            array(
2,1),
        );
        return(
$this->placeAtRandom($sides));
    }
    
/*------------------------------------------------------------*/
}
/*------------------------------------------------------------*/

Joins.class.php

<?php
/*------------------------------------------------------------*/
class Joins extends Mcontroller {
    
/*------------------------------------------------------------*/
    
public function index() {
        
$queries = array(
            array(
                
'title' => "Authors",
                
'sql' => "select * from authors order by last, first",
                
'exportFileName' => "Authors",
            ),
            array(
                
'title' => "Books",
                
'sql' => "select * from books order by title",
                
'exportFileName' => "Books",
            ),
            array(
                
'title' => "Join",
                
'sql' => "select a.*, b.* from authors a, books b where a.id = b.authorId order by a.first, a.last, b.title",
                
'exportFileName' => "BooksAndAuthors",
            ),
            array(
                
'title' => "Foundation",
                
'sql' => "select a.first, a.last, b.title from authors a, books b where a.id = b.authorId and b.title like '%Foundation%' order by a.first, a.last, b.title",
                
'exportFileName' => "Foundation",
            ),
            array(
                
'title' => "The Complete Cartesian Product",
                
'sql' => "select a.*, b.* from authors a, books b order by a.first, a.last, b.title",
                
'exportFileName' => "Cartesian",
            ),
        );
        foreach ( 
$queries as $query ) {
            
Mview::msg($query['title'], true);
            
Mview::msg($query['sql']);
            
$this->showRows($query['sql'], true$query['exportFileName']);
            echo 
"<br /><br />\n";
        }
        
$file "Joins.class.php";
        
Mview::msg($file);
        
highlight_file($file);
    }
    
/*------------------------------------------------------------*/
}
/*------------------------------------------------------------*/

Cal.class.php

<?php
/*------------------------------------------------------------*/
class Cal extends Mcontroller {
    
/*------------------------------------------------------------*/
    
public function index() {
        
$this->showCal(date("Y"), date("n"));
    }
    
/*------------------------------------------------------------*/
    
public function showCal($year null$month null) {
        if ( ! 
$year )
            
$year $_REQUEST['year'];
        if ( ! 
$month )
            
$month $_REQUEST['month'];
        
$cal Mdate::cal($year$month);
        
$this->menu($year$month);
        
$this->Mview->showTpl("cal.tpl", array(
            
'cal' => $cal,
            
'today' => ( date("Y") == $year && date("n") == $month ) ? date("d") : null,
        ));
    }
    
/*------------------------------------------------------------*/
    
private function menu($year null$month null) {
        if ( 
$year == null )
            
$year date("Y");
        if ( 
$month == null )
            
$month date("m");
        
$this->Mview->showTpl("cal.menu.tpl", array(
            
'year' => $year,
            
'month' => $month,
            
'months' => Mdate::monthLlist(),
        ));
    }
    
/*------------------------------------------------------------*/
    
public function nextYear() {
        
$this->showCal($_REQUEST['year']+1$_REQUEST['month']);
    }
    
/*------------------------------------------------------------*/
    
public function prevYear() {
        
$this->showCal($_REQUEST['year']-1$_REQUEST['month']);
    }
    
/*------------------------------------------------------------*/
    
public function nextMonth() {
        
$this->showCal($_REQUEST['year'] + (($_REQUEST['month'] == 12) ? 0), ($_REQUEST['month'])%12+1);
    }
    
/*------------------------------------------------------------*/
    
public function prevMonth() {
        
$this->showCal($_REQUEST['year'] - (($_REQUEST['month'] == 1) ? 0), ($_REQUEST['month']+10)%12+1);
    }
    
/*------------------------------------------------------------*/
}
/*------------------------------------------------------------*/

ShowSource.class.php

<?php
/*------------------------------------------------------------*/
class ShowSource extends Mcontroller {
    
/*------------------------------------------------------------*/
    
public function index() {
        
$this->menu();
    }
    
/*------------------------------------------------------------*/
    
public function fileList() {
        
$files = array(
            
"index.php",
            
"Mdemo.class.php",
            
"tpl/mdemo.tpl",
            
"Authors.class.php",
            
"TicTacToe.class.php",
            
"Joins.class.php",
            
"Cal.class.php",
            
"ShowSource.class.php",
            
"../M/Mmodel.class.php",
            
"../M/Mview.class.php",
            
"../M/Mcontroller.class.php",
        );
        return(
$files);
    }
    
/*------------------------------------------------------------*/
    
public function menu() {
        
$this->Mview->showTpl("showSource.menu.tpl", array(
            
'files' => $this->fileList(),
        ));
    }
    
/*------------------------------------------------------------*/
    
public function showFile($file null) {
        if ( ! 
$file ) {
            
$fileId $_REQUEST['fileId'];
            
$files $this->fileList();
            
$file $files[$fileId];
        }
        echo 
"<h4>$file</h4>\n";
        
highlight_file($file);
    }
    
/*------------------------------------------------------------*/
}
/*------------------------------------------------------------*/

../M/Mmodel.class.php

<?php
/*------------------------------------------------------------*/
/**
  * Mmodel - mysql convenience utilities
  *
  * @package M
  * @author Ohad Aloni
  */
/*------------------------------------------------------------*/
/**
 * Mmodel may be used independently,
 * It might use Mview to display some error messages
 */
require_once("Mview.class.php");
/*------------------------------------------------------------*/
/**
  * Mmodel - mysql convenience utilities
  *
  * @package M
  * @author Ohad Aloni
  */
class Mmodel {
    
/*------------------------------------------------------------*/
    
private $isConnected false;
    
/*------------------------------------------------------------*/
    
private $dbHost null;
    private 
$dbUser null;
    private 
$dbPasswd null;
    private 
$dbName null;
    
/*------------------------------*/
    
private $dbHandle null;
    private 
$lastSql null;
    private 
$lastError null;
    private 
$lastInsertId null;
    
/*------------------------------------------------------------*/
    
public function __construct($user null$passwd null$dbName null,  $host null) {
        if ( 
$host )
            
$this->dbHost host;
        elseif ( 
defined('M_HOST') )
            
$this->dbHost M_HOST;
        else
            
$this->dbHost 'localhost';

        if ( 
$dbName )
            
$this->dbName $dbName;
        elseif ( 
defined('M_DBNAME') && M_DBNAME != 'none' )
            
$this->dbName M_DBNAME;
        else
            
$this->dbName null;

        if ( 
$user )
            
$this->dbUser $user;
        elseif ( 
defined('M_USER') )
            
$this->dbUser M_USER;
        else
            
$this->dbUser null;

        if ( 
$passwd )
            
$this->dbPasswd $passwd;
        elseif ( 
defined('M_PASSWORD') )
            
$this->dbPasswd M_PASSWORD;
        else
            
$this->dbPasswd null;

        if ( ! 
$this->dbUser || ! $this->dbPasswd ) {
            
Mview::error("Must define M_USER M_PASSWORD or construct Mmodel with arguments");
            return;
        }
        if ( ! 
$this->selectHost($this->dbHost$this->dbUser$this->dbPasswd$this->dbName) )
            return;
        
$this->isConnected true;
    }
    
/*------------------------------*/
    
public function isConnected() {
        return(
$this->isConnected);
    }
    
/*------------------------------------------------------------*/
    
public function selectHost($dbHost$dbUser$dbPasswd$dbName) {
        
$this->dbHost $dbHost;
        
$this->dbUser $dbUser;
        
$this->dbPasswd $dbName;
        
$this->dbName $dbName;
        
$this->dbHandle = @mysql_connect($dbHost$dbUser$dbPasswd);
        if ( ! 
$this->dbHandle ) {
            
$error mysql_error();
            
$this->lastError $error;
            
Mview::error("Could not connect to db: $error");
            return(
false);
        }
        
$res $this->query("SET NAMES 'utf8'");
        if ( ! 
$res ) {
        }
        if ( 
$dbName )  {
            if ( ! 
$this->selectDb($dbName) ) {
                
$error = @mysql_error() ;
                
$this->lastError $error;
                
Mview::error("Unable to select db $dbName: $error");
            }
        }
        return(
true);
    }
    
/*------------------------------------------------------------*/
    /**
      * use database
      */
    
public function selectDB($db) {
        
$this->dbName $db;
        
$ret = @mysql_select_db($this->dbName);
        if ( 
$ret == false ) {
            
$error = @mysql_error() ;
            
$this->lastError $error;
            return(
false);
        }
        return(
true);
    }
    
/*------------------------------*/
    /**
      * use database
      */
    
public function useDB($db) {
        return(
$this->selectDB($db));
    }
    
/*------------------------------------------------------------*/
    /**
     * id of last insert - same as mysql_insert_id()
     *
     * @return int
     */
    
public function insertId() {
        return(
$this->lastInsertId);
    }
    
/*------------------------------------------------------------*/
    /**
     * make a string usable in quotes of sql statement
     * @param string
     * @return string
     */
    
public function str($str) {
        if ( ! 
$str )
            return(
$str);
        
// if they are already escaped
        
$ret str_replace("\\'""'"$str);
        
$ret str_replace("'""\\'"$ret);
        
$ret str_replace("\r\n""\n"$ret);
        
$ret str_replace("\n""\\n"$ret);
        return(
$ret);
    }
    
/*------------------------------------------------------------*/
    /**
     * query() is almost synonim to mysql_query().<br />
     * It is used for streaming when possibly large data sets are expected.
     *
     * For queries expecting a single row, item or a relativelly small result set,<br />
     * use getRow(), getRows(), getString(), getStrings() or getInt()<br />
     * for a complete "round trip" and single-line-coding convenience
     *
     * @param string the sql query
     * @return resource as returned from mysql_query()
     */
    
public function query($sql) {
        
$res null;
        try {
            
$res = @mysql_query($sql);
        } catch (
Exception $e) {
            
$msg $e->getMessage();
            
Mview::error($msg);
            
$error = @mysql_error();
            
$this->lastError $error;
            if ( 
$error )
                
Mview::error("sql error: $error");
            
Mview::error($sql);
            return(
null);
        }
        if ( ! 
$res ) {
            
$error = @mysql_error();
            
$this->lastError $error;
            if ( 
$error )
                
Mview::error("sql error: $error");
            
Mview::error($sql);
            if ( 
stristr($error"MySQL server has gone away") ) {
                
Mview::error("EXITTING");
                exit; 
// servers will auto restart
            
}
            return(
null);
        }
        return(
$res);
    }
    
/*------------------------------------------------------------*/
    /**
     * execute a query, expecting no particular resulting data.
     *
     * @param string the query
     * @param bool used internally (see dbLog())
     * @return int number of rows affected by the query
     */

    
public function _sql($sql$rememberLastSql true) {
        
$res $this->query($sql);
        if ( ! 
$res ) {
            return(
null);
        }
        if ( 
$rememberLastSql )
            
$this->lastSql $sql;
        
$affected = @mysql_affected_rows();
        @
mysql_free_result($res);
        return(
$affected);
    }
    
/*------------------------------*/
    /**
     * execute a query, expecting no particular resulting data.
     *
     * @param string the query
     * @return int number of rows affected by the query
     */
    
public function sql($sql) {
        if ( ! 
$this->isConnected ) {
            
Mview::error("Mmodel not connected");
            return(
null);
        }
        
$ret $this->_sql($sql);
        if ( 
strstr($sql'insert') )
            
$this->lastInsertId = @mysql_insert_id();
        if ( 
$ret )
            
$this->dbLog('''sql'0);

        return(
$ret);
    }
    
/*----------------------------------------*/
    /**
     * get rows from the database
     *
     * @param string the query
     * @return array array of associative arrays from mysql_fetch_assoc()
     */
    
public function getRows($sql$ttl null) {
        
$Mmemcache = new Mmemcache;
        
$memcacheKey get_class().":4:".__FUNCTION__."('$sql')";
        if ( 
$ttl !== null && ($rows $Mmemcache->get($memcacheKey)) !== false ) {
            return(
$rows);
        }
        if ( ! 
$this->isConnected ) {
            
Mview::error("Mmodel not connected");
            return(
null);
        }
        
$res $this->query($sql);
        if ( ! 
$res ) {
            return(
null);
        }
        
$ret = array();
        while(
$r = @mysql_fetch_assoc($res))
            
$ret[] = $r ;
        @
mysql_free_result($res);
        if ( 
$ttl !== null )
            
$Mmemcache->set($memcacheKey$ret$ttl);
        return(
$ret);
    }
    
/*----------------------------------------*/
    /**
     * get a single row from the database
     *
     * @param string the query
     * @return array associative array from mysql_fetch_assoc()
     */
    
public function getRow($sql$ttl null) {
        if ( ! 
$this->isConnected ) {
            
Mview::error("Mmodel not connected");
            return(
null);
        }
        
$rows $this->getRows($sql$ttl);
        if ( 
count($rows) == )
            return(
null);
        return(
$rows[0]);
    }
    
/*------------------------------*/
    /**
     * get a row from a table by its id
     *
     * @param string the table from which the data is fetched
     * @param int the id value
     * @param string the name of the id field if it is not 'id'
     * @return array associative array of data of the row
     */
    
public function getById($tableName$id$idName "id"$ttl null) {
        return(
$this->getRow("select * from $tableName where $idName = $id"$ttl));
    }
    
/*----------------------------------------*/
    /**
     * get a column of data from the database
     *
     * @param string the query
     * @return array an array with the data
     */
    
public function getStrings($sql$ttl null) {
        
$rows $this->getRows($sql$ttl);
        if ( 
$rows === null )
            return(
null);
        
$ret = array();
        foreach ( 
$rows as $row )
            
$ret[] = array_shift($row); // take the value of the first (and only) column, ignoring the index field name
        
return($ret);
    }
    
/*----------------------------------------*/
    /**
     * get an item (single row, single column) from the database
     *
     * @param string the query
     * @return string the item data
     */
    
public function getString($sql$ttl null) {
        if ( ! 
$this->isConnected ) {
            
Mview::error("Mmodel not connected");
            return(
null);
        }
        
$strings $this->getStrings($sql$ttl);
        if ( 
$strings )
            return(
$strings[0]);
        else
            return(
null);
    }
    
/*----------------------------------------*/
    /**
     * get an int item from the database
     *
     * @param string the query
     * @return int the returned number
     */
    
public function getInt($sql$ttl null) {
        if ( ! 
$this->isConnected ) {
            
Mview::error("Mmodel not connected");
            return(
null);
        }
        if ( (
$ret $this->getString($sql$ttl)) === null )
            return(
null);

       return((int)
$ret);
    }
    
/*------------------------------------------------------------*/
    /**
     * name of the auto_increment column in table
     *
     * @param string the name of the table
     * @return string name of auto_increment column
     */
    
public function autoIncrement($tableName) {
        
$Mmemcache = new Mmemcache;
        static 
$cache = array();

        if ( isset(
$cache[$tableName]) )
            return(
$cache[$tableName]);

        
$memcacheKey get_class().":1:".__FUNCTION__."('$tableName')";
        if ( (
$cache[$tableName] = $Mmemcache->get($memcacheKey)) != null )
            return(
$cache[$tableName]);

        
$fields $this->fields($tableName);
        if ( ! 
$fields ) {
            
$cache[$tableName] = null;
            return(
$cache[$tableName]);
        }
        foreach ( 
$fields as $field ) {
            if ( 
$field['isAutoInc'] ) {
                
$cache[$tableName] = $field['name'];
                break;
            }
        }
        if ( ! isset(
$cache[$tableName]) )
            
$cache[$tableName] = null;
        if ( 
$cache[$tableName] )
            
$Mmemcache->set($memcacheKey$cache[$tableName]);
        return(
$cache[$tableName]);
    }
    
/*------------------------------------------------------------*/
    
public function typeGroup($ftype) {
        if ( 
strncmp($ftype'int'3) == )
            return(
"int");
        if ( 
$ftype == 'text' )
            return(
"text");
        if ( 
strncmp($ftype'varchar'7) == )
            return(
"text");
        if ( 
in_array($ftype, array('date''datetime''timestamp')) )
            return(
"date");
        return(
$ftype);
    }
    
/*------------------------------------------------------------*/
    /**
     * schema information for a table:
     *
     * @param string the name of the table
     * @return array two dimensional array. For each column in the table: name, type, isNull, isPrimary, default, isAutoInc
     */
    
public function fields($tableName) {
        static 
$cache = array();

        if ( isset(
$cache[$tableName]) )
            return(
$cache[$tableName]);

        
$columnRows $this->getRows("show columns from $tableName");
        if ( ! 
$columnRows ) {
            
Mview::error("No columns for $tableName");
            
$cache[$tableName] = null ;
            return(
$cache[$tableName]);
        }
        
$fields = array();
        foreach ( 
$columnRows as $col )
            
$fields[] = array(
                
'name' => $col['Field'],
                
'type' => $col['Type'],
                
'typeGroup' => $this->typeGroup($col['Type']),
                
'isNull' => $col['Null'] == 'YES' true false,
                
'isPrimary' => $col['Key'] == 'PRI',
                
'default' => $col['Default'],
                
'isAutoInc' => $col['Extra'] == 'auto_increment',
                
'isKey' => $col['Key'] != null,
            );
        
$cache[$tableName] = $fields ;
        return(
$cache[$tableName]);
    }
    
/*----------------------------------------*/
    /**
     * schema of one field
     */
     
public function field($tableName$fieldName) {
         
$fields $this->fields($tableName);
        if ( ! 
$fields )
            return(
null);
        foreach ( 
$fields as $field )
            if ( 
$field['name'] == $fieldName )
                return(
$field);
        return(
null);
     }
    
/*----------------------------------------*/
    /**
     * list fields of a table
     *
     * @param string the name of the table
     * @return array list of field names
     */
    
public function columns($tableName) {
        static 
$cache = array();

        if ( isset(
$cache[$tableName]) )
            return(
$cache[$tableName]);

        
$columnRows $this->getRows("show columns from $tableName");
        if ( ! 
$columnRows ) {
            
Mview::error("No columns for $tableName");
            
$cache[$tableName] = null ;
            return(
$cache[$tableName]);
        }
        
$cols = array();
        foreach ( 
$columnRows as $col )
            
$cols[] = $col['Field'] ;
        
$cache[$tableName] = $cols ;
        return(
$cache[$tableName]);
    }
    
/*----------------------------------------*/
    /**
      * does field exist in a table
      *
      * @param string the name of the table
      * @param string the name of the field
      * @return bool
      */
    
public function isColumn($tableName$column) {
        
$columns $this->columns($tableName);
        if ( ! 
$columns )
            return(
false);
        return(
in_array($column$columns));
    }
    
/*----------------------------------------*/
    /**
      * number of rows in a table
      *
      * @param string the name of the table
      * @return int the number of rows in the table
      */
    
public function rowNum($t) {
        return(
$this->getInt("select count(*) from $t"));
    }
    
/*----------------------------------------*/
    /**
      * list of tables in database
      *
      * @param string the database name (optional - if not the default database)
      * @return array list of tables
      */
    
public function tables($db null) {
        static 
$cache null;
        if ( ! 
$db )
            
$db $this->dbName;

        if ( ! 
$db )
            return(
false);

        if ( isset(
$cache[$db]) )
            return(
$cache[$db]);
        
$cache[$db] = $this->getStrings("show tables from $db"2*3600);
        return(
$cache[$db]);
    }
    
/*----------------------------------------*/
    /**
     * list of databases
     */
    
public function databases() {
        static 
$cache null;
        static 
$excludes = array('information_schema''mysql''test',);
        
$dbHost $this->dbHost;
        if ( isset(
$cache[$dbHost]) )
            return(
$cache[$dbHost]);
        
$allDatabases $this->getStrings("show databases");
        
$databases = array();
        foreach ( 
$allDatabases as $db )
            if ( ! 
in_array($db$excludes) )
                
$databases[] = $db;
        
$cache[$dbHost] = $databases;
        return(
$cache[$dbHost]);
    }
    
/*----------------------------------------*/
    /**
     * does table exist
     *
     * @param string the table name
     * @return bool
     */
    
public function isTable($t) {
        
$pair explode('.'$t);
        if ( 
count($pair) == ) {
            
$dbname $pair[0];
            
$tname $pair[1];
            
$sql "select count(*) from information_schema.tables where table_schema = '$dbname' and table_name = '$tname'";
            return(
$this->getInt($sql));
        }
        
$tables $this->tables();
        
// with xampp, all table names are lowercase but $t might not be
        
return(in_array($t$tables) || in_array(strtolower($t), $tables));
    }
    
/*------------------------------------------------------------*/
    /**
      * dbInsert() without logging (see dbLog())
      *
      * @param string table to insert the data to
      * @param array associative array with data. Fields not matching columns of the table are silently ignored. 
      * @return int auto-increment id of the new row
      */
     
    
public function _dbInsert($tableName$data$rememberLastSql true$withId false) {
        if ( ! 
$this->isConnected ) {
            
Mview::error("Mmodel not connected");
            return(
null);
        }

        if ( (
$sql $this->dbInsertSql($tableName$data$withId)) == null )
            return(
null);

        
$affected $this->_sql($sql$rememberLastSql);
        if (  
$affected != )
            return(
null);
        
$this->lastInsertId mysql_insert_id();
        return(
$this->lastInsertId);
    }
    
/*------------------------------*/
    /**
      * insert data to database
      *
      * @param string table to insert the data to
      * @param array associative array with data. Fields not matching columns of the table are silently ignored. 
      * @return int auto-increment id of the new row
      */
    
public function dbInsert($tableName$data$withId false) {
        if ( (
$id $this->_dbInsert($tableName$datatrue$withId)) == null )
            return(
null);
        
$this->dbLog($tableName'insert'$id);
        return(
$id);
    }
    
/*------------------------------------------------------------*/
    /**
      * update a row of data - raw interface - see also dbUpdate() & dbLog() 
      *
      * @param string table name
      * @param int value of id key identifying the row to be updated
      * @param array associative array with data. Fields not matching columns of the table are silently ignored. 
      * @param string the name of the id field if it is not 'id'
      * @return int -1 on error, 0 if the query had no effect, 1 if an actual change occured (see mysql_affected_rows())
      */
    
public function _dbUpdate($tableName$id$data$idName "id") {
        if ( ! 
$this->isConnected ) {
            
Mview::error("Mmodel not connected");
            return(-
1);
        }
        
$cols $this->columns($tableName);
        if ( ! 
$cols )
            return(-
1);
        
$this->ammend($data$cols);
        
$origData $this->getRow("select * from $tableName where $idName = $id");
        
$pairs = array();
        foreach ( 
$data as $fname => $value ) {
            if ( 
$fname == $idName || ! in_array($fname$cols) || $this->equiv($origData[$fname], $value) )
                continue;
            
$dataType $this->dataType($tableName$fname);
            if ( 
$dataType == 'timestamp' )
                continue;
            if ( 
$dataType == 'date' && ($value Mdate::scan($value)) == null )
                    continue;
            if ( 
$dataType == 'datetime' && ($value Mdate::datetimeScan($value)) == null )
                    continue;
            
$str $this->str($value);
            if ( 
$str === 'now()' )
                
$pairs[] = "$fname = $str";
            elseif ( 
$str === null )
                
$pairs[] = "$fname = null";
            else
                
$pairs[] = "$fname = '$str'";
        }
        if ( ! 
$pairs ) {
            
// nothing changed - do nothing else
            
return(0);
        }
        
$pairList implode(", "$pairs);
        
$sql "update $tableName set $pairList where $idName = $id";
        
$affected $this->_sql($sql);
        return(
$affected);
    }
    
/*--------------------*/
    /**
      * update a row of data
      *
      * @param string table name
      * @param int value of id key identifying the row to be updated
      * @param array associative array with data. Fields not matching columns of the table are silently ignored. 
      * @param string the name of the id field if it is not 'id'
      * @return bool true if all is well, (including no-change), false on error
      */
    
public function dbUpdate($tableName$id$data$idName "id") {
        
$affected $this->_dbUpdate($tableName$id$data$idName "id");
        if ( 
$affected )
            
$this->dbLog($tableName'update'$id);
        return(
$affected >= 0);
    }
    
/*----------------------------------------*/
    /**
      * delete a row (without logging - see dbLog())
      *
      * @param string table name
      * @param int value of id key identifying the row to be updated
      * @param string the name of the id field if it is not 'id'
      * @return int 1 on success, 0 if nothing was deleted, -1 if an error occured
      */
    
public function _dbDelete($tableName$id$idName "id") {
        if ( ! 
$this->isConnected ) {
            
Mview::error("Mmodel not connected");
            return(-
1);
        }
        
$sql "delete from $tableName where $idName = $id";
        
$affected $this->_sql($sql);
        return(
$affected);
    }
    
/*----------------------------------------*/
    /**
      * delete a row
      *
      * @param string table name
      * @param int value of id key identifying the row to be updated
      * @param string the name of the id field if it is not 'id'
      * @return bool true if all is well, (including no-change), false on error
      */
    
public function dbDelete($tableName$id$idName "id") {
        
$affected $this->_dbDelete($tableName$id$idName);
        if ( 
$affected )
            
$this->dbLog($tableName'delete'$id);
        return(
$affected >= 0);
    }
    
/*------------------------------------------------------------*/
    /**
      * return sql representing the data in the table ( use dumpTable() to stream large tables )
      *
      * @param string the table name
      * @param string single field to sort by or null to avoid sorting
      * @return string sql statement(s) to (re-)insert data to the table
      */
    
public function tableDump($tableName$orderBy "id") {
        if ( 
$orderBy && $this->isColumn($tableName$orderBy) )
            
$ob "order by $orderBy";
        else
            
$ob "";
        
$ret "drop table if exists $tableName;\n";
        
$cr $this->getRow("show create table $tableName");
        
$crvalues array_values($cr);
        
$ret .= $crvalues[1].";\n";
        
$rows $this->getRows("select * from $tableName $ob");
        foreach ( 
$rows as $row )
            
$ret .= $this->dbInsertSql($tableName$rowtrue).";\n";
        return(
$ret);
    }
    
/*------------------------------------------------------------*/
    /**
      * stream an SQL dump of the table to the standard output
      *
      * @param string the table name
      */
    
public function dumpTable($tableName$select null) {
        
$ai $this->autoIncrement($tableName);
        if ( 
$ai )
            
$ob "order by $ai";
        else
            
$ob "";
        
$datetime date("Y-m-d G:i:s.u");
        if ( 
$select ) {
            
$query $select;
            echo 
"-- M::dumpTable - $tableName - $select\n";
        } else {
            
$query "select * from $tableName $ob";
            echo 
"-- M::dumpTable starting - $tableName - $datetime\n";
            echo 
"drop table if exists $tableName;\n";
            
$cr $this->getRow("show create table $tableName");
            
$crvalues array_values($cr);
            echo 
$crvalues[1].";\n";
        }
        
$res $this->query($query);
        if ( ! 
$res )
            return;
        while(
$row = @mysql_fetch_assoc($res))
            echo 
$this->dbInsertSql($tableName$rowtrue).";\n";

        if ( ! 
$select ) {
            
$datetime date("Y-m-d G:i:s.u");
            echo 
"-- M::dumpTable done - $tableName - $datetime\n";
            echo 
"\n";
        }
    }
    
/*------------------------------------------------------------*/
    /**
      * the data type of a column in a table
      *
      * @param string the table name
      * @param string the column name
      * @return string the data type
      */
    
public function dataType($tableName$fieldName) {
        static 
$cache = array();

        if ( isset(
$cache[$tableName][$fieldName]) )
            return(
$cache[$tableName][$fieldName]);
        
$columnRows $this->getRows("show columns from $tableName"2*3600);
        foreach ( 
$columnRows as $col )
            
$cache[$tableName][$col['Field']] = $col['Type'];

        if ( isset(
$cache[$tableName][$fieldName]) )
            return(
$cache[$tableName][$fieldName]);
        return(
null);
    }
    
/*------------------------------------------------------------*/
    /**
     * provide data for jquery autocomplete
     *
     * the request url is of the form id=$tname-$fname&q=...
     * e.g. 
     *    $(".autocomplete", context).autocomplete("?className=Mmodel&action=autocomplete&id=" + $this.id);
     */
     
public function autoComplete() {
        
$id explode('-', @$_REQUEST['id']);
        if ( ! 
$id ) {
            @
syslog(LOG_ERR__FILE__.":"__LINE__.": ".get_class().":".__FUNCTION__.": bad id args: ".$_SERVER['QUERY_STRING']);
            echo 
"autoComplete error\n";
            return;
        }
        
$cnt count($id);
        if ( 
$cnt == )
            list(
$tname$fname) = $id;
        elseif ( 
$cnt == ) {
            list(
$dbname$tname$fname) = $id;
            if ( ! 
$this->selectDB($dbname) ) {
                @
syslog(LOG_ERR__FILE__.":"__LINE__.": ".get_class().":".__FUNCTION__.": bad id args: ".$_SERVER['QUERY_STRING']);
                echo 
"autoComplete: cannont use $dbname\n";
                return;
            }
        } else {
            @
syslog(LOG_ERR__FILE__.":"__LINE__.": ".get_class().":".__FUNCTION__.": bad id args: ".$_SERVER['QUERY_STRING']);
            echo 
"autoComplete error\n";
            return;
        }

         
$q  $_REQUEST['q'];
        
$sql "select distinct $fname from $tname where $fname like '$q%' order by $fname";
        
$strings $this->getStrings($sql);
        if ( ! 
$strings )
            return;
        
$ret implode("\n"$strings)."\n";
        echo 
$ret;
     }
    
/*------------------------------*/
    
public function permit() {
         
// allows automatic Mautocomplete!!
        
return(true);
    }
    
/*------------------------------------------------------------*/
    /**
     * update a single item in the database from parameters in $_REQUEST
     * 
     * saveFieldInfo() is used directly from the jQuery jeditable plugin
     * as a complete server-side ajax updater/responder
     */
    
public function saveFieldInfo() {
        
$elementId $_REQUEST['id'];
        
$value $_REQUEST['value'];

            
        
$nameId explode("-"$elementId);
        
$tname $nameId[0];
        
$fname $nameId[1];
        
$id $nameId[2];
        
$OldValue $this->getString("select $fname from $tname where id = $id");

        
// updating the database requires login credentials
        
$Msession = new Msession;
        if ( ! 
$Msession->get('loginName') ) {
            echo 
"$OldValue";
            exit;
        }
        
$dataType $this->dataType($tname$fname);
        if ( 
$dataType == 'date' ) {
            if ( (
$value Mdate::scan($value)) == null ) {
                echo 
"$OldValue";
                exit;
            }
        }
        if ( 
$dataType == 'time' ) {
            if ( (
$value Mtime::fmt($value)) == null ) {
                echo 
"$OldValue";
                exit;
            }
        }
        
$sqlValue $this->str($value);
        
$sql "update $tname set $fname = '$sqlValue' where id = $id";
        
$affected $this->sql($sql);
        
$newValue $this->getString("select $fname from $tname where id = $id");

        if ( 
$dataType == 'date' )
            
$newValue Mdate::fmt($newValue);
        elseif ( 
$dataType == 'time' )
            
$newValue Mtime::fmt($newValue);
        elseif ( 
$dataType == 'float' || $dataType == 'double' )
            
$newValue msuFloatFmt((float)$newValue);

        echo 
$newValue;
    }
    
/*------------------------------------------------------------*/
    /**
     * log database activity in a table called queryLog
     *
     * Normally only used as 'private',
     * dbInsert(), dbUpdate(), dbDelete() and sql() attempt
     * to log activities when successful changes occur,
     * while _dbInsert(), _dbUpdate(), _dbDelete() and _sql() do not.
     * 
     * @param string table name
     * @param operation performed
     * @param int id of the affected row
     * @param string the sql statement performing the operation
     */
    
public function dbLog($tname$op$tid$querySql null) {
        if ( ! 
$this->isTable('queryLog') )
            return;
        
$Msession = new Msession;
        
$row = array(
                
'tname' => $tname,
                
'op' => $op,
                
'tid' => $tid,
                
'querySql' => $querySql $querySql $this->lastSql,
                
'loginName' => $this->Msession->get('loginName'),
                
'stamp' => 'now()'
            
);
        
$this->_dbInsert('queryLog'$rowfalse);
    }
    
/*------------------------------------------------------------*/
    /**
     * a database ready representation of now()
     *
     * @return string
     */
    
public function datetimeNow() {
        
$today Mdate::dash(Mdate::today());
        
$now Mtime::fmt(Mtime::now());
        
$ret "$today $now";
        return(
$ret);
    }
    
/*------------------------------*/
    /**
     * a database ready representation of now() in a given timezone
     *
     * @param string timezone  (see date_default_timezone_set())
     * @return string
     */
    
public function datetimeNowInTZ($tz null) {
        
$todayInTZ Mdate::dash(Mdate::todayInTZ($tz));
        
$nowInTZ Mtime::nowInTZ($tztrue);
        
$ret "$todayInTZ $nowInTZ";
        return(
$ret);
    }
    
/*------------------------------------------------------------*/
    /**
     * sql for a sample of rows from table
     *
     * @param string name of table
     */
    
public function sampleSql($table) {
        if ( ! 
$this->isTable($table) )
            return(
null);
        
$id $this->autoIncrement($table);
        
$fieldList implode(','$this->columns($table));

        
$rowNum $this->rowNum($table);
        if ( ! 
$id  ) {
            if ( 
$rowNum 500 )
                
$limit "limit 500";
            else
                
$limit "";
            return(
"select $fieldList from $table $limit");
        }
        if ( 
$rowNum 100 )
            
$sql "select $fieldList from $table order by $id";
        elseif ( 
$rowNum 1000 )
            
$sql "select $fieldList from $table where $id % 10 = 0 order by $id";
        elseif ( 
$rowNum 10000 )
            
$sql "select $fieldList from $table where $id % 100 = 0 order by $id";
        elseif ( 
$rowNum 100000 )
            
$sql "select $fieldList from $table where $id % 1000 = 0 order by $id";
        else
            
$sql "select $fieldList from $table order by $id desc limit 100";
        return(
$sql);
    }
    
/*------------------------------------------------------------*/
    
public function name($tname$fname$id) {
        static 
$cache = array();
        if ( isset(
$cache[$tname][$fname][$id]) )
            return(
$cache[$tname][$fname][$id]);
        if ( ! @
$cache[$tname] )
            
$cache[$tname] = array();
        if ( ! @
$cache[$tname][$fname] ) {
            
$cache[$tname][$fname] = array();
            
$rows $this->getRows("select id,$fname from $tname"30*60);
            foreach ( 
$rows as $row )
                
$cache[$tname][$fname][$row['id']] = $row[$fname];
        }
        return(@
$cache[$tname][$fname][$id]);
    }
    
/*------------------------------*/
    
public function id($tname$fname$name$make false) {
        static 
$cache = array();
        if ( isset(
$cache[$tname][$fname][$name]) )
            return(
$cache[$tname][$fname][$name]);
        if ( ! @
$cache[$tname] )
            
$cache[$tname] = array();
        if ( ! @
$cache[$tname][$fname] ) {
            
$cache[$tname][$fname] = array();
            
$rows $this->getRows("select id,$fname from $tname"30*60);
            foreach ( 
$rows as $row )
                
$cache[$tname][$fname][$row[$fname]] = $row['id'];
        }
        if ( @
$cache[$tname][$fname][$name] )
            return(
$cache[$tname][$fname][$name]);
        if ( ! 
$make )
            return(
null);
        
$id $this->_dbInsert($tname, array(
            
$fname => $name,
        ));
        if ( ! 
$id )
            return(
null);
        
$cache[$tname][$fname][$name] = $id;
        return(
$cache[$tname][$fname][$name]);
    }
    
/*------------------------------------------------------------*/
    
public function lastError() {
        return(
$this->lastError);
    }
    
/*------------------------------------------------------------*/
    /*------------------------------------------------------------*/
    /*------------------------------------------------------------*/
    
private function ammend(&$data$cols$insert false) {
        
$Msession = new Msession;
        if ( 
$insert && in_array('createdOn'$cols) && ! isset($data['createdOn']) )
            
$data['createdOn'] = date("Y-m-d G:i:s");
        if ( 
$insert && in_array('createdBy'$cols) && ! isset($data['createdBy']) )
            
$data['createdBy'] = $Msession->get('loginName');
        if ( 
in_array('lastChange'$cols) && ! isset($data['lastChange']) )
            
$data['lastChange'] = date("Y-m-d G:i:s");
        if ( 
in_array('lastChangeBy'$cols) && ! isset($data['lastChangeBy']) )
            
$data['lastChangeBy'] = $Msession->get('loginName');
    }
    
/*------------------------------*/
    
private function dbInsertSql($tableName$data$withId false) {
        
$cols $this->columns($tableName);
        if ( ! 
$cols )
            return(
null);
        
$row = array();
        
$idName $this->autoIncrement($tableName);
        foreach ( 
$data as $fname => $value ) {
            if ( ! 
in_array($fname$cols) )
                continue;
            if ( 
$fname == $idName )
                continue;
            
$dataType $this->dataType($tableName$fname);
            if ( 
$dataType == 'timestamp' )
                continue;
            if ( 
$dataType == 'date' && ($value Mdate::scan($value)) == null )
                    continue;
            if ( 
$dataType == 'datetime' && ($value Mdate::datetimeScan($value)) == null )
                    continue;
            
$str $value;
            if ( 
$str === null )
                continue;
            
$row[$fname] = $str;
        }
        
$insertData = array();
        foreach ( 
$row as $fname => $value )
                
$insertData[$fname] = $value;
        
$this->ammend($insertData$colstrue);
        
$fieldList implode(','array_keys($insertData));
        
$valueList = array();
        foreach ( 
$insertData as $value )
            if ( 
$value === 'now()' )
                
$values[] = 'now()';
            else
                
$values[] = "'".$this->str($value)."'";
        
$valuesList implode(","$values);
        
$sql "insert into $tableName ( $fieldList ) values ( $valuesList )";
        return(
$sql);
    }
    
/*------------------------------------------------------------*/
    /*
     * do fields have equivalent values:
     * nulls and empty strings, dates in int or dashed format are equivalent
     * updating of equivalent values is skipped
     */
    
private function equiv($a$b) {
        if ( 
$a == $b )
            return(
true);
        
$alen strlen($a);
        
$blen strlen($b);
        
// dates
        
if (
            ( 
$alen == 10 || $alen == )
            && ( 
$alen == 10 || $alen == )
            && 
str_replace('-'''$a) == str_replace('-'''$b)
            )
            return(
true);
        
// text
        
if (
            
str_replace("\r\n""\n"$a) == str_replace("\r\n""\n"$b)
            )
            return(
true);
        return(
false);
    }
    
/*------------------------------------------------------------*/
}
/*------------------------------------------------------------*/

../M/Mview.class.php

<?php
/*------------------------------------------------------------*/
/**
  * @package M
  * @author Ohad Aloni
  */
/*------------------------------------------------------------*/
/**
  * requires Smarty software
  */
if ( ! class_exists("Smarty") )
    require_once(
"Smarty-2.6.9/libs/Smarty.class.php");
/*------------------------------------------------------------*/
require_once("msu.php");
require_once(
"Mdate.class.php");
/*------------------------------------------------------------*/
/**
  *  Mview - View class
  *
  * Mview is an extension of the class Smarty (smarty.php.net)
  *
  * @package M
  * @author Ohad Aloni
  */
class Mview extends Smarty {
    
/*------------------------------------------------------------*/
    // messages and error require no construction with new()
    /*------------------------------------------------------------*/
    /**
     * @var bool
     */
    
private $templateDirPath = array();
    
/*------------------------------------------------------------*/
    
function __construct() {
        if ( ! 
defined('SMARTY_TPL_DIR') )
            
define('SMARTY_TPL_DIR''tpl');

        if ( ! 
defined('SMARTY_RUN_DIR') )
            
define('SMARTY_RUN_DIR''smarty');

        
$this->use_sub_dirs false;
        
$smartyTplDir SMARTY_TPL_DIR ;
        
$smartyRunDir SMARTY_RUN_DIR ;
        
$this->template_dir $smartyTplDir;
        
$this->prependTemplateDir($smartyTplDir);
        if ( 
defined('M_DIR') )
            
$this->appendTemplateDir(M_DIR."/tpl");
        
$this->compile_dir "$smartyRunDir/compile/";
        
$this->config_dir "$smartyRunDir/config/";
        
$this->cache_dir "$smartyRunDir/cache/";

        
$this->registerFunction('msuShowTpl');
        
$this->registerFunction('msuVarDump');
        
$this->registerFunction('showMsg');
        
$this->registerFunction('showError');
        
$this->registerFunction('msuSetTitle');
        
$this->registerFunction('msuSetStatus');

        
$this->registerModifier('msuMoneyFmt');
        
$this->registerModifier('msuFloatFmt');
        
$this->registerModifier('msuDateFmt');
        
$this->registerModifier('msuDateTimePickerFmt');
        
$this->registerModifier('msuIntFmt');
        
$this->registerModifier('msuTimeFmt');
        
$this->registerModifier('htmlspecialchars');
        
$this->registerModifier('undash''Mdate');

        
$this->assign(array(
            
"yesNo" => array(
                
=> 'No',
                
=> 'Yes',
            ),
        ));
    }
    
/*------------------------------------------------------------*/
    
private function registerClass($method$class) {
        if ( 
function_exists($method) )
            return(
true);

        if ( 
$class ) {
            if ( ! 
class_exists($class) ) {
                require_once(
"$class.class.php");
                if ( ! 
class_exists($class) ) {
                    
self::error("Mview::registerClass: class $class not found");
                    return(
null);
                }
            }
            if ( 
method_exists($class$method) )
                return(
$class);
            else {
                
self::error("Mview::registerClass: method $method not found in $class");
                return(
null);
            }
        }

        if ( 
method_exists("Mutils"$method) )
            return(
"Mutils");
        
self::error("Mview::registerClass: method $method not found");
        return(
null);
    }
    
/*------------------------------*/
    /**
     * register a smarty plugin function
     *
     * @param string can be any function or method in Mview or Mutils or the passed class
     * @param string
     */
    
private function registerFunction($method$class null) {
        
$callable = array($this$method);
        if ( 
is_callable($callable) ) {
            
$this->register_function($method$callable);
            return;
        }
        
// todo
        
$class $this->registerClass($method$class);
        if ( ! 
$class )
            return;
        if ( 
$class === true )
            
$this->register_function($method$methodfalse);
        else
            
$this->register_function($method, array($class$method,));
    }
    
/*------------------------------------------------------------*/
    /**
     * register a smarty plugin filter (modifier)
     *
     * @param string can be any function or method in Mview or Mutils or the passed class
     * @param string
     */
    
public function registerModifier($method$class null) {
        
$class $this->registerClass($method$class);
        if ( ! 
$class )
            return;
        if ( 
$class === true )
            
$this->register_modifier($method$method);
        else
            
$this->register_modifier($method, array($class$method,));
    }
    
/*------------------------------------------------------------*/
    /**
     * prepend a template folder to the template search path
     *
     * for use with {msuShowTpl file=... arg1=...} - 
     *
     * when using include in templates, only the first $smarty->template_dir is recognized
     *
     */
    
public function prependTemplateDir($dir) {
        
array_unshift($this->templateDirPath$dir);
    }
    
/*------------------------------*/
    /**
     * append a template folder to the template search path
     *
     * for use with {msuShowTpl file=... arg1=...} - 
     *
     * when using include in templates, only the first $smarty->template_dir is recognized
     *
     */
    
public function appendTemplateDir($dir) {
        
$this->templateDirPath[] = $dir;
    }
    
/*------------------------------------------------------------*/
    
private function _render($tpl) {
        if ( ! 
is_writable($this->compile_dir) ) {
            
$pwd trim(`pwd`);
            
$this->error("Smarty Compile Dir $pwd/{$this->compile_dir} not writable");
            return(
false);
        }
        if ( 
is_readable($tpl) )
            return(
$this->fetch($tpl));
        
$cwd getcwd();
        foreach ( 
$this->templateDirPath as $dir ) {
            if ( 
is_readable("$cwd/$dir/$tpl") )
                return(
$this->fetch("$cwd/$dir/$tpl"));
            if ( 
is_readable("$dir/$tpl") )
                return(
$this->fetch("$dir/$tpl"));
        }
        if ( isset(
$this->template_dir) ) {
            
$td $this->template_dir;
            if ( 
is_readable("$td/$tpl") )
                return(
$this->fetch($tpl));
        }
        
$showPath implode(":"$this->templateDirPath);
        
$this->error("Mview: $tpl not found in $showPath");
        return(
null);
    }
    
/*------------------------------------------------------------*/
    /**
     * return a rendered template
     */
    
public function render($tpl$args) {
        if ( 
is_array($args) ) {
            
$this->assign($args);
            
$this->assign(array('tplArgs' => $args));
        }
        
$rendered $this->_render($tpl);
        if ( 
is_array($args) ) {
            
$keys array_keys($args);
            
$this->clear_assign($keys);
            
$this->clear_assign('tplArgs');
        }
        return(
$rendered);
    }
    
/*------------------------------------------------------------*/
    
private static $holdOutput false;
    private static 
$outputBuffer "";
    
/*------------------------------*/
    
public static function holdOutput() {
        
self::$holdOutput true;
    }
    
/*------------------------------*/
    
public static function flushOutput() {
        
// msgbuf had better been output by now by app, not usable after this;
        
$Msession = new Msession;
        
$msgBuf $Msession->get('msgBuf');
        if ( 
$msgBuf )
            
$Msession->set('msgBuf', array()); // sets cookie - must be bfore next...
        
if ( self::$outputBuffer ) {
            echo 
self::$outputBuffer;
            
self::$outputBuffer "";
        }
        
flush();
        @
ob_flush(); // may have been turned off by ob_implicit_flush()
    
}
    
/*------------------------------*/
    
private static function pushOutput($htmlText) {
        if ( 
self::$holdOutput )
            
self::$outputBuffer .= $htmlText;
        else
            echo 
$htmlText;
    }
    
/*------------------------------*/
    /**
     * show a template
     *
     * @param string file name
     * @param array list of named arguments
     * @param fetch only return the rendered template and do not display if true
     *
     */
    
public function showTpl($tpl null$args null$fetch false) {
        if ( 
$tpl == null )
            
$tpl = @$_REQUEST['tpl'];
        
$fetched $this->render($tpl$args);
        if ( ! 
$fetch )
            
self::pushOutput($fetched);
        return(
$fetched);
    }
    
/*------------------------------*/
    
public function permit() {
         
// allow automatic urls /Mview/showTpl?tpl=
        
return(true);
    }
    
/*------------------------------------------------------------*/
    
public static function showRows($rows$exportFileName null) {
        global 
$Mview// need an instance anyway

        
if ( ! $rows || ! is_array($rows) || count($rows) == ) {
            
self::msg("No Rows");
            return;
        }
        
$columns array_keys($rows[0]);

        if ( ! 
$Mview )
            
$Mview = new Mview;
        
$Mview->showTpl("mShowRows.tpl", array(
                
'columns' => $columns,
                
'rows' => $rows,
                
'exportFileName' => $exportFileName,
            ));
    }
    
/*------------------------------------------------------------*/
    
private static $msgBuf = array();
    private static 
$isHold false;
    
/*------------------------------*/
    /**
     * buffered messages
     */
    
public function messages() {
        return(
self::$msgBuf);
    }
    
/*------------------------------*/
    /**
     * buffer requested messages and errors
     * do not show anything at least until a further notice by flushMsgs()
     */
    
public function holdMsgs() {
        
self::$isHold true;
    }
    
/*------------------------------*/
    /**
     * flush messages and errors previously held due to a call to holdMsgs() and stop buffering
     */
    
function flushMsgs() {
        
self::$isHold false ;

        foreach ( 
self::$msgBuf as $msg )
            
self::message($msg['msg'], $msg['iserror']);
        
self::$msgBuf = array();
    }
    
/*------------------------------*/
    
private static function message($msg$iserror) {
        
$me get_class()."::".__FUNCTION__."()";
        if ( 
strstr($msg"Cannot ") ) {
            
Mutils::trace();
            
self::flushOutput();
            exit;
        }
        
$isHtml = isset($_SERVER['REMOTE_ADDR']);
        if ( 
$isHtml ) {
            
$color $iserror "red" "blue" ;
            
$msg htmlspecialchars($msg);
            
$msg nl2br($msg);
            
$text "<div style=\"color:$color; font-size:small; font-weight: bold;\">$msg</div>\n" ;
        }
        else {
            
$pfx $iserror "ERROR: " "" ;
            
$text =  "$pfx$msg\n" ;
        }
        
$Msession = new Msession;
        
$sessionMsgBuf $Msession->get('msgBuf');
        if ( ! 
$sessionMsgBuf )
            
$sessionMsgBuf = array();
        
$numMessages count($sessionMsgBuf);
        if ( 
$numMessages >= ) {
            
$lastText $sessionMsgBuf[$numMessages-1];
            if ( 
$lastText != '...' ) {
                
$sessionMsgBuf[] = '...';
                
$Msession->set('msgBuf'$sessionMsgBuf);
            }
        } else {
            
$sessionMsgBuf[] = $text;
            
$Msession->set('msgBuf'$sessionMsgBuf);
        }
        
self::pushOutput($text);
    }
    
/*------------------------------*/
    /**
     * show a msg (or hold it - see holdMsgs())
     *
     * @param string
     */
    
public static function msg($msg$iserror false) {
        if ( 
self::$isHold )
            
self::$msgBuf[] = array('msg' => $msg'iserror' => $iserror);
        else
            
self::message($msg$iserror);
    }
    
/*------------------------------*/
    /**
     * show an error (or hold it - see holdMsgs())
     *
     * @param string
     */
    
public static function error($msg) {
        
self::msg($msgtrue);
    }
    
/*------------------------------------------------------------*/
    /**
     * show a message from a template
     *
     * {showMsg msg="..."}
     */
    
public function showMsg($a) { self::msg($a['msg']); }
    
/*------------------------------*/
    /**
     * show an error from a template
     *
     * {showError msg="..."}
     */
    
public function showError($a) { self::error($a['msg']); }
    
/*------------------------------------------------------------*/
    /**
     * a fancier version of print_r helps debugging by showing a title, file and line number
     * in a more legible display
     *
     *
     * e.g<br />
     * Mview::print_r($_REQUEST, "_REQUEST", __FILE__, __LINE__);
     *
     * @param mixed
     * @param string
     * @param string
     * @param int
     * 
     */
    
public static function print_r($var$varName null$file null$line null$return false) {
        
$isHtml = isset($_SERVER['REMOTE_ADDR']);
        
$ret "";
        if ( 
$isHtml )
            
$ret .= "\n<table border=\"0\"><tr><td align=\"left\"><pre>\n";
        if ( 
$file ) {
            
$fileParts explode("/"trim($file"/"));
            
$fileName $fileParts[count($fileParts)-1];
            
$ret .= "$varName ($fileName: $line)\n--------------------------------------------------------------\n";
        }
        
$ret .= print_r($vartrue);
        
$ret .= "\n";
        if ( 
$isHtml )
            
$ret .= "\n</pre></td></tr></table>\n";
        if ( ! 
$return )
            
self::pushOutput($ret);
        return(
$ret);
    }
    
/*------------------------------------------------------------*/
    /**
     *
     * escape quotes for javascript
     *
     * @param string
     * @return string
     */
    
public function jsStr($str) {
        
// escape with \ but
        // if they are already escaped ...
        
$ret str_replace("\\'""'"$str);
        
$ret str_replace("'""\\'"$ret);
        
$ret str_replace("\r\n""\n"$ret);
        
$ret str_replace("\\n""\n"$ret);
        
$ret str_replace("\n""\\n"$ret);
        return(
$ret);
    }
    
/*------------------------------*/
    /**
     * execute javascript by echoing it wrapped in html and flushing output buffers for immeadiate execution
     *
     * @param string
     */
    
public function js($s) {
        echo 
"<script type=\"text/javascript\"> $s </script>\n" ;
        
flush();
        
ob_flush();
    }
    
/*------------------------------*/
    /**
     * set title of page using javascript
     *
     * @param string
     */
    
public function jsTitle($s) {
        
$title $this->jsStr($s);
        
$this->js("document.title = '$title' ; ");
        
    }
    
/*------------------------------------------------------------*/
    
public static function setCookie($name$value$expires null) {
        if ( 
$expires ) {
            unset(
$_COOKIE[$name]);
            @
setcookie($namenulltime(0) + 7"/");
            return;
        }
        
$defaultExpires 10*365*24*60*60;
        if ( 
$expires  == null )
            
$expires $defaultExpires;
        if ( 
$expires  <= $defaultExpires )
            
$expires += time();
        if ( @
setcookie($name$value$expires"/") ) {
            
$_COOKIE[$name] = $value;
        } else {
            
/*    self::error("Cannot set cookie - output already started");    */
            /*    Mutils::trace();    */
            /*    self::flush();    */
            /*    exit;    */
        
}
    }
    
/*------------------------------------------------------------*/
    /**
     * include a template
     * {msuShowTpl file="abc.tpl" a=... b=... c=...}
     */
    
public function msuShowTpl($a) {
        
$tpl $a['file'];
        
$b $a ;
        
$b['tplArgs'] = $a;
        
$rendered $this->showTpl($tpl$btrue);
        
// call from a smarty template -
        // skip output buffering or output will be reversed.
        
echo $rendered;
    }
    
/*------------------------------------------------------------*/
}

/*------------------------------------------------------------*/

../M/Mcontroller.class.php

<?php
/*------------------------------------------------------------*/
/**
  * @package M
  * @author Ohad Aloni
  */
/*------------------------------------------------------------*/
/**
 * Mmodel and Mview are accessible from Mcontroller and any class
 * that extends Mcontroller
 */
require_once("Mmodel.class.php");
require_once(
"Mview.class.php");
/*------------------------------------------------------------*/
/**
  * 
  * Mcontroller has several control functions:
  * 1. Allow control to flow only if appropriate permission conditions are met.<br />
  * 2. Branch URLs of the form className=...&action=... to the corresponding class method.<br />
  * 3. Serve as a superclass to be extended with seamless inheritance from Mmoel and Mview.<br />
  *
  * @package M
  */
/*------------------------------------------------------------*/
class Mcontroller {
    
/**
    * @var controller name of the current controller
    */
    
protected $controller;
    
/**
    * @var action name of the current action
    */
    
protected $action;
    private 
$isConstructed false;
    
/**
    * @var Mmodel access the Mmodel class from this instance
    */
    
public $Mmodel;
    
/**
    * @var Mview access the Mview class from this instance
    */
    
public $Mview;
    
/*------------------------------------------------------------*/
    /**
     * Mcontroller is typically extended with no arguments
     * or created with no arguments.
     *
     * @param Mmodel force use of this instance 
     * @param Mview force use of this instance 
     *
     */
    
function __construct($Mm null$Mv null) {
        global 
$Mmodel;
        global 
$Mview;
        

        if ( 
$Mm !== null )
            
$this->Mmodel $Mm;
        elseif ( isset(
$Mmodel) && $Mmodel != null )
            
$this->Mmodel $Mmodel;
        else
            
$this->Mmodel = new Mmodel();

        if ( 
$Mv !== null )
            
$this->Mview $Mv;
        elseif ( isset(
$Mview) && $Mview != null )
            
$this->Mview $Mview;
        else
            
$this->Mview = new Mview();

        if ( ! 
$this->Mmodel ) {
            
$stack debug_backtrace(false);
            
Mview::print_r($stack"stack"__FILE____LINE__);
            
$Mview->flush();
            exit;
        }
        if ( 
$this->Mmodel->isConnected() )
            
$this->isConstructed true;
    }
    
/*------------------------------*/
    
public function isConstructed() {
        return(
$this->isConstructed);
    }
    
/*------------------------------------------------------------*/
    /**
     * control() decides which method of which class is executed.
     * It is typically called from index.php
     * after a control class is extended from Mcontroller:<br />
     * $mcc = new MyControlClass;<br />
     * $mcc->control();<br />
     * 
     * className denotes the class name and can be presented:
     * 1. in the URL as className=...<br />
     * 2. as the first part of path info<br />
     * 3. as a hidden field in a form.<br />
     * the method is denoted by the word 'action' and can be presented:<br />
     * 1. in the URL as action=...<br />
     * 2. as the second part of path info<br />
     * 3. as a hidden field in a form.<br />
     *
     * The class Abc will be automatically loaded from Abc.class.php
     * if it is not already loaded and there is such a file.
     *
     * if called from within a controller, dispatiching is redirected and args are (re)placed in $_REQUEST<br />
     * e.g.<br />
     *   $this->control("Authors", "listBooks", array('authorId' => 2,))<br />
     * will execute as if from a url ?className=Authors&action=listBooks&authorId=2<br />
     * this re-dispatches the action directly (without a web redirect)<br /><br />
     * so<br />
     *   $this->control("Authors", "listBooks", array('authorId' => 2,))<br />
     *   $this->control("Authors", "listBooks", array('authorId' => 3,))<br />
     * might have the effect of rendering the results of both actions consecutively on the same page<br />
     *    
     */
    
public function control($className null$action null$args null) {
        
/*    $this->topUri = $topUri;    */
        
$requestArgs = array();
        if ( 
is_string($args) ) {
            
$vars explode('&'$args);
            foreach ( 
$vars as $var ) {
                
$nv explode('='$var);
                if ( 
count($nv) != ) {
                    
$this->Mview->error("$var ???");
                    continue;
                }
                list(
$n$v) = $nv;
                
$requestArgs[$n] = $v;
            }
        } else if ( 
$args ) {
            foreach ( 
$args as $key => $arg )
                
$requestArgs[$key] = $arg;
        }

        
$obj $this->obj($className);
        if ( ! 
$obj )
            return(
null);

        
$action $this->action($action);
        if ( 
$action == null )
            
$action "index";

        if ( ! 
is_callable(array($obj$action)) ) {
            
$className get_class($obj);
            
$this->Mview->error("Mcontroller: Method '$action' not callable in class '$className'");
            return(
null);
        }
        
$className get_class($obj);

        
$this->controller strtolower($className);
        
$this->action strtolower($action);
        
$obj->controller strtolower($className);
        
$obj->action strtolower($action);
        
$savedRequestArgs $this->setRequestArgs($requestArgs);
        if ( ! 
$obj->permit($className$action) )
            return(
null);
        if ( 
method_exists($obj"before") ) // e.g. Mmodel auto-autocomplete does not extend Mcontroller
            
$obj->before();
        
$obj->$action();
        if ( 
method_exists($obj"after") ) // e.g. Mmodel auto-autocomplete does not extend Mcontroller
            
$obj->after();
        
$this->revertRequestArgs($requestArgs$savedRequestArgs);
    }
    
/*------------------------------*/
    
private function setRequestArgs($requestArgs) {
        
$savedRequestArgs = array();
        foreach ( 
$requestArgs as $key => $arg ) {
            if ( 
array_key_exists($key$_REQUEST) )
                
$savedRequestArgs[$key] = $_REQUEST[$key];
            
$_REQUEST[$key] = $arg;
        }
        return(
$savedRequestArgs);
    }
    
/*------------------------------*/
    
private function revertRequestArgs($requestArgs$savedRequestArgs) {
        foreach ( 
$requestArgs as $key =>  $arg )
            unset(
$_REQUEST[$key]);
        foreach ( 
$savedRequestArgs as $key =>  $arg )
            
$_REQUEST[$key] = $arg;
    }

    
/*------------------------------*/
    
protected function before() {}
    protected function 
after() {}
    
/*------------------------------------------------------------*/
    
private function obj($className) {
        if ( (
$className $this->className($className)) == null )
            return(
$this);
        if ( 
class_exists($className) ) {
                
$obj = new $className;
                return(
$obj);
        }
        
$files Mutils::listDir(".""php");
        
// git problems - Fri Dec 14 15:06:35 IST 2012
        
foreach ( $files as $file ) {
            
$fileParts explode("."$file);
            
$baseName reset($fileParts);
            if(
strtolower($className) != strtolower($baseName) )
                continue;
            require_once(
$file);
            if ( 
class_exists($baseName) ) {
                
$obj = new $baseName;
                return(
$obj);
            }
            
$this->Mview->error("class $baseName no found in $file");
        }
        
/*    $this->Mview->error("cannot find class for '$className' in '".implode(",", $files));    */
        
$this->Mview->error("cannot find class for '$className'");
        return(
null);
    }
    
/*------------------------------------------------------------*/
    
public function redirect($url null)  {
        if ( 
$url && substr($url04) == "http" ) {
            
header("Location: $url");
            exit;
        }
        if ( ! 
$url ) {
            
$url $this->controller;
            if ( 
$this->action )
                
$url .= "/".$this->action;
        }
        if ( 
$url == "/" )
            
$url "";
        
$serverName $_SERVER['SERVER_NAME'];
        
$isHttps = @$_SERVER['HTTPS'] == "on";
        
$s $isHttps "s" "";
        
$url trim($url"/");
        
header("Location: http$s://$serverName/$url");
        exit;
    }
    
/*------------------------------------------------------------*/
    /**
     * decide whether to allow the execution of a method by the user
     *
     * permit() is by default suitable for public access.
     * It returns true thus allowing everything.<br />
     * In secure and controlled systems, permit() is overridden by the extending class
     * to check login credentials
     * 
     * @param string
     * @param string
     * @return bool
     */
     
protected function permit() {
         return(
true);
     }
    
/*------------------------------------------------------------*/
    /**
     * when a URL specifies a controller without an action
     * the method index() is called.
     *
     */

    
public function index() {
        
$className get_class($this);
        
$this->Mview->error("$className: method index() not defined");
        return(
null);
    }
    
/*------------------------------*/
    
public function defaultAction() { $this->index(); }
    public function 
main() { $this->index(); }
    
/*------------------------------------------------------------*/
    /**
     *
     * export data to excel - streaming - for larger data sets
     *
     * @param string the query
     * @param string the filename that the browser will offer to 'save as'
     */
    
public function exportStreamToExcel($sql$fileName null) {
        if ( ! 
$fileName && isset($_REQUEST['fileName']) )
            
$fileName $_REQUEST['fileName'];
        if ( ! 
$fileName )
            
$fileName "M2excel-".date("Ymd");
        
$isheaded false;
        
$res $this->Mmodel->query($sql);
        if ( 
$res === null )
            return;
        while ( 
$row = @mysql_fetch_assoc($res) ) {
            if ( ! 
$isheaded ) {
                
header("Content-type: application/vnd.ms-excel");
                
header("Content-Disposition: attachment; filename=$fileName.xls");
                
$headings array_keys($row);
                
$this->Mview->showTpl("excelHeader.tpl", array("headings" => $headings,));
                
$isheaded true;
            }
            
$this->Mview->showTpl("excelRow.tpl", array("row" => $row,));
        }
        if ( 
$isheaded )
            
$this->Mview->showTpl("excelFooter.tpl");
        else
            
$this->Mview->error("No Rows");
            
    }
    
/*------------------------------------------------------------*/
    /**
     * export data to excel - non-streaming
     *
     * @param array|string as from Mmodel->getRow, or sql query
     * @param string optional 'save as' file name
     */
    
public function exportToExcel($rows null$fileName null) {
        if ( 
is_string($rows) )
            
$sql $rows;
        else if ( 
$rows === null && isset($_REQUEST['sql']) )
            
$sql stripslashes($_REQUEST['sql']);

        if ( isset(
$sql) ) {
            
$this->exportStreamToExcel($sql$fileName);
            return;
        }

        if ( 
count($rows) == ) {
            
$this->Mview->msg("No Rows");
            return;
        }
            
        
$headings array_keys($rows[0]);
        
$this->Mview->assign(array("headings" => $headings"rows" => $rows));
        if ( ! 
$fileName && isset($_REQUEST['fileName']) )
            
$fileName $_REQUEST['fileName'];
        if ( ! 
$fileName )
            
$fileName "M2excel-".date("Ymd");
        
$content $this->Mview->fetch("excel.tpl");
        
$filesize strlen($content);
        
header("Content-type: application/vnd.ms-excel");
        
header("Content-Disposition: attachment; filename=$fileName.xls");
        
header("Content-Length: $filesize");
        echo 
$content;
    }
    
/*------------------------------------------------------------*/
    /**
     *
     * show rows on screen
     *
     * @param string the query
     * @param boolean show number of rows on screen
     * @param string the filename that the browser will offer to 'save as'
     */
    
public function showRowsFromSql($sql$showCount true$exportFileName null) {
        if ( ! 
$exportFileName )
            
$exportFileName "M2excel-".date("Ymd");
        
$res $this->Mmodel->query($sql);
        if ( 
$res === null )
            return;
        
$numRows ;
        
$isheaded false;
        
$numRowsSpanId "numRows".rand(11000000);
        
$numRowsSSpanId "numRowsS".rand(11000000);
        while ( 
$row = @mysql_fetch_assoc($res) ) {
            
$numRows++;
            if ( ! 
$isheaded ) {
                
$headings array_keys($row);
                
$this->Mview->showTpl("mShowRowsHeader.tpl", array(
                    
"sql" => $sql,
                    
'showCount' => $showCount,
                    
'columns' => $headings,
                    
'exportFileName' => $exportFileName,
                    
'numRowsSpanId' => $numRowsSpanId,
                    
'numRowsSSpanId' => $numRowsSSpanId,
                ));
                
$isheaded true;
            }
            
$this->Mview->showTpl("mShowRowsRow.tpl", array("row" => $row,));
        }
        if ( 
$isheaded )
            
$this->Mview->showTpl("mShowRowsFooter.tpl", array(
                
'numRows' => $numRows,
                
'numRowsSpanId' => $numRowsSpanId,
                
'numRowsSSpanId' => $numRowsSSpanId,
            ));
        else
            
$this->Mview->error("No Rows");
            
    }
    
/*------------------------------------------------------------*/
    /**
    /**
     * show rows on screen
     *
     * @param array the rows to be shown
     *
     * each an associative array with indexes to be used for heading titles
     * if rows is a string, it is an sql to fetch the rows and a streaming interface is used
     * optional arguments tell wether to show the number of rows
     * and set the dfefault file name for exporting
     */
    
public function showRows($rows$showCount false$exportFileName null) {
        if ( 
is_string($rows) ) {
            
$this->showRowsFromSql($rows$showCount$exportFileName);
            return;
        }

        if ( ! 
$rows || ! is_array($rows) || count($rows) == ) {
            
$this->Mview->msg("No Rows");
            return;
        }
        
$headings array_keys($rows[0]);
        
$this->Mview->showTpl("mShowRows.tpl", array(
                
'showCount' => $showCount,
                
'columns' => $headings,
                
'rows' => $rows,
                
'exportFileName' => $exportFileName,
            ));
    }
    
/*------------------------------------------------------------*/
    /**
     * show an array on screen - for developing and debugging
     *
     * @param array
     */
    
public function showArray($a) {
        
$this->Mview->showTpl("mShowArray.tpl", array(
                
'a' => $a,
            ));
        
    }
    
/*------------------------------------------------------------*/
    
public function pathParts() {
        if ( isset(
$_REQUEST['PATH_INFO']) )
            
$path $_REQUEST['PATH_INFO'];
        elseif ( isset(
$_SERVER['PATH_INFO']) )
            
$path $_SERVER['PATH_INFO'];
        else
            return(
null);

        
// ignore leading, trailing and duplicate slashes
        
$pathParts = array();
        
$parts explode("/"$path);
        foreach ( 
$parts as $part )
            if ( 
$part != "" )
                
$pathParts[] = $part;

        return(
$pathParts);
    }
    
/*------------------------------------------------------------*/
    
protected static $debugLevel 0;
    
/*------------------------------*/
    
public static function debugLevel($level null) {
        
$currentLevel self::$debugLevel;
        
$requestLevel = @$_REQUEST['debugLevel'];
        
$envLevel Mutils::getenv("debugLevel");
        
$newLevel 0;
        if ( 
$level != null )
            
$newLevel $level;
        if ( 
$currentLevel $newLevel )
            
$newLevel $currentLevel;
        if ( 
$requestLevel $newLevel )
            
$newLevel $requestLevel;
        if ( 
$envLevel $newLevel )
            
$newLevel $envLevel;
        if ( 
self::$debugLevel != $newLevel )
            
Mutils::setenv("debugLevel"$newLevel);
        
self::$debugLevel $newLevel;
        return(
$newLevel);
    }
    
/*------------------------------*/
    
public function debug($file$lineNo$tag$msg null$debugLevelAtLeast 1) {
        
$debugLevel $this->debugLevel();
        if ( 
self::$debugLevel $debugLevelAtLeast )
            return;
        
$datetime date("Y-m-d G:i:s");
        
$fileName basename($file);

        
$text "$datetime: $fileName:$lineNo:$tag";
        if ( 
$msg )
            
$text .= ": $msg";
        
$isHttp = @$_SERVER['SERVER_ADDR'] != null;
        if ( 
$isHttp )
            
$text htmlspecialchars($text)."<br />";
        echo 
"$text\n";
    }
    
/*------------------------------------------------------------*/
    
public function space($tag) {
        
$me get_class()."::".__FUNCTION__."()";
        
$space Perf::space();
        
Mview::msg("$tag: $space space");
        return(
$space);
    }
    
/*------------------------------------------------------------*/
    /*------------------------------------------------------------*/
    /*------------------------------------------------------------*/
    
private function className($className null) {
        if ( 
$className )
            return(
$className);
        if ( isset(
$_POST['className']) && $_POST['className'] != '' )
            return(
$_POST['className']);
        if ( isset(
$_GET['className']) && $_GET['className'] != '' )
            return(
$_GET['className']);
        
$pathParts $this->pathParts();
        return(isset(
$pathParts[0]) ? $pathParts[0] : null);
    }
    
/*------------------------------------------------------------*/
    
private function action($action null) {
        if ( 
$action )
            return(
$action);
        if ( isset(
$_POST['action']) && $_POST['action'] != '' )
            return(
$_POST['action']);
        if ( isset(
$_GET['action']) && $_GET['action'] != '' )
            return(
$_GET['action']);
        
$pathParts $this->pathParts();
        if ( isset(
$pathParts[1]) )
            return(
$pathParts[1]);
        return(
null);
    }
    
/*------------------------------------------------------------*/
}
/*------------------------------------------------------------*/
© Copyright Ohad Aloni. 2012. All Rights Reserved.