├── src ├── template │ ├── lib │ │ ├── .htaccess │ │ ├── Util │ │ │ ├── Database.php │ │ │ └── Session.php │ │ ├── Controller │ │ │ └── Session_Controller.php │ │ └── Core.php │ ├── site │ │ ├── .htaccess │ │ └── config.php │ ├── public │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ └── glyphicons-halflings-regular.woff │ │ ├── js │ │ │ ├── npm.js │ │ │ ├── underscore-min.js │ │ │ ├── backbone-min.js │ │ │ └── bootstrap.min.js │ │ └── css │ │ │ ├── bootstrap-theme.min.css │ │ │ ├── bootstrap-theme.css │ │ │ └── bootstrap-theme.css.map │ ├── doc │ │ └── Makefile │ ├── .htaccess │ └── api.php ├── SQL_Unique.php ├── SQL_Database.php ├── SQL_Index.php ├── SQL_Colspec.php ├── Model_Relationship.php ├── SQL_Input.php ├── SQL_Statement.php ├── SQL_Constraint.php ├── Model_Index.php ├── SQL_Table.php ├── Model_Entity.php └── SQL_Token.php ├── .gitignore ├── generate.php └── README.md /src/template/lib/.htaccess: -------------------------------------------------------------------------------- 1 | Deny from all 2 | -------------------------------------------------------------------------------- /src/template/site/.htaccess: -------------------------------------------------------------------------------- 1 | Deny from all 2 | -------------------------------------------------------------------------------- /src/template/public/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mike42/php-mysql-model-generator/master/src/template/public/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/template/public/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mike42/php-mysql-model-generator/master/src/template/public/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/template/public/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mike42/php-mysql-model-generator/master/src/template/public/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse files 2 | .buildpath 3 | .project 4 | .settings/* 5 | 6 | # Site-specific files 7 | examples/* 8 | 9 | # temp and hidden files 10 | *~ 11 | 12 | # Eclipse files 13 | .settings/* 14 | .project 15 | -------------------------------------------------------------------------------- /src/template/site/config.php: -------------------------------------------------------------------------------- 1 | $${i%.*}.pdf; done 6 | 7 | clean: 8 | rm --preserve-root -Rf html latex doxygen_sqlite3.db diagram/*.pdf 9 | 10 | -------------------------------------------------------------------------------- /src/template/.htaccess: -------------------------------------------------------------------------------- 1 | # Rewrite rules for auth 2 | RewriteEngine On 3 | RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f 4 | RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-d 5 | 6 | # Handle stylesheets and scripts 7 | RewriteRule ^api/(.*)$ api.php?p=$1 [PT,L,QSA] 8 | -------------------------------------------------------------------------------- /src/SQL_Unique.php: -------------------------------------------------------------------------------- 1 | name = $idx -> name; 10 | $this -> fields = $idx -> fields; 11 | } 12 | } -------------------------------------------------------------------------------- /generate.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | name = $argv[1]; 11 | 12 | $mg = new Model_Generator($import); 13 | $mg -> generate(); 14 | -------------------------------------------------------------------------------- /src/template/public/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /src/template/lib/Util/Database.php: -------------------------------------------------------------------------------- 1 | exec("SET NAMES 'utf8';"); 14 | self::$dbh -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 15 | } 16 | } 17 | ?> 18 | -------------------------------------------------------------------------------- /src/SQL_Database.php: -------------------------------------------------------------------------------- 1 | statement as $statement) { 19 | if($statement -> type == SQL_Statement::CREATE_TABLE) { 20 | $this -> addTable($statement); 21 | } 22 | } 23 | } 24 | 25 | private function addTable(SQL_Statement $statement) { 26 | $table = new SQL_Table($statement); 27 | $this -> table[$table -> name] = $table; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/template/lib/Controller/Session_Controller.php: -------------------------------------------------------------------------------- 1 | 'No login details provided', 'code' => '403'); 10 | } 11 | 12 | /* Get username & password */ 13 | $username = $_POST['username']; 14 | $password = $_POST['password']; 15 | $ok = Session::authenticate($username, $password); 16 | if($ok) { 17 | return array('success' => 'true', 'username' => $username, 'role' => Session::getRole()); 18 | } 19 | return array('error' => 'Login failed', 'code' => '403'); 20 | } 21 | 22 | public static function logout() { 23 | Session::logout(); 24 | return array('success' => 'true'); // Log-out will always succeed 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/SQL_Index.php: -------------------------------------------------------------------------------- 1 | type == SQL_Token::IDENTIFIER && count($token -> sub) != 0) { 15 | /* Stop on an identifier with brackets */ 16 | $this -> addIndex($token); 17 | return; 18 | } 19 | } 20 | throw new Exception("Couldn't parse index"); 21 | } 22 | 23 | private function addIndex(SQL_Token $token) { 24 | $this -> name = SQL_Token::get_identifier($token -> str); 25 | $this -> fields = array(); 26 | foreach($token -> sub as $sub) { 27 | if($sub -> type == SQL_Token::IDENTIFIER) { 28 | $this -> fields[] = SQL_Token::get_identifier($sub -> str); 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/template/lib/Util/Session.php: -------------------------------------------------------------------------------- 1 | nullable = true; 12 | 13 | $this -> name = SQL_Token::get_identifier($tokens[0] -> str); 14 | $this -> type = strtoupper($tokens[1] -> str); 15 | $size = array(); 16 | if(count($tokens[1] -> sub) != 0) { 17 | foreach($tokens[1] -> sub as $subtoken) { 18 | if($this -> type == "ENUM" && $subtoken -> type == SQL_Token::STRING_LITERAL) { 19 | $this -> values[] = SQL_Token::get_string_literal($subtoken -> str); 20 | } else { 21 | if($subtoken -> type == SQL_Token::NUMBER_LITERAL) { 22 | $this -> size[] = SQL_Token::get_number_literal($subtoken -> str); 23 | } else if($subtoken -> type == SQL_Token::STRING_LITERAL) { 24 | $this -> size[] = SQL_Token::get_string_literal($subtoken -> str); 25 | } 26 | } 27 | } 28 | } 29 | 30 | /* Find comment */ 31 | $this -> comment = ""; 32 | $commentNext = false; 33 | $not = false; 34 | foreach($tokens as $token) { 35 | if($commentNext) { 36 | if($token -> type == SQL_Token::STRING_LITERAL) { 37 | $this -> comment = trim(SQL_Token::get_string_literal($token -> str)); 38 | break; 39 | } else { 40 | break; // Not a comment. Give up. 41 | } 42 | } else if($token -> type == SQL_Token::IDENTIFIER && strtoupper($token -> str) == "COMMENT") { 43 | $commentNext = true; 44 | } else if($not && $token -> type == SQL_Token::KEYWORD && strtoupper($token -> str) == "NULL") { 45 | $this -> nullable = false; 46 | } 47 | 48 | /* Track the NOT keyword so we can spot NOT NULL */ 49 | if($token -> type == SQL_Token::KEYWORD && strtoupper($token -> str) == "NOT") { 50 | $not = true; 51 | } else { 52 | $not = false; 53 | } 54 | } 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/template/api.php: -------------------------------------------------------------------------------- 1 | "read", 9 | "POST" => "create", 10 | "PUT" => "update", 11 | "PATCH" => "update", 12 | "DELETE" => "delete", 13 | ); 14 | if(array_key_exists($request_method, $request_method_defaults)) { 15 | $default_action = $request_method_defaults[$request_method]; 16 | } else { 17 | Core::fizzle("Unsupported request type", "400"); 18 | } 19 | 20 | /* Get page (or go to default if none is specified) */ 21 | $help = "Make requests with api.php?p={controller}/{id}, or api.php?p={Controller}/{action}/{id}"; 22 | if(isset($_GET['p']) && $_GET['p'] != '') { 23 | $arg = explode('/', $_REQUEST['p']); 24 | } else { 25 | Core::fizzle("Not enough information: $help", "400"); 26 | } 27 | 28 | /* Map arguments to a controller and method name */ 29 | $controller = array_shift($arg); 30 | if(count($arg) > 1) { 31 | $action = array_shift($arg); 32 | } elseif(count($arg) <= 1) { 33 | $action = $default_action; 34 | } 35 | 36 | /* Sanity check (leading & trailing '/' will trigger these) */ 37 | if(trim($controller) == "") { 38 | Core::fizzle("Controller not specified: $help", "400"); 39 | } 40 | if(trim($action) == "") { 41 | Core::fizzle("Action not specified: $help", "400"); 42 | } 43 | 44 | /* Figure out class and method name */ 45 | try { 46 | $controllerClassName = $controller.'_Controller'; 47 | core::loadClass($controllerClassName); 48 | if(!is_callable($controllerClassName . "::" . $action)) { 49 | Core::fizzle("Controller '$controllerClassName' does not support a '$action' action.", '404'); 50 | } 51 | $ret = call_user_func_array(array($controllerClassName, $action), $arg); 52 | if(isset($ret['error'])) { 53 | /* Something went wrong, we got back an 'error' property. */ 54 | Core::fizzle($ret['error'], isset($ret['code']) ? $ret['code'] : '500'); 55 | } 56 | echo json_encode($ret); 57 | } catch(Exception $e) { 58 | /* Something went wrong, an exception was thrown */ 59 | Core::fizzle($e -> getMessage(), '500'); 60 | } 61 | -------------------------------------------------------------------------------- /src/Model_Relationship.php: -------------------------------------------------------------------------------- 1 | table[$foreignKey -> parent_table]; 13 | $this -> constraint = $foreignKey; 14 | $this -> dest = new Model_Entity($tbl, $database, $children); 15 | 16 | $this -> shortName = self::filterName($this -> constraint -> name, $children[count($children) - 1]); 17 | 18 | $this -> toOne = true; 19 | if($foreignKey -> reversed) { 20 | $this -> nullable = true; 21 | $this -> toOne = false; 22 | 23 | /* Check if the foreign key comprises a single, unique field */ 24 | if(count($foreignKey -> parent_fields) == 1) { 25 | $field = $foreignKey -> parent_fields[0]; 26 | $t = $database -> table[$foreignKey -> parent_table]; 27 | 28 | /* Count primary key and UNIQUE indexes only */ 29 | $unique_keys = array($t -> pk); 30 | foreach($t -> unique as $u) { 31 | $unique_keys[] = $u -> fields; 32 | } 33 | 34 | foreach($unique_keys as $u) { 35 | if(count($u) == 1 && $u[0] == $field) { 36 | $this -> toOne = true; 37 | } 38 | } 39 | } 40 | } else { 41 | /* Check if any of the fields in the foreign key are nullable */ 42 | $this -> nullable = false; 43 | foreach($foreignKey -> child_fields as $field_name) { 44 | $this -> nullable |= $database -> table[$children[count($children) - 1]] -> cols[$field_name] -> nullable; 45 | } 46 | } 47 | } 48 | 49 | public static function filterName($name, $child) { 50 | /* Re-name relationship */ 51 | $pref = array("fk_", $child . "_"); 52 | $suf = array("_idx", "_UNIQUE"); 53 | 54 | foreach($pref as $p) { 55 | if(substr($name, 0, strlen($p)) == $p) { 56 | $name = substr($name, strlen($p), strlen($name) - strlen($p)); 57 | } 58 | } 59 | 60 | foreach($suf as $s) { 61 | if(substr($name, strlen($name) - strlen($s), strlen($s)) == $s) { 62 | $name = substr($name, 0, strlen($name) - strlen($s)); 63 | } 64 | } 65 | return $name; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/SQL_Input.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class SQL_Input { 11 | public $sql; 12 | public $statement = array(); 13 | 14 | function __construct($sql) { 15 | /* Split input into characters to extract tokens */ 16 | $curtoken = new SQL_Token(); 17 | $alltokens = array(); 18 | for($i = 0; $i < strlen($sql); $i++) { 19 | $c = substr($sql, $i, 1); 20 | if(!$curtoken -> add($c)) { 21 | if($curtoken -> type != SQL_Token::WHITESPACE && $curtoken -> type != SQL_Token::LINE_COMMENT) { // Skip over whitespace 22 | $alltokens[] = $curtoken; 23 | } 24 | $curtoken = new SQL_Token(); 25 | $i--; 26 | } 27 | } 28 | if($curtoken -> type != SQL_Token::WHITESPACE && $curtoken -> type != SQL_Token::LINE_COMMENT) { // Skip over whitespace 29 | // Add final token 30 | $alltokens[] = $curtoken; 31 | } 32 | 33 | /* Structure according to bracketing */ 34 | do { 35 | $lastcount = count($alltokens); 36 | $parent = -1; 37 | $lastidx = -1; 38 | $children = array(); 39 | $depth = 0; 40 | $repeat = false; 41 | 42 | foreach($alltokens as $idx => $token) { 43 | if($token -> type == SQL_Token::OPENBRACKET) { 44 | if($parent != -1) { 45 | $repeat = true; 46 | } 47 | $parent = $lastidx; 48 | $children = array($idx => $token); 49 | $depth++; 50 | } else if($token -> type == SQL_Token::CLOSEBRACKET) { 51 | $depth--; 52 | if($depth < 0) { 53 | throw new Exception("Too many close brackets"); 54 | } 55 | $children[$idx] = $token; 56 | $alltokens[$parent] -> has_sub = true; 57 | 58 | foreach($children as $cid => $c) { 59 | if($c -> type != SQL_Token::OPENBRACKET && $c -> type != SQL_Token::CLOSEBRACKET) { 60 | $alltokens[$parent] -> sub[] = $c; 61 | } 62 | unset($alltokens[$cid]); 63 | unset($children[$cid]); 64 | } 65 | break; 66 | } else { 67 | $lastidx = $idx; 68 | $children[$idx] = $token; 69 | } 70 | } 71 | } while(count($alltokens) != count($children)); 72 | 73 | /* Extract list of statements */ 74 | $nextStatement = array(); 75 | foreach($alltokens as $id => $token) { 76 | $nextStatement[] = $token; 77 | unset($alltokens[$id]); 78 | if($token -> type == SQL_Token::SEMICOLON) { 79 | $this -> statement[] = new SQL_Statement($nextStatement); 80 | $nextStatement = array(); 81 | } 82 | } 83 | if(count($nextStatement) != 0) { 84 | throw new Exception("Statement was not ended with a semicolon"); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /src/SQL_Statement.php: -------------------------------------------------------------------------------- 1 | array( 35 | "database" => self::ALTER_DATABASE, 36 | "function" => self::ALTER_FUNCTION, 37 | "procedure"=> self::ALTER_PROCEDURE, 38 | "table" => self::ALTER_TABLE, 39 | "view" => self::CREATE_VIEW 40 | ), 41 | "create" => array( 42 | "database" => self::CREATE_DATABASE, 43 | "function" => self::CREATE_FUNCTION, 44 | "index" => self::CREATE_INDEX, 45 | "procedure" => self::CREATE_PROCEDURE, 46 | "table" => self::CREATE_TABLE, 47 | "trigger" => self::CREATE_TRIGGER, 48 | "view" => self::CREATE_VIEW 49 | ), 50 | "drop" => array( 51 | "database" => self::DROP_DATABASE, 52 | "function" => self::DROP_FUNCTION, 53 | "index" => self::DROP_INDEX, 54 | "procedure" => self::DROP_PROCEDURE, 55 | "table" => self::DROP_TABLE, 56 | "trigger" => self::DROP_TRIGGER, 57 | "view" => self::DROP_VIEW 58 | ), 59 | "rename" => array( 60 | "table" => self::RENAME_TABLE 61 | ), 62 | "truncate" => array( 63 | "table" => self::TRUNCATE_TABLE 64 | ), 65 | "set" => self::SET 66 | ); 67 | 68 | function __construct($tokens) { 69 | $this -> token = $tokens; 70 | 71 | /* Identify the statement */ 72 | $candidates = array(self::UNKNOWN); 73 | $prev = self::$map; 74 | foreach($tokens as $token) { 75 | if($token -> type != SQL_Token::KEYWORD || !is_array($prev)) { 76 | break; 77 | } 78 | if(!isset($prev[strtolower($token -> str)])) { 79 | break; 80 | } 81 | $prev = $prev[strtolower($token -> str)]; 82 | $candidates[] = $prev; 83 | } 84 | 85 | /* Correction for half-complete statements, eg "CREATE `foo`" */ 86 | do { 87 | $type = array_pop($candidates); 88 | } while(is_array($type)); 89 | $this -> type = $type; 90 | } 91 | } -------------------------------------------------------------------------------- /src/SQL_Constraint.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | class SQL_Constraint { 8 | public $name; 9 | public $child_fields; 10 | public $parent_table; 11 | public $parent_fields; 12 | public $child_table; // Used when generating models 13 | public $reversed; 14 | 15 | /** 16 | * Roughly parse constraints 17 | * 18 | * @param SQL_Token $tokens 19 | * @throws Exception 20 | */ 21 | public function __construct($tokens) { 22 | $this -> child_fields = array(); 23 | $this -> parent_fields = array(); 24 | $this -> reversed = false; 25 | 26 | $stage = 0; 27 | foreach($tokens as $id => $token) { 28 | switch($stage) { 29 | case 0: 30 | if($token -> type == SQL_Token::KEYWORD && strtoupper($token -> str) == "FOREIGN") { 31 | $stage++; 32 | } else if($token -> type == SQL_Token::IDENTIFIER) { 33 | $this -> name = SQL_Token::get_identifier($token -> str); 34 | } 35 | break; 36 | 37 | case 1: 38 | if($token -> type == SQL_Token::KEYWORD && strtoupper($token -> str) == "KEY" && count($token -> sub) != 0) { 39 | $this -> addChildFields($token -> sub); 40 | $stage++; 41 | } else { 42 | break 2; // FOREIGN must be followed by KEY 43 | } 44 | break; 45 | case 2: 46 | if($token -> type == SQL_Token::KEYWORD && strtoupper($token -> str) == "REFERENCES") { 47 | $stage++; 48 | } 49 | break; 50 | case 3: 51 | if($token -> type == SQL_Token::IDENTIFIER && count($token -> sub) != 0) { 52 | $this -> parent_table = SQL_Token::get_identifier($token -> str); 53 | $this -> addParentFields($token -> sub); 54 | return; 55 | } 56 | break; 57 | } 58 | } 59 | 60 | throw new Exception("Unable to parse constraint"); 61 | } 62 | 63 | /** 64 | * Populate child fields from bracketed list (`foo`, `bar`) 65 | * 66 | * @param SQL_Token[] $tokens 67 | */ 68 | private function addChildFields($tokens) { 69 | foreach($tokens as $token) { 70 | if($token -> type == SQL_Token::IDENTIFIER) { 71 | $this -> child_fields[] = SQL_Token::get_identifier($token -> str); 72 | } 73 | } 74 | } 75 | 76 | /** 77 | * Populate parent fields from bracketed list (`baz`, `quux`) 78 | * 79 | * @param SQL_Token[] $tokens 80 | */ 81 | private function addParentFields($tokens) { 82 | foreach($tokens as $token) { 83 | if($token -> type == SQL_Token::IDENTIFIER) { 84 | $this -> parent_fields[] = SQL_Token::get_identifier($token -> str); 85 | } 86 | } 87 | } 88 | 89 | /** 90 | * Reverse direction of the relationship 91 | */ 92 | public function reverse() { 93 | // Swap tables 94 | $tmp = $this -> parent_table; 95 | $this -> parent_table = $this -> child_table; 96 | $this -> child_table = $tmp; 97 | 98 | // Swap fields 99 | $tmp = $this -> parent_fields; 100 | $this -> parent_fields = $this -> child_fields; 101 | $this -> child_fields = $tmp; 102 | 103 | $this -> reversed = !$this -> reversed; 104 | } 105 | } -------------------------------------------------------------------------------- /src/Model_Index.php: -------------------------------------------------------------------------------- 1 | name = $name; 10 | $this -> fields = $fields; 11 | $this -> isUnique = $isUnique; 12 | } 13 | 14 | public static function addIndex(array $indices, Model_Index $new) { 15 | // TODO guarantee that names are unique (low-priority). 16 | foreach($indices as $key => $index) { 17 | if(self::field_match($new -> fields, $index -> fields)) { 18 | if($new -> isUnique) { 19 | $indices[$key] -> isUnique = true; 20 | } 21 | return $indices; // Already on the list 22 | } 23 | } 24 | $indices[] = $new; 25 | return $indices; 26 | } 27 | 28 | /** 29 | * Look up a field list in a list of indices, and return the one that matches. 30 | * 31 | * @param array $indices 32 | * @param array $fields 33 | * @throws Exception 34 | * @return unknown 35 | */ 36 | public static function retrieveFieldIndex(array $indices, array $fields) { 37 | foreach($indices as $key => $index) { 38 | if(self::field_match($fields, $index -> fields)) { 39 | return $index; 40 | } 41 | } 42 | throw new Exception("Index not found with fields specified: " . implode(", ", $fields)); 43 | } 44 | 45 | /** 46 | * Look up relationship in a list of indices, and return the one that matches. 47 | * 48 | * @param array $indices 49 | * @param Model_Relationship $rel 50 | * @throws Exception 51 | * @return unknown 52 | */ 53 | public static function retrieveParentIndex(array $indices, Model_Relationship $rel) { 54 | $new = Model_Index::fromModel_Relationship($rel); 55 | foreach($indices as $key => $index) { 56 | if(self::field_match($new -> fields, $index -> fields)) { 57 | return $index; 58 | } 59 | } 60 | throw new Exception("Index not found with fields specified: " . implode(", ", $new -> fields)); 61 | } 62 | 63 | /** 64 | * Look up relationship in a list of indices, and return the one that matches. 65 | * 66 | * @param array $indices 67 | * @param Model_Relationship $rel 68 | * @throws Exception 69 | * @return unknown 70 | */ 71 | public static function retrieveChildIndex(Model_Relationship $child) { 72 | $new = Model_Index::fromModel_RelationshipRev($child); 73 | foreach($child -> dest -> index as $key => $index) { 74 | if(self::field_match($new -> fields, $index -> fields)) { 75 | return $index; 76 | } 77 | } 78 | throw new Exception("Index not found with fields specified: " . implode(", ", $new -> fields)); 79 | } 80 | 81 | public function getFunctionName() { 82 | if($this -> name === null) { 83 | return "get"; 84 | } 85 | $f = $this -> isUnique ? "get" : "list"; 86 | return "${f}By".Model_Generator::titleCase($this -> name); 87 | } 88 | 89 | public static function fromSQL_Index(SQL_Index $orig) { 90 | $name = Model_Relationship::filterName($orig -> name, ""); 91 | return new Model_Index($orig -> fields, $name, false); 92 | } 93 | 94 | public static function fromSQL_Unique(SQL_Unique $orig) { 95 | $name = Model_Relationship::filterName($orig -> name, ""); 96 | return new Model_Index($orig -> fields, $name, true); 97 | } 98 | 99 | public static function fromModel_Relationship(Model_Relationship $orig) { 100 | return new Model_Index($orig -> constraint -> child_fields, $orig -> dest -> model_storage_name, false); 101 | } 102 | 103 | public static function fromModel_RelationshipRev(Model_Relationship $orig) { 104 | return new Model_Index($orig -> constraint -> parent_fields, $orig -> dest -> model_storage_name, false); 105 | } 106 | 107 | /** 108 | * Check if two lists of fields are equal 109 | * 110 | * @param array $f1 111 | * @param array $f2 112 | * @return boolean 113 | */ 114 | private static function field_match(array $f1, array $f2) { 115 | sort($f1); 116 | sort($f2); 117 | if(count(!$f1) != count($f2)) { 118 | return false; 119 | } 120 | for($i = 0; $i < count($f1); $i++) { 121 | if(!isset($f1[$i]) || !isset($f2[$i]) || $f1[$i] != $f2[$i]) { 122 | return false; 123 | } 124 | } 125 | return true; 126 | } 127 | } -------------------------------------------------------------------------------- /src/SQL_Table.php: -------------------------------------------------------------------------------- 1 | pk = $this -> cols = $this -> constraints = $this -> index = $this -> unique = array(); 18 | 19 | /* Find list of columns in the statement */ 20 | $lastId = $token = false; 21 | $token = null; 22 | $commentCounter = -1; 23 | $this -> comment == ""; 24 | foreach($statement -> token as $thisToken) { 25 | if(is_null($token) && $thisToken -> type == SQL_Token::IDENTIFIER && $thisToken -> has_sub) { 26 | $token = $thisToken; // This is the one which has the column defs 27 | $commentCounter = 0; 28 | } else if($commentCounter >= 0) { // Tick through expected tokens for finding a table comment 29 | if($commentCounter == 0 && $thisToken -> type == SQL_Token::IDENTIFIER && strtoupper($thisToken -> str) == "COMMENT") { 30 | $commentCounter++; 31 | } else if($commentCounter == 1 && $thisToken -> type == SQL_Token::OPERATOR && $thisToken -> str == "=") { 32 | $commentCounter++; 33 | } else if($commentCounter == 2 && $thisToken -> type = SQL_Token::STRING_LITERAL) { 34 | $this -> comment = trim(SQL_Token::get_string_literal($thisToken -> str)); 35 | $commentCounter = -1; // Reset 36 | } else if($commentCounter > 0) { 37 | $commentCounter = -1; // Something unexpected. Give up. 38 | } 39 | } 40 | } 41 | 42 | /* Split up comma-separated columns and process them all */ 43 | $this -> name = SQL_Token::get_identifier($token -> str); 44 | $nextCol = array(); 45 | foreach($token -> sub as $subtoken) { 46 | if($subtoken -> type == SQL_Token::COMMA) { 47 | $this -> addSpec($nextCol); 48 | $nextCol = array(); 49 | } else { 50 | $nextCol[] = $subtoken; 51 | } 52 | } 53 | 54 | $this -> addSpec($nextCol); // Catch last col too 55 | } 56 | 57 | /** 58 | * Identify and add row, constraint, or whatever 59 | * 60 | * @param string $tokens 61 | */ 62 | private function addSpec($tokens) { 63 | if(count($tokens) == 0) { 64 | return; 65 | } 66 | 67 | if($tokens[0] -> type == SQL_Token::IDENTIFIER) { 68 | $this -> addCol($tokens); 69 | } else if($tokens[0] -> type == SQL_Token::KEYWORD && strtoupper($tokens[0] -> str) == "INDEX") { 70 | $this -> addIndex($tokens); 71 | } else if($tokens[0] -> type == SQL_Token::KEYWORD && strtoupper($tokens[0] -> str) == "UNIQUE") { 72 | $this -> addUnique($tokens); 73 | } else if($tokens[0] -> type == SQL_Token::KEYWORD && strtoupper($tokens[0] -> str) == "PRIMARY") { 74 | $this -> addPrimaryKey($tokens); 75 | } else if($tokens[0] -> type == SQL_Token::KEYWORD && strtoupper($tokens[0] -> str) == "CONSTRAINT") { 76 | $this -> addConstraint($tokens); 77 | } else { 78 | echo "Unknown line in table def. Tokens printed below:\n"; 79 | print_r($tokens[0]); 80 | } 81 | } 82 | 83 | private function addPrimaryKey($tokens) { 84 | foreach($tokens as $token) { 85 | if(count($token -> sub) != 0) { 86 | /* Stop on an identifier with brackets */ 87 | foreach($token -> sub as $sub) { 88 | if($sub -> type == SQL_Token::IDENTIFIER) { 89 | $this -> pk[] = SQL_Token::get_identifier($sub -> str); 90 | } 91 | } 92 | return; 93 | } 94 | } 95 | throw new Exception("Couldn't parse primary key"); 96 | } 97 | 98 | private function addIndex($tokens) { 99 | $index = new SQL_Index($tokens); 100 | $this -> index[$index -> name] = $index; 101 | } 102 | 103 | private function addUnique($tokens) { 104 | $unique = new SQL_Unique($tokens); 105 | $this -> unique[$unique -> name] = $unique; 106 | } 107 | 108 | private function addCol($tokens) { 109 | $col = new SQL_Colspec($tokens); 110 | $this -> cols[$col -> name] = $col; 111 | 112 | } 113 | 114 | private function addConstraint($tokens) { 115 | $constraint = new SQL_Constraint($tokens); 116 | $this -> constraints[] = $constraint; 117 | } 118 | } -------------------------------------------------------------------------------- /src/template/lib/Core.php: -------------------------------------------------------------------------------- 1 | getMessage(), '500'); 28 | } 29 | } 30 | } 31 | 32 | /** 33 | * @param unknown_type $section 34 | * @throws Exception if the section does not exist 35 | * @return unknown 36 | */ 37 | static public function getConfig($section) { 38 | include(dirname(__FILE__) . "/../site/config.php"); 39 | if(!isset($config[$section])) { 40 | throw new Exception("No configuration found for '$section'"); 41 | } 42 | return $config[$section]; 43 | } 44 | 45 | /** 46 | * Load a class by name 47 | * 48 | * @param string $className The name of the class to load. 49 | */ 50 | static public function loadClass($className) { 51 | if(!class_exists($className)) { 52 | $sp = explode("_", $className); 53 | 54 | if(count($sp) == 1) { 55 | /* If there are no underscores, it should be in misc */ 56 | $sp[0] = self::alphanumeric($sp[0]); 57 | $fn = dirname(__FILE__)."/Util/".$sp[0].".php"; 58 | } else { 59 | /* Otherwise look in the folder suggested by the name */ 60 | $folder = self::alphanumeric(array_pop($sp)); 61 | $classfile = self::alphanumeric($className); 62 | $fn = dirname(__FILE__)."/$folder/$classfile.php"; 63 | } 64 | 65 | self::loadClassFromFile($fn, $className); 66 | } 67 | } 68 | 69 | static public function constructURL($controller, $action, $arg, $fmt) { 70 | $config = self::$config; 71 | $part = array(); 72 | 73 | if(count($arg) == 1 && $action == $config['default']['action']) { 74 | /* We can abbreviate if there is only one argument and we are using the default view */ 75 | if($controller != $config['default']['controller'] ) { 76 | /* The controller isn't default, need to add that */ 77 | array_push($part, urlencode($arg[0])); 78 | array_unshift($part, urlencode($controller)); 79 | } else { 80 | /* default controller and action. Check for default args */ 81 | if($arg[0] != $config['default']['arg'][0]) { 82 | array_push($part, urlencode($arg[0])); 83 | } 84 | } 85 | } else { 86 | /* urlencode all arguments */ 87 | foreach($arg as $a) { 88 | array_push($part, urlencode($a)); 89 | } 90 | 91 | /* Nothing is default: add controller and view */ 92 | array_unshift($part, urlencode($controller), urlencode($action)); 93 | } 94 | 95 | /* Only add format suffix if the format is non-default (ie, strip .html) */ 96 | $fmt_suff = (($fmt != $config['default']['format'])? "." . urlencode($fmt) : ""); 97 | return $config['webroot'] . implode("/", $part) . $fmt_suff; 98 | } 99 | 100 | /** 101 | * Escape user-provided string for safe inclusion in HTML code 102 | * 103 | * @param string $inp 104 | * @return string 105 | */ 106 | public static function escapeHTML($inp) { 107 | return htmlentities($inp, null, 'UTF-8'); 108 | } 109 | 110 | /** 111 | * Clear anything other than alphanumeric characters from a string (to prevent arbitrary inclusion) 112 | * 113 | * @param string $inp An input string to be sanitised. 114 | * @return string The input string containing alphanumeric characters only 115 | */ 116 | static public function alphanumeric($inp) { 117 | return preg_replace("#[^-a-zA-Z0-9]+#", "_", $inp); 118 | } 119 | 120 | public static function fizzle($message, $code=500) { 121 | switch($code) { 122 | case "403": 123 | header("HTTP/1.1 403 Forbidden"); 124 | break; 125 | case "404": 126 | header("HTTP/1.1 404 Not Found"); 127 | break; 128 | case "500": 129 | default: 130 | header("HTTP/1.1 500 Internal Server Error"); 131 | } 132 | echo json_encode(array("error" => $message)); 133 | exit(0); 134 | } 135 | 136 | public function redirect($location) { 137 | header("location: $location"); 138 | } 139 | 140 | public static function init() { 141 | /* Load permissions */ 142 | include(dirname(__FILE__) . "/../site/permissions.php"); 143 | self::$permission = $permission; 144 | } 145 | } 146 | core::init(); 147 | -------------------------------------------------------------------------------- /src/Model_Entity.php: -------------------------------------------------------------------------------- 1 | child = $this -> parent = array(); 25 | $this -> table = $table; 26 | $this -> index = array(); 27 | $this -> loadIndices(); 28 | 29 | if(array_search($table -> name, $children) !== false) { 30 | /* Only recurse if this item has not yet appeared in the tree */ 31 | return; 32 | } 33 | 34 | /* Add actual parent tables */ 35 | $newChildren = $children; 36 | $newChildren[] = $table -> name; 37 | foreach($table -> constraints as $constraint) { 38 | $this -> parent[] = new Model_Relationship($constraint, $database, $newChildren); 39 | } 40 | 41 | /* Create reverse lookup for child tables */ 42 | foreach($database -> table as $t) { 43 | foreach($t -> constraints as $constraint) { 44 | if($constraint -> parent_table == $table -> name) { 45 | $match = false; 46 | foreach($this -> parent as $p) { 47 | if($p -> constraint -> name == $constraint -> name) { 48 | $match = true; 49 | } 50 | } 51 | 52 | if(!$match) { 53 | /* Flip the constraint */ 54 | $revConstraint = clone $constraint; 55 | $revConstraint -> child_table = $t -> name; 56 | $revConstraint -> reverse(); 57 | 58 | $this -> child[] = new Model_Relationship($revConstraint, $database, array($t -> name)); 59 | } 60 | } 61 | } 62 | } 63 | 64 | /* Ensure uniqueness of parent and child names as siblings */ 65 | $this -> model_storage_name = $this -> table -> name; // Later over-ridden by parent if this isn't the root. 66 | $nameTaken = array(); 67 | foreach($this -> parent as $rel) { 68 | /* Find a unique name for this entity for the PHP model */ 69 | $num = 0; 70 | do { 71 | $testName = $rel -> shortName . ($num == 0 ? "" : $num); 72 | $num++; 73 | } while(isset($nameTaken[$testName])); 74 | $nameTaken[$testName] = true; 75 | $rel -> dest -> model_storage_name = $testName; 76 | } 77 | foreach($this -> child as $rel) { 78 | /* More uniqueness */ 79 | $num = 0; 80 | do { 81 | if($table -> name == $rel -> shortName) { 82 | $testName = $rel -> dest -> table -> name . ($num == 0 ? "" : $num); 83 | } else { 84 | $testName = $rel -> dest -> table -> name . "_by_" . $rel -> shortName . ($num == 0 ? "" : $num); 85 | } 86 | $num++; 87 | } while(isset($nameTaken[$testName])); 88 | $nameTaken[$testName] = true; 89 | $rel -> dest -> model_storage_name = $testName; 90 | } 91 | 92 | /* Breadth-first search to name all sub-tables uniquely */ 93 | $this -> query_table_name = $this -> table -> name; 94 | $nameTaken = array($this -> query_table_name); 95 | $queue = array($this); 96 | while(count($queue) != 0) { 97 | $current = array_shift($queue); 98 | 99 | /* Find a unique name for this entity when querying */ 100 | $num = 0; 101 | do { 102 | $testName = $current -> table -> name . ($num == 0 ? "" : $num); 103 | $num++; 104 | } while(isset($nameTaken[$testName])); 105 | $nameTaken[$testName] = true; 106 | $current -> query_table_name = $testName; 107 | 108 | /* Add more */ 109 | foreach($current -> parent as $p) { 110 | $queue[] = $p -> dest; 111 | } 112 | } 113 | } 114 | 115 | /** 116 | * Assemble all indices into an array with neat names 117 | */ 118 | private function loadIndices() { 119 | /* Queue up all the indexes to make a list */ 120 | $this -> index = Model_Index::addIndex($this -> index, new Model_Index($this -> table -> pk, null, true)); 121 | foreach($this -> parent as $parent) { 122 | $new = Model_Index::fromModel_Relationship($parent); 123 | $this -> index = Model_Index::addIndex($this -> index, $new); 124 | } 125 | foreach($this -> table -> unique as $unique) { 126 | $new = Model_Index::fromSQL_Unique($unique); 127 | $this -> index = Model_Index::addIndex($this -> index, $new); 128 | } 129 | foreach($this -> table -> index as $index) { 130 | $new = Model_Index::fromSQL_Index($index); 131 | $this -> index = Model_Index::addIndex($this -> index, $new); 132 | } 133 | } 134 | 135 | public function toGraphVizDotFile() { 136 | return "digraph G {\n overlap=false;rankdir=LR;splines=true; \n node[shape=record,colorscheme=set39,style=filled];\n ".implode("\n ", $this -> toGraphVizDot()) . "\n}\n"; 137 | } 138 | 139 | /** 140 | * Generate GraphViz code for a table 141 | * 142 | * @param string $id 143 | * @param string $name 144 | * @param boolean $toOne 145 | * @param boolean $isChild 146 | * @return multitype: 147 | */ 148 | private function toGraphVizDot($id = null, $toOne = true, $isChild = false) { 149 | if($id == null) { 150 | $col = 1; 151 | $id = $this -> query_table_name; 152 | } else if ($isChild) { 153 | $col = 9; 154 | } else { 155 | $col = 2; 156 | } 157 | $ret = array("\"" . $id . "\" [label=\"" . $this -> model_storage_name . " : " . $this -> table -> name . (!$toOne ? "[]" : "") . "\",fillcolor=$col];"); 158 | 159 | foreach($this -> parent as $next) { 160 | $ret = array_merge($ret, $next -> dest -> toGraphVizDot($next -> dest -> query_table_name, true, false)); 161 | $dot = $next -> nullable ? " [arrowhead=teeodot]" : " [arrowhead=tee]"; 162 | $ret[] = "\"" . $id . "\" -> \"" . $next -> dest -> query_table_name . "\":w$dot;"; 163 | } 164 | 165 | foreach($this -> child as $childId => $next) { 166 | $nextId = $this -> query_table_name . "-child-" . $next -> dest -> model_storage_name; 167 | $ret = array_merge($ret, $next -> dest -> toGraphVizDot($nextId, $next -> toOne, true)); 168 | $dot = $next -> toOne ? " [arrowhead=teeodot,style=dashed]" : " [arrowhead=crowodot,style=dashed]"; 169 | $ret[] = "\"" . $id . "\" -> \"" . $nextId . "\":w$dot;"; 170 | } 171 | 172 | return $ret; 173 | } 174 | 175 | /** 176 | * Extract a data structure which can be used to build joins and variables. 177 | * 178 | * @return Ambigous , multitype:multitype: Ambigous > 179 | */ 180 | public function process() { 181 | $ret = array(); 182 | $ret['fields'] = self::extractFields($this); 183 | $ret['join'] = array(); 184 | 185 | foreach($this -> parent as $p) { 186 | /* Join to the parent table */ 187 | $ret['join'][] = array("table" => $p -> dest -> table -> name, "as" => $p -> dest -> query_table_name, "on" => self::extractIndexFields($this, $p)); 188 | 189 | /* Merge lower-level info in */ 190 | $sub = $p -> dest -> process(); 191 | foreach($sub['fields'] as $id => $f) { 192 | array_unshift($sub['fields'][$id]['var'], $p -> dest -> model_storage_name); 193 | } 194 | 195 | $ret['fields'] = array_merge($ret['fields'], $sub['fields']); 196 | $ret['join'] = array_merge($ret['join'], $sub['join']); 197 | } 198 | return $ret; 199 | } 200 | 201 | /** 202 | * Extract list of fields for a table, labelled by the actual table 203 | * 204 | * @param Model_Entity $e 205 | * @return multitype:multitype:NULL 206 | */ 207 | private static function extractFields(Model_Entity $e) { 208 | $ret = array(); 209 | foreach($e -> table -> cols as $col) { 210 | $ret[] = array( 211 | "table" => $e -> query_table_name, 212 | "table_orig" => $e -> table -> name, 213 | "col" => $col -> name, 214 | "var" => array() 215 | ); 216 | } 217 | return $ret; 218 | } 219 | 220 | /** 221 | * Extract fields to JOIN on 222 | * 223 | * @param Model_Entity $child 224 | * @param Model_Relationship $r 225 | * @throws Exception 226 | * @return multitype:multitype:multitype:NULL multitype:NULL string 227 | */ 228 | private static function extractIndexFields(Model_Entity $child, Model_Relationship $r) { 229 | if(count($r -> constraint -> child_fields) != count($r -> constraint -> parent_table)) { 230 | throw new Exception("Index size mis-match: " . $r -> constraint -> name); 231 | } 232 | 233 | $ret = array(); 234 | for($i = 0; $i < count($r -> constraint -> child_fields); $i++) { 235 | $ret[] = array( 236 | array("table" => $r -> dest -> query_table_name, "col" => $r -> constraint -> parent_fields[$i]), 237 | array("table" => $child -> query_table_name, "col" => $r -> constraint -> child_fields[$i]) 238 | ); 239 | } 240 | return $ret; 241 | } 242 | } -------------------------------------------------------------------------------- /src/SQL_Token.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | class SQL_Token { 8 | public $type = 'UNKNOWN'; 9 | public $str = ""; 10 | public $sub = array(); 11 | public $has_sub = false; 12 | 13 | const UNKNOWN = 'UNKNOWN'; 14 | const KEYWORD = 'KEYWORD'; 15 | const STRING_LITERAL = 'STRING_LITERAL'; 16 | const IDENTIFIER = 'IDENTIFIER'; 17 | const OPERATOR = 'OPERATOR'; 18 | const OPENBRACKET = 'OPENBRACKET'; 19 | const CLOSEBRACKET = 'CLOSEBRACKET'; 20 | const LINE_COMMENT = 'LINE_COMMENT'; 21 | const WHITESPACE = 'WHITESPACE'; 22 | const COMMA = 'COMMA'; 23 | const DOT = 'DOT'; 24 | const SEMICOLON = 'SEMICOLON'; 25 | const NUMBER_LITERAL = 'NUMBER_LITERAL'; 26 | 27 | /* See http://dev.mysql.com/doc/mysqld-version-reference/en/mysqld-version-reference-reservedwords-5-5.html */ 28 | private static $keywords = array( 29 | "ACCESSIBLE", "ADD", "ALL", "ALTER", "ANALYZE", "AND", "AS", "ASC", "ASENSITIVE", "BEFORE", "BETWEEN", "BIGINT", 30 | "BINARY", "BLOB", "BOTH", "BY", "CALL", "CASCADE", "CASE", "CHANGE", "CHAR", "CHARACTER", "CHECK", "COLLATE", 31 | "COLUMN", "CONDITION", "CONSTRAINT", "CONTINUE", "CONVERT", "CREATE", "CROSS", "CURRENT_DATE", "CURRENT_TIME", 32 | "CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR", "DATABASE", "DATABASES", "DAY_HOUR", "DAY_MICROSECOND", 33 | "DAY_MINUTE", "DAY_SECOND", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DELAYED", "DELETE", "DESC", "DESCRIBE", 34 | "DETERMINISTIC", "DISTINCT", "DISTINCTROW", "DIV", "DOUBLE", "DROP", "DUAL", "EACH", "ELSE", "ELSEIF", 35 | "ENCLOSED", "ESCAPED", "EXISTS", "EXIT", "EXPLAIN", "FALSE", "FETCH", "FLOAT", "FLOAT4", "FLOAT8", "FOR", 36 | "FORCE", "FOREIGN", "FROM", "FULLTEXT", "GENERAL", "GRANT", "GROUP", "HAVING", "HIGH_PRIORITY", 37 | "HOUR_MICROSECOND", "HOUR_MINUTE", "HOUR_SECOND", "IF", "IGNORE", "IGNORE_SERVER_IDS[b]", "IN", "INDEX", 38 | "INFILE", "INNER", "INOUT", "INSENSITIVE", "INSERT", "INT", "INT1", "INT2", "INT3", "INT4", "INT8", "INTEGER", 39 | "INTERVAL", "INTO", "IS", "ITERATE", "JOIN", "KEY", "KEYS", "KILL", "LEADING", "LEAVE", "LEFT", "LIKE", "LIMIT", 40 | "LINEAR", "LINES", "LOAD", "LOCALTIME", "LOCALTIMESTAMP", "LOCK", "LONG", "LONGBLOB", "LONGTEXT", "LOOP", 41 | "LOW_PRIORITY", "MASTER_HEARTBEAT_PERIOD", "MASTER_SSL_VERIFY_SERVER_CERT", "MATCH", "MAXVALUE", "MEDIUMBLOB", 42 | "MEDIUMINT", "MEDIUMTEXT", "MIDDLEINT", "MINUTE_MICROSECOND", "MINUTE_SECOND", "MOD", "MODIFIES", "NATURAL", "NOT", 43 | "NO_WRITE_TO_BINLOG", "NULL", "NUMERIC", "ON", "OPTIMIZE", "OPTION", "OPTIONALLY", "OR", "ORDER", "OUT", "OUTER", 44 | "OUTFILE", "PRECISION", "PRIMARY", "PROCEDURE", "PURGE", "RANGE", "READ", "READS", "READ_WRITE", "REAL", 45 | "REFERENCES", "REGEXP", "RELEASE", "RENAME", "REPEAT", "REPLACE", "REQUIRE", "RESIGNAL", "RESTRICT", "RETURN", 46 | "REVOKE", "RIGHT", "RLIKE", "SCHEMA", "SCHEMAS", "SECOND_MICROSECOND", "SELECT", "SENSITIVE", "SEPARATOR", "SET", 47 | "SHOW", "SIGNAL", "SLOW", "SMALLINT", "SPATIAL", "SPECIFIC", "SQL", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", 48 | "SQL_BIG_RESULT", "SQL_CALC_FOUND_ROWS", "SQL_SMALL_RESULT", "SSL", "STARTING", "STRAIGHT_JOIN", "TABLE", 49 | "TERMINATED", "THEN", "TINYBLOB", "TINYINT", "TINYTEXT", "TO", "TRAILING", "TRIGGER", "TRUE", "UNDO", "UNION", 50 | "UNIQUE", "UNLOCK", "UNSIGNED", "UPDATE", "USAGE", "USE", "USING", "UTC_DATE", "UTC_TIME", "UTC_TIMESTAMP", 51 | "VALUES", "VARBINARY", "VARCHAR", "VARCHARACTER", "VARYING", "WHEN", "WHERE", "WHILE", "WITH", "WRITE", "XOR", 52 | "YEAR_MONTH", "ZEROFILL" 53 | ); 54 | 55 | private static $whitespace = " \t\r\n"; 56 | private static $alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 57 | private static $num = "0123456789"; 58 | private static $operators = "+-=*> type = self::UNKNOWN; 65 | $this -> str = ""; 66 | $this -> sub = array(); 67 | $this -> has_sub = false; // For spotting ( ) 68 | } 69 | 70 | /** 71 | * Test if this is a keyword 72 | * 73 | * @param string $word 74 | * @return boolean 75 | */ 76 | public static function is_keyword($word) { 77 | return in_array(strtoupper($word), self::$keywords); 78 | } 79 | 80 | /** 81 | * Test for a character appearing in a string 82 | * 83 | * @param string $char 84 | * @param string $list 85 | * @return boolean 86 | */ 87 | public static function test($char, $list) { 88 | return !(strpos($list, $char) === false); 89 | } 90 | 91 | /** 92 | * Test adding a single character 93 | * 94 | * @param string $char 95 | * @return boolean 96 | */ 97 | public function add($char) { 98 | if($this -> str == "") { 99 | $this -> str .= $char; 100 | switch($char) { 101 | case ".": 102 | $this -> type = self::DOT; 103 | break; 104 | case ",": 105 | $this -> type = self::COMMA; 106 | break; 107 | case "(": 108 | $this -> type = self::OPENBRACKET; 109 | break; 110 | case ")": 111 | $this -> type = self::CLOSEBRACKET; 112 | break; 113 | case "'": 114 | $this -> type = self::STRING_LITERAL; 115 | break; 116 | case "`": 117 | $this -> type = self::IDENTIFIER; 118 | break; 119 | case ";": 120 | $this -> type = self::SEMICOLON; 121 | break; 122 | default: 123 | /* Whitespace */ 124 | if($this -> test($char, self::$whitespace)) { 125 | $this -> type = self::WHITESPACE; 126 | } 127 | if($char != "-" && $this -> test($char, self::$operators)) { 128 | $this -> type = self::OPERATOR; 129 | } 130 | if($this -> test($char, self::$num)) { 131 | $this -> type = self::NUMBER_LITERAL; 132 | } 133 | } 134 | return true; 135 | } else { 136 | /* Whitespace */ 137 | if($this -> type == self::WHITESPACE) { 138 | if($this -> test($char, self::$whitespace)) { 139 | $this -> str .= $char; 140 | return true; 141 | } else { 142 | return false; 143 | } 144 | } 145 | 146 | /* Quoted identifier */ 147 | if($this -> type == self::IDENTIFIER) { 148 | if(strlen($this -> str) > 1 && substr($this -> str, -1) == '`') { 149 | return false; 150 | } 151 | if($this -> test($char, self::$alpha . self::$num . "_` ")) { 152 | $this -> str .= $char; 153 | return true; 154 | } else { 155 | return false; 156 | } 157 | } 158 | 159 | /* Comment */ 160 | if($this -> type == self::UNKNOWN && $char == "-" && $this -> str == "-") { 161 | $this -> type = self::LINE_COMMENT; 162 | } else if($this -> type == self::UNKNOWN && $char == "-") { 163 | if($this -> test($char, self::$num)) { 164 | $this -> type = self::NUMBER_LITERAL; 165 | } else { 166 | $this -> type = self::OPERATOR; 167 | } 168 | } 169 | if($this -> type == self::LINE_COMMENT && substr($this -> str, -1) != "\n") { 170 | $this -> str .= $char; 171 | return true; 172 | } 173 | 174 | if($this -> type == self::OPERATOR) { 175 | if($this -> test($char, self::$operators)) { 176 | $this -> str .= $char; 177 | return true; 178 | } 179 | } 180 | 181 | /* Unknown */ 182 | if($this -> type == self::UNKNOWN) { 183 | if($this -> test($char, self::$alpha . self::$num . "_@")) { 184 | $this -> str .= $char; 185 | return true; 186 | } else { 187 | if(self::is_keyword($this -> str)) { 188 | $this -> type = self::KEYWORD; 189 | } else { 190 | $this -> type = self::IDENTIFIER; 191 | } 192 | } 193 | } 194 | 195 | if($this -> type == self::NUMBER_LITERAL) { 196 | if($this -> test($char, self::$num . ".e+")) { 197 | $this -> str .= $char; 198 | return true; 199 | } else { 200 | return false; 201 | } 202 | } 203 | 204 | if($this -> type == self::STRING_LITERAL) { 205 | if(!$char != "'" && self::quotes_valid($this -> str)) { 206 | return false; 207 | } 208 | $this -> str .= $char; 209 | return true; 210 | } 211 | 212 | return false; 213 | } 214 | } 215 | 216 | /** 217 | * Returns true if the quoting of a string is valid 218 | * 219 | * @param return $str 220 | */ 221 | public static function quotes_valid($str) { 222 | $str = str_replace("\\\\", "", $str); // Ignore backslashes that escape themselves 223 | $str = str_replace("\\'", "", $str); // Ignore quotes that are escaped 224 | $str = str_replace("''", "", $str); // Ignore quotes that are doubled 225 | if($str == "'") { 226 | return false; 227 | } 228 | return($str == "" || (substr($str, 0, 1) == "'" && substr($str, -1) == "'")); 229 | } 230 | 231 | /** 232 | * Take `quotes` off an identifier if it has them 233 | * 234 | * @param string $str 235 | */ 236 | public static function get_identifier($str) { 237 | if(strlen($str) >= 2 && substr($str, 0, 1) == "`" && substr($str, -1) == "`") { 238 | return substr($str, 1, strlen($str) - 2); 239 | } 240 | return $str; 241 | } 242 | 243 | /** 244 | * Take 'quotes' off a string literal (does not handle anything inside the string properly) 245 | * 246 | * @param string $str 247 | */ 248 | public static function get_string_literal($str) { 249 | if(strlen($str) >= 2 && substr($str, 0, 1) == "'" && substr($str, -1) == "'") { 250 | $str = stripslashes(substr($str, 1, strlen($str) - 2)); 251 | } 252 | return $str; 253 | } 254 | 255 | public static function get_number_literal($str) { 256 | return (int)$str; 257 | } 258 | } -------------------------------------------------------------------------------- /src/template/public/js/underscore-min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.7.0 2 | // http://underscorejs.org 3 | // (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 4 | // Underscore may be freely distributed under the MIT license. 5 | (function(){var n=this,t=n._,r=Array.prototype,e=Object.prototype,u=Function.prototype,i=r.push,a=r.slice,o=r.concat,l=e.toString,c=e.hasOwnProperty,f=Array.isArray,s=Object.keys,p=u.bind,h=function(n){return n instanceof h?n:this instanceof h?void(this._wrapped=n):new h(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=h),exports._=h):n._=h,h.VERSION="1.7.0";var g=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}};h.iteratee=function(n,t,r){return null==n?h.identity:h.isFunction(n)?g(n,t,r):h.isObject(n)?h.matches(n):h.property(n)},h.each=h.forEach=function(n,t,r){if(null==n)return n;t=g(t,r);var e,u=n.length;if(u===+u)for(e=0;u>e;e++)t(n[e],e,n);else{var i=h.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},h.map=h.collect=function(n,t,r){if(null==n)return[];t=h.iteratee(t,r);for(var e,u=n.length!==+n.length&&h.keys(n),i=(u||n).length,a=Array(i),o=0;i>o;o++)e=u?u[o]:o,a[o]=t(n[e],e,n);return a};var v="Reduce of empty array with no initial value";h.reduce=h.foldl=h.inject=function(n,t,r,e){null==n&&(n=[]),t=g(t,e,4);var u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length,o=0;if(arguments.length<3){if(!a)throw new TypeError(v);r=n[i?i[o++]:o++]}for(;a>o;o++)u=i?i[o]:o,r=t(r,n[u],u,n);return r},h.reduceRight=h.foldr=function(n,t,r,e){null==n&&(n=[]),t=g(t,e,4);var u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;if(arguments.length<3){if(!a)throw new TypeError(v);r=n[i?i[--a]:--a]}for(;a--;)u=i?i[a]:a,r=t(r,n[u],u,n);return r},h.find=h.detect=function(n,t,r){var e;return t=h.iteratee(t,r),h.some(n,function(n,r,u){return t(n,r,u)?(e=n,!0):void 0}),e},h.filter=h.select=function(n,t,r){var e=[];return null==n?e:(t=h.iteratee(t,r),h.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e)},h.reject=function(n,t,r){return h.filter(n,h.negate(h.iteratee(t)),r)},h.every=h.all=function(n,t,r){if(null==n)return!0;t=h.iteratee(t,r);var e,u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;for(e=0;a>e;e++)if(u=i?i[e]:e,!t(n[u],u,n))return!1;return!0},h.some=h.any=function(n,t,r){if(null==n)return!1;t=h.iteratee(t,r);var e,u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;for(e=0;a>e;e++)if(u=i?i[e]:e,t(n[u],u,n))return!0;return!1},h.contains=h.include=function(n,t){return null==n?!1:(n.length!==+n.length&&(n=h.values(n)),h.indexOf(n,t)>=0)},h.invoke=function(n,t){var r=a.call(arguments,2),e=h.isFunction(t);return h.map(n,function(n){return(e?t:n[t]).apply(n,r)})},h.pluck=function(n,t){return h.map(n,h.property(t))},h.where=function(n,t){return h.filter(n,h.matches(t))},h.findWhere=function(n,t){return h.find(n,h.matches(t))},h.max=function(n,t,r){var e,u,i=-1/0,a=-1/0;if(null==t&&null!=n){n=n.length===+n.length?n:h.values(n);for(var o=0,l=n.length;l>o;o++)e=n[o],e>i&&(i=e)}else t=h.iteratee(t,r),h.each(n,function(n,r,e){u=t(n,r,e),(u>a||u===-1/0&&i===-1/0)&&(i=n,a=u)});return i},h.min=function(n,t,r){var e,u,i=1/0,a=1/0;if(null==t&&null!=n){n=n.length===+n.length?n:h.values(n);for(var o=0,l=n.length;l>o;o++)e=n[o],i>e&&(i=e)}else t=h.iteratee(t,r),h.each(n,function(n,r,e){u=t(n,r,e),(a>u||1/0===u&&1/0===i)&&(i=n,a=u)});return i},h.shuffle=function(n){for(var t,r=n&&n.length===+n.length?n:h.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=h.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},h.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=h.values(n)),n[h.random(n.length-1)]):h.shuffle(n).slice(0,Math.max(0,t))},h.sortBy=function(n,t,r){return t=h.iteratee(t,r),h.pluck(h.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var m=function(n){return function(t,r,e){var u={};return r=h.iteratee(r,e),h.each(t,function(e,i){var a=r(e,i,t);n(u,e,a)}),u}};h.groupBy=m(function(n,t,r){h.has(n,r)?n[r].push(t):n[r]=[t]}),h.indexBy=m(function(n,t,r){n[r]=t}),h.countBy=m(function(n,t,r){h.has(n,r)?n[r]++:n[r]=1}),h.sortedIndex=function(n,t,r,e){r=h.iteratee(r,e,1);for(var u=r(t),i=0,a=n.length;a>i;){var o=i+a>>>1;r(n[o])t?[]:a.call(n,0,t)},h.initial=function(n,t,r){return a.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},h.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:a.call(n,Math.max(n.length-t,0))},h.rest=h.tail=h.drop=function(n,t,r){return a.call(n,null==t||r?1:t)},h.compact=function(n){return h.filter(n,h.identity)};var y=function(n,t,r,e){if(t&&h.every(n,h.isArray))return o.apply(e,n);for(var u=0,a=n.length;a>u;u++){var l=n[u];h.isArray(l)||h.isArguments(l)?t?i.apply(e,l):y(l,t,r,e):r||e.push(l)}return e};h.flatten=function(n,t){return y(n,t,!1,[])},h.without=function(n){return h.difference(n,a.call(arguments,1))},h.uniq=h.unique=function(n,t,r,e){if(null==n)return[];h.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=h.iteratee(r,e));for(var u=[],i=[],a=0,o=n.length;o>a;a++){var l=n[a];if(t)a&&i===l||u.push(l),i=l;else if(r){var c=r(l,a,n);h.indexOf(i,c)<0&&(i.push(c),u.push(l))}else h.indexOf(u,l)<0&&u.push(l)}return u},h.union=function(){return h.uniq(y(arguments,!0,!0,[]))},h.intersection=function(n){if(null==n)return[];for(var t=[],r=arguments.length,e=0,u=n.length;u>e;e++){var i=n[e];if(!h.contains(t,i)){for(var a=1;r>a&&h.contains(arguments[a],i);a++);a===r&&t.push(i)}}return t},h.difference=function(n){var t=y(a.call(arguments,1),!0,!0,[]);return h.filter(n,function(n){return!h.contains(t,n)})},h.zip=function(n){if(null==n)return[];for(var t=h.max(arguments,"length").length,r=Array(t),e=0;t>e;e++)r[e]=h.pluck(arguments,e);return r},h.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},h.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=h.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}for(;u>e;e++)if(n[e]===t)return e;return-1},h.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=n.length;for("number"==typeof r&&(e=0>r?e+r+1:Math.min(e,r+1));--e>=0;)if(n[e]===t)return e;return-1},h.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=r||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=Array(e),i=0;e>i;i++,n+=r)u[i]=n;return u};var d=function(){};h.bind=function(n,t){var r,e;if(p&&n.bind===p)return p.apply(n,a.call(arguments,1));if(!h.isFunction(n))throw new TypeError("Bind must be called on a function");return r=a.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(a.call(arguments)));d.prototype=n.prototype;var u=new d;d.prototype=null;var i=n.apply(u,r.concat(a.call(arguments)));return h.isObject(i)?i:u}},h.partial=function(n){var t=a.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===h&&(e[u]=arguments[r++]);for(;r=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=h.bind(n[r],n);return n},h.memoize=function(n,t){var r=function(e){var u=r.cache,i=t?t.apply(this,arguments):e;return h.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},h.delay=function(n,t){var r=a.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},h.defer=function(n){return h.delay.apply(h,[n,1].concat(a.call(arguments,1)))},h.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var l=function(){o=r.leading===!1?0:h.now(),a=null,i=n.apply(e,u),a||(e=u=null)};return function(){var c=h.now();o||r.leading!==!1||(o=c);var f=t-(c-o);return e=this,u=arguments,0>=f||f>t?(clearTimeout(a),a=null,o=c,i=n.apply(e,u),a||(e=u=null)):a||r.trailing===!1||(a=setTimeout(l,f)),i}},h.debounce=function(n,t,r){var e,u,i,a,o,l=function(){var c=h.now()-a;t>c&&c>0?e=setTimeout(l,t-c):(e=null,r||(o=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,a=h.now();var c=r&&!e;return e||(e=setTimeout(l,t)),c&&(o=n.apply(i,u),i=u=null),o}},h.wrap=function(n,t){return h.partial(t,n)},h.negate=function(n){return function(){return!n.apply(this,arguments)}},h.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},h.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},h.before=function(n,t){var r;return function(){return--n>0?r=t.apply(this,arguments):t=null,r}},h.once=h.partial(h.before,2),h.keys=function(n){if(!h.isObject(n))return[];if(s)return s(n);var t=[];for(var r in n)h.has(n,r)&&t.push(r);return t},h.values=function(n){for(var t=h.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},h.pairs=function(n){for(var t=h.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},h.invert=function(n){for(var t={},r=h.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},h.functions=h.methods=function(n){var t=[];for(var r in n)h.isFunction(n[r])&&t.push(r);return t.sort()},h.extend=function(n){if(!h.isObject(n))return n;for(var t,r,e=1,u=arguments.length;u>e;e++){t=arguments[e];for(r in t)c.call(t,r)&&(n[r]=t[r])}return n},h.pick=function(n,t,r){var e,u={};if(null==n)return u;if(h.isFunction(t)){t=g(t,r);for(e in n){var i=n[e];t(i,e,n)&&(u[e]=i)}}else{var l=o.apply([],a.call(arguments,1));n=new Object(n);for(var c=0,f=l.length;f>c;c++)e=l[c],e in n&&(u[e]=n[e])}return u},h.omit=function(n,t,r){if(h.isFunction(t))t=h.negate(t);else{var e=h.map(o.apply([],a.call(arguments,1)),String);t=function(n,t){return!h.contains(e,t)}}return h.pick(n,t,r)},h.defaults=function(n){if(!h.isObject(n))return n;for(var t=1,r=arguments.length;r>t;t++){var e=arguments[t];for(var u in e)n[u]===void 0&&(n[u]=e[u])}return n},h.clone=function(n){return h.isObject(n)?h.isArray(n)?n.slice():h.extend({},n):n},h.tap=function(n,t){return t(n),n};var b=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof h&&(n=n._wrapped),t instanceof h&&(t=t._wrapped);var u=l.call(n);if(u!==l.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]===n)return e[i]===t;var a=n.constructor,o=t.constructor;if(a!==o&&"constructor"in n&&"constructor"in t&&!(h.isFunction(a)&&a instanceof a&&h.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c,f;if("[object Array]"===u){if(c=n.length,f=c===t.length)for(;c--&&(f=b(n[c],t[c],r,e)););}else{var s,p=h.keys(n);if(c=p.length,f=h.keys(t).length===c)for(;c--&&(s=p[c],f=h.has(t,s)&&b(n[s],t[s],r,e)););}return r.pop(),e.pop(),f};h.isEqual=function(n,t){return b(n,t,[],[])},h.isEmpty=function(n){if(null==n)return!0;if(h.isArray(n)||h.isString(n)||h.isArguments(n))return 0===n.length;for(var t in n)if(h.has(n,t))return!1;return!0},h.isElement=function(n){return!(!n||1!==n.nodeType)},h.isArray=f||function(n){return"[object Array]"===l.call(n)},h.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},h.each(["Arguments","Function","String","Number","Date","RegExp"],function(n){h["is"+n]=function(t){return l.call(t)==="[object "+n+"]"}}),h.isArguments(arguments)||(h.isArguments=function(n){return h.has(n,"callee")}),"function"!=typeof/./&&(h.isFunction=function(n){return"function"==typeof n||!1}),h.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},h.isNaN=function(n){return h.isNumber(n)&&n!==+n},h.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===l.call(n)},h.isNull=function(n){return null===n},h.isUndefined=function(n){return n===void 0},h.has=function(n,t){return null!=n&&c.call(n,t)},h.noConflict=function(){return n._=t,this},h.identity=function(n){return n},h.constant=function(n){return function(){return n}},h.noop=function(){},h.property=function(n){return function(t){return t[n]}},h.matches=function(n){var t=h.pairs(n),r=t.length;return function(n){if(null==n)return!r;n=new Object(n);for(var e=0;r>e;e++){var u=t[e],i=u[0];if(u[1]!==n[i]||!(i in n))return!1}return!0}},h.times=function(n,t,r){var e=Array(Math.max(0,n));t=g(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},h.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},h.now=Date.now||function(){return(new Date).getTime()};var _={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},w=h.invert(_),j=function(n){var t=function(t){return n[t]},r="(?:"+h.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};h.escape=j(_),h.unescape=j(w),h.result=function(n,t){if(null==n)return void 0;var r=n[t];return h.isFunction(r)?n[t]():r};var x=0;h.uniqueId=function(n){var t=++x+"";return n?n+t:t},h.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var A=/(.)^/,k={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},O=/\\|'|\r|\n|\u2028|\u2029/g,F=function(n){return"\\"+k[n]};h.template=function(n,t,r){!t&&r&&(t=r),t=h.defaults({},t,h.templateSettings);var e=RegExp([(t.escape||A).source,(t.interpolate||A).source,(t.evaluate||A).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,a,o){return i+=n.slice(u,o).replace(O,F),u=o+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":a&&(i+="';\n"+a+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var a=new Function(t.variable||"obj","_",i)}catch(o){throw o.source=i,o}var l=function(n){return a.call(this,n,h)},c=t.variable||"obj";return l.source="function("+c+"){\n"+i+"}",l},h.chain=function(n){var t=h(n);return t._chain=!0,t};var E=function(n){return this._chain?h(n).chain():n};h.mixin=function(n){h.each(h.functions(n),function(t){var r=h[t]=n[t];h.prototype[t]=function(){var n=[this._wrapped];return i.apply(n,arguments),E.call(this,r.apply(h,n))}})},h.mixin(h),h.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=r[n];h.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],E.call(this,r)}}),h.each(["concat","join","slice"],function(n){var t=r[n];h.prototype[n]=function(){return E.call(this,t.apply(this._wrapped,arguments))}}),h.prototype.value=function(){return this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return h})}).call(this); 6 | //# sourceMappingURL=underscore-min.map -------------------------------------------------------------------------------- /src/template/public/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.1 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-default .badge,.btn-primary .badge,.btn-success .badge,.btn-info .badge,.btn-warning .badge,.btn-danger .badge{text-shadow:none}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:hover,.btn-primary:focus{background-color:#265a88;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#265a88;border-color:#245580}.btn-primary:disabled,.btn-primary[disabled]{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:hover .badge,.list-group-item.active:focus .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /src/template/public/js/backbone-min.js: -------------------------------------------------------------------------------- 1 | (function(t,e){if(typeof define==="function"&&define.amd){define(["underscore","jquery","exports"],function(i,r,s){t.Backbone=e(t,s,i,r)})}else if(typeof exports!=="undefined"){var i=require("underscore");e(t,exports,i)}else{t.Backbone=e(t,{},t._,t.jQuery||t.Zepto||t.ender||t.$)}})(this,function(t,e,i,r){var s=t.Backbone;var n=[];var a=n.push;var o=n.slice;var h=n.splice;e.VERSION="1.1.2";e.$=r;e.noConflict=function(){t.Backbone=s;return this};e.emulateHTTP=false;e.emulateJSON=false;var u=e.Events={on:function(t,e,i){if(!c(this,"on",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,r){if(!c(this,"once",t,[e,r])||!e)return this;var s=this;var n=i.once(function(){s.off(t,n);e.apply(this,arguments)});n._callback=e;return this.on(t,n,r)},off:function(t,e,r){var s,n,a,o,h,u,l,f;if(!this._events||!c(this,"off",t,[e,r]))return this;if(!t&&!e&&!r){this._events=void 0;return this}o=t?[t]:i.keys(this._events);for(h=0,u=o.length;h").attr(t);this.setElement(r,false)}else{this.setElement(i.result(this,"el"),false)}}});e.sync=function(t,r,s){var n=T[t];i.defaults(s||(s={}),{emulateHTTP:e.emulateHTTP,emulateJSON:e.emulateJSON});var a={type:n,dataType:"json"};if(!s.url){a.url=i.result(r,"url")||M()}if(s.data==null&&r&&(t==="create"||t==="update"||t==="patch")){a.contentType="application/json";a.data=JSON.stringify(s.attrs||r.toJSON(s))}if(s.emulateJSON){a.contentType="application/x-www-form-urlencoded";a.data=a.data?{model:a.data}:{}}if(s.emulateHTTP&&(n==="PUT"||n==="DELETE"||n==="PATCH")){a.type="POST";if(s.emulateJSON)a.data._method=n;var o=s.beforeSend;s.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",n);if(o)return o.apply(this,arguments)}}if(a.type!=="GET"&&!s.emulateJSON){a.processData=false}if(a.type==="PATCH"&&k){a.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var h=s.xhr=e.ajax(i.extend(a,s));r.trigger("request",r,h,s);return h};var k=typeof window!=="undefined"&&!!window.ActiveXObject&&!(window.XMLHttpRequest&&(new XMLHttpRequest).dispatchEvent);var T={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};e.ajax=function(){return e.$.ajax.apply(e.$,arguments)};var $=e.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var S=/\((.*?)\)/g;var H=/(\(\?)?:\w+/g;var A=/\*\w+/g;var I=/[\-{}\[\]+?.,\\\^$|#\s]/g;i.extend($.prototype,u,{initialize:function(){},route:function(t,r,s){if(!i.isRegExp(t))t=this._routeToRegExp(t);if(i.isFunction(r)){s=r;r=""}if(!s)s=this[r];var n=this;e.history.route(t,function(i){var a=n._extractParameters(t,i);n.execute(s,a);n.trigger.apply(n,["route:"+r].concat(a));n.trigger("route",r,a);e.history.trigger("route",n,r,a)});return this},execute:function(t,e){if(t)t.apply(this,e)},navigate:function(t,i){e.history.navigate(t,i);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=i.result(this,"routes");var t,e=i.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(I,"\\$&").replace(S,"(?:$1)?").replace(H,function(t,e){return e?t:"([^/?]+)"}).replace(A,"([^?]*?)");return new RegExp("^"+t+"(?:\\?([\\s\\S]*))?$")},_extractParameters:function(t,e){var r=t.exec(e).slice(1);return i.map(r,function(t,e){if(e===r.length-1)return t||null;return t?decodeURIComponent(t):null})}});var N=e.History=function(){this.handlers=[];i.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var R=/^[#\/]|\s+$/g;var O=/^\/+|\/+$/g;var P=/msie [\w.]+/;var C=/\/$/;var j=/#.*$/;N.started=false;i.extend(N.prototype,u,{interval:50,atRoot:function(){return this.location.pathname.replace(/[^\/]$/,"$&/")===this.root},getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=decodeURI(this.location.pathname+this.location.search);var i=this.root.replace(C,"");if(!t.indexOf(i))t=t.slice(i.length)}else{t=this.getHash()}}return t.replace(R,"")},start:function(t){if(N.started)throw new Error("Backbone.history has already been started");N.started=true;this.options=i.extend({root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var r=this.getFragment();var s=document.documentMode;var n=P.exec(navigator.userAgent.toLowerCase())&&(!s||s<=7);this.root=("/"+this.root+"/").replace(O,"/");if(n&&this._wantsHashChange){var a=e.$('