├── .gitignore ├── tests ├── bootstrap.php ├── codeCoverage │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── css │ │ ├── style.css │ │ └── nv.d3.min.css │ ├── js │ │ ├── respond.min.js │ │ ├── html5shiv.min.js │ │ ├── holder.min.js │ │ └── bootstrap.min.js │ ├── index.html │ ├── dashboard.html │ └── RawSQL.php.html ├── test_classes.php └── FullTest.php ├── example.php ├── src ├── RawSQL.php ├── ExtendedDataModel.php └── DataModel.php ├── composer.json ├── phpunit.xml ├── .travis.yml ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | parameters.ini 2 | /vendor/ 3 | /nbproject/private/ -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 'SET NAMES \'UTF8\'')); 7 | 8 | $blog = new Blog(['title' => 'Test enty', 'body' => 'This is a test body with HTML'], Blog::LOAD_NEW); 9 | $blog->updated_at = new DateTime(); 10 | $blog->increaseReads(); 11 | $blog2 = new FilteredBlog($blog->id, FilteredBlog::LOAD_BY_PK); 12 | $blog2->delete(); 13 | -------------------------------------------------------------------------------- /src/RawSQL.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class RawSQL { 12 | 13 | protected $value; 14 | 15 | public static function make($value) { 16 | return new static($value); 17 | } 18 | 19 | public function __construct($value) { 20 | $this->value = $value; 21 | } 22 | 23 | public function __toString() { 24 | return (string) $this->value; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "itvisionsy/simple-orm", 3 | "description": "A simple PHP class to access your MySQL records.", 4 | "keywords": [ 5 | "orm", 6 | "mysql", 7 | "database", 8 | "php", 9 | "pdo" 10 | ], 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Alex Joyce", 15 | "email": "im@alex-joyce.com" 16 | }, 17 | { 18 | "name": "Muhannad Shelleh", 19 | "email": "muhannad.shelleh@live.com" 20 | } 21 | ], 22 | "autoload": { 23 | "psr-4": { 24 | "ItvisionSy\\SimpleORM\\": "src/" 25 | } 26 | }, 27 | "require-dev": { 28 | "php": ">=5.6", 29 | "phpunit/phpunit": "^5.7" 30 | }, 31 | "require": { 32 | "php": ">=5.4" 33 | } 34 | } -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | ./tests 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ./src 31 | 32 | 33 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | 6 | services: 7 | - mysql 8 | 9 | before_install: 10 | - mysql -e 'CREATE DATABASE IF NOT EXISTS test;' 11 | - mysql -e 'CREATE TABLE `test`.`blog` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(100) NOT NULL, `body` text, `reads` int(11) NOT NULL DEFAULT '0', `rate` float(6,2) DEFAULT '0.00', `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; ' 12 | - mysql -u root -e "CREATE USER 'test'@'localhost' IDENTIFIED BY 'test';" 13 | - mysql -u root -e "GRANT ALL ON test.* TO 'test'@'localhost';" 14 | 15 | before_script: 16 | - composer self-update 17 | - composer install --no-interaction 18 | - composer dumpautoload 19 | - export DB_USER=test 20 | - export DB_PASS=test 21 | - export DB_HOST=localhost 22 | 23 | script: 24 | - vendor/bin/phpunit -c phpunit.xml --coverage-text 25 | 26 | env: 27 | global: 28 | - DB_HOST=localhost 29 | - DB_USER=test 30 | - DB_PASS=test 31 | - DB_NAME=test 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Alex Joyce 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/test_classes.php: -------------------------------------------------------------------------------- 1 | 0]; 9 | protected static $tableName = 'blog'; 10 | 11 | protected function filterOutReads(array $data) { 12 | $data['active'] = $data['reads'] > 0; 13 | return $data; 14 | } 15 | 16 | protected function filterOutBody(array $data) { 17 | $data['body'] = strip_tags($data['body']); 18 | return $data; 19 | } 20 | 21 | protected function filterOutDates(array $data) { 22 | $data['created_at'] = DateTime::createFromFormat('Y-m-d H:i:s', $data['created_at']); 23 | $data['updated_at'] = DateTime::createFromFormat('Y-m-d H:i:s', $data['updated_at']); 24 | return $data; 25 | } 26 | 27 | protected function filterInDates(array $data) { 28 | $data['created_at'] = $data['created_at'] instanceof DateTime ? $data['created_at']->format('Y-m-d H:i:s') : $data['created_at']; 29 | $data['updated_at'] = $data['updated_at'] instanceof DateTime ? $data['updated_at']->format('Y-m-d H:i:s') : $data['updated_at']; 30 | return $data; 31 | } 32 | 33 | public function increaseReads() { 34 | $this->reads++; 35 | $this->save(); 36 | } 37 | 38 | } 39 | 40 | class FilteredBlog extends Blog { 41 | 42 | /** 43 | * 44 | * @return DateTime 45 | */ 46 | public function createdAt() { 47 | return $this->created_at; 48 | } 49 | 50 | public function preInsert(array &$data = array()) { 51 | $true = !empty($data['body']); 52 | return parent::preInsert($data) && $true; 53 | } 54 | 55 | public function preUpdate(array &$data = array()) { 56 | $true = !empty($data['body']); 57 | return parent::preUpdate($data) && $true; 58 | } 59 | 60 | public function preDelete() { 61 | $true = $this->createdAt()->getTimestamp() <= time() - 1 * 60 * 60; 62 | return parent::preDelete() && $true; 63 | } 64 | 65 | } 66 | 67 | class ReadBlog extends Blog { 68 | 69 | protected static $readOnly = true; 70 | 71 | } 72 | 73 | class UnexistedTable extends DataModel { 74 | 75 | } 76 | -------------------------------------------------------------------------------- /tests/codeCoverage/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 10px; 3 | } 4 | 5 | .popover { 6 | max-width: none; 7 | } 8 | 9 | .glyphicon { 10 | margin-right:.25em; 11 | } 12 | 13 | .table-bordered>thead>tr>td { 14 | border-bottom-width: 1px; 15 | } 16 | 17 | .table tbody>tr>td, .table thead>tr>td { 18 | padding-top: 3px; 19 | padding-bottom: 3px; 20 | } 21 | 22 | .table-condensed tbody>tr>td { 23 | padding-top: 0; 24 | padding-bottom: 0; 25 | } 26 | 27 | .table .progress { 28 | margin-bottom: inherit; 29 | } 30 | 31 | .table-borderless th, .table-borderless td { 32 | border: 0 !important; 33 | } 34 | 35 | .table tbody tr.covered-by-large-tests, li.covered-by-large-tests, tr.success, td.success, li.success, span.success { 36 | background-color: #dff0d8; 37 | } 38 | 39 | .table tbody tr.covered-by-medium-tests, li.covered-by-medium-tests { 40 | background-color: #c3e3b5; 41 | } 42 | 43 | .table tbody tr.covered-by-small-tests, li.covered-by-small-tests { 44 | background-color: #99cb84; 45 | } 46 | 47 | .table tbody tr.danger, .table tbody td.danger, li.danger, span.danger { 48 | background-color: #f2dede; 49 | } 50 | 51 | .table tbody td.warning, li.warning, span.warning { 52 | background-color: #fcf8e3; 53 | } 54 | 55 | .table tbody td.info { 56 | background-color: #d9edf7; 57 | } 58 | 59 | td.big { 60 | width: 117px; 61 | } 62 | 63 | td.small { 64 | } 65 | 66 | td.codeLine { 67 | font-family: monospace; 68 | white-space: pre; 69 | } 70 | 71 | td span.comment { 72 | color: #888a85; 73 | } 74 | 75 | td span.default { 76 | color: #2e3436; 77 | } 78 | 79 | td span.html { 80 | color: #888a85; 81 | } 82 | 83 | td span.keyword { 84 | color: #2e3436; 85 | font-weight: bold; 86 | } 87 | 88 | pre span.string { 89 | color: #2e3436; 90 | } 91 | 92 | span.success, span.warning, span.danger { 93 | margin-right: 2px; 94 | padding-left: 10px; 95 | padding-right: 10px; 96 | text-align: center; 97 | } 98 | 99 | #classCoverageDistribution, #classComplexity { 100 | height: 200px; 101 | width: 475px; 102 | } 103 | 104 | #toplink { 105 | position: fixed; 106 | left: 5px; 107 | bottom: 5px; 108 | outline: 0; 109 | } 110 | 111 | svg text { 112 | font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif; 113 | font-size: 11px; 114 | color: #666; 115 | fill: #666; 116 | } 117 | 118 | .scrollbox { 119 | height:245px; 120 | overflow-x:hidden; 121 | overflow-y:scroll; 122 | } 123 | -------------------------------------------------------------------------------- /src/ExtendedDataModel.php: -------------------------------------------------------------------------------- 1 | cache[$key] = $value; 23 | return $this; 24 | } 25 | 26 | /** 27 | * 28 | * @param string $key 29 | * @return boolean 30 | */ 31 | protected function isCached($key) { 32 | return array_key_exists($key, $this->cache); 33 | } 34 | 35 | /** 36 | * 37 | * @param string $key 38 | * @param mixed $default 39 | * @return mixed 40 | */ 41 | protected function cached($key, $default = null) { 42 | return $this->isCached($key) ? $this->cache[$key] : $default; 43 | } 44 | 45 | /** 46 | * 47 | * @param tring $key 48 | * @param \callable $value 49 | * @return mixed 50 | */ 51 | protected function cachedOrCache($key, callable $value) { 52 | if (!$this->isCached($key)) { 53 | $this->cache($key, $value()); 54 | } 55 | return $this->cached($key); 56 | } 57 | 58 | /** 59 | * 60 | */ 61 | protected static function assertStaticCacheExists() { 62 | if (!array_key_exists(get_called_class(), static::$staticCache)) { 63 | static::$staticCache[get_called_class()] = []; 64 | } 65 | } 66 | 67 | /** 68 | * 69 | * @param string $key 70 | * @param scalar|mixed $value 71 | * @return $this 72 | */ 73 | protected static function staticCache($key, $value) { 74 | static::assertStaticCacheExists(); 75 | static::$staticCache[get_called_class()][$key] = $value; 76 | return $this; 77 | } 78 | 79 | /** 80 | * 81 | * @param string $key 82 | * @return boolean 83 | */ 84 | protected static function staticIsCached($key) { 85 | static::assertStaticCacheExists(); 86 | return array_key_exists($key, static::$staticCache[get_called_class()]); 87 | } 88 | 89 | /** 90 | * 91 | * @param string $key 92 | * @param mixed $default 93 | * @return mixed 94 | */ 95 | protected static function staticCached($key, $default = null) { 96 | static::assertStaticCacheExists(); 97 | return static::$staticIsCached($key) ? static::$staticCache[get_called_class()][$key] : $default; 98 | } 99 | 100 | /** 101 | * 102 | * @param string $key 103 | * @param \callable $value 104 | * @return mixed 105 | */ 106 | protected static function staticCachedOrCache($key, callable $value) { 107 | if (!static::staticIsCached($key)) { 108 | static::staticCache($key, $value()); 109 | } 110 | return static::staticCached($key); 111 | } 112 | 113 | /** 114 | * 115 | * @param string $modelClass 116 | * @param string $localKey 117 | * @param string $foreignKey 118 | * @param string $fetchMode 119 | * @param boolean $forceReload 120 | * @param string $key 121 | * @param string $extraQuery 122 | * @param array $extraParams 123 | * @return ExtendedDataModel|ExtendedDataModel[]|array|self|self[]|static|static[] 124 | */ 125 | protected function loadRelation($modelClass, $localKey, $foreignKey, $fetchMode = self::FETCH_MANY, $forceReload = false, $key = null, $extraQuery = null, array $extraParams = []) { 126 | $key = 'fk__' . ($key ?: $modelClass . $localKey . $foreignKey . $fetchMode); 127 | return $this->cachedOrCache($key, function() use($modelClass, $localKey, $foreignKey, $fetchMode, $extraQuery, $extraParams) { 128 | return call_user_func_array([$modelClass, "sql"], ["SELECT * FROM :table WHERE {$foreignKey}=?" . ($extraQuery ? " {$extraQuery}" : ""), $fetchMode, array_merge([$this->$localKey], $extraParams)]); 129 | }); 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /tests/codeCoverage/js/respond.min.js: -------------------------------------------------------------------------------- 1 | /*! Respond.js v1.4.2: min/max-width media query polyfill * Copyright 2013 Scott Jehl 2 | * Licensed under https://github.com/scottjehl/Respond/blob/master/LICENSE-MIT 3 | * */ 4 | 5 | !function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b 2 | 3 | 4 | 5 | Code Coverage for /Users/muhannad/Projects/php-simple-orm/src 6 | 7 | 8 | 9 | 13 | 14 | 15 |
16 |
17 |
18 |
19 | 24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 51 | 52 | 53 | 59 | 60 | 61 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 81 | 87 | 88 | 89 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 107 | 108 | 109 | 115 | 116 | 117 | 123 | 124 | 125 | 126 | 127 | 128 | 129 |
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
46 |
47 | 100.00% covered (success) 48 |
49 |
50 |
100.00%
282 / 282
54 |
55 | 100.00% covered (success) 56 |
57 |
58 |
100.00%
50 / 50
62 |
63 | 100.00% covered (success) 64 |
65 |
66 |
100.00%
2 / 2
DataModel.php
74 |
75 | 100.00% covered (success) 76 |
77 |
78 |
100.00%
278 / 278
82 |
83 | 100.00% covered (success) 84 |
85 |
86 |
100.00%
47 / 47
90 |
91 | 100.00% covered (success) 92 |
93 |
94 |
100.00%
1 / 1
RawSQL.php
102 |
103 | 100.00% covered (success) 104 |
105 |
106 |
100.00%
4 / 4
110 |
111 | 100.00% covered (success) 112 |
113 |
114 |
100.00%
3 / 3
118 |
119 | 100.00% covered (success) 120 |
121 |
122 |
100.00%
1 / 1
130 | 142 |
143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![PHP](https://img.shields.io/badge/PHP-5.4+-4F5B93.svg)]() 2 | [![Packagist](https://img.shields.io/packagist/v/itvisionsy/simple-orm.svg)](https://packagist.org/packages/itvisionsy/simple-orm) 3 | [![Build Status](https://travis-ci.org/itvisionsy/php-simple-orm.svg?branch=master)](https://travis-ci.org/itvisionsy/php-simple-orm) 4 | [![license](https://img.shields.io/github/license/itvisionsy/laravel-modules.svg)]() 5 | 6 | Simple ORM 7 | ========== 8 | 9 | Simple ORM is an object-relational mapper for PHP & MySQL (using mysqli). It 10 | provides a simple way to create, retrieve, update & delete records. 11 | 12 | This is not intended for large scale projects as it has extra database interaction than necessary. I would suggest [Doctrine](http://www.doctrine-project.org/) for such things. 13 | 14 | Simple ORM is a few years old but it has been upgraded over time, therefore **PHP 5.3** is a requirement. 15 | 16 | 17 | Configuration 18 | -------------- 19 | 20 | To utilise this tool you need to do a few things: 21 | 22 | 1. Include the class file. 23 | 2. Create a `mysqli` object. 24 | 3. Tell `SimpleOrm` to use the `mysqli` connection. 25 | 26 | For example: 27 | 28 | // Include the Simple ORM class 29 | include 'SimpleOrm.class.php'; 30 | 31 | // Connect to the database using mysqli 32 | $conn = new mysqli('host', 'user', 'password'); 33 | 34 | if ($conn->connect_error) 35 | die(sprintf('Unable to connect to the database. %s', $conn->connect_error)); 36 | 37 | // Tell Simple ORM to use the connection you just created. 38 | SimpleOrm::useConnection($conn, 'database'); 39 | 40 | 41 | Object/Table Definition 42 | ------------------------ 43 | 44 | Define an object that relates to a table. 45 | 46 | class Blog extends SimpleOrm { } 47 | 48 | 49 | Basic Usage 50 | ------------ 51 | 52 | Create an entry: 53 | 54 | $entry = new Blog; 55 | $entry->title = 'Hello'; 56 | $entry->body = 'World!'; 57 | $entry->save(); 58 | 59 | Retrieve a record by it's primary key: 60 | 61 | $entry = Blog::retrieveByPK(1); 62 | 63 | Retrieve a record using a column name: 64 | 65 | $entry = Blog::retrieveByTitle('Hello', SimpleOrm::FETCH_ONE); 66 | 67 | Update a record: 68 | 69 | $entry->body = 'Mars!'; 70 | $entry->save(); 71 | 72 | Delete the record: 73 | 74 | $entry->delete(); 75 | 76 | 77 | Class Configuration 78 | ==================== 79 | 80 | This section will detail how you define your objects and how they relate to your MySQL tables. 81 | 82 | A Basic Object 83 | --------------- 84 | class Foo extends SimpleOrm {} 85 | 86 | 87 | Class Naming 88 | ------------- 89 | The following assumptions will be made for all objects: 90 | 91 | 1. The database used is the database loaded in the `mysqli` object. 92 | 2. The table name is the class name in lower case. 93 | 3. The primary key is `id`. 94 | 95 | 96 | Customisation 97 | -------------- 98 | You can customise the assumptions listed above using the following static properties: 99 | 100 | * database 101 | * table 102 | * pk 103 | 104 | For example: 105 | 106 | class Foo extends SimpleOrm 107 | { 108 | protected static 109 | $database = 'test', 110 | $table = 'foobar', 111 | $pk = 'fooid'; 112 | } 113 | 114 | 115 | Data Manipulation 116 | ================== 117 | 118 | This section will detail how you modify your records/objects. 119 | 120 | 121 | Creating/Inserting New Records 122 | ------------------------------- 123 | You can start a new instance & save the object or you can feed it an array. 124 | 125 | $foo = new Foo; 126 | $foo->title = 'hi!'; 127 | $foo->save(); 128 | 129 | or 130 | 131 | $foo = new Foo(array('title'=>'hi!')); 132 | $foo->save(); 133 | 134 | 135 | Updating 136 | --------- 137 | Simply modify any property on the object & use the save() method. 138 | 139 | $foo->title = 'hi!'; 140 | $foo->save(); 141 | 142 | If you want to have some more control over manipulating data you can use set(), get() & isModified(). 143 | 144 | $foo->set('title', 'hi!'); 145 | $foo->save(); 146 | 147 | 148 | Deleting 149 | --------- 150 | Use the delete() method. 151 | 152 | $foo->delete(); 153 | 154 | 155 | Data Retrieval 156 | =============== 157 | 158 | This section will detail how you fetch data from mysql and boot your objects. 159 | 160 | 161 | Using the Primary Key 162 | ---------------------- 163 | $foo = Foo::retrieveByPK(1); 164 | 165 | or 166 | 167 | $foo = new Foo(1); 168 | 169 | 170 | Using a Column Name 171 | -------------------- 172 | $foo = Foo::retrieveByField('bar', SimpleOrm::FETCH_ONE); 173 | 174 | By default, the retrieveBy* method will return an array of objects (SimpleOrm::FETCH_MANY). 175 | 176 | 177 | Select All 178 | ----------- 179 | $foo = Foo::all(); 180 | 181 | 182 | Fetch Constants 183 | ---------------- 184 | `SimpleOrm::FETCH_ONE` will return a single object or null if the record is not found. 185 | 186 | `SimpleOrm::FETCH_MANY` will always return an array of hydrated objects. 187 | 188 | 189 | Populating from an Array (Hydration) 190 | ------------------------------------- 191 | You can pass in an associative array to populate an object. This saves retrieving a record each time from the database. 192 | 193 | $foo = Foo::hydrate($array); 194 | 195 | 196 | SQL Statements 197 | -------------- 198 | Any SQL statement can be used as long as all the returning data is for the object. 199 | 200 | Example 201 | 202 | $foo = Foo::sql("SELECT * FROM :table WHERE foo = 'bar'"); 203 | 204 | This will return an array of hydrated Foo objects. 205 | 206 | If you only want a single entry to be returned you can request this. 207 | 208 | $foo = Foo::sql("SELECT * FROM :table WHERE foo = 'bar'", SimpleOrm::FETCH_ONE); 209 | 210 | 211 | SQL Tokens 212 | ----------- 213 | The table name & primary key have shortcuts to save you writing the same names over & over in your SQL statements: 214 | 215 | * `:database` will be replaced with the database name. 216 | * `:table` will be replaced with the table name. 217 | * `:pk` will be replaced with the primary key field name. 218 | 219 | 220 | Extra Data & Aggregate Functions 221 | --------------------------------- 222 | Any extra data that does not belong to object being loaded will have those fields populated in the object: 223 | 224 | $foo = Foo::sql("SELECT *, 'hi' AS otherData FROM :table WHERE foo = 'bar'", SimpleOrm::FETCH_ONE); 225 | 226 | echo $foo->otherData; // returns 'hi' 227 | 228 | This can be useful if you plan to use aggregate functions within your object & you want to pre-load the data. 229 | 230 | 231 | Filters 232 | ======== 233 | 234 | Input & output filters can be created to alter data going into the database & when it comes out. 235 | 236 | To add a filter, you only need to create a method with either filterIn or filterOut as a prefix (e.g. filterIn, filterInHi, filterIn_hi). 237 | 238 | These methods will automatically fire when data is loaded into the object (hydration) or saved into the database (save, update, insert). 239 | 240 | 241 | Input Filters 242 | -------------- 243 | 244 | Data being saved to the database can be modified. 245 | 246 | For example: 247 | 248 | class Foo extends SimpleOrm 249 | { 250 | protected function filterIn_dates ($data) 251 | { 252 | $data['updated_at'] = time(); 253 | 254 | return $data; 255 | } 256 | } 257 | 258 | In the example above, every time the object is saved, the updated_at field is populated with a current time & date. 259 | 260 | Note: You must return the input array otherwise no fields will be updated. 261 | 262 | 263 | Output Filters 264 | --------------- 265 | 266 | Any data loaded into the object will be passed through any output filters. 267 | 268 | class Foo extends SimpleOrm 269 | { 270 | protected function filterOut () 271 | { 272 | $this->foo = unserialize($this->foo); 273 | } 274 | } 275 | 276 | In the example above, each time the object is hydrated, the foo property is unserialized. 277 | -------------------------------------------------------------------------------- /tests/FullTest.php: -------------------------------------------------------------------------------- 1 | getenv('DB_HOST') ?: 'localhost', 12 | 'user' => getenv('DB_USER') ?: 'root', 13 | 'pass' => getenv('DB_PASS') ?: null, 14 | 'name' => getenv('DB_NAME') ?: 'test' 15 | ]; 16 | } 17 | 18 | public function setUp() { 19 | parent::setup(); 20 | $db = static::db(); 21 | Blog::createConnection($db['host'], $db['user'], $db['pass'], $db['name'], array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'')); 22 | } 23 | 24 | public function testTruncate() { 25 | $db = static::db(); 26 | Blog::createConnection($db['host'], $db['user'], $db['pass'], $db['name'], array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'')); 27 | Blog::truncate(); 28 | $this->assertEquals(0, Blog::count()); 29 | } 30 | 31 | public function testConnections() { 32 | //use 33 | $db = static::db(); 34 | Blog::useConnection(new PDO("mysql:dbname={$db['name']};host={$db['host']}", $db['user'], $db['pass'])); 35 | $this->assertEquals(0, Blog::count()); 36 | } 37 | 38 | /** 39 | */ 40 | public function testCreateNew() { 41 | 42 | //directly 43 | $blog = new Blog(); 44 | $this->assertEquals(Blog::LOAD_EMPTY, $blog->getLoadMethod()); 45 | $this->assertNull($blog->getLoadData()); 46 | $blog->title = 'Test'; 47 | $blog->body = 'Test Body'; 48 | $blog->rate = 3.6; 49 | $blog->save(); 50 | $this->assertNotNull($blog->id()); 51 | 52 | //by array 53 | $blog = new Blog(['title' => 'Test', 'body' => 'Test body'], Blog::LOAD_NEW); 54 | $this->assertEquals(Blog::LOAD_NEW, $blog->getLoadMethod()); 55 | $this->assertTrue(is_array($blog->getLoadData())); 56 | $this->assertNotNull($blog->id()); 57 | $blog->delete(); 58 | 59 | $blog2 = new Blog(); 60 | $blog2->set($blog->getRaw()); 61 | $blog2->update(); 62 | $this->assertNotNull($blog2->id()); 63 | } 64 | 65 | public function testFetchAll() { 66 | $blogs = Blog::all(); 67 | $this->assertEquals(2, count($blogs)); 68 | } 69 | 70 | public function testLoad() { 71 | 72 | //by id 73 | $blog = Blog::retrieveByPK(1); 74 | $this->assertNotNull($blog); 75 | $this->assertEquals('Test', $blog->title); 76 | 77 | //static method retrieveBy 78 | $blog = Blog::retrieveById(2); 79 | $this->assertNotNull($blog); 80 | } 81 | 82 | public function testCreatedAndUpdatedAt() { 83 | 84 | $date = Blog::sql("SELECT CURRENT_TIMESTAMP", BLOG::FETCH_FIELD); 85 | $blog = new Blog(['title' => 'Test', 'body' => 'Test body'], Blog::LOAD_NEW); 86 | $this->assertEquals($date, $blog->created_at->format('Y-m-d H:i:s')); 87 | $this->assertEquals($date, $blog->updated_at->format('Y-m-d H:i:s')); 88 | sleep(2); 89 | $date = Blog::sql("SELECT CURRENT_TIMESTAMP", BLOG::FETCH_FIELD); 90 | $blog->save(); 91 | $this->assertEquals($date, $blog->updated_at->format('Y-m-d H:i:s')); 92 | } 93 | 94 | public function testAccess() { 95 | $blog = Blog::retrieveByPK(1); 96 | $this->assertInstanceOf(DateTime::class, $blog->get('updated_at')); 97 | $this->assertStringMatchesFormat("%d-%d-%d %d:%d:%d", $blog->getRaw('updated_at')); 98 | $this->assertTrue(is_array($blog->get())); 99 | $this->assertTrue(is_array($blog->getRaw())); 100 | $this->assertEquals(count($blog->getRaw()) + 1, count($blog->get())); 101 | } 102 | 103 | public function testRevert() { 104 | $blog = Blog::retrieveByField('id', 1, Blog::FETCH_ONE); 105 | $this->assertFalse($blog->isModified()); 106 | $this->assertEquals('Test', $blog->title); 107 | $blog->set(['title' => 'Title2']); 108 | $this->assertEquals('Title2', $blog->title); 109 | $blog->set('title', 'Title3'); 110 | $this->assertEquals('Title3', $blog->get('title')); 111 | $this->assertTrue($blog->isModified()); 112 | $this->assertEquals(1, count($blog->modified())); 113 | $this->assertEquals(2, count($blog->modified()['title'])); 114 | $blog2 = $blog->revert(true); 115 | $this->assertEquals('Test', $blog2->title); 116 | $this->assertNotEquals($blog, $blog2); 117 | } 118 | 119 | public function testDelete() { 120 | $blog = new Blog(['title' => 'To delete', 'body' => 'To be deleted'], Blog::LOAD_NEW); 121 | $this->assertNotNull($blog); 122 | $blog->delete(); 123 | $this->expectException(Exception::class); 124 | $blog->revert(); 125 | } 126 | 127 | public function testIncorrectIdLoad() { 128 | 129 | //load wrong id 130 | $this->expectException(Exception::class); 131 | Blog::retrieveByPK(22); 132 | } 133 | 134 | public function testTruncateReadOnly() { 135 | //read only truncate 136 | $this->expectException(Exception::class); 137 | ReadBlog::truncate(); 138 | } 139 | 140 | public function testInsertReadOnly() { 141 | //write new to readonly 142 | $this->expectException(Exception::class); 143 | $blog = new ReadBlog(['title' => 'Erroring one', 'body' => 'the body'], ReadBlog::LOAD_NEW); 144 | } 145 | 146 | public function testUpdateReadOnly() { 147 | //modify to readonly 148 | $this->expectException(Exception::class); 149 | $blog = ReadBlog::retrieveByPK(1); 150 | $blog->increaseReads(); 151 | } 152 | 153 | public function testDeleteReadonly() { 154 | //modify to readonly 155 | $this->expectException(Exception::class); 156 | $blog = ReadBlog::retrieveByPK(1); 157 | $blog->delete(); 158 | } 159 | 160 | public function testDeleteNew() { 161 | //modify to readonly 162 | $this->expectException(Exception::class); 163 | $blog = new Blog(['title' => 'Test', 'body' => 'Test'], Blog::LOAD_BY_ARRAY); 164 | $blog->delete(); 165 | } 166 | 167 | public function testDescribeUnexisted() { 168 | //modify to readonly 169 | $this->expectException(Exception::class); 170 | $entry = new UnexistedTable(null, UnexistedTable::LOAD_EMPTY); 171 | } 172 | 173 | public function testInvalidSQL() { 174 | //modify to readonly 175 | $this->expectException(Exception::class); 176 | $entry = Blog::sql("SELECT SomeInvalidFunction();"); 177 | } 178 | 179 | public function testInvalidPkValue() { 180 | //modify to readonly 181 | $this->expectException(InvalidArgumentException::class); 182 | $entry = Blog::retrieveByPK([]); 183 | } 184 | 185 | public function testInvalidStaticCall() { 186 | //modify to readonly 187 | $this->expectException(Exception::class); 188 | $entry = Blog::someMethodDoesNotExist([]); 189 | } 190 | 191 | public function testInvalidSet() { 192 | //modify to readonly 193 | $this->expectException(Exception::class); 194 | $entry = Blog::retrieveByPK(1); 195 | $entry->some_invalid_value = 1; 196 | } 197 | 198 | public function testInvalidExecuteStatement() { 199 | //modify to readonly 200 | $this->expectException(Exception::class); 201 | $entry = new UnexistedTable(['k' => 1], UnexistedTable::LOAD_NEW); 202 | } 203 | 204 | public function testFilteredCrud() { 205 | //insert 206 | $blog = new FilteredBlog(['title' => 'Test', 'body' => ''], FilteredBlog::LOAD_NEW); 207 | $this->assertTrue($blog->isNew()); 208 | 209 | //update 210 | $blog = FilteredBlog::retrieveByPK(1); 211 | $blog->body = ''; 212 | $blog->save(); 213 | $blog->revert(); 214 | $this->assertEquals('Test Body', $blog->body); 215 | 216 | //delete 217 | $blog->delete(); 218 | $blog->revert(); 219 | $this->assertNotEmpty($blog); 220 | } 221 | 222 | } 223 | -------------------------------------------------------------------------------- /tests/codeCoverage/css/nv.d3.min.css: -------------------------------------------------------------------------------- 1 | .nvd3 .nv-axis{pointer-events:none;opacity:1}.nvd3 .nv-axis path{fill:none;stroke:#000;stroke-opacity:.75;shape-rendering:crispEdges}.nvd3 .nv-axis path.domain{stroke-opacity:.75}.nvd3 .nv-axis.nv-x path.domain{stroke-opacity:0}.nvd3 .nv-axis line{fill:none;stroke:#e5e5e5;shape-rendering:crispEdges}.nvd3 .nv-axis .zero line,.nvd3 .nv-axis line.zero{stroke-opacity:.75}.nvd3 .nv-axis .nv-axisMaxMin text{font-weight:700}.nvd3 .x .nv-axis .nv-axisMaxMin text,.nvd3 .x2 .nv-axis .nv-axisMaxMin text,.nvd3 .x3 .nv-axis .nv-axisMaxMin text{text-anchor:middle}.nvd3 .nv-axis.nv-disabled{opacity:0}.nvd3 .nv-bars rect{fill-opacity:.75;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-bars rect.hover{fill-opacity:1}.nvd3 .nv-bars .hover rect{fill:#add8e6}.nvd3 .nv-bars text{fill:rgba(0,0,0,0)}.nvd3 .nv-bars .hover text{fill:rgba(0,0,0,1)}.nvd3 .nv-multibar .nv-groups rect,.nvd3 .nv-multibarHorizontal .nv-groups rect,.nvd3 .nv-discretebar .nv-groups rect{stroke-opacity:0;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-multibar .nv-groups rect:hover,.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,.nvd3 .nv-candlestickBar .nv-ticks rect:hover,.nvd3 .nv-discretebar .nv-groups rect:hover{fill-opacity:1}.nvd3 .nv-discretebar .nv-groups text,.nvd3 .nv-multibarHorizontal .nv-groups text{font-weight:700;fill:rgba(0,0,0,1);stroke:rgba(0,0,0,0)}.nvd3 .nv-boxplot circle{fill-opacity:.5}.nvd3 .nv-boxplot circle:hover{fill-opacity:1}.nvd3 .nv-boxplot rect:hover{fill-opacity:1}.nvd3 line.nv-boxplot-median{stroke:#000}.nv-boxplot-tick:hover{stroke-width:2.5px}.nvd3.nv-bullet{font:10px sans-serif}.nvd3.nv-bullet .nv-measure{fill-opacity:.8}.nvd3.nv-bullet .nv-measure:hover{fill-opacity:1}.nvd3.nv-bullet .nv-marker{stroke:#000;stroke-width:2px}.nvd3.nv-bullet .nv-markerTriangle{stroke:#000;fill:#fff;stroke-width:1.5px}.nvd3.nv-bullet .nv-tick line{stroke:#666;stroke-width:.5px}.nvd3.nv-bullet .nv-range.nv-s0{fill:#eee}.nvd3.nv-bullet .nv-range.nv-s1{fill:#ddd}.nvd3.nv-bullet .nv-range.nv-s2{fill:#ccc}.nvd3.nv-bullet .nv-title{font-size:14px;font-weight:700}.nvd3.nv-bullet .nv-subtitle{fill:#999}.nvd3.nv-bullet .nv-range{fill:#bababa;fill-opacity:.4}.nvd3.nv-bullet .nv-range:hover{fill-opacity:.7}.nvd3.nv-candlestickBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.positive rect{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.negative rect{stroke:#d62728;fill:#d62728}.with-transitions .nv-candlestickBar .nv-ticks .nv-tick{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-candlestickBar .nv-ticks line{stroke:#333}.nvd3 .nv-legend .nv-disabled rect{}.nvd3 .nv-check-box .nv-box{fill-opacity:0;stroke-width:2}.nvd3 .nv-check-box .nv-check{fill-opacity:0;stroke-width:4}.nvd3 .nv-series.nv-disabled .nv-check-box .nv-check{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-controlsWrap .nv-legend .nv-check-box .nv-check{opacity:0}.nvd3.nv-linePlusBar .nv-bar rect{fill-opacity:.75}.nvd3.nv-linePlusBar .nv-bar rect:hover{fill-opacity:1}.nvd3 .nv-groups path.nv-line{fill:none}.nvd3 .nv-groups path.nv-area{stroke:none}.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point{fill-opacity:0;stroke-opacity:0}.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point{fill-opacity:.5!important;stroke-opacity:.5!important}.with-transitions .nvd3 .nv-groups .nv-point{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-scatter .nv-groups .nv-point.hover,.nvd3 .nv-groups .nv-point.hover{stroke-width:7px;fill-opacity:.95!important;stroke-opacity:.95!important}.nvd3 .nv-point-paths path{stroke:#aaa;stroke-opacity:0;fill:#eee;fill-opacity:0}.nvd3 .nv-indexLine{cursor:ew-resize}svg.nvd3-svg{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-ms-user-select:none;-moz-user-select:none;user-select:none;display:block;width:100%;height:100%}.nvtooltip.with-3d-shadow,.with-3d-shadow .nvtooltip{-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nvd3 text{font:400 12px Arial}.nvd3 .title{font:700 14px Arial}.nvd3 .nv-background{fill:#fff;fill-opacity:0}.nvd3.nv-noData{font-size:18px;font-weight:700}.nv-brush .extent{fill-opacity:.125;shape-rendering:crispEdges}.nv-brush .resize path{fill:#eee;stroke:#666}.nvd3 .nv-legend .nv-series{cursor:pointer}.nvd3 .nv-legend .nv-disabled circle{fill-opacity:0}.nvd3 .nv-brush .extent{fill-opacity:0!important}.nvd3 .nv-brushBackground rect{stroke:#000;stroke-width:.4;fill:#fff;fill-opacity:.7}.nvd3.nv-ohlcBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive{stroke:#2ca02c}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative{stroke:#d62728}.nvd3 .background path{fill:none;stroke:#EEE;stroke-opacity:.4;shape-rendering:crispEdges}.nvd3 .foreground path{fill:none;stroke-opacity:.7}.nvd3 .nv-parallelCoordinates-brush .extent{fill:#fff;fill-opacity:.6;stroke:gray;shape-rendering:crispEdges}.nvd3 .nv-parallelCoordinates .hover{fill-opacity:1;stroke-width:3px}.nvd3 .missingValuesline line{fill:none;stroke:#000;stroke-width:1;stroke-opacity:1;stroke-dasharray:5,5}.nvd3.nv-pie path{stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-pie .nv-pie-title{font-size:24px;fill:rgba(19,196,249,.59)}.nvd3.nv-pie .nv-slice text{stroke:#000;stroke-width:0}.nvd3.nv-pie path{stroke:#fff;stroke-width:1px;stroke-opacity:1}.nvd3.nv-pie .hover path{fill-opacity:.7}.nvd3.nv-pie .nv-label{pointer-events:none}.nvd3.nv-pie .nv-label rect{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-groups .nv-point.hover{stroke-width:20px;stroke-opacity:.5}.nvd3 .nv-scatter .nv-point.hover{fill-opacity:1}.nv-noninteractive{pointer-events:none}.nv-distx,.nv-disty{pointer-events:none}.nvd3.nv-sparkline path{fill:none}.nvd3.nv-sparklineplus g.nv-hoverValue{pointer-events:none}.nvd3.nv-sparklineplus .nv-hoverValue line{stroke:#333;stroke-width:1.5px}.nvd3.nv-sparklineplus,.nvd3.nv-sparklineplus g{pointer-events:all}.nvd3 .nv-hoverArea{fill-opacity:0;stroke-opacity:0}.nvd3.nv-sparklineplus .nv-xValue,.nvd3.nv-sparklineplus .nv-yValue{stroke-width:0;font-size:.9em;font-weight:400}.nvd3.nv-sparklineplus .nv-yValue{stroke:#f66}.nvd3.nv-sparklineplus .nv-maxValue{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-sparklineplus .nv-minValue{stroke:#d62728;fill:#d62728}.nvd3.nv-sparklineplus .nv-currentValue{font-weight:700;font-size:1.1em}.nvd3.nv-stackedarea path.nv-area{fill-opacity:.7;stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-stackedarea path.nv-area.hover{fill-opacity:.9}.nvd3.nv-stackedarea .nv-groups .nv-point{stroke-opacity:0;fill-opacity:0}.nvtooltip{position:absolute;background-color:rgba(255,255,255,1);color:rgba(0,0,0,1);padding:1px;border:1px solid rgba(0,0,0,.2);z-index:10000;display:block;font-family:Arial;font-size:13px;text-align:left;pointer-events:none;white-space:nowrap;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.nvtooltip{background:rgba(255,255,255,.8);border:1px solid rgba(0,0,0,.5);border-radius:4px}.nvtooltip.with-transitions,.with-transitions .nvtooltip{transition:opacity 50ms linear;-moz-transition:opacity 50ms linear;-webkit-transition:opacity 50ms linear;transition-delay:200ms;-moz-transition-delay:200ms;-webkit-transition-delay:200ms}.nvtooltip.x-nvtooltip,.nvtooltip.y-nvtooltip{padding:8px}.nvtooltip h3{margin:0;padding:4px 14px;line-height:18px;font-weight:400;background-color:rgba(247,247,247,.75);color:rgba(0,0,0,1);text-align:center;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.nvtooltip p{margin:0;padding:5px 14px;text-align:center}.nvtooltip span{display:inline-block;margin:2px 0}.nvtooltip table{margin:6px;border-spacing:0}.nvtooltip table td{padding:2px 9px 2px 0;vertical-align:middle}.nvtooltip table td.key{font-weight:400}.nvtooltip table td.value{text-align:right;font-weight:700}.nvtooltip table tr.highlight td{padding:1px 9px 1px 0;border-bottom-style:solid;border-bottom-width:1px;border-top-style:solid;border-top-width:1px}.nvtooltip table td.legend-color-guide div{width:8px;height:8px;vertical-align:middle}.nvtooltip table td.legend-color-guide div{width:12px;height:12px;border:1px solid #999}.nvtooltip .footer{padding:3px;text-align:center}.nvtooltip-pending-removal{pointer-events:none;display:none}.nvd3 .nv-interactiveGuideLine{pointer-events:none}.nvd3 line.nv-guideline{stroke:#ccc} -------------------------------------------------------------------------------- /tests/codeCoverage/js/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | ;(function(window, document) { 5 | /*jshint evil:true */ 6 | /** version */ 7 | var version = '3.7.3'; 8 | 9 | /** Preset options */ 10 | var options = window.html5 || {}; 11 | 12 | /** Used to skip problem elements */ 13 | var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; 14 | 15 | /** Not all elements can be cloned in IE **/ 16 | var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i; 17 | 18 | /** Detect whether the browser supports default html5 styles */ 19 | var supportsHtml5Styles; 20 | 21 | /** Name of the expando, to work with multiple documents or to re-shiv one document */ 22 | var expando = '_html5shiv'; 23 | 24 | /** The id for the the documents expando */ 25 | var expanID = 0; 26 | 27 | /** Cached data for each document */ 28 | var expandoData = {}; 29 | 30 | /** Detect whether the browser supports unknown elements */ 31 | var supportsUnknownElements; 32 | 33 | (function() { 34 | try { 35 | var a = document.createElement('a'); 36 | a.innerHTML = ''; 37 | //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles 38 | supportsHtml5Styles = ('hidden' in a); 39 | 40 | supportsUnknownElements = a.childNodes.length == 1 || (function() { 41 | // assign a false positive if unable to shiv 42 | (document.createElement)('a'); 43 | var frag = document.createDocumentFragment(); 44 | return ( 45 | typeof frag.cloneNode == 'undefined' || 46 | typeof frag.createDocumentFragment == 'undefined' || 47 | typeof frag.createElement == 'undefined' 48 | ); 49 | }()); 50 | } catch(e) { 51 | // assign a false positive if detection fails => unable to shiv 52 | supportsHtml5Styles = true; 53 | supportsUnknownElements = true; 54 | } 55 | 56 | }()); 57 | 58 | /*--------------------------------------------------------------------------*/ 59 | 60 | /** 61 | * Creates a style sheet with the given CSS text and adds it to the document. 62 | * @private 63 | * @param {Document} ownerDocument The document. 64 | * @param {String} cssText The CSS text. 65 | * @returns {StyleSheet} The style element. 66 | */ 67 | function addStyleSheet(ownerDocument, cssText) { 68 | var p = ownerDocument.createElement('p'), 69 | parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; 70 | 71 | p.innerHTML = 'x'; 72 | return parent.insertBefore(p.lastChild, parent.firstChild); 73 | } 74 | 75 | /** 76 | * Returns the value of `html5.elements` as an array. 77 | * @private 78 | * @returns {Array} An array of shived element node names. 79 | */ 80 | function getElements() { 81 | var elements = html5.elements; 82 | return typeof elements == 'string' ? elements.split(' ') : elements; 83 | } 84 | 85 | /** 86 | * Extends the built-in list of html5 elements 87 | * @memberOf html5 88 | * @param {String|Array} newElements whitespace separated list or array of new element names to shiv 89 | * @param {Document} ownerDocument The context document. 90 | */ 91 | function addElements(newElements, ownerDocument) { 92 | var elements = html5.elements; 93 | if(typeof elements != 'string'){ 94 | elements = elements.join(' '); 95 | } 96 | if(typeof newElements != 'string'){ 97 | newElements = newElements.join(' '); 98 | } 99 | html5.elements = elements +' '+ newElements; 100 | shivDocument(ownerDocument); 101 | } 102 | 103 | /** 104 | * Returns the data associated to the given document 105 | * @private 106 | * @param {Document} ownerDocument The document. 107 | * @returns {Object} An object of data. 108 | */ 109 | function getExpandoData(ownerDocument) { 110 | var data = expandoData[ownerDocument[expando]]; 111 | if (!data) { 112 | data = {}; 113 | expanID++; 114 | ownerDocument[expando] = expanID; 115 | expandoData[expanID] = data; 116 | } 117 | return data; 118 | } 119 | 120 | /** 121 | * returns a shived element for the given nodeName and document 122 | * @memberOf html5 123 | * @param {String} nodeName name of the element 124 | * @param {Document|DocumentFragment} ownerDocument The context document. 125 | * @returns {Object} The shived element. 126 | */ 127 | function createElement(nodeName, ownerDocument, data){ 128 | if (!ownerDocument) { 129 | ownerDocument = document; 130 | } 131 | if(supportsUnknownElements){ 132 | return ownerDocument.createElement(nodeName); 133 | } 134 | if (!data) { 135 | data = getExpandoData(ownerDocument); 136 | } 137 | var node; 138 | 139 | if (data.cache[nodeName]) { 140 | node = data.cache[nodeName].cloneNode(); 141 | } else if (saveClones.test(nodeName)) { 142 | node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); 143 | } else { 144 | node = data.createElem(nodeName); 145 | } 146 | 147 | // Avoid adding some elements to fragments in IE < 9 because 148 | // * Attributes like `name` or `type` cannot be set/changed once an element 149 | // is inserted into a document/fragment 150 | // * Link elements with `src` attributes that are inaccessible, as with 151 | // a 403 response, will cause the tab/window to crash 152 | // * Script elements appended to fragments will execute when their `src` 153 | // or `text` property is set 154 | return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node; 155 | } 156 | 157 | /** 158 | * returns a shived DocumentFragment for the given document 159 | * @memberOf html5 160 | * @param {Document} ownerDocument The context document. 161 | * @returns {Object} The shived DocumentFragment. 162 | */ 163 | function createDocumentFragment(ownerDocument, data){ 164 | if (!ownerDocument) { 165 | ownerDocument = document; 166 | } 167 | if(supportsUnknownElements){ 168 | return ownerDocument.createDocumentFragment(); 169 | } 170 | data = data || getExpandoData(ownerDocument); 171 | var clone = data.frag.cloneNode(), 172 | i = 0, 173 | elems = getElements(), 174 | l = elems.length; 175 | for(;i 2 | 3 | 4 | 5 | Dashboard for /Users/muhannad/Projects/php-simple-orm/src 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 |
17 |
18 |
19 |
20 | 25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |

Classes

33 |
34 |
35 |
36 |
37 |

Coverage Distribution

38 |
39 | 40 |
41 |
42 |
43 |

Complexity

44 |
45 | 46 |
47 |
48 |
49 |
50 |
51 |

Insufficient Coverage

52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
ClassCoverage
64 |
65 |
66 |
67 |

Project Risks

68 |
69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |
ClassCRAP
80 |
81 |
82 |
83 |
84 |
85 |

Methods

86 |
87 |
88 |
89 |
90 |

Coverage Distribution

91 |
92 | 93 |
94 |
95 |
96 |

Complexity

97 |
98 | 99 |
100 |
101 |
102 |
103 |
104 |

Insufficient Coverage

105 |
106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 |
MethodCoverage
117 |
118 |
119 |
120 |

Project Risks

121 |
122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 |
MethodCRAP
133 |
134 |
135 |
136 | 142 |
143 | 144 | 145 | 146 | 147 | 148 | 285 | 286 | 287 | -------------------------------------------------------------------------------- /tests/codeCoverage/RawSQL.php.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Code Coverage for /Users/muhannad/Projects/php-simple-orm/src/RawSQL.php 6 | 7 | 8 | 9 | 13 | 14 | 15 |
16 |
17 |
18 |
19 | 24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 80 | 81 | 82 | 88 | 89 | 90 | 91 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 109 | 110 | 111 | 112 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 130 | 131 | 132 | 133 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 151 | 152 | 153 | 154 | 160 | 161 | 162 | 163 | 164 | 165 | 166 |
 
Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
46 |
47 | 100.00% covered (success) 48 |
49 |
50 |
100.00%
1 / 1
54 |
55 | 100.00% covered (success) 56 |
57 |
58 |
100.00%
3 / 3
CRAP
63 |
64 | 100.00% covered (success) 65 |
66 |
67 |
100.00%
4 / 4
RawSQL
75 |
76 | 100.00% covered (success) 77 |
78 |
79 |
100.00%
1 / 1
83 |
84 | 100.00% covered (success) 85 |
86 |
87 |
100.00%
3 / 3
3
92 |
93 | 100.00% covered (success) 94 |
95 |
96 |
100.00%
4 / 4
 make
104 |
105 | 100.00% covered (success) 106 |
107 |
108 |
100.00%
1 / 1
1
113 |
114 | 100.00% covered (success) 115 |
116 |
117 |
100.00%
1 / 1
 __construct
125 |
126 | 100.00% covered (success) 127 |
128 |
129 |
100.00%
1 / 1
1
134 |
135 | 100.00% covered (success) 136 |
137 |
138 |
100.00%
2 / 2
 __toString
146 |
147 | 100.00% covered (success) 148 |
149 |
150 |
100.00%
1 / 1
1
155 |
156 | 100.00% covered (success) 157 |
158 |
159 |
100.00%
1 / 1
167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 |
<?php
namespace ItvisionSy\SimpleORM;
/**
 * Class to allow raw SQL strings in the SQL query
 * 
 * @package ItvisionSy\SimpleORM
 * @author  Muhannad Shelleh <muhannad.shelleh@live.com>
 */
class RawSQL {
    protected $value;
    public static function make($value) {
        return new static($value);
    }
    public function __construct($value) {
        $this->value = $value;
    }
    public function __toString() {
        return (string) $this->value;
    }
}
199 | 212 |
213 | 214 | 215 | 216 | 239 | 240 | 241 | -------------------------------------------------------------------------------- /tests/codeCoverage/js/holder.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | Holder - client side image placeholders 4 | Version 2.7.1+6hydf 5 | © 2015 Ivan Malopinsky - http://imsky.co 6 | 7 | Site: http://holderjs.com 8 | Issues: https://github.com/imsky/holder/issues 9 | License: http://opensource.org/licenses/MIT 10 | 11 | */ 12 | !function(a){if(a.document){var b=a.document;b.querySelectorAll||(b.querySelectorAll=function(c){var d,e=b.createElement("style"),f=[];for(b.documentElement.firstChild.appendChild(e),b._qsa=[],e.styleSheet.cssText=c+"{x-qsa:expression(document._qsa && document._qsa.push(this))}",a.scrollBy(0,0),e.parentNode.removeChild(e);b._qsa.length;)d=b._qsa.shift(),d.style.removeAttribute("x-qsa"),f.push(d);return b._qsa=null,f}),b.querySelector||(b.querySelector=function(a){var c=b.querySelectorAll(a);return c.length?c[0]:null}),b.getElementsByClassName||(b.getElementsByClassName=function(a){return a=String(a).replace(/^|\s+/g,"."),b.querySelectorAll(a)}),Object.keys||(Object.keys=function(a){if(a!==Object(a))throw TypeError("Object.keys called on non-object");var b,c=[];for(b in a)Object.prototype.hasOwnProperty.call(a,b)&&c.push(b);return c}),function(a){var b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";a.atob=a.atob||function(a){a=String(a);var c,d=0,e=[],f=0,g=0;if(a=a.replace(/\s/g,""),a.length%4===0&&(a=a.replace(/=+$/,"")),a.length%4===1)throw Error("InvalidCharacterError");if(/[^+/0-9A-Za-z]/.test(a))throw Error("InvalidCharacterError");for(;d>16&255)),e.push(String.fromCharCode(f>>8&255)),e.push(String.fromCharCode(255&f)),g=0,f=0),d+=1;return 12===g?(f>>=4,e.push(String.fromCharCode(255&f))):18===g&&(f>>=2,e.push(String.fromCharCode(f>>8&255)),e.push(String.fromCharCode(255&f))),e.join("")},a.btoa=a.btoa||function(a){a=String(a);var c,d,e,f,g,h,i,j=0,k=[];if(/[^\x00-\xFF]/.test(a))throw Error("InvalidCharacterError");for(;j>2,g=(3&c)<<4|d>>4,h=(15&d)<<2|e>>6,i=63&e,j===a.length+2?(h=64,i=64):j===a.length+1&&(i=64),k.push(b.charAt(f),b.charAt(g),b.charAt(h),b.charAt(i));return k.join("")}}(a),Object.prototype.hasOwnProperty||(Object.prototype.hasOwnProperty=function(a){var b=this.__proto__||this.constructor.prototype;return a in this&&(!(a in b)||b[a]!==this[a])}),function(){if("performance"in a==!1&&(a.performance={}),Date.now=Date.now||function(){return(new Date).getTime()},"now"in a.performance==!1){var b=Date.now();performance.timing&&performance.timing.navigationStart&&(b=performance.timing.navigationStart),a.performance.now=function(){return Date.now()-b}}}(),a.requestAnimationFrame||(a.webkitRequestAnimationFrame?!function(a){a.requestAnimationFrame=function(b){return webkitRequestAnimationFrame(function(){b(a.performance.now())})},a.cancelAnimationFrame=webkitCancelAnimationFrame}(a):a.mozRequestAnimationFrame?!function(a){a.requestAnimationFrame=function(b){return mozRequestAnimationFrame(function(){b(a.performance.now())})},a.cancelAnimationFrame=mozCancelAnimationFrame}(a):!function(a){a.requestAnimationFrame=function(b){return a.setTimeout(b,1e3/60)},a.cancelAnimationFrame=a.clearTimeout}(a))}}(this),function(a,b){"object"==typeof exports&&"object"==typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):"object"==typeof exports?exports.Holder=b():a.Holder=b()}(this,function(){return function(a){function b(d){if(c[d])return c[d].exports;var e=c[d]={exports:{},id:d,loaded:!1};return a[d].call(e.exports,e,e.exports,b),e.loaded=!0,e.exports}var c={};return b.m=a,b.c=c,b.p="",b(0)}([function(a,b,c){(function(b){function d(a,b,c,d){var f=e(c.substr(c.lastIndexOf(a.domain)),a);f&&h({mode:null,el:d,flags:f,engineSettings:b})}function e(a,b){var c={theme:B(J.settings.themes.gray,null),stylesheets:b.stylesheets,instanceOptions:b};return a.match(/([\d]+p?)x([\d]+p?)(?:\?|$)/)?f(a,c):g(a,c)}function f(a,b){var c=a.split("?"),d=c[0].split("/");b.holderURL=a;var e=d[1],f=e.match(/([\d]+p?)x([\d]+p?)/);if(!f)return!1;if(b.fluid=-1!==e.indexOf("p"),b.dimensions={width:f[1].replace("p","%"),height:f[2].replace("p","%")},2===c.length){var g=A.parse(c[1]);if(g.bg&&(b.theme.background=(-1===g.bg.indexOf("#")?"#":"")+g.bg),g.fg&&(b.theme.foreground=(-1===g.fg.indexOf("#")?"#":"")+g.fg),g.theme&&b.instanceOptions.themes.hasOwnProperty(g.theme)&&(b.theme=B(b.instanceOptions.themes[g.theme],null)),g.text&&(b.text=g.text),g.textmode&&(b.textmode=g.textmode),g.size&&(b.size=g.size),g.font&&(b.font=g.font),g.align&&(b.align=g.align),b.nowrap=z.truthy(g.nowrap),b.auto=z.truthy(g.auto),z.truthy(g.random)){J.vars.cache.themeKeys=J.vars.cache.themeKeys||Object.keys(b.instanceOptions.themes);var h=J.vars.cache.themeKeys[0|Math.random()*J.vars.cache.themeKeys.length];b.theme=B(b.instanceOptions.themes[h],null)}}return b}function g(a,b){var c=!1,d=String.fromCharCode(11),e=a.replace(/([^\\])\//g,"$1"+d).split(d),f=/%[0-9a-f]{2}/gi,g=b.instanceOptions;b.holderURL=[];for(var h=e.length,i=0;h>i;i++){var j=e[i];if(j.match(f))try{j=decodeURIComponent(j)}catch(k){j=e[i]}var l=!1;if(J.flags.dimensions.match(j))c=!0,b.dimensions=J.flags.dimensions.output(j),l=!0;else if(J.flags.fluid.match(j))c=!0,b.dimensions=J.flags.fluid.output(j),b.fluid=!0,l=!0;else if(J.flags.textmode.match(j))b.textmode=J.flags.textmode.output(j),l=!0;else if(J.flags.colors.match(j)){var m=J.flags.colors.output(j);b.theme=B(b.theme,m),l=!0}else if(g.themes[j])g.themes.hasOwnProperty(j)&&(b.theme=B(g.themes[j],null)),l=!0;else if(J.flags.font.match(j))b.font=J.flags.font.output(j),l=!0;else if(J.flags.auto.match(j))b.auto=!0,l=!0;else if(J.flags.text.match(j))b.text=J.flags.text.output(j),l=!0;else if(J.flags.size.match(j))b.size=J.flags.size.output(j),l=!0;else if(J.flags.random.match(j)){null==J.vars.cache.themeKeys&&(J.vars.cache.themeKeys=Object.keys(g.themes));var n=J.vars.cache.themeKeys[0|Math.random()*J.vars.cache.themeKeys.length];b.theme=B(g.themes[n],null),l=!0}l&&b.holderURL.push(j)}return b.holderURL.unshift(g.domain),b.holderURL=b.holderURL.join("/"),c?b:!1}function h(a){var b=a.mode,c=a.el,d=a.flags,e=a.engineSettings,f=d.dimensions,g=d.theme,h=f.width+"x"+f.height;if(b=null==b?d.fluid?"fluid":"image":b,null!=d.text&&(g.text=d.text,"object"===c.nodeName.toLowerCase())){for(var j=g.text.split("\\n"),k=0;k1){var n,o=0,p=0,q=0;j=new e.Group("line"+q),("left"===a.align||"right"===a.align)&&(m=a.width*(1-2*(1-J.setup.lineWrapRatio)));for(var r=0;r=m||t===!0)&&(b(g,j,o,g.properties.leading),g.add(j),o=0,p+=g.properties.leading,q+=1,j=new e.Group("line"+q),j.y=p),t!==!0&&(i.moveTo(o,0),o+=h.spaceWidth+s.width,j.add(i))}if(b(g,j,o,g.properties.leading),g.add(j),"left"===a.align)g.moveTo(a.width-l,null,null);else if("right"===a.align){for(n in g.children)j=g.children[n],j.moveTo(a.width-j.width,null,null);g.moveTo(0-(a.width-l),null,null)}else{for(n in g.children)j=g.children[n],j.moveTo((g.width-j.width)/2,null,null);g.moveTo((a.width-g.width)/2,null,null)}g.moveTo(null,(a.height-g.height)/2,null),(a.height-g.height)/2<0&&g.moveTo(null,0,null)}else i=new e.Text(a.text),j=new e.Group("line0"),j.add(i),g.add(j),"left"===a.align?g.moveTo(a.width-l,null,null):"right"===a.align?g.moveTo(0-(a.width-l),null,null):g.moveTo((a.width-h.boundingBox.width)/2,null,null),g.moveTo(null,(a.height-h.boundingBox.height)/2,null);return d}function k(a,b,c){var d=parseInt(a,10),e=parseInt(b,10),f=Math.max(d,e),g=Math.min(d,e),h=.8*Math.min(g,f*J.defaults.scale);return Math.round(Math.max(c,h))}function l(a){var b;b=null==a||null==a.nodeType?J.vars.resizableImages:[a];for(var c=0,d=b.length;d>c;c++){var e=b[c];if(e.holderData){var f=e.holderData.flags,g=D(e);if(g){if(!e.holderData.resizeUpdate)continue;if(f.fluid&&f.auto){var h=e.holderData.fluidConfig;switch(h.mode){case"width":g.height=g.width/h.ratio;break;case"height":g.width=g.height*h.ratio}}var j={mode:"image",holderSettings:{dimensions:g,theme:f.theme,flags:f},el:e,engineSettings:e.holderData.engineSettings};"exact"==f.textmode&&(f.exactDimensions=g,j.holderSettings.dimensions=f.dimensions),i(j)}else p(e)}}}function m(a){if(a.holderData){var b=D(a);if(b){var c=a.holderData.flags,d={fluidHeight:"%"==c.dimensions.height.slice(-1),fluidWidth:"%"==c.dimensions.width.slice(-1),mode:null,initialDimensions:b};d.fluidWidth&&!d.fluidHeight?(d.mode="width",d.ratio=d.initialDimensions.width/parseFloat(c.dimensions.height)):!d.fluidWidth&&d.fluidHeight&&(d.mode="height",d.ratio=parseFloat(c.dimensions.width)/d.initialDimensions.height),a.holderData.fluidConfig=d}else p(a)}}function n(){for(var a,c=[],d=Object.keys(J.vars.invisibleImages),e=0,f=d.length;f>e;e++)a=J.vars.invisibleImages[d[e]],D(a)&&"img"==a.nodeName.toLowerCase()&&(c.push(a),delete J.vars.invisibleImages[d[e]]);c.length&&I.run({images:c}),b.requestAnimationFrame(n)}function o(){J.vars.visibilityCheckStarted||(b.requestAnimationFrame(n),J.vars.visibilityCheckStarted=!0)}function p(a){a.holderData.invisibleId||(J.vars.invisibleId+=1,J.vars.invisibleImages["i"+J.vars.invisibleId]=a,a.holderData.invisibleId=J.vars.invisibleId)}function q(a,b){return null==b?document.createElement(a):document.createElementNS(b,a)}function r(a,b){for(var c in b)a.setAttribute(c,b[c])}function s(a,b,c){var d,e;null==a?(a=q("svg",E),d=q("defs",E),e=q("style",E),r(e,{type:"text/css"}),d.appendChild(e),a.appendChild(d)):e=a.querySelector("style"),a.webkitMatchesSelector&&a.setAttribute("xmlns",E);for(var f=0;f=0;h--){var i=g.createProcessingInstruction("xml-stylesheet",'href="'+f[h]+'" rel="stylesheet"');g.insertBefore(i,g.firstChild)}g.removeChild(g.documentElement),e=d.serializeToString(g)}var j=d.serializeToString(a);return j=j.replace(/\&(\#[0-9]{2,}\;)/g,"&$1"),e+j}}function u(){return b.DOMParser?(new DOMParser).parseFromString("","application/xml"):void 0}function v(a){J.vars.debounceTimer||a.call(this),J.vars.debounceTimer&&b.clearTimeout(J.vars.debounceTimer),J.vars.debounceTimer=b.setTimeout(function(){J.vars.debounceTimer=null,a.call(this)},J.setup.debounce)}function w(){v(function(){l(null)})}var x=c(1),y=c(2),z=c(3),A=c(4),B=z.extend,C=z.getNodeArray,D=z.dimensionCheck,E="http://www.w3.org/2000/svg",F=8,G="2.7.1",H="\nCreated with Holder.js "+G+".\nLearn more at http://holderjs.com\n(c) 2012-2015 Ivan Malopinsky - http://imsky.co\n",I={version:G,addTheme:function(a,b){return null!=a&&null!=b&&(J.settings.themes[a]=b),delete J.vars.cache.themeKeys,this},addImage:function(a,b){var c=document.querySelectorAll(b);if(c.length)for(var d=0,e=c.length;e>d;d++){var f=q("img"),g={};g[J.vars.dataAttr]=a,r(f,g),c[d].appendChild(f)}return this},setResizeUpdate:function(a,b){a.holderData&&(a.holderData.resizeUpdate=!!b,a.holderData.resizeUpdate&&l(a))},run:function(a){a=a||{};var c={},f=B(J.settings,a);J.vars.preempted=!0,J.vars.dataAttr=f.dataAttr||J.vars.dataAttr,c.renderer=f.renderer?f.renderer:J.setup.renderer,-1===J.setup.renderers.join(",").indexOf(c.renderer)&&(c.renderer=J.setup.supportsSVG?"svg":J.setup.supportsCanvas?"canvas":"html");var g=C(f.images),i=C(f.bgnodes),j=C(f.stylenodes),k=C(f.objects);c.stylesheets=[],c.svgXMLStylesheet=!0,c.noFontFallback=f.noFontFallback?f.noFontFallback:!1;for(var l=0;l1){c.nodeValue="";for(var u=0;u=0?b:1)}function f(a){v?e(a):w.push(a)}null==document.readyState&&document.addEventListener&&(document.addEventListener("DOMContentLoaded",function y(){document.removeEventListener("DOMContentLoaded",y,!1),document.readyState="complete"},!1),document.readyState="loading");var g=a.document,h=g.documentElement,i="load",j=!1,k="on"+i,l="complete",m="readyState",n="attachEvent",o="detachEvent",p="addEventListener",q="DOMContentLoaded",r="onreadystatechange",s="removeEventListener",t=p in g,u=j,v=j,w=[];if(g[m]===l)e(b);else if(t)g[p](q,c,j),a[p](i,c,j);else{g[n](r,c),a[n](k,c);try{u=null==a.frameElement&&h}catch(x){}u&&u.doScroll&&!function z(){if(!v){try{u.doScroll("left")}catch(a){return e(z,50)}d(),b()}}()}return f.version="1.4.0",f.isReady=function(){return v},f}a.exports="undefined"!=typeof window&&b(window)},function(a,b,c){var d=c(5),e=function(a){function b(a,b){for(var c in b)a[c]=b[c];return a}var c=1,e=d.defclass({constructor:function(a){c++,this.parent=null,this.children={},this.id=c,this.name="n"+c,null!=a&&(this.name=a),this.x=0,this.y=0,this.z=0,this.width=0,this.height=0},resize:function(a,b){null!=a&&(this.width=a),null!=b&&(this.height=b)},moveTo:function(a,b,c){this.x=null!=a?a:this.x,this.y=null!=b?b:this.y,this.z=null!=c?c:this.z},add:function(a){var b=a.name;if(null!=this.children[b])throw"SceneGraph: child with that name already exists: "+b;this.children[b]=a,a.parent=this}}),f=d(e,function(b){this.constructor=function(){b.constructor.call(this,"root"),this.properties=a}}),g=d(e,function(a){function c(c,d){if(a.constructor.call(this,c),this.properties={fill:"#000"},null!=d)b(this.properties,d);else if(null!=c&&"string"!=typeof c)throw"SceneGraph: invalid node name"}this.Group=d.extend(this,{constructor:c,type:"group"}),this.Rect=d.extend(this,{constructor:c,type:"rect"}),this.Text=d.extend(this,{constructor:function(a){c.call(this),this.properties.text=a},type:"text"})}),h=new f;return this.Shape=g,this.root=h,this};a.exports=e},function(a,b){(function(a){b.extend=function(a,b){var c={};for(var d in a)a.hasOwnProperty(d)&&(c[d]=a[d]);if(null!=b)for(var e in b)b.hasOwnProperty(e)&&(c[e]=b[e]);return c},b.cssProps=function(a){var b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c+":"+a[c]);return b.join(";")},b.encodeHtmlEntity=function(a){for(var b=[],c=0,d=a.length-1;d>=0;d--)c=a.charCodeAt(d),b.unshift(c>128?["&#",c,";"].join(""):a[d]);return b.join("")},b.getNodeArray=function(b){var c=null;return"string"==typeof b?c=document.querySelectorAll(b):a.NodeList&&b instanceof a.NodeList?c=b:a.Node&&b instanceof a.Node?c=[b]:a.HTMLCollection&&b instanceof a.HTMLCollection?c=b:b instanceof Array?c=b:null===b&&(c=[]),c},b.imageExists=function(a,b){var c=new Image;c.onerror=function(){b.call(this,!1)},c.onload=function(){b.call(this,!0)},c.src=a},b.decodeHtmlEntity=function(a){return a.replace(/&#(\d+);/g,function(a,b){return String.fromCharCode(b)})},b.dimensionCheck=function(a){var b={height:a.clientHeight,width:a.clientWidth};return b.height&&b.width?b:!1},b.truthy=function(a){return"string"==typeof a?"true"===a||"yes"===a||"1"===a||"on"===a||"✓"===a:!!a}}).call(b,function(){return this}())},function(a,b,c){var d=encodeURIComponent,e=decodeURIComponent,f=c(6),g=c(7),h=/(\w+)\[(\d+)\]/,i=/\w+\.\w+/;b.parse=function(a){if("string"!=typeof a)return{};if(a=f(a),""===a)return{};"?"===a.charAt(0)&&(a=a.slice(1));for(var b={},c=a.split("&"),d=0;d 16 | * @author Muhannad Shelleh 17 | */ 18 | abstract class DataModel { 19 | 20 | /** @var PDO db connection object */ 21 | protected static $conn; 22 | 23 | /** @var string name of table */ 24 | protected static $tableName; 25 | 26 | /** @var string name of pk column */ 27 | protected static $pkColumn; 28 | 29 | /** @var string name of created at column timestamp */ 30 | protected static $createdAtColumn; 31 | 32 | /** @var string name of updated at column timestamp */ 33 | protected static $updatedAtColumn; 34 | 35 | /** @var boolean true to disable insert/update/delete */ 36 | protected static $readOnly = false; 37 | 38 | /** @var array default values (used on object instantiation) */ 39 | protected static $defaultValues = []; 40 | 41 | /** @var mixed internally used */ 42 | protected $reflectionObject; 43 | 44 | /** @var string method used to load the object */ 45 | protected $loadMethod; 46 | 47 | /** @var mixed initial data loaded on object instantiation */ 48 | protected $loadData; 49 | 50 | /** @var array history of object fields modifications */ 51 | protected $modifiedFields = []; 52 | 53 | /** @var boolean is the object new (not persisted in db) */ 54 | protected $isNew = false; 55 | 56 | /** @var boolean to ignore pk value on update */ 57 | protected $ignoreKeyOnUpdate = true; 58 | 59 | /** @var boolean to ignore pk value on insert */ 60 | protected $ignoreKeyOnInsert = true; 61 | 62 | /** @var array the data loaded/to-load to db */ 63 | protected $data = []; 64 | 65 | /** @var array the data loaded from database after filtration */ 66 | protected $filteredData = []; 67 | 68 | /** @var mixed value of the pk (unique id of the object) */ 69 | protected $pkValue; 70 | 71 | /** @var boolean internal flag to identify whether to run the input filters or not */ 72 | protected $inSetTransaction = false; 73 | 74 | /** 75 | * ER Fine Tuning 76 | */ 77 | const FILTER_IN_PREFIX = 'filterIn'; 78 | const FILTER_OUT_PREFIX = 'filterOut'; 79 | 80 | /** 81 | * Loading options. 82 | */ 83 | const LOAD_BY_PK = 1; 84 | const LOAD_BY_ARRAY = 2; 85 | const LOAD_NEW = 3; 86 | const LOAD_EMPTY = 4; 87 | 88 | /** 89 | * Fetch options: 90 | * FIELD: only first field of first record 91 | * ONE: Fetch & return one record only. 92 | * MANY: Fetch multiple records. 93 | * NONE: Don't fetch. 94 | */ 95 | const FETCH_ONE = 1; 96 | const FETCH_MANY = 2; 97 | const FETCH_NONE = 3; 98 | const FETCH_FIELD = 4; 99 | 100 | /** 101 | * Constructor. 102 | * 103 | * @access public 104 | * @param mixed $data 105 | * @param integer $method 106 | * @return void 107 | */ 108 | public function __construct($data = null, $method = self::LOAD_EMPTY) { 109 | // store raw data 110 | $this->loadData = $data; 111 | $this->loadMethod = $method; 112 | 113 | // load our data 114 | switch ($method) { 115 | case self::LOAD_BY_PK: 116 | $this->loadByPK(); 117 | break; 118 | 119 | case self::LOAD_BY_ARRAY: 120 | $this->hydrateEmpty(); 121 | $this->loadByArray(); 122 | break; 123 | 124 | case self::LOAD_NEW: 125 | $this->hydrateEmpty(); 126 | $this->loadByArray(); 127 | $this->insert(); 128 | break; 129 | 130 | case self::LOAD_EMPTY: 131 | $this->hydrateEmpty(); 132 | break; 133 | } 134 | 135 | $this->initialise(); 136 | } 137 | 138 | /** 139 | * Give the class a connection to play with. 140 | * 141 | * @access public 142 | * @static 143 | * @param PDO $conn PDO connection instance. 144 | * @param string $database 145 | * @return void 146 | */ 147 | public static function useConnection(PDO $conn) { 148 | static::$conn = $conn; 149 | } 150 | 151 | public static function createConnection($host, $username, $password, $database, array $options = [], $port = 3306, $charset = null) { 152 | $dsn = "mysql:dbname={$database};host={$host};port={$port}"; 153 | static::$conn = new PDO($dsn, $username, $password, $options); 154 | } 155 | 156 | /** 157 | * Get our connection instance. 158 | * 159 | * @access public 160 | * @static 161 | * @return PDO 162 | */ 163 | public static function getConnection() { 164 | return static::$conn; 165 | } 166 | 167 | /** 168 | * Get load method. 169 | * 170 | * @access public 171 | * @return integer 172 | */ 173 | public function getLoadMethod() { 174 | return $this->loadMethod; 175 | } 176 | 177 | /** 178 | * Get load data (raw). 179 | * 180 | * @access public 181 | * @return array 182 | */ 183 | public function getLoadData() { 184 | return $this->loadData; 185 | } 186 | 187 | /** 188 | * Load ER by Primary Key 189 | * 190 | * @access protected 191 | * @return void 192 | */ 193 | protected function loadByPK() { 194 | // populate PK 195 | $this->pkValue = $this->loadData; 196 | 197 | // load data 198 | $this->hydrateFromDatabase(); 199 | } 200 | 201 | /** 202 | * Load ER by array hydration. 203 | * 204 | * @access protected 205 | * @return void 206 | */ 207 | protected function loadByArray() { 208 | // set our data 209 | foreach ($this->loadData AS $key => $value) { 210 | $this->data[$key] = $value; 211 | } 212 | // extract columns 213 | $this->executeOutputFilters(); 214 | } 215 | 216 | /** 217 | * Hydrate the object with null or default values. 218 | * Fetches column names using DESCRIBE. 219 | * 220 | * @access protected 221 | * @return void 222 | */ 223 | protected function hydrateEmpty() { 224 | 225 | $defaults = static::$defaultValues ? static::$defaultValues : []; 226 | 227 | foreach ($this->getColumnNames() AS $field) { 228 | $this->data[$field] = array_key_exists($field, $defaults) ? $defaults[$field] : null; 229 | } 230 | 231 | // mark object as new 232 | $this->isNew = true; 233 | } 234 | 235 | /** 236 | * Fetch the data from the database. 237 | * 238 | * @access protected 239 | * @throws Exception If the record is not found. 240 | * @return void 241 | */ 242 | protected function hydrateFromDatabase() { 243 | $sql = sprintf("SELECT * FROM `%s` WHERE `%s` = '%s';", static::getTableName(), static::getTablePk(), $this->id()); 244 | $result = static::getConnection()->query($sql); 245 | 246 | if (!$result || !$result->rowCount()) { 247 | throw new Exception(sprintf("%s record not found in database. (PK: %s)", get_called_class(), $this->id()), 2); 248 | } 249 | 250 | foreach ($result->fetch(PDO::FETCH_ASSOC) AS $key => $value) { 251 | $this->data[$key] = $value; 252 | } 253 | 254 | unset($result); 255 | 256 | // extract columns 257 | $this->executeOutputFilters(); 258 | } 259 | 260 | /** 261 | * Get the table name for this ER class. 262 | * 263 | * @access public 264 | * @static 265 | * @return string 266 | */ 267 | public static function getTableName() { 268 | return @static::$tableName ? static::$tableName : strtolower(basename(str_replace("\\", DIRECTORY_SEPARATOR, get_called_class()))); 269 | } 270 | 271 | /** 272 | * Get the PK field name for this ER class. 273 | * 274 | * @access public 275 | * @static 276 | * @return string 277 | */ 278 | public static function getTablePk() { 279 | return @static::$pkColumn ? static::$pkColumn : 'id'; 280 | } 281 | 282 | /** 283 | * Return the PK for this record. 284 | * 285 | * @access public 286 | * @return integer 287 | */ 288 | public function id() { 289 | return $this->pkValue ? $this->pkValue : ( 290 | array_key_exists(static::getTablePk(), $this->data) ? $this->data[static::getTablePk()] : null 291 | ); 292 | } 293 | 294 | /** 295 | * Check if the current record has just been created in this instance. 296 | * 297 | * @access public 298 | * @return boolean 299 | */ 300 | public function isNew() { 301 | return $this->isNew; 302 | } 303 | 304 | /** 305 | * Marks an instance as not new 306 | * 307 | * @access protected 308 | */ 309 | protected function notNew() { 310 | $this->isNew = false; 311 | } 312 | 313 | /** 314 | * Executed just before any new records are created. 315 | * Place holder for sub-classes. 316 | * 317 | * @access public 318 | * @return void 319 | */ 320 | public function preInsert(array &$data = []) { 321 | if (static::$createdAtColumn) { 322 | $data[static::$createdAtColumn] = static::setCurrentTimestampValue(); 323 | } 324 | if (static::$updatedAtColumn) { 325 | $data[static::$updatedAtColumn] = static::setCurrentTimestampValue(); 326 | } 327 | } 328 | 329 | /** 330 | * Executed just after any new records are created. 331 | * Place holder for sub-classes. 332 | * 333 | * @access public 334 | * @return void 335 | */ 336 | public function postInsert() { 337 | 338 | } 339 | 340 | /** 341 | * Executed just before any new records are created. 342 | * Place holder for sub-classes. 343 | * 344 | * @access public 345 | * @return void 346 | */ 347 | public function preDelete() { 348 | 349 | } 350 | 351 | /** 352 | * Executed just after any new records are created. 353 | * Place holder for sub-classes. 354 | * 355 | * @access public 356 | * @return void 357 | */ 358 | public function postDelete() { 359 | 360 | } 361 | 362 | /** 363 | * Executed just before any new records are created. 364 | * Place holder for sub-classes. 365 | * 366 | * @access public 367 | * @return void 368 | */ 369 | public function preUpdate(array &$data = []) { 370 | if (static::$updatedAtColumn) { 371 | $data[static::$updatedAtColumn] = static::setCurrentTimestampValue(); 372 | } 373 | } 374 | 375 | /** 376 | * Executed just after any new records are created. 377 | * Place holder for sub-classes. 378 | * 379 | * @access public 380 | * @return void 381 | */ 382 | public function postUpdate() { 383 | 384 | } 385 | 386 | /** 387 | * Executed just after the record has loaded. 388 | * Place holder for sub-classes. 389 | * 390 | * @access public 391 | * @return void 392 | */ 393 | public function initialise() { 394 | 395 | } 396 | 397 | /** 398 | * Execute these filters when loading data from the database. 399 | * 400 | * Receives array of data, returns array of data OR directly change it by reference 401 | * 402 | * @access protected 403 | * @return void 404 | */ 405 | protected function executeOutputFilters() { 406 | $r = new ReflectionClass(get_class($this)); 407 | 408 | $data = $this->data; 409 | 410 | foreach ($r->getMethods() AS $method) { 411 | if (substr($method->name, 0, strlen(self::FILTER_OUT_PREFIX)) == self::FILTER_OUT_PREFIX) { 412 | $returnedData = $this->{$method->name}($data); 413 | $data = (is_array($returnedData) ? $returnedData : []) + $data; 414 | } 415 | } 416 | 417 | $this->filteredData = (is_array($data) ? $data : []) + $this->data; 418 | } 419 | 420 | /** 421 | * Execute these filters when saving data to the database. 422 | * 423 | * @access protected 424 | * @return void 425 | */ 426 | protected function executeInputFilters($array) { 427 | $r = new ReflectionClass(get_class($this)); 428 | 429 | foreach ($r->getMethods() AS $method) { 430 | if (substr($method->name, 0, strlen(self::FILTER_IN_PREFIX)) == self::FILTER_IN_PREFIX) { 431 | $array = $this->{$method->name}($array); 432 | } 433 | } 434 | 435 | return $array; 436 | } 437 | 438 | /** 439 | * Save (insert/update) to the database. 440 | * 441 | * @access public 442 | * @return $this 443 | */ 444 | public function save() { 445 | if ($this->isNew()) { 446 | $this->insert(); 447 | } else { 448 | $this->update(); 449 | } 450 | return $this; 451 | } 452 | 453 | /** 454 | * Insert the record. 455 | * 456 | * @access protected 457 | * @throws Exception 458 | * @return void 459 | */ 460 | protected function insert() { 461 | 462 | if (static::$readOnly) { 463 | throw new Exception("Cannot write to READ ONLY tables."); 464 | } 465 | 466 | $array = $this->getRaw(); 467 | 468 | // run pre inserts 469 | if ($this->preInsert($array) === false) { 470 | return; 471 | } 472 | 473 | // input filters 474 | $array = $this->executeInputFilters($array); 475 | 476 | // remove data not relevant 477 | $array = array_intersect_key($array, array_flip($this->getColumnNames())); 478 | 479 | // to PK or not to PK 480 | if ($this->ignoreKeyOnInsert === true) { 481 | unset($array[static::getTablePk()]); 482 | } 483 | 484 | // compile statement 485 | $fieldNames = $fieldMarkers = $types = $values = []; 486 | 487 | foreach ($array AS $key => $value) { 488 | $fieldNames[] = sprintf('`%s`', $key); 489 | if (is_object($value) && $value instanceof RawSQL) { 490 | $fieldMarkers[] = (string) $value; 491 | } else { 492 | $fieldMarkers[] = '?'; 493 | $types[] = $this->parseValueType($value); 494 | $values[] = &$array[$key]; 495 | } 496 | } 497 | 498 | // build sql statement 499 | $sql = sprintf("INSERT INTO `%s` (%s) VALUES (%s)", static::getTableName(), implode(', ', $fieldNames), implode(', ', $fieldMarkers)); 500 | 501 | // prepare, bind & execute 502 | static::sql($sql, self::FETCH_NONE, array_values($values)); 503 | 504 | $lastId = static::getConnection()->lastInsertId(); 505 | 506 | // set our PK (if exists) 507 | if ($lastId) { 508 | $this->pkValue = $lastId; 509 | $this->data[static::getTablePk()] = $lastId; 510 | } 511 | 512 | // mark as old 513 | $this->isNew = false; 514 | 515 | // hydrate 516 | $this->hydrateFromDatabase($lastId); 517 | 518 | // run post inserts 519 | $this->postInsert(); 520 | } 521 | 522 | /** 523 | * Update the record. 524 | * 525 | * @access public 526 | * @throws Exception 527 | * @return void 528 | */ 529 | public function update() { 530 | 531 | if (static::$readOnly) { 532 | throw new Exception("Cannot write to READ ONLY tables."); 533 | } 534 | 535 | if ($this->isNew()) { 536 | return $this->insert(); 537 | } 538 | 539 | $pk = static::getTablePk(); 540 | $id = $this->id(); 541 | 542 | $array = $this->getRaw(); 543 | 544 | //preupdate 545 | if ($this->preUpdate($array) === false) { 546 | return; 547 | } 548 | 549 | // input filters 550 | $array = $this->executeInputFilters($array); 551 | 552 | // remove data not relevant 553 | $array = array_intersect_key($array, array_flip($this->getColumnNames())); 554 | 555 | // to PK or not to PK 556 | if ($this->ignoreKeyOnUpdate === true) { 557 | unset($array[$pk]); 558 | } 559 | 560 | // compile statement 561 | $fields = $types = $values = []; 562 | 563 | foreach ($array AS $key => $value) { 564 | if (is_object($value) && $value instanceof RawSQL) { 565 | $fields[] = sprintf('`%s` = %s', $key, (string) $value); 566 | } else { 567 | $fields[] = sprintf('`%s` = ?', $key); 568 | $types[] = $this->parseValueType($value); 569 | $values[] = &$array[$key]; 570 | } 571 | } 572 | 573 | // where 574 | $types[] = 'i'; 575 | $values[] = &$id; 576 | 577 | // build sql statement 578 | $sql = sprintf("UPDATE `%s` SET %s WHERE `%s` = ?", static::getTableName(), implode(', ', $fields), $pk); 579 | 580 | // prepare, bind & execute 581 | static::sql($sql, self::FETCH_NONE, $values); 582 | 583 | // reset modified list 584 | $this->modifiedFields = []; 585 | 586 | $this->hydrateFromDatabase(); 587 | 588 | $this->postUpdate(); 589 | } 590 | 591 | /** 592 | * Delete the record from the database. 593 | * 594 | * @access public 595 | * @return void 596 | */ 597 | public function delete() { 598 | 599 | if (static::$readOnly) { 600 | throw new Exception("Cannot write to READ ONLY tables."); 601 | } 602 | 603 | if ($this->isNew()) { 604 | throw new Exception('Unable to delete object, record is new (and therefore doesn\'t exist in the database).'); 605 | } 606 | 607 | if ($this->preDelete() === false) { 608 | return; 609 | } 610 | 611 | // build sql statement 612 | $sql = sprintf("DELETE FROM `%s` WHERE `%s` = ?", static::getTableName(), static::getTablePk()); 613 | $id = $this->id(); 614 | 615 | // prepare, bind & execute 616 | static::sql($sql, self::FETCH_NONE, [$id]); 617 | 618 | $this->postDelete(); 619 | } 620 | 621 | /** 622 | * Fetch column names directly from MySQL. 623 | * 624 | * @access public 625 | * @return array 626 | */ 627 | public static function getColumnNames() { 628 | $conn = static::getConnection(); 629 | $result = $conn->query(sprintf("DESCRIBE %s;", static::getTableName())); 630 | 631 | if ($result === false) { 632 | throw new Exception(sprintf('Unable to fetch the column names. %s.', $conn->errorCode())); 633 | } 634 | 635 | $ret = []; 636 | 637 | while ($row = $result->fetch(PDO::FETCH_ASSOC)) { 638 | $ret[] = $row['Field']; 639 | } 640 | 641 | $result->closeCursor(); 642 | 643 | return $ret; 644 | } 645 | 646 | /** 647 | * Parse a value type. 648 | * 649 | * @access protected 650 | * @param mixed $value 651 | * @return string 652 | */ 653 | protected static function parseValueType($value) { 654 | // ints 655 | if (is_int($value)) { 656 | return 'i'; 657 | } 658 | 659 | // doubles 660 | if (is_double($value)) { 661 | return 'd'; 662 | } 663 | 664 | return 's'; 665 | } 666 | 667 | /** 668 | * Revert the object by reloading our data. 669 | * 670 | * @access public 671 | * @param boolean $return If true the current object won't be reverted, it will return a new object via cloning. 672 | * @return void | clone 673 | */ 674 | public function revert($return = false) { 675 | if ($return) { 676 | $ret = clone $this; 677 | $ret->revert(); 678 | 679 | return $ret; 680 | } 681 | 682 | $this->hydrateFromDatabase(); 683 | } 684 | 685 | /** 686 | * Get a value for a particular field or all values. 687 | * 688 | * @access public 689 | * @param string $fieldName If false (default), the entire record will be returned as an array. 690 | * @return array | string 691 | */ 692 | public function get($fieldName = false) { 693 | // return all data 694 | if ($fieldName === false) { 695 | return $this->filteredData; 696 | } 697 | 698 | return array_key_exists($fieldName, $this->filteredData) ? $this->filteredData[$fieldName] : ( 699 | array_key_exists($fieldName, $this->data) ? $this->data[$fieldName] : $this->{$fieldName} 700 | ); 701 | } 702 | 703 | public function getRaw($fieldName = false) { 704 | // return all data 705 | if ($fieldName === false) { 706 | return $this->data; 707 | } 708 | 709 | return array_key_exists($fieldName, $this->data) ? $this->data[$fieldName] : $this->{$fieldName}; 710 | } 711 | 712 | /** 713 | * Set a new value for a particular field. 714 | * 715 | * @access public 716 | * @param string|array $fieldName list of key=>values OR a key name 717 | * @param string $newValue if $dataMapOrFieldName is a key name, this will be the value 718 | * @return void 719 | */ 720 | public function set($fieldName, $newValue = null) { 721 | if (is_array($fieldName)) { 722 | $this->inSetTransaction = true; 723 | foreach ($fieldName as $key => $value) { 724 | $this->set($key, $value); 725 | } 726 | $this->data = $this->executeInputFilters($this->data); 727 | $this->executeOutputFilters(); 728 | $this->inSetTransaction = false; 729 | } elseif (is_scalar($fieldName)) { 730 | // if changed, mark object as modified 731 | if ($this->get($fieldName) != $newValue) { 732 | $this->modifiedFields($fieldName, $newValue); 733 | } 734 | $this->data[$fieldName] = $newValue; 735 | if (!$this->inSetTransaction) { 736 | $this->data = $this->executeInputFilters($this->data); 737 | $this->executeOutputFilters(); 738 | } 739 | } 740 | return $this; 741 | } 742 | 743 | /** 744 | * Check if our record has been modified since boot up. 745 | * This is only available if you use set() to change the object. 746 | * 747 | * @access public 748 | * @return array | false 749 | */ 750 | public function isModified() { 751 | return count($this->modifiedFields) > 0; 752 | ; 753 | } 754 | 755 | /** 756 | * Modification history of all fields, or null if nothing is changed since load 757 | * 758 | * @access public 759 | * @return null|array 760 | */ 761 | public function modified() { 762 | return $this->isModified() ? $this->modifiedFields : null; 763 | } 764 | 765 | /** 766 | * Mark a field as modified & add the change to our history. 767 | * 768 | * @access protected 769 | * @param string $fieldName 770 | * @param string $newValue 771 | * @return void 772 | */ 773 | protected function modifiedFields($fieldName, $newValue) { 774 | // add modified field to a list 775 | if (!isset($this->modifiedFields[$fieldName])) { 776 | $this->modifiedFields[$fieldName] = $newValue; 777 | 778 | return; 779 | } 780 | 781 | // already modified, initiate a numerical array 782 | if (!is_array($this->modifiedFields[$fieldName])) { 783 | $this->modifiedFields[$fieldName] = [$this->modifiedFields[$fieldName]]; 784 | } 785 | 786 | // add new change to array 787 | $this->modifiedFields[$fieldName][] = $newValue; 788 | } 789 | 790 | /** 791 | * Execute an SQL statement & get all records as hydrated objects. 792 | * 793 | * @access public 794 | * @param string $sql 795 | * @param integer $return 796 | * @param array|null $params parameters to bind 797 | * @return mixed 798 | */ 799 | public static function sql($sql, $return = self::FETCH_MANY, array $params = null) { 800 | // shortcuts 801 | $sql = str_replace([':table', ':pk'], [static::getTableName(), static::getTablePk()], $sql); 802 | 803 | // prepare 804 | $stmt = static::getConnection()->prepare($sql); 805 | if (!$stmt || !$stmt->execute($params)) { 806 | throw new Exception(sprintf('Unable to execute SQL statement. %s', static::getConnection()->errorCode())); 807 | } 808 | 809 | if ($return === self::FETCH_NONE) { 810 | return; 811 | } 812 | 813 | $ret = []; 814 | 815 | while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { 816 | $obj = $return == self::FETCH_FIELD ? $row : call_user_func_array([get_called_class(), 'hydrate'], [$row, false]); 817 | $ret[] = $obj; 818 | } 819 | 820 | $stmt->closeCursor(); 821 | 822 | // return one if requested 823 | if ($return === self::FETCH_ONE || $return === self::FETCH_FIELD) { 824 | $ret = isset($ret[0]) ? $ret[0] : null; 825 | } 826 | 827 | if ($return === self::FETCH_FIELD && $ret) { 828 | $data = $ret instanceof DataModel ? $ret->get() : $ret; 829 | $ret = array_values($ret)[0]; 830 | } 831 | 832 | return $ret; 833 | } 834 | 835 | /** 836 | * Execute a Count SQL statement & return the number. 837 | * 838 | * @access public 839 | * @param string $sql 840 | * @param integer $return 841 | * @return mixed 842 | */ 843 | public static function count($sql = "SELECT count(*) FROM :table") { 844 | $count = (int) (static::sql($sql, self::FETCH_FIELD)); 845 | 846 | return $count > 0 ? $count : 0; 847 | } 848 | 849 | /** 850 | * Truncate the table. 851 | * All data will be removed permanently. 852 | * 853 | * @access public 854 | * @static 855 | * @return void 856 | */ 857 | public static function truncate() { 858 | 859 | if (static::$readOnly) { 860 | throw new Exception("Cannot write to READ ONLY tables."); 861 | } 862 | 863 | static::sql('TRUNCATE :table', self::FETCH_NONE); 864 | } 865 | 866 | /** 867 | * Get all records. 868 | * 869 | * @access public 870 | * @return array 871 | */ 872 | public static function all() { 873 | return static::sql("SELECT * FROM :table"); 874 | } 875 | 876 | /** 877 | * Retrieve a record by its primary key (PK). 878 | * 879 | * @access public 880 | * @param integer|string $pk 881 | * @return mixed|static|$this|DataModel|static|object 882 | */ 883 | public static function retrieveByPK($pk) { 884 | if (!is_numeric($pk) && !is_string($pk)) { 885 | throw new InvalidArgumentException('The PK must be an integer or string.'); 886 | } 887 | 888 | return new static($pk, self::LOAD_BY_PK); 889 | } 890 | 891 | /** 892 | * Load an ER object by array. 893 | * This skips reloading the data from the database. 894 | * 895 | * @access public 896 | * @param array $data 897 | * @param boolean $asNew 898 | * @return object 899 | */ 900 | public static function hydrate(array $data, $asNew = true) { 901 | $reflectionObj = new ReflectionClass(get_called_class()); 902 | 903 | $instance = $reflectionObj->newInstanceArgs([$data, self::LOAD_BY_ARRAY]); 904 | 905 | if (!$asNew) { 906 | $instance->notNew(); 907 | } 908 | 909 | return $instance; 910 | } 911 | 912 | /** 913 | * Retrieve a record by a particular column name using the retrieveBy prefix. 914 | * e.g. 915 | * 1) Foo::retrieveByTitle('Hello World') is equal to Foo::retrieveByField('title', 'Hello World'); 916 | * 2) Foo::retrieveByIsPublic(true) is equal to Foo::retrieveByField('is_public', true); 917 | * 918 | * @access public 919 | * @static 920 | * @param string $name 921 | * @param array $args 922 | * @return mixed 923 | */ 924 | public static function __callStatic($name, $args) { 925 | $class = get_called_class(); 926 | 927 | if (substr($name, 0, 10) == 'retrieveBy') { 928 | // prepend field name to args 929 | $field = strtolower(preg_replace('/\B([A-Z])/', '_${1}', substr($name, 10))); 930 | array_unshift($args, $field); 931 | 932 | return call_user_func_array([$class, 'retrieveByField'], $args); 933 | } 934 | 935 | throw new Exception(sprintf('There is no static method named "%s" in the class "%s".', $name, $class)); 936 | } 937 | 938 | /** 939 | * Retrieve a record by a particular column name. 940 | * 941 | * @access public 942 | * @static 943 | * @param string $field 944 | * @param mixed $value 945 | * @param integer $return 946 | * @return mixed|static|$this|DataModel|static 947 | */ 948 | public static function retrieveByField($field, $value, $return = self::FETCH_MANY) { 949 | if (!is_string($field)) 950 | throw new InvalidArgumentException('The field name must be a string.'); 951 | 952 | // build our query 953 | $operator = (strpos($value, '%') === false) ? '=' : 'LIKE'; 954 | 955 | $sql = sprintf("SELECT * FROM :table WHERE %s %s '%s'", $field, $operator, $value); 956 | 957 | if ($return === self::FETCH_ONE) { 958 | $sql .= ' LIMIT 0,1'; 959 | } 960 | 961 | // fetch our records 962 | return static::sql($sql, $return); 963 | } 964 | 965 | protected static function setCurrentTimestampValue() { 966 | return RawSQL::make('CURRENT_TIMESTAMP'); 967 | } 968 | 969 | public function __get($name) { 970 | return $this->get($name); 971 | } 972 | 973 | public function __set($name, $value) { 974 | if (array_key_exists($name, $this->data)) { 975 | $this->set($name, $value); 976 | return; 977 | } 978 | throw new Exception(sprintf("Can not set property %s", $name)); 979 | } 980 | 981 | } 982 | -------------------------------------------------------------------------------- /tests/codeCoverage/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); --------------------------------------------------------------------------------