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
First Name Last Name
Douglas Adams
Isaac Asimov
Roald Dahl
Charles Dickens
Robert Heinlein
mark56 iUTMUATUqCOKzq
Stanisław Lem
J. R. R. Tolkien
Book Titles
So Long, and Thanks for All the Fish
The Hitchhiker's Guide to the Galaxy
Book Titles
Foundation
Foundation & Empire
I, Robot
Second Foundation
Book Titles
James and the Giant Peach
Tales of the Unexpected
Book Titles
David Copperfield
Nicholas Nickleby
Oliver Twist
Book Titles
Starship Troopers
The Number of the Beast
Book Titles
Book Titles
Return from the Stars
Solaris
Book Titles
The Fellowship of the Ring
The Hobbit
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
Export to Excel ( Row)
id first last
7 Douglas Adams
1 Isaac Asimov
6 Roald Dahl
4 Charles Dickens
2 Robert Heinlein
8 mark56 iUTMUATUqCOKzq
3 Stanisław Lem
5 J. R. R. Tolkien


Books
select * from books order by title
Export to Excel ( Row)
id authorId title
9 4 David Copperfield
2 1 Foundation
1 1 Foundation & Empire
14 1 I, Robot
20 6 James and the Giant Peach
11 4 Nicholas Nickleby
13 4 Oliver Twist
34 3 Return from the Stars
5 1 Second Foundation
31 7 So Long, and Thanks for All the Fish
4 3 Solaris
3 2 Starship Troopers
23 6 Tales of the Unexpected
24 5 The Fellowship of the Ring
28 7 The Hitchhiker's Guide to the Galaxy
25 5 The Hobbit
15 2 The Number of the Beast


Join
select a.*, b.* from authors a, books b where a.id = b.authorId order by a.first, a.last, b.title
Export to Excel ( Row)
id first last authorId title
9 Charles Dickens 4 David Copperfield
11 Charles Dickens 4 Nicholas Nickleby
13 Charles Dickens 4 Oliver Twist
31 Douglas Adams 7 So Long, and Thanks for All the Fish
28 Douglas Adams 7 The Hitchhiker's Guide to the Galaxy
2 Isaac Asimov 1 Foundation
1 Isaac Asimov 1 Foundation & Empire
14 Isaac Asimov 1 I, Robot
5 Isaac Asimov 1 Second Foundation
24 J. R. R. Tolkien 5 The Fellowship of the Ring
25 J. R. R. Tolkien 5 The Hobbit
20 Roald Dahl 6 James and the Giant Peach
23 Roald Dahl 6 Tales of the Unexpected
3 Robert Heinlein 2 Starship Troopers
15 Robert Heinlein 2 The Number of the Beast
34 Stanisław Lem 3 Return from the Stars
4 Stanisław Lem 3 Solaris


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
Export to Excel ( Row)
first last title
Isaac Asimov Foundation
Isaac Asimov Foundation & Empire
Isaac Asimov Second Foundation


The Complete Cartesian Product
select a.*, b.* from authors a, books b order by a.first, a.last, b.title
Export to Excel ( Row)
id first last authorId title
9 Charles Dickens 4 David Copperfield
2 Charles Dickens 1 Foundation
1 Charles Dickens 1 Foundation & Empire
14 Charles Dickens 1 I, Robot
20 Charles Dickens 6 James and the Giant Peach
11 Charles Dickens 4 Nicholas Nickleby
13 Charles Dickens 4 Oliver Twist
34 Charles Dickens 3 Return from the Stars
5 Charles Dickens 1 Second Foundation
31 Charles Dickens 7 So Long, and Thanks for All the Fish
4 Charles Dickens 3 Solaris
3 Charles Dickens 2 Starship Troopers
23 Charles Dickens 6 Tales of the Unexpected
24 Charles Dickens 5 The Fellowship of the Ring
28 Charles Dickens 7 The Hitchhiker's Guide to the Galaxy
25 Charles Dickens 5 The Hobbit
15 Charles Dickens 2 The Number of the Beast
9 Douglas Adams 4 David Copperfield
2 Douglas Adams 1 Foundation
1 Douglas Adams 1 Foundation & Empire
14 Douglas Adams 1 I, Robot
20 Douglas Adams 6 James and the Giant Peach
11 Douglas Adams 4 Nicholas Nickleby
13 Douglas Adams 4 Oliver Twist
34 Douglas Adams 3 Return from the Stars
5 Douglas Adams 1 Second Foundation
31 Douglas Adams 7 So Long, and Thanks for All the Fish
4 Douglas Adams 3 Solaris
3 Douglas Adams 2 Starship Troopers
23 Douglas Adams 6 Tales of the Unexpected
24 Douglas Adams 5 The Fellowship of the Ring
28 Douglas Adams 7 The Hitchhiker's Guide to the Galaxy
25 Douglas Adams 5 The Hobbit
15 Douglas Adams 2 The Number of the Beast
9 Isaac Asimov 4 David Copperfield
2 Isaac Asimov 1 Foundation
1 Isaac Asimov 1 Foundation & Empire
14 Isaac Asimov 1 I, Robot
20 Isaac Asimov 6 James and the Giant Peach
11 Isaac Asimov 4 Nicholas Nickleby
13 Isaac Asimov 4 Oliver Twist
34 Isaac Asimov 3 Return from the Stars
5 Isaac Asimov 1 Second Foundation
31 Isaac Asimov 7 So Long, and Thanks for All the Fish
4 Isaac Asimov 3 Solaris
3 Isaac Asimov 2 Starship Troopers
23 Isaac Asimov 6 Tales of the Unexpected
24 Isaac Asimov 5 The Fellowship of the Ring
28 Isaac Asimov 7 The Hitchhiker's Guide to the Galaxy
25 Isaac Asimov 5 The Hobbit
15 Isaac Asimov 2 The Number of the Beast
9 J. R. R. Tolkien 4 David Copperfield
2 J. R. R. Tolkien 1 Foundation
1 J. R. R. Tolkien 1 Foundation & Empire
14 J. R. R. Tolkien 1 I, Robot
20 J. R. R. Tolkien 6 James and the Giant Peach
11 J. R. R. Tolkien 4 Nicholas Nickleby
13 J. R. R. Tolkien 4 Oliver Twist
34 J. R. R. Tolkien 3 Return from the Stars
5 J. R. R. Tolkien 1 Second Foundation
31 J. R. R. Tolkien 7 So Long, and Thanks for All the Fish
4 J. R. R. Tolkien 3 Solaris
3 J. R. R. Tolkien 2 Starship Troopers
23 J. R. R. Tolkien 6 Tales of the Unexpected
24 J. R. R. Tolkien 5 The Fellowship of the Ring
28 J. R. R. Tolkien 7 The Hitchhiker's Guide to the Galaxy
25 J. R. R. Tolkien 5 The Hobbit
15 J. R. R. Tolkien 2 The Number of the Beast
9 mark56 iUTMUATUqCOKzq 4 David Copperfield
2 mark56 iUTMUATUqCOKzq 1 Foundation
1 mark56 iUTMUATUqCOKzq 1 Foundation & Empire
14 mark56 iUTMUATUqCOKzq 1 I, Robot
20 mark56 iUTMUATUqCOKzq 6 James and the Giant Peach
11 mark56 iUTMUATUqCOKzq 4 Nicholas Nickleby
13 mark56 iUTMUATUqCOKzq 4 Oliver Twist
34 mark56 iUTMUATUqCOKzq 3 Return from the Stars
5 mark56 iUTMUATUqCOKzq 1 Second Foundation
31 mark56 iUTMUATUqCOKzq 7 So Long, and Thanks for All the Fish
4 mark56 iUTMUATUqCOKzq 3 Solaris
3 mark56 iUTMUATUqCOKzq 2 Starship Troopers
23 mark56 iUTMUATUqCOKzq 6 Tales of the Unexpected
24 mark56 iUTMUATUqCOKzq 5 The Fellowship of the Ring
28 mark56 iUTMUATUqCOKzq 7 The Hitchhiker's Guide to the Galaxy
25 mark56 iUTMUATUqCOKzq 5 The Hobbit
15 mark56 iUTMUATUqCOKzq 2 The Number of the Beast
9 Roald Dahl 4 David Copperfield
2 Roald Dahl 1 Foundation
1 Roald Dahl 1 Foundation & Empire
14 Roald Dahl 1 I, Robot
20 Roald Dahl 6 James and the Giant Peach
11 Roald Dahl 4 Nicholas Nickleby
13 Roald Dahl 4 Oliver Twist
34 Roald Dahl 3 Return from the Stars
5 Roald Dahl 1 Second Foundation
31 Roald Dahl 7 So Long, and Thanks for All the Fish
4 Roald Dahl 3 Solaris
3 Roald Dahl 2 Starship Troopers
23 Roald Dahl 6 Tales of the Unexpected
24 Roald Dahl 5 The Fellowship of the Ring
28 Roald Dahl 7 The Hitchhiker's Guide to the Galaxy
25 Roald Dahl 5 The Hobbit
15 Roald Dahl 2 The Number of the Beast
9 Robert Heinlein 4 David Copperfield
2 Robert Heinlein 1 Foundation
1 Robert Heinlein 1 Foundation & Empire
14 Robert Heinlein 1 I, Robot
20 Robert Heinlein 6 James and the Giant Peach
11 Robert Heinlein 4 Nicholas Nickleby
13 Robert Heinlein 4 Oliver Twist
34 Robert Heinlein 3 Return from the Stars
5 Robert Heinlein 1 Second Foundation
31 Robert Heinlein 7 So Long, and Thanks for All the Fish
4 Robert Heinlein 3 Solaris
3 Robert Heinlein 2 Starship Troopers
23 Robert Heinlein 6 Tales of the Unexpected
24 Robert Heinlein 5 The Fellowship of the Ring
28 Robert Heinlein 7 The Hitchhiker's Guide to the Galaxy
25 Robert Heinlein 5 The Hobbit
15 Robert Heinlein 2 The Number of the Beast
9 Stanisław Lem 4 David Copperfield
2 Stanisław Lem 1 Foundation
1 Stanisław Lem 1 Foundation & Empire
14 Stanisław Lem 1 I, Robot
20 Stanisław Lem 6 James and the Giant Peach
11 Stanisław Lem 4 Nicholas Nickleby
13 Stanisław Lem 4 Oliver Twist
34 Stanisław Lem 3 Return from the Stars
5 Stanisław Lem 1 Second Foundation
31 Stanisław Lem 7 So Long, and Thanks for All the Fish
4 Stanisław Lem 3 Solaris
3 Stanisław Lem 2 Starship Troopers
23 Stanisław Lem 6 Tales of the Unexpected
24 Stanisław Lem 5 The Fellowship of the Ring
28 Stanisław Lem 7 The Hitchhiker's Guide to the Galaxy
25 Stanisław Lem 5 The Hobbit
15 Stanisław Lem 2 The Number of the Beast


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_DIR."/mfiles.php");
require_once(
"Mdemo.class.php");
/*------------------------------------------------------------*/
date_default_timezone_set("Asia/Jerusalem");
/*------------------------------------------------------------*/
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;
    
/*------------------------------*/
    
private $Mmemcache 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 === null ) {
            
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 = @mysqli_connect($dbHost$dbUser$dbPasswd);
        if ( ! 
$this->dbHandle ) {
            
$error "Cannot connect to DB on $dbHost";
            
$this->lastError $error;
            
Mview::error($error);
            return(
false);
        }
        
$res $this->query("SET NAMES 'utf8'");
        if ( ! 
$res ) {
            
Mview::error("cannot set names to utf8");
            return(
false);
        }
        if ( 
$dbName )  {
            if ( ! 
$this->selectDb($dbName) ) {
                
$error = @mysqli_error($this->dbHandle) ;
                
$this->lastError $error;
                
Mview::error("Unable to select db $dbName$error");
            }
        }
        return(
true);
    }
    
/*------------------------------------------------------------*/
    /**
      * use database
      */
    
public function selectDB($db) {
        
$this->dbName $db;
        
$ret = @mysqli_select_db($this->dbHandle$this->dbName);
        if ( 
$ret == false ) {
            
$error = @mysqli_error($this->dbHandle) ;
            
$this->lastError $error;
            return(
false);
        }
        return(
true);
    }
    
/*------------------------------*/
    /**
      * use database
      */
    
public function useDB($db) {
        return(
$this->selectDB($db));
    }
    
/*------------------------------------------------------------*/
    
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);
        
$ret $str;
        
$ret str_replace("\\""\\\\"$ret);
        
// if they are already escaped
        
$ret str_replace("\\'""'"$ret);
        
$ret str_replace("'""\\'"$ret);
        
$ret str_replace("\r\n""\n"$ret);
        
$ret str_replace("\n""\\n"$ret);
        return(
$ret);
    }
    
/*------------------------------------------------------------*/
    
public function query($sql) {
        
$res null;
        try {
            
$res = @mysqli_query($this->dbHandle$sql);
        } catch (
Exception $e) {
            
$msg $e->getMessage();
            
Mview::error($msg);
            
$error = @mysqli_error($this->dbHandle);
            
$this->lastError $error;
            if ( 
$error )
                
Mview::error("sql error: $error");
            
Mview::error($sql);
            return(
null);
        }
        if ( ! 
$res ) {
            
$error = @mysqli_error($this->dbHandle);
            
$this->lastError $error;
            if ( 
$error )
                
Mview::error("sql error: $error");
            
Mview::error(substr($sql0500));
            if ( 
stristr($error"MySQL server has gone away") ) {
                
Mview::error("EXITTING");
                exit; 
// servers will auto restart
            
}
            return(
null);
        }
        return(
$res);
    }
    
/*------------------------------------------------------------*/
    
public function _sql($sql$rememberLastSql true) {
        
$res $this->query($sql);
        if ( ! 
$res ) {
            return(
null);
        }
        if ( 
$rememberLastSql )
            
$this->lastSql $sql;
        
$affected = @mysqli_affected_rows($this->dbHandle);
        @
mysqli_free_result($res);
        return(
$affected);
    }
    
/*------------------------------*/
    
public function sql($sql) {
        if ( ! 
$this->isConnected ) {
            return(
null);
        }
        
$ret $this->_sql($sql);
        if ( 
strstr($sql'insert') )
            
$this->lastInsertId = @mysqli_insert_id($this->dbHandle);
        if ( 
$ret )
            
$this->dbLog('''sql'0);

        return(
$ret);
    }
    
/*----------------------------------------*/
    
public function getRows($sql$ttl null) {
        if ( 
$ttl !== null && ! $this->Mmemcache )
            
$this->Mmemcache = new Mmemcache;
        
$memcacheKey $this->memcacheKey($sql);
        if ( 
$ttl !== null && ($rows $this->Mmemcache->get($memcacheKey)) !== false ) {
            return(
$rows);
        }
        if ( ! 
$this->isConnected ) {
            return(
null);
        }
        
$res $this->query($sql);
        if ( ! 
$res ) {
            return(
null);
        }
        
$ret = array();
        while(
$r = @mysqli_fetch_assoc($res))
            
$ret[] = $r ;
        @
mysqli_free_result($res);
        if ( 
$ttl !== null ) {
            
$set $this->Mmemcache->set($memcacheKey$ret$ttl);
            
$debugLevel Mutils::getenv("debugLevel");
            if ( ! 
$set && $debugLevel )
                echo 
"Failed to Mmemcache->set($memcacheKey, ..., $ttl)<br />\n";
            if ( 
$debugLevel ) {
                static 
$visited false;
                if ( ! 
$visited ) {
                    
$visited true;
                    
$get $this->Mmemcache->get($memcacheKey);
                    if ( 
$get === false ) {
                        echo 
"Failed to get after Mmemcache->set($memcacheKey, ..., $ttl)<br />\n";
                        
$this->memcacheTestData($ret$memcacheKey$ttl);
                    }
                }
            }
        }
        return(
$ret);
    }
    
/*----------*/
    
private function memcacheKey($sql) {
        
$codeVersion 9;
        
$dbHost $this->dbHost;
        
$dbName $this->dbName;
        
$memcacheKey "$codeVersion-$dbHost-$dbName-$sql";
        return(
$memcacheKey);
    }
    
/*--------------------*/
    
private function memcacheTestData($data$key$ttl) {
        
$set $this->Mmemcache->set($key$data$ttl);
        
$get $this->Mmemcache->get($key);

        
$memcacheTestResults = array(
            
'key' => $key,
            
'data' => $data,
            
'ttl' => $ttl,
            
'set' => $set,
            
'get' => $get,
        );
        
Mview::print_r($memcacheTestResults"memcacheTestResults"basename(__FILE__), __LINE__);
        return(
$set);
    }
    
/*------------------------------------------------------------*/
    
public function getRow($sql$ttl null) {
        if ( ! 
$this->isConnected ) {
            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$ttl null$idName "id") {
        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 ) {
            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 ) {
            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) {
        if ( ! 
$this->Mmemcache )
            
$this->Mmemcache = new Mmemcache;
        static 
$cache = array();

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

        
$memcacheKey $this->memcacheKey("autoIncrement-$tableName");
        if ( (
$cache[$tableName] = $this->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] )
            
$this->Mmemcache->set($memcacheKey$cache[$tableName], 600);
        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"30);
        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"30);
        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"5*60);
        return(
$cache[$db]);
    }
    
/*----------------------------------------*/
    /**
     * list of databases
     */
    
public function databases() {
        static 
$cache null;
        static 
$excludes = array('performance_schema''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 ) {
            return(
null);
        }

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

        
$affected $this->_sql($sql$rememberLastSql);
        if (  
$affected != )
            return(
null);
        
$this->lastInsertId mysqli_insert_id($this->dbHandle);
        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);
    }
    
/*------------------------------------------------------------*/
    
public function bulkInsert($tableName$rows) {
        
$columns $this->columns($tableName);
        unset(
$columns[0]); // avoid the id field
        
$names implode(", "$columns);
        
$valuesLists = array();
        foreach ( 
$rows as $row ) {
            
$values = array();
            foreach ( 
$columns as $column ) {
                if ( isset(
$row[$column]) ) {
                    
$value $row[$column];
                    
$dbStr $this->str($value);
                    
$valueStr "'$dbStr'";
                } else {
                    
$valueStr "null";
                }
                
$values[] = $valueStr;
            }
            
$valuesString implode(", "$values);
            
$valuesLists[] = $valuesString;
        }
        
$bulkValues "( ".implode(" ), ( "$valuesLists)." )";
        
$sql "insert $tableName ( $names ) values $bulkValues";
        
$affected $this->sql($sql);
        return(
$affected);
    }
    
/*------------------------------------------------------------*/
    /**
      * 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
      */
    
public function _dbUpdate($tableName$id$data$idName "id") {
        if ( ! 
$this->isConnected ) {
            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 != null && ($value Mdate::scan($value)) == null )
                    continue;
            if ( 
$dataType == 'datetime' && $value != null && ($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 ) {
            
/*    $json = json_encode($data);    */
            /*    Mview::msg("$tableName: nothing changed: $json");    */
            // 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);
        if ( 
$affected )
            
$this->dbLog($tableName'update'$id);
        return(
$affected);
    }
    
/*----------------------------------------*/
    /**
      * 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 ) {
            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 = @mysqli_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
        
if ( ! Mlogin::get('MloginName') ) {
            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;
        
$excludeTables = array(
            
'authLog',
            
'errorLog',
            
'online',
            
'tlog',
            
'timeWatch',
            
'usageStats',
            
'loads',
        );
        if ( 
in_array($tname$excludeTables) )
            return;
        
$querySql $querySql $querySql $this->lastSql;
        
$querySql str_replace("\\'""'"$querySql); // remove added escapes that will be added again by _dbInsert
        
foreach ( $excludeTables as $excludeTable ) {
            if ( 
strstr($querySql$excludeTable ") )
                return;
        }
        
$row = array(
                
'tname' => $tname,
                
'op' => $op,
                
'tid' => $tid,
                
'querySql' => $querySql,
                
'loginName' => Mlogin::get('MloginName'),
                
'stamp' => date("Y-m-d H:i:s"),
            );
        
$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) {
        
$loginName Mlogin::get('MloginName');
        if ( 
$insert && in_array('createdOn'$cols) )
            
$data['createdOn'] = date("Y-m-d G:i:s");
        if ( 
$loginName && $insert && in_array('createdBy'$cols) )
            
$data['createdBy'] = $loginName;
        if ( 
in_array('lastChange'$cols) )
            
$data['lastChange'] = date("Y-m-d G:i:s");
        if ( 
$loginName && in_array('lastChangeBy'$cols) )
            
$data['lastChangeBy'] = $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();
        
$values = 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 == )
            && 
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('msuImplode');
        
$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;
        }
        
$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) {
        if ( ! 
in_array($dir$this->templateDirPath) )
            
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) {
        if ( ! 
in_array($dir$this->templateDirPath) )
            
$this->templateDirPath[] = $dir;
    }
    
/*------------------------------------------------------------*/
    
public function templateDirPath() {
        
$templateDirPath implode(":"$this->templateDirPath);
        return(
$templateDirPath);
    }
    
/*------------------------------------------------------------*/
    
private function _render($tpl$errorLogger null) {
        if ( ! 
is_writable($this->compile_dir) ) {
            
$pwd trim(`pwd`);
            
$error "Smarty Compile Dir $pwd/{$this->compile_dir} not writable";
            
$this->error($error);
            if ( 
$errorLogger )
                
$errorLogger->log($error);
            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));
        }
        
$templateDirPath $this->templateDirPath();
        
$error "Mview: $tpl not found in $templateDirPath";
        
$this->error($error);
        if ( 
$errorLogger )
            
$errorLogger->log($error);
        return(
null);
    }
    
/*------------------------------------------------------------*/
    
public function renderText($text$args = array(), $errorLogger null) {
        
$args['evalText'] = $text;
        
$rendered $this->render("eval.tpl"$args$errorLogger);
        return(
$rendered);
    }
    
/*------------------------------------------------------------*/
    /**
     * return a rendered template
     */
    
public function render($tpl$args null$errorLogger null) {
        if ( 
is_array($args) ) {
            
$this->assign($args);
            
$this->assign(array('tplArgs' => $args));
        }
        
$rendered $this->_render($tpl$errorLogger);
        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()
    
}
    
/*------------------------------*/
    
public 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$url null) {
        
$me get_class()."::".__FUNCTION__."()";
        
$isHtml = isset($_SERVER['REMOTE_ADDR']);
        if ( 
$isHtml ) {
            
$type $iserror "alert-danger" "alert-info" ;
            
$msg htmlspecialchars($msg);
            
            
$tokens explode("\n"$msg);
            
            
$before "";
            for(
$i=0;$i<count($tokens);$i++){
                if(
trim($tokens[$i])!= "")
                    break;
                if(
trim($tokens[$i])== "")
                    
$before.="<br/>";
            }
            
$tokens array_reverse($tokens);
            
$after "";
            for(
$i=0;$i<count($tokens);$i++){
                if(
trim($tokens[$i])!= "")
                    break;
                if(
trim($tokens[$i]) == "")
                    
$after.="<br/>";
            }            
            
$msg trim($msg,"\n");
            
$msg nl2br($msg);
            if (
$url){
                
$msg "<a target=\"_blank\" href=\"$url\">$msg</a>";
            }
            
$text "<div class='alert $type'><strong>$msg</strong></div>" ;
            
$text $before.$text.$after;
        }
        else {
            
$pfx $iserror "ERROR: " "" ;
            
$text =  "$pfx$msg\n" ;
        }
        if ( 
$isHtml ) {
            
$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$url null) {
        if ( 
self::$isHold )
            
self::$msgBuf[] = array('msg' => $msg'iserror' => $iserror'url' => $url, );
        else
            
self::message($msg$iserror$url);
    }
    
/*------------------------------*/
    /**
     * show an error (or hold it - see holdMsgs())
     *
     * @param string
     */
    
public static function error($msg) {
        
error_log($msg);
        
self::msg($msgtrue);
    }
    
/*------------------------------*/
    
public static function urlMsg($msg$url) {
        
self::msg($msgfalse$url);
    }
    
/*------------------------------------------------------------*/
    /**
     * 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$logError false) {
        
$print_r print_r($vartrue);
        if ( 
$file ) {
            
$fileParts explode("/"trim($file"/"));
            
$fileName $fileParts[count($fileParts)-1];
        }
        if ( 
$logError ) {
            
$str str_replace("\n"" --> "$print_r);
            
$str "$fileName:$line$varName: [[[ $print_r ]]]";
            
error_log($str);
            return;
        }
        
$isHtml = isset($_SERVER['REMOTE_ADDR']);
        
$ret "";
        if ( 
$isHtml )
            
$ret .= "\n<table border=\"0\"><tr><td align=\"left\"><pre>\n";
        if ( 
$file ) {
            
$ret .= "$varName ($fileName$line)\n--------------------------------------------------------------\n";
        }
        
$ret .= $print_r;
        
$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) - 3"/");
            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;
    private 
$controllerError// to be requested in case false is returned.
    /**
    * @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);
    }
    
/*------------------------------------------------------------*/
    
public function _control($silent false) {
        return(
$this->control(nullnullnull$silent));
    }
    
/*------------------------------------------------------------*/
    
public function lastControllerError() {
        return(
$this->controllerError);
    }
    
/*------------------------------------------------------------*/
    
private function controllerError($msg$silent false) {
        
$this->controllerError $msg;
        if ( 
$silent )
            
error_log($msg);
        else
            
$this->Mview->error($msg);
        
    }
    
/*------------------------------------------------------------*/
    
public function control($className null$action null$args null$silent false) {            
        
$requestArgs = array();
        if ( 
is_string($args) ) {
            
$vars explode('&'$args);
            foreach ( 
$vars as $var ) {
                
$nv explode('='$var);
                if ( 
count($nv) != ) {
                    
$this->controllerError("$var ???"$silent);
                    return(
false);
                }
                list(
$n$v) = $nv;
                
$requestArgs[$n] = $v;
            }
        } else if ( 
$args ) {
            foreach ( 
$args as $key => $arg )
                
$requestArgs[$key] = $arg;
        }                
        
        
$obj $this->obj($className$silent);
        if ( ! 
$obj ) {
            return(
false);
        }
        
        
$action $this->action($action);
        if ( 
$action == null )
            
$action "index";
        
        
        if ( ! 
is_callable(array($obj$action)) ) {            
            
$className get_class($obj);            
            
$this->controllerError("Method '$action' not callable in class '$className'");
            return(
false);
        }
        
$className get_class($obj);

        
$this->controller strtolower($className);
        
$this->action strtolower($action);
        
$obj->controller strtolower($className);
        
$obj->action strtolower($action);
        
Mutils::setenv("controller"$this->controller);
        
Mutils::setenv("action"$this->action);
        
$savedRequestArgs $this->setRequestArgs($requestArgs);
        if ( ! 
$obj->permit($className$action) )
            return(
false);
        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);
        return(
true);
    }
    
/*------------------------------*/
    
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$silent false) {
        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->controllerError("class $baseName not found in $file"$silent);
            return(
false);
        }
        
$this->controllerError("cannot find class for '$className'"$silent);
        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
     * 
     * @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(); }
    
/*------------------------------------------------------------*/
    /**
     * 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);
    }
    
/*------------------------------------------------------------*/
    
public function exportToExcel($rows$fileName null) {
        if ( 
count($rows) == ) {
            
$this->Mview->msg("No Rows");
            return;
        }
            
        
$headings array_keys($rows[0]);
        
$content $this->Mview->render("excel.tpl", array(
            
'headings' => $headings,
            
'rows' => $rows,
        ));
        if ( ! 
$fileName && isset($_REQUEST['fileName']) )
            
$fileName $_REQUEST['fileName'];
        if ( ! 
$fileName )
            
$fileName "M2excel-".date("Ymd");
        
$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 = @mysqli_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,
            ));
    }
    
/*------------------------------------------------------------*/
    /*------------------------------------------------------------*/
    /*------------------------------------------------------------*/
    
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();
        
$pr print_r($pathPartstrue);
        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. 2018. All Rights Reserved.