=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
+"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
+h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l ";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
+q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=" ";
+if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="
";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
+(function(){var g=s.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
+function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
+{},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
+"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
+d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
+a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
+1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"+d+">"},F={option:[1,""," "],legend:[1,""," "],thead:[1,""],tr:[2,""],td:[3,""],col:[2,""],area:[1,""," "],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
+c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
+wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
+prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
+this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
+return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
+""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
+return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
+""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
+c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
+c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
+function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
+Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
+"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
+a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
+a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/
+
+
+
+ $methods): $link = $route->uri(array('class' => $class)) ?>
+
+
+
+
diff --git a/includes/kohana/modules/userguide/views/userguide/error.php b/includes/kohana/modules/userguide/views/userguide/error.php
new file mode 100644
index 0000000..1f7b97b
--- /dev/null
+++ b/includes/kohana/modules/userguide/views/userguide/error.php
@@ -0,0 +1,3 @@
+Kodoc -
+
+
\ No newline at end of file
diff --git a/includes/kohana/modules/userguide/views/userguide/examples/error.php b/includes/kohana/modules/userguide/views/userguide/examples/error.php
new file mode 100644
index 0000000..81548ce
--- /dev/null
+++ b/includes/kohana/modules/userguide/views/userguide/examples/error.php
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/includes/kohana/modules/userguide/views/userguide/examples/hello_world_error.php b/includes/kohana/modules/userguide/views/userguide/examples/hello_world_error.php
new file mode 100644
index 0000000..d5dcf44
--- /dev/null
+++ b/includes/kohana/modules/userguide/views/userguide/examples/hello_world_error.php
@@ -0,0 +1,696 @@
+
+
+
+
Kohana_View_Exception [ 0 ]: The requested view site could not be found
+
+
SYSPATH/classes/kohana/view.php [ 215 ]
+
210 */
+
+211 public function set_filename($file)
+212 {
+213 if (($path = Kohana::find_file('views', $file)) === FALSE)
+214 {
+215 throw new Kohana_View_Exception('The requested view :file could not be found', array(
+216 ':file' => $file,
+
+217 ));
+218 }
+219
+220 // Store the file path locally
+
+
+
+
+
+ SYSPATH/classes/kohana/view.php [ 115 ]
+
+ »
+ Kohana_View->set_filename(arguments )
+
+
+
+
+
+ file
+ string (4) "site"
+
+
+
+
+ 110 */
+111 public function __construct($file = NULL, array $data = NULL)
+112 {
+113 if ($file !== NULL)
+114 {
+115 $this->set_filename($file);
+
+116 }
+117
+118 if ( $data !== NULL)
+119 {
+120 // Add the values to the current data
+
+
+
+
+
+
+ SYSPATH/classes/kohana/view.php [ 26 ]
+
+ »
+ Kohana_View->__construct(arguments )
+
+
+
+
+
+ file
+ string (4) "site"
+
+
+
+ data
+ NULL
+
+
+
+ 21 * @param array array of values
+
+22 * @return View
+23 */
+24 public static function factory($file = NULL, array $data = NULL)
+25 {
+26 return new View($file, $data);
+27 }
+
+28
+29 /**
+30 * Captures the output that is generated when a view is included.
+31 * The view data will be extracted to make local variables. This method
+
+
+
+
+
+
+ SYSPATH/classes/kohana/controller/template.php [ 32 ]
+
+ »
+ Kohana_View::factory(arguments )
+
+
+
+
+
+ file
+ string (4) "site"
+
+
+
+
+ 27 public function before()
+28 {
+29 if ($this->auto_render === TRUE)
+30 {
+31 // Load the template
+
+32 $this->template = View::factory($this->template);
+33 }
+34 }
+35
+36 /**
+37 * Assigns the template as the request response.
+
+
+
+
+
+
+ {PHP internal call}
+
+ »
+ Kohana_Controller_Template->before()
+
+
+
+
+
+
+ SYSPATH/classes/kohana/request.php [ 840 ]
+
+ »
+ ReflectionMethod->invoke(arguments )
+
+
+
+
+
+ object
+ object Controller_Hello(3) {
+ public template => string (4) "site"
+ public auto_render => bool TRUE
+ public request => object Request(9) {
+ public route => object Route(4) {
+ protected _uri => string (32) "(<controller>(/<action>(/<id>)))"
+ protected _regex => array (0)
+ protected _defaults => array (2) (
+ "controller" => string (7) "welcome"
+ "action" => string (5) "index"
+ )
+
+ protected _route_regex => string (87) "#^(?:(?P<controller>[^/.,;?]++)(?:/(?P<action>[^/.,;?]++)(?:/(?P<id>[^/.,;?]++))?)?)?$#"
+ }
+ public status => integer 500
+ public response => string (0) ""
+ public headers => array (1) (
+ "Content-Type" => string (24) "text/html; charset=utf-8"
+ )
+
+ public directory => string (0) ""
+ public controller => string (5) "hello"
+ public action => string (5) "index"
+ public uri => string (5) "hello"
+ protected _params => array (0)
+ }
+
+}
+
+
+
+ 835
+836 // Create a new instance of the controller
+837 $controller = $class->newInstance($this);
+
+838
+839 // Execute the "before action" method
+840 $class->getMethod('before')->invoke($controller);
+841
+842 // Determine the action to use
+843 $action = empty($this->action) ? Route::$default_action : $this->action;
+
+844
+845 // Execute the main action with the parameters
+
+
+
+
+
+ APPPATH/bootstrap.php [ 76 ]
+
+
+ »
+ Kohana_Request->execute()
+
+ 71 /**
+72 * Execute the main request. A source of the URI can be passed, eg: $_SERVER['PATH_INFO'].
+73 * If no source is specified, the URI will be automatically detected.
+
+74 */
+75 echo Request::instance()
+76 ->execute()
+77 ->send_headers()
+78 ->response;
+
+
+
+
+
+
+ DOCROOT/index.php [ 106 ]
+
+ »
+ require(arguments )
+
+
+
+
+
+ 0
+ string (49) "/var/www/kohana/testing/application/bootstrap.php"
+
+
+
+
+ 101 // Load empty core extension
+102 require SYSPATH.'classes/kohana'.EXT;
+103 }
+104
+105 // Bootstrap the application
+
+106 require APPPATH.'bootstrap'.EXT;
+
+
+
+
+
+
+
+
+
+
+
+ DOCROOT/index.php
+
+
+ SYSPATH/base.php
+
+
+
+ SYSPATH/classes/kohana/core.php
+
+
+ SYSPATH/classes/kohana.php
+
+
+ APPPATH/bootstrap.php
+
+
+
+ SYSPATH/classes/profiler.php
+
+
+ SYSPATH/classes/kohana/profiler.php
+
+
+
+ SYSPATH/classes/kohana/log.php
+
+
+ SYSPATH/classes/kohana/config.php
+
+
+ SYSPATH/classes/kohana/log/file.php
+
+
+
+ SYSPATH/classes/kohana/log/writer.php
+
+
+ SYSPATH/classes/kohana/config/file.php
+
+
+
+ SYSPATH/classes/kohana/config/reader.php
+
+
+ MODPATH/codebench/init.php
+
+
+ SYSPATH/classes/route.php
+
+
+
+ SYSPATH/classes/kohana/route.php
+
+
+ /var/www/kohana/userguide/init.php
+
+
+
+ SYSPATH/classes/request.php
+
+
+ SYSPATH/classes/kohana/request.php
+
+
+ APPPATH/classes/controller/hello.php
+
+
+
+ SYSPATH/classes/controller/template.php
+
+
+ SYSPATH/classes/kohana/controller/template.php
+
+
+
+ SYSPATH/classes/controller.php
+
+
+ SYSPATH/classes/kohana/controller.php
+
+
+ SYSPATH/classes/view.php
+
+
+
+ SYSPATH/classes/kohana/view.php
+
+
+ SYSPATH/classes/kohana/view/exception.php
+
+
+
+ SYSPATH/classes/kohana/exception.php
+
+
+ SYSPATH/classes/i18n.php
+
+
+ SYSPATH/classes/kohana/i18n.php
+
+
+
+ SYSPATH/views/kohana/error.php
+
+
+
+
+
+
+
+
+ zip
+
+
+ xmlwriter
+
+
+
+ libxml
+
+
+ xml
+
+
+ wddx
+
+
+
+ tokenizer
+
+
+ sysvshm
+
+
+
+ sysvsem
+
+
+ sysvmsg
+
+
+ session
+
+
+
+ SimpleXML
+
+
+ sockets
+
+
+
+ soap
+
+
+ SPL
+
+
+ shmop
+
+
+
+ standard
+
+
+ Reflection
+
+
+
+ posix
+
+
+ mime_magic
+
+
+ mbstring
+
+
+
+ json
+
+
+ iconv
+
+
+
+ hash
+
+
+ gettext
+
+
+ ftp
+
+
+
+ filter
+
+
+ exif
+
+
+
+ dom
+
+
+ dba
+
+
+ date
+
+
+
+ ctype
+
+
+ calendar
+
+
+
+ bz2
+
+
+ bcmath
+
+
+ zlib
+
+
+
+ pcre
+
+
+ openssl
+
+
+
+ xmlreader
+
+
+ apache2handler
+
+
+ curl
+
+
+
+ PDO
+
+
+
+
+
+
+
+
+ HTTP_HOST
+ string (9) "localhost"
+
+
+
+ HTTP_USER_AGENT
+ string (105) "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.0.14) Gecko/2009090216 Ubuntu/9.04 (jaunty) Firefox/3.0.14"
+
+
+ HTTP_ACCEPT
+ string (63) "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
+
+
+
+ HTTP_ACCEPT_LANGUAGE
+ string (14) "en-gb,en;q=0.5"
+
+
+
+ HTTP_ACCEPT_ENCODING
+ string (12) "gzip,deflate"
+
+
+ HTTP_ACCEPT_CHARSET
+ string (30) "ISO-8859-1,utf-8;q=0.7,*;q=0.7"
+
+
+
+ HTTP_KEEP_ALIVE
+ string (3) "300"
+
+
+
+ HTTP_CONNECTION
+ string (10) "keep-alive"
+
+
+ PATH
+ string (28) "/usr/local/bin:/usr/bin:/bin"
+
+
+
+ SERVER_SIGNATURE
+ string (110) "<address>Apache/2.2.11 (Ubuntu) PHP/5.2.6-3ubuntu4.2 with Suhosin-Patch Server at localhost Port 80</address>
+"
+
+
+
+ SERVER_SOFTWARE
+ string (62) "Apache/2.2.11 (Ubuntu) PHP/5.2.6-3ubuntu4.2 with Suhosin-Patch"
+
+
+
+ SERVER_NAME
+ string (9) "localhost"
+
+
+ SERVER_ADDR
+ string (3) "::1"
+
+
+
+ SERVER_PORT
+ string (2) "80"
+
+
+
+ REMOTE_ADDR
+ string (3) "::1"
+
+
+ DOCUMENT_ROOT
+ string (8) "/var/www"
+
+
+
+ SERVER_ADMIN
+ string (19) "webmaster@localhost"
+
+
+
+ SCRIPT_FILENAME
+ string (33) "/var/www/kohana/testing/index.php"
+
+
+ REMOTE_PORT
+ string (5) "39409"
+
+
+
+ GATEWAY_INTERFACE
+ string (7) "CGI/1.1"
+
+
+
+ SERVER_PROTOCOL
+ string (8) "HTTP/1.1"
+
+
+ REQUEST_METHOD
+ string (3) "GET"
+
+
+
+ QUERY_STRING
+ string (0) ""
+
+
+
+ REQUEST_URI
+ string (31) "/kohana/testing/index.php/hello"
+
+
+ SCRIPT_NAME
+ string (25) "/kohana/testing/index.php"
+
+
+
+ PATH_INFO
+ string (6) "/hello"
+
+
+
+ PATH_TRANSLATED
+ string (14) "/var/www/hello"
+
+
+ PHP_SELF
+ string (31) "/kohana/testing/index.php/hello"
+
+
+
+ REQUEST_TIME
+ integer 1254245682
+
+
+ argv
+
+ array (0)
+
+
+ argc
+ integer 0
+
+
+
+
+
+
diff --git a/includes/kohana/modules/userguide/views/userguide/index.php b/includes/kohana/modules/userguide/views/userguide/index.php
new file mode 100644
index 0000000..51edc15
--- /dev/null
+++ b/includes/kohana/modules/userguide/views/userguide/index.php
@@ -0,0 +1,20 @@
+User Guide
+
+The following modules have userguide pages:
+
+
+
+ $options): ?>
+
+
+ uri(array('module' => $url)), $options['name']) ?> -
+
+
+
+
+
+
+
+ I couldn't find any modules with userguide pages.
+
+
\ No newline at end of file
diff --git a/includes/kohana/modules/userguide/views/userguide/menu.php b/includes/kohana/modules/userguide/views/userguide/menu.php
new file mode 100644
index 0000000..e813608
--- /dev/null
+++ b/includes/kohana/modules/userguide/views/userguide/menu.php
@@ -0,0 +1,17 @@
+Modules
+
+
+
+
+ $options): ?>
+
+ uri(array('module' => $url)), $options['name']) ?>
+
+
+
+
+
+
+ No modules.
+
+
\ No newline at end of file
diff --git a/includes/kohana/modules/userguide/views/userguide/page-toc.php b/includes/kohana/modules/userguide/views/userguide/page-toc.php
new file mode 100644
index 0000000..eacdba5
--- /dev/null
+++ b/includes/kohana/modules/userguide/views/userguide/page-toc.php
@@ -0,0 +1,10 @@
+
+
+
+ 1): ?>
+
+
+
+
+
+
diff --git a/includes/kohana/modules/userguide/views/userguide/template.php b/includes/kohana/modules/userguide/views/userguide/template.php
new file mode 100644
index 0000000..2ab8c24
--- /dev/null
+++ b/includes/kohana/modules/userguide/views/userguide/template.php
@@ -0,0 +1,108 @@
+
+
+
+
+
+ | Kohana
+
+ $media) echo HTML::style($style, array('media' => $media), TRUE), "\n" ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $title): ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/includes/kohana/system/base.php b/includes/kohana/system/base.php
new file mode 100644
index 0000000..c39bf4e
--- /dev/null
+++ b/includes/kohana/system/base.php
@@ -0,0 +1,50 @@
+ $username));
+ *
+ * [!!] The target language is defined by [I18n::$lang]. The default source
+ * language is defined by [I18n::$source].
+ *
+ * @uses I18n::get
+ * @param string text to translate
+ * @param array values to replace in the translated text
+ * @param string source language
+ * @return string
+ */
+function __($string, array $values = NULL, $source = NULL)
+{
+ if ( ! $source)
+ {
+ // Use the default source language
+ $source = I18n::$source;
+ }
+
+ if ($source !== I18n::$lang)
+ {
+ // The message and target languages are different
+ // Get the translation for this message
+ $string = I18n::get($string);
+ }
+
+ return empty($values) ? $string : strtr($string, $values);
+}
diff --git a/includes/kohana/system/classes/arr.php b/includes/kohana/system/classes/arr.php
new file mode 100644
index 0000000..a279831
--- /dev/null
+++ b/includes/kohana/system/classes/arr.php
@@ -0,0 +1,3 @@
+ 'john.doe'));
+ *
+ * // Returns FALSE
+ * Arr::is_assoc('foo', 'bar');
+ *
+ * @param array array to check
+ * @return boolean
+ */
+ public static function is_assoc(array $array)
+ {
+ // Keys of the array
+ $keys = array_keys($array);
+
+ // If the array keys of the keys match the keys, then the array must
+ // not be associative (e.g. the keys array looked like {0:0, 1:1...}).
+ return array_keys($keys) !== $keys;
+ }
+
+ /**
+ * Test if a value is an array with an additional check for array-like objects.
+ *
+ * // Returns TRUE
+ * Arr::is_array(array());
+ * Arr::is_array(new ArrayObject);
+ *
+ * // Returns FALSE
+ * Arr::is_array(FALSE);
+ * Arr::is_array('not an array!');
+ * Arr::is_array(Database::instance());
+ *
+ * @param mixed value to check
+ * @return boolean
+ */
+ public static function is_array($value)
+ {
+ if (is_array($value))
+ {
+ // Definitely an array
+ return TRUE;
+ }
+ else
+ {
+ // Possibly a Traversable object, functionally the same as an array
+ return (is_object($value) AND $value instanceof Traversable);
+ }
+ }
+
+ /**
+ * Gets a value from an array using a dot separated path.
+ *
+ * // Get the value of $array['foo']['bar']
+ * $value = Arr::path($array, 'foo.bar');
+ *
+ * Using a wildcard "*" will search intermediate arrays and return an array.
+ *
+ * // Get the values of "color" in theme
+ * $colors = Arr::path($array, 'theme.*.color');
+ *
+ * // Using an array of keys
+ * $colors = Arr::path($array, array('theme', '*', 'color'));
+ *
+ * @param array array to search
+ * @param mixed key path string (delimiter separated) or array of keys
+ * @param mixed default value if the path is not set
+ * @param string key path delimiter
+ * @return mixed
+ */
+ public static function path($array, $path, $default = NULL, $delimiter = NULL)
+ {
+ if ( ! Arr::is_array($array))
+ {
+ // This is not an array!
+ return $default;
+ }
+
+ if (is_array($path))
+ {
+ // The path has already been separated into keys
+ $keys = $path;
+ }
+ else
+ {
+ if (array_key_exists($path, $array))
+ {
+ // No need to do extra processing
+ return $array[$path];
+ }
+
+ if ($delimiter === NULL)
+ {
+ // Use the default delimiter
+ $delimiter = Arr::$delimiter;
+ }
+
+ // Remove starting delimiters and spaces
+ $path = ltrim($path, "{$delimiter} ");
+
+ // Remove ending delimiters, spaces, and wildcards
+ $path = rtrim($path, "{$delimiter} *");
+
+ // Split the keys by delimiter
+ $keys = explode($delimiter, $path);
+ }
+
+ do
+ {
+ $key = array_shift($keys);
+
+ if (ctype_digit($key))
+ {
+ // Make the key an integer
+ $key = (int) $key;
+ }
+
+ if (isset($array[$key]))
+ {
+ if ($keys)
+ {
+ if (Arr::is_array($array[$key]))
+ {
+ // Dig down into the next part of the path
+ $array = $array[$key];
+ }
+ else
+ {
+ // Unable to dig deeper
+ break;
+ }
+ }
+ else
+ {
+ // Found the path requested
+ return $array[$key];
+ }
+ }
+ elseif ($key === '*')
+ {
+ // Handle wildcards
+
+ $values = array();
+ foreach ($array as $arr)
+ {
+ if ($value = Arr::path($arr, implode('.', $keys)))
+ {
+ $values[] = $value;
+ }
+ }
+
+ if ($values)
+ {
+ // Found the values requested
+ return $values;
+ }
+ else
+ {
+ // Unable to dig deeper
+ break;
+ }
+ }
+ else
+ {
+ // Unable to dig deeper
+ break;
+ }
+ }
+ while ($keys);
+
+ // Unable to find the value requested
+ return $default;
+ }
+
+ /**
+ * Fill an array with a range of numbers.
+ *
+ * // Fill an array with values 5, 10, 15, 20
+ * $values = Arr::range(5, 20);
+ *
+ * @param integer stepping
+ * @param integer ending number
+ * @return array
+ */
+ public static function range($step = 10, $max = 100)
+ {
+ if ($step < 1)
+ return array();
+
+ $array = array();
+ for ($i = $step; $i <= $max; $i += $step)
+ {
+ $array[$i] = $i;
+ }
+
+ return $array;
+ }
+
+ /**
+ * Retrieve a single key from an array. If the key does not exist in the
+ * array, the default value will be returned instead.
+ *
+ * // Get the value "username" from $_POST, if it exists
+ * $username = Arr::get($_POST, 'username');
+ *
+ * // Get the value "sorting" from $_GET, if it exists
+ * $sorting = Arr::get($_GET, 'sorting');
+ *
+ * @param array array to extract from
+ * @param string key name
+ * @param mixed default value
+ * @return mixed
+ */
+ public static function get($array, $key, $default = NULL)
+ {
+ return isset($array[$key]) ? $array[$key] : $default;
+ }
+
+ /**
+ * Retrieves multiple keys from an array. If the key does not exist in the
+ * array, the default value will be added instead.
+ *
+ * // Get the values "username", "password" from $_POST
+ * $auth = Arr::extract($_POST, array('username', 'password'));
+ *
+ * @param array array to extract keys from
+ * @param array list of key names
+ * @param mixed default value
+ * @return array
+ */
+ public static function extract($array, array $keys, $default = NULL)
+ {
+ $found = array();
+ foreach ($keys as $key)
+ {
+ $found[$key] = isset($array[$key]) ? $array[$key] : $default;
+ }
+
+ return $found;
+ }
+
+ /**
+ * Retrieves muliple single-key values from a list of arrays.
+ *
+ * // Get all of the "id" values from a result
+ * $ids = Arr::pluck($result, 'id');
+ *
+ * [!!] A list of arrays is an array that contains arrays, eg: array(array $a, array $b, array $c, ...)
+ *
+ * @param array list of arrays to check
+ * @param string key to pluck
+ * @return array
+ */
+ public static function pluck($array, $key)
+ {
+ $values = array();
+
+ foreach ($array as $row)
+ {
+ if (isset($row[$key]))
+ {
+ // Found a value in this row
+ $values[] = $row[$key];
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * Binary search algorithm.
+ *
+ * @deprecated Use [array_search](http://php.net/array_search) instead
+ *
+ * @param mixed the value to search for
+ * @param array an array of values to search in
+ * @param boolean sort the array now
+ * @return integer the index of the match
+ * @return FALSE no matching index found
+ */
+ public static function binary_search($needle, $haystack, $sort = FALSE)
+ {
+ return array_search($needle, $haystack);
+ }
+
+ /**
+ * Adds a value to the beginning of an associative array.
+ *
+ * // Add an empty value to the start of a select list
+ * Arr::unshift_assoc($array, 'none', 'Select a value');
+ *
+ * @param array array to modify
+ * @param string array key name
+ * @param mixed array value
+ * @return array
+ */
+ public static function unshift( array & $array, $key, $val)
+ {
+ $array = array_reverse($array, TRUE);
+ $array[$key] = $val;
+ $array = array_reverse($array, TRUE);
+
+ return $array;
+ }
+
+ /**
+ * Recursive version of [array_map](http://php.net/array_map), applies the
+ * same callback to all elements in an array, including sub-arrays.
+ *
+ * // Apply "strip_tags" to every element in the array
+ * $array = Arr::map('strip_tags', $array);
+ *
+ * [!!] Unlike `array_map`, this method requires a callback and will only map
+ * a single array.
+ *
+ * @param mixed callback applied to every element in the array
+ * @param array array to map
+ * @return array
+ */
+ public static function map($callback, $array)
+ {
+ foreach ($array as $key => $val)
+ {
+ if (is_array($val))
+ {
+ $array[$key] = Arr::map($callback, $val);
+ }
+ else
+ {
+ $array[$key] = call_user_func($callback, $val);
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * Merges one or more arrays recursively and preserves all keys.
+ * Note that this does not work the same as [array_merge_recursive](http://php.net/array_merge_recursive)!
+ *
+ * $john = array('name' => 'john', 'children' => array('fred', 'paul', 'sally', 'jane'));
+ * $mary = array('name' => 'mary', 'children' => array('jane'));
+ *
+ * // John and Mary are married, merge them together
+ * $john = Arr::merge($john, $mary);
+ *
+ * // The output of $john will now be:
+ * array('name' => 'mary', 'children' => array('fred', 'paul', 'sally', 'jane'))
+ *
+ * @param array initial array
+ * @param array array to merge
+ * @param array ...
+ * @return array
+ */
+ public static function merge(array $a1, array $a2)
+ {
+ $result = array();
+ for ($i = 0, $total = func_num_args(); $i < $total; $i++)
+ {
+ // Get the next array
+ $arr = func_get_arg($i);
+
+ // Is the array associative?
+ $assoc = Arr::is_assoc($arr);
+
+ foreach ($arr as $key => $val)
+ {
+ if (isset($result[$key]))
+ {
+ if (is_array($val) AND is_array($result[$key]))
+ {
+ if (Arr::is_assoc($val))
+ {
+ // Associative arrays are merged recursively
+ $result[$key] = Arr::merge($result[$key], $val);
+ }
+ else
+ {
+ // Find the values that are not already present
+ $diff = array_diff($val, $result[$key]);
+
+ // Indexed arrays are merged to prevent duplicates
+ $result[$key] = array_merge($result[$key], $diff);
+ }
+ }
+ else
+ {
+ if ($assoc)
+ {
+ // Associative values are replaced
+ $result[$key] = $val;
+ }
+ elseif ( ! in_array($val, $result, TRUE))
+ {
+ // Indexed values are added only if they do not yet exist
+ $result[] = $val;
+ }
+ }
+ }
+ else
+ {
+ // New values are added
+ $result[$key] = $val;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Overwrites an array with values from input arrays.
+ * Keys that do not exist in the first array will not be added!
+ *
+ * $a1 = array('name' => 'john', 'mood' => 'happy', 'food' => 'bacon');
+ * $a2 = array('name' => 'jack', 'food' => 'tacos', 'drink' => 'beer');
+ *
+ * // Overwrite the values of $a1 with $a2
+ * $array = Arr::overwrite($a1, $a2);
+ *
+ * // The output of $array will now be:
+ * array('name' => 'jack', 'mood' => 'happy', 'food' => 'bacon')
+ *
+ * @param array master array
+ * @param array input arrays that will overwrite existing values
+ * @return array
+ */
+ public static function overwrite($array1, $array2)
+ {
+ foreach (array_intersect_key($array2, $array1) as $key => $value)
+ {
+ $array1[$key] = $value;
+ }
+
+ if (func_num_args() > 2)
+ {
+ foreach (array_slice(func_get_args(), 2) as $array2)
+ {
+ foreach (array_intersect_key($array2, $array1) as $key => $value)
+ {
+ $array1[$key] = $value;
+ }
+ }
+ }
+
+ return $array1;
+ }
+
+ /**
+ * Creates a callable function and parameter list from a string representation.
+ * Note that this function does not validate the callback string.
+ *
+ * // Get the callback function and parameters
+ * list($func, $params) = Arr::callback('Foo::bar(apple,orange)');
+ *
+ * // Get the result of the callback
+ * $result = call_user_func_array($func, $params);
+ *
+ * @param string callback string
+ * @return array function, params
+ */
+ public static function callback($str)
+ {
+ // Overloaded as parts are found
+ $command = $params = NULL;
+
+ // command[param,param]
+ if (preg_match('/^([^\(]*+)\((.*)\)$/', $str, $match))
+ {
+ // command
+ $command = $match[1];
+
+ if ($match[2] !== '')
+ {
+ // param,param
+ $params = preg_split('/(? array('one' => 'something'), 'two' => 'other');
+ *
+ * // Flatten the array
+ * $array = Arr::flatten($array);
+ *
+ * // The array will now be
+ * array('one' => 'something', 'two' => 'other');
+ *
+ * [!!] The keys of array values will be discarded.
+ *
+ * @param array array to flatten
+ * @return array
+ * @since 3.0.6
+ */
+ public static function flatten($array)
+ {
+ $flat = array();
+ foreach ($array as $key => $value)
+ {
+ if (is_array($value))
+ {
+ $flat += Arr::flatten($value);
+ }
+ else
+ {
+ $flat[$key] = $value;
+ }
+ }
+ return $flat;
+ }
+
+} // End arr
diff --git a/includes/kohana/system/classes/kohana/cli.php b/includes/kohana/system/classes/kohana/cli.php
new file mode 100644
index 0000000..415aea6
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/cli.php
@@ -0,0 +1,75 @@
+attach($reader); // Try first
+ * $config->attach($reader, FALSE); // Try last
+ *
+ * @param object Kohana_Config_Reader instance
+ * @param boolean add the reader as the first used object
+ * @return $this
+ */
+ public function attach(Kohana_Config_Reader $reader, $first = TRUE)
+ {
+ if ($first === TRUE)
+ {
+ // Place the log reader at the top of the stack
+ array_unshift($this->_readers, $reader);
+ }
+ else
+ {
+ // Place the reader at the bottom of the stack
+ $this->_readers[] = $reader;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Detach a configuration reader.
+ *
+ * $config->detach($reader);
+ *
+ * @param object Kohana_Config_Reader instance
+ * @return $this
+ */
+ public function detach(Kohana_Config_Reader $reader)
+ {
+ if (($key = array_search($reader, $this->_readers)) !== FALSE)
+ {
+ // Remove the writer
+ unset($this->_readers[$key]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Load a configuration group. Searches the readers in order until the
+ * group is found. If the group does not exist, an empty configuration
+ * array will be loaded using the first reader.
+ *
+ * $array = $config->load($name);
+ *
+ * @param string configuration group name
+ * @return object Kohana_Config_Reader
+ * @throws Kohana_Exception
+ */
+ public function load($group)
+ {
+ foreach ($this->_readers as $reader)
+ {
+ if ($config = $reader->load($group))
+ {
+ // Found a reader for this configuration group
+ return $config;
+ }
+ }
+
+ // Reset the iterator
+ reset($this->_readers);
+
+ if ( ! is_object($config = current($this->_readers)))
+ {
+ throw new Kohana_Exception('No configuration readers attached');
+ }
+
+ // Load the reader as an empty array
+ return $config->load($group, array());
+ }
+
+ /**
+ * Copy one configuration group to all of the other readers.
+ *
+ * $config->copy($name);
+ *
+ * @param string configuration group name
+ * @return $this
+ */
+ public function copy($group)
+ {
+ // Load the configuration group
+ $config = $this->load($group);
+
+ foreach ($this->_readers as $reader)
+ {
+ if ($config instanceof $reader)
+ {
+ // Do not copy the config to the same group
+ continue;
+ }
+
+ // Load the configuration object
+ $object = $reader->load($group, array());
+
+ foreach ($config as $key => $value)
+ {
+ // Copy each value in the config
+ $object->offsetSet($key, $value);
+ }
+ }
+
+ return $this;
+ }
+
+} // End Kohana_Config
diff --git a/includes/kohana/system/classes/kohana/config/file.php b/includes/kohana/system/classes/kohana/config/file.php
new file mode 100644
index 0000000..9015c28
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/config/file.php
@@ -0,0 +1,60 @@
+_directory = trim($directory, '/');
+
+ // Load the empty array
+ parent::__construct();
+ }
+
+ /**
+ * Load and merge all of the configuration files in this group.
+ *
+ * $config->load($name);
+ *
+ * @param string configuration group name
+ * @param array configuration array
+ * @return $this clone of the current object
+ * @uses Kohana::load
+ */
+ public function load($group, array $config = NULL)
+ {
+ if ($files = Kohana::find_file($this->_directory, $group, NULL, TRUE))
+ {
+ // Initialize the config array
+ $config = array();
+
+ foreach ($files as $file)
+ {
+ // Merge each file to the configuration array
+ $config = Arr::merge($config, Kohana::load($file));
+ }
+ }
+
+ return parent::load($group, $config);
+ }
+
+} // End Kohana_Config
diff --git a/includes/kohana/system/classes/kohana/config/reader.php b/includes/kohana/system/classes/kohana/config/reader.php
new file mode 100644
index 0000000..098cdac
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/config/reader.php
@@ -0,0 +1,115 @@
+getArrayCopy());
+ }
+
+ /**
+ * Loads a configuration group.
+ *
+ * $config->load($name, $array);
+ *
+ * This method must be extended by all readers. After the group has been
+ * loaded, call `parent::load($group, $config)` for final preparation.
+ *
+ * @param string configuration group name
+ * @param array configuration array
+ * @return $this a clone of this object
+ */
+ public function load($group, array $config = NULL)
+ {
+ if ($config === NULL)
+ {
+ return FALSE;
+ }
+
+ // Clone the current object
+ $object = clone $this;
+
+ // Set the group name
+ $object->_configuration_group = $group;
+
+ // Swap the array with the actual configuration
+ $object->exchangeArray($config);
+
+ return $object;
+ }
+
+ /**
+ * Return the raw array that is being used for this object.
+ *
+ * $array = $config->as_array();
+ *
+ * @return array
+ */
+ public function as_array()
+ {
+ return $this->getArrayCopy();
+ }
+
+ /**
+ * Get a variable from the configuration or return the default value.
+ *
+ * $value = $config->get($key);
+ *
+ * @param string array key
+ * @param mixed default value
+ * @return mixed
+ */
+ public function get($key, $default = NULL)
+ {
+ return $this->offsetExists($key) ? $this->offsetGet($key) : $default;
+ }
+
+ /**
+ * Sets a value in the configuration array.
+ *
+ * $config->set($key, $new_value);
+ *
+ * @param string array key
+ * @param mixed array value
+ * @return $this
+ */
+ public function set($key, $value)
+ {
+ $this->offsetSet($key, $value);
+
+ return $this;
+ }
+
+} // End Kohana_Config_Reader
diff --git a/includes/kohana/system/classes/kohana/controller.php b/includes/kohana/system/classes/kohana/controller.php
new file mode 100644
index 0000000..4830ce1
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/controller.php
@@ -0,0 +1,66 @@
+before();
+ * $controller->action_bar();
+ * $controller->after();
+ *
+ * The controller action should add the output it creates to
+ * `$this->request->response`, typically in the form of a [View], during the
+ * "action" part of execution.
+ *
+ * @package Kohana
+ * @category Controller
+ * @author Kohana Team
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+abstract class Kohana_Controller {
+
+ /**
+ * @var Request Request that created the controller
+ */
+ public $request;
+
+ /**
+ * Creates a new controller instance. Each controller must be constructed
+ * with the request object that created it.
+ *
+ * @param Request Request that created the controller
+ * @return void
+ */
+ public function __construct(Request $request)
+ {
+ // Assign the request to the controller
+ $this->request = $request;
+ }
+
+ /**
+ * Automatically executed before the controller action. Can be used to set
+ * class properties, do authorization checks, and execute other custom code.
+ *
+ * @return void
+ */
+ public function before()
+ {
+ // Nothing by default
+ }
+
+ /**
+ * Automatically executed after the controller action. Can be used to apply
+ * transformation to the request response, add extra output, and execute
+ * other custom code.
+ *
+ * @return void
+ */
+ public function after()
+ {
+ // Nothing by default
+ }
+
+} // End Controller
diff --git a/includes/kohana/system/classes/kohana/controller/rest.php b/includes/kohana/system/classes/kohana/controller/rest.php
new file mode 100644
index 0000000..e270e36
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/controller/rest.php
@@ -0,0 +1,80 @@
+ 'index',
+ 'PUT' => 'update',
+ 'POST' => 'create',
+ 'DELETE' => 'delete',
+ );
+
+ /**
+ * @var string requested action
+ */
+ protected $_action_requested = '';
+
+ /**
+ * Checks the requested method against the available methods. If the method
+ * is supported, sets the request action from the map. If not supported,
+ * the "invalid" action will be called.
+ */
+ public function before()
+ {
+ $this->_action_requested = $this->request->action;
+
+ if ( ! isset($this->_action_map[Request::$method]))
+ {
+ $this->request->action = 'invalid';
+ }
+ else
+ {
+ $this->request->action = $this->_action_map[Request::$method];
+ }
+
+ return parent::before();
+ }
+
+ /**
+ * Sends a 405 "Method Not Allowed" response and a list of allowed actions.
+ */
+ public function action_invalid()
+ {
+ // Send the "Method Not Allowed" response
+ $this->request->status = 405;
+ $this->request->headers['Allow'] = implode(', ', array_keys($this->_action_map));
+ }
+
+} // End REST
diff --git a/includes/kohana/system/classes/kohana/controller/template.php b/includes/kohana/system/classes/kohana/controller/template.php
new file mode 100644
index 0000000..76c4222
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/controller/template.php
@@ -0,0 +1,50 @@
+auto_render === TRUE)
+ {
+ // Load the template
+ $this->template = View::factory($this->template);
+ }
+
+ return parent::before();
+ }
+
+ /**
+ * Assigns the template [View] as the request response.
+ */
+ public function after()
+ {
+ if ($this->auto_render === TRUE)
+ {
+ $this->request->response = $this->template;
+ }
+
+ return parent::after();
+ }
+
+} // End Controller_Template
diff --git a/includes/kohana/system/classes/kohana/cookie.php b/includes/kohana/system/classes/kohana/cookie.php
new file mode 100644
index 0000000..4be8f44
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/cookie.php
@@ -0,0 +1,155 @@
+ 'Fatal Error',
+ E_USER_ERROR => 'User Error',
+ E_PARSE => 'Parse Error',
+ E_WARNING => 'Warning',
+ E_USER_WARNING => 'User Warning',
+ E_STRICT => 'Strict',
+ E_NOTICE => 'Notice',
+ E_RECOVERABLE_ERROR => 'Recoverable Error',
+ );
+
+ /**
+ * @var string Current environment name
+ */
+ public static $environment = Kohana::DEVELOPMENT;
+
+ /**
+ * @var boolean True if Kohana is running from the command line
+ */
+ public static $is_cli = FALSE;
+
+ /**
+ * @var boolean True if Kohana is running on windows
+ */
+ public static $is_windows = FALSE;
+
+ /**
+ * @var boolean True if [magic quotes](http://php.net/manual/en/security.magicquotes.php) is enabled.
+ */
+ public static $magic_quotes = FALSE;
+
+ /**
+ * @var boolean Should errors and exceptions be logged
+ */
+ public static $log_errors = FALSE;
+
+ /**
+ * @var boolean TRUE if PHP safe mode is on
+ */
+ public static $safe_mode = FALSE;
+
+ /**
+ * @var string Character set of input and output. Set by [Kohana::init]
+ */
+ public static $charset = 'utf-8';
+
+ /**
+ * @var string Base URL to the application. Set by [Kohana::init]
+ */
+ public static $base_url = '/';
+
+ /**
+ * @var string Application index file, added to links generated by Kohana. Set by [Kohana::init]
+ */
+ public static $index_file = 'index.php';
+
+ /**
+ * @var string Cache directory, used by [Kohana::cache]. Set by [Kohana::init]
+ */
+ public static $cache_dir;
+
+ /**
+ * @var integer Default lifetime for caching, in seconds, used by [Kohana::cache]. Set by [Kohana::init]
+ */
+ public static $cache_life = 60;
+
+ /**
+ * @var boolean Whether to use internal caching for [Kohana::find_file], does not apply to [Kohana::cache]. Set by [Kohana::init]
+ */
+ public static $caching = FALSE;
+
+ /**
+ * @var boolean Whether to enable [profiling](kohana/profiling). Set by [Kohana::init]
+ */
+ public static $profiling = TRUE;
+
+ /**
+ * @var boolean Enable Kohana catching and displaying PHP errors and exceptions. Set by [Kohana::init]
+ */
+ public static $errors = TRUE;
+
+ /**
+ * @var string Error rendering view when Kohana catches PHP errors and exceptions. Set by [Kohana::init]
+ */
+ public static $error_view = 'kohana/error';
+
+ /**
+ * @var array Types of errors to display at shutdown
+ */
+ public static $shutdown_errors = array(E_PARSE, E_ERROR, E_USER_ERROR);
+
+ /**
+ * @var boolean escape quotes in Kohana::debug?
+ */
+ public static $debug_escape_quotes = FALSE;
+
+ /**
+ * @var Kohana_Log logging object
+ */
+ public static $log;
+
+ /**
+ * @var Kohana_Config config object
+ */
+ public static $config;
+
+ /**
+ * @var boolean Has [Kohana::init] been called?
+ */
+ protected static $_init = FALSE;
+
+ /**
+ * @var array Currently active modules
+ */
+ protected static $_modules = array();
+
+ /**
+ * @var array Include paths that are used to find files
+ */
+ protected static $_paths = array(APPPATH, SYSPATH);
+
+ /**
+ * @var array File path cache, used when caching is true in [Kohana::init]
+ */
+ protected static $_files = array();
+
+ /**
+ * @var boolean Has the file path cache changed during this execution? Used internally when when caching is true in [Kohana::init]
+ */
+ protected static $_files_changed = FALSE;
+
+ /**
+ * Initializes the environment:
+ *
+ * - Disables register_globals and magic_quotes_gpc
+ * - Determines the current environment
+ * - Set global settings
+ * - Sanitizes GET, POST, and COOKIE variables
+ * - Converts GET, POST, and COOKIE variables to the global character set
+ *
+ * The following settings can be set:
+ *
+ * Type | Setting | Description | Default Value
+ * ----------|------------|------------------------------------------------|---------------
+ * `string` | base_url | The base URL for your application. This should be the *relative* path from your DOCROOT to your `index.php` file, in other words, if Kohana is in a subfolder, set this to the subfolder name, otherwise leave it as the default. **The leading slash is required**, trailing slash is optional. | `"/"`
+ * `string` | index_file | The name of the [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern). This is used by Kohana to generate relative urls like [HTML::anchor()] and [URL::base()]. This is usually `index.php`. To [remove index.php from your urls](tutorials/clean-urls), set this to `FALSE`. | `"index.php"`
+ * `string` | charset | Character set used for all input and output | `"utf-8"`
+ * `string` | cache_dir | Kohana's cache directory. Used by [Kohana::cache] for simple internal caching, like [Fragments](kohana/fragments) and **\[caching database queries](this should link somewhere)**. This has nothing to do with the [Cache module](cache). | `APPPATH."cache"`
+ * `integer` | cache_life | Lifetime, in seconds, of items cached by [Kohana::cache] | `60`
+ * `boolean` | errors | Should Kohana catch PHP errors and uncaught Exceptions and show the `error_view`. See [Error Handling](kohana/errors) for more info. Recommended setting: `TRUE` while developing, `FALSE` on production servers. | `TRUE`
+ * `string` | error_view | The view to use to display errors. Only used when `errors` is `TRUE`. | `"kohana/error"`
+ * `boolean` | profile | Whether to enable the [Profiler](kohana/profiling). Recommended setting: `TRUE` while developing, `FALSE` on production servers. | `TRUE`
+ * `boolean` | caching | Cache file locations to speed up [Kohana::find_file]. This has nothing to do with [Kohana::cache], [Fragments](kohana/fragments) or the [Cache module](cache). Recommended setting: `FALSE` while developing, `TRUE` on production servers. | `FALSE`
+ *
+ * @throws Kohana_Exception
+ * @param array Array of settings. See above.
+ * @return void
+ * @uses Kohana::globals
+ * @uses Kohana::sanitize
+ * @uses Kohana::cache
+ * @uses Profiler
+ */
+ public static function init(array $settings = NULL)
+ {
+ if (Kohana::$_init)
+ {
+ // Do not allow execution twice
+ return;
+ }
+
+ // Kohana is now initialized
+ Kohana::$_init = TRUE;
+
+ if (isset($settings['profile']))
+ {
+ // Enable profiling
+ Kohana::$profiling = (bool) $settings['profile'];
+ }
+
+ // Start an output buffer
+ ob_start();
+
+ if (defined('E_DEPRECATED'))
+ {
+ // E_DEPRECATED only exists in PHP >= 5.3.0
+ Kohana::$php_errors[E_DEPRECATED] = 'Deprecated';
+ }
+
+ if (isset($settings['errors']))
+ {
+ // Enable error handling
+ Kohana::$errors = (bool) $settings['errors'];
+ }
+
+ if (Kohana::$errors === TRUE)
+ {
+ // Enable Kohana exception handling, adds stack traces and error source.
+ set_exception_handler(array('Kohana', 'exception_handler'));
+
+ // Enable Kohana error handling, converts all PHP errors to exceptions.
+ set_error_handler(array('Kohana', 'error_handler'));
+ }
+
+ // Enable the Kohana shutdown handler, which catches E_FATAL errors.
+ register_shutdown_function(array('Kohana', 'shutdown_handler'));
+
+ if (isset($settings['error_view']))
+ {
+ if ( ! Kohana::find_file('views', $settings['error_view']))
+ {
+ throw new Kohana_Exception('Error view file does not exist: views/:file', array(
+ ':file' => $settings['error_view'],
+ ));
+ }
+
+ // Change the default error rendering
+ Kohana::$error_view = (string) $settings['error_view'];
+ }
+
+ if (ini_get('register_globals'))
+ {
+ // Reverse the effects of register_globals
+ Kohana::globals();
+ }
+
+ // Determine if we are running in a command line environment
+ Kohana::$is_cli = (PHP_SAPI === 'cli');
+
+ // Determine if we are running in a Windows environment
+ Kohana::$is_windows = (DIRECTORY_SEPARATOR === '\\');
+
+ // Determine if we are running in safe mode
+ Kohana::$safe_mode = (bool) ini_get('safe_mode');
+
+ if (isset($settings['cache_dir']))
+ {
+ if ( ! is_dir($settings['cache_dir']))
+ {
+ try
+ {
+ // Create the cache directory
+ mkdir($settings['cache_dir'], 0755, TRUE);
+
+ // Set permissions (must be manually set to fix umask issues)
+ chmod($settings['cache_dir'], 0755);
+ }
+ catch (Exception $e)
+ {
+ throw new Kohana_Exception('Could not create cache directory :dir',
+ array(':dir' => Kohana::debug_path($settings['cache_dir'])));
+ }
+ }
+
+ // Set the cache directory path
+ Kohana::$cache_dir = realpath($settings['cache_dir']);
+ }
+ else
+ {
+ // Use the default cache directory
+ Kohana::$cache_dir = APPPATH.'cache';
+ }
+
+ if ( ! is_writable(Kohana::$cache_dir))
+ {
+ throw new Kohana_Exception('Directory :dir must be writable',
+ array(':dir' => Kohana::debug_path(Kohana::$cache_dir)));
+ }
+
+ if (isset($settings['cache_life']))
+ {
+ // Set the default cache lifetime
+ Kohana::$cache_life = (int) $settings['cache_life'];
+ }
+
+ if (isset($settings['caching']))
+ {
+ // Enable or disable internal caching
+ Kohana::$caching = (bool) $settings['caching'];
+ }
+
+ if (Kohana::$caching === TRUE)
+ {
+ // Load the file path cache
+ Kohana::$_files = Kohana::cache('Kohana::find_file()');
+ }
+
+ if (isset($settings['charset']))
+ {
+ // Set the system character set
+ Kohana::$charset = strtolower($settings['charset']);
+ }
+
+ if (function_exists('mb_internal_encoding'))
+ {
+ // Set the MB extension encoding to the same character set
+ mb_internal_encoding(Kohana::$charset);
+ }
+
+ if (isset($settings['base_url']))
+ {
+ // Set the base URL
+ Kohana::$base_url = rtrim($settings['base_url'], '/').'/';
+ }
+
+ if (isset($settings['index_file']))
+ {
+ // Set the index file
+ Kohana::$index_file = trim($settings['index_file'], '/');
+ }
+
+ // Determine if the extremely evil magic quotes are enabled
+ Kohana::$magic_quotes = (bool) get_magic_quotes_gpc();
+
+ // Sanitize all request variables
+ $_GET = Kohana::sanitize($_GET);
+ $_POST = Kohana::sanitize($_POST);
+ $_COOKIE = Kohana::sanitize($_COOKIE);
+
+ // Load the logger
+ Kohana::$log = Kohana_Log::instance();
+
+ // Load the config
+ Kohana::$config = Kohana_Config::instance();
+ }
+
+ /**
+ * Cleans up the environment:
+ *
+ * - Restore the previous error and exception handlers
+ * - Destroy the Kohana::$log and Kohana::$config objects
+ *
+ * @return void
+ */
+ public static function deinit()
+ {
+ if (Kohana::$_init)
+ {
+ // Removed the autoloader
+ spl_autoload_unregister(array('Kohana', 'auto_load'));
+
+ if (Kohana::$errors)
+ {
+ // Go back to the previous error handler
+ restore_error_handler();
+
+ // Go back to the previous exception handler
+ restore_exception_handler();
+ }
+
+ // Destroy objects created by init
+ Kohana::$log = Kohana::$config = NULL;
+
+ // Reset internal storage
+ Kohana::$_modules = Kohana::$_files = array();
+ Kohana::$_paths = array(APPPATH, SYSPATH);
+
+ // Reset file cache status
+ Kohana::$_files_changed = FALSE;
+
+ // Kohana is no longer initialized
+ Kohana::$_init = FALSE;
+ }
+ }
+
+ /**
+ * Reverts the effects of the `register_globals` PHP setting by unsetting
+ * all global varibles except for the default super globals (GPCS, etc),
+ * which is a [potential security hole.][ref-wikibooks]
+ *
+ * This is called automatically by [Kohana::init] if `register_globals` is
+ * on.
+ *
+ *
+ * [ref-wikibooks]: http://en.wikibooks.org/wiki/PHP_Programming/Register_Globals
+ *
+ * @return void
+ */
+ public static function globals()
+ {
+ if (isset($_REQUEST['GLOBALS']) OR isset($_FILES['GLOBALS']))
+ {
+ // Prevent malicious GLOBALS overload attack
+ echo "Global variable overload attack detected! Request aborted.\n";
+
+ // Exit with an error status
+ exit(1);
+ }
+
+ // Get the variable names of all globals
+ $global_variables = array_keys($GLOBALS);
+
+ // Remove the standard global variables from the list
+ $global_variables = array_diff($global_variables, array(
+ '_COOKIE',
+ '_ENV',
+ '_GET',
+ '_FILES',
+ '_POST',
+ '_REQUEST',
+ '_SERVER',
+ '_SESSION',
+ 'GLOBALS',
+ ));
+
+ foreach ($global_variables as $name)
+ {
+ // Unset the global variable, effectively disabling register_globals
+ unset($GLOBALS[$name]);
+ }
+ }
+
+ /**
+ * Recursively sanitizes an input variable:
+ *
+ * - Strips slashes if magic quotes are enabled
+ * - Normalizes all newlines to LF
+ *
+ * @param mixed any variable
+ * @return mixed sanitized variable
+ */
+ public static function sanitize($value)
+ {
+ if (is_array($value) OR is_object($value))
+ {
+ foreach ($value as $key => $val)
+ {
+ // Recursively clean each value
+ $value[$key] = Kohana::sanitize($val);
+ }
+ }
+ elseif (is_string($value))
+ {
+ if (Kohana::$magic_quotes === TRUE)
+ {
+ // Remove slashes added by magic quotes
+ $value = stripslashes($value);
+ }
+
+ if (strpos($value, "\r") !== FALSE)
+ {
+ // Standardize newlines
+ $value = str_replace(array("\r\n", "\r"), "\n", $value);
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Provides auto-loading support of classes that follow Kohana's [class
+ * naming conventions](kohana/conventions#class-names-and-file-location).
+ * See [Loading Classes](kohana/autoloading) for more information.
+ *
+ * Class names are converted to file names by making the class name
+ * lowercase and converting underscores to slashes:
+ *
+ * // Loads classes/my/class/name.php
+ * Kohana::auto_load('My_Class_Name');
+ *
+ * You should never have to call this function, as simply calling a class
+ * will cause it to be called.
+ *
+ * This function must be enabled as an autoloader in the bootstrap:
+ *
+ * spl_autoload_register(array('Kohana', 'auto_load'));
+ *
+ * @param string class name
+ * @return boolean
+ */
+ public static function auto_load($class)
+ {
+ try
+ {
+ // Transform the class name into a path
+ $file = str_replace('_', '/', strtolower($class));
+
+ if ($path = Kohana::find_file('classes', $file))
+ {
+ // Load the class file
+ require $path;
+
+ // Class has been found
+ return TRUE;
+ }
+
+ // Class is not in the filesystem
+ return FALSE;
+ }
+ catch (Exception $e)
+ {
+ Kohana::exception_handler($e);
+ die;
+ }
+ }
+
+ /**
+ * Changes the currently enabled modules. Module paths may be relative
+ * or absolute, but must point to a directory:
+ *
+ * Kohana::modules(array('modules/foo', MODPATH.'bar'));
+ *
+ * @param array list of module paths
+ * @return array enabled modules
+ */
+ public static function modules(array $modules = NULL)
+ {
+ if ($modules === NULL)
+ {
+ // Not changing modules, just return the current set
+ return Kohana::$_modules;
+ }
+
+ // Start a new list of include paths, APPPATH first
+ $paths = array(APPPATH);
+
+ foreach ($modules as $name => $path)
+ {
+ if (is_dir($path))
+ {
+ // Add the module to include paths
+ $paths[] = $modules[$name] = realpath($path).DIRECTORY_SEPARATOR;
+ }
+ else
+ {
+ // This module is invalid, remove it
+ unset($modules[$name]);
+ }
+ }
+
+ // Finish the include paths by adding SYSPATH
+ $paths[] = SYSPATH;
+
+ // Set the new include paths
+ Kohana::$_paths = $paths;
+
+ // Set the current module list
+ Kohana::$_modules = $modules;
+
+ foreach (Kohana::$_modules as $path)
+ {
+ $init = $path.'init'.EXT;
+
+ if (is_file($init))
+ {
+ // Include the module initialization file once
+ require_once $init;
+ }
+ }
+
+ return Kohana::$_modules;
+ }
+
+ /**
+ * Returns the the currently active include paths, including the
+ * application, system, and each module's path.
+ *
+ * @return array
+ */
+ public static function include_paths()
+ {
+ return Kohana::$_paths;
+ }
+
+ /**
+ * Searches for a file in the [Cascading Filesystem](kohana/files), and
+ * returns the path to the file that has the highest precedence, so that it
+ * can be included.
+ *
+ * When searching the "config", "messages", or "i18n" directories, or when
+ * the `$array` flag is set to true, an array of all the files that match
+ * that path in the [Cascading Filesystem](kohana/files) will be returned.
+ * These files will return arrays which must be merged together.
+ *
+ * If no extension is given, the default extension (`EXT` set in
+ * `index.php`) will be used.
+ *
+ * // Returns an absolute path to views/template.php
+ * Kohana::find_file('views', 'template');
+ *
+ * // Returns an absolute path to media/css/style.css
+ * Kohana::find_file('media', 'css/style', 'css');
+ *
+ * // Returns an array of all the "mimes" configuration files
+ * Kohana::find_file('config', 'mimes');
+ *
+ * @param string directory name (views, i18n, classes, extensions, etc.)
+ * @param string filename with subdirectory
+ * @param string extension to search for
+ * @param boolean return an array of files?
+ * @return array a list of files when $array is TRUE
+ * @return string single file path
+ */
+ public static function find_file($dir, $file, $ext = NULL, $array = FALSE)
+ {
+ if ($ext === NULL)
+ {
+ // Use the default extension
+ $ext = EXT;
+ }
+ elseif ($ext)
+ {
+ // Prefix the extension with a period
+ $ext = ".{$ext}";
+ }
+ else
+ {
+ // Use no extension
+ $ext = '';
+ }
+
+ // Create a partial path of the filename
+ $path = $dir.DIRECTORY_SEPARATOR.$file.$ext;
+
+ if (Kohana::$caching === TRUE AND isset(Kohana::$_files[$path]))
+ {
+ // This path has been cached
+ return Kohana::$_files[$path];
+ }
+
+ if (Kohana::$profiling === TRUE AND class_exists('Profiler', FALSE))
+ {
+ // Start a new benchmark
+ $benchmark = Profiler::start('Kohana', __FUNCTION__);
+ }
+
+ if ($array OR $dir === 'config' OR $dir === 'i18n' OR $dir === 'messages')
+ {
+ // Include paths must be searched in reverse
+ $paths = array_reverse(Kohana::$_paths);
+
+ // Array of files that have been found
+ $found = array();
+
+ foreach ($paths as $dir)
+ {
+ if (is_file($dir.$path))
+ {
+ // This path has a file, add it to the list
+ $found[] = $dir.$path;
+ }
+ }
+ }
+ else
+ {
+ // The file has not been found yet
+ $found = FALSE;
+
+ foreach (Kohana::$_paths as $dir)
+ {
+ if (is_file($dir.$path))
+ {
+ // A path has been found
+ $found = $dir.$path;
+
+ // Stop searching
+ break;
+ }
+ }
+ }
+
+ if (Kohana::$caching === TRUE)
+ {
+ // Add the path to the cache
+ Kohana::$_files[$path] = $found;
+
+ // Files have been changed
+ Kohana::$_files_changed = TRUE;
+ }
+
+ if (isset($benchmark))
+ {
+ // Stop the benchmark
+ Profiler::stop($benchmark);
+ }
+
+ return $found;
+ }
+
+ /**
+ * Recursively finds all of the files in the specified directory at any
+ * location in the [Cascading Filesystem](kohana/files), and returns an
+ * array of all the files found, sorted alphabetically.
+ *
+ * // Find all view files.
+ * $views = Kohana::list_files('views');
+ *
+ * @param string directory name
+ * @param array list of paths to search
+ * @return array
+ */
+ public static function list_files($directory = NULL, array $paths = NULL)
+ {
+ if ($directory !== NULL)
+ {
+ // Add the directory separator
+ $directory .= DIRECTORY_SEPARATOR;
+ }
+
+ if ($paths === NULL)
+ {
+ // Use the default paths
+ $paths = Kohana::$_paths;
+ }
+
+ // Create an array for the files
+ $found = array();
+
+ foreach ($paths as $path)
+ {
+ if (is_dir($path.$directory))
+ {
+ // Create a new directory iterator
+ $dir = new DirectoryIterator($path.$directory);
+
+ foreach ($dir as $file)
+ {
+ // Get the file name
+ $filename = $file->getFilename();
+
+ if ($filename[0] === '.' OR $filename[strlen($filename)-1] === '~')
+ {
+ // Skip all hidden files and UNIX backup files
+ continue;
+ }
+
+ // Relative filename is the array key
+ $key = $directory.$filename;
+
+ if ($file->isDir())
+ {
+ if ($sub_dir = Kohana::list_files($key, $paths))
+ {
+ if (isset($found[$key]))
+ {
+ // Append the sub-directory list
+ $found[$key] += $sub_dir;
+ }
+ else
+ {
+ // Create a new sub-directory list
+ $found[$key] = $sub_dir;
+ }
+ }
+ }
+ else
+ {
+ if ( ! isset($found[$key]))
+ {
+ // Add new files to the list
+ $found[$key] = realpath($file->getPathName());
+ }
+ }
+ }
+ }
+ }
+
+ // Sort the results alphabetically
+ ksort($found);
+
+ return $found;
+ }
+
+ /**
+ * Loads a file within a totally empty scope and returns the output:
+ *
+ * $foo = Kohana::load('foo.php');
+ *
+ * @param string
+ * @return mixed
+ */
+ public static function load($file)
+ {
+ return include $file;
+ }
+
+ /**
+ * Returns the configuration array for the requested group. See
+ * [configuration files](kohana/files/config) for more information.
+ *
+ * // Get all the configuration in config/database.php
+ * $config = Kohana::config('database');
+ *
+ * // Get only the default connection configuration
+ * $default = Kohana::config('database.default')
+ *
+ * // Get only the hostname of the default connection
+ * $host = Kohana::config('database.default.connection.hostname')
+ *
+ * @param string group name
+ * @return Kohana_Config
+ */
+ public static function config($group)
+ {
+ static $config;
+
+ if (strpos($group, '.') !== FALSE)
+ {
+ // Split the config group and path
+ list ($group, $path) = explode('.', $group, 2);
+ }
+
+ if ( ! isset($config[$group]))
+ {
+ // Load the config group into the cache
+ $config[$group] = Kohana::$config->load($group);
+ }
+
+ if (isset($path))
+ {
+ return Arr::path($config[$group], $path, NULL, '.');
+ }
+ else
+ {
+ return $config[$group];
+ }
+ }
+
+ /**
+ * Provides simple file-based caching for strings and arrays:
+ *
+ * // Set the "foo" cache
+ * Kohana::cache('foo', 'hello, world');
+ *
+ * // Get the "foo" cache
+ * $foo = Kohana::cache('foo');
+ *
+ * All caches are stored as PHP code, generated with [var_export][ref-var].
+ * Caching objects may not work as expected. Storing references or an
+ * object or array that has recursion will cause an E_FATAL.
+ *
+ * The cache directory and default cache lifetime is set by [Kohana::init]
+ *
+ * [ref-var]: http://php.net/var_export
+ *
+ * @throws Kohana_Exception
+ * @param string name of the cache
+ * @param mixed data to cache
+ * @param integer number of seconds the cache is valid for
+ * @return mixed for getting
+ * @return boolean for setting
+ */
+ public static function cache($name, $data = NULL, $lifetime = NULL)
+ {
+ // Cache file is a hash of the name
+ $file = sha1($name).'.txt';
+
+ // Cache directories are split by keys to prevent filesystem overload
+ $dir = Kohana::$cache_dir.DIRECTORY_SEPARATOR.$file[0].$file[1].DIRECTORY_SEPARATOR;
+
+ if ($lifetime === NULL)
+ {
+ // Use the default lifetime
+ $lifetime = Kohana::$cache_life;
+ }
+
+ if ($data === NULL)
+ {
+ if (is_file($dir.$file))
+ {
+ if ((time() - filemtime($dir.$file)) < $lifetime)
+ {
+ // Return the cache
+ try
+ {
+ return unserialize(file_get_contents($dir.$file));
+ }
+ catch (Exception $e)
+ {
+ // Cache is corrupt, let return happen normally.
+ }
+ }
+ else
+ {
+ try
+ {
+ // Cache has expired
+ unlink($dir.$file);
+ }
+ catch (Exception $e)
+ {
+ // Cache has mostly likely already been deleted,
+ // let return happen normally.
+ }
+ }
+ }
+
+ // Cache not found
+ return NULL;
+ }
+
+ if ( ! is_dir($dir))
+ {
+ // Create the cache directory
+ mkdir($dir, 0777, TRUE);
+
+ // Set permissions (must be manually set to fix umask issues)
+ chmod($dir, 0777);
+ }
+
+ // Force the data to be a string
+ $data = serialize($data);
+
+ try
+ {
+ // Write the cache
+ return (bool) file_put_contents($dir.$file, $data, LOCK_EX);
+ }
+ catch (Exception $e)
+ {
+ // Failed to write cache
+ return FALSE;
+ }
+ }
+
+ /**
+ * Get a message from a file. Messages are arbitary strings that are stored
+ * in the `messages/` directory and reference by a key. Translation is not
+ * performed on the returned values. See [message files](kohana/files/messages)
+ * for more information.
+ *
+ * // Get "username" from messages/text.php
+ * $username = Kohana::message('text', 'username');
+ *
+ * @param string file name
+ * @param string key path to get
+ * @param mixed default value if the path does not exist
+ * @return string message string for the given path
+ * @return array complete message list, when no path is specified
+ * @uses Arr::merge
+ * @uses Arr::path
+ */
+ public static function message($file, $path = NULL, $default = NULL)
+ {
+ static $messages;
+
+ if ( ! isset($messages[$file]))
+ {
+ // Create a new message list
+ $messages[$file] = array();
+
+ if ($files = Kohana::find_file('messages', $file))
+ {
+ foreach ($files as $f)
+ {
+ // Combine all the messages recursively
+ $messages[$file] = Arr::merge($messages[$file], Kohana::load($f));
+ }
+ }
+ }
+
+ if ($path === NULL)
+ {
+ // Return all of the messages
+ return $messages[$file];
+ }
+ else
+ {
+ // Get a message using the path
+ return Arr::path($messages[$file], $path, $default);
+ }
+ }
+
+ /**
+ * PHP error handler, converts all errors into ErrorExceptions. This handler
+ * respects error_reporting settings.
+ *
+ * @throws ErrorException
+ * @return TRUE
+ */
+ public static function error_handler($code, $error, $file = NULL, $line = NULL)
+ {
+ if (error_reporting() & $code)
+ {
+ // This error is not suppressed by current error reporting settings
+ // Convert the error into an ErrorException
+ throw new ErrorException($error, $code, 0, $file, $line);
+ }
+
+ // Do not execute the PHP error handler
+ return TRUE;
+ }
+
+ /**
+ * Inline exception handler, displays the error message, source of the
+ * exception, and the stack trace of the error.
+ *
+ * @uses Kohana::exception_text
+ * @param object exception object
+ * @return boolean
+ */
+ public static function exception_handler(Exception $e)
+ {
+ try
+ {
+ // Get the exception information
+ $type = get_class($e);
+ $code = $e->getCode();
+ $message = $e->getMessage();
+ $file = $e->getFile();
+ $line = $e->getLine();
+
+ // Create a text version of the exception
+ $error = Kohana::exception_text($e);
+
+ if (is_object(Kohana::$log))
+ {
+ // Add this exception to the log
+ Kohana::$log->add(Kohana::ERROR, $error);
+
+ // Make sure the logs are written
+ Kohana::$log->write();
+ }
+
+ if (Kohana::$is_cli)
+ {
+ // Just display the text of the exception
+ echo "\n{$error}\n";
+
+ return TRUE;
+ }
+
+ // Get the exception backtrace
+ $trace = $e->getTrace();
+
+ if ($e instanceof ErrorException)
+ {
+ if (isset(Kohana::$php_errors[$code]))
+ {
+ // Use the human-readable error name
+ $code = Kohana::$php_errors[$code];
+ }
+
+ if (version_compare(PHP_VERSION, '5.3', '<'))
+ {
+ // Workaround for a bug in ErrorException::getTrace() that exists in
+ // all PHP 5.2 versions. @see http://bugs.php.net/bug.php?id=45895
+ for ($i = count($trace) - 1; $i > 0; --$i)
+ {
+ if (isset($trace[$i - 1]['args']))
+ {
+ // Re-position the args
+ $trace[$i]['args'] = $trace[$i - 1]['args'];
+
+ // Remove the args
+ unset($trace[$i - 1]['args']);
+ }
+ }
+ }
+ }
+
+ if ( ! headers_sent())
+ {
+ // Make sure the proper content type is sent with a 500 status
+ header('Content-Type: text/html; charset='.Kohana::$charset, TRUE, 500);
+ }
+
+ // Start an output buffer
+ ob_start();
+
+ // Include the exception HTML
+ include Kohana::find_file('views', Kohana::$error_view);
+
+ // Display the contents of the output buffer
+ echo ob_get_clean();
+
+ return TRUE;
+ }
+ catch (Exception $e)
+ {
+ // Clean the output buffer if one exists
+ ob_get_level() and ob_clean();
+
+ // Display the exception text
+ echo Kohana::exception_text($e), "\n";
+
+ // Exit with an error status
+ exit(1);
+ }
+ }
+
+ /**
+ * Catches errors that are not caught by the error handler, such as E_PARSE.
+ *
+ * @uses Kohana::exception_handler
+ * @return void
+ */
+ public static function shutdown_handler()
+ {
+ if ( ! Kohana::$_init)
+ {
+ // Do not execute when not active
+ return;
+ }
+
+ try
+ {
+ if (Kohana::$caching === TRUE AND Kohana::$_files_changed === TRUE)
+ {
+ // Write the file path cache
+ Kohana::cache('Kohana::find_file()', Kohana::$_files);
+ }
+ }
+ catch (Exception $e)
+ {
+ // Pass the exception to the handler
+ Kohana::exception_handler($e);
+ }
+
+ if (Kohana::$errors AND $error = error_get_last() AND in_array($error['type'], Kohana::$shutdown_errors))
+ {
+ // Clean the output buffer
+ ob_get_level() and ob_clean();
+
+ // Fake an exception for nice debugging
+ Kohana::exception_handler(new ErrorException($error['message'], $error['type'], 0, $error['file'], $error['line']));
+
+ // Shutdown now to avoid a "death loop"
+ exit(1);
+ }
+ }
+
+ /**
+ * Get a single line of text representing the exception:
+ *
+ * Error [ Code ]: Message ~ File [ Line ]
+ *
+ * @param object Exception
+ * @return string
+ */
+ public static function exception_text(Exception $e)
+ {
+ return sprintf('%s [ %s ]: %s ~ %s [ %d ]',
+ get_class($e), $e->getCode(), strip_tags($e->getMessage()), Kohana::debug_path($e->getFile()), $e->getLine());
+ }
+
+ /**
+ * Returns an HTML string of debugging information about any number of
+ * variables, each wrapped in a "pre" tag:
+ *
+ * // Displays the type and value of each variable
+ * echo Kohana::debug($foo, $bar, $baz);
+ *
+ * @param mixed variable to debug
+ * @param ...
+ * @return string
+ */
+ public static function debug()
+ {
+ if (func_num_args() === 0)
+ return;
+
+ // Get all passed variables
+ $variables = func_get_args();
+
+ $output = array();
+ foreach ($variables as $var)
+ {
+ $output[] = Kohana::_dump($var, 1024);
+ }
+
+ return ''.implode("\n", $output).' ';
+ }
+
+ /**
+ * Returns an HTML string of information about a single variable.
+ *
+ * Borrows heavily on concepts from the Debug class of [Nette](http://nettephp.com/).
+ *
+ * @param mixed variable to dump
+ * @param integer maximum length of strings
+ * @return string
+ */
+ public static function dump($value, $length = 128)
+ {
+ return Kohana::_dump($value, $length);
+ }
+
+ /**
+ * Helper for Kohana::dump(), handles recursion in arrays and objects.
+ *
+ * @param mixed variable to dump
+ * @param integer maximum length of strings
+ * @param integer recursion level (internal)
+ * @return string
+ */
+ protected static function _dump( & $var, $length = 128, $level = 0)
+ {
+ if ($var === NULL)
+ {
+ return 'NULL ';
+ }
+ elseif (is_bool($var))
+ {
+ return 'bool '.($var ? 'TRUE' : 'FALSE');
+ }
+ elseif (is_float($var))
+ {
+ return 'float '.$var;
+ }
+ elseif (is_resource($var))
+ {
+ if (($type = get_resource_type($var)) === 'stream' AND $meta = stream_get_meta_data($var))
+ {
+ $meta = stream_get_meta_data($var);
+
+ if (isset($meta['uri']))
+ {
+ $file = $meta['uri'];
+
+ if (function_exists('stream_is_local'))
+ {
+ // Only exists on PHP >= 5.2.4
+ if (stream_is_local($file))
+ {
+ $file = Kohana::debug_path($file);
+ }
+ }
+
+ return 'resource ('.$type.') '.htmlspecialchars($file, ENT_NOQUOTES, Kohana::$charset);
+ }
+ }
+ else
+ {
+ return 'resource ('.$type.') ';
+ }
+ }
+ elseif (is_string($var))
+ {
+ // Clean invalid multibyte characters. iconv is only invoked
+ // if there are non ASCII characters in the string, so this
+ // isn't too much of a hit.
+ $var = UTF8::clean($var, Kohana::$charset);
+
+ if (UTF8::strlen($var) > $length)
+ {
+ // Encode the truncated string
+ $str = htmlspecialchars(UTF8::substr($var, 0, $length), ENT_NOQUOTES, Kohana::$charset).' …';
+ }
+ else
+ {
+ // Encode the string
+ $str = htmlspecialchars($var, ENT_NOQUOTES, Kohana::$charset);
+ }
+
+ if (Kohana::$debug_escape_quotes)
+ {
+ // Escape strings (for syntax highlighters, mostly)
+ $str = str_replace('"', '\\"', $str);
+ }
+
+ return 'string ('.strlen($var).') "'.$str.'"';
+ }
+ elseif (is_array($var))
+ {
+ $output = array();
+
+ // Indentation for this variable
+ $space = str_repeat($s = ' ', $level);
+
+ static $marker;
+
+ if ($marker === NULL)
+ {
+ // Make a unique marker
+ $marker = uniqid("\x00");
+ }
+
+ if (empty($var))
+ {
+ // Do nothing
+ }
+ elseif (isset($var[$marker]))
+ {
+ $output[] = "(\n$space$s*RECURSION*\n$space)";
+ }
+ elseif ($level < 5)
+ {
+ $output[] = "(";
+
+ $var[$marker] = TRUE;
+ foreach ($var as $key => & $val)
+ {
+ if ($key === $marker) continue;
+ if ( ! is_int($key))
+ {
+ $key = '"'.htmlspecialchars($key, ENT_NOQUOTES, self::$charset).'"';
+ }
+
+ $output[] = "$space$s$key => ".Kohana::_dump($val, $length, $level + 1);
+ }
+ unset($var[$marker]);
+
+ $output[] = "$space) ";
+ }
+ else
+ {
+ // Depth too great
+ $output[] = "(\n$space$s...\n$space)";
+ }
+
+ return 'array ('.count($var).') '.implode("\n", $output);
+ }
+ elseif (is_object($var))
+ {
+ // Copy the object as an array
+ $array = (array) $var;
+
+ $output = array();
+
+ // Indentation for this variable
+ $space = str_repeat($s = ' ', $level);
+
+ $hash = spl_object_hash($var);
+
+ // Objects that are being dumped
+ static $objects = array();
+
+ if (empty($var))
+ {
+ // Do nothing
+ }
+ elseif (isset($objects[$hash]))
+ {
+ $output[] = "{\n$space$s*RECURSION*\n$space}";
+ }
+ elseif ($level < 10)
+ {
+ $output[] = "{";
+
+ $objects[$hash] = TRUE;
+ foreach ($array as $key => & $val)
+ {
+ if ($key[0] === "\x00")
+ {
+ // Determine if the access is protected or protected
+ $access = ''.(($key[1] === '*') ? 'protected' : 'private').' ';
+
+ // Remove the access level from the variable name
+ $key = substr($key, strrpos($key, "\x00") + 1);
+ }
+ else
+ {
+ $access = 'public ';
+ }
+
+ $output[] = "$space$s$access $key => ".Kohana::_dump($val, $length, $level + 1);
+ }
+ unset($objects[$hash]);
+
+ $output[] = "$space}
";
+ }
+ else
+ {
+ // Depth too great
+ $output[] = "{\n$space$s...\n$space}";
+ }
+
+ return 'object '.get_class($var).'('.count($array).') '.implode("\n", $output);
+ }
+ else
+ {
+ return ''.gettype($var).' '.htmlspecialchars(print_r($var, TRUE), ENT_NOQUOTES, Kohana::$charset);
+ }
+ }
+
+ /**
+ * Removes application, system, modpath, or docroot from a filename,
+ * replacing them with the plain text equivalents. Useful for debugging
+ * when you want to display a shorter path.
+ *
+ * // Displays SYSPATH/classes/kohana.php
+ * echo Kohana::debug_path(Kohana::find_file('classes', 'kohana'));
+ *
+ * @param string path to debug
+ * @return string
+ */
+ public static function debug_path($file)
+ {
+ if (strpos($file, APPPATH) === 0)
+ {
+ $file = 'APPPATH'.DIRECTORY_SEPARATOR.substr($file, strlen(APPPATH));
+ }
+ elseif (strpos($file, SYSPATH) === 0)
+ {
+ $file = 'SYSPATH'.DIRECTORY_SEPARATOR.substr($file, strlen(SYSPATH));
+ }
+ elseif (strpos($file, MODPATH) === 0)
+ {
+ $file = 'MODPATH'.DIRECTORY_SEPARATOR.substr($file, strlen(MODPATH));
+ }
+ elseif (strpos($file, DOCROOT) === 0)
+ {
+ $file = 'DOCROOT'.DIRECTORY_SEPARATOR.substr($file, strlen(DOCROOT));
+ }
+
+ return $file;
+ }
+
+ /**
+ * Returns an HTML string, highlighting a specific line of a file, with some
+ * number of lines padded above and below.
+ *
+ * // Highlights the current line of the current file
+ * echo Kohana::debug_source(__FILE__, __LINE__);
+ *
+ * @param string file to open
+ * @param integer line number to highlight
+ * @param integer number of padding lines
+ * @return string source of file
+ * @return FALSE file is unreadable
+ */
+ public static function debug_source($file, $line_number, $padding = 5)
+ {
+ if ( ! $file OR ! is_readable($file))
+ {
+ // Continuing will cause errors
+ return FALSE;
+ }
+
+ // Open the file and set the line position
+ $file = fopen($file, 'r');
+ $line = 0;
+
+ // Set the reading range
+ $range = array('start' => $line_number - $padding, 'end' => $line_number + $padding);
+
+ // Set the zero-padding amount for line numbers
+ $format = '% '.strlen($range['end']).'d';
+
+ $source = '';
+ while (($row = fgets($file)) !== FALSE)
+ {
+ // Increment the line number
+ if (++$line > $range['end'])
+ break;
+
+ if ($line >= $range['start'])
+ {
+ // Make the row safe for output
+ $row = htmlspecialchars($row, ENT_NOQUOTES, Kohana::$charset);
+
+ // Trim whitespace and sanitize the row
+ $row = ''.sprintf($format, $line).' '.$row;
+
+ if ($line === $line_number)
+ {
+ // Apply highlighting to this row
+ $row = ''.$row.' ';
+ }
+ else
+ {
+ $row = ''.$row.' ';
+ }
+
+ // Add to the captured source
+ $source .= $row;
+ }
+ }
+
+ // Close the file
+ fclose($file);
+
+ return ''.$source.'
';
+ }
+
+ /**
+ * Returns an array of HTML strings that represent each step in the backtrace.
+ *
+ * // Displays the entire current backtrace
+ * echo implode(' ', Kohana::trace());
+ *
+ * @param string path to debug
+ * @return string
+ */
+ public static function trace(array $trace = NULL)
+ {
+ if ($trace === NULL)
+ {
+ // Start a new trace
+ $trace = debug_backtrace();
+ }
+
+ // Non-standard function calls
+ $statements = array('include', 'include_once', 'require', 'require_once');
+
+ $output = array();
+ foreach ($trace as $step)
+ {
+ if ( ! isset($step['function']))
+ {
+ // Invalid trace step
+ continue;
+ }
+
+ if (isset($step['file']) AND isset($step['line']))
+ {
+ // Include the source of this step
+ $source = Kohana::debug_source($step['file'], $step['line']);
+ }
+
+ if (isset($step['file']))
+ {
+ $file = $step['file'];
+
+ if (isset($step['line']))
+ {
+ $line = $step['line'];
+ }
+ }
+
+ // function()
+ $function = $step['function'];
+
+ if (in_array($step['function'], $statements))
+ {
+ if (empty($step['args']))
+ {
+ // No arguments
+ $args = array();
+ }
+ else
+ {
+ // Sanitize the file path
+ $args = array(Kohana::debug_path($step['args'][0]));
+ }
+ }
+ elseif (isset($step['args']))
+ {
+ if ( ! function_exists($step['function']) OR strpos($step['function'], '{closure}') !== FALSE)
+ {
+ // Introspection on closures or language constructs in a stack trace is impossible
+ $params = NULL;
+ }
+ else
+ {
+ if (isset($step['class']))
+ {
+ if (method_exists($step['class'], $step['function']))
+ {
+ $reflection = new ReflectionMethod($step['class'], $step['function']);
+ }
+ else
+ {
+ $reflection = new ReflectionMethod($step['class'], '__call');
+ }
+ }
+ else
+ {
+ $reflection = new ReflectionFunction($step['function']);
+ }
+
+ // Get the function parameters
+ $params = $reflection->getParameters();
+ }
+
+ $args = array();
+
+ foreach ($step['args'] as $i => $arg)
+ {
+ if (isset($params[$i]))
+ {
+ // Assign the argument by the parameter name
+ $args[$params[$i]->name] = $arg;
+ }
+ else
+ {
+ // Assign the argument by number
+ $args[$i] = $arg;
+ }
+ }
+ }
+
+ if (isset($step['class']))
+ {
+ // Class->method() or Class::method()
+ $function = $step['class'].$step['type'].$step['function'];
+ }
+
+ $output[] = array(
+ 'function' => $function,
+ 'args' => isset($args) ? $args : NULL,
+ 'file' => isset($file) ? $file : NULL,
+ 'line' => isset($line) ? $line : NULL,
+ 'source' => isset($source) ? $source : NULL,
+ );
+
+ unset($function, $args, $file, $line, $source);
+ }
+
+ return $output;
+ }
+
+} // End Kohana
diff --git a/includes/kohana/system/classes/kohana/date.php b/includes/kohana/system/classes/kohana/date.php
new file mode 100644
index 0000000..506dab4
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/date.php
@@ -0,0 +1,566 @@
+.
+ *
+ * @param string timezone that to find the offset of
+ * @param string timezone used as the baseline
+ * @param mixed UNIX timestamp or date string
+ * @return integer
+ */
+ public static function offset($remote, $local = NULL, $now = NULL)
+ {
+ if ($local === NULL)
+ {
+ // Use the default timezone
+ $local = date_default_timezone_get();
+ }
+
+ if (is_int($now))
+ {
+ // Convert the timestamp into a string
+ $now = date(DateTime::RFC2822, $now);
+ }
+
+ // Create timezone objects
+ $zone_remote = new DateTimeZone($remote);
+ $zone_local = new DateTimeZone($local);
+
+ // Create date objects from timezones
+ $time_remote = new DateTime($now, $zone_remote);
+ $time_local = new DateTime($now, $zone_local);
+
+ // Find the offset
+ $offset = $zone_remote->getOffset($time_remote) - $zone_local->getOffset($time_local);
+
+ return $offset;
+ }
+
+ /**
+ * Number of seconds in a minute, incrementing by a step. Typically used as
+ * a shortcut for generating a list that can used in a form.
+ *
+ * $seconds = Date::seconds(); // 01, 02, 03, ..., 58, 59, 60
+ *
+ * @param integer amount to increment each step by, 1 to 30
+ * @param integer start value
+ * @param integer end value
+ * @return array A mirrored (foo => foo) array from 1-60.
+ */
+ public static function seconds($step = 1, $start = 0, $end = 60)
+ {
+ // Always integer
+ $step = (int) $step;
+
+ $seconds = array();
+
+ for ($i = $start; $i < $end; $i += $step)
+ {
+ $seconds[$i] = sprintf('%02d', $i);
+ }
+
+ return $seconds;
+ }
+
+ /**
+ * Number of minutes in an hour, incrementing by a step. Typically used as
+ * a shortcut for generating a list that can be used in a form.
+ *
+ * $minutes = Date::minutes(); // 05, 10, 15, ..., 50, 55, 60
+ *
+ * @uses Date::seconds
+ * @param integer amount to increment each step by, 1 to 30
+ * @return array A mirrored (foo => foo) array from 1-60.
+ */
+ public static function minutes($step = 5)
+ {
+ // Because there are the same number of minutes as seconds in this set,
+ // we choose to re-use seconds(), rather than creating an entirely new
+ // function. Shhhh, it's cheating! ;) There are several more of these
+ // in the following methods.
+ return Date::seconds($step);
+ }
+
+ /**
+ * Number of hours in a day. Typically used as a shortcut for generating a
+ * list that can be used in a form.
+ *
+ * $hours = Date::hours(); // 01, 02, 03, ..., 10, 11, 12
+ *
+ * @param integer amount to increment each step by
+ * @param boolean use 24-hour time
+ * @param integer the hour to start at
+ * @return array A mirrored (foo => foo) array from start-12 or start-23.
+ */
+ public static function hours($step = 1, $long = FALSE, $start = NULL)
+ {
+ // Default values
+ $step = (int) $step;
+ $long = (bool) $long;
+ $hours = array();
+
+ // Set the default start if none was specified.
+ if ($start === NULL)
+ {
+ $start = ($long === FALSE) ? 1 : 0;
+ }
+
+ $hours = array();
+
+ // 24-hour time has 24 hours, instead of 12
+ $size = ($long === TRUE) ? 23 : 12;
+
+ for ($i = $start; $i <= $size; $i += $step)
+ {
+ $hours[$i] = (string) $i;
+ }
+
+ return $hours;
+ }
+
+ /**
+ * Returns AM or PM, based on a given hour (in 24 hour format).
+ *
+ * $type = Date::ampm(12); // PM
+ * $type = Date::ampm(1); // AM
+ *
+ * @param integer number of the hour
+ * @return string
+ */
+ public static function ampm($hour)
+ {
+ // Always integer
+ $hour = (int) $hour;
+
+ return ($hour > 11) ? 'PM' : 'AM';
+ }
+
+ /**
+ * Adjusts a non-24-hour number into a 24-hour number.
+ *
+ * $hour = Date::adjust(3, 'pm'); // 15
+ *
+ * @param integer hour to adjust
+ * @param string AM or PM
+ * @return string
+ */
+ public static function adjust($hour, $ampm)
+ {
+ $hour = (int) $hour;
+ $ampm = strtolower($ampm);
+
+ switch ($ampm)
+ {
+ case 'am':
+ if ($hour == 12)
+ {
+ $hour = 0;
+ }
+ break;
+ case 'pm':
+ if ($hour < 12)
+ {
+ $hour += 12;
+ }
+ break;
+ }
+
+ return sprintf('%02d', $hour);
+ }
+
+ /**
+ * Number of days in a given month and year. Typically used as a shortcut
+ * for generating a list that can be used in a form.
+ *
+ * Date::days(4, 2010); // 1, 2, 3, ..., 28, 29, 30
+ *
+ * @param integer number of month
+ * @param integer number of year to check month, defaults to the current year
+ * @return array A mirrored (foo => foo) array of the days.
+ */
+ public static function days($month, $year = FALSE)
+ {
+ static $months;
+
+ if ($year === FALSE)
+ {
+ // Use the current year by default
+ $year = date('Y');
+ }
+
+ // Always integers
+ $month = (int) $month;
+ $year = (int) $year;
+
+ // We use caching for months, because time functions are used
+ if (empty($months[$year][$month]))
+ {
+ $months[$year][$month] = array();
+
+ // Use date to find the number of days in the given month
+ $total = date('t', mktime(1, 0, 0, $month, 1, $year)) + 1;
+
+ for ($i = 1; $i < $total; $i++)
+ {
+ $months[$year][$month][$i] = (string) $i;
+ }
+ }
+
+ return $months[$year][$month];
+ }
+
+ /**
+ * Number of months in a year. Typically used as a shortcut for generating
+ * a list that can be used in a form.
+ *
+ * Date::months(); // 01, 02, 03, ..., 10, 11, 12
+ *
+ * @uses Date::hours
+ * @return array A mirrored (foo => foo) array from 1-12.
+ */
+ public static function months()
+ {
+ return Date::hours();
+ }
+
+ /**
+ * Returns an array of years between a starting and ending year. By default,
+ * the the current year - 5 and current year + 5 will be used. Typically used
+ * as a shortcut for generating a list that can be used in a form.
+ *
+ * $years = Date::years(2000, 2010); // 2000, 2001, ..., 2009, 2010
+ *
+ * @param integer starting year (default is current year - 5)
+ * @param integer ending year (default is current year + 5)
+ * @return array
+ */
+ public static function years($start = FALSE, $end = FALSE)
+ {
+ // Default values
+ $start = ($start === FALSE) ? (date('Y') - 5) : (int) $start;
+ $end = ($end === FALSE) ? (date('Y') + 5) : (int) $end;
+
+ $years = array();
+
+ for ($i = $start; $i <= $end; $i++)
+ {
+ $years[$i] = (string) $i;
+ }
+
+ return $years;
+ }
+
+ /**
+ * Returns time difference between two timestamps, in human readable format.
+ * If the second timestamp is not given, the current time will be used.
+ * Also consider using [Date::fuzzy_span] when displaying a span.
+ *
+ * $span = Date::span(60, 182, 'minutes,seconds'); // array('minutes' => 2, 'seconds' => 2)
+ * $span = Date::span(60, 182, 'minutes'); // 2
+ *
+ * @param integer timestamp to find the span of
+ * @param integer timestamp to use as the baseline
+ * @param string formatting string
+ * @return string when only a single output is requested
+ * @return array associative list of all outputs requested
+ */
+ public static function span($remote, $local = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds')
+ {
+ // Normalize output
+ $output = trim(strtolower( (string) $output));
+
+ if ( ! $output)
+ {
+ // Invalid output
+ return FALSE;
+ }
+
+ // Array with the output formats
+ $output = preg_split('/[^a-z]+/', $output);
+
+ // Convert the list of outputs to an associative array
+ $output = array_combine($output, array_fill(0, count($output), 0));
+
+ // Make the output values into keys
+ extract(array_flip($output), EXTR_SKIP);
+
+ if ($local === NULL)
+ {
+ // Calculate the span from the current time
+ $local = time();
+ }
+
+ // Calculate timespan (seconds)
+ $timespan = abs($remote - $local);
+
+ if (isset($output['years']))
+ {
+ $timespan -= Date::YEAR * ($output['years'] = (int) floor($timespan / Date::YEAR));
+ }
+
+ if (isset($output['months']))
+ {
+ $timespan -= Date::MONTH * ($output['months'] = (int) floor($timespan / Date::MONTH));
+ }
+
+ if (isset($output['weeks']))
+ {
+ $timespan -= Date::WEEK * ($output['weeks'] = (int) floor($timespan / Date::WEEK));
+ }
+
+ if (isset($output['days']))
+ {
+ $timespan -= Date::DAY * ($output['days'] = (int) floor($timespan / Date::DAY));
+ }
+
+ if (isset($output['hours']))
+ {
+ $timespan -= Date::HOUR * ($output['hours'] = (int) floor($timespan / Date::HOUR));
+ }
+
+ if (isset($output['minutes']))
+ {
+ $timespan -= Date::MINUTE * ($output['minutes'] = (int) floor($timespan / Date::MINUTE));
+ }
+
+ // Seconds ago, 1
+ if (isset($output['seconds']))
+ {
+ $output['seconds'] = $timespan;
+ }
+
+ if (count($output) === 1)
+ {
+ // Only a single output was requested, return it
+ return array_pop($output);
+ }
+
+ // Return array
+ return $output;
+ }
+
+ /**
+ * Returns the difference between a time and now in a "fuzzy" way.
+ * Displaying a fuzzy time instead of a date is usually faster to read and understand.
+ *
+ * $span = Date::fuzzy_span(time() - 10); // "moments ago"
+ * $span = Date::fuzzy_span(time() + 20); // "in moments"
+ *
+ * A second parameter is available to manually set the "local" timestamp,
+ * however this parameter shouldn't be needed in normal usage and is only
+ * included for unit tests
+ *
+ * @param integer "remote" timestamp
+ * @param integer "local" timestamp, defaults to time()
+ * @return string
+ */
+ public static function fuzzy_span($timestamp, $local_timestamp = NULL)
+ {
+ $local_timestamp = ($local_timestamp === NULL) ? time() : (int) $local_timestamp;
+
+ // Determine the difference in seconds
+ $offset = abs($local_timestamp - $timestamp);
+
+ if ($offset <= Date::MINUTE)
+ {
+ $span = 'moments';
+ }
+ elseif ($offset < (Date::MINUTE * 20))
+ {
+ $span = 'a few minutes';
+ }
+ elseif ($offset < Date::HOUR)
+ {
+ $span = 'less than an hour';
+ }
+ elseif ($offset < (Date::HOUR * 4))
+ {
+ $span = 'a couple of hours';
+ }
+ elseif ($offset < Date::DAY)
+ {
+ $span = 'less than a day';
+ }
+ elseif ($offset < (Date::DAY * 2))
+ {
+ $span = 'about a day';
+ }
+ elseif ($offset < (Date::DAY * 4))
+ {
+ $span = 'a couple of days';
+ }
+ elseif ($offset < Date::WEEK)
+ {
+ $span = 'less than a week';
+ }
+ elseif ($offset < (Date::WEEK * 2))
+ {
+ $span = 'about a week';
+ }
+ elseif ($offset < Date::MONTH)
+ {
+ $span = 'less than a month';
+ }
+ elseif ($offset < (Date::MONTH * 2))
+ {
+ $span = 'about a month';
+ }
+ elseif ($offset < (Date::MONTH * 4))
+ {
+ $span = 'a couple of months';
+ }
+ elseif ($offset < Date::YEAR)
+ {
+ $span = 'less than a year';
+ }
+ elseif ($offset < (Date::YEAR * 2))
+ {
+ $span = 'about a year';
+ }
+ elseif ($offset < (Date::YEAR * 4))
+ {
+ $span = 'a couple of years';
+ }
+ elseif ($offset < (Date::YEAR * 8))
+ {
+ $span = 'a few years';
+ }
+ elseif ($offset < (Date::YEAR * 12))
+ {
+ $span = 'about a decade';
+ }
+ elseif ($offset < (Date::YEAR * 24))
+ {
+ $span = 'a couple of decades';
+ }
+ elseif ($offset < (Date::YEAR * 64))
+ {
+ $span = 'several decades';
+ }
+ else
+ {
+ $span = 'a long time';
+ }
+
+ if ($timestamp <= $local_timestamp)
+ {
+ // This is in the past
+ return $span.' ago';
+ }
+ else
+ {
+ // This in the future
+ return 'in '.$span;
+ }
+ }
+
+ /**
+ * Converts a UNIX timestamp to DOS format. There are very few cases where
+ * this is needed, but some binary formats use it (eg: zip files.)
+ * Converting the other direction is done using {@link Date::dos2unix}.
+ *
+ * $dos = Date::unix2dos($unix);
+ *
+ * @param integer UNIX timestamp
+ * @return integer
+ */
+ public static function unix2dos($timestamp = FALSE)
+ {
+ $timestamp = ($timestamp === FALSE) ? getdate() : getdate($timestamp);
+
+ if ($timestamp['year'] < 1980)
+ {
+ return (1 << 21 | 1 << 16);
+ }
+
+ $timestamp['year'] -= 1980;
+
+ // What voodoo is this? I have no idea... Geert can explain it though,
+ // and that's good enough for me.
+ return ($timestamp['year'] << 25 | $timestamp['mon'] << 21 |
+ $timestamp['mday'] << 16 | $timestamp['hours'] << 11 |
+ $timestamp['minutes'] << 5 | $timestamp['seconds'] >> 1);
+ }
+
+ /**
+ * Converts a DOS timestamp to UNIX format.There are very few cases where
+ * this is needed, but some binary formats use it (eg: zip files.)
+ * Converting the other direction is done using {@link Date::unix2dos}.
+ *
+ * $unix = Date::dos2unix($dos);
+ *
+ * @param integer DOS timestamp
+ * @return integer
+ */
+ public static function dos2unix($timestamp = FALSE)
+ {
+ $sec = 2 * ($timestamp & 0x1f);
+ $min = ($timestamp >> 5) & 0x3f;
+ $hrs = ($timestamp >> 11) & 0x1f;
+ $day = ($timestamp >> 16) & 0x1f;
+ $mon = ($timestamp >> 21) & 0x0f;
+ $year = ($timestamp >> 25) & 0x7f;
+
+ return mktime($hrs, $min, $sec, $mon, $day, $year + 1980);
+ }
+
+ /**
+ * Returns a date/time string with the specified timestamp format
+ *
+ * $time = Date::formatted_time('5 minutes ago');
+ *
+ * @see http://php.net/manual/en/datetime.construct.php
+ * @param string datetime_str datetime string
+ * @param string timestamp_format timestamp format
+ * @return string
+ */
+ public static function formatted_time($datetime_str = 'now', $timestamp_format = NULL, $timezone = NULL)
+ {
+ $timestamp_format = ($timestamp_format == NULL) ? Date::$timestamp_format : $timestamp_format;
+ $timezone = ($timezone === NULL) ? Date::$timezone : $timezone;
+
+ $time = new DateTime($datetime_str, new DateTimeZone(
+ $timezone ? $timezone : date_default_timezone_get()
+ ));
+
+ return $time->format($timestamp_format);
+ }
+
+} // End date
diff --git a/includes/kohana/system/classes/kohana/encrypt.php b/includes/kohana/system/classes/kohana/encrypt.php
new file mode 100644
index 0000000..c071f1d
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/encrypt.php
@@ -0,0 +1,213 @@
+$name;
+
+ if ( ! isset($config['key']))
+ {
+ // No default encryption key is provided!
+ throw new Kohana_Exception('No encryption key is defined in the encryption configuration group: :group',
+ array(':group' => $name));
+ }
+
+ if ( ! isset($config['mode']))
+ {
+ // Add the default mode
+ $config['mode'] = MCRYPT_MODE_NOFB;
+ }
+
+ if ( ! isset($config['cipher']))
+ {
+ // Add the default cipher
+ $config['cipher'] = MCRYPT_RIJNDAEL_128;
+ }
+
+ // Create a new instance
+ Encrypt::$instances[$name] = new Encrypt($config['key'], $config['mode'], $config['cipher']);
+ }
+
+ return Encrypt::$instances[$name];
+ }
+
+ /**
+ * Creates a new mcrypt wrapper.
+ *
+ * @param string encryption key
+ * @param string mcrypt mode
+ * @param string mcrypt cipher
+ */
+ public function __construct($key, $mode, $cipher)
+ {
+ // Find the max length of the key, based on cipher and mode
+ $size = mcrypt_get_key_size($cipher, $mode);
+
+ if (isset($key[$size]))
+ {
+ // Shorten the key to the maximum size
+ $key = substr($key, 0, $size);
+ }
+
+ // Store the key, mode, and cipher
+ $this->_key = $key;
+ $this->_mode = $mode;
+ $this->_cipher = $cipher;
+
+ // Store the IV size
+ $this->_iv_size = mcrypt_get_iv_size($this->_cipher, $this->_mode);
+ }
+
+ /**
+ * Encrypts a string and returns an encrypted string that can be decoded.
+ *
+ * $data = $encrypt->encode($data);
+ *
+ * The encrypted binary data is encoded using [base64](http://php.net/base64_encode)
+ * to convert it to a string. This string can be stored in a database,
+ * displayed, and passed using most other means without corruption.
+ *
+ * @param string data to be encrypted
+ * @return string
+ */
+ public function encode($data)
+ {
+ // Set the rand type if it has not already been set
+ if (Encrypt::$_rand === NULL)
+ {
+ if (Kohana::$is_windows)
+ {
+ // Windows only supports the system random number generator
+ Encrypt::$_rand = MCRYPT_RAND;
+ }
+ else
+ {
+ if (defined('MCRYPT_DEV_URANDOM'))
+ {
+ // Use /dev/urandom
+ Encrypt::$_rand = MCRYPT_DEV_URANDOM;
+ }
+ elseif (defined('MCRYPT_DEV_RANDOM'))
+ {
+ // Use /dev/random
+ Encrypt::$_rand = MCRYPT_DEV_RANDOM;
+ }
+ else
+ {
+ // Use the system random number generator
+ Encrypt::$_rand = MCRYPT_RAND;
+ }
+ }
+ }
+
+ if (Encrypt::$_rand === MCRYPT_RAND)
+ {
+ // The system random number generator must always be seeded each
+ // time it is used, or it will not produce true random results
+ mt_srand();
+ }
+
+ // Create a random initialization vector of the proper size for the current cipher
+ $iv = mcrypt_create_iv($this->_iv_size, Encrypt::$_rand);
+
+ // Encrypt the data using the configured options and generated iv
+ $data = mcrypt_encrypt($this->_cipher, $this->_key, $data, $this->_mode, $iv);
+
+ // Use base64 encoding to convert to a string
+ return base64_encode($iv.$data);
+ }
+
+ /**
+ * Decrypts an encoded string back to its original value.
+ *
+ * $data = $encrypt->decode($data);
+ *
+ * @param string encoded string to be decrypted
+ * @return FALSE if decryption fails
+ * @return string
+ */
+ public function decode($data)
+ {
+ // Convert the data back to binary
+ $data = base64_decode($data, TRUE);
+
+ if ( ! $data)
+ {
+ // Invalid base64 data
+ return FALSE;
+ }
+
+ // Extract the initialization vector from the data
+ $iv = substr($data, 0, $this->_iv_size);
+
+ if ($this->_iv_size !== strlen($iv))
+ {
+ // The iv is not the expected size
+ return FALSE;
+ }
+
+ // Remove the iv from the data
+ $data = substr($data, $this->_iv_size);
+
+ // Return the decrypted data, trimming the \0 padding bytes from the end of the data
+ return rtrim(mcrypt_decrypt($this->_cipher, $this->_key, $data, $this->_mode, $iv), "\0");
+ }
+
+} // End Encrypt
diff --git a/includes/kohana/system/classes/kohana/exception.php b/includes/kohana/system/classes/kohana/exception.php
new file mode 100644
index 0000000..95ef633
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/exception.php
@@ -0,0 +1,46 @@
+ $user));
+ *
+ * @param string error message
+ * @param array translation variables
+ * @param integer the exception code
+ * @return void
+ */
+ public function __construct($message, array $variables = NULL, $code = 0)
+ {
+ // Set the message
+ $message = __($message, $variables);
+
+ // Pass the message to the parent
+ parent::__construct($message, $code);
+ }
+
+ /**
+ * Magic object-to-string method.
+ *
+ * echo $exception;
+ *
+ * @uses Kohana::exception_text
+ * @return string
+ */
+ public function __toString()
+ {
+ return Kohana::exception_text($this);
+ }
+
+} // End Kohana_Exception
diff --git a/includes/kohana/system/classes/kohana/feed.php b/includes/kohana/system/classes/kohana/feed.php
new file mode 100644
index 0000000..6e3ea5c
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/feed.php
@@ -0,0 +1,176 @@
+getNamespaces(true);
+
+ // Detect the feed type. RSS 1.0/2.0 and Atom 1.0 are supported.
+ $feed = isset($feed->channel) ? $feed->xpath('//item') : $feed->entry;
+
+ $i = 0;
+ $items = array();
+
+ foreach ($feed as $item)
+ {
+ if ($limit > 0 AND $i++ === $limit)
+ break;
+ $item_fields = (array) $item;
+
+ // get namespaced tags
+ foreach ($namespaces as $ns)
+ {
+ $item_fields += (array) $item->children($ns);
+ }
+ $items[] = $item_fields;
+ }
+
+ return $items;
+ }
+
+ /**
+ * Creates a feed from the given parameters.
+ *
+ * @param array feed information
+ * @param array items to add to the feed
+ * @param string define which format to use (only rss2 is supported)
+ * @param string define which encoding to use
+ * @return string
+ */
+ public static function create($info, $items, $format = 'rss2', $encoding = 'UTF-8')
+ {
+ $info += array('title' => 'Generated Feed', 'link' => '', 'generator' => 'KohanaPHP');
+
+ $feed = ' ';
+ $feed = simplexml_load_string($feed);
+
+ foreach ($info as $name => $value)
+ {
+ if ($name === 'image')
+ {
+ // Create an image element
+ $image = $feed->channel->addChild('image');
+
+ if ( ! isset($value['link'], $value['url'], $value['title']))
+ {
+ throw new Kohana_Exception('Feed images require a link, url, and title');
+ }
+
+ if (strpos($value['link'], '://') === FALSE)
+ {
+ // Convert URIs to URLs
+ $value['link'] = URL::site($value['link'], 'http');
+ }
+
+ if (strpos($value['url'], '://') === FALSE)
+ {
+ // Convert URIs to URLs
+ $value['url'] = URL::site($value['url'], 'http');
+ }
+
+ // Create the image elements
+ $image->addChild('link', $value['link']);
+ $image->addChild('url', $value['url']);
+ $image->addChild('title', $value['title']);
+ }
+ else
+ {
+ if (($name === 'pubDate' OR $name === 'lastBuildDate') AND (is_int($value) OR ctype_digit($value)))
+ {
+ // Convert timestamps to RFC 822 formatted dates
+ $value = date('r', $value);
+ }
+ elseif (($name === 'link' OR $name === 'docs') AND strpos($value, '://') === FALSE)
+ {
+ // Convert URIs to URLs
+ $value = URL::site($value, 'http');
+ }
+
+ // Add the info to the channel
+ $feed->channel->addChild($name, $value);
+ }
+ }
+
+ foreach ($items as $item)
+ {
+ // Add the item to the channel
+ $row = $feed->channel->addChild('item');
+
+ foreach ($item as $name => $value)
+ {
+ if ($name === 'pubDate' AND (is_int($value) OR ctype_digit($value)))
+ {
+ // Convert timestamps to RFC 822 formatted dates
+ $value = date('r', $value);
+ }
+ elseif (($name === 'link' OR $name === 'guid') AND strpos($value, '://') === FALSE)
+ {
+ // Convert URIs to URLs
+ $value = URL::site($value, 'http');
+ }
+
+ // Add the info to the row
+ $row->addChild($name, $value);
+ }
+ }
+
+ if (function_exists('dom_import_simplexml'))
+ {
+ // Convert the feed object to a DOM object
+ $feed = dom_import_simplexml($feed)->ownerDocument;
+
+ // DOM generates more readable XML
+ $feed->formatOutput = TRUE;
+
+ // Export the document as XML
+ $feed = $feed->saveXML();
+ }
+ else
+ {
+ // Export the document as XML
+ $feed = $feed->asXML();
+ }
+
+ return $feed;
+ }
+
+} // End Feed
diff --git a/includes/kohana/system/classes/kohana/file.php b/includes/kohana/system/classes/kohana/file.php
new file mode 100644
index 0000000..066617d
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/file.php
@@ -0,0 +1,179 @@
+file($filename);
+ }
+ }
+
+ if (ini_get('mime_magic.magicfile') AND function_exists('mime_content_type'))
+ {
+ // The mime_content_type function is only useful with a magic file
+ return mime_content_type($filename);
+ }
+
+ if ( ! empty($extension))
+ {
+ return File::mime_by_ext($extension);
+ }
+
+ // Unable to find the mime-type
+ return FALSE;
+ }
+
+ /**
+ * Return the mime type of an extension.
+ *
+ * $mime = File::mime_by_ext('png'); // "image/png"
+ *
+ * @param string extension: php, pdf, txt, etc
+ * @return string mime type on success
+ * @return FALSE on failure
+ */
+ public static function mime_by_ext($extension)
+ {
+ // Load all of the mime types
+ $mimes = Kohana::config('mimes');
+
+ return isset($mimes[$extension]) ? $mimes[$extension][0] : FALSE;
+ }
+
+ /**
+ * Split a file into pieces matching a specific size. Used when you need to
+ * split large files into smaller pieces for easy transmission.
+ *
+ * $count = File::split($file);
+ *
+ * @param string file to be split
+ * @param string directory to output to, defaults to the same directory as the file
+ * @param integer size, in MB, for each piece to be
+ * @return integer The number of pieces that were created
+ */
+ public static function split($filename, $piece_size = 10)
+ {
+ // Open the input file
+ $file = fopen($filename, 'rb');
+
+ // Change the piece size to bytes
+ $piece_size = floor($piece_size * 1024 * 1024);
+
+ // Write files in 8k blocks
+ $block_size = 1024 * 8;
+
+ // Total number of peices
+ $peices = 0;
+
+ while ( ! feof($file))
+ {
+ // Create another piece
+ $peices += 1;
+
+ // Create a new file piece
+ $piece = str_pad($peices, 3, '0', STR_PAD_LEFT);
+ $piece = fopen($filename.'.'.$piece, 'wb+');
+
+ // Number of bytes read
+ $read = 0;
+
+ do
+ {
+ // Transfer the data in blocks
+ fwrite($piece, fread($file, $block_size));
+
+ // Another block has been read
+ $read += $block_size;
+ }
+ while ($read < $piece_size);
+
+ // Close the piece
+ fclose($piece);
+ }
+
+ // Close the file
+ fclose($file);
+
+ return $peices;
+ }
+
+ /**
+ * Join a split file into a whole file. Does the reverse of [File::split].
+ *
+ * $count = File::join($file);
+ *
+ * @param string split filename, without .000 extension
+ * @param string output filename, if different then an the filename
+ * @return integer The number of pieces that were joined.
+ */
+ public static function join($filename)
+ {
+ // Open the file
+ $file = fopen($filename, 'wb+');
+
+ // Read files in 8k blocks
+ $block_size = 1024 * 8;
+
+ // Total number of peices
+ $pieces = 0;
+
+ while (is_file($piece = $filename.'.'.str_pad($pieces + 1, 3, '0', STR_PAD_LEFT)))
+ {
+ // Read another piece
+ $pieces += 1;
+
+ // Open the piece for reading
+ $piece = fopen($piece, 'rb');
+
+ while ( ! feof($piece))
+ {
+ // Transfer the data in blocks
+ fwrite($file, fread($piece, $block_size));
+ }
+
+ // Close the peice
+ fclose($piece);
+ }
+
+ return $pieces;
+ }
+
+} // End file
diff --git a/includes/kohana/system/classes/kohana/form.php b/includes/kohana/system/classes/kohana/form.php
new file mode 100644
index 0000000..27016de
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/form.php
@@ -0,0 +1,434 @@
+ 'get'));
+ *
+ * // When "file" inputs are present, you must include the "enctype"
+ * echo Form::open(NULL, array('enctype' => 'multipart/form-data'));
+ *
+ * @param string form action, defaults to the current request URI
+ * @param array html attributes
+ * @return string
+ * @uses Request::instance
+ * @uses URL::site
+ * @uses HTML::attributes
+ */
+ public static function open($action = NULL, array $attributes = NULL)
+ {
+ if ($action === NULL)
+ {
+ // Use the current URI
+ $action = Request::current()->uri;
+ }
+
+ if ($action === '')
+ {
+ // Use only the base URI
+ $action = Kohana::$base_url;
+ }
+ elseif (strpos($action, '://') === FALSE)
+ {
+ // Make the URI absolute
+ $action = URL::site($action);
+ }
+
+ // Add the form action to the attributes
+ $attributes['action'] = $action;
+
+ // Only accept the default character set
+ $attributes['accept-charset'] = Kohana::$charset;
+
+ if ( ! isset($attributes['method']))
+ {
+ // Use POST method
+ $attributes['method'] = 'post';
+ }
+
+ return '';
+ }
+
+ /**
+ * Creates a form input. If no type is specified, a "text" type input will
+ * be returned.
+ *
+ * echo Form::input('username', $username);
+ *
+ * @param string input name
+ * @param string input value
+ * @param array html attributes
+ * @return string
+ * @uses HTML::attributes
+ */
+ public static function input($name, $value = NULL, array $attributes = NULL)
+ {
+ // Set the input name
+ $attributes['name'] = $name;
+
+ // Set the input value
+ $attributes['value'] = $value;
+
+ if ( ! isset($attributes['type']))
+ {
+ // Default type is text
+ $attributes['type'] = 'text';
+ }
+
+ return ' ';
+ }
+
+ /**
+ * Creates a hidden form input.
+ *
+ * echo Form::hidden('csrf', $token);
+ *
+ * @param string input name
+ * @param string input value
+ * @param array html attributes
+ * @return string
+ * @uses Form::input
+ */
+ public static function hidden($name, $value = NULL, array $attributes = NULL)
+ {
+ $attributes['type'] = 'hidden';
+
+ return Form::input($name, $value, $attributes);
+ }
+
+ /**
+ * Creates a password form input.
+ *
+ * echo Form::password('password');
+ *
+ * @param string input name
+ * @param string input value
+ * @param array html attributes
+ * @return string
+ * @uses Form::input
+ */
+ public static function password($name, $value = NULL, array $attributes = NULL)
+ {
+ $attributes['type'] = 'password';
+
+ return Form::input($name, $value, $attributes);
+ }
+
+ /**
+ * Creates a file upload form input. No input value can be specified.
+ *
+ * echo Form::file('image');
+ *
+ * @param string input name
+ * @param array html attributes
+ * @return string
+ * @uses Form::input
+ */
+ public static function file($name, array $attributes = NULL)
+ {
+ $attributes['type'] = 'file';
+
+ return Form::input($name, NULL, $attributes);
+ }
+
+ /**
+ * Creates a checkbox form input.
+ *
+ * echo Form::checkbox('remember_me', 1, (bool) $remember);
+ *
+ * @param string input name
+ * @param string input value
+ * @param boolean checked status
+ * @param array html attributes
+ * @return string
+ * @uses Form::input
+ */
+ public static function checkbox($name, $value = NULL, $checked = FALSE, array $attributes = NULL)
+ {
+ $attributes['type'] = 'checkbox';
+
+ if ($checked === TRUE)
+ {
+ // Make the checkbox active
+ $attributes['checked'] = 'checked';
+ }
+
+ return Form::input($name, $value, $attributes);
+ }
+
+ /**
+ * Creates a radio form input.
+ *
+ * echo Form::radio('like_cats', 1, $cats);
+ * echo Form::radio('like_cats', 0, ! $cats);
+ *
+ * @param string input name
+ * @param string input value
+ * @param boolean checked status
+ * @param array html attributes
+ * @return string
+ * @uses Form::input
+ */
+ public static function radio($name, $value = NULL, $checked = FALSE, array $attributes = NULL)
+ {
+ $attributes['type'] = 'radio';
+
+ if ($checked === TRUE)
+ {
+ // Make the radio active
+ $attributes['checked'] = 'checked';
+ }
+
+ return Form::input($name, $value, $attributes);
+ }
+
+ /**
+ * Creates a textarea form input.
+ *
+ * echo Form::textarea('about', $about);
+ *
+ * @param string textarea name
+ * @param string textarea body
+ * @param array html attributes
+ * @param boolean encode existing HTML characters
+ * @return string
+ * @uses HTML::attributes
+ * @uses HTML::chars
+ */
+ public static function textarea($name, $body = '', array $attributes = NULL, $double_encode = TRUE)
+ {
+ // Set the input name
+ $attributes['name'] = $name;
+
+ // Add default rows and cols attributes (required)
+ $attributes += array('rows' => 10, 'cols' => 50);
+
+ return '';
+ }
+
+ /**
+ * Creates a select form input.
+ *
+ * echo Form::select('country', $countries, $country);
+ *
+ * [!!] Support for multiple selected options was added in v3.0.7.
+ *
+ * @param string input name
+ * @param array available options
+ * @param mixed selected option string, or an array of selected options
+ * @param array html attributes
+ * @return string
+ * @uses HTML::attributes
+ */
+ public static function select($name, array $options = NULL, $selected = NULL, array $attributes = NULL)
+ {
+ // Set the input name
+ $attributes['name'] = $name;
+
+ if (is_array($selected))
+ {
+ // This is a multi-select, god save us!
+ $attributes['multiple'] = 'multiple';
+ }
+
+ if ( ! is_array($selected))
+ {
+ if ($selected === NULL)
+ {
+ // Use an empty array
+ $selected = array();
+ }
+ else
+ {
+ // Convert the selected options to an array
+ $selected = array( (string) $selected);
+ }
+ }
+
+ if (empty($options))
+ {
+ // There are no options
+ $options = '';
+ }
+ else
+ {
+ foreach ($options as $value => $name)
+ {
+ if (is_array($name))
+ {
+ // Create a new optgroup
+ $group = array('label' => $value);
+
+ // Create a new list of options
+ $_options = array();
+
+ foreach ($name as $_value => $_name)
+ {
+ // Force value to be string
+ $_value = (string) $_value;
+
+ // Create a new attribute set for this option
+ $option = array('value' => $_value);
+
+ if (in_array($_value, $selected))
+ {
+ // This option is selected
+ $option['selected'] = 'selected';
+ }
+
+ // Change the option to the HTML string
+ $_options[] = ''.HTML::chars($_name, FALSE).' ';
+ }
+
+ // Compile the options into a string
+ $_options = "\n".implode("\n", $_options)."\n";
+
+ $options[$value] = ''.$_options.' ';
+ }
+ else
+ {
+ // Force value to be string
+ $value = (string) $value;
+
+ // Create a new attribute set for this option
+ $option = array('value' => $value);
+
+ if (in_array($value, $selected))
+ {
+ // This option is selected
+ $option['selected'] = 'selected';
+ }
+
+ // Change the option to the HTML string
+ $options[$value] = ''.HTML::chars($name, FALSE).' ';
+ }
+ }
+
+ // Compile the options into a single string
+ $options = "\n".implode("\n", $options)."\n";
+ }
+
+ return ''.$options.' ';
+ }
+
+ /**
+ * Creates a submit form input.
+ *
+ * echo Form::submit(NULL, 'Login');
+ *
+ * @param string input name
+ * @param string input value
+ * @param array html attributes
+ * @return string
+ * @uses Form::input
+ */
+ public static function submit($name, $value, array $attributes = NULL)
+ {
+ $attributes['type'] = 'submit';
+
+ return Form::input($name, $value, $attributes);
+ }
+
+ /**
+ * Creates a image form input.
+ *
+ * echo Form::image(NULL, NULL, array('src' => 'media/img/login.png'));
+ *
+ * @param string input name
+ * @param string input value
+ * @param array html attributes
+ * @param boolean add index file to URL?
+ * @return string
+ * @uses Form::input
+ */
+ public static function image($name, $value, array $attributes = NULL, $index = FALSE)
+ {
+ if ( ! empty($attributes['src']))
+ {
+ if (strpos($attributes['src'], '://') === FALSE)
+ {
+ // Add the base URL
+ $attributes['src'] = URL::base($index).$attributes['src'];
+ }
+ }
+
+ $attributes['type'] = 'image';
+
+ return Form::input($name, $value, $attributes);
+ }
+
+ /**
+ * Creates a button form input. Note that the body of a button is NOT escaped,
+ * to allow images and other HTML to be used.
+ *
+ * echo Form::button('save', 'Save Profile', array('type' => 'submit'));
+ *
+ * @param string input name
+ * @param string input value
+ * @param array html attributes
+ * @return string
+ * @uses HTML::attributes
+ */
+ public static function button($name, $body, array $attributes = NULL)
+ {
+ // Set the input name
+ $attributes['name'] = $name;
+
+ return ''.$body.' ';
+ }
+
+ /**
+ * Creates a form label. Label text is not automatically translated.
+ *
+ * echo Form::label('username', 'Username');
+ *
+ * @param string target input
+ * @param string label text
+ * @param array html attributes
+ * @return string
+ * @uses HTML::attributes
+ */
+ public static function label($input, $text = NULL, array $attributes = NULL)
+ {
+ if ($text === NULL)
+ {
+ // Use the input name as the text
+ $text = ucwords(preg_replace('/[\W_]+/', ' ', $input));
+ }
+
+ // Set the label target
+ $attributes['for'] = $input;
+
+ return ''.$text.' ';
+ }
+
+} // End form
diff --git a/includes/kohana/system/classes/kohana/fragment.php b/includes/kohana/system/classes/kohana/fragment.php
new file mode 100644
index 0000000..c2f2127
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/fragment.php
@@ -0,0 +1,147 @@
+ cache key
+ */
+ protected static $_caches = array();
+
+ /**
+ * Generate the cache key name for a fragment.
+ *
+ * $key = Fragment::_cache_key('footer', TRUE);
+ *
+ * @param string fragment name
+ * @param boolean multilingual fragment support
+ * @return string
+ * @uses I18n::lang
+ * @since 3.0.4
+ */
+ protected static function _cache_key($name, $i18n = NULL)
+ {
+ if ($i18n === NULL)
+ {
+ // Use the default setting
+ $i18n = Fragment::$i18n;
+ }
+
+ // Language prefix for cache key
+ $i18n = ($i18n === TRUE) ? I18n::lang() : '';
+
+ // Note: $i18n and $name need to be delimited to prevent naming collisions
+ return 'Fragment::cache('.$i18n.'+'.$name.')';
+ }
+
+ /**
+ * Load a fragment from cache and display it. Multiple fragments can
+ * be nested with different life times.
+ *
+ * if ( ! Fragment::load('footer')) {
+ * // Anything that is echo'ed here will be saved
+ * Fragment::save();
+ * }
+ *
+ * @param string fragment name
+ * @param integer fragment cache lifetime
+ * @param boolean multilingual fragment support
+ * @return boolean
+ */
+ public static function load($name, $lifetime = NULL, $i18n = NULL)
+ {
+ // Set the cache lifetime
+ $lifetime = ($lifetime === NULL) ? Fragment::$lifetime : (int) $lifetime;
+
+ // Get the cache key name
+ $cache_key = Fragment::_cache_key($name, $i18n);
+
+ if ($fragment = Kohana::cache($cache_key, NULL, $lifetime))
+ {
+ // Display the cached fragment now
+ echo $fragment;
+
+ return TRUE;
+ }
+ else
+ {
+ // Start the output buffer
+ ob_start();
+
+ // Store the cache key by the buffer level
+ Fragment::$_caches[ob_get_level()] = $cache_key;
+
+ return FALSE;
+ }
+ }
+
+ /**
+ * Saves the currently open fragment in the cache.
+ *
+ * Fragment::save();
+ *
+ * @return void
+ */
+ public static function save()
+ {
+ // Get the buffer level
+ $level = ob_get_level();
+
+ if (isset(Fragment::$_caches[$level]))
+ {
+ // Get the cache key based on the level
+ $cache_key = Fragment::$_caches[$level];
+
+ // Delete the cache key, we don't need it anymore
+ unset(Fragment::$_caches[$level]);
+
+ // Get the output buffer and display it at the same time
+ $fragment = ob_get_flush();
+
+ // Cache the fragment
+ Kohana::cache($cache_key, $fragment);
+ }
+ }
+
+ /**
+ * Delete a cached fragment.
+ *
+ * Fragment::delete($key);
+ *
+ * @param string fragment name
+ * @param boolean multilingual fragment support
+ * @return void
+ */
+ public static function delete($name, $i18n = NULL)
+ {
+ // Invalid the cache
+ Kohana::cache(Fragment::_cache_key($name, $i18n), NULL, -3600);
+ }
+
+} // End Fragment
diff --git a/includes/kohana/system/classes/kohana/html.php b/includes/kohana/system/classes/kohana/html.php
new file mode 100644
index 0000000..5495ed6
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/html.php
@@ -0,0 +1,380 @@
+'.$title.'';
+ }
+
+ /**
+ * Creates an HTML anchor to a file. Note that the title is not escaped,
+ * to allow HTML elements within links (images, etc).
+ *
+ * echo HTML::file_anchor('media/doc/user_guide.pdf', 'User Guide');
+ *
+ * @param string name of file to link to
+ * @param string link text
+ * @param array HTML anchor attributes
+ * @param string non-default protocol, eg: ftp
+ * @return string
+ * @uses URL::base
+ * @uses HTML::attributes
+ */
+ public static function file_anchor($file, $title = NULL, array $attributes = NULL, $protocol = NULL)
+ {
+ if ($title === NULL)
+ {
+ // Use the file name as the title
+ $title = basename($file);
+ }
+
+ // Add the file link to the attributes
+ $attributes['href'] = URL::base(FALSE, $protocol).$file;
+
+ return ''.$title.' ';
+ }
+
+ /**
+ * Generates an obfuscated version of a string. Text passed through this
+ * method is less likely to be read by web crawlers and robots, which can
+ * be helpful for spam prevention, but can prevent legitimate robots from
+ * reading your content.
+ *
+ * echo HTML::obfuscate($text);
+ *
+ * @param string string to obfuscate
+ * @return string
+ * @since 3.0.3
+ */
+ public static function obfuscate($string)
+ {
+ $safe = '';
+ foreach (str_split($string) as $letter)
+ {
+ switch (rand(1, 3))
+ {
+ // HTML entity code
+ case 1:
+ $safe .= ''.ord($letter).';';
+ break;
+
+ // Hex character code
+ case 2:
+ $safe .= ''.dechex(ord($letter)).';';
+ break;
+
+ // Raw (no) encoding
+ case 3:
+ $safe .= $letter;
+ }
+ }
+
+ return $safe;
+ }
+
+ /**
+ * Generates an obfuscated version of an email address. Helps prevent spam
+ * robots from finding email addresses.
+ *
+ * echo HTML::email($address);
+ *
+ * @param string email address
+ * @return string
+ * @uses HTML::obfuscate
+ */
+ public static function email($email)
+ {
+ // Make sure the at sign is always obfuscated
+ return str_replace('@', '@', HTML::obfuscate($email));
+ }
+
+ /**
+ * Creates an email (mailto:) anchor. Note that the title is not escaped,
+ * to allow HTML elements within links (images, etc).
+ *
+ * echo HTML::mailto($address);
+ *
+ * @param string email address to send to
+ * @param string link text
+ * @param array HTML anchor attributes
+ * @return string
+ * @uses HTML::email
+ * @uses HTML::attributes
+ */
+ public static function mailto($email, $title = NULL, array $attributes = NULL)
+ {
+ // Obfuscate email address
+ $email = HTML::email($email);
+
+ if ($title === NULL)
+ {
+ // Use the email address as the title
+ $title = $email;
+ }
+
+ return ''.$title.' ';
+ }
+
+ /**
+ * Creates a style sheet link element.
+ *
+ * echo HTML::style('media/css/screen.css');
+ *
+ * @param string file name
+ * @param array default attributes
+ * @param boolean include the index page
+ * @return string
+ * @uses URL::base
+ * @uses HTML::attributes
+ */
+ public static function style($file, array $attributes = NULL, $index = FALSE)
+ {
+ if (strpos($file, '://') === FALSE)
+ {
+ // Add the base URL
+ $file = URL::base($index).$file;
+ }
+
+ // Set the stylesheet link
+ $attributes['href'] = $file;
+
+ // Set the stylesheet rel
+ $attributes['rel'] = 'stylesheet';
+
+ // Set the stylesheet type
+ $attributes['type'] = 'text/css';
+
+ return ' ';
+ }
+
+ /**
+ * Creates a script link.
+ *
+ * echo HTML::script('media/js/jquery.min.js');
+ *
+ * @param string file name
+ * @param array default attributes
+ * @param boolean include the index page
+ * @return string
+ * @uses URL::base
+ * @uses HTML::attributes
+ */
+ public static function script($file, array $attributes = NULL, $index = FALSE)
+ {
+ if (strpos($file, '://') === FALSE)
+ {
+ // Add the base URL
+ $file = URL::base($index).$file;
+ }
+
+ // Set the script link
+ $attributes['src'] = $file;
+
+ // Set the script type
+ $attributes['type'] = 'text/javascript';
+
+ return '';
+ }
+
+ /**
+ * Creates a image link.
+ *
+ * echo HTML::image('media/img/logo.png', array('alt' => 'My Company'));
+ *
+ * @param string file name
+ * @param array default attributes
+ * @return string
+ * @uses URL::base
+ * @uses HTML::attributes
+ */
+ public static function image($file, array $attributes = NULL, $index = FALSE)
+ {
+ if (strpos($file, '://') === FALSE)
+ {
+ // Add the base URL
+ $file = URL::base($index).$file;
+ }
+
+ // Add the image link
+ $attributes['src'] = $file;
+
+ return ' ';
+ }
+
+ /**
+ * Compiles an array of HTML attributes into an attribute string.
+ * Attributes will be sorted using HTML::$attribute_order for consistency.
+ *
+ * echo ''.$content.'
';
+ *
+ * @param array attribute list
+ * @return string
+ */
+ public static function attributes(array $attributes = NULL)
+ {
+ if (empty($attributes))
+ return '';
+
+ $sorted = array();
+ foreach (HTML::$attribute_order as $key)
+ {
+ if (isset($attributes[$key]))
+ {
+ // Add the attribute to the sorted list
+ $sorted[$key] = $attributes[$key];
+ }
+ }
+
+ // Combine the sorted attributes
+ $attributes = $sorted + $attributes;
+
+ $compiled = '';
+ foreach ($attributes as $key => $value)
+ {
+ if ($value === NULL)
+ {
+ // Skip attributes that have NULL values
+ continue;
+ }
+
+ if (is_int($key))
+ {
+ // Assume non-associative keys are mirrored attributes
+ $key = $value;
+ }
+
+ // Add the attribute value
+ $compiled .= ' '.$key.'="'.HTML::chars($value).'"';
+ }
+
+ return $compiled;
+ }
+
+} // End html
diff --git a/includes/kohana/system/classes/kohana/i18n.php b/includes/kohana/system/classes/kohana/i18n.php
new file mode 100644
index 0000000..f9a06bb
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/i18n.php
@@ -0,0 +1,139 @@
+ $username));
+ *
+ * [!!] The __() function is declared in `SYSPATH/base.php`.
+ *
+ * @package Kohana
+ * @category Base
+ * @author Kohana Team
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+class Kohana_I18n {
+
+ /**
+ * @var string target language: en-us, es-es, zh-cn, etc
+ */
+ public static $lang = 'en-us';
+
+ /**
+ * @var string source language: en-us, es-es, zh-cn, etc
+ */
+ public static $source = 'en-us';
+
+ /**
+ * @var array cache of loaded languages
+ */
+ protected static $_cache = array();
+
+ /**
+ * Get and set the target language.
+ *
+ * // Get the current language
+ * $lang = I18n::lang();
+ *
+ * // Change the current language to Spanish
+ * I18n::lang('es-es');
+ *
+ * @param string new language setting
+ * @return string
+ * @since 3.0.2
+ */
+ public static function lang($lang = NULL)
+ {
+ if ($lang)
+ {
+ // Normalize the language
+ I18n::$lang = strtolower(str_replace(array(' ', '_'), '-', $lang));
+ }
+
+ return I18n::$lang;
+ }
+
+ /**
+ * Returns translation of a string. If no translation exists, the original
+ * string will be returned. No parameters are replaced.
+ *
+ * $hello = I18n::get('Hello friends, my name is :name');
+ *
+ * @param string text to translate
+ * @param string target language
+ * @return string
+ */
+ public static function get($string, $lang = NULL)
+ {
+ if ( ! $lang)
+ {
+ // Use the global target language
+ $lang = I18n::$lang;
+ }
+
+ // Load the translation table for this language
+ $table = I18n::load($lang);
+
+ // Return the translated string if it exists
+ return isset($table[$string]) ? $table[$string] : $string;
+ }
+
+ /**
+ * Returns the translation table for a given language.
+ *
+ * // Get all defined Spanish messages
+ * $messages = I18n::load('es-es');
+ *
+ * @param string language to load
+ * @return array
+ */
+ public static function load($lang)
+ {
+ if (isset(I18n::$_cache[$lang]))
+ {
+ return I18n::$_cache[$lang];
+ }
+
+ // New translation table
+ $table = array();
+
+ // Split the language: language, region, locale, etc
+ $parts = explode('-', $lang);
+
+ do
+ {
+ // Create a path for this set of parts
+ $path = implode(DIRECTORY_SEPARATOR, $parts);
+
+ if ($files = Kohana::find_file('i18n', $path, NULL, TRUE))
+ {
+ $t = array();
+ foreach ($files as $file)
+ {
+ // Merge the language strings into the sub table
+ $t = array_merge($t, Kohana::load($file));
+ }
+
+ // Append the sub table, preventing less specific language
+ // files from overloading more specific files
+ $table += $t;
+ }
+
+ // Remove the last part
+ array_pop($parts);
+ }
+ while ($parts);
+
+ // Cache the translation table locally
+ return I18n::$_cache[$lang] = $table;
+ }
+
+} // End I18n
diff --git a/includes/kohana/system/classes/kohana/inflector.php b/includes/kohana/system/classes/kohana/inflector.php
new file mode 100644
index 0000000..5edd618
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/inflector.php
@@ -0,0 +1,269 @@
+uncountable;
+
+ // Make uncountables mirrored
+ Inflector::$uncountable = array_combine(Inflector::$uncountable, Inflector::$uncountable);
+ }
+
+ return isset(Inflector::$uncountable[strtolower($str)]);
+ }
+
+ /**
+ * Makes a plural word singular.
+ *
+ * echo Inflector::singular('cats'); // "cat"
+ * echo Inflector::singular('fish'); // "fish", uncountable
+ *
+ * You can also provide the count to make inflection more intelligent.
+ * In this case, it will only return the singular value if the count is
+ * greater than one and not zero.
+ *
+ * echo Inflector::singular('cats', 2); // "cats"
+ *
+ * [!!] Special inflections are defined in `config/inflector.php`.
+ *
+ * @param string word to singularize
+ * @param integer count of thing
+ * @return string
+ * @uses Inflector::uncountable
+ */
+ public static function singular($str, $count = NULL)
+ {
+ // $count should always be a float
+ $count = ($count === NULL) ? 1.0 : (float) $count;
+
+ // Do nothing when $count is not 1
+ if ($count != 1)
+ return $str;
+
+ // Remove garbage
+ $str = strtolower(trim($str));
+
+ // Cache key name
+ $key = 'singular_'.$str.$count;
+
+ if (isset(Inflector::$cache[$key]))
+ return Inflector::$cache[$key];
+
+ if (Inflector::uncountable($str))
+ return Inflector::$cache[$key] = $str;
+
+ if (empty(Inflector::$irregular))
+ {
+ // Cache irregular words
+ Inflector::$irregular = Kohana::config('inflector')->irregular;
+ }
+
+ if ($irregular = array_search($str, Inflector::$irregular))
+ {
+ $str = $irregular;
+ }
+ elseif (preg_match('/us$/', $str))
+ {
+ // http://en.wikipedia.org/wiki/Plural_form_of_words_ending_in_-us
+ // Already singular, do nothing
+ }
+ elseif (preg_match('/[sxz]es$/', $str) OR preg_match('/[^aeioudgkprt]hes$/', $str))
+ {
+ // Remove "es"
+ $str = substr($str, 0, -2);
+ }
+ elseif (preg_match('/[^aeiou]ies$/', $str))
+ {
+ // Replace "ies" with "y"
+ $str = substr($str, 0, -3).'y';
+ }
+ elseif (substr($str, -1) === 's' AND substr($str, -2) !== 'ss')
+ {
+ // Remove singular "s"
+ $str = substr($str, 0, -1);
+ }
+
+ return Inflector::$cache[$key] = $str;
+ }
+
+ /**
+ * Makes a singular word plural.
+ *
+ * echo Inflector::plural('fish'); // "fish", uncountable
+ * echo Inflector::plural('cat'); // "cats"
+ *
+ * You can also provide the count to make inflection more intelligent.
+ * In this case, it will only return the plural value if the count is
+ * not one.
+ *
+ * echo Inflector::singular('cats', 3); // "cats"
+ *
+ * [!!] Special inflections are defined in `config/inflector.php`.
+ *
+ * @param string word to pluralize
+ * @param integer count of thing
+ * @return string
+ * @uses Inflector::uncountable
+ */
+ public static function plural($str, $count = NULL)
+ {
+ // $count should always be a float
+ $count = ($count === NULL) ? 0.0 : (float) $count;
+
+ // Do nothing with singular
+ if ($count == 1)
+ return $str;
+
+ // Remove garbage
+ $str = trim($str);
+
+ // Cache key name
+ $key = 'plural_'.$str.$count;
+
+ // Check uppercase
+ $is_uppercase = ctype_upper($str);
+
+ if (isset(Inflector::$cache[$key]))
+ return Inflector::$cache[$key];
+
+ if (Inflector::uncountable($str))
+ return Inflector::$cache[$key] = $str;
+
+ if (empty(Inflector::$irregular))
+ {
+ // Cache irregular words
+ Inflector::$irregular = Kohana::config('inflector')->irregular;
+ }
+
+ if (isset(Inflector::$irregular[$str]))
+ {
+ $str = Inflector::$irregular[$str];
+ }
+ elseif (preg_match('/[sxz]$/', $str) OR preg_match('/[^aeioudgkprt]h$/', $str))
+ {
+ $str .= 'es';
+ }
+ elseif (preg_match('/[^aeiou]y$/', $str))
+ {
+ // Change "y" to "ies"
+ $str = substr_replace($str, 'ies', -1);
+ }
+ else
+ {
+ $str .= 's';
+ }
+
+ // Convert to uppsecase if nessasary
+ if ($is_uppercase)
+ {
+ $str = strtoupper($str);
+ }
+
+ // Set the cache and return
+ return Inflector::$cache[$key] = $str;
+ }
+
+ /**
+ * Makes a phrase camel case. Spaces and underscores will be removed.
+ *
+ * $str = Inflector::camelize('mother cat'); // "motherCat"
+ * $str = Inflector::camelize('kittens in bed'); // "kittensInBed"
+ *
+ * @param string phrase to camelize
+ * @return string
+ */
+ public static function camelize($str)
+ {
+ $str = 'x'.strtolower(trim($str));
+ $str = ucwords(preg_replace('/[\s_]+/', ' ', $str));
+
+ return substr(str_replace(' ', '', $str), 1);
+ }
+
+ /**
+ * Converts a camel case phrase into a spaced phrase.
+ *
+ * $str = Inflector::decamelize('houseCat'); // "house cat"
+ * $str = Inflector::decamelize('kingAllyCat'); // "king ally cat"
+ *
+ * @param string phrase to camelize
+ * @param string word separator
+ * @return string
+ */
+ public static function decamelize($str, $sep = ' ')
+ {
+ return strtolower(preg_replace('/([a-z])([A-Z])/', '$1'.$sep.'$2', trim($str)));
+ }
+
+ /**
+ * Makes a phrase underscored instead of spaced.
+ *
+ * $str = Inflector::underscore('five cats'); // "five_cats";
+ *
+ * @param string phrase to underscore
+ * @return string
+ */
+ public static function underscore($str)
+ {
+ return preg_replace('/\s+/', '_', trim($str));
+ }
+
+ /**
+ * Makes an underscored or dashed phrase human-readable.
+ *
+ * $str = Inflector::humanize('kittens-are-cats'); // "kittens are cats"
+ * $str = Inflector::humanize('dogs_as_well'); // "dogs as well"
+ *
+ * @param string phrase to make human-readable
+ * @return string
+ */
+ public static function humanize($str)
+ {
+ return preg_replace('/[_-]+/', ' ', trim($str));
+ }
+
+} // End Inflector
diff --git a/includes/kohana/system/classes/kohana/log.php b/includes/kohana/system/classes/kohana/log.php
new file mode 100644
index 0000000..50f9b6f
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/log.php
@@ -0,0 +1,189 @@
+attach($writer);
+ *
+ * @param object Kohana_Log_Writer instance
+ * @param array messages types to write
+ * @return Kohana_Log
+ */
+ public function attach(Kohana_Log_Writer $writer, array $types = NULL)
+ {
+ $this->_writers["{$writer}"] = array
+ (
+ 'object' => $writer,
+ 'types' => $types
+ );
+
+ return $this;
+ }
+
+ /**
+ * Detaches a log writer. The same writer object must be used.
+ *
+ * $log->detach($writer);
+ *
+ * @param object Kohana_Log_Writer instance
+ * @return Kohana_Log
+ */
+ public function detach(Kohana_Log_Writer $writer)
+ {
+ // Remove the writer
+ unset($this->_writers["{$writer}"]);
+
+ return $this;
+ }
+
+ /**
+ * Adds a message to the log. Replacement values must be passed in to be
+ * replaced using [strtr](http://php.net/strtr).
+ *
+ * $log->add('error', 'Could not locate user: :user', array(
+ * ':user' => $username,
+ * ));
+ *
+ * @param string type of message
+ * @param string message body
+ * @param array values to replace in the message
+ * @return Kohana_Log
+ */
+ public function add($type, $message, array $values = NULL)
+ {
+ if ($values)
+ {
+ // Insert the values into the message
+ $message = strtr($message, $values);
+ }
+
+ // Create a new message and timestamp it
+ $this->_messages[] = array
+ (
+ 'time' => Date::formatted_time('now', self::$timestamp, self::$timezone),
+ 'type' => $type,
+ 'body' => $message,
+ );
+
+ if (self::$write_on_add)
+ {
+ // Write logs as they are added
+ $this->write();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Write and clear all of the messages.
+ *
+ * $log->write();
+ *
+ * @return void
+ */
+ public function write()
+ {
+ if (empty($this->_messages))
+ {
+ // There is nothing to write, move along
+ return;
+ }
+
+ // Import all messages locally
+ $messages = $this->_messages;
+
+ // Reset the messages array
+ $this->_messages = array();
+
+ foreach ($this->_writers as $writer)
+ {
+ if (empty($writer['types']))
+ {
+ // Write all of the messages
+ $writer['object']->write($messages);
+ }
+ else
+ {
+ // Filtered messages
+ $filtered = array();
+
+ foreach ($messages as $message)
+ {
+ if (in_array($message['type'], $writer['types']))
+ {
+ // Writer accepts this kind of message
+ $filtered[] = $message;
+ }
+ }
+
+ // Write the filtered messages
+ $writer['object']->write($filtered);
+ }
+ }
+ }
+
+} // End Kohana_Log
diff --git a/includes/kohana/system/classes/kohana/log/file.php b/includes/kohana/system/classes/kohana/log/file.php
new file mode 100644
index 0000000..488d9ad
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/log/file.php
@@ -0,0 +1,97 @@
+ Kohana::debug_path($directory)));
+ }
+
+ // Determine the directory path
+ $this->_directory = realpath($directory).DIRECTORY_SEPARATOR;
+ }
+
+ /**
+ * Writes each of the messages into the log file. The log file will be
+ * appended to the `YYYY/MM/DD.log.php` file, where YYYY is the current
+ * year, MM is the current month, and DD is the current day.
+ *
+ * $writer->write($messages);
+ *
+ * @param array messages
+ * @return void
+ */
+ public function write(array $messages)
+ {
+ // Set the yearly directory name
+ $directory = $this->_directory.date('Y');
+
+ if ( ! is_dir($directory))
+ {
+ // Create the yearly directory
+ mkdir($directory, 02777);
+
+ // Set permissions (must be manually set to fix umask issues)
+ chmod($directory, 02777);
+ }
+
+ // Add the month to the directory
+ $directory .= DIRECTORY_SEPARATOR.date('m');
+
+ if ( ! is_dir($directory))
+ {
+ // Create the yearly directory
+ mkdir($directory, 02777);
+
+ // Set permissions (must be manually set to fix umask issues)
+ chmod($directory, 02777);
+ }
+
+ // Set the name of the log file
+ $filename = $directory.DIRECTORY_SEPARATOR.date('d').EXT;
+
+ if ( ! file_exists($filename))
+ {
+ // Create the log file
+ file_put_contents($filename, Kohana::FILE_SECURITY.' ?>'.PHP_EOL);
+
+ // Allow anyone to write to log files
+ chmod($filename, 0666);
+ }
+
+ // Set the log line format
+ $format = 'time --- type: body';
+
+ foreach ($messages as $message)
+ {
+ // Write each message into the log file
+ file_put_contents($filename, PHP_EOL.strtr($format, $message), FILE_APPEND);
+ }
+ }
+
+} // End Kohana_Log_File
\ No newline at end of file
diff --git a/includes/kohana/system/classes/kohana/log/stderr.php b/includes/kohana/system/classes/kohana/log/stderr.php
new file mode 100644
index 0000000..e11ce6e
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/log/stderr.php
@@ -0,0 +1,31 @@
+write($messages);
+ *
+ * @param array messages
+ * @return void
+ */
+ public function write(array $messages)
+ {
+ // Set the log line format
+ $format = 'time --- type: body';
+
+ foreach ($messages as $message)
+ {
+ // Writes out each message
+ fwrite(STDERR, PHP_EOL.strtr($format, $message));
+ }
+ }
+} // End Kohana_Log_StdErr
diff --git a/includes/kohana/system/classes/kohana/log/stdout.php b/includes/kohana/system/classes/kohana/log/stdout.php
new file mode 100644
index 0000000..d8cddd1
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/log/stdout.php
@@ -0,0 +1,31 @@
+write($messages);
+ *
+ * @param array messages
+ * @return void
+ */
+ public function write(array $messages)
+ {
+ // Set the log line format
+ $format = 'time --- type: body';
+
+ foreach ($messages as $message)
+ {
+ // Writes out each message
+ fwrite(STDOUT, PHP_EOL.strtr($format, $message));
+ }
+ }
+} // End Kohana_Log_StdOut
diff --git a/includes/kohana/system/classes/kohana/log/syslog.php b/includes/kohana/system/classes/kohana/log/syslog.php
new file mode 100644
index 0000000..afb1149
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/log/syslog.php
@@ -0,0 +1,70 @@
+ LOG_ERR,
+ 'CRITICAL' => LOG_CRIT,
+ 'STRACE' => LOG_ALERT,
+ 'ALERT' => LOG_WARNING,
+ 'INFO' => LOG_INFO,
+ 'DEBUG' => LOG_DEBUG);
+
+ /**
+ * Creates a new syslog logger.
+ *
+ * @see http://us2.php.net/openlog
+ *
+ * @param string syslog identifier
+ * @param int facility to log to
+ * @return void
+ */
+ public function __construct($ident = 'KohanaPHP', $facility = LOG_USER)
+ {
+ $this->_ident = $ident;
+
+ // Open the connection to syslog
+ openlog($this->_ident, LOG_CONS, $facility);
+ }
+
+ /**
+ * Writes each of the messages into the syslog.
+ *
+ * @param array messages
+ * @return void
+ */
+ public function write(array $messages)
+ {
+ foreach ($messages as $message)
+ {
+ syslog($this->_syslog_levels[$message['type']], $message['body']);
+ }
+ }
+
+ /**
+ * Closes the syslog connection
+ *
+ * @return void
+ */
+ public function __destruct()
+ {
+ // Close connection to syslog
+ closelog();
+ }
+
+} // End Kohana_Log_Syslog
\ No newline at end of file
diff --git a/includes/kohana/system/classes/kohana/log/writer.php b/includes/kohana/system/classes/kohana/log/writer.php
new file mode 100644
index 0000000..00ca3a9
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/log/writer.php
@@ -0,0 +1,35 @@
+write($messages);
+ *
+ * @param array messages
+ * @return void
+ */
+ abstract public function write(array $messages);
+
+ /**
+ * Allows the writer to have a unique key when stored.
+ *
+ * echo $writer;
+ *
+ * @return string
+ */
+ final public function __toString()
+ {
+ return spl_object_hash($this);
+ }
+
+} // End Kohana_Log_Writer
diff --git a/includes/kohana/system/classes/kohana/model.php b/includes/kohana/system/classes/kohana/model.php
new file mode 100644
index 0000000..7270df0
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/model.php
@@ -0,0 +1,65 @@
+_db = $db;
+ }
+ elseif ( ! $this->_db)
+ {
+ // Use the global database
+ $this->_db = Database::$default;
+ }
+
+ if (is_string($this->_db))
+ {
+ // Load the database
+ $this->_db = Database::instance($this->_db);
+ }
+ }
+
+} // End Model
diff --git a/includes/kohana/system/classes/kohana/num.php b/includes/kohana/system/classes/kohana/num.php
new file mode 100644
index 0000000..80094e3
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/num.php
@@ -0,0 +1,81 @@
+ 10 AND $number % 100 < 14)
+ {
+ return 'th';
+ }
+
+ switch ($number % 10)
+ {
+ case 1:
+ return 'st';
+ case 2:
+ return 'nd';
+ case 3:
+ return 'rd';
+ default:
+ return 'th';
+ }
+ }
+
+ /**
+ * Locale-aware number and monetary formatting.
+ *
+ * // In English, "1,200.05"
+ * // In Spanish, "1200,05"
+ * // In Portuguese, "1 200,05"
+ * echo Num::format(1200.05, 2);
+ *
+ * // In English, "1,200.05"
+ * // In Spanish, "1.200,05"
+ * // In Portuguese, "1.200.05"
+ * echo Num::format(1200.05, 2, TRUE);
+ *
+ * @param float number to format
+ * @param integer decimal places
+ * @param boolean monetary formatting?
+ * @return string
+ * @since 3.0.2
+ */
+ public static function format($number, $places, $monetary = FALSE)
+ {
+ $info = localeconv();
+
+ if ($monetary)
+ {
+ $decimal = $info['mon_decimal_point'];
+ $thousands = $info['mon_thousands_sep'];
+ }
+ else
+ {
+ $decimal = $info['decimal_point'];
+ $thousands = $info['thousands_sep'];
+ }
+
+ return number_format($number, $places, $decimal, $thousands);
+ }
+
+} // End num
diff --git a/includes/kohana/system/classes/kohana/profiler.php b/includes/kohana/system/classes/kohana/profiler.php
new file mode 100644
index 0000000..481ac9d
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/profiler.php
@@ -0,0 +1,385 @@
+ strtolower($group),
+ 'name' => (string) $name,
+
+ // Start the benchmark
+ 'start_time' => microtime(TRUE),
+ 'start_memory' => memory_get_usage(),
+
+ // Set the stop keys without values
+ 'stop_time' => FALSE,
+ 'stop_memory' => FALSE,
+ );
+
+ return $token;
+ }
+
+ /**
+ * Stops a benchmark.
+ *
+ * Profiler::stop($token);
+ *
+ * @param string token
+ * @return void
+ */
+ public static function stop($token)
+ {
+ // Stop the benchmark
+ Profiler::$_marks[$token]['stop_time'] = microtime(TRUE);
+ Profiler::$_marks[$token]['stop_memory'] = memory_get_usage();
+ }
+
+ /**
+ * Deletes a benchmark. If an error occurs during the benchmark, it is
+ * recommended to delete the benchmark to prevent statistics from being
+ * adversely affected.
+ *
+ * Profiler::delete($token);
+ *
+ * @param string token
+ * @return void
+ */
+ public static function delete($token)
+ {
+ // Remove the benchmark
+ unset(Profiler::$_marks[$token]);
+ }
+
+ /**
+ * Returns all the benchmark tokens by group and name as an array.
+ *
+ * $groups = Profiler::groups();
+ *
+ * @return array
+ */
+ public static function groups()
+ {
+ $groups = array();
+
+ foreach (Profiler::$_marks as $token => $mark)
+ {
+ // Sort the tokens by the group and name
+ $groups[$mark['group']][$mark['name']][] = $token;
+ }
+
+ return $groups;
+ }
+
+ /**
+ * Gets the min, max, average and total of a set of tokens as an array.
+ *
+ * $stats = Profiler::stats($tokens);
+ *
+ * @param array profiler tokens
+ * @return array min, max, average, total
+ * @uses Profiler::total
+ */
+ public static function stats(array $tokens)
+ {
+ // Min and max are unknown by default
+ $min = $max = array(
+ 'time' => NULL,
+ 'memory' => NULL);
+
+ // Total values are always integers
+ $total = array(
+ 'time' => 0,
+ 'memory' => 0);
+
+ foreach ($tokens as $token)
+ {
+ // Get the total time and memory for this benchmark
+ list($time, $memory) = Profiler::total($token);
+
+ if ($max['time'] === NULL OR $time > $max['time'])
+ {
+ // Set the maximum time
+ $max['time'] = $time;
+ }
+
+ if ($min['time'] === NULL OR $time < $min['time'])
+ {
+ // Set the minimum time
+ $min['time'] = $time;
+ }
+
+ // Increase the total time
+ $total['time'] += $time;
+
+ if ($max['memory'] === NULL OR $memory > $max['memory'])
+ {
+ // Set the maximum memory
+ $max['memory'] = $memory;
+ }
+
+ if ($min['memory'] === NULL OR $memory < $min['memory'])
+ {
+ // Set the minimum memory
+ $min['memory'] = $memory;
+ }
+
+ // Increase the total memory
+ $total['memory'] += $memory;
+ }
+
+ // Determine the number of tokens
+ $count = count($tokens);
+
+ // Determine the averages
+ $average = array(
+ 'time' => $total['time'] / $count,
+ 'memory' => $total['memory'] / $count);
+
+ return array(
+ 'min' => $min,
+ 'max' => $max,
+ 'total' => $total,
+ 'average' => $average);
+ }
+
+ /**
+ * Gets the min, max, average and total of profiler groups as an array.
+ *
+ * $stats = Profiler::group_stats('test');
+ *
+ * @param mixed single group name string, or array with group names; all groups by default
+ * @return array min, max, average, total
+ * @uses Profiler::groups
+ * @uses Profiler::stats
+ */
+ public static function group_stats($groups = NULL)
+ {
+ // Which groups do we need to calculate stats for?
+ $groups = ($groups === NULL)
+ ? Profiler::groups()
+ : array_intersect_key(Profiler::groups(), array_flip( (array) $groups));
+
+ // All statistics
+ $stats = array();
+
+ foreach ($groups as $group => $names)
+ {
+ foreach ($names as $name => $tokens)
+ {
+ // Store the stats for each subgroup.
+ // We only need the values for "total".
+ $_stats = Profiler::stats($tokens);
+ $stats[$group][$name] = $_stats['total'];
+ }
+ }
+
+ // Group stats
+ $groups = array();
+
+ foreach ($stats as $group => $names)
+ {
+ // Min and max are unknown by default
+ $groups[$group]['min'] = $groups[$group]['max'] = array(
+ 'time' => NULL,
+ 'memory' => NULL);
+
+ // Total values are always integers
+ $groups[$group]['total'] = array(
+ 'time' => 0,
+ 'memory' => 0);
+
+ foreach ($names as $total)
+ {
+ if ( ! isset($groups[$group]['min']['time']) OR $groups[$group]['min']['time'] > $total['time'])
+ {
+ // Set the minimum time
+ $groups[$group]['min']['time'] = $total['time'];
+ }
+ if ( ! isset($groups[$group]['min']['memory']) OR $groups[$group]['min']['memory'] > $total['memory'])
+ {
+ // Set the minimum memory
+ $groups[$group]['min']['memory'] = $total['memory'];
+ }
+
+ if ( ! isset($groups[$group]['max']['time']) OR $groups[$group]['max']['time'] < $total['time'])
+ {
+ // Set the maximum time
+ $groups[$group]['max']['time'] = $total['time'];
+ }
+ if ( ! isset($groups[$group]['max']['memory']) OR $groups[$group]['max']['memory'] < $total['memory'])
+ {
+ // Set the maximum memory
+ $groups[$group]['max']['memory'] = $total['memory'];
+ }
+
+ // Increase the total time and memory
+ $groups[$group]['total']['time'] += $total['time'];
+ $groups[$group]['total']['memory'] += $total['memory'];
+ }
+
+ // Determine the number of names (subgroups)
+ $count = count($names);
+
+ // Determine the averages
+ $groups[$group]['average']['time'] = $groups[$group]['total']['time'] / $count;
+ $groups[$group]['average']['memory'] = $groups[$group]['total']['memory'] / $count;
+ }
+
+ return $groups;
+ }
+
+ /**
+ * Gets the total execution time and memory usage of a benchmark as a list.
+ *
+ * list($time, $memory) = Profiler::total($token);
+ *
+ * @param string token
+ * @return array execution time, memory
+ */
+ public static function total($token)
+ {
+ // Import the benchmark data
+ $mark = Profiler::$_marks[$token];
+
+ if ($mark['stop_time'] === FALSE)
+ {
+ // The benchmark has not been stopped yet
+ $mark['stop_time'] = microtime(TRUE);
+ $mark['stop_memory'] = memory_get_usage();
+ }
+
+ return array
+ (
+ // Total time in seconds
+ $mark['stop_time'] - $mark['start_time'],
+
+ // Amount of memory in bytes
+ $mark['stop_memory'] - $mark['start_memory'],
+ );
+ }
+
+ /**
+ * Gets the total application run time and memory usage. Caches the result
+ * so that it can be compared between requests.
+ *
+ * list($time, $memory) = Profiler::application();
+ *
+ * @return array execution time, memory
+ * @uses Kohana::cache
+ */
+ public static function application()
+ {
+ // Load the stats from cache, which is valid for 1 day
+ $stats = Kohana::cache('profiler_application_stats', NULL, 3600 * 24);
+
+ if ( ! is_array($stats) OR $stats['count'] > Profiler::$rollover)
+ {
+ // Initialize the stats array
+ $stats = array(
+ 'min' => array(
+ 'time' => NULL,
+ 'memory' => NULL),
+ 'max' => array(
+ 'time' => NULL,
+ 'memory' => NULL),
+ 'total' => array(
+ 'time' => NULL,
+ 'memory' => NULL),
+ 'count' => 0);
+ }
+
+ // Get the application run time
+ $time = microtime(TRUE) - KOHANA_START_TIME;
+
+ // Get the total memory usage
+ $memory = memory_get_usage() - KOHANA_START_MEMORY;
+
+ // Calculate max time
+ if ($stats['max']['time'] === NULL OR $time > $stats['max']['time'])
+ {
+ $stats['max']['time'] = $time;
+ }
+
+ // Calculate min time
+ if ($stats['min']['time'] === NULL OR $time < $stats['min']['time'])
+ {
+ $stats['min']['time'] = $time;
+ }
+
+ // Add to total time
+ $stats['total']['time'] += $time;
+
+ // Calculate max memory
+ if ($stats['max']['memory'] === NULL OR $memory > $stats['max']['memory'])
+ {
+ $stats['max']['memory'] = $memory;
+ }
+
+ // Calculate min memory
+ if ($stats['min']['memory'] === NULL OR $memory < $stats['min']['memory'])
+ {
+ $stats['min']['memory'] = $memory;
+ }
+
+ // Add to total memory
+ $stats['total']['memory'] += $memory;
+
+ // Another mark has been added to the stats
+ $stats['count']++;
+
+ // Determine the averages
+ $stats['average'] = array(
+ 'time' => $stats['total']['time'] / $stats['count'],
+ 'memory' => $stats['total']['memory'] / $stats['count']);
+
+ // Cache the new stats
+ Kohana::cache('profiler_application_stats', $stats);
+
+ // Set the current application execution time and memory
+ // Do NOT cache these, they are specific to the current request only
+ $stats['current']['time'] = $time;
+ $stats['current']['memory'] = $memory;
+
+ // Return the total application run time and memory usage
+ return $stats;
+ }
+
+} // End Profiler
diff --git a/includes/kohana/system/classes/kohana/remote.php b/includes/kohana/system/classes/kohana/remote.php
new file mode 100644
index 0000000..bfecec3
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/remote.php
@@ -0,0 +1,154 @@
+ 'Mozilla/5.0 (compatible; Kohana v3.0 +http://kohanaframework.org/)',
+ CURLOPT_CONNECTTIMEOUT => 5,
+ CURLOPT_TIMEOUT => 5,
+ );
+
+ /**
+ * Returns the output of a remote URL. Any [curl option](http://php.net/curl_setopt)
+ * may be used.
+ *
+ * // Do a simple GET request
+ * $data = Remote::get($url);
+ *
+ * // Do a POST request
+ * $data = Remote::get($url, array(
+ * CURLOPT_POST => TRUE,
+ * CURLOPT_POSTFIELDS => http_build_query($array),
+ * ));
+ *
+ * @param string remote URL
+ * @param array curl options
+ * @return string
+ * @throws Kohana_Exception
+ */
+ public static function get($url, array $options = NULL)
+ {
+ if ($options === NULL)
+ {
+ // Use default options
+ $options = Remote::$default_options;
+ }
+ else
+ {
+ // Add default options
+ $options = $options + Remote::$default_options;
+ }
+
+ // The transfer must always be returned
+ $options[CURLOPT_RETURNTRANSFER] = TRUE;
+
+ // Open a new remote connection
+ $remote = curl_init($url);
+
+ // Set connection options
+ if ( ! curl_setopt_array($remote, $options))
+ {
+ throw new Kohana_Exception('Failed to set CURL options, check CURL documentation: :url',
+ array(':url' => 'http://php.net/curl_setopt_array'));
+ }
+
+ // Get the response
+ $response = curl_exec($remote);
+
+ // Get the response information
+ $code = curl_getinfo($remote, CURLINFO_HTTP_CODE);
+
+ if ($code AND $code < 200 OR $code > 299)
+ {
+ $error = $response;
+ }
+ elseif ($response === FALSE)
+ {
+ $error = curl_error($remote);
+ }
+
+ // Close the connection
+ curl_close($remote);
+
+ if (isset($error))
+ {
+ throw new Kohana_Exception('Error fetching remote :url [ status :code ] :error',
+ array(':url' => $url, ':code' => $code, ':error' => $error));
+ }
+
+ return $response;
+ }
+
+ /**
+ * Returns the status code (200, 500, etc) for a URL.
+ *
+ * $status = Remote::status($url);
+ *
+ * @param string URL to check
+ * @return integer
+ */
+ public static function status($url)
+ {
+ // Get the hostname and path
+ $url = parse_url($url);
+
+ if (empty($url['path']))
+ {
+ // Request the root document
+ $url['path'] = '/';
+ }
+
+ // Open a remote connection
+ $port = isset($url['port']) ? $url['port'] : 80;
+ $remote = fsockopen($url['host'], $port, $errno, $errstr, 5);
+
+ if ( ! is_resource($remote))
+ return FALSE;
+
+ // Set CRLF
+ $line_feed = "\r\n";
+
+ // Send request
+ fwrite($remote, 'HEAD '.$url['path'].' HTTP/1.0'.$line_feed);
+ fwrite($remote, 'Host: '.$url['host'].$line_feed);
+ fwrite($remote, 'Connection: close'.$line_feed);
+ fwrite($remote, 'User-Agent: Kohana Framework (+http://kohanaframework.org/)'.$line_feed);
+
+ // Send one more CRLF to terminate the headers
+ fwrite($remote, $line_feed);
+
+ // Remote is offline
+ $response = FALSE;
+
+ while ( ! feof($remote))
+ {
+ // Get the line
+ $line = trim(fgets($remote, 512));
+
+ if ($line !== '' AND preg_match('#^HTTP/1\.[01] (\d{3})#', $line, $matches))
+ {
+ // Response code found
+ $response = (int) $matches[1];
+ break;
+ }
+ }
+
+ // Close the connection
+ fclose($remote);
+
+ return $response;
+ }
+
+} // End remote
diff --git a/includes/kohana/system/classes/kohana/request.php b/includes/kohana/system/classes/kohana/request.php
new file mode 100644
index 0000000..a295b34
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/request.php
@@ -0,0 +1,1305 @@
+ 'Continue',
+ 101 => 'Switching Protocols',
+
+ // Success 2xx
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-Status',
+
+ // Redirection 3xx
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found', // 1.1
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ // 306 is deprecated but reserved
+ 307 => 'Temporary Redirect',
+
+ // Client Error 4xx
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+
+ // Server Error 5xx
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ 507 => 'Insufficient Storage',
+ 509 => 'Bandwidth Limit Exceeded'
+ );
+
+ /**
+ * @var string method: GET, POST, PUT, DELETE, etc
+ */
+ public static $method = 'GET';
+
+ /**
+ * @var string protocol: http, https, ftp, cli, etc
+ */
+ public static $protocol = 'http';
+
+ /**
+ * @var string referring URL
+ */
+ public static $referrer;
+
+ /**
+ * @var string client user agent
+ */
+ public static $user_agent = '';
+
+ /**
+ * @var string client IP address
+ */
+ public static $client_ip = '0.0.0.0';
+
+ /**
+ * @var boolean AJAX-generated request
+ */
+ public static $is_ajax = FALSE;
+
+ /**
+ * @var Request main request instance
+ */
+ public static $instance;
+
+ /**
+ * @var Request currently executing request instance
+ */
+ public static $current;
+
+ /**
+ * Main request singleton instance. If no URI is provided, the URI will
+ * be automatically detected.
+ *
+ * $request = Request::instance();
+ *
+ * @param string URI of the request
+ * @return Request
+ * @uses Request::detect_uri
+ */
+ public static function instance( & $uri = TRUE)
+ {
+ if ( ! Request::$instance)
+ {
+ if (Kohana::$is_cli)
+ {
+ // Default protocol for command line is cli://
+ Request::$protocol = 'cli';
+
+ // Get the command line options
+ $options = CLI::options('uri', 'method', 'get', 'post');
+
+ if (isset($options['uri']))
+ {
+ // Use the specified URI
+ $uri = $options['uri'];
+ }
+
+ if (isset($options['method']))
+ {
+ // Use the specified method
+ Request::$method = strtoupper($options['method']);
+ }
+
+ if (isset($options['get']))
+ {
+ // Overload the global GET data
+ parse_str($options['get'], $_GET);
+ }
+
+ if (isset($options['post']))
+ {
+ // Overload the global POST data
+ parse_str($options['post'], $_POST);
+ }
+ }
+ else
+ {
+ if (isset($_SERVER['REQUEST_METHOD']))
+ {
+ // Use the server request method
+ Request::$method = $_SERVER['REQUEST_METHOD'];
+ }
+
+ if ( ! empty($_SERVER['HTTPS']) AND filter_var($_SERVER['HTTPS'], FILTER_VALIDATE_BOOLEAN))
+ {
+ // This request is secure
+ Request::$protocol = 'https';
+ }
+
+ if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) AND strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest')
+ {
+ // This request is an AJAX request
+ Request::$is_ajax = TRUE;
+ }
+
+ if (isset($_SERVER['HTTP_REFERER']))
+ {
+ // There is a referrer for this request
+ Request::$referrer = $_SERVER['HTTP_REFERER'];
+ }
+
+ if (isset($_SERVER['HTTP_USER_AGENT']))
+ {
+ // Set the client user agent
+ Request::$user_agent = $_SERVER['HTTP_USER_AGENT'];
+ }
+
+ if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
+ {
+ // Use the forwarded IP address, typically set when the
+ // client is using a proxy server.
+ Request::$client_ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
+ }
+ elseif (isset($_SERVER['HTTP_CLIENT_IP']))
+ {
+ // Use the forwarded IP address, typically set when the
+ // client is using a proxy server.
+ Request::$client_ip = $_SERVER['HTTP_CLIENT_IP'];
+ }
+ elseif (isset($_SERVER['REMOTE_ADDR']))
+ {
+ // The remote IP address
+ Request::$client_ip = $_SERVER['REMOTE_ADDR'];
+ }
+
+ if (Request::$method !== 'GET' AND Request::$method !== 'POST')
+ {
+ // Methods besides GET and POST do not properly parse the form-encoded
+ // query string into the $_POST array, so we overload it manually.
+ parse_str(file_get_contents('php://input'), $_POST);
+ }
+
+ if ($uri === TRUE)
+ {
+ $uri = Request::detect_uri();
+ }
+ }
+
+ // Reduce multiple slashes to a single slash
+ $uri = preg_replace('#//+#', '/', $uri);
+
+ // Remove all dot-paths from the URI, they are not valid
+ $uri = preg_replace('#\.[\s./]*/#', '', $uri);
+
+ // Create the instance singleton
+ Request::$instance = Request::$current = new Request($uri);
+
+ // Add the default Content-Type header
+ Request::$instance->headers['Content-Type'] = 'text/html; charset='.Kohana::$charset;
+ }
+
+ return Request::$instance;
+ }
+
+ /**
+ * Automatically detects the URI of the main request using PATH_INFO,
+ * REQUEST_URI, PHP_SELF or REDIRECT_URL.
+ *
+ * $uri = Request::detect_uri();
+ *
+ * @return string URI of the main request
+ * @throws Kohana_Exception
+ * @since 3.0.8
+ */
+ public static function detect_uri()
+ {
+ if ( ! empty($_SERVER['PATH_INFO']))
+ {
+ // PATH_INFO does not contain the docroot or index
+ $uri = $_SERVER['PATH_INFO'];
+ }
+ else
+ {
+ // REQUEST_URI and PHP_SELF include the docroot and index
+
+ if (isset($_SERVER['REQUEST_URI']))
+ {
+ // REQUEST_URI includes the query string, remove it
+ $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
+
+ // Decode the request URI
+ $uri = rawurldecode($uri);
+ }
+ elseif (isset($_SERVER['PHP_SELF']))
+ {
+ $uri = $_SERVER['PHP_SELF'];
+ }
+ elseif (isset($_SERVER['REDIRECT_URL']))
+ {
+ $uri = $_SERVER['REDIRECT_URL'];
+ }
+ else
+ {
+ // If you ever see this error, please report an issue at http://dev.kohanaphp.com/projects/kohana3/issues
+ // along with any relevant information about your web server setup. Thanks!
+ throw new Kohana_Exception('Unable to detect the URI using PATH_INFO, REQUEST_URI, PHP_SELF or REDIRECT_URL');
+ }
+
+ // Get the path from the base URL, including the index file
+ $base_url = parse_url(Kohana::$base_url, PHP_URL_PATH);
+
+ if (strpos($uri, $base_url) === 0)
+ {
+ // Remove the base URL from the URI
+ $uri = (string) substr($uri, strlen($base_url));
+ }
+
+ if (Kohana::$index_file AND strpos($uri, Kohana::$index_file) === 0)
+ {
+ // Remove the index file from the URI
+ $uri = (string) substr($uri, strlen(Kohana::$index_file));
+ }
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Return the currently executing request. This is changed to the current
+ * request when [Request::execute] is called and restored when the request
+ * is completed.
+ *
+ * $request = Request::current();
+ *
+ * @return Request
+ * @since 3.0.5
+ */
+ public static function current()
+ {
+ return Request::$current;
+ }
+
+ /**
+ * Creates a new request object for the given URI. This differs from
+ * [Request::instance] in that it does not automatically detect the URI
+ * and should only be used for creating HMVC requests.
+ *
+ * $request = Request::factory($uri);
+ *
+ * @param string URI of the request
+ * @return Request
+ */
+ public static function factory($uri)
+ {
+ return new Request($uri);
+ }
+
+ /**
+ * Returns information about the client user agent.
+ *
+ * // Returns "Chrome" when using Google Chrome
+ * $browser = Request::user_agent('browser');
+ *
+ * Multiple values can be returned at once by using an array:
+ *
+ * // Get the browser and platform with a single call
+ * $info = Request::user_agent(array('browser', 'platform'));
+ *
+ * When using an array for the value, an associative array will be returned.
+ *
+ * @param mixed string to return: browser, version, robot, mobile, platform; or array of values
+ * @return mixed requested information, FALSE if nothing is found
+ * @uses Kohana::config
+ * @uses Request::$user_agent
+ */
+ public static function user_agent($value)
+ {
+ if (is_array($value))
+ {
+ $agent = array();
+ foreach ($value as $v)
+ {
+ // Add each key to the set
+ $agent[$v] = Request::user_agent($v);
+ }
+
+ return $agent;
+ }
+
+ static $info;
+
+ if (isset($info[$value]))
+ {
+ // This value has already been found
+ return $info[$value];
+ }
+
+ if ($value === 'browser' OR $value == 'version')
+ {
+ // Load browsers
+ $browsers = Kohana::config('user_agents')->browser;
+
+ foreach ($browsers as $search => $name)
+ {
+ if (stripos(Request::$user_agent, $search) !== FALSE)
+ {
+ // Set the browser name
+ $info['browser'] = $name;
+
+ if (preg_match('#'.preg_quote($search).'[^0-9.]*+([0-9.][0-9.a-z]*)#i', Request::$user_agent, $matches))
+ {
+ // Set the version number
+ $info['version'] = $matches[1];
+ }
+ else
+ {
+ // No version number found
+ $info['version'] = FALSE;
+ }
+
+ return $info[$value];
+ }
+ }
+ }
+ else
+ {
+ // Load the search group for this type
+ $group = Kohana::config('user_agents')->$value;
+
+ foreach ($group as $search => $name)
+ {
+ if (stripos(Request::$user_agent, $search) !== FALSE)
+ {
+ // Set the value name
+ return $info[$value] = $name;
+ }
+ }
+ }
+
+ // The value requested could not be found
+ return $info[$value] = FALSE;
+ }
+
+ /**
+ * Returns the accepted content types. If a specific type is defined,
+ * the quality of that type will be returned.
+ *
+ * $types = Request::accept_type();
+ *
+ * @param string content MIME type
+ * @return float when checking a specific type
+ * @return array
+ * @uses Request::_parse_accept
+ */
+ public static function accept_type($type = NULL)
+ {
+ static $accepts;
+
+ if ($accepts === NULL)
+ {
+ // Parse the HTTP_ACCEPT header
+ $accepts = Request::_parse_accept($_SERVER['HTTP_ACCEPT'], array('*/*' => 1.0));
+ }
+
+ if (isset($type))
+ {
+ // Return the quality setting for this type
+ return isset($accepts[$type]) ? $accepts[$type] : $accepts['*/*'];
+ }
+
+ return $accepts;
+ }
+
+ /**
+ * Returns the accepted languages. If a specific language is defined,
+ * the quality of that language will be returned. If the language is not
+ * accepted, FALSE will be returned.
+ *
+ * $langs = Request::accept_lang();
+ *
+ * @param string language code
+ * @return float when checking a specific language
+ * @return array
+ * @uses Request::_parse_accept
+ */
+ public static function accept_lang($lang = NULL)
+ {
+ static $accepts;
+
+ if ($accepts === NULL)
+ {
+ // Parse the HTTP_ACCEPT_LANGUAGE header
+ $accepts = Request::_parse_accept($_SERVER['HTTP_ACCEPT_LANGUAGE']);
+ }
+
+ if (isset($lang))
+ {
+ // Return the quality setting for this lang
+ return isset($accepts[$lang]) ? $accepts[$lang] : FALSE;
+ }
+
+ return $accepts;
+ }
+
+ /**
+ * Returns the accepted encodings. If a specific encoding is defined,
+ * the quality of that encoding will be returned. If the encoding is not
+ * accepted, FALSE will be returned.
+ *
+ * $encodings = Request::accept_encoding();
+ *
+ * @param string encoding type
+ * @return float when checking a specific encoding
+ * @return array
+ * @uses Request::_parse_accept
+ */
+ public static function accept_encoding($type = NULL)
+ {
+ static $accepts;
+
+ if ($accepts === NULL)
+ {
+ // Parse the HTTP_ACCEPT_LANGUAGE header
+ $accepts = Request::_parse_accept($_SERVER['HTTP_ACCEPT_ENCODING']);
+ }
+
+ if (isset($type))
+ {
+ // Return the quality setting for this type
+ return isset($accepts[$type]) ? $accepts[$type] : FALSE;
+ }
+
+ return $accepts;
+ }
+
+ /**
+ * Parses an accept header and returns an array (type => quality) of the
+ * accepted types, ordered by quality.
+ *
+ * $accept = Request::_parse_accept($header, $defaults);
+ *
+ * @param string header to parse
+ * @param array default values
+ * @return array
+ */
+ protected static function _parse_accept( & $header, array $accepts = NULL)
+ {
+ if ( ! empty($header))
+ {
+ // Get all of the types
+ $types = explode(',', $header);
+
+ foreach ($types as $type)
+ {
+ // Split the type into parts
+ $parts = explode(';', $type);
+
+ // Make the type only the MIME
+ $type = trim(array_shift($parts));
+
+ // Default quality is 1.0
+ $quality = 1.0;
+
+ foreach ($parts as $part)
+ {
+ // Prevent undefined $value notice below
+ if (strpos($part, '=') === FALSE)
+ continue;
+
+ // Separate the key and value
+ list ($key, $value) = explode('=', trim($part));
+
+ if ($key === 'q')
+ {
+ // There is a quality for this type
+ $quality = (float) trim($value);
+ }
+ }
+
+ // Add the accept type and quality
+ $accepts[$type] = $quality;
+ }
+ }
+
+ // Make sure that accepts is an array
+ $accepts = (array) $accepts;
+
+ // Order by quality
+ arsort($accepts);
+
+ return $accepts;
+ }
+
+ /**
+ * @var object route matched for this request
+ */
+ public $route;
+
+ /**
+ * @var integer HTTP response code: 200, 404, 500, etc
+ */
+ public $status = 200;
+
+ /**
+ * @var string response body
+ */
+ public $response = '';
+
+ /**
+ * @var array headers to send with the response body
+ */
+ public $headers = array();
+
+ /**
+ * @var string controller directory
+ */
+ public $directory = '';
+
+ /**
+ * @var string controller to be executed
+ */
+ public $controller;
+
+ /**
+ * @var string action to be executed in the controller
+ */
+ public $action;
+
+ /**
+ * @var string the URI of the request
+ */
+ public $uri;
+
+ // Parameters extracted from the route
+ protected $_params;
+
+ /**
+ * Creates a new request object for the given URI. New requests should be
+ * created using the [Request::instance] or [Request::factory] methods.
+ *
+ * $request = new Request($uri);
+ *
+ * @param string URI of the request
+ * @return void
+ * @throws Kohana_Request_Exception
+ * @uses Route::all
+ * @uses Route::matches
+ */
+ public function __construct($uri)
+ {
+ // Remove trailing slashes from the URI
+ $uri = trim($uri, '/');
+
+ // Load routes
+ $routes = Route::all();
+
+ foreach ($routes as $name => $route)
+ {
+ if ($params = $route->matches($uri))
+ {
+ // Store the URI
+ $this->uri = $uri;
+
+ // Store the matching route
+ $this->route = $route;
+
+ if (isset($params['directory']))
+ {
+ // Controllers are in a sub-directory
+ $this->directory = $params['directory'];
+ }
+
+ // Store the controller
+ $this->controller = $params['controller'];
+
+ if (isset($params['action']))
+ {
+ // Store the action
+ $this->action = $params['action'];
+ }
+ else
+ {
+ // Use the default action
+ $this->action = Route::$default_action;
+ }
+
+ // These are accessible as public vars and can be overloaded
+ unset($params['controller'], $params['action'], $params['directory']);
+
+ // Params cannot be changed once matched
+ $this->_params = $params;
+
+ return;
+ }
+ }
+
+ // No matching route for this URI
+ $this->status = 404;
+
+ throw new Kohana_Request_Exception('Unable to find a route to match the URI: :uri',
+ array(':uri' => $uri));
+ }
+
+ /**
+ * Returns the response as the string representation of a request.
+ *
+ * echo $request;
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return (string) $this->response;
+ }
+
+ /**
+ * Generates a relative URI for the current route.
+ *
+ * $request->uri($params);
+ *
+ * @param array additional route parameters
+ * @return string
+ * @uses Route::uri
+ */
+ public function uri(array $params = NULL)
+ {
+ if ( ! isset($params['directory']))
+ {
+ // Add the current directory
+ $params['directory'] = $this->directory;
+ }
+
+ if ( ! isset($params['controller']))
+ {
+ // Add the current controller
+ $params['controller'] = $this->controller;
+ }
+
+ if ( ! isset($params['action']))
+ {
+ // Add the current action
+ $params['action'] = $this->action;
+ }
+
+ // Add the current parameters
+ $params += $this->_params;
+
+ return $this->route->uri($params);
+ }
+
+ /**
+ * Create a URL from the current request. This is a shortcut for:
+ *
+ * echo URL::site($this->request->uri($params), $protocol);
+ *
+ * @param string route name
+ * @param array URI parameters
+ * @param mixed protocol string or boolean, adds protocol and domain
+ * @return string
+ * @since 3.0.7
+ * @uses URL::site
+ */
+ public function url(array $params = NULL, $protocol = NULL)
+ {
+ // Create a URI with the current route and convert it to a URL
+ return URL::site($this->uri($params), $protocol);
+ }
+
+ /**
+ * Retrieves a value from the route parameters.
+ *
+ * $id = $request->param('id');
+ *
+ * @param string key of the value
+ * @param mixed default value if the key is not set
+ * @return mixed
+ */
+ public function param($key = NULL, $default = NULL)
+ {
+ if ($key === NULL)
+ {
+ // Return the full array
+ return $this->_params;
+ }
+
+ return isset($this->_params[$key]) ? $this->_params[$key] : $default;
+ }
+
+ /**
+ * Sends the response status and all set headers. The current server
+ * protocol (HTTP/1.0 or HTTP/1.1) will be used when available. If not
+ * available, HTTP/1.1 will be used.
+ *
+ * $request->send_headers();
+ *
+ * @return $this
+ * @uses Request::$messages
+ */
+ public function send_headers()
+ {
+ if ( ! headers_sent())
+ {
+ if (isset($_SERVER['SERVER_PROTOCOL']))
+ {
+ // Use the default server protocol
+ $protocol = $_SERVER['SERVER_PROTOCOL'];
+ }
+ else
+ {
+ // Default to using newer protocol
+ $protocol = 'HTTP/1.1';
+ }
+
+ // HTTP status line
+ header($protocol.' '.$this->status.' '.Request::$messages[$this->status]);
+
+ foreach ($this->headers as $name => $value)
+ {
+ if (is_string($name))
+ {
+ // Combine the name and value to make a raw header
+ $value = "{$name}: {$value}";
+ }
+
+ // Send the raw header
+ header($value, TRUE);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Redirects as the request response. If the URL does not include a
+ * protocol, it will be converted into a complete URL.
+ *
+ * $request->redirect($url);
+ *
+ * [!!] No further processing can be done after this method is called!
+ *
+ * @param string redirect location
+ * @param integer status code: 301, 302, etc
+ * @return void
+ * @uses URL::site
+ * @uses Request::send_headers
+ */
+ public function redirect($url = '', $code = 302)
+ {
+ if (strpos($url, '://') === FALSE)
+ {
+ // Make the URI into a URL
+ $url = URL::site($url, TRUE);
+ }
+
+ // Set the response status
+ $this->status = $code;
+
+ // Set the location header
+ $this->headers['Location'] = $url;
+
+ // Send headers
+ $this->send_headers();
+
+ // Stop execution
+ exit;
+ }
+
+ /**
+ * Send file download as the response. All execution will be halted when
+ * this method is called! Use TRUE for the filename to send the current
+ * response as the file content. The third parameter allows the following
+ * options to be set:
+ *
+ * Type | Option | Description | Default Value
+ * ----------|-----------|------------------------------------|--------------
+ * `boolean` | inline | Display inline instead of download | `FALSE`
+ * `string` | mime_type | Manual mime type | Automatic
+ * `boolean` | delete | Delete the file after sending | `FALSE`
+ *
+ * Download a file that already exists:
+ *
+ * $request->send_file('media/packages/kohana.zip');
+ *
+ * Download generated content as a file:
+ *
+ * $request->response = $content;
+ * $request->send_file(TRUE, $filename);
+ *
+ * [!!] No further processing can be done after this method is called!
+ *
+ * @param string filename with path, or TRUE for the current response
+ * @param string downloaded file name
+ * @param array additional options
+ * @return void
+ * @throws Kohana_Exception
+ * @uses File::mime_by_ext
+ * @uses File::mime
+ * @uses Request::send_headers
+ */
+ public function send_file($filename, $download = NULL, array $options = NULL)
+ {
+ if ( ! empty($options['mime_type']))
+ {
+ // The mime-type has been manually set
+ $mime = $options['mime_type'];
+ }
+
+ if ($filename === TRUE)
+ {
+ if (empty($download))
+ {
+ throw new Kohana_Exception('Download name must be provided for streaming files');
+ }
+
+ // Temporary files will automatically be deleted
+ $options['delete'] = FALSE;
+
+ if ( ! isset($mime))
+ {
+ // Guess the mime using the file extension
+ $mime = File::mime_by_ext(strtolower(pathinfo($download, PATHINFO_EXTENSION)));
+ }
+
+ // Force the data to be rendered if
+ $file_data = (string) $this->response;
+
+ // Get the content size
+ $size = strlen($file_data);
+
+ // Create a temporary file to hold the current response
+ $file = tmpfile();
+
+ // Write the current response into the file
+ fwrite($file, $file_data);
+
+ // File data is no longer needed
+ unset($file_data);
+ }
+ else
+ {
+ // Get the complete file path
+ $filename = realpath($filename);
+
+ if (empty($download))
+ {
+ // Use the file name as the download file name
+ $download = pathinfo($filename, PATHINFO_BASENAME);
+ }
+
+ // Get the file size
+ $size = filesize($filename);
+
+ if ( ! isset($mime))
+ {
+ // Get the mime type
+ $mime = File::mime($filename);
+ }
+
+ // Open the file for reading
+ $file = fopen($filename, 'rb');
+ }
+
+ if ( ! is_resource($file))
+ {
+ throw new Kohana_Exception('Could not read file to send: :file', array(
+ ':file' => $download,
+ ));
+ }
+
+ // Inline or download?
+ $disposition = empty($options['inline']) ? 'attachment' : 'inline';
+
+ // Calculate byte range to download.
+ list($start, $end) = $this->_calculate_byte_range($size);
+
+ if ( ! empty($options['resumable']))
+ {
+ if ($start > 0 OR $end < ($size - 1))
+ {
+ // Partial Content
+ $this->status = 206;
+ }
+
+ // Range of bytes being sent
+ $this->headers['Content-Range'] = 'bytes '.$start.'-'.$end.'/'.$size;
+ $this->headers['Accept-Ranges'] = 'bytes';
+ }
+
+ // Set the headers for a download
+ $this->headers['Content-Disposition'] = $disposition.'; filename="'.$download.'"';
+ $this->headers['Content-Type'] = $mime;
+ $this->headers['Content-Length'] = ($end - $start) + 1;
+
+ if (Request::user_agent('browser') === 'Internet Explorer')
+ {
+ // Naturally, IE does not act like a real browser...
+ if (Request::$protocol === 'https')
+ {
+ // http://support.microsoft.com/kb/316431
+ $this->headers['Pragma'] = $this->headers['Cache-Control'] = 'public';
+ }
+
+ if (version_compare(Request::user_agent('version'), '8.0', '>='))
+ {
+ // http://ajaxian.com/archives/ie-8-security
+ $this->headers['X-Content-Type-Options'] = 'nosniff';
+ }
+ }
+
+ // Send all headers now
+ $this->send_headers();
+
+ while (ob_get_level())
+ {
+ // Flush all output buffers
+ ob_end_flush();
+ }
+
+ // Manually stop execution
+ ignore_user_abort(TRUE);
+
+ if ( ! Kohana::$safe_mode)
+ {
+ // Keep the script running forever
+ set_time_limit(0);
+ }
+
+ // Send data in 16kb blocks
+ $block = 1024 * 16;
+
+ fseek($file, $start);
+
+ while ( ! feof($file) AND ($pos = ftell($file)) <= $end)
+ {
+ if (connection_aborted())
+ break;
+
+ if ($pos + $block > $end)
+ {
+ // Don't read past the buffer.
+ $block = $end - $pos + 1;
+ }
+
+ // Output a block of the file
+ echo fread($file, $block);
+
+ // Send the data now
+ flush();
+ }
+
+ // Close the file
+ fclose($file);
+
+ if ( ! empty($options['delete']))
+ {
+ try
+ {
+ // Attempt to remove the file
+ unlink($filename);
+ }
+ catch (Exception $e)
+ {
+ // Create a text version of the exception
+ $error = Kohana::exception_text($e);
+
+ if (is_object(Kohana::$log))
+ {
+ // Add this exception to the log
+ Kohana::$log->add(Kohana::ERROR, $error);
+
+ // Make sure the logs are written
+ Kohana::$log->write();
+ }
+
+ // Do NOT display the exception, it will corrupt the output!
+ }
+ }
+
+ // Stop execution
+ exit;
+ }
+
+ /**
+ * Parse the byte ranges from the HTTP_RANGE header used for
+ * resumable downloads.
+ *
+ * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
+ * @return array|FALSE
+ */
+ protected function _parse_byte_range()
+ {
+ if ( ! isset($_SERVER['HTTP_RANGE']))
+ {
+ return FALSE;
+ }
+
+ // TODO, speed this up with the use of string functions.
+ preg_match_all('/(-?[0-9]++(?:-(?![0-9]++))?)(?:-?([0-9]++))?/', $_SERVER['HTTP_RANGE'], $matches, PREG_SET_ORDER);
+
+ return $matches[0];
+ }
+
+ /**
+ * Calculates the byte range to use with send_file. If HTTP_RANGE doesn't
+ * exist then the complete byte range is returned
+ *
+ * @param integer $size
+ * @return array
+ */
+ protected function _calculate_byte_range($size)
+ {
+ // Defaults to start with when the HTTP_RANGE header doesn't exist.
+ $start = 0;
+ $end = $size - 1;
+
+ if ($range = $this->_parse_byte_range())
+ {
+ // We have a byte range from HTTP_RANGE
+ $start = $range[1];
+
+ if ($start[0] === '-')
+ {
+ // A negative value means we start from the end, so -500 would be the
+ // last 500 bytes.
+ $start = $size - abs($start);
+ }
+
+ if (isset($range[2]))
+ {
+ // Set the end range
+ $end = $range[2];
+ }
+ }
+
+ // Normalize values.
+ $start = abs(intval($start));
+
+ // Keep the the end value in bounds and normalize it.
+ $end = min(abs(intval($end)), $size - 1);
+
+ // Keep the start in bounds.
+ $start = ($end < $start) ? 0 : max($start, 0);
+
+ return array($start, $end);
+ }
+
+ /**
+ * Processes the request, executing the controller action that handles this
+ * request, determined by the [Route].
+ *
+ * 1. Before the controller action is called, the [Controller::before] method
+ * will be called.
+ * 2. Next the controller action will be called.
+ * 3. After the controller action is called, the [Controller::after] method
+ * will be called.
+ *
+ * By default, the output from the controller is captured and returned, and
+ * no headers are sent.
+ *
+ * $request->execute();
+ *
+ * @return $this
+ * @throws Kohana_Exception
+ * @uses [Kohana::$profiling]
+ * @uses [Profiler]
+ */
+ public function execute()
+ {
+ // Create the class prefix
+ $prefix = 'controller_';
+
+ if ($this->directory)
+ {
+ // Add the directory name to the class prefix
+ $prefix .= str_replace(array('\\', '/'), '_', trim($this->directory, '/')).'_';
+ }
+
+ if (Kohana::$profiling)
+ {
+ // Set the benchmark name
+ $benchmark = '"'.$this->uri.'"';
+
+ if ($this !== Request::$instance AND Request::$current)
+ {
+ // Add the parent request uri
+ $benchmark .= ' « "'.Request::$current->uri.'"';
+ }
+
+ // Start benchmarking
+ $benchmark = Profiler::start('Requests', $benchmark);
+ }
+
+ // Store the currently active request
+ $previous = Request::$current;
+
+ // Change the current request to this request
+ Request::$current = $this;
+
+ try
+ {
+ // Load the controller using reflection
+ $class = new ReflectionClass($prefix.$this->controller);
+
+ if ($class->isAbstract())
+ {
+ throw new Kohana_Exception('Cannot create instances of abstract :controller',
+ array(':controller' => $prefix.$this->controller));
+ }
+
+ // Create a new instance of the controller
+ $controller = $class->newInstance($this);
+
+ // Execute the "before action" method
+ $class->getMethod('before')->invoke($controller);
+
+ // Determine the action to use
+ $action = empty($this->action) ? Route::$default_action : $this->action;
+
+ // Execute the main action with the parameters
+ $class->getMethod('action_'.$action)->invokeArgs($controller, $this->_params);
+
+ // Execute the "after action" method
+ $class->getMethod('after')->invoke($controller);
+ }
+ catch (Exception $e)
+ {
+ // Restore the previous request
+ Request::$current = $previous;
+
+ if (isset($benchmark))
+ {
+ // Delete the benchmark, it is invalid
+ Profiler::delete($benchmark);
+ }
+
+ if ($e instanceof ReflectionException)
+ {
+ // Reflection will throw exceptions for missing classes or actions
+ $this->status = 404;
+ }
+ else
+ {
+ // All other exceptions are PHP/server errors
+ $this->status = 500;
+ }
+
+ // Re-throw the exception
+ throw $e;
+ }
+
+ // Restore the previous request
+ Request::$current = $previous;
+
+ if (isset($benchmark))
+ {
+ // Stop the benchmark
+ Profiler::stop($benchmark);
+ }
+
+ return $this;
+ }
+
+
+ /**
+ * Generates an [ETag](http://en.wikipedia.org/wiki/HTTP_ETag) from the
+ * request response.
+ *
+ * $etag = $request->generate_etag();
+ *
+ * [!!] If the request response is empty when this method is called, an
+ * exception will be thrown!
+ *
+ * @return string
+ * @throws Kohana_Request_Exception
+ */
+ public function generate_etag()
+ {
+ if ($this->response === NULL)
+ {
+ throw new Kohana_Request_Exception('No response yet associated with request - cannot auto generate resource ETag');
+ }
+
+ // Generate a unique hash for the response
+ return '"'.sha1($this->response).'"';
+ }
+
+
+ /**
+ * Checks the browser cache to see the response needs to be returned.
+ *
+ * $request->check_cache($etag);
+ *
+ * [!!] If the cache check succeeds, no further processing can be done!
+ *
+ * @param string etag to check
+ * @return $this
+ * @throws Kohana_Request_Exception
+ * @uses Request::generate_etag
+ */
+ public function check_cache($etag = null)
+ {
+ if (empty($etag))
+ {
+ $etag = $this->generate_etag();
+ }
+
+ // Set the ETag header
+ $this->headers['ETag'] = $etag;
+
+ // Add the Cache-Control header if it is not already set
+ // This allows etags to be used with Max-Age, etc
+ $this->headers += array(
+ 'Cache-Control' => 'must-revalidate',
+ );
+
+ if (isset($_SERVER['HTTP_IF_NONE_MATCH']) AND $_SERVER['HTTP_IF_NONE_MATCH'] === $etag)
+ {
+ // No need to send data again
+ $this->status = 304;
+ $this->send_headers();
+
+ // Stop execution
+ exit;
+ }
+
+ return $this;
+ }
+
+} // End Request
diff --git a/includes/kohana/system/classes/kohana/request/exception.php b/includes/kohana/system/classes/kohana/request/exception.php
new file mode 100644
index 0000000..1e8e149
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/request/exception.php
@@ -0,0 +1,9 @@
+ will be translated to a regular expression using a default
+ * regular expression pattern. You can override the default pattern by providing
+ * a pattern for the key:
+ *
+ * // This route will only match when is a digit
+ * Route::set('user', 'user//', array('id' => '\d+'));
+ *
+ * // This route will match when is anything
+ * Route::set('file', '', array('path' => '.*'));
+ *
+ * It is also possible to create optional segments by using parentheses in
+ * the URI definition:
+ *
+ * // This is the standard default route, and no keys are required
+ * Route::set('default', '((/(/)))');
+ *
+ * // This route only requires the key
+ * Route::set('file', '(/)(.)', array('path' => '.*', 'format' => '\w+'));
+ *
+ * Routes also provide a way to generate URIs (called "reverse routing"), which
+ * makes them an extremely powerful and flexible way to generate internal links.
+ *
+ * @package Kohana
+ * @category Base
+ * @author Kohana Team
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+class Kohana_Route {
+
+ // Defines the pattern of a
+ const REGEX_KEY = '<([a-zA-Z0-9_]++)>';
+
+ // What can be part of a value
+ const REGEX_SEGMENT = '[^/.,;?\n]++';
+
+ // What must be escaped in the route regex
+ const REGEX_ESCAPE = '[.\\+*?[^\\]${}=!|]';
+
+ /**
+ * @var string default action for all routes
+ */
+ public static $default_action = 'index';
+
+ /**
+ * @var array list of route objects
+ */
+ protected static $_routes = array();
+
+ /**
+ * Stores a named route and returns it. The "action" will always be set to
+ * "index" if it is not defined.
+ *
+ * Route::set('default', '((/(/)))')
+ * ->defaults(array(
+ * 'controller' => 'welcome',
+ * ));
+ *
+ * @param string route name
+ * @param string URI pattern
+ * @param array regex patterns for route keys
+ * @return Route
+ */
+ public static function set($name, $uri, array $regex = NULL)
+ {
+ return Route::$_routes[$name] = new Route($uri, $regex);
+ }
+
+ /**
+ * Retrieves a named route.
+ *
+ * $route = Route::get('default');
+ *
+ * @param string route name
+ * @return Route
+ * @throws Kohana_Exception
+ */
+ public static function get($name)
+ {
+ if ( ! isset(Route::$_routes[$name]))
+ {
+ throw new Kohana_Exception('The requested route does not exist: :route',
+ array(':route' => $name));
+ }
+
+ return Route::$_routes[$name];
+ }
+
+ /**
+ * Retrieves all named routes.
+ *
+ * $routes = Route::all();
+ *
+ * @return array routes by name
+ */
+ public static function all()
+ {
+ return Route::$_routes;
+ }
+
+ /**
+ * Get the name of a route.
+ *
+ * $name = Route::name($route)
+ *
+ * @param object Route instance
+ * @return string
+ */
+ public static function name(Route $route)
+ {
+ return array_search($route, Route::$_routes);
+ }
+
+ /**
+ * Saves or loads the route cache. If your routes will remain the same for
+ * a long period of time, use this to reload the routes from the cache
+ * rather than redefining them on every page load.
+ *
+ * if ( ! Route::cache())
+ * {
+ * // Set routes here
+ * Route::cache(TRUE);
+ * }
+ *
+ * @param boolean cache the current routes
+ * @return void when saving routes
+ * @return boolean when loading routes
+ * @uses Kohana::cache
+ */
+ public static function cache($save = FALSE)
+ {
+ if ($save === TRUE)
+ {
+ // Cache all defined routes
+ Kohana::cache('Route::cache()', Route::$_routes);
+ }
+ else
+ {
+ if ($routes = Kohana::cache('Route::cache()'))
+ {
+ Route::$_routes = $routes;
+
+ // Routes were cached
+ return TRUE;
+ }
+ else
+ {
+ // Routes were not cached
+ return FALSE;
+ }
+ }
+ }
+
+ /**
+ * Create a URL from a route name. This is a shortcut for:
+ *
+ * echo URL::site(Route::get($name)->uri($params), $protocol);
+ *
+ * @param string route name
+ * @param array URI parameters
+ * @param mixed protocol string or boolean, adds protocol and domain
+ * @return string
+ * @since 3.0.7
+ * @uses URL::site
+ */
+ public static function url($name, array $params = NULL, $protocol = NULL)
+ {
+ // Create a URI with the route and convert it to a URL
+ return URL::site(Route::get($name)->uri($params), $protocol);
+ }
+
+ // Route URI string
+ protected $_uri = '';
+
+ // Regular expressions for route keys
+ protected $_regex = array();
+
+ // Default values for route keys
+ protected $_defaults = array('action' => 'index');
+
+ // Compiled regex cache
+ protected $_route_regex;
+
+ /**
+ * Creates a new route. Sets the URI and regular expressions for keys.
+ * Routes should always be created with [Route::set] or they will not
+ * be properly stored.
+ *
+ * $route = new Route($uri, $regex);
+ *
+ * @param string route URI pattern
+ * @param array key patterns
+ * @return void
+ * @uses Route::_compile
+ */
+ public function __construct($uri = NULL, array $regex = NULL)
+ {
+ if ($uri === NULL)
+ {
+ // Assume the route is from cache
+ return;
+ }
+
+ if ( ! empty($regex))
+ {
+ $this->_regex = $regex;
+ }
+
+ // Store the URI that this route will match
+ $this->_uri = $uri;
+
+ // Store the compiled regex locally
+ $this->_route_regex = $this->_compile();
+ }
+
+ /**
+ * Provides default values for keys when they are not present. The default
+ * action will always be "index" unless it is overloaded here.
+ *
+ * $route->defaults(array(
+ * 'controller' => 'welcome',
+ * 'action' => 'index'
+ * ));
+ *
+ * @param array key values
+ * @return $this
+ */
+ public function defaults(array $defaults = NULL)
+ {
+ $this->_defaults = $defaults;
+
+ return $this;
+ }
+
+ /**
+ * Tests if the route matches a given URI. A successful match will return
+ * all of the routed parameters as an array. A failed match will return
+ * boolean FALSE.
+ *
+ * // Params: controller = users, action = edit, id = 10
+ * $params = $route->matches('users/edit/10');
+ *
+ * This method should almost always be used within an if/else block:
+ *
+ * if ($params = $route->matches($uri))
+ * {
+ * // Parse the parameters
+ * }
+ *
+ * @param string URI to match
+ * @return array on success
+ * @return FALSE on failure
+ */
+ public function matches($uri)
+ {
+ if ( ! preg_match($this->_route_regex, $uri, $matches))
+ return FALSE;
+
+ $params = array();
+ foreach ($matches as $key => $value)
+ {
+ if (is_int($key))
+ {
+ // Skip all unnamed keys
+ continue;
+ }
+
+ // Set the value for all matched keys
+ $params[$key] = $value;
+ }
+
+ foreach ($this->_defaults as $key => $value)
+ {
+ if ( ! isset($params[$key]) OR $params[$key] === '')
+ {
+ // Set default values for any key that was not matched
+ $params[$key] = $value;
+ }
+ }
+
+ return $params;
+ }
+
+ /**
+ * Generates a URI for the current route based on the parameters given.
+ *
+ * // Using the "default" route: "users/profile/10"
+ * $route->uri(array(
+ * 'controller' => 'users',
+ * 'action' => 'profile',
+ * 'id' => '10'
+ * ));
+ *
+ * @param array URI parameters
+ * @return string
+ * @throws Kohana_Exception
+ * @uses Route::REGEX_Key
+ */
+ public function uri(array $params = NULL)
+ {
+ if ($params === NULL)
+ {
+ // Use the default parameters
+ $params = $this->_defaults;
+ }
+ else
+ {
+ // Add the default parameters
+ $params += $this->_defaults;
+ }
+
+ // Start with the routed URI
+ $uri = $this->_uri;
+
+ if (strpos($uri, '<') === FALSE AND strpos($uri, '(') === FALSE)
+ {
+ // This is a static route, no need to replace anything
+ return $uri;
+ }
+
+ while (preg_match('#\([^()]++\)#', $uri, $match))
+ {
+ // Search for the matched value
+ $search = $match[0];
+
+ // Remove the parenthesis from the match as the replace
+ $replace = substr($match[0], 1, -1);
+
+ while (preg_match('#'.Route::REGEX_KEY.'#', $replace, $match))
+ {
+ list($key, $param) = $match;
+
+ if (isset($params[$param]))
+ {
+ // Replace the key with the parameter value
+ $replace = str_replace($key, $params[$param], $replace);
+ }
+ else
+ {
+ // This group has missing parameters
+ $replace = '';
+ break;
+ }
+ }
+
+ // Replace the group in the URI
+ $uri = str_replace($search, $replace, $uri);
+ }
+
+ while (preg_match('#'.Route::REGEX_KEY.'#', $uri, $match))
+ {
+ list($key, $param) = $match;
+
+ if ( ! isset($params[$param]))
+ {
+ // Ungrouped parameters are required
+ throw new Kohana_Exception('Required route parameter not passed: :param',
+ array(':param' => $param));
+ }
+
+ $uri = str_replace($key, $params[$param], $uri);
+ }
+
+ // Trim all extra slashes from the URI
+ $uri = preg_replace('#//+#', '/', rtrim($uri, '/'));
+
+ return $uri;
+ }
+
+ /**
+ * Returns the compiled regular expression for the route. This translates
+ * keys and optional groups to a proper PCRE regular expression.
+ *
+ * $regex = $route->_compile();
+ *
+ * @return string
+ * @uses Route::REGEX_ESCAPE
+ * @uses Route::REGEX_SEGMENT
+ */
+ protected function _compile()
+ {
+ // The URI should be considered literal except for keys and optional parts
+ // Escape everything preg_quote would escape except for : ( ) < >
+ $regex = preg_replace('#'.Route::REGEX_ESCAPE.'#', '\\\\$0', $this->_uri);
+
+ if (strpos($regex, '(') !== FALSE)
+ {
+ // Make optional parts of the URI non-capturing and optional
+ $regex = str_replace(array('(', ')'), array('(?:', ')?'), $regex);
+ }
+
+ // Insert default regex for keys
+ $regex = str_replace(array('<', '>'), array('(?P<', '>'.Route::REGEX_SEGMENT.')'), $regex);
+
+ if ( ! empty($this->_regex))
+ {
+ $search = $replace = array();
+ foreach ($this->_regex as $key => $value)
+ {
+ $search[] = "<$key>".Route::REGEX_SEGMENT;
+ $replace[] = "<$key>$value";
+ }
+
+ // Replace the default regex with the user-specified regex
+ $regex = str_replace($search, $replace, $regex);
+ }
+
+ return '#^'.$regex.'$#uD';
+ }
+
+} // End Route
diff --git a/includes/kohana/system/classes/kohana/security.php b/includes/kohana/system/classes/kohana/security.php
new file mode 100644
index 0000000..020bb68
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/security.php
@@ -0,0 +1,193 @@
+
+ * @copyright (c) 2001-2006 Bitflux GmbH
+ * @param mixed string or array to sanitize
+ * @return string
+ * @deprecated since v3.0.5
+ */
+ public static function xss_clean($str)
+ {
+ // http://svn.bitflux.ch/repos/public/popoon/trunk/classes/externalinput.php
+ // +----------------------------------------------------------------------+
+ // | Copyright (c) 2001-2006 Bitflux GmbH |
+ // +----------------------------------------------------------------------+
+ // | Licensed under the Apache License, Version 2.0 (the "License"); |
+ // | you may not use this file except in compliance with the License. |
+ // | You may obtain a copy of the License at |
+ // | http://www.apache.org/licenses/LICENSE-2.0 |
+ // | Unless required by applicable law or agreed to in writing, software |
+ // | distributed under the License is distributed on an "AS IS" BASIS, |
+ // | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
+ // | implied. See the License for the specific language governing |
+ // | permissions and limitations under the License. |
+ // +----------------------------------------------------------------------+
+ // | Author: Christian Stocker |
+ // +----------------------------------------------------------------------+
+ //
+ // Kohana Modifications:
+ // * Changed double quotes to single quotes, changed indenting and spacing
+ // * Removed magic_quotes stuff
+ // * Increased regex readability:
+ // * Used delimeters that aren't found in the pattern
+ // * Removed all unneeded escapes
+ // * Deleted U modifiers and swapped greediness where needed
+ // * Increased regex speed:
+ // * Made capturing parentheses non-capturing where possible
+ // * Removed parentheses where possible
+ // * Split up alternation alternatives
+ // * Made some quantifiers possessive
+ // * Handle arrays recursively
+
+ if (is_array($str) OR is_object($str))
+ {
+ foreach ($str as $k => $s)
+ {
+ $str[$k] = Security::xss_clean($s);
+ }
+
+ return $str;
+ }
+
+ // Remove all NULL bytes
+ $str = str_replace("\0", '', $str);
+
+ // Fix &entity\n;
+ $str = str_replace(array('&','<','>'), array('&','<','>'), $str);
+ $str = preg_replace('/(*\w+)[\x00-\x20]+;/u', '$1;', $str);
+ $str = preg_replace('/(*[0-9A-F]+);*/iu', '$1;', $str);
+ $str = html_entity_decode($str, ENT_COMPAT, Kohana::$charset);
+
+ // Remove any attribute starting with "on" or xmlns
+ $str = preg_replace('#(?:on[a-z]+|xmlns)\s*=\s*[\'"\x00-\x20]?[^\'>"]*[\'"\x00-\x20]?\s?#iu', '', $str);
+
+ // Remove javascript: and vbscript: protocols
+ $str = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $str);
+ $str = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $str);
+ $str = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $str);
+
+ // Only works in IE:
+ $str = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#is', '$1>', $str);
+ $str = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#is', '$1>', $str);
+ $str = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#ius', '$1>', $str);
+
+ // Remove namespaced elements (we do not need them)
+ $str = preg_replace('#*\w+:\w[^>]*+>#i', '', $str);
+
+ do
+ {
+ // Remove really unwanted tags
+ $old = $str;
+ $str = preg_replace('#*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $str);
+ }
+ while ($old !== $str);
+
+ return $str;
+ }
+
+ /**
+ * Generate and store a unique token which can be used to help prevent
+ * [CSRF](http://wikipedia.org/wiki/Cross_Site_Request_Forgery) attacks.
+ *
+ * $token = Security::token();
+ *
+ * You can insert this token into your forms as a hidden field:
+ *
+ * echo Form::hidden('csrf', Security::token());
+ *
+ * And then check it when using [Validate]:
+ *
+ * $array->rules('csrf', array(
+ * 'not_empty' => NULL,
+ * 'Security::check' => NULL,
+ * ));
+ *
+ * This provides a basic, but effective, method of preventing CSRF attacks.
+ *
+ * @param boolean force a new token to be generated?
+ * @return string
+ * @uses Session::instance
+ */
+ public static function token($new = FALSE)
+ {
+ $session = Session::instance();
+
+ // Get the current token
+ $token = $session->get(Security::$token_name);
+
+ if ($new === TRUE OR ! $token)
+ {
+ // Generate a new unique token
+ $token = uniqid('security');
+
+ // Store the new token
+ $session->set(Security::$token_name, $token);
+ }
+
+ return $token;
+ }
+
+ /**
+ * Check that the given token matches the currently stored security token.
+ *
+ * if (Security::check($token))
+ * {
+ * // Pass
+ * }
+ *
+ * @param string token to check
+ * @return boolean
+ * @uses Security::token
+ */
+ public static function check($token)
+ {
+ return Security::token() === $token;
+ }
+
+ /**
+ * Remove image tags from a string.
+ *
+ * $str = Security::strip_image_tags($str);
+ *
+ * @param string string to sanitize
+ * @return string
+ */
+ public static function strip_image_tags($str)
+ {
+ return preg_replace('# \s]*)["\']?[^>]*)?>#is', '$1', $str);
+ }
+
+ /**
+ * Encodes PHP tags in a string.
+ *
+ * $str = Security::encode_php_tags($str);
+ *
+ * @param string string to sanitize
+ * @return string
+ */
+ public static function encode_php_tags($str)
+ {
+ return str_replace(array('', '?>'), array('<?', '?>'), $str);
+ }
+
+} // End security
diff --git a/includes/kohana/system/classes/kohana/session.php b/includes/kohana/system/classes/kohana/session.php
new file mode 100644
index 0000000..3977916
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/session.php
@@ -0,0 +1,427 @@
+get($type);
+
+ // Set the session class name
+ $class = 'Session_'.ucfirst($type);
+
+ // Create a new session instance
+ Session::$instances[$type] = $session = new $class($config, $id);
+
+ // Write the session at shutdown
+ register_shutdown_function(array($session, 'write'));
+ }
+
+ return Session::$instances[$type];
+ }
+
+ /**
+ * @var string cookie name
+ */
+ protected $_name = 'session';
+
+ /**
+ * @var int cookie lifetime
+ */
+ protected $_lifetime = 0;
+
+ /**
+ * @var bool encrypt session data?
+ */
+ protected $_encrypted = FALSE;
+
+ /**
+ * @var array session data
+ */
+ protected $_data = array();
+
+ /**
+ * @var bool session destroyed?
+ */
+ protected $_destroyed = FALSE;
+
+ /**
+ * Overloads the name, lifetime, and encrypted session settings.
+ *
+ * [!!] Sessions can only be created using the [Session::instance] method.
+ *
+ * @param array configuration
+ * @param string session id
+ * @return void
+ * @uses Session::read
+ */
+ public function __construct(array $config = NULL, $id = NULL)
+ {
+ if (isset($config['name']))
+ {
+ // Cookie name to store the session id in
+ $this->_name = (string) $config['name'];
+ }
+
+ if (isset($config['lifetime']))
+ {
+ // Cookie lifetime
+ $this->_lifetime = (int) $config['lifetime'];
+ }
+
+ if (isset($config['encrypted']))
+ {
+ if ($config['encrypted'] === TRUE)
+ {
+ // Use the default Encrypt instance
+ $config['encrypted'] = 'default';
+ }
+
+ // Enable or disable encryption of data
+ $this->_encrypted = $config['encrypted'];
+ }
+
+ // Load the session
+ $this->read($id);
+ }
+
+ /**
+ * Session object is rendered to a serialized string. If encryption is
+ * enabled, the session will be encrypted. If not, the output string will
+ * be encoded using [base64_encode].
+ *
+ * echo $session;
+ *
+ * @return string
+ * @uses Encrypt::encode
+ */
+ public function __toString()
+ {
+ // Serialize the data array
+ $data = serialize($this->_data);
+
+ if ($this->_encrypted)
+ {
+ // Encrypt the data using the default key
+ $data = Encrypt::instance($this->_encrypted)->encode($data);
+ }
+ else
+ {
+ // Obfuscate the data with base64 encoding
+ $data = base64_encode($data);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Returns the current session array. The returned array can also be
+ * assigned by reference.
+ *
+ * // Get a copy of the current session data
+ * $data = $session->as_array();
+ *
+ * // Assign by reference for modification
+ * $data =& $session->as_array();
+ *
+ * @return array
+ */
+ public function & as_array()
+ {
+ return $this->_data;
+ }
+
+ /**
+ * Get the current session id, if the session supports it.
+ *
+ * $id = $session->id();
+ *
+ * [!!] Not all session types have ids.
+ *
+ * @return string
+ * @since 3.0.8
+ */
+ public function id()
+ {
+ return NULL;
+ }
+
+ /**
+ * Get the current session cookie name.
+ *
+ * $name = $session->name();
+ *
+ * @return string
+ * @since 3.0.8
+ */
+ public function name()
+ {
+ return $this->_name;
+ }
+
+ /**
+ * Get a variable from the session array.
+ *
+ * $foo = $session->get('foo');
+ *
+ * @param string variable name
+ * @param mixed default value to return
+ * @return mixed
+ */
+ public function get($key, $default = NULL)
+ {
+ return array_key_exists($key, $this->_data) ? $this->_data[$key] : $default;
+ }
+
+ /**
+ * Get and delete a variable from the session array.
+ *
+ * $bar = $session->get_once('bar');
+ *
+ * @param string variable name
+ * @param mixed default value to return
+ * @return mixed
+ */
+ public function get_once($key, $default = NULL)
+ {
+ $value = $this->get($key, $default);
+
+ unset($this->_data[$key]);
+
+ return $value;
+ }
+
+ /**
+ * Set a variable in the session array.
+ *
+ * $session->set('foo', 'bar');
+ *
+ * @param string variable name
+ * @param mixed value
+ * @return $this
+ */
+ public function set($key, $value)
+ {
+ $this->_data[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Set a variable by reference.
+ *
+ * $session->bind('foo', $foo);
+ *
+ * @param string variable name
+ * @param mixed referenced value
+ * @return $this
+ */
+ public function bind($key, & $value)
+ {
+ $this->_data[$key] =& $value;
+
+ return $this;
+ }
+
+ /**
+ * Removes a variable in the session array.
+ *
+ * $session->delete('foo');
+ *
+ * @param string variable name
+ * @param ...
+ * @return $this
+ */
+ public function delete($key)
+ {
+ $args = func_get_args();
+
+ foreach ($args as $key)
+ {
+ unset($this->_data[$key]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Loads existing session data.
+ *
+ * $session->read();
+ *
+ * @param string session id
+ * @return void
+ */
+ public function read($id = NULL)
+ {
+ if (is_string($data = $this->_read($id)))
+ {
+ try
+ {
+ if ($this->_encrypted)
+ {
+ // Decrypt the data using the default key
+ $data = Encrypt::instance($this->_encrypted)->decode($data);
+ }
+ else
+ {
+ // Decode the base64 encoded data
+ $data = base64_decode($data);
+ }
+
+ // Unserialize the data
+ $data = unserialize($data);
+ }
+ catch (Exception $e)
+ {
+ // Ignore all reading errors
+ }
+ }
+
+ if (is_array($data))
+ {
+ // Load the data locally
+ $this->_data = $data;
+ }
+ }
+
+ /**
+ * Generates a new session id and returns it.
+ *
+ * $id = $session->regenerate();
+ *
+ * @return string
+ */
+ public function regenerate()
+ {
+ return $this->_regenerate();
+ }
+
+ /**
+ * Sets the last_active timestamp and saves the session.
+ *
+ * $session->write();
+ *
+ * [!!] Any errors that occur during session writing will be logged,
+ * but not displayed, because sessions are written after output has
+ * been sent.
+ *
+ * @return boolean
+ * @uses Kohana::$log
+ */
+ public function write()
+ {
+ if (headers_sent() OR $this->_destroyed)
+ {
+ // Session cannot be written when the headers are sent or when
+ // the session has been destroyed
+ return FALSE;
+ }
+
+ // Set the last active timestamp
+ $this->_data['last_active'] = time();
+
+ try
+ {
+ return $this->_write();
+ }
+ catch (Exception $e)
+ {
+ // Log & ignore all errors when a write fails
+ Kohana::$log->add(Kohana::ERROR, Kohana::exception_text($e))->write();
+
+ return FALSE;
+ }
+ }
+
+ /**
+ * Completely destroy the current session.
+ *
+ * $success = $session->destroy();
+ *
+ * @return boolean
+ */
+ public function destroy()
+ {
+ if ($this->_destroyed === FALSE)
+ {
+ if ($this->_destroyed = $this->_destroy())
+ {
+ // The session has been destroyed, clear all data
+ $this->_data = array();
+ }
+ }
+
+ return $this->_destroyed;
+ }
+
+ /**
+ * Loads the raw session data string and returns it.
+ *
+ * @param string session id
+ * @return string
+ */
+ abstract protected function _read($id = NULL);
+
+ /**
+ * Generate a new session id and return it.
+ *
+ * @return string
+ */
+ abstract protected function _regenerate();
+
+ /**
+ * Writes the current session.
+ *
+ * @return boolean
+ */
+ abstract protected function _write();
+
+ /**
+ * Destroys the current session.
+ *
+ * @return boolean
+ */
+ abstract protected function _destroy();
+
+} // End Session
diff --git a/includes/kohana/system/classes/kohana/session/cookie.php b/includes/kohana/system/classes/kohana/session/cookie.php
new file mode 100644
index 0000000..92e2a11
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/session/cookie.php
@@ -0,0 +1,47 @@
+_name, NULL);
+ }
+
+ /**
+ * @return null
+ */
+ protected function _regenerate()
+ {
+ // Cookie sessions have no id
+ return NULL;
+ }
+
+ /**
+ * @return bool
+ */
+ protected function _write()
+ {
+ return Cookie::set($this->_name, $this->__toString(), $this->_lifetime);
+ }
+
+ /**
+ * @return bool
+ */
+ protected function _destroy()
+ {
+ return Cookie::delete($this->_name);
+ }
+
+} // End Session_Cookie
diff --git a/includes/kohana/system/classes/kohana/session/native.php b/includes/kohana/system/classes/kohana/session/native.php
new file mode 100644
index 0000000..961bf32
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/session/native.php
@@ -0,0 +1,93 @@
+_lifetime, Cookie::$path, Cookie::$domain, Cookie::$secure, Cookie::$httponly);
+
+ // Do not allow PHP to send Cache-Control headers
+ session_cache_limiter(FALSE);
+
+ // Set the session cookie name
+ session_name($this->_name);
+
+ if ($id)
+ {
+ // Set the session id
+ session_id($id);
+ }
+
+ // Start the session
+ session_start();
+
+ // Use the $_SESSION global for storing data
+ $this->_data =& $_SESSION;
+
+ return NULL;
+ }
+
+ /**
+ * @return string
+ */
+ protected function _regenerate()
+ {
+ // Regenerate the session id
+ session_regenerate_id();
+
+ return session_id();
+ }
+
+ /**
+ * @return bool
+ */
+ protected function _write()
+ {
+ // Write and close the session
+ session_write_close();
+
+ return TRUE;
+ }
+
+ /**
+ * @return bool
+ */
+ protected function _destroy()
+ {
+ // Destroy the current session
+ session_destroy();
+
+ // Did destruction work?
+ $status = ! session_id();
+
+ if ($status)
+ {
+ // Make sure the session cannot be restarted
+ Cookie::delete($this->_name);
+ }
+
+ return $status;
+ }
+
+} // End Session_Native
diff --git a/includes/kohana/system/classes/kohana/text.php b/includes/kohana/system/classes/kohana/text.php
new file mode 100644
index 0000000..7919249
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/text.php
@@ -0,0 +1,590 @@
+ 'billion',
+ 1000000 => 'million',
+ 1000 => 'thousand',
+ 100 => 'hundred',
+ 90 => 'ninety',
+ 80 => 'eighty',
+ 70 => 'seventy',
+ 60 => 'sixty',
+ 50 => 'fifty',
+ 40 => 'fourty',
+ 30 => 'thirty',
+ 20 => 'twenty',
+ 19 => 'nineteen',
+ 18 => 'eighteen',
+ 17 => 'seventeen',
+ 16 => 'sixteen',
+ 15 => 'fifteen',
+ 14 => 'fourteen',
+ 13 => 'thirteen',
+ 12 => 'twelve',
+ 11 => 'eleven',
+ 10 => 'ten',
+ 9 => 'nine',
+ 8 => 'eight',
+ 7 => 'seven',
+ 6 => 'six',
+ 5 => 'five',
+ 4 => 'four',
+ 3 => 'three',
+ 2 => 'two',
+ 1 => 'one',
+ );
+
+ /**
+ * Limits a phrase to a given number of words.
+ *
+ * $text = Text::limit_words($text);
+ *
+ * @param string phrase to limit words of
+ * @param integer number of words to limit to
+ * @param string end character or entity
+ * @return string
+ */
+ public static function limit_words($str, $limit = 100, $end_char = NULL)
+ {
+ $limit = (int) $limit;
+ $end_char = ($end_char === NULL) ? '…' : $end_char;
+
+ if (trim($str) === '')
+ return $str;
+
+ if ($limit <= 0)
+ return $end_char;
+
+ preg_match('/^\s*+(?:\S++\s*+){1,'.$limit.'}/u', $str, $matches);
+
+ // Only attach the end character if the matched string is shorter
+ // than the starting string.
+ return rtrim($matches[0]).((strlen($matches[0]) === strlen($str)) ? '' : $end_char);
+ }
+
+ /**
+ * Limits a phrase to a given number of characters.
+ *
+ * $text = Text::limit_chars($text);
+ *
+ * @param string phrase to limit characters of
+ * @param integer number of characters to limit to
+ * @param string end character or entity
+ * @param boolean enable or disable the preservation of words while limiting
+ * @return string
+ * @uses UTF8::strlen
+ */
+ public static function limit_chars($str, $limit = 100, $end_char = NULL, $preserve_words = FALSE)
+ {
+ $end_char = ($end_char === NULL) ? '…' : $end_char;
+
+ $limit = (int) $limit;
+
+ if (trim($str) === '' OR UTF8::strlen($str) <= $limit)
+ return $str;
+
+ if ($limit <= 0)
+ return $end_char;
+
+ if ($preserve_words === FALSE)
+ return rtrim(UTF8::substr($str, 0, $limit)).$end_char;
+
+ // Don't preserve words. The limit is considered the top limit.
+ // No strings with a length longer than $limit should be returned.
+ if ( ! preg_match('/^.{0,'.$limit.'}\s/us', $str, $matches))
+ return $end_char;
+
+ return rtrim($matches[0]).((strlen($matches[0]) === strlen($str)) ? '' : $end_char);
+ }
+
+ /**
+ * Alternates between two or more strings.
+ *
+ * echo Text::alternate('one', 'two'); // "one"
+ * echo Text::alternate('one', 'two'); // "two"
+ * echo Text::alternate('one', 'two'); // "one"
+ *
+ * Note that using multiple iterations of different strings may produce
+ * unexpected results.
+ *
+ * @param string strings to alternate between
+ * @return string
+ */
+ public static function alternate()
+ {
+ static $i;
+
+ if (func_num_args() === 0)
+ {
+ $i = 0;
+ return '';
+ }
+
+ $args = func_get_args();
+ return $args[($i++ % count($args))];
+ }
+
+ /**
+ * Generates a random string of a given type and length.
+ *
+ *
+ * $str = Text::random(); // 8 character random string
+ *
+ * The following types are supported:
+ *
+ * alnum
+ * : Upper and lower case a-z, 0-9 (default)
+ *
+ * alpha
+ * : Upper and lower case a-z
+ *
+ * hexdec
+ * : Hexadecimal characters a-f, 0-9
+ *
+ * distinct
+ * : Uppercase characters and numbers that cannot be confused
+ *
+ * You can also create a custom type by providing the "pool" of characters
+ * as the type.
+ *
+ * @param string a type of pool, or a string of characters to use as the pool
+ * @param integer length of string to return
+ * @return string
+ * @uses UTF8::split
+ */
+ public static function random($type = NULL, $length = 8)
+ {
+ if ($type === NULL)
+ {
+ // Default is to generate an alphanumeric string
+ $type = 'alnum';
+ }
+
+ $utf8 = FALSE;
+
+ switch ($type)
+ {
+ case 'alnum':
+ $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ break;
+ case 'alpha':
+ $pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ break;
+ case 'hexdec':
+ $pool = '0123456789abcdef';
+ break;
+ case 'numeric':
+ $pool = '0123456789';
+ break;
+ case 'nozero':
+ $pool = '123456789';
+ break;
+ case 'distinct':
+ $pool = '2345679ACDEFHJKLMNPRSTUVWXYZ';
+ break;
+ default:
+ $pool = (string) $type;
+ $utf8 = ! UTF8::is_ascii($pool);
+ break;
+ }
+
+ // Split the pool into an array of characters
+ $pool = ($utf8 === TRUE) ? UTF8::str_split($pool, 1) : str_split($pool, 1);
+
+ // Largest pool key
+ $max = count($pool) - 1;
+
+ $str = '';
+ for ($i = 0; $i < $length; $i++)
+ {
+ // Select a random character from the pool and add it to the string
+ $str .= $pool[mt_rand(0, $max)];
+ }
+
+ // Make sure alnum strings contain at least one letter and one digit
+ if ($type === 'alnum' AND $length > 1)
+ {
+ if (ctype_alpha($str))
+ {
+ // Add a random digit
+ $str[mt_rand(0, $length - 1)] = chr(mt_rand(48, 57));
+ }
+ elseif (ctype_digit($str))
+ {
+ // Add a random letter
+ $str[mt_rand(0, $length - 1)] = chr(mt_rand(65, 90));
+ }
+ }
+
+ return $str;
+ }
+
+ /**
+ * Reduces multiple slashes in a string to single slashes.
+ *
+ * $str = Text::reduce_slashes('foo//bar/baz'); // "foo/bar/baz"
+ *
+ * @param string string to reduce slashes of
+ * @return string
+ */
+ public static function reduce_slashes($str)
+ {
+ return preg_replace('#(? '#####',
+ * ));
+ *
+ * @param string phrase to replace words in
+ * @param array words to replace
+ * @param string replacement string
+ * @param boolean replace words across word boundries (space, period, etc)
+ * @return string
+ * @uses UTF8::strlen
+ */
+ public static function censor($str, $badwords, $replacement = '#', $replace_partial_words = TRUE)
+ {
+ foreach ( (array) $badwords as $key => $badword)
+ {
+ $badwords[$key] = str_replace('\*', '\S*?', preg_quote( (string) $badword));
+ }
+
+ $regex = '('.implode('|', $badwords).')';
+
+ if ($replace_partial_words === FALSE)
+ {
+ // Just using \b isn't sufficient when we need to replace a badword that already contains word boundaries itself
+ $regex = '(?<=\b|\s|^)'.$regex.'(?=\b|\s|$)';
+ }
+
+ $regex = '!'.$regex.'!ui';
+
+ if (UTF8::strlen($replacement) == 1)
+ {
+ $regex .= 'e';
+ return preg_replace($regex, 'str_repeat($replacement, UTF8::strlen(\'$1\'))', $str);
+ }
+
+ return preg_replace($regex, $replacement, $str);
+ }
+
+ /**
+ * Finds the text that is similar between a set of words.
+ *
+ * $match = Text::similar(array('fred', 'fran', 'free'); // "fr"
+ *
+ * @param array words to find similar text of
+ * @return string
+ */
+ public static function similar(array $words)
+ {
+ // First word is the word to match against
+ $word = current($words);
+
+ for ($i = 0, $max = strlen($word); $i < $max; ++$i)
+ {
+ foreach ($words as $w)
+ {
+ // Once a difference is found, break out of the loops
+ if ( ! isset($w[$i]) OR $w[$i] !== $word[$i])
+ break 2;
+ }
+ }
+
+ // Return the similar text
+ return substr($word, 0, $i);
+ }
+
+ /**
+ * Converts text email addresses and anchors into links. Existing links
+ * will not be altered.
+ *
+ * echo Text::auto_link($text);
+ *
+ * [!!] This method is not foolproof since it uses regex to parse HTML.
+ *
+ * @param string text to auto link
+ * @return string
+ * @uses Text::auto_link_urls
+ * @uses Text::auto_link_emails
+ */
+ public static function auto_link($text)
+ {
+ // Auto link emails first to prevent problems with "www.domain.com@example.com"
+ return Text::auto_link_urls(Text::auto_link_emails($text));
+ }
+
+ /**
+ * Converts text anchors into links. Existing links will not be altered.
+ *
+ * echo Text::auto_link_urls($text);
+ *
+ * [!!] This method is not foolproof since it uses regex to parse HTML.
+ *
+ * @param string text to auto link
+ * @return string
+ * @uses HTML::anchor
+ */
+ public static function auto_link_urls($text)
+ {
+ // Find and replace all http/https/ftp/ftps links that are not part of an existing html anchor
+ $text = preg_replace_callback('~\b(?)(?:ht|f)tps?://\S+(?:/|\b)~i', 'Text::_auto_link_urls_callback1', $text);
+
+ // Find and replace all naked www.links.com (without http://)
+ return preg_replace_callback('~\b(?)www(?:\.[a-z0-9][-a-z0-9]*+)+\.[a-z]{2,6}\b~i', 'Text::_auto_link_urls_callback2', $text);
+ }
+
+ protected static function _auto_link_urls_callback1($matches)
+ {
+ return HTML::anchor($matches[0]);
+ }
+
+ protected static function _auto_link_urls_callback2($matches)
+ {
+ return HTML::anchor('http://'.$matches[0], $matches[0]);
+ }
+
+ /**
+ * Converts text email addresses into links. Existing links will not
+ * be altered.
+ *
+ * echo Text::auto_link_emails($text);
+ *
+ * [!!] This method is not foolproof since it uses regex to parse HTML.
+ *
+ * @param string text to auto link
+ * @return string
+ * @uses HTML::mailto
+ */
+ public static function auto_link_emails($text)
+ {
+ // Find and replace all email addresses that are not part of an existing html mailto anchor
+ // Note: The "58;" negative lookbehind prevents matching of existing encoded html mailto anchors
+ // The html entity for a colon (:) is : or : or : etc.
+ return preg_replace_callback('~\b(?)~i', 'Text::_auto_link_emails_callback', $text);
+ }
+
+ protected static function _auto_link_emails_callback($matches)
+ {
+ return HTML::mailto($matches[0]);
+ }
+
+ /**
+ * Automatically applies "p" and "br" markup to text.
+ * Basically [nl2br](http://php.net/nl2br) on steroids.
+ *
+ * echo Text::auto_p($text);
+ *
+ * [!!] This method is not foolproof since it uses regex to parse HTML.
+ *
+ * @param string subject
+ * @param boolean convert single linebreaks to
+ * @return string
+ */
+ public static function auto_p($str, $br = TRUE)
+ {
+ // Trim whitespace
+ if (($str = trim($str)) === '')
+ return '';
+
+ // Standardize newlines
+ $str = str_replace(array("\r\n", "\r"), "\n", $str);
+
+ // Trim whitespace on each line
+ $str = preg_replace('~^[ \t]+~m', '', $str);
+ $str = preg_replace('~[ \t]+$~m', '', $str);
+
+ // The following regexes only need to be executed if the string contains html
+ if ($html_found = (strpos($str, '<') !== FALSE))
+ {
+ // Elements that should not be surrounded by p tags
+ $no_p = '(?:p|div|h[1-6r]|ul|ol|li|blockquote|d[dlt]|pre|t[dhr]|t(?:able|body|foot|head)|c(?:aption|olgroup)|form|s(?:elect|tyle)|a(?:ddress|rea)|ma(?:p|th))';
+
+ // Put at least two linebreaks before and after $no_p elements
+ $str = preg_replace('~^<'.$no_p.'[^>]*+>~im', "\n$0", $str);
+ $str = preg_replace('~'.$no_p.'\s*+>$~im', "$0\n", $str);
+ }
+
+ // Do the magic!
+ $str = '
'.trim($str).'
';
+ $str = preg_replace('~\n{2,}~', "\n\n", $str);
+
+ // The following regexes only need to be executed if the string contains html
+ if ($html_found !== FALSE)
+ {
+ // Remove p tags around $no_p elements
+ $str = preg_replace('~
(?=?'.$no_p.'[^>]*+>)~i', '', $str);
+ $str = preg_replace('~(?'.$no_p.'[^>]*+>)
~i', '$1', $str);
+ }
+
+ // Convert single linebreaks to
+ if ($br === TRUE)
+ {
+ $str = preg_replace('~(?\n", $str);
+ }
+
+ return $str;
+ }
+
+ /**
+ * Returns human readable sizes. Based on original functions written by
+ * [Aidan Lister](http://aidanlister.com/repos/v/function.size_readable.php)
+ * and [Quentin Zervaas](http://www.phpriot.com/d/code/strings/filesize-format/).
+ *
+ * echo Text::bytes(filesize($file));
+ *
+ * @param integer size in bytes
+ * @param string a definitive unit
+ * @param string the return string format
+ * @param boolean whether to use SI prefixes or IEC
+ * @return string
+ */
+ public static function bytes($bytes, $force_unit = NULL, $format = NULL, $si = TRUE)
+ {
+ // Format string
+ $format = ($format === NULL) ? '%01.2f %s' : (string) $format;
+
+ // IEC prefixes (binary)
+ if ($si == FALSE OR strpos($force_unit, 'i') !== FALSE)
+ {
+ $units = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB');
+ $mod = 1024;
+ }
+ // SI prefixes (decimal)
+ else
+ {
+ $units = array('B', 'kB', 'MB', 'GB', 'TB', 'PB');
+ $mod = 1000;
+ }
+
+ // Determine unit to use
+ if (($power = array_search( (string) $force_unit, $units)) === FALSE)
+ {
+ $power = ($bytes > 0) ? floor(log($bytes, $mod)) : 0;
+ }
+
+ return sprintf($format, $bytes / pow($mod, $power), $units[$power]);
+ }
+
+ /**
+ * Format a number to human-readable text.
+ *
+ * // Display: one thousand and twenty-four
+ * echo Text::number(1024);
+ *
+ * // Display: five million, six hundred and thirty-two
+ * echo Text::number(5000632);
+ *
+ * @param integer number to format
+ * @return string
+ * @since 3.0.8
+ */
+ public static function number($number)
+ {
+ // The number must always be an integer
+ $number = (int) $number;
+
+ // Uncompiled text version
+ $text = array();
+
+ // Last matched unit within the loop
+ $last_unit = NULL;
+
+ // The last matched item within the loop
+ $last_item = '';
+
+ foreach (Text::$units as $unit => $name)
+ {
+ if ($number / $unit >= 1)
+ {
+ // $value = the number of times the number is divisble by unit
+ $number -= $unit * ($value = (int) floor($number / $unit));
+ // Temporary var for textifying the current unit
+ $item = '';
+
+ if ($unit < 100)
+ {
+ if ($last_unit < 100 AND $last_unit >= 20)
+ {
+ $last_item .= '-'.$name;
+ }
+ else
+ {
+ $item = $name;
+ }
+ }
+ else
+ {
+ $item = Text::number($value).' '.$name;
+ }
+
+ // In the situation that we need to make a composite number (i.e. twenty-three)
+ // then we need to modify the previous entry
+ if (empty($item))
+ {
+ array_pop($text);
+
+ $item = $last_item;
+ }
+
+ $last_item = $text[] = $item;
+ $last_unit = $unit;
+ }
+ }
+
+ if (count($text) > 1)
+ {
+ $and = array_pop($text);
+ }
+
+ $text = implode(', ', $text);
+
+ if (isset($and))
+ {
+ $text .= ' and '.$and;
+ }
+
+ return $text;
+ }
+
+ /**
+ * Prevents [widow words](http://www.shauninman.com/archive/2006/08/22/widont_wordpress_plugin)
+ * by inserting a non-breaking space between the last two words.
+ *
+ * echo Text::widont($text);
+ *
+ * @param string text to remove widows from
+ * @return string
+ */
+ public static function widont($str)
+ {
+ $str = rtrim($str);
+ $space = strrpos($str, ' ');
+
+ if ($space !== FALSE)
+ {
+ $str = substr($str, 0, $space).' '.substr($str, $space + 1);
+ }
+
+ return $str;
+ }
+
+} // End text
diff --git a/includes/kohana/system/classes/kohana/upload.php b/includes/kohana/system/classes/kohana/upload.php
new file mode 100644
index 0000000..6ab5a50
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/upload.php
@@ -0,0 +1,213 @@
+check())
+ * {
+ * // Upload is valid, save it
+ * Upload::save($_FILES['file']);
+ * }
+ *
+ * @param array uploaded file data
+ * @param string new filename
+ * @param string new directory
+ * @param integer chmod mask
+ * @return string on success, full path to new file
+ * @return FALSE on failure
+ */
+ public static function save(array $file, $filename = NULL, $directory = NULL, $chmod = 0644)
+ {
+ if ( ! isset($file['tmp_name']) OR ! is_uploaded_file($file['tmp_name']))
+ {
+ // Ignore corrupted uploads
+ return FALSE;
+ }
+
+ if ($filename === NULL)
+ {
+ // Use the default filename, with a timestamp pre-pended
+ $filename = uniqid().$file['name'];
+ }
+
+ if (Upload::$remove_spaces === TRUE)
+ {
+ // Remove spaces from the filename
+ $filename = preg_replace('/\s+/', '_', $filename);
+ }
+
+ if ($directory === NULL)
+ {
+ // Use the pre-configured upload directory
+ $directory = Upload::$default_directory;
+ }
+
+ if ( ! is_dir($directory) OR ! is_writable(realpath($directory)))
+ {
+ throw new Kohana_Exception('Directory :dir must be writable',
+ array(':dir' => Kohana::debug_path($directory)));
+ }
+
+ // Make the filename into a complete path
+ $filename = realpath($directory).DIRECTORY_SEPARATOR.$filename;
+
+ if (move_uploaded_file($file['tmp_name'], $filename))
+ {
+ if ($chmod !== FALSE)
+ {
+ // Set permissions on filename
+ chmod($filename, $chmod);
+ }
+
+ // Return new file path
+ return $filename;
+ }
+
+ return FALSE;
+ }
+
+ /**
+ * Tests if upload data is valid, even if no file was uploaded. If you
+ * _do_ require a file to be uploaded, add the [Upload::not_empty] rule
+ * before this rule.
+ *
+ * $array->rule('file', 'Upload::valid')
+ *
+ * @param array $_FILES item
+ * @return bool
+ */
+ public static function valid($file)
+ {
+ return (isset($file['error'])
+ AND isset($file['name'])
+ AND isset($file['type'])
+ AND isset($file['tmp_name'])
+ AND isset($file['size']));
+ }
+
+ /**
+ * Tests if a successful upload has been made.
+ *
+ * $array->rule('file', 'Upload::not_empty');
+ *
+ * @param array $_FILES item
+ * @return bool
+ */
+ public static function not_empty(array $file)
+ {
+ return (isset($file['error'])
+ AND isset($file['tmp_name'])
+ AND $file['error'] === UPLOAD_ERR_OK
+ AND is_uploaded_file($file['tmp_name'])
+ );
+ }
+
+ /**
+ * Test if an uploaded file is an allowed file type, by extension.
+ *
+ * $array->rule('file', 'Upload::type', array(array('jpg', 'png', 'gif')));
+ *
+ * @param array $_FILES item
+ * @param array allowed file extensions
+ * @return bool
+ */
+ public static function type(array $file, array $allowed)
+ {
+ if ($file['error'] !== UPLOAD_ERR_OK)
+ return TRUE;
+
+ $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
+
+ return in_array($ext, $allowed);
+ }
+
+ /**
+ * Validation rule to test if an uploaded file is allowed by file size.
+ * File sizes are defined as: SB, where S is the size (1, 15, 300, etc) and
+ * B is the byte modifier: (B)ytes, (K)ilobytes, (M)egabytes, (G)igabytes.
+ *
+ * $array->rule('file', 'Upload::size', array('1M'))
+ *
+ * @param array $_FILES item
+ * @param string maximum file size
+ * @return bool
+ */
+ public static function size(array $file, $size)
+ {
+ if ($file['error'] === UPLOAD_ERR_INI_SIZE)
+ {
+ // Upload is larger than PHP allowed size
+ return FALSE;
+ }
+
+ if ($file['error'] !== UPLOAD_ERR_OK)
+ {
+ // The upload failed, no size to check
+ return TRUE;
+ }
+
+ // Only one size is allowed
+ $size = strtoupper(trim($size));
+
+ if ( ! preg_match('/^[0-9]++[BKMG]$/', $size))
+ {
+ throw new Kohana_Exception('Size does not contain a digit and a byte value: :size', array(
+ ':size' => $size,
+ ));
+ }
+
+ // Make the size into a power of 1024
+ switch (substr($size, -1))
+ {
+ case 'G':
+ $size = intval($size) * pow(1024, 3);
+ break;
+ case 'M':
+ $size = intval($size) * pow(1024, 2);
+ break;
+ case 'K':
+ $size = intval($size) * pow(1024, 1);
+ break;
+ default:
+ $size = intval($size);
+ break;
+ }
+
+ // Test that the file is under or equal to the max size
+ return ($file['size'] <= $size);
+ }
+
+} // End upload
diff --git a/includes/kohana/system/classes/kohana/url.php b/includes/kohana/system/classes/kohana/url.php
new file mode 100644
index 0000000..359870c
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/url.php
@@ -0,0 +1,186 @@
+ 'title', 'limit' => 10));
+ *
+ * Typically you would use this when you are sorting query results,
+ * or something similar.
+ *
+ * [!!] Parameters with a NULL value are left out.
+ *
+ * @param array array of GET parameters
+ * @param boolean include current request GET parameters
+ * @return string
+ */
+ public static function query(array $params = NULL, $use_get = TRUE)
+ {
+ if ($use_get)
+ {
+ if ($params === NULL)
+ {
+ // Use only the current parameters
+ $params = $_GET;
+ }
+ else
+ {
+ // Merge the current and new parameters
+ $params = array_merge($_GET, $params);
+ }
+ }
+
+ if (empty($params))
+ {
+ // No query parameters
+ return '';
+ }
+
+ // Note: http_build_query returns an empty string for a params array with only NULL values
+ $query = http_build_query($params, '', '&');
+
+ // Don't prepend '?' to an empty string
+ return ($query === '') ? '' : ('?'.$query);
+ }
+
+ /**
+ * Convert a phrase to a URL-safe title.
+ *
+ * echo URL::title('My Blog Post'); // "my-blog-post"
+ *
+ * @param string phrase to convert
+ * @param string word separator (any single character)
+ * @param boolean transliterate to ASCII?
+ * @return string
+ * @uses UTF8::transliterate_to_ascii
+ */
+ public static function title($title, $separator = '-', $ascii_only = FALSE)
+ {
+ if ($ascii_only === TRUE)
+ {
+ // Transliterate non-ASCII characters
+ $title = UTF8::transliterate_to_ascii($title);
+
+ // Remove all characters that are not the separator, a-z, 0-9, or whitespace
+ $title = preg_replace('![^'.preg_quote($separator).'a-z0-9\s]+!', '', strtolower($title));
+ }
+ else
+ {
+ // Remove all characters that are not the separator, letters, numbers, or whitespace
+ $title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', UTF8::strtolower($title));
+ }
+
+ // Replace all separator characters and whitespace by a single separator
+ $title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title);
+
+ // Trim separators from the beginning and end
+ return trim($title, $separator);
+ }
+
+} // End url
\ No newline at end of file
diff --git a/includes/kohana/system/classes/kohana/utf8.php b/includes/kohana/system/classes/kohana/utf8.php
new file mode 100644
index 0000000..cfcaef1
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/utf8.php
@@ -0,0 +1,767 @@
+ $val)
+ {
+ // Recursion!
+ $var[self::clean($key)] = self::clean($val);
+ }
+ }
+ elseif (is_string($var) AND $var !== '')
+ {
+ // Remove control characters
+ $var = self::strip_ascii_ctrl($var);
+
+ if ( ! self::is_ascii($var))
+ {
+ // Disable notices
+ $error_reporting = error_reporting(~E_NOTICE);
+
+ // iconv is expensive, so it is only used when needed
+ $var = iconv($charset, $charset.'//IGNORE', $var);
+
+ // Turn notices back on
+ error_reporting($error_reporting);
+ }
+ }
+
+ return $var;
+ }
+
+ /**
+ * Tests whether a string contains only 7-bit ASCII bytes. This is used to
+ * determine when to use native functions or UTF-8 functions.
+ *
+ * $ascii = UTF8::is_ascii($str);
+ *
+ * @param mixed string or array of strings to check
+ * @return boolean
+ */
+ public static function is_ascii($str)
+ {
+ if (is_array($str))
+ {
+ $str = implode($str);
+ }
+
+ return ! preg_match('/[^\x00-\x7F]/S', $str);
+ }
+
+ /**
+ * Strips out device control codes in the ASCII range.
+ *
+ * $str = UTF8::strip_ascii_ctrl($str);
+ *
+ * @param string string to clean
+ * @return string
+ */
+ public static function strip_ascii_ctrl($str)
+ {
+ return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $str);
+ }
+
+ /**
+ * Strips out all non-7bit ASCII bytes.
+ *
+ * $str = UTF8::strip_non_ascii($str);
+ *
+ * @param string string to clean
+ * @return string
+ */
+ public static function strip_non_ascii($str)
+ {
+ return preg_replace('/[^\x00-\x7F]+/S', '', $str);
+ }
+
+ /**
+ * Replaces special/accented UTF-8 characters by ASCII-7 "equivalents".
+ *
+ * $ascii = UTF8::transliterate_to_ascii($utf8);
+ *
+ * @author Andreas Gohr
+ * @param string string to transliterate
+ * @param integer -1 lowercase only, +1 uppercase only, 0 both cases
+ * @return string
+ */
+ public static function transliterate_to_ascii($str, $case = 0)
+ {
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _transliterate_to_ascii($str, $case);
+ }
+
+ /**
+ * Returns the length of the given string. This is a UTF8-aware version
+ * of [strlen](http://php.net/strlen).
+ *
+ * $length = UTF8::strlen($str);
+ *
+ * @param string string being measured for length
+ * @return integer
+ * @uses UTF8::$server_utf8
+ */
+ public static function strlen($str)
+ {
+ if (UTF8::$server_utf8)
+ return mb_strlen($str, Kohana::$charset);
+
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _strlen($str);
+ }
+
+ /**
+ * Finds position of first occurrence of a UTF-8 string. This is a
+ * UTF8-aware version of [strpos](http://php.net/strpos).
+ *
+ * $position = UTF8::strpos($str, $search);
+ *
+ * @author Harry Fuecks
+ * @param string haystack
+ * @param string needle
+ * @param integer offset from which character in haystack to start searching
+ * @return integer position of needle
+ * @return boolean FALSE if the needle is not found
+ * @uses UTF8::$server_utf8
+ */
+ public static function strpos($str, $search, $offset = 0)
+ {
+ if (UTF8::$server_utf8)
+ return mb_strpos($str, $search, $offset, Kohana::$charset);
+
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _strpos($str, $search, $offset);
+ }
+
+ /**
+ * Finds position of last occurrence of a char in a UTF-8 string. This is
+ * a UTF8-aware version of [strrpos](http://php.net/strrpos).
+ *
+ * $position = UTF8::strrpos($str, $search);
+ *
+ * @author Harry Fuecks
+ * @param string haystack
+ * @param string needle
+ * @param integer offset from which character in haystack to start searching
+ * @return integer position of needle
+ * @return boolean FALSE if the needle is not found
+ * @uses UTF8::$server_utf8
+ */
+ public static function strrpos($str, $search, $offset = 0)
+ {
+ if (UTF8::$server_utf8)
+ return mb_strrpos($str, $search, $offset, Kohana::$charset);
+
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _strrpos($str, $search, $offset);
+ }
+
+ /**
+ * Returns part of a UTF-8 string. This is a UTF8-aware version
+ * of [substr](http://php.net/substr).
+ *
+ * $sub = UTF8::substr($str, $offset);
+ *
+ * @author Chris Smith
+ * @param string input string
+ * @param integer offset
+ * @param integer length limit
+ * @return string
+ * @uses UTF8::$server_utf8
+ * @uses Kohana::$charset
+ */
+ public static function substr($str, $offset, $length = NULL)
+ {
+ if (UTF8::$server_utf8)
+ return ($length === NULL)
+ ? mb_substr($str, $offset, mb_strlen($str), Kohana::$charset)
+ : mb_substr($str, $offset, $length, Kohana::$charset);
+
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _substr($str, $offset, $length);
+ }
+
+ /**
+ * Replaces text within a portion of a UTF-8 string. This is a UTF8-aware
+ * version of [substr_replace](http://php.net/substr_replace).
+ *
+ * $str = UTF8::substr_replace($str, $replacement, $offset);
+ *
+ * @author Harry Fuecks
+ * @param string input string
+ * @param string replacement string
+ * @param integer offset
+ * @return string
+ */
+ public static function substr_replace($str, $replacement, $offset, $length = NULL)
+ {
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _substr_replace($str, $replacement, $offset, $length);
+ }
+
+ /**
+ * Makes a UTF-8 string lowercase. This is a UTF8-aware version
+ * of [strtolower](http://php.net/strtolower).
+ *
+ * $str = UTF8::strtolower($str);
+ *
+ * @author Andreas Gohr
+ * @param string mixed case string
+ * @return string
+ * @uses UTF8::$server_utf8
+ */
+ public static function strtolower($str)
+ {
+ if (UTF8::$server_utf8)
+ return mb_strtolower($str, Kohana::$charset);
+
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _strtolower($str);
+ }
+
+ /**
+ * Makes a UTF-8 string uppercase. This is a UTF8-aware version
+ * of [strtoupper](http://php.net/strtoupper).
+ *
+ * @author Andreas Gohr
+ * @param string mixed case string
+ * @return string
+ * @uses UTF8::$server_utf8
+ * @uses Kohana::$charset
+ */
+ public static function strtoupper($str)
+ {
+ if (UTF8::$server_utf8)
+ return mb_strtoupper($str, Kohana::$charset);
+
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _strtoupper($str);
+ }
+
+ /**
+ * Makes a UTF-8 string's first character uppercase. This is a UTF8-aware
+ * version of [ucfirst](http://php.net/ucfirst).
+ *
+ * $str = UTF8::ucfirst($str);
+ *
+ * @author Harry Fuecks
+ * @param string mixed case string
+ * @return string
+ */
+ public static function ucfirst($str)
+ {
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _ucfirst($str);
+ }
+
+ /**
+ * Makes the first character of every word in a UTF-8 string uppercase.
+ * This is a UTF8-aware version of [ucwords](http://php.net/ucwords).
+ *
+ * $str = UTF8::ucwords($str);
+ *
+ * @author Harry Fuecks
+ * @param string mixed case string
+ * @return string
+ * @uses UTF8::$server_utf8
+ */
+ public static function ucwords($str)
+ {
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _ucwords($str);
+ }
+
+ /**
+ * Case-insensitive UTF-8 string comparison. This is a UTF8-aware version
+ * of [strcasecmp](http://php.net/strcasecmp).
+ *
+ * $compare = UTF8::strcasecmp($str1, $str2);
+ *
+ * @author Harry Fuecks
+ * @param string string to compare
+ * @param string string to compare
+ * @return integer less than 0 if str1 is less than str2
+ * @return integer greater than 0 if str1 is greater than str2
+ * @return integer 0 if they are equal
+ */
+ public static function strcasecmp($str1, $str2)
+ {
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _strcasecmp($str1, $str2);
+ }
+
+ /**
+ * Returns a string or an array with all occurrences of search in subject
+ * (ignoring case) and replaced with the given replace value. This is a
+ * UTF8-aware version of [str_ireplace](http://php.net/str_ireplace).
+ *
+ * [!!] This function is very slow compared to the native version. Avoid
+ * using it when possible.
+ *
+ * @author Harry Fuecks
+ * @param string input string
+ * @param string needle
+ * @return string matched substring if found
+ * @return FALSE if the substring was not found
+ */
+ public static function stristr($str, $search)
+ {
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _stristr($str, $search);
+ }
+
+ /**
+ * Finds the length of the initial segment matching mask. This is a
+ * UTF8-aware version of [strspn](http://php.net/strspn).
+ *
+ * $found = UTF8::strspn($str, $mask);
+ *
+ * @author Harry Fuecks
+ * @param string input string
+ * @param string mask for search
+ * @param integer start position of the string to examine
+ * @param integer length of the string to examine
+ * @return integer length of the initial segment that contains characters in the mask
+ */
+ public static function strspn($str, $mask, $offset = NULL, $length = NULL)
+ {
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _strspn($str, $mask, $offset, $length);
+ }
+
+ /**
+ * Finds the length of the initial segment not matching mask. This is a
+ * UTF8-aware version of [strcspn](http://php.net/strcspn).
+ *
+ * $found = UTF8::strcspn($str, $mask);
+ *
+ * @author Harry Fuecks
+ * @param string input string
+ * @param string mask for search
+ * @param integer start position of the string to examine
+ * @param integer length of the string to examine
+ * @return integer length of the initial segment that contains characters not in the mask
+ */
+ public static function strcspn($str, $mask, $offset = NULL, $length = NULL)
+ {
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _strcspn($str, $mask, $offset, $length);
+ }
+
+ /**
+ * Pads a UTF-8 string to a certain length with another string. This is a
+ * UTF8-aware version of [str_pad](http://php.net/str_pad).
+ *
+ * $str = UTF8::str_pad($str, $length);
+ *
+ * @author Harry Fuecks
+ * @param string input string
+ * @param integer desired string length after padding
+ * @param string string to use as padding
+ * @param string padding type: STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH
+ * @return string
+ */
+ public static function str_pad($str, $final_str_length, $pad_str = ' ', $pad_type = STR_PAD_RIGHT)
+ {
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _str_pad($str, $final_str_length, $pad_str, $pad_type);
+ }
+
+ /**
+ * Converts a UTF-8 string to an array. This is a UTF8-aware version of
+ * [str_split](http://php.net/str_split).
+ *
+ * $array = UTF8::str_split($str);
+ *
+ * @author Harry Fuecks
+ * @param string input string
+ * @param integer maximum length of each chunk
+ * @return array
+ */
+ public static function str_split($str, $split_length = 1)
+ {
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _str_split($str, $split_length);
+ }
+
+ /**
+ * Reverses a UTF-8 string. This is a UTF8-aware version of [strrev](http://php.net/strrev).
+ *
+ * $str = UTF8::strrev($str);
+ *
+ * @author Harry Fuecks
+ * @param string string to be reversed
+ * @return string
+ */
+ public static function strrev($str)
+ {
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _strrev($str);
+ }
+
+ /**
+ * Strips whitespace (or other UTF-8 characters) from the beginning and
+ * end of a string. This is a UTF8-aware version of [trim](http://php.net/trim).
+ *
+ * $str = UTF8::trim($str);
+ *
+ * @author Andreas Gohr
+ * @param string input string
+ * @param string string of characters to remove
+ * @return string
+ */
+ public static function trim($str, $charlist = NULL)
+ {
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _trim($str, $charlist);
+ }
+
+ /**
+ * Strips whitespace (or other UTF-8 characters) from the beginning of
+ * a string. This is a UTF8-aware version of [ltrim](http://php.net/ltrim).
+ *
+ * $str = UTF8::ltrim($str);
+ *
+ * @author Andreas Gohr
+ * @param string input string
+ * @param string string of characters to remove
+ * @return string
+ */
+ public static function ltrim($str, $charlist = NULL)
+ {
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _ltrim($str, $charlist);
+ }
+
+ /**
+ * Strips whitespace (or other UTF-8 characters) from the end of a string.
+ * This is a UTF8-aware version of [rtrim](http://php.net/rtrim).
+ *
+ * $str = UTF8::rtrim($str);
+ *
+ * @author Andreas Gohr
+ * @param string input string
+ * @param string string of characters to remove
+ * @return string
+ */
+ public static function rtrim($str, $charlist = NULL)
+ {
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _rtrim($str, $charlist);
+ }
+
+ /**
+ * Returns the unicode ordinal for a character. This is a UTF8-aware
+ * version of [ord](http://php.net/ord).
+ *
+ * $digit = UTF8::ord($character);
+ *
+ * @author Harry Fuecks
+ * @param string UTF-8 encoded character
+ * @return integer
+ */
+ public static function ord($chr)
+ {
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _ord($chr);
+ }
+
+ /**
+ * Takes an UTF-8 string and returns an array of ints representing the Unicode characters.
+ * Astral planes are supported i.e. the ints in the output can be > 0xFFFF.
+ * Occurrences of the BOM are ignored. Surrogates are not allowed.
+ *
+ * $array = UTF8::to_unicode($str);
+ *
+ * The Original Code is Mozilla Communicator client code.
+ * The Initial Developer of the Original Code is Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer.
+ * Ported to PHP by Henri Sivonen , see
+ * Slight modifications to fit with phputf8 library by Harry Fuecks
+ *
+ * @param string UTF-8 encoded string
+ * @return array unicode code points
+ * @return FALSE if the string is invalid
+ */
+ public static function to_unicode($str)
+ {
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _to_unicode($str);
+ }
+
+ /**
+ * Takes an array of ints representing the Unicode characters and returns a UTF-8 string.
+ * Astral planes are supported i.e. the ints in the input can be > 0xFFFF.
+ * Occurrances of the BOM are ignored. Surrogates are not allowed.
+ *
+ * $str = UTF8::to_unicode($array);
+ *
+ * The Original Code is Mozilla Communicator client code.
+ * The Initial Developer of the Original Code is Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer.
+ * Ported to PHP by Henri Sivonen , see http://hsivonen.iki.fi/php-utf8/
+ * Slight modifications to fit with phputf8 library by Harry Fuecks .
+ *
+ * @param array unicode code points representing a string
+ * @return string utf8 string of characters
+ * @return boolean FALSE if a code point cannot be found
+ */
+ public static function from_unicode($arr)
+ {
+ if ( ! isset(self::$called[__FUNCTION__]))
+ {
+ require SYSPATH.'utf8'.DIRECTORY_SEPARATOR.__FUNCTION__.EXT;
+
+ // Function has been called
+ self::$called[__FUNCTION__] = TRUE;
+ }
+
+ return _from_unicode($arr);
+ }
+
+} // End UTF8
+
+if (Kohana_UTF8::$server_utf8 === NULL)
+{
+ // Determine if this server supports UTF-8 natively
+ Kohana_UTF8::$server_utf8 = extension_loaded('mbstring');
+}
diff --git a/includes/kohana/system/classes/kohana/validate.php b/includes/kohana/system/classes/kohana/validate.php
new file mode 100644
index 0000000..70b036e
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/validate.php
@@ -0,0 +1,1190 @@
+getArrayCopy();
+ }
+
+ // Value cannot be NULL, FALSE, '', or an empty array
+ return ! in_array($value, array(NULL, FALSE, '', array()), TRUE);
+ }
+
+ /**
+ * Checks a field against a regular expression.
+ *
+ * @param string value
+ * @param string regular expression to match (including delimiters)
+ * @return boolean
+ */
+ public static function regex($value, $expression)
+ {
+ return (bool) preg_match($expression, (string) $value);
+ }
+
+ /**
+ * Checks that a field is long enough.
+ *
+ * @param string value
+ * @param integer minimum length required
+ * @return boolean
+ */
+ public static function min_length($value, $length)
+ {
+ return UTF8::strlen($value) >= $length;
+ }
+
+ /**
+ * Checks that a field is short enough.
+ *
+ * @param string value
+ * @param integer maximum length required
+ * @return boolean
+ */
+ public static function max_length($value, $length)
+ {
+ return UTF8::strlen($value) <= $length;
+ }
+
+ /**
+ * Checks that a field is exactly the right length.
+ *
+ * @param string value
+ * @param integer exact length required
+ * @return boolean
+ */
+ public static function exact_length($value, $length)
+ {
+ return UTF8::strlen($value) === $length;
+ }
+
+ /**
+ * CHecks that a field is exactly the value required.
+ *
+ * @param string value
+ * @param string required value
+ * @return boolean
+ */
+ public static function equals($value, $required)
+ {
+ return ($value === $required);
+ }
+
+ /**
+ * Check an email address for correct format.
+ *
+ * @link http://www.iamcal.com/publish/articles/php/parsing_email/
+ * @link http://www.w3.org/Protocols/rfc822/
+ *
+ * @param string email address
+ * @param boolean strict RFC compatibility
+ * @return boolean
+ */
+ public static function email($email, $strict = FALSE)
+ {
+ if ($strict === TRUE)
+ {
+ $qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]';
+ $dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]';
+ $atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+';
+ $pair = '\\x5c[\\x00-\\x7f]';
+
+ $domain_literal = "\\x5b($dtext|$pair)*\\x5d";
+ $quoted_string = "\\x22($qtext|$pair)*\\x22";
+ $sub_domain = "($atom|$domain_literal)";
+ $word = "($atom|$quoted_string)";
+ $domain = "$sub_domain(\\x2e$sub_domain)*";
+ $local_part = "$word(\\x2e$word)*";
+
+ $expression = "/^$local_part\\x40$domain$/D";
+ }
+ else
+ {
+ $expression = '/^[-_a-z0-9\'+*$^&%=~!?{}]++(?:\.[-_a-z0-9\'+*$^&%=~!?{}]+)*+@(?:(?![-.])[-a-z0-9.]+(? 253)
+ return FALSE;
+
+ // An extra check for the top level domain
+ // It must start with a letter
+ $tld = ltrim(substr($matches[1], (int) strrpos($matches[1], '.')), '.');
+ return ctype_alpha($tld[0]);
+ }
+
+ /**
+ * Validate an IP.
+ *
+ * @param string IP address
+ * @param boolean allow private IP networks
+ * @return boolean
+ */
+ public static function ip($ip, $allow_private = TRUE)
+ {
+ // Do not allow reserved addresses
+ $flags = FILTER_FLAG_NO_RES_RANGE;
+
+ if ($allow_private === FALSE)
+ {
+ // Do not allow private or reserved addresses
+ $flags = $flags | FILTER_FLAG_NO_PRIV_RANGE;
+ }
+
+ return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flags);
+ }
+
+ /**
+ * Validates a credit card number, with a Luhn check if possible.
+ *
+ * @param integer credit card number
+ * @param string|array card type, or an array of card types
+ * @return boolean
+ * @uses Validate::luhn
+ */
+ public static function credit_card($number, $type = NULL)
+ {
+ // Remove all non-digit characters from the number
+ if (($number = preg_replace('/\D+/', '', $number)) === '')
+ return FALSE;
+
+ if ($type == NULL)
+ {
+ // Use the default type
+ $type = 'default';
+ }
+ elseif (is_array($type))
+ {
+ foreach ($type as $t)
+ {
+ // Test each type for validity
+ if (Validate::credit_card($number, $t))
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ $cards = Kohana::config('credit_cards');
+
+ // Check card type
+ $type = strtolower($type);
+
+ if ( ! isset($cards[$type]))
+ return FALSE;
+
+ // Check card number length
+ $length = strlen($number);
+
+ // Validate the card length by the card type
+ if ( ! in_array($length, preg_split('/\D+/', $cards[$type]['length'])))
+ return FALSE;
+
+ // Check card number prefix
+ if ( ! preg_match('/^'.$cards[$type]['prefix'].'/', $number))
+ return FALSE;
+
+ // No Luhn check required
+ if ($cards[$type]['luhn'] == FALSE)
+ return TRUE;
+
+ return Validate::luhn($number);
+ }
+
+ /**
+ * Validate a number against the [Luhn](http://en.wikipedia.org/wiki/Luhn_algorithm)
+ * (mod10) formula.
+ *
+ * @param string number to check
+ * @return boolean
+ */
+ public static function luhn($number)
+ {
+ // Force the value to be a string as this method uses string functions.
+ // Converting to an integer may pass PHP_INT_MAX and result in an error!
+ $number = (string) $number;
+
+ if ( ! ctype_digit($number))
+ {
+ // Luhn can only be used on numbers!
+ return FALSE;
+ }
+
+ // Check number length
+ $length = strlen($number);
+
+ // Checksum of the card number
+ $checksum = 0;
+
+ for ($i = $length - 1; $i >= 0; $i -= 2)
+ {
+ // Add up every 2nd digit, starting from the right
+ $checksum += substr($number, $i, 1);
+ }
+
+ for ($i = $length - 2; $i >= 0; $i -= 2)
+ {
+ // Add up every 2nd digit doubled, starting from the right
+ $double = substr($number, $i, 1) * 2;
+
+ // Subtract 9 from the double where value is greater than 10
+ $checksum += ($double >= 10) ? ($double - 9) : $double;
+ }
+
+ // If the checksum is a multiple of 10, the number is valid
+ return ($checksum % 10 === 0);
+ }
+
+ /**
+ * Checks if a phone number is valid.
+ *
+ * @param string phone number to check
+ * @return boolean
+ */
+ public static function phone($number, $lengths = NULL)
+ {
+ if ( ! is_array($lengths))
+ {
+ $lengths = array(7,10,11);
+ }
+
+ // Remove all non-digit characters from the number
+ $number = preg_replace('/\D+/', '', $number);
+
+ // Check if the number is within range
+ return in_array(strlen($number), $lengths);
+ }
+
+ /**
+ * Tests if a string is a valid date string.
+ *
+ * @param string date to check
+ * @return boolean
+ */
+ public static function date($str)
+ {
+ return (strtotime($str) !== FALSE);
+ }
+
+ /**
+ * Checks whether a string consists of alphabetical characters only.
+ *
+ * @param string input string
+ * @param boolean trigger UTF-8 compatibility
+ * @return boolean
+ */
+ public static function alpha($str, $utf8 = FALSE)
+ {
+ $str = (string) $str;
+
+ if ($utf8 === TRUE)
+ {
+ return (bool) preg_match('/^\pL++$/uD', $str);
+ }
+ else
+ {
+ return ctype_alpha($str);
+ }
+ }
+
+ /**
+ * Checks whether a string consists of alphabetical characters and numbers only.
+ *
+ * @param string input string
+ * @param boolean trigger UTF-8 compatibility
+ * @return boolean
+ */
+ public static function alpha_numeric($str, $utf8 = FALSE)
+ {
+ if ($utf8 === TRUE)
+ {
+ return (bool) preg_match('/^[\pL\pN]++$/uD', $str);
+ }
+ else
+ {
+ return ctype_alnum($str);
+ }
+ }
+
+ /**
+ * Checks whether a string consists of alphabetical characters, numbers, underscores and dashes only.
+ *
+ * @param string input string
+ * @param boolean trigger UTF-8 compatibility
+ * @return boolean
+ */
+ public static function alpha_dash($str, $utf8 = FALSE)
+ {
+ if ($utf8 === TRUE)
+ {
+ $regex = '/^[-\pL\pN_]++$/uD';
+ }
+ else
+ {
+ $regex = '/^[-a-z0-9_]++$/iD';
+ }
+
+ return (bool) preg_match($regex, $str);
+ }
+
+ /**
+ * Checks whether a string consists of digits only (no dots or dashes).
+ *
+ * @param string input string
+ * @param boolean trigger UTF-8 compatibility
+ * @return boolean
+ */
+ public static function digit($str, $utf8 = FALSE)
+ {
+ if ($utf8 === TRUE)
+ {
+ return (bool) preg_match('/^\pN++$/uD', $str);
+ }
+ else
+ {
+ return (is_int($str) AND $str >= 0) OR ctype_digit($str);
+ }
+ }
+
+ /**
+ * Checks whether a string is a valid number (negative and decimal numbers allowed).
+ *
+ * Uses {@link http://www.php.net/manual/en/function.localeconv.php locale conversion}
+ * to allow decimal point to be locale specific.
+ *
+ * @param string input string
+ * @return boolean
+ */
+ public static function numeric($str)
+ {
+ // Get the decimal point for the current locale
+ list($decimal) = array_values(localeconv());
+
+ // A lookahead is used to make sure the string contains at least one digit (before or after the decimal point)
+ return (bool) preg_match('/^-?+(?=.*[0-9])[0-9]*+'.preg_quote($decimal).'?+[0-9]*+$/D', (string) $str);
+ }
+
+ /**
+ * Tests if a number is within a range.
+ *
+ * @param string number to check
+ * @param integer minimum value
+ * @param integer maximum value
+ * @return boolean
+ */
+ public static function range($number, $min, $max)
+ {
+ return ($number >= $min AND $number <= $max);
+ }
+
+ /**
+ * Checks if a string is a proper decimal format. Optionally, a specific
+ * number of digits can be checked too.
+ *
+ * @param string number to check
+ * @param integer number of decimal places
+ * @param integer number of digits
+ * @return boolean
+ */
+ public static function decimal($str, $places = 2, $digits = NULL)
+ {
+ if ($digits > 0)
+ {
+ // Specific number of digits
+ $digits = '{'. (int) $digits.'}';
+ }
+ else
+ {
+ // Any number of digits
+ $digits = '+';
+ }
+
+ // Get the decimal point for the current locale
+ list($decimal) = array_values(localeconv());
+
+ return (bool) preg_match('/^[0-9]'.$digits.preg_quote($decimal).'[0-9]{'. (int) $places.'}$/D', $str);
+ }
+
+ /**
+ * Checks if a string is a proper hexadecimal HTML color value. The validation
+ * is quite flexible as it does not require an initial "#" and also allows for
+ * the short notation using only three instead of six hexadecimal characters.
+ *
+ * @param string input string
+ * @return boolean
+ */
+ public static function color($str)
+ {
+ return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $str);
+ }
+
+ // Field filters
+ protected $_filters = array();
+
+ // Field rules
+ protected $_rules = array();
+
+ // Field callbacks
+ protected $_callbacks = array();
+
+ // Field labels
+ protected $_labels = array();
+
+ // Rules that are executed even when the value is empty
+ protected $_empty_rules = array('not_empty', 'matches');
+
+ // Error list, field => rule
+ protected $_errors = array();
+
+ /**
+ * Sets the unique "any field" key and creates an ArrayObject from the
+ * passed array.
+ *
+ * @param array array to validate
+ * @return void
+ */
+ public function __construct(array $array)
+ {
+ parent::__construct($array, ArrayObject::STD_PROP_LIST);
+ }
+
+ /**
+ * Copies the current filter/rule/callback to a new array.
+ *
+ * $copy = $array->copy($new_data);
+ *
+ * @param array new data set
+ * @return Validation
+ * @since 3.0.5
+ */
+ public function copy(array $array)
+ {
+ // Create a copy of the current validation set
+ $copy = clone $this;
+
+ // Replace the data set
+ $copy->exchangeArray($array);
+
+ return $copy;
+ }
+
+ /**
+ * Returns the array representation of the current object.
+ *
+ * @return array
+ */
+ public function as_array()
+ {
+ return $this->getArrayCopy();
+ }
+
+ /**
+ * Sets or overwrites the label name for a field.
+ *
+ * @param string field name
+ * @param string label
+ * @return $this
+ */
+ public function label($field, $label)
+ {
+ // Set the label for this field
+ $this->_labels[$field] = $label;
+
+ return $this;
+ }
+
+ /**
+ * Sets labels using an array.
+ *
+ * @param array list of field => label names
+ * @return $this
+ */
+ public function labels(array $labels)
+ {
+ $this->_labels = $labels + $this->_labels;
+
+ return $this;
+ }
+
+ /**
+ * Overwrites or appends filters to a field. Each filter will be executed once.
+ * All rules must be valid PHP callbacks.
+ *
+ * // Run trim() on all fields
+ * $validation->filter(TRUE, 'trim');
+ *
+ * @param string field name
+ * @param mixed valid PHP callback
+ * @param array extra parameters for the filter
+ * @return $this
+ */
+ public function filter($field, $filter, array $params = NULL)
+ {
+ if ($field !== TRUE AND ! isset($this->_labels[$field]))
+ {
+ // Set the field label to the field name
+ $this->_labels[$field] = preg_replace('/[^\pL]+/u', ' ', $field);
+ }
+
+ // Store the filter and params for this rule
+ $this->_filters[$field][$filter] = (array) $params;
+
+ return $this;
+ }
+
+ /**
+ * Add filters using an array.
+ *
+ * @param string field name
+ * @param array list of functions or static method name
+ * @return $this
+ */
+ public function filters($field, array $filters)
+ {
+ foreach ($filters as $filter => $params)
+ {
+ $this->filter($field, $filter, $params);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Overwrites or appends rules to a field. Each rule will be executed once.
+ * All rules must be string names of functions method names.
+ *
+ * // The "username" must not be empty and have a minimum length of 4
+ * $validation->rule('username', 'not_empty')
+ * ->rule('username', 'min_length', array(4));
+ *
+ * @param string field name
+ * @param string function or static method name
+ * @param array extra parameters for the rule
+ * @return $this
+ */
+ public function rule($field, $rule, array $params = NULL)
+ {
+ if ($field !== TRUE AND ! isset($this->_labels[$field]))
+ {
+ // Set the field label to the field name
+ $this->_labels[$field] = trim(preg_replace('/[^\pL]+/u', ' ', $field));
+ }
+
+ if ('matches' === $rule AND ! isset($this->_labels[$params[0]]))
+ {
+ $match_field = $params[0];
+ $this->_labels[$match_field] = trim(preg_replace('/[^\pL]+/u', ' ', $match_field));
+ }
+
+ // Store the rule and params for this rule
+ $this->_rules[$field][$rule] = (array) $params;
+
+ return $this;
+ }
+
+ /**
+ * Add rules using an array.
+ *
+ * @param string field name
+ * @param array list of functions or static method name
+ * @return $this
+ */
+ public function rules($field, array $rules)
+ {
+ foreach ($rules as $rule => $params)
+ {
+ $this->rule($field, $rule, $params);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a callback to a field. Each callback will be executed only once.
+ *
+ * // The "username" must be checked with a custom method
+ * $validation->callback('username', array($this, 'check_username'));
+ *
+ * To add a callback to every field already set, use TRUE for the field name.
+ *
+ * @param string field name
+ * @param mixed callback to add
+ * @param array extra parameters for the callback
+ * @return $this
+ */
+ public function callback($field, $callback, array $params = array())
+ {
+ if ( ! isset($this->_callbacks[$field]))
+ {
+ // Create the list for this field
+ $this->_callbacks[$field] = array();
+ }
+
+ if ($field !== TRUE AND ! isset($this->_labels[$field]))
+ {
+ // Set the field label to the field name
+ $this->_labels[$field] = preg_replace('/[^\pL]+/u', ' ', $field);
+ }
+
+ if ( ! in_array($callback, $this->_callbacks[$field], TRUE))
+ {
+ // Store the callback
+ $this->_callbacks[$field][] = array($callback, $params);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add callbacks using an array.
+ *
+ * @param string field name
+ * @param array list of callbacks
+ * @return $this
+ */
+ public function callbacks($field, array $callbacks)
+ {
+ foreach ($callbacks as $callback)
+ {
+ $this->callback($field, $callback);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Executes all validation filters, rules, and callbacks. This should
+ * typically be called within an if/else block.
+ *
+ * if ($validation->check())
+ * {
+ * // The data is valid, do something here
+ * }
+ *
+ * @param boolean allow empty array?
+ * @return boolean
+ */
+ public function check($allow_empty = FALSE)
+ {
+ if (Kohana::$profiling === TRUE)
+ {
+ // Start a new benchmark
+ $benchmark = Profiler::start('Validation', __FUNCTION__);
+ }
+
+ // New data set
+ $data = $this->_errors = array();
+
+ // Assume nothing has been submitted
+ $submitted = FALSE;
+
+ // Get a list of the expected fields
+ $expected = array_keys($this->_labels);
+
+ // Import the filters, rules, and callbacks locally
+ $filters = $this->_filters;
+ $rules = $this->_rules;
+ $callbacks = $this->_callbacks;
+
+ foreach ($expected as $field)
+ {
+ if (isset($this[$field]))
+ {
+ // Some data has been submitted, continue validation
+ $submitted = TRUE;
+
+ // Use the submitted value
+ $data[$field] = $this[$field];
+ }
+ else
+ {
+ // No data exists for this field
+ $data[$field] = NULL;
+ }
+
+ if (isset($filters[TRUE]))
+ {
+ if ( ! isset($filters[$field]))
+ {
+ // Initialize the filters for this field
+ $filters[$field] = array();
+ }
+
+ // Append the filters
+ $filters[$field] += $filters[TRUE];
+ }
+
+ if (isset($rules[TRUE]))
+ {
+ if ( ! isset($rules[$field]))
+ {
+ // Initialize the rules for this field
+ $rules[$field] = array();
+ }
+
+ // Append the rules
+ $rules[$field] += $rules[TRUE];
+ }
+
+ if (isset($callbacks[TRUE]))
+ {
+ if ( ! isset($callbacks[$field]))
+ {
+ // Initialize the callbacks for this field
+ $callbacks[$field] = array();
+ }
+
+ // Append the callbacks
+ $callbacks[$field] += $callbacks[TRUE];
+ }
+ }
+
+ // Overload the current array with the new one
+ $this->exchangeArray($data);
+
+ if ($submitted === FALSE AND ! $allow_empty)
+ {
+ // Because no data was submitted, validation will not be forced
+ return FALSE;
+ }
+
+ // Remove the filters, rules, and callbacks that apply to every field
+ unset($filters[TRUE], $rules[TRUE], $callbacks[TRUE]);
+
+ // Execute the filters
+
+ foreach ($filters as $field => $set)
+ {
+ // Get the field value
+ $value = $this[$field];
+
+ foreach ($set as $filter => $params)
+ {
+ // Add the field value to the parameters
+ array_unshift($params, $value);
+
+ if (strpos($filter, '::') === FALSE)
+ {
+ // Use a function call
+ $function = new ReflectionFunction($filter);
+
+ // Call $function($this[$field], $param, ...) with Reflection
+ $value = $function->invokeArgs($params);
+ }
+ else
+ {
+ // Split the class and method of the rule
+ list($class, $method) = explode('::', $filter, 2);
+
+ // Use a static method call
+ $method = new ReflectionMethod($class, $method);
+
+ // Call $Class::$method($this[$field], $param, ...) with Reflection
+ $value = $method->invokeArgs(NULL, $params);
+ }
+ }
+
+ // Set the filtered value
+ $this[$field] = $value;
+ }
+
+ // Execute the rules
+
+ foreach ($rules as $field => $set)
+ {
+ // Get the field value
+ $value = $this[$field];
+
+ foreach ($set as $rule => $params)
+ {
+ if ( ! in_array($rule, $this->_empty_rules) AND ! Validate::not_empty($value))
+ {
+ // Skip this rule for empty fields
+ continue;
+ }
+
+ // Add the field value to the parameters
+ array_unshift($params, $value);
+
+ if (method_exists($this, $rule))
+ {
+ // Use a method in this object
+ $method = new ReflectionMethod($this, $rule);
+
+ if ($method->isStatic())
+ {
+ // Call static::$rule($this[$field], $param, ...) with Reflection
+ $passed = $method->invokeArgs(NULL, $params);
+ }
+ else
+ {
+ // Do not use Reflection here, the method may be protected
+ $passed = call_user_func_array(array($this, $rule), $params);
+ }
+ }
+ elseif (strpos($rule, '::') === FALSE)
+ {
+ // Use a function call
+ $function = new ReflectionFunction($rule);
+
+ // Call $function($this[$field], $param, ...) with Reflection
+ $passed = $function->invokeArgs($params);
+ }
+ else
+ {
+ // Split the class and method of the rule
+ list($class, $method) = explode('::', $rule, 2);
+
+ // Use a static method call
+ $method = new ReflectionMethod($class, $method);
+
+ // Call $Class::$method($this[$field], $param, ...) with Reflection
+ $passed = $method->invokeArgs(NULL, $params);
+ }
+
+ if ($passed === FALSE)
+ {
+ // Remove the field value from the parameters
+ array_shift($params);
+
+ // Add the rule to the errors
+ $this->error($field, $rule, $params);
+
+ // This field has an error, stop executing rules
+ break;
+ }
+ }
+ }
+
+ // Execute the callbacks
+
+ foreach ($callbacks as $field => $set)
+ {
+ if (isset($this->_errors[$field]))
+ {
+ // Skip any field that already has an error
+ continue;
+ }
+
+ foreach ($set as $callback_array)
+ {
+ list($callback, $params) = $callback_array;
+
+ if (is_string($callback) AND strpos($callback, '::') !== FALSE)
+ {
+ // Make the static callback into an array
+ $callback = explode('::', $callback, 2);
+ }
+
+ if (is_array($callback))
+ {
+ // Separate the object and method
+ list ($object, $method) = $callback;
+
+ // Use a method in the given object
+ $method = new ReflectionMethod($object, $method);
+
+ if ( ! is_object($object))
+ {
+ // The object must be NULL for static calls
+ $object = NULL;
+ }
+
+ // Call $object->$method($this, $field, $errors) with Reflection
+ $method->invoke($object, $this, $field, $params);
+ }
+ else
+ {
+ // Use a function call
+ $function = new ReflectionFunction($callback);
+
+ // Call $function($this, $field, $errors) with Reflection
+ $function->invoke($this, $field, $params);
+ }
+
+ if (isset($this->_errors[$field]))
+ {
+ // An error was added, stop processing callbacks
+ break;
+ }
+ }
+ }
+
+ if (isset($benchmark))
+ {
+ // Stop benchmarking
+ Profiler::stop($benchmark);
+ }
+
+ return empty($this->_errors);
+ }
+
+ /**
+ * Add an error to a field.
+ *
+ * @param string field name
+ * @param string error message
+ * @return $this
+ */
+ public function error($field, $error, array $params = NULL)
+ {
+ $this->_errors[$field] = array($error, $params);
+
+ return $this;
+ }
+
+ /**
+ * Returns the error messages. If no file is specified, the error message
+ * will be the name of the rule that failed. When a file is specified, the
+ * message will be loaded from "field/rule", or if no rule-specific message
+ * exists, "field/default" will be used. If neither is set, the returned
+ * message will be "file/field/rule".
+ *
+ * By default all messages are translated using the default language.
+ * A string can be used as the second parameter to specified the language
+ * that the message was written in.
+ *
+ * // Get errors from messages/forms/login.php
+ * $errors = $validate->errors('forms/login');
+ *
+ * @uses Kohana::message
+ * @param string file to load error messages from
+ * @param mixed translate the message
+ * @return array
+ */
+ public function errors($file = NULL, $translate = TRUE)
+ {
+ if ($file === NULL)
+ {
+ // Return the error list
+ return $this->_errors;
+ }
+
+ // Create a new message list
+ $messages = array();
+
+ foreach ($this->_errors as $field => $set)
+ {
+ list($error, $params) = $set;
+
+ // Get the label for this field
+ $label = $this->_labels[$field];
+
+ if ($translate)
+ {
+ if (is_string($translate))
+ {
+ // Translate the label using the specified language
+ $label = __($label, NULL, $translate);
+ }
+ else
+ {
+ // Translate the label
+ $label = __($label);
+ }
+ }
+
+ // Start the translation values list
+ $values = array(
+ ':field' => $label,
+ ':value' => $this[$field],
+ );
+
+ if (is_array($values[':value']))
+ {
+ // All values must be strings
+ $values[':value'] = implode(', ', Arr::flatten($values[':value']));
+ }
+
+ if ($params)
+ {
+ foreach ($params as $key => $value)
+ {
+ if (is_array($value))
+ {
+ // All values must be strings
+ $value = implode(', ', Arr::flatten($value));
+ }
+
+ // Check if a label for this parameter exists
+ if (isset($this->_labels[$value]))
+ {
+ // Use the label as the value, eg: related field name for "matches"
+ $value = $this->_labels[$value];
+
+ if ($translate)
+ {
+ if (is_string($translate))
+ {
+ // Translate the value using the specified language
+ $value = __($value, NULL, $translate);
+ }
+ else
+ {
+ // Translate the value
+ $value = __($value);
+ }
+ }
+ }
+
+ // Add each parameter as a numbered value, starting from 1
+ $values[':param'.($key + 1)] = $value;
+ }
+ }
+
+ if ($message = Kohana::message($file, "{$field}.{$error}"))
+ {
+ // Found a message for this field and error
+ }
+ elseif ($message = Kohana::message($file, "{$field}.default"))
+ {
+ // Found a default message for this field
+ }
+ elseif ($message = Kohana::message($file, $error))
+ {
+ // Found a default message for this error
+ }
+ elseif ($message = Kohana::message('validate', $error))
+ {
+ // Found a default message for this error
+ }
+ else
+ {
+ // No message exists, display the path expected
+ $message = "{$file}.{$field}.{$error}";
+ }
+
+ if ($translate)
+ {
+ if (is_string($translate))
+ {
+ // Translate the message using specified language
+ $message = __($message, $values, $translate);
+ }
+ else
+ {
+ // Translate the message using the default language
+ $message = __($message, $values);
+ }
+ }
+ else
+ {
+ // Do not translate, just replace the values
+ $message = strtr($message, $values);
+ }
+
+ // Set the message for this field
+ $messages[$field] = $message;
+ }
+
+ return $messages;
+ }
+
+ /**
+ * Checks if a field matches the value of another field.
+ *
+ * @param string field value
+ * @param string field name to match
+ * @return boolean
+ */
+ protected function matches($value, $match)
+ {
+ return ($value === $this[$match]);
+ }
+
+} // End Validation
diff --git a/includes/kohana/system/classes/kohana/validate/exception.php b/includes/kohana/system/classes/kohana/validate/exception.php
new file mode 100644
index 0000000..bde9095
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/validate/exception.php
@@ -0,0 +1,29 @@
+array = $array;
+
+ parent::__construct($message, $values, $code);
+ }
+
+} // End Kohana_Validate_Exception
diff --git a/includes/kohana/system/classes/kohana/view.php b/includes/kohana/system/classes/kohana/view.php
new file mode 100644
index 0000000..1839700
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/view.php
@@ -0,0 +1,346 @@
+ $value)
+ {
+ View::$_global_data[$key2] = $value;
+ }
+ }
+ else
+ {
+ View::$_global_data[$key] = $value;
+ }
+ }
+
+ /**
+ * Assigns a global variable by reference, similar to [View::bind], except
+ * that the variable will be accessible to all views.
+ *
+ * View::bind_global($key, $value);
+ *
+ * @param string variable name
+ * @param mixed referenced variable
+ * @return void
+ */
+ public static function bind_global($key, & $value)
+ {
+ View::$_global_data[$key] =& $value;
+ }
+
+ // View filename
+ protected $_file;
+
+ // Array of local variables
+ protected $_data = array();
+
+ /**
+ * Sets the initial view filename and local data. Views should almost
+ * always only be created using [View::factory].
+ *
+ * $view = new View($file);
+ *
+ * @param string view filename
+ * @param array array of values
+ * @return void
+ * @uses View::set_filename
+ */
+ public function __construct($file = NULL, array $data = NULL)
+ {
+ if ($file !== NULL)
+ {
+ $this->set_filename($file);
+ }
+
+ if ($data !== NULL)
+ {
+ // Add the values to the current data
+ $this->_data = $data + $this->_data;
+ }
+ }
+
+ /**
+ * Magic method, searches for the given variable and returns its value.
+ * Local variables will be returned before global variables.
+ *
+ * $value = $view->foo;
+ *
+ * [!!] If the variable has not yet been set, an exception will be thrown.
+ *
+ * @param string variable name
+ * @return mixed
+ * @throws Kohana_Exception
+ */
+ public function & __get($key)
+ {
+ if (array_key_exists($key, $this->_data))
+ {
+ return $this->_data[$key];
+ }
+ elseif (array_key_exists($key, View::$_global_data))
+ {
+ return View::$_global_data[$key];
+ }
+ else
+ {
+ throw new Kohana_Exception('View variable is not set: :var',
+ array(':var' => $key));
+ }
+ }
+
+ /**
+ * Magic method, calls [View::set] with the same parameters.
+ *
+ * $view->foo = 'something';
+ *
+ * @param string variable name
+ * @param mixed value
+ * @return void
+ */
+ public function __set($key, $value)
+ {
+ $this->set($key, $value);
+ }
+
+ /**
+ * Magic method, determines if a variable is set.
+ *
+ * isset($view->foo);
+ *
+ * [!!] `NULL` variables are not considered to be set by [isset](http://php.net/isset).
+ *
+ * @param string variable name
+ * @return boolean
+ */
+ public function __isset($key)
+ {
+ return (isset($this->_data[$key]) OR isset(View::$_global_data[$key]));
+ }
+
+ /**
+ * Magic method, unsets a given variable.
+ *
+ * unset($view->foo);
+ *
+ * @param string variable name
+ * @return void
+ */
+ public function __unset($key)
+ {
+ unset($this->_data[$key], View::$_global_data[$key]);
+ }
+
+ /**
+ * Magic method, returns the output of [View::render].
+ *
+ * @return string
+ * @uses View::render
+ */
+ public function __toString()
+ {
+ try
+ {
+ return $this->render();
+ }
+ catch (Exception $e)
+ {
+ // Display the exception message
+ Kohana::exception_handler($e);
+
+ return '';
+ }
+ }
+
+ /**
+ * Sets the view filename.
+ *
+ * $view->set_filename($file);
+ *
+ * @param string view filename
+ * @return View
+ * @throws Kohana_View_Exception
+ */
+ public function set_filename($file)
+ {
+ if (($path = Kohana::find_file('views', $file)) === FALSE)
+ {
+ throw new Kohana_View_Exception('The requested view :file could not be found', array(
+ ':file' => $file,
+ ));
+ }
+
+ // Store the file path locally
+ $this->_file = $path;
+
+ return $this;
+ }
+
+ /**
+ * Assigns a variable by name. Assigned values will be available as a
+ * variable within the view file:
+ *
+ * // This value can be accessed as $foo within the view
+ * $view->set('foo', 'my value');
+ *
+ * You can also use an array to set several values at once:
+ *
+ * // Create the values $food and $beverage in the view
+ * $view->set(array('food' => 'bread', 'beverage' => 'water'));
+ *
+ * @param string variable name or an array of variables
+ * @param mixed value
+ * @return $this
+ */
+ public function set($key, $value = NULL)
+ {
+ if (is_array($key))
+ {
+ foreach ($key as $name => $value)
+ {
+ $this->_data[$name] = $value;
+ }
+ }
+ else
+ {
+ $this->_data[$key] = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Assigns a value by reference. The benefit of binding is that values can
+ * be altered without re-setting them. It is also possible to bind variables
+ * before they have values. Assigned values will be available as a
+ * variable within the view file:
+ *
+ * // This reference can be accessed as $ref within the view
+ * $view->bind('ref', $bar);
+ *
+ * @param string variable name
+ * @param mixed referenced variable
+ * @return $this
+ */
+ public function bind($key, & $value)
+ {
+ $this->_data[$key] =& $value;
+
+ return $this;
+ }
+
+ /**
+ * Renders the view object to a string. Global and local data are merged
+ * and extracted to create local variables within the view file.
+ *
+ * $output = $view->render();
+ *
+ * [!!] Global variables with the same key name as local variables will be
+ * overwritten by the local variable.
+ *
+ * @param string view filename
+ * @return string
+ * @throws Kohana_View_Exception
+ * @uses View::capture
+ */
+ public function render($file = NULL)
+ {
+ if ($file !== NULL)
+ {
+ $this->set_filename($file);
+ }
+
+ if (empty($this->_file))
+ {
+ throw new Kohana_View_Exception('You must set the file to use within your view before rendering');
+ }
+
+ // Combine local and global data and capture the output
+ return View::capture($this->_file, $this->_data);
+ }
+
+} // End View
diff --git a/includes/kohana/system/classes/kohana/view/exception.php b/includes/kohana/system/classes/kohana/view/exception.php
new file mode 100644
index 0000000..a83ba4e
--- /dev/null
+++ b/includes/kohana/system/classes/kohana/view/exception.php
@@ -0,0 +1,9 @@
+ array(
+ 'length' => '13,14,15,16,17,18,19',
+ 'prefix' => '',
+ 'luhn' => TRUE,
+ ),
+
+ 'american express' => array(
+ 'length' => '15',
+ 'prefix' => '3[47]',
+ 'luhn' => TRUE,
+ ),
+
+ 'diners club' => array(
+ 'length' => '14,16',
+ 'prefix' => '36|55|30[0-5]',
+ 'luhn' => TRUE,
+ ),
+
+ 'discover' => array(
+ 'length' => '16',
+ 'prefix' => '6(?:5|011)',
+ 'luhn' => TRUE,
+ ),
+
+ 'jcb' => array(
+ 'length' => '15,16',
+ 'prefix' => '3|1800|2131',
+ 'luhn' => TRUE,
+ ),
+
+ 'maestro' => array(
+ 'length' => '16,18',
+ 'prefix' => '50(?:20|38)|6(?:304|759)',
+ 'luhn' => TRUE,
+ ),
+
+ 'mastercard' => array(
+ 'length' => '16',
+ 'prefix' => '5[1-5]',
+ 'luhn' => TRUE,
+ ),
+
+ 'visa' => array(
+ 'length' => '13,16',
+ 'prefix' => '4',
+ 'luhn' => TRUE,
+ ),
+
+);
\ No newline at end of file
diff --git a/includes/kohana/system/config/encrypt.php b/includes/kohana/system/config/encrypt.php
new file mode 100644
index 0000000..ae9126a
--- /dev/null
+++ b/includes/kohana/system/config/encrypt.php
@@ -0,0 +1,17 @@
+ array(
+ /**
+ * The following options must be set:
+ *
+ * string key secret passphrase
+ * integer mode encryption mode, one of MCRYPT_MODE_*
+ * integer cipher encryption cipher, one of the Mcrpyt cipher constants
+ */
+ 'cipher' => MCRYPT_RIJNDAEL_128,
+ 'mode' => MCRYPT_MODE_NOFB,
+ ),
+
+);
diff --git a/includes/kohana/system/config/inflector.php b/includes/kohana/system/config/inflector.php
new file mode 100644
index 0000000..f54bb50
--- /dev/null
+++ b/includes/kohana/system/config/inflector.php
@@ -0,0 +1,65 @@
+ array(
+ 'access',
+ 'advice',
+ 'art',
+ 'baggage',
+ 'dances',
+ 'equipment',
+ 'fish',
+ 'fuel',
+ 'furniture',
+ 'heat',
+ 'honey',
+ 'homework',
+ 'impatience',
+ 'information',
+ 'knowledge',
+ 'luggage',
+ 'media',
+ 'money',
+ 'music',
+ 'news',
+ 'patience',
+ 'progress',
+ 'pollution',
+ 'research',
+ 'rice',
+ 'sand',
+ 'series',
+ 'sheep',
+ 'sms',
+ 'spam',
+ 'species',
+ 'staff',
+ 'toothpaste',
+ 'traffic',
+ 'understanding',
+ 'water',
+ 'weather',
+ 'work',
+ ),
+
+ 'irregular' => array(
+ 'child' => 'children',
+ 'clothes' => 'clothing',
+ 'man' => 'men',
+ 'movie' => 'movies',
+ 'person' => 'people',
+ 'woman' => 'women',
+ 'mouse' => 'mice',
+ 'goose' => 'geese',
+ 'ox' => 'oxen',
+ 'leaf' => 'leaves',
+ 'course' => 'courses',
+ 'size' => 'sizes',
+ 'was' => 'were',
+ 'is' => 'are',
+ 'verse' => 'verses',
+ 'hero' => 'heroes',
+ 'purchase' => 'purchases',
+ ),
+);
diff --git a/includes/kohana/system/config/mimes.php b/includes/kohana/system/config/mimes.php
new file mode 100644
index 0000000..e63a9ca
--- /dev/null
+++ b/includes/kohana/system/config/mimes.php
@@ -0,0 +1,225 @@
+ array('text/h323'),
+ '7z' => array('application/x-7z-compressed'),
+ 'abw' => array('application/x-abiword'),
+ 'acx' => array('application/internet-property-stream'),
+ 'ai' => array('application/postscript'),
+ 'aif' => array('audio/x-aiff'),
+ 'aifc' => array('audio/x-aiff'),
+ 'aiff' => array('audio/x-aiff'),
+ 'asf' => array('video/x-ms-asf'),
+ 'asr' => array('video/x-ms-asf'),
+ 'asx' => array('video/x-ms-asf'),
+ 'atom' => array('application/atom+xml'),
+ 'avi' => array('video/avi', 'video/msvideo', 'video/x-msvideo'),
+ 'bin' => array('application/octet-stream','application/macbinary'),
+ 'bmp' => array('image/bmp'),
+ 'c' => array('text/x-csrc'),
+ 'c++' => array('text/x-c++src'),
+ 'cab' => array('application/x-cab'),
+ 'cc' => array('text/x-c++src'),
+ 'cda' => array('application/x-cdf'),
+ 'class' => array('application/octet-stream'),
+ 'cpp' => array('text/x-c++src'),
+ 'cpt' => array('application/mac-compactpro'),
+ 'csh' => array('text/x-csh'),
+ 'css' => array('text/css'),
+ 'csv' => array('text/x-comma-separated-values', 'application/vnd.ms-excel', 'text/comma-separated-values', 'text/csv'),
+ 'dbk' => array('application/docbook+xml'),
+ 'dcr' => array('application/x-director'),
+ 'deb' => array('application/x-debian-package'),
+ 'diff' => array('text/x-diff'),
+ 'dir' => array('application/x-director'),
+ 'divx' => array('video/divx'),
+ 'dll' => array('application/octet-stream', 'application/x-msdos-program'),
+ 'dmg' => array('application/x-apple-diskimage'),
+ 'dms' => array('application/octet-stream'),
+ 'doc' => array('application/msword'),
+ 'docx' => array('application/vnd.openxmlformats-officedocument.wordprocessingml.document'),
+ 'dvi' => array('application/x-dvi'),
+ 'dxr' => array('application/x-director'),
+ 'eml' => array('message/rfc822'),
+ 'eps' => array('application/postscript'),
+ 'evy' => array('application/envoy'),
+ 'exe' => array('application/x-msdos-program', 'application/octet-stream'),
+ 'fla' => array('application/octet-stream'),
+ 'flac' => array('application/x-flac'),
+ 'flc' => array('video/flc'),
+ 'fli' => array('video/fli'),
+ 'flv' => array('video/x-flv'),
+ 'gif' => array('image/gif'),
+ 'gtar' => array('application/x-gtar'),
+ 'gz' => array('application/x-gzip'),
+ 'h' => array('text/x-chdr'),
+ 'h++' => array('text/x-c++hdr'),
+ 'hh' => array('text/x-c++hdr'),
+ 'hpp' => array('text/x-c++hdr'),
+ 'hqx' => array('application/mac-binhex40'),
+ 'hs' => array('text/x-haskell'),
+ 'htm' => array('text/html'),
+ 'html' => array('text/html'),
+ 'ico' => array('image/x-icon'),
+ 'ics' => array('text/calendar'),
+ 'iii' => array('application/x-iphone'),
+ 'ins' => array('application/x-internet-signup'),
+ 'iso' => array('application/x-iso9660-image'),
+ 'isp' => array('application/x-internet-signup'),
+ 'jar' => array('application/java-archive'),
+ 'java' => array('application/x-java-applet'),
+ 'jpe' => array('image/jpeg', 'image/pjpeg'),
+ 'jpeg' => array('image/jpeg', 'image/pjpeg'),
+ 'jpg' => array('image/jpeg', 'image/pjpeg'),
+ 'js' => array('application/x-javascript'),
+ 'json' => array('application/json'),
+ 'latex' => array('application/x-latex'),
+ 'lha' => array('application/octet-stream'),
+ 'log' => array('text/plain', 'text/x-log'),
+ 'lzh' => array('application/octet-stream'),
+ 'm4a' => array('audio/mpeg'),
+ 'm4p' => array('video/mp4v-es'),
+ 'm4v' => array('video/mp4'),
+ 'man' => array('application/x-troff-man'),
+ 'mdb' => array('application/x-msaccess'),
+ 'midi' => array('audio/midi'),
+ 'mid' => array('audio/midi'),
+ 'mif' => array('application/vnd.mif'),
+ 'mka' => array('audio/x-matroska'),
+ 'mkv' => array('video/x-matroska'),
+ 'mov' => array('video/quicktime'),
+ 'movie' => array('video/x-sgi-movie'),
+ 'mp2' => array('audio/mpeg'),
+ 'mp3' => array('audio/mpeg'),
+ 'mp4' => array('application/mp4','audio/mp4','video/mp4'),
+ 'mpa' => array('video/mpeg'),
+ 'mpe' => array('video/mpeg'),
+ 'mpeg' => array('video/mpeg'),
+ 'mpg' => array('video/mpeg'),
+ 'mpg4' => array('video/mp4'),
+ 'mpga' => array('audio/mpeg'),
+ 'mpp' => array('application/vnd.ms-project'),
+ 'mpv' => array('video/x-matroska'),
+ 'mpv2' => array('video/mpeg'),
+ 'ms' => array('application/x-troff-ms'),
+ 'msg' => array('application/msoutlook','application/x-msg'),
+ 'msi' => array('application/x-msi'),
+ 'nws' => array('message/rfc822'),
+ 'oda' => array('application/oda'),
+ 'odb' => array('application/vnd.oasis.opendocument.database'),
+ 'odc' => array('application/vnd.oasis.opendocument.chart'),
+ 'odf' => array('application/vnd.oasis.opendocument.forumla'),
+ 'odg' => array('application/vnd.oasis.opendocument.graphics'),
+ 'odi' => array('application/vnd.oasis.opendocument.image'),
+ 'odm' => array('application/vnd.oasis.opendocument.text-master'),
+ 'odp' => array('application/vnd.oasis.opendocument.presentation'),
+ 'ods' => array('application/vnd.oasis.opendocument.spreadsheet'),
+ 'odt' => array('application/vnd.oasis.opendocument.text'),
+ 'oga' => array('audio/ogg'),
+ 'ogg' => array('application/ogg'),
+ 'ogv' => array('video/ogg'),
+ 'otg' => array('application/vnd.oasis.opendocument.graphics-template'),
+ 'oth' => array('application/vnd.oasis.opendocument.web'),
+ 'otp' => array('application/vnd.oasis.opendocument.presentation-template'),
+ 'ots' => array('application/vnd.oasis.opendocument.spreadsheet-template'),
+ 'ott' => array('application/vnd.oasis.opendocument.template'),
+ 'p' => array('text/x-pascal'),
+ 'pas' => array('text/x-pascal'),
+ 'patch' => array('text/x-diff'),
+ 'pbm' => array('image/x-portable-bitmap'),
+ 'pdf' => array('application/pdf', 'application/x-download'),
+ 'php' => array('application/x-httpd-php'),
+ 'php3' => array('application/x-httpd-php'),
+ 'php4' => array('application/x-httpd-php'),
+ 'php5' => array('application/x-httpd-php'),
+ 'phps' => array('application/x-httpd-php-source'),
+ 'phtml' => array('application/x-httpd-php'),
+ 'pl' => array('text/x-perl'),
+ 'pm' => array('text/x-perl'),
+ 'png' => array('image/png', 'image/x-png'),
+ 'po' => array('text/x-gettext-translation'),
+ 'pot' => array('application/vnd.ms-powerpoint'),
+ 'pps' => array('application/vnd.ms-powerpoint'),
+ 'ppt' => array('application/powerpoint'),
+ 'pptx' => array('application/vnd.openxmlformats-officedocument.presentationml.presentation'),
+ 'ps' => array('application/postscript'),
+ 'psd' => array('application/x-photoshop', 'image/x-photoshop'),
+ 'pub' => array('application/x-mspublisher'),
+ 'py' => array('text/x-python'),
+ 'qt' => array('video/quicktime'),
+ 'ra' => array('audio/x-realaudio'),
+ 'ram' => array('audio/x-realaudio', 'audio/x-pn-realaudio'),
+ 'rar' => array('application/rar'),
+ 'rgb' => array('image/x-rgb'),
+ 'rm' => array('audio/x-pn-realaudio'),
+ 'rpm' => array('audio/x-pn-realaudio-plugin', 'application/x-redhat-package-manager'),
+ 'rss' => array('application/rss+xml'),
+ 'rtf' => array('text/rtf'),
+ 'rtx' => array('text/richtext'),
+ 'rv' => array('video/vnd.rn-realvideo'),
+ 'sea' => array('application/octet-stream'),
+ 'sh' => array('text/x-sh'),
+ 'shtml' => array('text/html'),
+ 'sit' => array('application/x-stuffit'),
+ 'smi' => array('application/smil'),
+ 'smil' => array('application/smil'),
+ 'so' => array('application/octet-stream'),
+ 'src' => array('application/x-wais-source'),
+ 'svg' => array('image/svg+xml'),
+ 'swf' => array('application/x-shockwave-flash'),
+ 't' => array('application/x-troff'),
+ 'tar' => array('application/x-tar'),
+ 'tcl' => array('text/x-tcl'),
+ 'tex' => array('application/x-tex'),
+ 'text' => array('text/plain'),
+ 'texti' => array('application/x-texinfo'),
+ 'textinfo' => array('application/x-texinfo'),
+ 'tgz' => array('application/x-tar'),
+ 'tif' => array('image/tiff'),
+ 'tiff' => array('image/tiff'),
+ 'torrent' => array('application/x-bittorrent'),
+ 'tr' => array('application/x-troff'),
+ 'tsv' => array('text/tab-separated-values'),
+ 'txt' => array('text/plain'),
+ 'wav' => array('audio/x-wav'),
+ 'wax' => array('audio/x-ms-wax'),
+ 'wbxml' => array('application/wbxml'),
+ 'wm' => array('video/x-ms-wm'),
+ 'wma' => array('audio/x-ms-wma'),
+ 'wmd' => array('application/x-ms-wmd'),
+ 'wmlc' => array('application/wmlc'),
+ 'wmv' => array('video/x-ms-wmv', 'application/octet-stream'),
+ 'wmx' => array('video/x-ms-wmx'),
+ 'wmz' => array('application/x-ms-wmz'),
+ 'word' => array('application/msword', 'application/octet-stream'),
+ 'wp5' => array('application/wordperfect5.1'),
+ 'wpd' => array('application/vnd.wordperfect'),
+ 'wvx' => array('video/x-ms-wvx'),
+ 'xbm' => array('image/x-xbitmap'),
+ 'xcf' => array('image/xcf'),
+ 'xhtml' => array('application/xhtml+xml'),
+ 'xht' => array('application/xhtml+xml'),
+ 'xl' => array('application/excel', 'application/vnd.ms-excel'),
+ 'xla' => array('application/excel', 'application/vnd.ms-excel'),
+ 'xlc' => array('application/excel', 'application/vnd.ms-excel'),
+ 'xlm' => array('application/excel', 'application/vnd.ms-excel'),
+ 'xls' => array('application/excel', 'application/vnd.ms-excel'),
+ 'xlsx' => array('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'),
+ 'xlt' => array('application/excel', 'application/vnd.ms-excel'),
+ 'xml' => array('text/xml', 'application/xml'),
+ 'xof' => array('x-world/x-vrml'),
+ 'xpm' => array('image/x-xpixmap'),
+ 'xsl' => array('text/xml'),
+ 'xvid' => array('video/x-xvid'),
+ 'xwd' => array('image/x-xwindowdump'),
+ 'z' => array('application/x-compress'),
+ 'zip' => array('application/x-zip', 'application/zip', 'application/x-zip-compressed')
+);
diff --git a/includes/kohana/system/config/session.php b/includes/kohana/system/config/session.php
new file mode 100644
index 0000000..78ac9fa
--- /dev/null
+++ b/includes/kohana/system/config/session.php
@@ -0,0 +1,7 @@
+ array(
+ 'encrypted' => FALSE,
+ ),
+);
diff --git a/includes/kohana/system/config/user_agents.php b/includes/kohana/system/config/user_agents.php
new file mode 100644
index 0000000..5753c23
--- /dev/null
+++ b/includes/kohana/system/config/user_agents.php
@@ -0,0 +1,104 @@
+ array(
+ 'windows nt 6.1' => 'Windows 7',
+ 'windows nt 6.0' => 'Windows Vista',
+ 'windows nt 5.2' => 'Windows 2003',
+ 'windows nt 5.1' => 'Windows XP',
+ 'windows nt 5.0' => 'Windows 2000',
+ 'windows nt 4.0' => 'Windows NT',
+ 'winnt4.0' => 'Windows NT',
+ 'winnt 4.0' => 'Windows NT',
+ 'winnt' => 'Windows NT',
+ 'windows 98' => 'Windows 98',
+ 'win98' => 'Windows 98',
+ 'windows 95' => 'Windows 95',
+ 'win95' => 'Windows 95',
+ 'windows' => 'Unknown Windows OS',
+ 'os x' => 'Mac OS X',
+ 'intel mac' => 'Intel Mac',
+ 'ppc mac' => 'PowerPC Mac',
+ 'powerpc' => 'PowerPC',
+ 'ppc' => 'PowerPC',
+ 'cygwin' => 'Cygwin',
+ 'linux' => 'Linux',
+ 'debian' => 'Debian',
+ 'openvms' => 'OpenVMS',
+ 'sunos' => 'Sun Solaris',
+ 'amiga' => 'Amiga',
+ 'beos' => 'BeOS',
+ 'apachebench' => 'ApacheBench',
+ 'freebsd' => 'FreeBSD',
+ 'netbsd' => 'NetBSD',
+ 'bsdi' => 'BSDi',
+ 'openbsd' => 'OpenBSD',
+ 'os/2' => 'OS/2',
+ 'warp' => 'OS/2',
+ 'aix' => 'AIX',
+ 'irix' => 'Irix',
+ 'osf' => 'DEC OSF',
+ 'hp-ux' => 'HP-UX',
+ 'hurd' => 'GNU/Hurd',
+ 'unix' => 'Unknown Unix OS',
+ ),
+
+ 'browser' => array(
+ 'Opera' => 'Opera',
+ 'MSIE' => 'Internet Explorer',
+ 'Internet Explorer' => 'Internet Explorer',
+ 'Shiira' => 'Shiira',
+ 'Firefox' => 'Firefox',
+ 'Chimera' => 'Chimera',
+ 'Phoenix' => 'Phoenix',
+ 'Firebird' => 'Firebird',
+ 'Camino' => 'Camino',
+ 'Navigator' => 'Netscape',
+ 'Netscape' => 'Netscape',
+ 'OmniWeb' => 'OmniWeb',
+ 'Chrome' => 'Chrome',
+ 'Safari' => 'Safari',
+ 'CFNetwork' => 'Safari', // Core Foundation for OSX, WebKit/Safari
+ 'Konqueror' => 'Konqueror',
+ 'Epiphany' => 'Epiphany',
+ 'Galeon' => 'Galeon',
+ 'Mozilla' => 'Mozilla',
+ 'icab' => 'iCab',
+ 'lynx' => 'Lynx',
+ 'links' => 'Links',
+ 'hotjava' => 'HotJava',
+ 'amaya' => 'Amaya',
+ 'IBrowse' => 'IBrowse',
+ ),
+
+ 'mobile' => array(
+ 'mobileexplorer' => 'Mobile Explorer',
+ 'openwave' => 'Open Wave',
+ 'opera mini' => 'Opera Mini',
+ 'operamini' => 'Opera Mini',
+ 'elaine' => 'Palm',
+ 'palmsource' => 'Palm',
+ 'digital paths' => 'Palm',
+ 'avantgo' => 'Avantgo',
+ 'xiino' => 'Xiino',
+ 'palmscape' => 'Palmscape',
+ 'nokia' => 'Nokia',
+ 'ericsson' => 'Ericsson',
+ 'blackBerry' => 'BlackBerry',
+ 'motorola' => 'Motorola',
+ 'iphone' => 'iPhone',
+ 'android' => 'Android',
+ ),
+
+ 'robot' => array(
+ 'googlebot' => 'Googlebot',
+ 'msnbot' => 'MSNBot',
+ 'slurp' => 'Inktomi Slurp',
+ 'yahoo' => 'Yahoo',
+ 'askjeeves' => 'AskJeeves',
+ 'fastcrawler' => 'FastCrawler',
+ 'infoseek' => 'InfoSeek Robot 1.0',
+ 'lycos' => 'Lycos',
+ ),
+);
diff --git a/includes/kohana/system/config/userguide.php b/includes/kohana/system/config/userguide.php
new file mode 100644
index 0000000..3c49362
--- /dev/null
+++ b/includes/kohana/system/config/userguide.php
@@ -0,0 +1,23 @@
+ array(
+
+ // This should be the path to this modules userguide pages, without the 'guide/'. Ex: '/guide/modulename/' would be 'modulename'
+ 'kohana' => array(
+
+ // Whether this modules userguide pages should be shown
+ 'enabled' => TRUE,
+
+ // The name that should show up on the userguide index page
+ 'name' => 'Kohana',
+
+ // A short description of this module, shown on the index page
+ 'description' => 'Documentation for Kohana core/system.',
+
+ // Copyright message, shown in the footer for this module
+ 'copyright' => '© 2008–2010 Kohana Team',
+ )
+ )
+);
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/autoloading.md b/includes/kohana/system/guide/kohana/autoloading.md
new file mode 100644
index 0000000..def15bc
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/autoloading.md
@@ -0,0 +1,69 @@
+# Loading Classes
+
+Kohana takes advantage of PHP [autoloading](http://php.net/manual/language.oop5.autoload.php). This removes the need to call [include](http://php.net/include) or [require](http://php.net/require) before using a class. When you use a class Kohana will find and include the class file for you. For instance, when you want to use the [Cookie::set] method, you simply call:
+
+ Cookie::set('mycookie', 'any string value');
+
+Or to load an [Encrypt] instance, just call [Encrypt::instance]:
+
+ $encrypt = Encrypt::instance();
+
+Classes are loaded via the [Kohana::auto_load] method, which makes a simple conversion from class name to file name:
+
+1. Classes are placed in the `classes/` directory of the [filesystem](files)
+2. Any underscore characters in the class name are converted to slashes
+2. The filename is lowercase
+
+When calling a class that has not been loaded (eg: `Session_Cookie`), Kohana will search the filesystem using [Kohana::find_file] for a file named `classes/session/cookie.php`.
+
+If your classes do not follow this convention, they cannot be autoloaded by Kohana. You will have to manually included your files, or add your own [autoload function.](http://us3.php.net/manual/en/function.spl-autoload-register.php)
+
+## Custom Autoloaders
+
+Kohana's default autoloader is enabled in `application/bootstrap.php` using [spl_autoload_register](http://php.net/spl_autoload_register):
+
+ spl_autoload_register(array('Kohana', 'auto_load'));
+
+This allows [Kohana::auto_load] to attempt to find and include any class that does not yet exist when the class is first used.
+
+### Example: Zend
+
+You can easily gain access to other libraries if they include an autoloader. For example, here is how to enable Zend's autoloader so you can use Zend libraries in your Kohana application.
+
+#### Download and install the Zend Framework files
+
+- [Download the latest Zend Framework files](http://framework.zend.com/download/latest).
+- Create a `vendor` directory at `application/vendor`. This keeps third party software separate from your application classes.
+- Move the decompressed Zend folder containing Zend Framework to `application/vendor/Zend`.
+
+
+#### Include Zend's Autoloader in your bootstrap
+
+Somewhere in `application/bootstrap.php`, copy the following code:
+
+ /**
+ * Enable Zend Framework autoloading
+ */
+ if ($path = Kohana::find_file('vendor', 'Zend/Loader'))
+ {
+ ini_set('include_path',
+ ini_get('include_path').PATH_SEPARATOR.dirname(dirname($path)));
+
+ require_once 'Zend/Loader/Autoloader.php';
+ Zend_Loader_Autoloader::getInstance();
+ }
+
+#### Usage example
+
+You can now autoload any Zend Framework classes from inside your Kohana application.
+
+ if ($validate($_POST))
+ {
+ $mailer = new Zend_Mail;
+
+ $mailer->setBodyHtml($view)
+ ->setFrom(Kohana::config('site')->email_from)
+ ->addTo($email)
+ ->setSubject($message)
+ ->send();
+ }
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/bootstrap.md b/includes/kohana/system/guide/kohana/bootstrap.md
new file mode 100644
index 0000000..6ff9588
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/bootstrap.md
@@ -0,0 +1,164 @@
+# Bootstrap
+
+The bootstrap is located at `application/bootstrap.php`. It is responsible for setting up the Kohana environment and executing the main response. It is included by `index.php` (see [Request flow](flow))
+
+[!!] The bootstrap is responsible for the flow of your application. In previous versions of Kohana the bootstrap was in `system` and was somewhat of an unseen, uneditible force. In Kohana 3 the bootstrap takes on a much more integral and versatile role. Do not be afraid to edit and change your bootstrap however you see fit.
+
+## Environment setup
+
+First the bootstrap sets the timezone and the locale, and adds Kohana's autoloader so the [cascading filesystem](files) works. You could add any other settings that all your application needed here.
+
+~~~
+// Sample excerpt from bootstrap.php with comments trimmed down
+
+// Set the default time zone.
+date_default_timezone_set('America/Chicago');
+
+// Set the default locale.
+setlocale(LC_ALL, 'en_US.utf-8');
+
+// Enable the Kohana auto-loader.
+spl_autoload_register(array('Kohana', 'auto_load'));
+
+// Enable the Kohana auto-loader for unserialization.
+ini_set('unserialize_callback_func', 'spl_autoload_call');
+~~~
+
+## Initilization and Configuration
+
+Kohana is then initialized by calling [Kohana::init], and the log and [config](files/config) reader/writers are enabled.
+
+~~~
+// Sample excerpt from bootstrap.php with comments trimmed down
+
+Kohana::init(array('
+ base_url' => '/kohana/',
+ index_file => false,
+));
+
+// Attach the file writer to logging. Multiple writers are supported.
+Kohana::$log->attach(new Kohana_Log_File(APPPATH.'logs'));
+
+// Attach a file reader to config. Multiple readers are supported.
+Kohana::$config->attach(new Kohana_Config_File);
+~~~
+
+You can add conditional statements to make the bootstrap have different values based on certain settings. For example, detect whether we are live by checking `$_SERVER['HTTP_HOST']` and set caching, profiling, etc. accordingly. This is just an example, there are many different ways to accomplish the same thing.
+
+~~~
+// Excerpt from http://github.com/isaiahdw/kohanaphp.com/blob/f2afe8e28b/application/bootstrap.php
+... [trimmed]
+
+/**
+ * Set the environment status by the domain.
+ */
+if (strpos($_SERVER['HTTP_HOST'], 'kohanaphp.com') !== FALSE)
+{
+ // We are live!
+ Kohana::$environment = Kohana::PRODUCTION;
+
+ // Turn off notices and strict errors
+ error_reporting(E_ALL ^ E_NOTICE ^ E_STRICT);
+}
+
+/**
+ * Initialize Kohana, setting the default options.
+ ... [trimmed]
+ */
+Kohana::init(array(
+ 'base_url' => Kohana::$environment === Kohana::PRODUCTION ? '/' : '/kohanaphp.com/',
+ 'caching' => Kohana::$environment === Kohana::PRODUCTION,
+ 'profile' => Kohana::$environment !== Kohana::PRODUCTION,
+ 'index_file' => FALSE,
+));
+
+... [trimmed]
+
+try
+{
+ $request = Request::instance()->execute();
+}
+catch (Exception $e)
+{
+ // If we are in development and the error wasn't a 404, show the stack trace.
+ if ( Kohana::$environment == "development" AND $e->getCode() != 404 )
+ {
+ throw $e;
+ }
+...[trimmed]
+~~~
+
+[!!] Note: The default bootstrap will set `Kohana::$environment = $_ENV['KOHANA_ENV']` if set. Docs on how to supply this variable are available in your web server's documentation (e.g. [Apache](http://httpd.apache.org/docs/1.3/mod/mod_env.html#setenv), [Lighttpd](http://redmine.lighttpd.net/wiki/1/Docs:ModSetEnv#Options)). This is considered better practice than many alternative methods to set `Kohana::$enviroment`, as you can change the setting per server, without having to rely on config options or hostnames.
+
+## Modules
+
+**Read the [Modules](modules) page for a more detailed description.**
+
+[Modules](modules) are then loaded using [Kohana::modules()]. Including modules is optional.
+
+Each key in the array should be the name of the module, and the value is the path to the module, either relative or absolute.
+~~~
+// Example excerpt from bootstrap.php
+
+Kohana::modules(array(
+ 'database' => MODPATH.'database',
+ 'orm' => MODPATH.'orm',
+ 'userguide' => MODPATH.'userguide',
+));
+~~~
+
+## Routes
+
+**Read the [Routing](routing) page for a more detailed description and more examples.**
+
+[Routes](routing) are then defined via [Route::set()].
+
+~~~
+// The default route that comes with Kohana 3
+Route::set('default', '((/(/)))')
+ ->defaults(array(
+ 'controller' => 'welcome',
+ 'action' => 'index',
+ ));
+~~~
+
+## Execution
+
+Once our environment is initialized and routes defined, it's time to execute our application. This area of the bootstrap is very flexible. Do not be afraid to change this around to whatever suits your needs.
+
+### Basic Example
+The most simple way to do this, and what comes default with Kohana 3 is simply:
+~~~
+// Execute the main request
+echo Request::instance()
+ ->execute()
+ ->send_headers()
+ ->response;
+~~~
+
+### Catching Exceptions
+
+**See [Error Handling](errors) for a more detailed description and more examples.**
+
+The previous example provides no error catching, which means if an error occurs a stack trace would be shown which could show sensitive info, as well as be unfriendly for the user. One way to solve this is to add a `try catch` block. If we get an exception, we will show the view located at `views/errors/404.php`. **Note: Because we catch the exception, Kohana will not log the error! It is your responsibility to log the error.**
+
+~~~
+try
+{
+ // Execute the main request
+ $request = Request::instance()->execute();
+}
+catch (Exception $e)
+{
+ // Be sure to log the error
+ Kohana::$log->add(Kohana::ERROR, Kohana::exception_text($e));
+
+ // If there was an error, send a 404 response and display an error
+ $request->status = 404;
+ $request->response = View::factory('errors/404');
+}
+
+// Send the headers and echo the response
+$request->send_headers();
+echo $request->response;
+~~~
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/controllers.md b/includes/kohana/system/guide/kohana/controllers.md
new file mode 100644
index 0000000..d95d98a
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/controllers.md
@@ -0,0 +1 @@
+This will discuss controller basics, like before() and after(), private function, and about extending controllers like the Controller_Template, or using a parent::before() for authentication.
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/conventions.md b/includes/kohana/system/guide/kohana/conventions.md
new file mode 100644
index 0000000..e047746
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/conventions.md
@@ -0,0 +1,306 @@
+# Conventions and Coding Style
+
+It is encouraged that you follow Kohana's [coding style](http://dev.kohanaframework.org/wiki/kohana2/CodingStyle). This makes code more readable and allows for easier code sharing and contributing.
+
+## Class Names and File Location
+
+Class names in Kohana follow a strict convention to facilitate [autoloading](autoloading). Class names should have uppercase first letters with underscores to separate words. Underscores are significant as they directly reflect the file location in the filesystem.
+
+The following conventions apply:
+
+1. CamelCased class names should not be used, except when it is undesirable to create a new directory level.
+2. All class file names and directory names are lowercase.
+3. All classes should be in the `classes` directory. This may be at any level in the [cascading filesystem](files).
+
+[!!] Unlike Kohana v2.x, there is no separation between "controllers", "models", "libraries" and "helpers". All classes are placed in the "classes/" directory, regardless if they are static "helpers" or object "libraries". You can use whatever kind of class design you want: static, singleton, adapter, etc.
+
+### Examples {#class-name-examples}
+
+Remember that in a class, an underscore means a new directory. Consider the following examples:
+
+Class Name | File Path
+----------------------|-------------------------------
+Controller_Template | classes/controller/template.php
+Model_User | classes/model/user.php
+Database | classes/database.php
+Database_Query | classes/database/query.php
+Form | classes/form.php
+
+## Coding Standards
+
+In order to produce highly consistent source code, we ask that everyone follow the coding standards as closely as possible.
+
+### Brackets
+Please use [BSD/Allman Style](http://en.wikipedia.org/wiki/Indent_style#BSD.2FAllman_style) bracketing. Brackets are always on their own line. The exception to this rule is the opening bracket for a class, which can be on the same line.
+
+ if ($foo == 'bar')
+ {
+ $baz->bar();
+ }
+ else
+ {
+ $baz->default();
+ }
+
+ // The opening bracket for a class can be on the same line
+ Class Foobar {
+
+
+### Naming Conventions
+
+Kohana uses under_score naming, not camelCase naming.
+
+#### Classes
+
+ // Controller class, uses Controller_ prefix
+ class Controller_Apple extends Controller {
+
+ // Model class, uses Model_ prefix
+ class Model_Cheese extends Model {
+
+ // Regular class
+ class Peanut {
+
+When creating an instance of a class, don't use parentheses if you're not passing something on to the constructor:
+
+ // Correct:
+ $db = new Database;
+
+ // Incorrect:
+ $db = new Database();
+
+#### Functions and Methods
+
+Functions should be all lowercase, and use under_scores to separate words:
+
+ function drink_beverage($beverage)
+ {
+
+#### Variables
+
+All variables should be lowercase and use under_score, not camelCase:
+
+ // Correct:
+ $foo = 'bar';
+ $long_example = 'uses underscores';
+
+ // Incorrect:
+ $weDontWantThis = 'understood?';
+
+### Indentation
+
+You must use tabs to indent your code. Using spaces for tabbing is strictly forbidden.
+
+Vertical spacing (for multi-line) is done with spaces. Tabs are not good for vertical alignment because different people have different tab widths.
+
+ $text = 'this is a long text block that is wrapped. Normally, we aim for '
+ .'wrapping at 80 chars. Vertical alignment is very important for '
+ .'code readability. Remember that all indentation is done with tabs,'
+ .'but vertical alignment should be completed with spaces, after '
+ .'indenting with tabs.';
+
+### String concatenation
+
+Do not put spaces around the concatenation operator:
+
+ // Correct:
+ $str = 'one'.$var.'two';
+
+ // Incorrect:
+ $str = 'one'. $var .'two';
+ $str = 'one' . $var . 'two';
+
+### Single Line Statements
+
+Single-line IF statements should only be used when breaking normal execution (e.g. return or continue):
+
+ // Acceptable:
+ if ($foo == $bar)
+ return $foo;
+
+ if ($foo == $bar)
+ continue;
+
+ if ($foo == $bar)
+ break;
+
+ if ($foo == $bar)
+ throw new Exception('You screwed up!');
+
+ // Not acceptable:
+ if ($baz == $bun)
+ $baz = $bar + 2;
+
+### Comparison Operations
+
+Please use OR and AND for comparison:
+
+ // Correct:
+ if (($foo AND $bar) OR ($b AND $c))
+
+ // Incorrect:
+ if (($foo && $bar) || ($b && $c))
+
+Please use elseif, not else if:
+
+ // Correct:
+ elseif ($bar)
+
+ // Incorrect:
+ else if($bar)
+
+### Switch structures
+
+Each case, break and default should be on a separate line. The block inside a case or default must be indented by 1 tab.
+
+ switch ($var)
+ {
+ case 'bar':
+ case 'foo':
+ echo 'hello';
+ break;
+ case 1:
+ echo 'one';
+ break;
+ default:
+ echo 'bye';
+ break;
+ }
+
+### Parentheses
+
+There should be one space after statement name, followed by a parenthesis. The ! (bang) character must have a space on either side to ensure maximum readability. Except in the case of a bang or type casting, there should be no whitespace after an opening parenthesis or before a closing parenthesis.
+
+ // Correct:
+ if ($foo == $bar)
+ if ( ! $foo)
+
+ // Incorrect:
+ if($foo == $bar)
+ if(!$foo)
+ if ((int) $foo)
+ if ( $foo == $bar )
+ if (! $foo)
+
+### Ternaries
+
+All ternary operations should follow a standard format. Use parentheses around expressions only, not around just variables.
+
+ $foo = ($bar == $foo) ? $foo : $bar;
+ $foo = $bar ? $foo : $bar;
+
+All comparisons and operations must be done inside of a parentheses group:
+
+ $foo = ($bar > 5) ? ($bar + $foo) : strlen($bar);
+
+When separating complex ternaries (ternaries where the first part goes beyond ~80 chars) into multiple lines, spaces should be used to line up operators, which should be at the front of the successive lines:
+
+ $foo = ($bar == $foo)
+ ? $foo
+ : $bar;
+
+### Type Casting
+
+Type casting should be done with spaces on each side of the cast:
+
+ // Correct:
+ $foo = (string) $bar;
+ if ( (string) $bar)
+
+ // Incorrect:
+ $foo = (string)$bar;
+
+When possible, please use type casting instead of ternary operations:
+
+ // Correct:
+ $foo = (bool) $bar;
+
+ // Incorrect:
+ $foo = ($bar == TRUE) ? TRUE : FALSE;
+
+When casting type to integer or boolean, use the short format:
+
+ // Correct:
+ $foo = (int) $bar;
+ $foo = (bool) $bar;
+
+ // Incorrect:
+ $foo = (integer) $bar;
+ $foo = (boolean) $bar;
+
+### Constants
+
+Always use uppercase for constants:
+
+ // Correct:
+ define('MY_CONSTANT', 'my_value');
+ $a = TRUE;
+ $b = NULL;
+
+ // Incorrect:
+ define('MyConstant', 'my_value');
+ $a = True;
+ $b = null;
+
+Place constant comparisons at the end of tests:
+
+ // Correct:
+ if ($foo !== FALSE)
+
+ // Incorrect:
+ if (FALSE !== $foo)
+
+This is a slightly controversial choice, so I will explain the reasoning. If we were to write the previous example in plain English, the correct example would read:
+
+ if variable $foo is not exactly FALSE
+
+And the incorrect example would read:
+
+ if FALSE is not exactly variable $foo
+
+Since we are reading left to right, it simply doesn't make sense to put the constant first.
+
+### Comments
+
+#### One-line comments
+
+Use //, preferably above the line of code you're commenting on. Leave a space after it and start with a capital. Never use #.
+
+ // Correct
+
+ //Incorrect
+ // incorrect
+ # Incorrect
+
+### Regular expressions
+
+When coding regular expressions please use PCRE rather than the POSIX flavor. PCRE is considered more powerful and faster.
+
+ // Correct:
+ if (preg_match('/abc/i'), $str)
+
+ // Incorrect:
+ if (eregi('abc', $str))
+
+Use single quotes around your regular expressions rather than double quotes. Single-quoted strings are more convenient because of their simplicity. Unlike double-quoted strings they don't support variable interpolation nor integrated backslash sequences like \n or \t, etc.
+
+ // Correct:
+ preg_match('/abc/', $str);
+
+ // Incorrect:
+ preg_match("/abc/", $str);
+
+When performing a regular expression search and replace, please use the $n notation for backreferences. This is preferred over \\n.
+
+ // Correct:
+ preg_replace('/(\d+) dollar/', '$1 euro', $str);
+
+ // Incorrect:
+ preg_replace('/(\d+) dollar/', '\\1 euro', $str);
+
+Finally, please note that the $ character for matching the position at the end of the line allows for a following newline character. Use the D modifier to fix this if needed. [More info](http://blog.php-security.org/archives/76-Holes-in-most-preg_match-filters.html).
+
+ $str = "email@example.com\n";
+
+ preg_match('/^.+@.+$/', $str); // TRUE
+ preg_match('/^.+@.+$/D', $str); // FALSE
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/cookies.md b/includes/kohana/system/guide/kohana/cookies.md
new file mode 100644
index 0000000..5b5a310
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/cookies.md
@@ -0,0 +1,89 @@
+# Cookies
+
+Kohana provides classes that make it easy to work with both cookies and sessions. At a high level both sessions and cookies provide the same functionality. They allow the developer to store temporary or persistent information about a specific client for later retrieval, usually to make something persistent between requests.
+
+[Cookies](http://en.wikipedia.org/wiki/HTTP_cookie) should be used for storing non-private data that is persistent for a long period of time. For example storing a user preference or a language setting. Use the [Cookie] class for getting and setting cookies.
+
+[!!] Kohana uses "signed" cookies. Every cookie that is stored is combined with a secure hash to prevent modification of the cookie. If a cookie is modified outside of Kohana the hash will be incorrect and the cookie will be deleted. This hash is generated using [Cookie::salt()], which uses the [Cookie::$salt] property. You should change this setting when your application is live.
+
+Nothing stops you from using `$_COOKIE` like normal, but you can not mix using the Cookie class and the regular `$_COOKIE` global, because the hash that Kohana uses to sign cookies will not be present, and Kohana will delete the cookie.
+
+## Storing, Retrieving, and Deleting Data
+
+[Cookie] and [Session] provide a very similar API for storing data. The main difference between them is that sessions are accessed using an object, and cookies are accessed using a static class.
+
+### Storing Data
+
+Storing session or cookie data is done using the [Cookie::set] method:
+
+ // Set cookie data
+ Cookie::set($key, $value);
+
+ // Store a user id
+ Cookie::set('user_id', 10);
+
+### Retrieving Data
+
+Getting session or cookie data is done using the [Cookie::get] method:
+
+ // Get cookie data
+ $data = Cookie::get($key, $default_value);
+
+ // Get the user id
+ $user = Cookie::get('user_id');
+
+### Deleting Data
+
+Deleting session or cookie data is done using the [Cookie::delete] method:
+
+ // Delete cookie data
+ Cookie::delete($key);
+
+ // Delete the user id
+ Cookie::delete('user_id');
+
+## Cookie Settings
+
+All of the cookie settings are changed using static properties. You can either change these settings in `bootstrap.php` or by using [transparent extension](extension). Always check these settings before making your application live, as many of them will have a direct affect on the security of your application.
+
+The most important setting is [Cookie::$salt], which is used for secure signing. This value should be changed and kept secret:
+
+ Cookie::$salt = 'your secret is safe with me';
+
+[!!] Changing this value will render all cookies that have been set before invalid.
+
+By default, cookies are stored until the browser is closed. To use a specific lifetime, change the [Cookie::$expiration] setting:
+
+ // Set cookies to expire after 1 week
+ Cookie::$expiration = 604800;
+
+ // Alternative to using raw integers, for better clarity
+ Cookie::$expiration = Date::WEEK;
+
+The path that the cookie can be accessed from can be restricted using the [Cookie::$path] setting.
+
+ // Allow cookies only when going to /public/*
+ Cookie::$path = '/public/';
+
+The domain that the cookie can be accessed from can also be restricted, using the [Cookie::$domain] setting.
+
+ // Allow cookies only on the domain www.example.com
+ Cookie::$domain = 'www.example.com';
+
+If you want to make the cookie accessible on all subdomains, use a dot at the beginning of the domain.
+
+ // Allow cookies to be accessed on example.com and *.example.com
+ Cookie::$domain = '.example.com';
+
+To only allow the cookie to be accessed over a secure (HTTPS) connection, use the [Cookie::$secure] setting.
+
+ // Allow cookies to be accessed only on a secure connection
+ Cookie::$secure = TRUE;
+
+ // Allow cookies to be accessed on any connection
+ Cookie::$secure = FALSE;
+
+To prevent cookies from being accessed using Javascript, you can change the [Cookie::$httponly] setting.
+
+ // Make cookies inaccessible to Javascript
+ Cookie::$httponly = TRUE;
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/debugging.md b/includes/kohana/system/guide/kohana/debugging.md
new file mode 100644
index 0000000..1bb7174
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/debugging.md
@@ -0,0 +1,20 @@
+# Debugging
+
+Kohana includes several tools to help you debug your application.
+
+The most basic of these is [Kohana::debug]. This simple method will display any number of variables, similar to [var_export](http://php.net/var_export) or [print_r](http://php.net/print_r), but using HTML for extra formatting.
+
+ // Display a dump of the $foo and $bar variables
+ echo Kohana::debug($foo, $bar);
+
+Kohana also provides a method to show the source code of a particular file using [Kohana::debug_source].
+
+ // Display this line of source code
+ echo Kohana::debug_source(__FILE__, __LINE__);
+
+If you want to display information about your application files without exposing the installation directory, you can use [Kohana::debug_path]:
+
+ // Displays "APPPATH/cache" rather than the real path
+ echo Kohana::debug_path(APPPATH.'cache');
+
+If you are having trouble getting something to work correctly, you could check your Kohana logs and your webserver logs, as well as using a debugging tool like [Xdebug](http://www.xdebug.org/).
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/errors.md b/includes/kohana/system/guide/kohana/errors.md
new file mode 100644
index 0000000..fb63648
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/errors.md
@@ -0,0 +1,85 @@
+# Error/Exception Handling
+
+Kohana provides both an exception handler and an error handler that transforms errors into exceptions using PHP's [ErrorException](http://php.net/errorexception) class. Many details of the error and the internal state of the application is displayed by the handler:
+
+1. Exception class
+2. Error level
+3. Error message
+4. Source of the error, with the error line highlighted
+5. A [debug backtrace](http://php.net/debug_backtrace) of the execution flow
+6. Included files, loaded extensions, and global variables
+
+## Example
+
+Click any of the links to toggle the display of additional information:
+
+{{userguide/examples/error}}
+
+## Disabling Error/Exception Handling
+
+If you do not want to use the internal error handling, you can disable it when calling [Kohana::init]:
+
+ Kohana::init(array('errors' => FALSE));
+
+## Error Reporting
+
+By default, Kohana displays all errors, including strict mode warnings. This is set using [error_reporting](http://php.net/error_reporting):
+
+ error_reporting(E_ALL | E_STRICT);
+
+When you application is live and in production, a more conservative setting is recommended, such as ignoring notices:
+
+ error_reporting(E_ALL & ~E_NOTICE);
+
+If you get a white screen when an error is triggered, your host probably has disabled displaying errors. You can turn it on again by adding this line just after your `error_reporting` call:
+
+ ini_set('display_errors', TRUE);
+
+Errors should **always** be displayed, even in production, because it allows you to use [exception and error handling](debugging.errors) to serve a nice error page rather than a blank white screen when an error happens.
+
+
+## Last thoughts
+
+In production, **your application should never have any uncaught exceptions**, as this can expose sensitive information (via the stack trace). In the previous example we make the assumption that there is actually a view called 'views/errors/404', which is fairly safe to assume. One solution is to turn 'errors' off in Kohana::init for your production machine, so it displays the normal php errors rather than a stack trace.
+
+~~~
+// snippet from bootstrap.php
+Kohana::init(array('
+ ...
+ 'errors' => false,
+));
+~~~
+
+So rather than displaying the Kohana error page with the stack trace, it will display the default php error. Something like:
+
+**Fatal error: Uncaught Kohana_View_Exception [ 0 ]: The requested view errors/404 could not be found ~ SYSPATH/classes/kohana/view.php [ 215 ] thrown in /var/www/kohanut/docs.kohanaphp.com/3.0/system/classes/kohana/view.php on line 215**
+
+Keep in mind what I said earlier though: **your application should never have any uncaught exceptions**, so this should not be necesarry, though it is a good idea, simply because stack traces on a production environment are a *very* bad idea.
+
+Another solution is to always have a `catch` statement that can't fail, something like an `echo` and an `exit` or a `die()`. This should almost never be necesarry, but it makes some people feel better at night. You can either wrap your entire bootstrap in a try catch, or simply wrap the contents of the catch in another try catch. For example:
+
+~~~
+try
+{
+ // Execute the main request
+ $request->execute();
+}
+catch (Exception $e)
+{
+ try
+ {
+ // Be sure to log the error
+ Kohana::$log->add(Kohana::ERROR, Kohana::exception_text($e));
+
+ // If there was an error, send a 404 response and display an error
+ $request->status = 404;
+ $request->response = View::factory('errors/404');
+ }
+ catch
+ {
+ // This is completely overkill, but helps some people sleep at night
+ echo "Something went terribly wrong. Try again in a few minutes.";
+ exit;
+ }
+}
+~~~
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/extension.md b/includes/kohana/system/guide/kohana/extension.md
new file mode 100644
index 0000000..7a00a64
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/extension.md
@@ -0,0 +1,101 @@
+# Transparent Class Extension
+
+The [cascading filesystem](files) allows transparent class extension. For instance, the class [Cookie] is defined in `SYSPATH/classes/cookie.php` as:
+
+ class Cookie extends Kohana_Cookie {}
+
+The default Kohana classes, and many extensions, use this definition so that almost all classes can be extended. You extend any class transparently, by defining your own class in `APPPATH/classes/cookie.php` to add your own methods.
+
+[!!] You should **never** modify any of the files that are distributed with Kohana. Always make modifications to classes using transparent extension to prevent upgrade issues.
+
+For instance, if you wanted to create method that sets encrypted cookies using the [Encrypt] class, you would creat a file at `application/classes/cookie.php` that extends Kohana_Cookie, and adds your functions:
+
+ encode((string) $value);
+
+ parent::set($name, $value, $expiration);
+ }
+
+ /**
+ * Gets an encrypted cookie.
+ *
+ * @uses Cookie::get
+ * @uses Encrypt::decode
+ */
+ public static function decrypt($name, $default = NULL)
+ {
+ if ($value = parent::get($name, NULL))
+ {
+ $value = Encrypt::instance(Cookie::$encryption)->decode($value);
+ }
+
+ return isset($value) ? $value : $default;
+ }
+
+ } // End Cookie
+
+Now calling `Cookie::encrypt('secret', $data)` will create an encrypted cookie which we can decrypt with `$data = Cookie::decrypt('secret')`.
+
+## How it works
+
+To understand how this works, let's look at what happens normally. When you use the Cookie class, [Kohana::autoload] looks for `classes/cookie.php` in the [cascading filesystem](files). It looks in `application`, then each module, then `system`. The file is found in `system` and is included. Of coures, `system/classes/cookie.php` is just an empty class which extends `Kohana_Cookie`. Again, [Kohana::autoload] is called this time looking for `classes/kohana/cookie.php` which it finds in `system`.
+
+When you add your transparently extended cookie class at `application/classes/cookie.php` this file essentially "replaces" the file at `system/classes/cookie.php` without actually touching it. This happens because this time when we use the Cookie class [Kohana::autoload] looks for `classes/cookie.php` and finds the file in `application` and includes that one, instead of the one in system.
+
+## Example: changing [Cookie] settings
+
+If you are using the [Cookie](cookies) class, and want to change a setting, you should do so using transparent extension, rather than editing the file in the system folder. If you edit it directly, and in the future you upgrade your Kohana version by replacing the system folder, your changes will be reverted and your cookies will probably be invalid. Instead, create a cookie.php file either in `application/classes/cookie.php` or a module (`MODPATH//classes/cookie.php`).
+
+ class Cookie extends Kohana_Cookie {
+
+ // Set a new salt
+ public $salt = "some new better random salt phrase";
+
+ // Don't allow javascript access to cookies
+ public $httponly = TRUE;
+
+ }
+
+## Example: TODO: an example
+
+Just post the code and breif descript of what function it adds, you don't have to do the "How it works" like above.
+
+## Example: TODO: something else
+
+Just post the code and breif descript of what function it adds, you don't have to do the "How it works" like above.
+
+## More examples
+
+TODO: Provide some links to modules on github, etc that have examples of transparent extension in use.
+
+## Multiple Levels of Extension
+
+If you are extending a Kohana class in a module, you should maintain transparent extensions. In other words, do not include any variables or function in the "base" class (eg. Cookie). Instead make your own namespaced class, and have the "base" class extend that one. With our Encrypted cookie example we can create `MODPATH/mymod/encrypted/cookie.php`:
+
+ class Encrypted_Cookie extends Kohana_Cookie {
+
+ // Use the same encrypt() and decrypt() methods as above
+
+ }
+
+And create `MODPATH/mymod/cookie.php`:
+
+ class Cookie extends Encrypted_Cookie {}
+
+This will still allow users to add their own extension to [Cookie] while leaving your extensions intact. To do that they would make a cookie class that extends `Encrypted_Cookie` (rather than `Kohana_Cookie`) in their application folder.
diff --git a/includes/kohana/system/guide/kohana/files.md b/includes/kohana/system/guide/kohana/files.md
new file mode 100644
index 0000000..8725339
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/files.md
@@ -0,0 +1,83 @@
+# Cascading Filesystem
+
+The Kohana filesystem is a heirarchy of similar directory structures that cascade. The heirarchy in Kohana (used when a file is loaded by [Kohana::find_file]) is in the following order:
+
+1. **Application Path**
+ Defined as `APPPATH` in `index.php`. The default value is `application`.
+
+2. **Module Paths**
+ This is set as an associative array using [Kohana::modules] in `APPPATH/bootstrap.php`. Each of the values of the array will be searched **in the order that the modules are added**.
+
+3. **System Path**
+ Defined as `SYSPATH` in `index.php`. The default value is `system`. All of the main or "core" files and classes are defined here.
+
+Files that are in directories higher up the include path order take precedence over files of the same name lower down the order, which makes it is possible to overload any file by placing a file with the same name in a "higher" directory:
+
+![Cascading Filesystem Infographic](cascading_filesystem.png)
+
+This image is only shows certain files, but we can use it to illustrate some examples of the cascading filesystem:
+
+* If Kohana catches an error, it would display the `kohana/error.php` view, So it would call `Kohana::find_file('views', 'kohana/error')`. This would return `application/views/kohana/error.php` because it takes precidence over `system/views/kohana/error.php`. By doing this we can change the error view without editing the system folder.
+
+* If we used `View::factory('welcome')` it would call `Kohana::find_file('views','welcome')` which would return `application/views/welcome.php` because it takes precidence over `modules/common/views/welcome.php`. By doing this, you can overwrite things in a module without editing the modules files.
+
+* If use the Cookie class, [Kohana::auto_load] will call `Kohana::find_file('classes', 'cookie')` which will return `application/classes/cookie.php`. Assuming Cookie extends Kohana_Cookie, the autoloader would then call `Kohana::find_file('classes','kohana/cookie')` which will return `system/classes/kohana/cookie.php` because that file does not exist anywhere higher in the cascade. This is an example of [transparent extension](extension).
+
+* If you used `View::factory('user')` it would call `Kohana::find_file('views','user')` which would return `modules/common/views/user.php`.
+
+* If we wanted to change something in `config/database.php` we could copy the file to `application/config/database.php` and make the changes there. Keep in mind that [config files are merged](files/config#merge) rather than overwritten by the cascade.
+
+## Types of Files
+
+The top level directories of the application, module, and system paths have the following default directories:
+
+classes/
+: All classes that you want to [autoload](autoloading) should be stored here. This includes [controllers](mvc/controllers), [models](mvc/models), and all other classes. All classes must follow the [class naming conventions](conventions#class-names-and-file-location).
+
+config/
+: Configuration files return an associative array of options that can be loaded using [Kohana::config]. Config files are merged rather than overwritten by the cascade. See [config files](files/config) for more information.
+
+i18n/
+: Translation files return an associative array of strings. Translation is done using the `__()` method. To translate "Hello, world!" into Spanish, you would call `__('Hello, world!')` with [I18n::$lang] set to "es-es". I18n files are merged rather than overwritten by the cascade. See [I18n files](files/i18n) for more information.
+
+messages/
+: Message files return an associative array of strings that can be loaded using [Kohana::message]. Messages and i18n files differ in that messages are not translated, but always written in the default language and referred to by a single key. Message files are merged rather than overwritten by the cascade. See [message files](files/messages) for more information.
+
+views/
+: Views are plain PHP files which are used to generate HTML or other output. The view file is loaded into a [View] object and assigned variables, which it then converts into an HTML fragment. Multiple views can be used within each other. See [views](mvc/views) for more information.
+
+*other*
+: You can include any other folders in your cascading filesystem. Examples include, but are not limited to, `guide`, `vendor`, `media`, whatever you want. For example, to find `media/logo.png` in the cascading filesystem you would call `Kohana::find_file('media','logo','png')`.
+
+## Finding Files
+
+The path to any file within the filesystem can be found by calling [Kohana::find_file]:
+
+ // Find the full path to "classes/cookie.php"
+ $path = Kohana::find_file('classes', 'cookie');
+
+ // Find the full path to "views/user/login.php"
+ $path = Kohana::find_file('views', 'user/login');
+
+If the file doesn't have a `.php` extension, pass the extension as the third param.
+
+ // Find the full path to "guide/menu.md"
+ $path = Kohana::find_file('guide', 'menu', 'md');
+
+ // If $name is "2000-01-01-first-post" this would look for "posts/2000-01-01-first-post.textile"
+ $path = Kohana::find_file('posts', $name, '.textile');
+
+
+## Vendor Extensions
+
+We call extensions or external libraries that are not specific to Kohana "vendor" extensions, and they go in the vendor folder, either in application or in a module. Because these libraries do not follow Kohana's file naming conventions, they cannot be autoloaded by Kohana, so you will have to manually included them. Some examples of vendor libraries are [Markdown](http://daringfireball.net/projects/markdown/), [DOMPDF](http://code.google.com/p/dompdf), [Mustache](http://github.com/bobthecow/mustache.php) and [Swiftmailer](http://swiftmailer.org/).
+
+For example, if you wanted to use [DOMPDF](http://code.google.com/p/dompdf), you would copy it to `application/vendor/dompdf` and include the DOMPDF autoloading class. It can be useful to do this in a controller's before method, as part of a module's init.php, or the contstructor of a singleton class.
+
+ require Kohana::find_file('vendor', 'dompdf/dompdf/dompdf_config','inc');
+
+Now you can use DOMPDF without loading any more files:
+
+ $pdf = new DOMPDF;
+
+[!!] If you want to convert views into PDFs using DOMPDF, try the [PDFView](http://github.com/shadowhand/pdfview) module.
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/files/classes.md b/includes/kohana/system/guide/kohana/files/classes.md
new file mode 100644
index 0000000..cdffc60
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/files/classes.md
@@ -0,0 +1,41 @@
+# Classes
+
+TODO: Brief intro to classes.
+
+[Models](mvc/models) and [Controllers](mvc/controllers) are classes as well, but are treated slightly differently by Kohana. Read their respective pages to learn more.
+
+## Helper or Library?
+
+Kohana 3 does not differentiate between "helper" classes and "library" classes like in previous versions. They are all placed in the `classes/` folder and follow the same conventions. The distinction is that in general, a "helper" class is used statically, (for examples see the [helpers included in Kohana](helpers)), and library classes are typically instanciated and used as objects (like the [Database query builders](../database/query/builder)). The distinction is not black and white, and is irrelevant anyways, since they are treated the same by Kohana.
+
+## Creating a class
+
+To create a new class, simply place a file in the `classes/` directory at any point in the [Cascading Filesystem](files), that follows the [Class naming conventions](conventions#class-names-and-file-location). For example, lets create a `Foobar` class.
+
+ // classes/foobar.php
+
+ class Foobar {
+ static function magic() {
+ // Does something
+ }
+ }
+
+We can now call `Foobar::magic()` any where and Kohana will [autoload](autoloading) the file for us.
+
+We can also put classes in subdirectories.
+
+ // classes/professor/baxter.php
+
+ class Professor_Baxter {
+ static function teach() {
+ // Does something
+ }
+ }
+
+We could now call `Professor_Baxter::teach()` any where we want.
+
+For examples of how to create and use classes, simply look at the 'classes' folder in `system` or any module.
+
+## Namespacing your classes
+
+TODO: Discuss namespacing to provide transparent extension functionality in your own classes/modules.
diff --git a/includes/kohana/system/guide/kohana/files/config.md b/includes/kohana/system/guide/kohana/files/config.md
new file mode 100644
index 0000000..107101e
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/files/config.md
@@ -0,0 +1,95 @@
+# Config Files
+
+Configuration files are used to store any kind of configuration needed for a module, class, or anything else you want. They are plain PHP files, stored in the `config/` directory, which return an associative array:
+
+ 'value',
+ 'options' => array(
+ 'foo' => 'bar',
+ ),
+ );
+
+If the above configuration file was called `myconf.php`, you could access it using:
+
+ $config = Kohana::config('myconf');
+ $options = $config['options'];
+
+[Kohana::config] also provides a shortcut for accessing individual keys from configuration arrays using "dot paths" similar to [Arr::path].
+
+Get the "options" array:
+
+ $options = Kohana::config('myconf.options');
+
+Get the "foo" key from the "options" array:
+
+ $foo = Kohana::config('myconf.options.foo');
+
+Configuration arrays can also be accessed as objects, if you prefer that method:
+
+ $options = Kohana::config('myconf')->options;
+
+Please note that you can only access the top level of keys as object properties, all child keys must be accessed using standard array syntax:
+
+ $foo = Kohana::config('myconf')->options['foo'];
+
+## Merge
+
+Configuration files are slightly different from most other files within the [cascading filesystem](files) in that they are **merged** rather than overloaded. This means that all configuration files with the same file path are combined to produce the final configuration. The end result is that you can overload *individual* settings rather than duplicating an entire file.
+
+For example, if we wanted to change something in some file
+
+ [TODO]
+
+TODO exmaple of adding something to inflector
+
+## Creating your own config files
+
+Let's say we want a config file to store and easily change things like the title of a website, or the google analytics code. We would create a config file, let's call it `site.php`:
+
+ // config/site.php
+
+ 'Our Shiny Website',
+ 'analytics' => FALSE, // analytics code goes here, set to FALSE to disable
+ );
+
+We could now call `Kohana::config('site.title')` to get the site name, and `Kohana::config('site.analytics')` to get the analytics code.
+
+Let's say we want an archive of versions of some software. We could use config files to store each version, and include links to download, documentation, and issue tracking.
+
+ // config/versions.php
+
+ array(
+ 'codename' => 'Frog',
+ 'download' => 'files/ourapp-1.0.0.tar.gz',
+ 'documentation' => 'docs/1.0.0',
+ 'released' => '06/05/2009',
+ 'issues' => 'link/to/bug/tracker',
+ ),
+ '1.1.0' => array(
+ 'codename' => 'Lizard',
+ 'download' => 'files/ourapp-1.1.0.tar.gz',
+ 'documentation' => 'docs/1.1.0',
+ 'released' => '10/15/2009',
+ 'issues' => 'link/to/bug/tracker',
+ ),
+ /// ... etc ...
+ );
+
+You could then do the following:
+
+ // In your controller
+ $view->versions = Kohana::config('versions');
+
+ // In your view:
+ foreach ($versions as $version)
+ {
+ // echo some html to display each version
+ }
diff --git a/includes/kohana/system/guide/kohana/files/i18n.md b/includes/kohana/system/guide/kohana/files/i18n.md
new file mode 100644
index 0000000..88df52f
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/files/i18n.md
@@ -0,0 +1 @@
+Discuss the format of i18n files, and how to use them.
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/files/messages.md b/includes/kohana/system/guide/kohana/files/messages.md
new file mode 100644
index 0000000..b0c4a5c
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/files/messages.md
@@ -0,0 +1,5 @@
+
+
+Add that message files can be in subfolders, and you can use dot notation to retreive an array path: `Kohana::message('folder/subfolder/file','array.subarray.key')`
+
+Also reinforce that messages are merged by the cascade, not overwritten.
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/flow.md b/includes/kohana/system/guide/kohana/flow.md
new file mode 100644
index 0000000..bfd99d8
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/flow.md
@@ -0,0 +1,26 @@
+# Request Flow
+
+Every application follows the same flow:
+
+1. Application starts from `index.php`.
+ 1. The application, module, and system paths are set. (`APPPATH`, `MODPATH`, and `SYSPATH`)
+ 2. Error reporting levels are set.
+ 3. Install file is loaded, if it exists.
+ 4. The [Kohana] class is loaded.
+ 5. The bootstrap file, `APPPATH/bootstrap.php`, is included.
+2. Once we are in `bootstrap.php`:
+ 7. [Kohana::init] is called, which sets up error handling, caching, and logging.
+ 8. [Kohana_Config] readers and [Kohana_Log] writers are attached.
+ 9. [Kohana::modules] is called to enable additional modules.
+ * Module paths are added to the [cascading filesystem](files).
+ * Includes each module's `init.php` file, if it exists.
+ * The `init.php` file can perform additional environment setup, including adding routes.
+ 10. [Route::set] is called multiple times to define the [application routes](routing).
+ 11. [Request::instance] is called to start processing the request.
+ 1. Checks each route that has been set until a match is found.
+ 2. Creates the controller instance and passes the request to it.
+ 3. Calls the [Controller::before] method.
+ 4. Calls the controller action, which generates the request response.
+ 5. Calls the [Controller::after] method.
+ * The above 5 steps can be repeated multiple times when using [HMVC sub-requests](requests).
+ 12. The main [Request] response is displayed
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/fragments.md b/includes/kohana/system/guide/kohana/fragments.md
new file mode 100644
index 0000000..fa4f0e3
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/fragments.md
@@ -0,0 +1,135 @@
+# Fragments
+
+Fragments are a quick and simple way to cache HTML or other output. Fragments are not useful for caching objects or raw database results, in which case you should use a more robust caching method, which can be achieved with the [Cache module](../cache). Fragments use [Kohana::cache()] and will be placed in the cache directory (`application/cache` by default).
+
+You should use Fragment (or any caching solution) when reading the cache is faster than reprocessing the result. Reading and parsing a remote file, parsing a complicated template, calculating something, etc.
+
+Fragments are typically used in view files.
+
+## Usage
+
+Fragments are used by calling [Fragment::load()] in an `if` statement at the beginning of what you want cached, and [Fragment::save()] at the end. They use [output buffering](http://www.php.net/manual/en/function.ob-start.php) to capture the output between the two function calls.
+
+You can specify the lifetime (in seconds) of the Fragment using the second parameter of [Fragment::load()]. The default lifetime is 30 seconds. You can use the [Date] helper to make more readable times.
+
+Fragments will store a different cache for each language (using [I18n]) if you pass `true` as the third parameter to [Fragment::load()];
+
+You can force the deletion of a Fragment using [Fragment::delete()], or specify a lifetime of 0.
+
+~~~
+// Cache for 5 minutes, and cache each language
+if ( ! Fragment::load('foobar', Date::MINUTE * 5, true))
+{
+ // Anything that is echo'ed here will be saved
+ Fragment::save();
+}
+~~~
+
+## Example: Calculating Pi
+
+In this example we will calculate pi to 1000 places, and cache the result using a fragment. The first time you run this it will probably take a few seconds, but subsequent loads will be much faster, until the fragment lifetime runs out.
+
+~~~
+if ( ! Fragment::load('pi1000', Date::HOUR * 4))
+{
+ // Change function nesting limit
+ ini_set('xdebug.max_nesting_level',1000);
+
+ // Source: http://mgccl.com/2007/01/22/php-calculate-pi-revisited
+ function bcfact($n)
+ {
+ return ($n == 0 || $n== 1) ? 1 : bcmul($n,bcfact($n-1));
+ }
+ function bcpi($precision)
+ {
+ $num = 0;$k = 0;
+ bcscale($precision+3);
+ $limit = ($precision+3)/14;
+ while($k < $limit)
+ {
+ $num = bcadd($num, bcdiv(bcmul(bcadd('13591409',bcmul('545140134', $k)),bcmul(bcpow(-1, $k), bcfact(6*$k))),bcmul(bcmul(bcpow('640320',3*$k+1),bcsqrt('640320')), bcmul(bcfact(3*$k), bcpow(bcfact($k),3)))));
+ ++$k;
+ }
+ return bcdiv(1,(bcmul(12,($num))),$precision);
+ }
+
+ echo bcpi(1000);
+
+ Fragment::save();
+}
+
+echo View::factory('profiler/stats');
+
+?>
+~~~
+
+## Example: Recent Wikipedia edits
+
+In this example we will use the [Feed] class to retrieve and parse an RSS feed of recent edits to [http://en.wikipedia.org](http://en.wikipedia.org), then use Fragment to cache the results.
+
+~~~
+$feed = "http://en.wikipedia.org/w/index.php?title=Special:RecentChanges&feed=rss";
+$limit = 50;
+
+// Displayed feeds are cached for 30 seconds (default)
+if ( ! Fragment::load('rss:'.$feed)):
+
+ // Parse the feed
+ $items = Feed::parse($feed, $limit);
+
+ foreach ($items as $item):
+
+ // Convert $item to object
+ $item = (object) $item;
+
+ echo HTML::anchor($item->link,$item->title);
+
+ ?>
+
+ author: creator ?>
+ date: pubDate ?>
+
+ Home page stuff";
+
+ // Pretend like we are actually doing something :)
+ sleep(2);
+
+ // Cache this every hour since it doesn't change as often
+ if ( ! Fragment::load('homepage-subfragment', Date::HOUR)):
+
+ echo "Home page special thingy
";
+
+ // Pretend like this takes a long time
+ sleep(5);
+
+ Fragment::save(); endif;
+
+ echo "More home page stuff
";
+
+ Fragment::save();
+
+endif;
+
+echo View::factory('profiler/stats');
+~~~
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/helpers.md b/includes/kohana/system/guide/kohana/helpers.md
new file mode 100644
index 0000000..e8f6bcb
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/helpers.md
@@ -0,0 +1,53 @@
+# Helpers
+
+Kohana comes with many static helper functions to make certain tasks easier.
+
+You can make your own helpers by simply making a class and putting it in the `classes` directory, and you can also extend any helper to modify or add new functions using transparent extension.
+
+ - **[Arr]** - Array functions. Get an array key or default to a set value, get an array key by path, etc.
+
+ - **[CLI]** - Parse command line options.
+
+ - **[Cookie]** - Covered in more detail on the [Cookies](cookies) page.
+
+ - **[Date]** - Useful date functions and constants. Time between two dates, convert between am/pm and military, date offset, etc.
+
+ - **[Encrypt]** - Covered in more detail on the [Security](security) page.
+
+ - **[Feed]** - Parse and create RSS feeds.
+
+ - **[File]** - Get file type by mime, split and merge a file into small pieces.
+
+ - **[Form]** - Create HTML form elements.
+
+ - **[Fragment]** - Simple file based caching. Covered in more detail on the [Fragments](fragments) page.
+
+ - **[HTML]** - Useful HTML functions. Encode, obfuscate, create script, anchor, and image tags, etc.
+
+ - **[I18n]** - Internationalization helper for creating multilanguage sites.
+
+ - **[Inflector]** - Change a word into plural or singular form, camelize or humanize a phrase, etc.
+
+ - **[Kohana]** - The Kohana class is also a helper. Debug variables (like print_r but better), file loading, etc.
+
+ - **[Num]** - Provides locale aware formating and english ordinals (th, st, nd, etc).
+
+ - **[Profiler]** - Covered in more detail on the [Profiling](profiling) page.
+
+ - **[Remote]** - Remote server access helper using [CURL](http://php.net/curl).
+
+ - **[Request]** - Get the current request url, create expire tags, send a file, get the user agent, etc.
+
+ - **[Route]** - Create routes, create an internal link using a route.
+
+ - **[Security]** - Covered in more detail on the [Security](security) page.
+
+ - **[Session]** - Covered in more detail on the [Sessions](sessions) page.
+
+ - **[Text]** - Autolink, prevent window words, convert a number to text, etc.
+
+ - **[URL]** - Create a relative or absolute URL, make a URL-safe title, etc.
+
+ - **[UTF8]** - Provides multi-byte aware string functions like strlen, strpos, substr, etc.
+
+ - **[Upload]** - Helper for uploading files from a form.
diff --git a/includes/kohana/system/guide/kohana/index.md b/includes/kohana/system/guide/kohana/index.md
new file mode 100644
index 0000000..6f74f68
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/index.md
@@ -0,0 +1,19 @@
+# What is Kohana?
+
+Kohana is an open source, [object oriented](http://wikipedia.org/wiki/Object-Oriented_Programming) [MVC](http://wikipedia.org/wiki/Model–View–Controller "Model View Controller") [web framework](http://wikipedia.org/wiki/Web_Framework) built using [PHP5](http://php.net/manual/intro-whatis "PHP Hypertext Preprocessor") by a team of volunteers that aims to be swift, secure, and small.
+
+[!!] Kohana is licensed under a [BSD license](http://kohanaframework.org/license), so you can legally use it for any kind of open source, commercial, or personal project.
+
+## What makes Kohana great?
+
+Anything can be extended using the unique [filesystem](about.filesystem) design, little or no [configuration](about.configuration) is necessary, [error handling](debugging.errors) helps locate the source of errors quickly, and [debugging](debugging) and [profiling](debugging.profiling) provide insight into the application.
+
+To help secure your applications, tools for [XSS removal](security.xss), [input validation](security.validation), [signed cookies](security.cookies), [form](security.forms) and [HTML](security.html) generators are all included. The [database](security.database) layer provides protection against [SQL injection](http://wikipedia.org/wiki/SQL_Injection). Of course, all official code is carefully written and reviewed for security.
+
+## Contribute to the Documentation
+
+We are working very hard to provide complete documentation. To help improve the guide, please [fork the userguide](http://github.com/kohana/userguide), make your changes, and send a pull request. If you are not familiar with git, you can also submit a [feature request](http://dev.kohanaframework.org/projects/kohana3/issues) (requires registration).
+
+## Unofficial Documentation
+
+If you are having trouble finding an answer here, have a look through the [unofficial wiki](http://kerkness.ca/wiki/doku.php). Your answer may also be found by searching the [forum](http://forum.kohanaphp.com/) or [stackoverflow](http://stackoverflow.com/questions/tagged/kohana) followed by asking your question on either. Additionally, you can chat with the community of developers on the freenode [#kohana](irc://irc.freenode.net/kohana) IRC channel.
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/install.md b/includes/kohana/system/guide/kohana/install.md
new file mode 100644
index 0000000..876fb11
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/install.md
@@ -0,0 +1,33 @@
+# Installation
+
+1. Download the latest **stable** release from the [Kohana website](http://kohanaframework.org/).
+2. Unzip the downloaded package to create a `kohana` directory.
+3. Upload the contents of this folder to your webserver.
+4. Open `application/bootstrap.php` and make the following changes:
+ - Set the default [timezone](http://php.net/timezones) for your application.
+ - Set the `base_url` in the [Kohana::init] call to reflect the location of the kohana folder on your server relative to the document root.
+6. Make sure the `application/cache` and `application/logs` directories are writable by the web server.
+7. Test your installation by opening the URL you set as the `base_url` in your favorite browser.
+
+[!!] Depending on your platform, the installation's subdirs may have lost their permissions thanks to zip extraction. Chmod them all to 755 by running `find . -type d -exec chmod 0755 {} \;` from the root of your Kohana installation.
+
+You should see the installation page. If it reports any errors, you will need to correct them before continuing.
+
+![Install Page](install.png "Example of install page")
+
+Once your install page reports that your environment is set up correctly you need to either rename or delete `install.php` in the root directory. Kohana is now installed and you should see the output of the welcome controller:
+
+![Welcome Page](welcome.png "Example of welcome page")
+
+## Installing Kohana 3.0 From GitHub
+
+The [source code](http://github.com/kohana/kohana) for Kohana 3.0 is hosted with [GitHub](http://github.com). To install Kohana using the github source code first you need to install git. Visit [http://help.github.com](http://help.github.com) for details on how to install git on your platform.
+
+To install the last stable release of Kohana using git run these commands:
+
+ git clone git://github.com/kohana/kohana.git
+ cd kohana/
+ git submodule init
+ git submodule update
+
+[!!] For more information on using Git and Kohana see the [Working with Git](tutorials/git) tutorial.
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/menu.md b/includes/kohana/system/guide/kohana/menu.md
new file mode 100644
index 0000000..06da9d3
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/menu.md
@@ -0,0 +1,48 @@
+## [Kohana]()
+
+- Getting Started
+ - [Installation](install)
+ - [Conventions and Style](conventions)
+ - [Model View Controller](mvc)
+ - [Controllers](mvc/controllers)
+ - [Models](mvc/models)
+ - [Views](mvc/views)
+ - [Cascading Filesystem](files)
+ - [Class Files](files/classes)
+ - [Config Files](files/config)
+ - [Translation Files](files/i18n)
+ - [Message Files](files/messages)
+ - [Request Flow](flow)
+ - [Bootstrap](bootstrap)
+ - [Modules](modules)
+ - [Routing](routing)
+ - [Error Handling](errors)
+ - [Tips & Common Mistakes](tips)
+ - [Upgrading from v2.x](upgrading)
+- Basic Usage
+ - [Debugging](debugging)
+ - [Loading Classes](autoloading)
+ - [Transparent Extension](extension)
+ - [Helpers](helpers)
+ - [Requests](requests)
+ - [Sessions](sessions)
+ - [Cookies](cookies)
+ - [Fragments](fragments)
+ - [Profiling](profiling)
+- [Security](security)
+ - [XSS](security/xss)
+ - [Validation](security/validation)
+ - [Cookies](security/cookies)
+ - [Database](security/database)
+ - [Encryption](security/encryption)
+ - [Deploying](security/deploying)
+- [Tutorials](tutorials)
+ - [Hello World](tutorials/hello-world)
+ - [Simple MVC](tutorials/simple-mvc)
+ - [Routes & Links](tutorials/routes-and-links)
+ - [Custom Error Pages](tutorials/error-pages)
+ - [Content Translation](tutorials/translation)
+ - [Clean URLs](tutorials/clean-urls)
+ - [Sharing Kohana](tutorials/sharing-kohana)
+ - [Template Driven Site](tutorials/templates)
+ - [Working with Git](tutorials/git)
diff --git a/includes/kohana/system/guide/kohana/models.md b/includes/kohana/system/guide/kohana/models.md
new file mode 100644
index 0000000..f6c960b
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/models.md
@@ -0,0 +1 @@
+This will discuss models, explain what should be in a model, and give *breif* examples of extending models, like using modeling systems.
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/modules.md b/includes/kohana/system/guide/kohana/modules.md
new file mode 100644
index 0000000..62bf574
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/modules.md
@@ -0,0 +1,38 @@
+# Modules
+
+Modules are simply an addition to the [Cascading Filesystem](files). A module can add any kind of file (controllers, views, classes, config files, etc.) to the filesystem available to Kohana (via [Kohana::find_file]). This is useful to make any part of your application more transportable or shareable between different apps. For example, creating a new modeling system, a search engine, a css/js manager, etc.
+
+## Where to find modules
+
+Kolanos has created [kohana-universe](http://github.com/kolanos/kohana-universe/tree/master/modules/), a fairly comprehensive list of modules that are available on Github. To get your module listed there, send him a message via Github.
+
+Mon Geslani created a [very nice site](http://kohana.mongeslani.com/) that allows you to sort Github modules by activity, watchers, forks, etc. It seems to not be as comprehensive as kohana-universe.
+
+## Enabling modules
+
+Modules are enabled by calling [Kohana::modules] and passing an array of `'name' => 'path'`. The name isn't important, but the path obviously is. A module's path does not have to be in `MODPATH`, but usually is. You can only call [Kohana::modules] once.
+
+ Kohana::modules(array(
+ 'auth' => MODPATH.'auth', // Basic authentication
+ 'cache' => MODPATH.'cache', // Caching with multiple backends
+ 'codebench' => MODPATH.'codebench', // Benchmarking tool
+ 'database' => MODPATH.'database', // Database access
+ 'image' => MODPATH.'image', // Image manipulation
+ 'orm' => MODPATH.'orm', // Object Relationship Mapping
+ 'oauth' => MODPATH.'oauth', // OAuth authentication
+ 'pagination' => MODPATH.'pagination', // Paging of results
+ 'unittest' => MODPATH.'unittest', // Unit testing
+ 'userguide' => MODPATH.'userguide', // User guide and API documentation
+ ));
+
+## Init.php
+
+When a module is activated, if an `init.php` file exists in that module's directory, it is included. This is the ideal place to have a module include routes or other initialization necessary for the module to function. The Userguide and Codebench modules have init.php files you can look at.
+
+## How modules work
+
+A file in an enabled module is virtually the same as having that exact file in the same place in the application folder. The main difference being that it can be overwritten by a file of the same name in a higher location (a module enabled after it, or the application folder) via the [Cascading Filesystem](files). It also provides an easy way to organize and share your code.
+
+## Creating your own module
+
+To create a module simply create a folder (usually in `DOCROOT/modules`) and place the files you want to be in the module there, and activate that module in your bootstrap. To share your module, you can upload it to [Github](http://github.com). You can look at examples of modules made by [Kohana](http://github.com/kohana) or [other users](#where-to-find-modules).
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/mvc.md b/includes/kohana/system/guide/kohana/mvc.md
new file mode 100644
index 0000000..2ee8549
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/mvc.md
@@ -0,0 +1,3 @@
+
+
+Discus the MVC pattern, as it pertains to Kohana. Perhaps have an image, etc.
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/mvc/controllers.md b/includes/kohana/system/guide/kohana/mvc/controllers.md
new file mode 100644
index 0000000..ec15bea
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/mvc/controllers.md
@@ -0,0 +1,189 @@
+# Controllers
+
+A Controller is a class file that stands in between the models and the views in an application. It passes information on to the model when data needs to be changed and it requests information from the model when data needs to be loaded. Controllers then pass on the information of the model to the views where the final output can be rendered for the users. Controllers essentially control the flow of the application.
+
+Controllers are called by the [Request::execute()] function based on the [Route] that the url matched. Be sure to read the [routing](routing) page to understand how to use routes to map urls to your controllers.
+
+## Creating Controllers
+
+In order to function, a controller must do the following:
+
+* Reside in `classes/controller` (or a sub-directory)
+* Filename must be lowercase, e.g. `articles.php`
+* The class name must map to the filename (with `/` replaced with `_`) and each word is capitalized
+* Must have the Controller class as a (grand)parent
+
+Some examples of controller names and file locations:
+
+ // classes/controller/foobar.php
+ class Controller_Foobar extends Controller {
+
+ // classes/controller/admin.php
+ class Controller_Admin extends Controller {
+
+Controllers can be in sub-folders:
+
+ // classes/controller/baz/bar.php
+ class Controller_Baz_Bar extends Controller {
+
+ // classes/controller/product/category.php
+ class Controller_Product_Category extends Controller {
+
+[!!] Note that controllers in sub-folders can not be called by the default route, you will need to define a route that has a [directory](routing#directory) param or sets a default value for directory.
+
+Controllers can extend other controllers.
+
+ // classes/controller/users.php
+ class Controller_Users extends Controller_Template
+
+ // classes/controller/api.php
+ class Controller_Api extends Controller_REST
+
+[!!] [Controller_Template] and [Controller_REST] are some example controllers provided in Kohana.
+
+You can also have a controller extend another controller to share common things, such as requiring you to be logged in to use all of those controllers.
+
+ // classes/controller/admin.php
+ class Controller_Admin extends Controller {
+ // This controller would have a before() that checks if the user is logged in
+
+ // classes/controller/admin/plugins.php
+ class Controller_Admin_Plugins extends Controller_Admin {
+ // Because this controller extends Controller_Admin, it would have the same logged in check
+
+## $this->request
+
+Every controller has the `$this->request` property which is the [Request] object that called the controller. You can use this to get information about the current request, as well as set the response via `$this->request->response`.
+
+Here is a partial list of the properties and methods available to `$this->request`. These can also be accessed via `Request::instance()`, but `$this->request` is provided as a shortcut. See the [Request] class for more information on any of these.
+
+Property/method | What it does
+--- | ---
+[$this->request->route](../api/Request#property:route) | The [Route] that matched the current request url
+[$this->request->directory](../api/Request#property:directory), [$this->request->controller](../api/Request#property:controller), [$this->request->action](../api/Request#property:action) | The directory, controller and action that matched for the current route
+[$this->request->param()](../api/Request#param) | Any other params defined in your route
+[$this->request->response](../api/Request#property:response) | The content to return for this request
+[$this->request->status](../api/Request#property:status) | The HTTP status for the request (200, 404, 500, etc.)
+[$this->request->headers](../api/Request#property:headers) | The HTTP headers to return with the response
+[$this->request->redirect()](../api/Request#redirect) | Redirect the request to a different url
+
+
+## Actions
+
+You create actions for your controller by defining a public function with an `action_` prefix. Any method that is not declared as `public` and prefixed with `action_` can NOT be called via routing.
+
+An action method will decide what should be done based on the current request, it *controls* the application. Did the user want to save a blog post? Did they provide the necesarry fields? Do they have permission to da that? The controller will call other classes, including models, to accomplish this. Every action should set `$this->request->response` to the [view file](mvc/views) to be sent to the browser, unless it [redirected](../api/Request#redirect) or otherwise ended the script earlier.
+
+A very basic action method that simply loads a [view](mvc/views) file.
+
+ public function action_hello()
+ {
+ $this->request->response = View::factory('hello/world'); // This will load views/hello/world.php
+ }
+
+### Parameters
+
+Parameters can be accessed in two ways. The first is by calling `$this->request->param('name')` where `name` is the name defined in the route.
+
+ // Assuming Route::set('example','(/(/(/)))');
+
+ public function action_foobar()
+ {
+ $id = $this->request->param('id');
+ $new = $this->request->param('new');
+
+If that parameter is not set it will be returned as NULL. You can provide a second parameter to set a default value if that param is not set.
+
+ public function action_foobar()
+ {
+ // $id will be false if it was not supplied in the url
+ $id = $this->request->param('user',FALSE);
+
+The second way you can access route parameters is from the actions function definition. Any extra keys in your route (keys besides ``, ``, and ``) are passed as parameters to your action *in the order they appear in the route*.
+
+ // Assuming Route::set('example','(/(/(/)))');
+
+ public function action_foobar($id, $new)
+ {
+
+Note that the names do not actually matter, *only the order*. You could name the parameters anything you want in both the route and the function definition, they don't even need to match. The following code is identical in function to the previous example.
+
+ // Assuming Route::set('example','(/(/(/)))');
+
+ public function action_foobar($foo, $bar)
+ {
+
+You can provide default values in the same way you do for any php function.
+
+ public function action_foobar($id = 0, $new = NULL)
+ {
+
+You can use whichever method you prefer. Using function params is quick and easy and saves on `$this->request->param()` calls, but keep in mind that if your routes ever change it could change the paramater order and break things. Therefore, it is recommended you use `$this->request->param()`. For example, assuming the following route
+
+ Route::set('example','(/(/(/)))');
+
+If you called "example/foobar/4/bobcat" you could access the parameters by either:
+
+ public function action_foobar($id, $new)
+ {
+
+ // OR
+
+ public function action_foobar()
+ {
+ $id = $this->request->param('id');
+ $new = $this->request->param('new');
+
+Then, let's say sometime in the future you change your url schemes and your routes. The new route is:
+
+ // Note that id and new are switched
+ Route::set('example','(/(/(/)))');
+
+Because the `` and `` keys are in a different order, you will need to fix your function definition to be `action_foobar($new, $id)` whereas the function that used `$this->request->param()` calls would continue to function as desired.
+
+### Examples
+
+TODO: some examples of actions
+
+## Before and after
+
+You can use the `before()` and `after()` functions to have code executed before or after the action is executed. For example, you could check if the user is logged in, set a template view, loading a required file, etc.
+
+For example, if you look in `Controller_Template` you can see that in the be
+
+You can check what action has been requested (via `$this->request->action`) and do something based on that, such as requiring the user to be logged in to use a controller, unless they are using the login action.
+
+ // Checking auth/login in before, and redirecting if necessary:
+
+ Controller_Admin extends Controller {
+
+ public function before()
+ {
+ // If this user doesn't have the admin role, and is not trying to login, redirect to login
+ if ( ! Auth::instance()->logged_in('admin') AND $this->request->action !== 'login')
+ {
+ $this->request->redirect('admin/login');
+ }
+ }
+
+ public function action_login() {
+ ...
+
+### Custom __construct() function
+
+In general, you should not have to change the `__construct()` function, as anything you need for all actions can be done in `before()`. If you need to change the controller constructor, you must preserve the parameters or PHP will complain. This is so the Request object that called the controller is available. *Again, in most cases you should probably be using `before()`, and not changing the constructor*, but if you really, *really* need to it should look like this:
+
+ // You should almost never need to do this, use before() instead!
+
+ // Be sure Kohana_Request is in the params
+ public function __construct(Kohana_Request $request)
+ {
+ // You must call parent::__construct at some point in your function
+ parent::__construct($request);
+
+ // Do whatever else you want
+ }
+
+## Extending other controllers
+
+TODO: More description and examples of extending other controllers, multiple extension, etc.
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/mvc/models.md b/includes/kohana/system/guide/kohana/mvc/models.md
new file mode 100644
index 0000000..2fbcf0b
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/mvc/models.md
@@ -0,0 +1 @@
+Discuss models. What should go in a model, what shouldn't be in a model. Provide **very simple** examples using prepared statements, the query builder, as well as mention modeling libraries.
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/mvc/views.md b/includes/kohana/system/guide/kohana/mvc/views.md
new file mode 100644
index 0000000..77d757f
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/mvc/views.md
@@ -0,0 +1,161 @@
+# Views
+
+Views are files that contain the display information for your application. This is most commonly HTML, CSS and Javascript but can be anything you require such as XML or JSON for AJAX output. The purpose of views is to keep this information separate from your application logic for easy reusability and cleaner code.
+
+Views themselves can contain code used for displaying the data you pass into them. For example, looping through an array of product information and display each one on a new table row. Views are still PHP files so you can use any code you normally would. However, you should try to keep your views as "dumb" as possible and retreive all data you need in your controllers, then pass it to the view.
+
+# Creating View Files
+
+View files are stored in the `views` directory of the [filesystem](files). You can also create sub-directories within the `views` directory to organize your files. All of the following examples are reasonable view files:
+
+ APPPATH/views/home.php
+ APPPATH/views/pages/about.php
+ APPPATH/views/products/details.php
+ MODPATH/error/views/errors/404.php
+ MODPATH/common/views/template.php
+
+## Loading Views
+
+[View] objects will typically be created inside a [Controller](mvc/controllers) using the [View::factory] method. Typically the view is then assigned as the [Request::$response] property or to another view.
+
+ public function action_about()
+ {
+ $this->request->response = View::factory('pages/about');
+ }
+
+When a view is assigned as the [Request::$response], as in the example above, it will automatically be rendered when necessary. To get the rendered result of a view you can call the [View::render] method or just type cast it to a string. When a view is rendered, the view file is loaded and HTML is generated.
+
+ public function action_index()
+ {
+ $view = View::factory('pages/about');
+
+ // Render the view
+ $about_page = $view->render();
+
+ // Or just type cast it to a string
+ $about_page = (string) $view;
+
+ $this->request->response = $about_page;
+ }
+
+## Variables in Views
+
+Once view has been loaded, variables can be assigned to it using the [View::set] and [View::bind] methods.
+
+ public function action_roadtrip()
+ {
+ $view = View::factory('user/roadtrip')
+ ->set('places', array('Rome', 'Paris', 'London', 'New York', 'Tokyo'));
+ ->bind('user', $this->user);
+
+ // The view will have $places and $user variables
+ $this->request->response = $view;
+ }
+
+[!!] The only difference between `set()` and `bind()` is that `bind()` assigns the variable by reference. If you `bind()` a variable before it has been defined, the variable will be created with a value of `NULL`.
+
+You can also assign variables directly to the View object. This is identical to calling `set()`;
+
+ public function action_roadtrip()
+ {
+ $view = View::factory('user/roadtrip');
+
+ $view->places = array('Rome', 'Paris', 'London', 'New York', 'Tokyo');
+ $view->user = $this->user;
+
+ // The view will have $places and $user variables
+ $this->request->response = $view;
+ }
+
+### Global Variables
+
+An application may have several view files that need access to the same variables. For example, to display a page title in both the header of your template and in the body of the page content. You can create variables that are accessible in any view using the [View::set_global] and [View::bind_global] methods.
+
+ // Assign $page_title to all views
+ View::bind_global('page_title', $page_title);
+
+If the application has three views that are rendered for the home page: `template`, `template/sidebar`, and `pages/home`. First, an abstract controller to create the template will be created:
+
+ abstract class Controller_Website extends Controller_Template {
+
+ public $page_title;
+
+ public function before()
+ {
+ parent::before();
+
+ // Make $page_title available to all views
+ View::bind_global('page_title', $this->page_title);
+
+ // Load $sidebar into the template as a view
+ $this->template->sidebar = View::factory('template/sidebar');
+ }
+
+ }
+
+Next, the home controller will extend `Controller_Website`:
+
+ class Controller_Home extends Controller_Website {
+
+ public function action_index()
+ {
+ $this->page_title = 'Home';
+
+ $this->template->content = View::factory('pages/home');
+ }
+
+ }
+
+## Views Within Views
+
+If you want to include another view within a view, there are two choices. By calling [View::factory] you can sandbox the included view. This means that you will have to provide all of the variables to the view using [View::set] or [View::bind]:
+
+ // In your view file:
+
+ // Only the $user variable will be available in "views/user/login.php"
+ bind('user', $user) ?>
+
+The other option is to include the view directly, which makes all of the current variables available to the included view:
+
+ // In your view file:
+
+ // Any variable defined in this view will be included in "views/message.php"
+
+
+You can also assign a variable of your parent view to be the child view from within your controller. For example:
+
+ // In your controller:
+
+ public functin action_index()
+ {
+ $view = View::factory('common/template);
+
+ $view->title = "Some title";
+ $view->body = View::factory('pages/foobar');
+ }
+
+ // In views/common/template.php:
+
+
+
+
+
+
+
+
+
+
+
+Of course, you can also load an entire [Request] within a view:
+
+ execute() ?>
+
+This is an example of \[HMVC], which makes it possible to create and read calls to other URLs within your application.
+
+## Differences From v2.x
+
+Unlike version 2.x of Kohana, the view is not loaded within the context of
+the [Controller], so you will not be able to access `$this` as the controller
+that loaded the view. Passing the controller to the view must be done explictly:
+
+ $view->bind('controller', $this);
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/profiling.md b/includes/kohana/system/guide/kohana/profiling.md
new file mode 100644
index 0000000..e7303c8
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/profiling.md
@@ -0,0 +1,54 @@
+# Profiling
+
+Kohana provides a very simple way to display statistics about your application:
+
+1. Common [Kohana] method calls, such as [Kohana::find_file()].
+2. Requests. Including the main request, as well as any sub-requests.
+3. [Database] queries
+4. Average execution times for your application
+
+[!!] In order for profiling to work, the `profile` setting must be `TRUE` in your [Kohana::init()] call in your bootstrap.
+
+## Profiling your code
+
+You can easily add profiling to your own functions and code. This is done using the [Profiler::start()] function. The first parameter is the group, the second parameter is the name of the benchmark.
+
+ public function foobar($input)
+ {
+ // Be sure to only profile if it's enabled
+ if (Kohana::$profiling === TRUE)
+ {
+ // Start a new benchmark
+ $benchmark = Profiler::start('Your Category', __FUNCTION__);
+ }
+
+ // Do some stuff
+
+ if (isset($benchmark))
+ {
+ // Stop the benchmark
+ Profiler::stop($benchmark);
+ }
+
+ return $something;
+ }
+
+## How to read the profiling report
+
+The benchmarks are sorted into groups. Each benchmark will show its name, how many times it was run (show in parenthesis after the benchmark name), and then the min, max, average, and total time and memory spent on that benchmark. The total column will have shaded backgrounds to show the relative times between benchmarks in the same group.
+
+At the very end is a group called "Application Execution". This keeps track of how long each execution has taken. The number in parenthesis is how many executions are being compared. It shows the fastest, slowest, and average time and memory usage of the last several requsets. The last box is the time and memory usage of the current request.
+
+((This could use a picture of a profiler with some database queries, etc. with annotations to point out each area as just described.))
+
+## Displaying the profiler
+
+You can display or collect the current [profiler] statistics at any time:
+
+
+
+## Preview
+
+(This is the actual profiler stats for this page.)
+
+{{profiler/stats}}
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/requests.md b/includes/kohana/system/guide/kohana/requests.md
new file mode 100644
index 0000000..82a2c6e
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/requests.md
@@ -0,0 +1,15 @@
+# Requests
+
+blah
+
+## The main request
+
+request::instance() gets the main request, you set the response in the bootstrap (usually), you use $request->status to send a 404 or other status, $request->headers to send headers, is_ajax, etc.
+
+## Subrequests
+
+TODO: This will talk about subrequests.
+
+
+
+
diff --git a/includes/kohana/system/guide/kohana/routing.md b/includes/kohana/system/guide/kohana/routing.md
new file mode 100644
index 0000000..47eed9a
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/routing.md
@@ -0,0 +1,240 @@
+# Routing
+
+Kohana provides a very powerful routing system. In essence, routes provide an interface between the urls and your controllers and actions. With the correct routes you could make almost any url scheme correspond to almost any arrangement of controllers, and you could change one without impacting the other.
+
+As mentioned in the [Request Flow](flow) section, a request is handled by the [Request] class, which will look for a matching [Route] and load the appropriate controller to handle that request.
+
+[!!] It is important to understand that **routes are matched in the order they are added**, and as soon as a URL matches a route, routing is essentially "stopped" and *the remaining routes are never tried*. Because the default route matches almost anything, including an empty url, new routes must be place before it.
+
+## Creating routes
+
+If you look in `APPPATH/bootstrap.php` you will see the "default" route as follows:
+
+ Route::set('default', '((/(/)))')
+ ->defaults(array(
+ 'controller' => 'welcome',
+ 'action' => 'index',
+ ));
+
+[!!] The default route is simply provided as a sample, you can remove it and replace it with your own routes.
+
+So this creates a route with the name `default` that will match urls in the format of `((/(/)))`.
+
+Let's take a closer look at each of the parameters of [Route::set], which are `name`, `uri`, and an optional array `regex`.
+
+### Name
+
+The name of the route must be a **unique** string. If it is not it will overwrite the older route with the same name. The name is used for creating urls by reverse routing, or checking which route was matched.
+
+### URI
+
+The uri is a string that represents the format of urls that should be matched. The tokens surrounded with `<>` are *keys* and anything surrounded with `()` are *optional* parts of the uri. In Kohana routes, any character is allowed and treated literally aside from `()<>`. The `/` has no meaning besides being a character that must match in the uri. Usually the `/` is used as a static seperator but as long as the regex makes sense, there are no restrictions to how you can format your routes.
+
+Lets look at the default route again, the uri is `((/(/)))`. We have three keys or params: controller, action, and id. In this case, the entire uri is optional, so a blank uri would match and the default controller and action (set by defaults(), [covered below](#defaults)) would be assumed resulting in the `Controller_Welcome` class being loaded and the `action_index` method being called to handle the request.
+
+You can use any name you want for your keys, but the following keys have special meaning to the [Request] object, and will influence which controller and action are called:
+
+ * **Directory** - The sub-directory of `classes/controller` to look for the controller (\[covered below]\(#directory))
+ * **Controller** - The controller that the request should execute.
+ * **Action** - The action method to call.
+
+### Regex
+
+The Kohana route system uses [perl compatible regular expressions](http://perldoc.perl.org/perlre.html) in its matching process. By default each key (surrounded by `<>`) will match `[^/.,;?\n]++` (or in english: anything that is not a slash, period, comma, semicolon, question mark, or newline). You can define your own patterns for each key by passing an associative array of keys and patterns as an additional third argument to Route::set.
+
+In this example, we have controllers in two directories, `admin` and `affiliate`. Because this route will only match urls that begin with `admin` or `affiliate`, the default route would still work for controllers in `classes/controller`.
+
+ Route::set('sections', '(/(/(/)))',
+ array(
+ 'directory' => '(admin|affiliate)'
+ ))
+ ->defaults(array(
+ 'controller' => 'home',
+ 'action' => 'index',
+ ));
+
+You can also use a less restrictive regex to match unlimited parameters, or to ignore overflow in a route. In this example, the url `foobar/baz/and-anything/else_that/is-on-the/url` would be routed to `Controller_Foobar::action_baz()` and the `"stuff"` parameter would be `"and-anything/else_that/is-on-the/url"`. If you wanted to use this for unlimited parameters, you could [explode](http://php.net/manual/en/function.explode.php) it, or you just ignore the overflow.
+
+ Route::set('default', '((/(/)))', array('stuff' => '.*'))
+ ->defaults(array(
+ 'controller' => 'welcome',
+ 'action' => 'index',
+ ));
+
+
+### Default values
+
+If a key in a route is optional (or not present in the route), you can provide a default value for that key by passing an associated array of keys and default values to [Route::defaults], chained after your [Route::set]. This can be useful to provide a default controller or action for your site, among other things.
+
+[!!] The `controller` and `action` key must always have a value, so they either need to be required in your route (not inside of parentheses) or have a default value provided.
+
+In the default route, all the keys are optional, and the controller and action are given a default. If we called an empty url, the defaults would fill in and `Controller_Welcome::action_index()` would be called. If we called `foobar` then only the default for action would be used, so it would call `Controller_Foobar::action_index()` and finally, if we called `foobar/baz` then neither default would be used and `Controller_Foobar::action_baz()` would be called.
+
+TODO: need an example here
+
+You can also use defaults to set a key that isn't in the route at all.
+
+TODO: example of either using directory or controller where it isn't in the route, but set by defaults
+
+### Directory
+
+## Examples
+
+TODO: a million billion examples, you can use the following as a guide for some routes to include:
+
+
+
+
+
+There are countless other possibilities for routes. Here are some more examples:
+
+ /*
+ * Authentication shortcuts
+ */
+ Route::set('auth', '',
+ array(
+ 'action' => '(login|logout)'
+ ))
+ ->defaults(array(
+ 'controller' => 'auth'
+ ));
+
+ /*
+ * Multi-format feeds
+ * 452346/comments.rss
+ * 5373.json
+ */
+ Route::set('feeds', '(/).',
+ array(
+ 'user_id' => '\d+',
+ 'format' => '(rss|atom|json)',
+ ))
+ ->defaults(array(
+ 'controller' => 'feeds',
+ 'action' => 'status',
+ ));
+
+ /*
+ * Static pages
+ */
+ Route::set('static', '.html',
+ array(
+ 'path' => '[a-zA-Z0-9_/]+',
+ ))
+ ->defaults(array(
+ 'controller' => 'static',
+ 'action' => 'index',
+ ));
+
+ /*
+ * You don't like slashes?
+ * EditGallery:bahamas
+ * Watch:wakeboarding
+ */
+ Route::set('gallery', '():',
+ array(
+ 'controller' => '[A-Z][a-z]++',
+ 'action' => '[A-Z][a-z]++',
+ ))
+ ->defaults(array(
+ 'controller' => 'Slideshow',
+ ));
+
+ /*
+ * Quick search
+ */
+ Route::set('search', ':', array('query' => '.*'))
+ ->defaults(array(
+ 'controller' => 'search',
+ 'action' => 'index',
+ ));
+
+## Request parameters
+
+The `directory`, `controller` and `action` can be accessed from the [Request] as public properties like so:
+
+ // From within a controller:
+ $this->request->action;
+ $this->request->controller;
+ $this->request->directory;
+
+ // Can be used anywhere:
+ Request::instance()->action;
+ Request::instance()->controller;
+ Request::instance()->directory;
+
+All other keys specified in a route can be accessed via [Request::param()]:
+
+ // From within a controller:
+ $this->request->param('key_name');
+
+ // Can be used anywhere:
+ Request::instance()->param('key_name');
+
+The [Request::param] method takes an optional second argument to specify a default return value in case the key is not set by the route. If no arguments are given, all keys are returned as an associative array. In addition, `action`, `controller` and `directory` are not accessible via [Request::param()].
+
+For example, with the following route:
+
+ Route::set('ads','ad/(/)')
+ ->defaults(array(
+ 'controller' => 'ads',
+ 'action' => 'index',
+ ));
+
+If a url matches the route, then `Controller_Ads::index()` will be called. You could access the parameters in two ways:
+
+First, any non-special parameters (parameters other than controller, action, and directory) in a route are passed as parameters to the action method in the order they appear in the route. Be sure to define a default value for optional parameters if you don't define them in the route's `->defaults()`.
+
+ class Controller_Ads extends Controller {
+ public function action_index($ad, $affiliate = NULL)
+ {
+
+ }
+
+Secondly, you can access the parameters using the `param()` method of the [Request] class. Again, remember to define a default value (via the second, optional parameter of [Request::param]) if you didn't in `->defaults()`.
+
+ class Controller_Ads extends Controller {
+ public function action_index()
+ {
+ $ad = $this->request->param('ad');
+ $affiliate = $this->request->param('affiliate',NULL);
+ }
+
+
+## Where should routes be defined?
+
+The established convention is to either place your custom routes in the `MODPATH//init.php` file of your module if the routes belong to a module, or simply insert them into the `APPPATH/bootstrap.php` file (be sure to put them **above** the default route) if they are specific to the application. Of course, nothing stops you from including them from an external file, or even generating them dynamically.
+
+## A deeper look at how routes work
+
+TODO: talk about how routes are compiled
+
+## Creating URLs and links using routes
+
+Along with Kohana's powerful routing capabilities are included some methods for generating URLs for your routes' uris. You can always specify your uris as a string using [URL::site] to create a full URL like so:
+
+ URL::site('admin/edit/user/'.$user_id);
+
+However, Kohana also provides a method to generate the uri from the route's definition. This is extremely useful if your routing could ever change since it would relieve you from having to go back through your code and change everywhere that you specified a uri as a string. Here is an example of dynamic generation that corresponds to the `feeds` route example from above:
+
+ Route::get('feeds')->uri(array(
+ 'user_id' => $user_id,
+ 'action' => 'comments',
+ 'format' => 'rss'
+ ));
+
+Let's say you decided later to make that route definition more verbose by changing it to `feeds/(/).`. If you wrote your code with the above uri generation method you wouldn't have to change a single line! When a part of the uri is enclosed in parentheses and specifies a key for which there in no value provided for uri generation and no default value specified in the route, then that part will be removed from the uri. An example of this is the `(/)` part of the default route; this will not be included in the generated uri if an id is not provided.
+
+One method you might use frequently is the shortcut [Request::uri] which is the same as the above except it assumes the current route, directory, controller and action. If our current route is the default and the uri was `users/list`, we can do the following to generate uris in the format `users/view/$id`:
+
+ $this->request->uri(array('action' => 'view', 'id' => $user_id));
+
+Or if within a view, the preferable method is:
+
+ Request::instance()->uri(array('action' => 'view', 'id' => $user_id));
+
+TODO: examples of using html::anchor in addition to the above examples
+
+## Testing routes
+
+TODO: mention bluehawk's devtools module
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/security.md b/includes/kohana/system/guide/kohana/security.md
new file mode 100644
index 0000000..2f90a04
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/security.md
@@ -0,0 +1 @@
+General security concerns, like using the Security class, CSRF, and a brief intro to XSS, database security, etc. Also mention the security features that Kohana provides, like cleaning globals.
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/security/cookies.md b/includes/kohana/system/guide/kohana/security/cookies.md
new file mode 100644
index 0000000..3966f6c
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/security/cookies.md
@@ -0,0 +1,3 @@
+Discuss security of cookies, like changing the encryption key in the config.
+
+Not sure why I'm linking to this:
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/security/database.md b/includes/kohana/system/guide/kohana/security/database.md
new file mode 100644
index 0000000..e6190b7
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/security/database.md
@@ -0,0 +1,5 @@
+Discuss database security.
+
+How to avoid injection, etc.
+
+Not sure why I'm linking to this:
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/security/deploying.md b/includes/kohana/system/guide/kohana/security/deploying.md
new file mode 100644
index 0000000..538533f
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/security/deploying.md
@@ -0,0 +1,81 @@
+Changes that should happen when you deploy. (Production)
+
+Security settings from:
+
+
+
+
+## Setting up a production environment
+
+There are a few things you'll want to do with your application before moving into production.
+
+1. See the [Bootstrap page](bootstrap) in the docs.
+ This covers most of the global settings that would change between environments.
+ As a general rule, you should enable caching and disable profiling ([Kohana::init] settings) for production sites.
+ [Route::cache] can also help if you have a lot of routes.
+2. Catch all exceptions in `application/bootstrap.php`, so that sensitive data is cannot be leaked by stack traces.
+ See the example below which was taken from Shadowhand's [wingsc.com source](http://github.com/shadowhand/wingsc).
+3. Turn on APC or some kind of opcode caching.
+ This is the single easiest performance boost you can make to PHP itself. The more complex your application, the bigger the benefit of using opcode caching.
+
+ /**
+ * Set the environment string by the domain (defaults to Kohana::DEVELOPMENT).
+ */
+ Kohana::$environment = ($_SERVER['SERVER_NAME'] !== 'localhost') ? Kohana::PRODUCTION : Kohana::DEVELOPMENT;
+ /**
+ * Initialise Kohana based on environment
+ */
+ Kohana::init(array(
+ 'base_url' => '/',
+ 'index_file' => FALSE,
+ 'profile' => Kohana::$environment !== Kohana::PRODUCTION,
+ 'caching' => Kohana::$environment === Kohana::PRODUCTION,
+ ));
+
+ /**
+ * Execute the main request using PATH_INFO. If no URI source is specified,
+ * the URI will be automatically detected.
+ */
+ $request = Request::instance($_SERVER['PATH_INFO']);
+
+ try
+ {
+ // Attempt to execute the response
+ $request->execute();
+ }
+ catch (Exception $e)
+ {
+ if (Kohana::$environment === Kohana::DEVELOPMENT)
+ {
+ // Just re-throw the exception
+ throw $e;
+ }
+
+ // Log the error
+ Kohana::$log->add(Kohana::ERROR, Kohana::exception_text($e));
+
+ // Create a 404 response
+ $request->status = 404;
+ $request->response = View::factory('template')
+ ->set('title', '404')
+ ->set('content', View::factory('errors/404'));
+ }
+
+ if ($request->send_headers()->response)
+ {
+ // Get the total memory and execution time
+ $total = array(
+ '{memory_usage}' => number_format((memory_get_peak_usage() - KOHANA_START_MEMORY) / 1024, 2).'KB',
+ '{execution_time}' => number_format(microtime(TRUE) - KOHANA_START_TIME, 5).' seconds');
+
+ // Insert the totals into the response
+ $request->response = str_replace(array_keys($total), $total, $request->response);
+ }
+
+
+ /**
+ * Display the request response.
+ */
+ echo $request->response;
+
+
diff --git a/includes/kohana/system/guide/kohana/security/encryption.md b/includes/kohana/system/guide/kohana/security/encryption.md
new file mode 100644
index 0000000..8ba6ecf
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/security/encryption.md
@@ -0,0 +1 @@
+Discuss using encryption, including setting the encryption key in config.
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/security/validation.md b/includes/kohana/system/guide/kohana/security/validation.md
new file mode 100644
index 0000000..94a868a
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/security/validation.md
@@ -0,0 +1,247 @@
+# Validation
+
+*This page needs to be reviewed for accuracy by the development team. Better examples would be helpful.*
+
+Validation can be performed on any array using the [Validate] class. Labels, filters, rules, and callbacks can be attached to a Validate object by the array key, called a "field name".
+
+labels
+: A label is a human-readable version of the field name.
+
+filters
+: A filter modifies the value of an field before rules and callbacks are run.
+
+rules
+: A rule is a check on a field that returns `TRUE` or `FALSE`. If a rule
+ returns `FALSE`, an error will be added to the field.
+
+callbacks
+: A callback is custom method that can access the entire Validate object.
+ The return value of a callback is ignored. Instead, the callback must
+ manually add an error to the object using [Validate::error] on failure.
+
+[!!] Note that [Validate] callbacks and [PHP callbacks](http://php.net/manual/language.pseudo-types.php#language.types.callback) are not the same.
+
+Using `TRUE` as the field name when adding a filter, rule, or callback will by applied to all named fields.
+
+**The [Validate] object will remove all fields from the array that have not been specifically named by a label, filter, rule, or callback. This prevents access to fields that have not been validated as a security precaution.**
+
+Creating a validation object is done using the [Validate::factory] method:
+
+ $post = Validate::factory($_POST);
+
+[!!] The `$post` object will be used for the rest of this tutorial. This tutorial will show you how to validate the registration of a new user.
+
+### Default Rules
+
+Validation also comes with several default rules:
+
+Rule name | Function
+------------------------- |-------------------------------------------------
+[Validate::not_empty] | Value must be a non-empty value
+[Validate::regex] | Match the value against a regular expression
+[Validate::min_length] | Minimum number of characters for value
+[Validate::max_length] | Maximum number of characters for value
+[Validate::exact_length] | Value must be an exact number of characters
+[Validate::email] | An email address is required
+[Validate::email_domain] | Check that the domain of the email exists
+[Validate::url] | Value must be a URL
+[Validate::ip] | Value must be an IP address
+[Validate::phone] | Value must be a phone number
+[Validate::credit_card] | Require a credit card number
+[Validate::date] | Value must be a date (and time)
+[Validate::alpha] | Only alpha characters allowed
+[Validate::alpha_dash] | Only alpha and hyphens allowed
+[Validate::alpha_numeric] | Only alpha and numbers allowed
+[Validate::digit] | Value must be an integer digit
+[Validate::decimal] | Value must be a decimal or float value
+[Validate::numeric] | Only numeric characters allowed
+[Validate::range] | Value must be within a range
+[Validate::color] | Value must be a valid HEX color
+[Validate::matches] | Value matches another field value
+
+[!!] Any method that exists within the [Validate] class may be used as a validation rule without specifying a complete callback. For example, adding `'not_empty'` is the same as `array('Validate', 'not_empty')`.
+
+## Adding Filters
+
+All validation filters are defined as a field name, a method or function (using the [PHP callback](http://php.net/manual/language.pseudo-types.php#language.types.callback) syntax), and an array of parameters:
+
+ $object->filter($field, $callback, $parameter);
+
+Filters modify the field value before it is checked using rules or callbacks.
+
+If we wanted to convert the "username" field to lowercase:
+
+ $post->filter('username', 'strtolower');
+
+If we wanted to remove all leading and trailing whitespace from *all* fields:
+
+ $post->filter(TRUE, 'trim');
+
+## Adding Rules
+
+All validation rules are defined as a field name, a method or function (using the [PHP callback](http://php.net/callback) syntax), and an array of parameters:
+
+ $object->rule($field, $callback, $parameter);
+
+To start our example, we will perform validation on a `$_POST` array that contains user registration information:
+
+ $post = Validate::factory($_POST);
+
+Next we need to process the POST'ed information using [Validate]. To start, we need to add some rules:
+
+ $post
+ ->rule('username', 'not_empty')
+ ->rule('username', 'regex', array('/^[a-z_.]++$/iD'))
+
+ ->rule('password', 'not_empty')
+ ->rule('password', 'min_length', array('6'))
+ ->rule('confirm', 'matches', array('password'))
+
+ ->rule('use_ssl', 'not_empty');
+
+Any existing PHP function can also be used a rule. For instance, if we want to check if the user entered a proper value for the SSL question:
+
+ $post->rule('use_ssl', 'in_array', array(array('yes', 'no')));
+
+Note that all array parameters must still be wrapped in an array! Without the wrapping array, `in_array` would be called as `in_array($value, 'yes', 'no')`, which would result in a PHP error.
+
+Any custom rules can be added using a [PHP callback](http://php.net/manual/language.pseudo-types.php#language.types.callback]:
+
+ $post->rule('username', 'User_Model::unique_username');
+
+[!!] Currently (v3.0.7) it is not possible to use an object for a rule, only static methods and functions.
+
+The method `User_Model::unique_username()` would be defined similar to:
+
+ public static function unique_username($username)
+ {
+ // Check if the username already exists in the database
+ return ! DB::select(array(DB::expr('COUNT(username)'), 'total'))
+ ->from('users')
+ ->where('username', '=', $username)
+ ->execute()
+ ->get('total');
+ }
+
+[!!] Custom rules allow many additional checks to be reused for multiple purposes. These methods will almost always exist in a model, but may be defined in any class.
+
+## Adding callbacks
+
+All validation callbacks are defined as a field name and a method or function (using the [PHP callback](http://php.net/manual/language.pseudo-types.php#language.types.callback) syntax):
+
+ $object->callback($field, $callback);
+
+The user password must be hashed if it validates, so we will hash it using a callback:
+
+ $post->callback('password', array($model, 'hash_password'));
+
+This would assume that the `$model->hash_password()` method would be defined similar to:
+
+ public function hash_password(Validate $array, $field)
+ {
+ if ($array[$field])
+ {
+ // Hash the password if it exists
+ $array[$field] = sha1($array[$field]);
+ }
+ }
+
+# A Complete Example
+
+First, we need a [View] that contains the HTML form, which will be placed in `application/views/user/register.php`:
+
+
+
+ Some errors were encountered, please check the details you entered.
+
+
+
+
+
+
+
+
+
+
+
+
+ Passwords must be at least 6 characters long.
+
+
+
+
+ 'Always', 'no' => 'Only when necessary'), $post['use_ssl']) ?>
+ For security, SSL is always used when making payments.
+
+
+
+
+
+[!!] This example uses the [Form] helper extensively. Using [Form] instead of writing HTML ensures that all of the form inputs will properly handle input that includes HTML characters. If you prefer to write the HTML yourself, be sure to use [HTML::chars] to escape user input.
+
+Next, we need a controller and action to process the registration, which will be placed in `application/classes/controller/user.php`:
+
+ class Controller_User extends Controller {
+
+ public function action_register()
+ {
+ $user = Model::factory('user');
+
+ $post = Validate::factory($_POST)
+ ->filter(TRUE, 'trim')
+
+ ->filter('username', 'strtolower')
+
+ ->rule('username', 'not_empty')
+ ->rule('username', 'regex', array('/^[a-z_.]++$/iD'))
+ ->rule('username', array($user, 'unique_username'))
+
+ ->rule('password', 'not_empty')
+ ->rule('password', 'min_length', array('6'))
+ ->rule('confirm', 'matches', array('password'))
+
+ ->rule('use_ssl', 'not_empty')
+ ->rule('use_ssl', 'in_array', array(array('yes', 'no')))
+
+ ->callback('password', array($user, 'hash_password'));
+
+ if ($post->check())
+ {
+ // Data has been validated, register the user
+ $user->register($post);
+
+ // Always redirect after a successful POST to prevent refresh warnings
+ $this->request->redirect('user/profile');
+ }
+
+ // Validation failed, collect the errors
+ $errors = $post->errors('user');
+
+ // Display the registration form
+ $this->request->response = View::factory('user/register')
+ ->bind('post', $post)
+ ->bind('errors', $errors);
+ }
+
+ }
+
+We will also need a user model, which will be placed in `application/classes/model/user.php`:
+
+ class Model_User extends Model {
+
+ public function register($array)
+ {
+ // Create a new user record in the database
+ $id = DB::insert(array_keys($array))
+ ->values($array)
+ ->execute();
+
+ // Save the new user id to a cookie
+ cookie::set('user', $id);
+
+ return $id;
+ }
+
+ }
+
+That is it, we have a complete user registration example that properly checks user input!
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/security/xss.md b/includes/kohana/system/guide/kohana/security/xss.md
new file mode 100644
index 0000000..dfb315c
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/security/xss.md
@@ -0,0 +1,17 @@
+# Cross-Site Scripting (XSS) Security
+
+*This page is not comprehensive and should not be considered a complete guide to XSS prevention.*
+
+The first step to preventing [XSS](http://wikipedia.org/wiki/Cross-Site_Scripting) attacks is knowing when you need to protect yourself. XSS can only be triggered when it is displayed within HTML content, sometimes via a form input or being displayed from database results. Any global variable that contains client information can be tainted. This includes `$_GET`, `$_POST`, and `$_COOKIE` data.
+
+## Prevention
+
+There are a few simple rules to follow to guard your application HTML against XSS. The first is to use the [Security::xss] method to clean any input data that comes from a global variable. If you do not want HTML in a variable, use [strip_tags](http://php.net/strip_tags) to remove all unwanted HTML tags from a value.
+
+[!!] If you allow users to submit HTML to your application, it is highly recommended to use an HTML cleaning tool such as [HTML Purifier](http://htmlpurifier.org/) or [HTML Tidy](http://php.net/tidy).
+
+The second is to always escape data when inserting into HTML. The [HTML] class provides generators for many common tags, including script and stylesheet links, anchors, images, and email (mailto) links. Any untrusted content should be escaped using [HTML::chars].
+
+## References
+
+* [OWASP XSS Cheat Sheet](http://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet)
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/sessions.md b/includes/kohana/system/guide/kohana/sessions.md
new file mode 100644
index 0000000..d60e709
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/sessions.md
@@ -0,0 +1,167 @@
+# Sessions
+
+Kohana provides classes that make it easy to work with both cookies and sessions. At a high level both sessions and cookies provide the same functionality. They allow the developer to store temporary or persistent information about a specific client for later retrieval, usually to make something persistent between requests.
+
+Sessions should be used for storing temporary or private data. Very sensitive data should be stored using the [Session] class with the "database" or "native" adapters. When using the "cookie" adapter, the session should always be encrypted.
+
+[!!] For more information on best practices with session variables see [the seven deadly sins of sessions](http://lists.nyphp.org/pipermail/talk/2006-December/020358.html).
+
+## Storing, Retrieving, and Deleting Data
+
+[Cookie] and [Session] provide a very similar API for storing data. The main difference between them is that sessions are accessed using an object, and cookies are accessed using a static class.
+
+Accessing the session instance is done using the [Session::instance] method:
+
+ // Get the session instance
+ $session = Session::instance();
+
+When using sessions, you can also get all of the current session data using the [Session::as_array] method:
+
+ // Get all of the session data as an array
+ $data = $session->as_array();
+
+You can also use this to overload the `$_SESSION` global to get and set data in a way more similar to standard PHP:
+
+ // Overload $_SESSION with the session data
+ $_SESSION =& $session->as_array();
+
+ // Set session data
+ $_SESSION[$key] = $value;
+
+### Storing Data
+
+Storing session or cookie data is done using the `set` method:
+
+ // Set session data
+ $session->set($key, $value);
+ // Or
+ Session::instance()->set($key, $value);
+
+ // Store a user id
+ $session->set('user_id', 10);
+
+### Retrieving Data
+
+Getting session or cookie data is done using the `get` method:
+
+ // Get session data
+ $data = $session->get($key, $default_value);
+
+ // Get the user id
+ $user = $session->get('user_id');
+
+### Deleting Data
+
+Deleting session or cookie data is done using the `delete` method:
+
+ // Delete session data
+ $session->delete($key);
+
+
+ // Delete the user id
+ $session->delete('user_id');
+
+## Session Configuration
+
+Always check these settings before making your application live, as many of them will have a direct affect on the security of your application.
+
+## Session Adapters
+
+When creating or accessing an instance of the [Session] class you can decide which session adapter or driver you wish to use. The session adapters that are available to you are:
+
+Native
+: Stores session data in the default location for your web server. The storage location is defined by [session.save_path](http://php.net/manual/session.configuration.php#ini.session.save-path) in `php.ini` or defined by [ini_set](http://php.net/ini_set).
+
+Database
+: Stores session data in a database table using the [Session_Database] class. Requires the [Database] module to be enabled.
+
+Cookie
+: Stores session data in a cookie using the [Cookie] class. **Sessions will have a 4KB limit when using this adapter, and should be encrypted.**
+
+The default adapter can be set by changing the value of [Session::$default]. The default adapter is "native".
+
+To access a Session using the default adapter, simply call [Session::instance()]. To access a Session using something other than the default, pass the adapter name to `instance()`, for example: `Session::instance('cookie')`
+
+
+### Session Adapter Settings
+
+You can apply configuration settings to each of the session adapters by creating a session config file at `APPPATH/config/session.php`. The following sample configuration file defines all the settings for each adapter:
+
+[!!] As with cookies, a "lifetime" setting of "0" means that the session will expire when the browser is closed.
+
+ return array(
+ 'native' => array(
+ 'name' => 'session_name',
+ 'lifetime' => 43200,
+ ),
+ 'cookie' => array(
+ 'name' => 'cookie_name',
+ 'encrypted' => TRUE,
+ 'lifetime' => 43200,
+ ),
+ 'database' => array(
+ 'name' => 'cookie_name',
+ 'encrypted' => TRUE,
+ 'lifetime' => 43200,
+ 'group' => 'default',
+ 'table' => 'table_name',
+ 'columns' => array(
+ 'session_id' => 'session_id',
+ 'last_active' => 'last_active',
+ 'contents' => 'contents'
+ ),
+ 'gc' => 500,
+ ),
+ );
+
+#### Native Adapter
+
+Type | Setting | Description | Default
+----------|-----------|---------------------------------------------------|-----------
+`string` | name | name of the session | `"session"`
+`integer` | lifetime | number of seconds the session should live for | `0`
+
+#### Cookie Adapter
+
+Type | Setting | Description | Default
+----------|-----------|---------------------------------------------------|-----------
+`string` | name | name of the cookie used to store the session data | `"session"`
+`boolean` | encrypted | encrypt the session data using [Encrypt]? | `FALSE`
+`integer` | lifetime | number of seconds the session should live for | `0`
+
+#### Database Adapter
+
+Type | Setting | Description | Default
+----------|-----------|---------------------------------------------------|-----------
+`string` | group | [Database::instance] group name | `"default"`
+`string` | table | table name to store sessions in | `"sessions"`
+`array` | columns | associative array of column aliases | `array`
+`integer` | gc | 1:x chance that garbage collection will be run | `500`
+`string` | name | name of the cookie used to store the session data | `"session"`
+`boolean` | encrypted | encrypt the session data using [Encrypt]? | `FALSE`
+`integer` | lifetime | number of seconds the session should live for | `0`
+
+##### Table Schema
+
+You will need to create the session storage table in the database. This is the default schema:
+
+ CREATE TABLE `sessions` (
+ `session_id` VARCHAR(24) NOT NULL,
+ `last_active` INT UNSIGNED NOT NULL,
+ `contents` TEXT NOT NULL,
+ PRIMARY KEY (`session_id`),
+ INDEX (`last_active`)
+ ) ENGINE = MYISAM;
+
+##### Table Columns
+
+You can change the column names to match an existing database schema when connecting to a legacy session table. The default value is the same as the key value.
+
+session_id
+: the name of the "id" column
+
+last_active
+: UNIX timestamp of the last time the session was updated
+
+contents
+: session data stored as a serialized string, and optionally encrypted
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/tips.md b/includes/kohana/system/guide/kohana/tips.md
new file mode 100644
index 0000000..760232b
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/tips.md
@@ -0,0 +1,33 @@
+# Tips and Common Mistakes
+
+This is a collection of tips and common mistakes or errors you may encounter.
+
+## Never edit the `system` folder!
+
+You should (almost) never edit the system folder. Any change you want to make to files in system and modules can be made via the [cascading filesystem](files) and [transparent extension](extension) and won't break when you try to update your Kohana version.
+
+## Don't try and use one route for everything
+
+Kohana 3 [routes](routing) are very powerful and flexible, don't be afraid to use as many as you need to make your app function the way you want!
+
+## Reflection_Exception
+
+If you get a Reflection_Exception when setting up your site, it is almost certainly because your [Kohana::init] 'base_url' setting is wrong. If your base url is correct something is probably wrong with your [routes](routing).
+
+ ReflectionException [ -1 ]: Class controller_ does not exist
+ // where is part of the url you entered in your browser
+
+### Solution {#reflection-exception-solution}
+
+Set your [Kohana::init] 'base_url' to the correct setting. The base url should be the path to your index.php file relative to the webserver document root.
+
+## ORM/Session __sleep() bug
+
+There is a bug in php which can corrupt your session after a fatal error. A production server shouldn't have uncaught fatal errors, so this bug should only happen during development, when you do something stupid and cause a fatal error. On the next page load you will get a database connection error, then all subsequent page loads will display the following error:
+
+ ErrorException [ Notice ]: Undefined index: id
+ MODPATH/orm/classes/kohana/orm.php [ 1308 ]
+
+### Solution {#orm-session-sleep-solution}
+
+To fix this, clear your cookies for that domain to reset your session. This should never happen on a production server, so you won't have to explain to your clients how to clear their cookies. You can see the [discussion on this issue](http://dev.kohanaframework.org/issues/3242) for more details.
diff --git a/includes/kohana/system/guide/kohana/tutorials.md b/includes/kohana/system/guide/kohana/tutorials.md
new file mode 100644
index 0000000..c921bcd
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/tutorials.md
@@ -0,0 +1,17 @@
+# Tutorials
+
+## Tutorials in this guide
+
+## Tutorials written elsewhere
+
+### Ellisgl's KO3 tutorial on dealtaker.com:
+
+1. [Install and Basic Usage](http://www.dealtaker.com/blog/2009/11/20/kohana-php-3-0-ko3-tutorial-part-1/)
+2. [Views](http://www.dealtaker.com/blog/2009/12/07/kohana-php-3-0-ko3-tutorial-part-2/)
+3. [Controllers](http://www.dealtaker.com/blog/2009/12/30/kohana-php-3-0-ko3-tutorial-part-3/)
+4. [Models](http://www.dealtaker.com/blog/2010/02/01/kohana-php-3-0-ko3-tutorial-part-4/)
+5. [Subrequests](http://www.dealtaker.com/blog/2010/02/25/kohana-php-3-0-ko3-tutorial-part-5/)
+6. [Routes](http://www.dealtaker.com/blog/2010/03/03/kohana-php-3-0-ko3-tutorial-part-6/)
+7. [Helpers](http://www.dealtaker.com/blog/2010/03/26/kohana-php-3-0-ko3-tutorial-part-7/)
+8. [Modules](http://www.dealtaker.com/blog/2010/04/30/kohana-php-3-0-ko3-tutorial-part-8/)
+9. [Vendor Libraries](http://www.dealtaker.com/blog/2010/06/02/kohana-php-3-0-ko3-tutorial-part-9/)
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/tutorials/clean-urls.md b/includes/kohana/system/guide/kohana/tutorials/clean-urls.md
new file mode 100644
index 0000000..4e85966
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/tutorials/clean-urls.md
@@ -0,0 +1,80 @@
+# Clean URLs
+
+Removing `index.php` from your urls.
+
+To keep your URLs clean, you will probably want to be able to access your app without having `/index.php/` in the URL. There are two steps to remove `index.php` from the URL.
+
+1. Edit the bootstrap file
+2. Set up rewriting
+
+## 1. Configure Bootstrap
+
+The first thing you will need to change is the `index_file` setting of [Kohana::init] to false:
+
+ Kohana::init(array(
+ 'base_url' => '/myapp/',
+ 'index_file' => FALSE,
+ ));
+
+This change will make it so all of the links generated using [URL::site], [URL::base], and [HTML::anchor] will no longer include "index.php" in the URL. All generated links will start with `/myapp/` instead of `/myapp/index.php/`.
+
+## 2. URL Rewriting
+
+Enabling rewriting is done differently, depending on your web server.
+
+Rewriting will make it so urls will be passed to index.php.
+
+## Apache
+
+Rename `example.htaccess` to only `.htaccess` and alter the `RewriteBase` line to match the `base_url` setting from your [Kohana::init]
+
+ RewriteBase /myapp/
+
+The rest of the `.htaccess file` rewrites all requests through index.php, unless the file exists on the server (so your css, images, favicon, etc. are still loaded like normal). In most cases, you are done!
+
+### Failed!
+
+If you get a "Internal Server Error" or "No input file specified" error, try changing:
+
+ RewriteRule ^(?:application|modules|system)\b - [F,L]
+
+Instead, we can try a slash:
+
+ RewriteRule ^(application|modules|system)/ - [F,L]
+
+If that doesn't work, try changing:
+
+ RewriteRule .* index.php/$0 [PT]
+
+To something more simple:
+
+ RewriteRule .* index.php [PT]
+
+### Still Failed!
+
+If you are still getting errors, check to make sure that your host supports URL `mod_rewrite`. If you can change the Apache configuration, add these lines to the the configuration, usually `httpd.conf`:
+
+
+ Order allow,deny
+ Allow from all
+ AllowOverride All
+
+
+You should also check your Apache logs to see if they can shed some light on the error.
+
+## NGINX
+
+It is hard to give examples of nginx configuration, but here is a sample for a server:
+
+ location / {
+ index index.php index.html index.htm;
+ try_files $uri index.php;
+ }
+
+ location = index.php {
+ include fastcgi.conf;
+ fastcgi_pass 127.0.0.1:9000;
+ fastcgi_index index.php;
+ }
+
+If you are having issues getting this working, enable debug level logging in nginx and check the access and error logs.
diff --git a/includes/kohana/system/guide/kohana/tutorials/error-pages.md b/includes/kohana/system/guide/kohana/tutorials/error-pages.md
new file mode 100644
index 0000000..088ffe8
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/tutorials/error-pages.md
@@ -0,0 +1,168 @@
+# Friendly Error Pages written by Mathew Davies
+
+By default Kohana 3 doesn't have a method to display friendly error pages like that
+seen in Kohana 2; In this short guide I will teach you how it is done.
+
+## Prerequisites
+
+You will need `'errors' => TRUE` passed to `Kohana::init`. This will convert PHP
+errors into exceptions which are easier to handle.
+
+## 1. A Custom Exception
+
+First off, we are going to need a custom exception class. This is so we can perform different
+actions based on it in the exception handler. I will talk more about this later.
+
+_classes/http\_response\_exception.php_
+
+ add(Kohana::ERROR, Kohana::exception_text($e));
+
+ $attributes = array
+ (
+ 'action' => 500,
+ 'message' => rawurlencode($e->getMessage())
+ );
+
+ if ($e instanceof HTTP_Response_Exception)
+ {
+ $attributes['action'] = $e->getCode();
+ }
+
+ // Error sub-request.
+ echo Request::factory(Route::url('error', $attributes))
+ ->execute()
+ ->send_headers()
+ ->response;
+ }
+ }
+
+If we are in the development environment then pass it off to Kohana otherwise:
+
+* Log the error
+* Set the route action and message attributes.
+* If a `HTTP_Response_Exception` was thrown, then override the action with the error code.
+* Fire off an internal sub-request.
+
+The action will be used as the HTTP response code. By default this is: 500 (internal
+server error) unless a `HTTP_Response_Exception` was thrown.
+
+So this:
+
+ throw new HTTP_Response_Exception(':file does not exist', array(':file' => 'Gaia'), 404);
+
+would display a nice 404 error page, where:
+
+ throw new Kohana_Exception('Directory :dir must be writable',
+ array(':dir' => Kohana::debug_path(Kohana::$cache_dir)));
+
+would display an error 500 page.
+
+**The Route**
+
+ Route::set('error', 'error/(/)', array('action' => '[0-9]++', 'message' => '.+'))
+ ->defaults(array(
+ 'controller' => 'error_handler'
+ ));
+
+## 3. The Error Page Controller
+
+ public function before()
+ {
+ parent::before();
+
+ $this->template->page = URL::site(rawurldecode(Request::$instance->uri));
+
+ // Internal request only!
+ if (Request::$instance !== Request::$current)
+ {
+ if ($message = rawurldecode($this->request->param('message')))
+ {
+ $this->template->message = $message;
+ }
+ }
+ else
+ {
+ $this->request->action = 404;
+ }
+ }
+
+1. Set a template variable "page" so the user can see what they requested. This
+ is for display purposes only.
+2. If an internal request, then set a template variable "message" to be shown to
+ the user.
+3. Otherwise use the 404 action. Users could otherwise craft their own error messages, eg:
+ `error/404/email%20your%20login%20information%20to%20hacker%40google.com`
+
+
+~~~
+public function action_404()
+{
+ $this->template->title = '404 Not Found';
+
+ // Here we check to see if a 404 came from our website. This allows the
+ // webmaster to find broken links and update them in a shorter amount of time.
+ if (isset ($_SERVER['HTTP_REFERER']) AND strstr($_SERVER['HTTP_REFERER'], $_SERVER['SERVER_NAME']) !== FALSE)
+ {
+ // Set a local flag so we can display different messages in our template.
+ $this->template->local = TRUE;
+ }
+
+ // HTTP Status code.
+ $this->request->status = 404;
+}
+
+public function action_503()
+{
+ $this->template->title = 'Maintenance Mode';
+ $this->request->status = 503;
+}
+
+public function action_500()
+{
+ $this->template->title = 'Internal Server Error';
+ $this->request->status = 500;
+}
+~~~
+
+You will notice that each example method is named after the HTTP response code
+and sets the request response code.
+
+## 4. Handling 3rd Party Modules.
+
+Some Kohana modules will make calls to `Kohana::exception_handler`. We can redirect
+calls made to it by extending the Kohana class and passing the exception to our handler.
+
+
+
+Provide links to some git tutorials.
+
+### Creating a New Application
+
+[!!] The following examples assume that your web server is already set up, and you are going to create a new application at .
+
+Using your console, change to the empty directory `gitorial` and run `git init`. This will create the bare structure for a new git repository.
+
+Next, we will create a [submodule](http://www.kernel.org/pub/software/scm/git/docs/git-submodule.html) for the `system` directory. Go to and copy the "Clone URL":
+
+![Github Clone URL](http://img.skitch.com/20091019-rud5mmqbf776jwua6hx9nm1n.png)
+
+Now use the URL to create the submodule for `system`:
+
+ git submodule add git://github.com/kohana/core.git system
+
+[!!] This will create a link to the current development version of the next stable release. The development version should almost always be safe to use, have the same API as the current stable download with bugfixes applied.
+
+Now add whatever submodules you need. For example, if you need the [Database] module:
+
+ git submodule add git://github.com/kohana/database.git modules/database
+
+After submodules are added, they must be initialized:
+
+ git submodule init
+
+Now that the submodules are added, you can commit them:
+
+ git commit -m 'Added initial submodules'
+
+Next, create the application directory structure. This is the bare minimum required:
+
+ mkdir -p application/classes/{controller,model}
+ mkdir -p application/{config,views}
+ mkdir -m 0777 -p application/{cache,logs}
+
+If you run `find application` you should see this:
+
+ application
+ application/cache
+ application/config
+ application/classes
+ application/classes/controller
+ application/classes/model
+ application/logs
+ application/views
+
+We don't want git to track log or cache files, so add a `.gitignore` file to each of the directories. This will ignore all non-hidden files:
+
+ echo '[^.]*' > application/{logs,cache}/.gitignore
+
+[!!] Git ignores empty directories, so adding a `.gitignore` file also makes sure that git will track the directory, but not the files within it.
+
+Now we need the `index.php` and `bootstrap.php` files:
+
+ wget http://github.com/kohana/kohana/raw/master/index.php
+ wget http://github.com/kohana/kohana/raw/master/application/bootstrap.php -O application/bootstrap.php
+
+Commit these changes too:
+
+ git add application
+ git commit -m 'Added initial directory structure'
+
+That's all there is to it. You now have an application that is using Git for versioning.
+
+### Adding Submodules
+To add a new submodule complete the following steps:
+
+1. run the following code - git submodule add repository path for each new submodule e.g.:
+
+ git submodule add git://github.com/shadowhand/sprig.git modules/sprig
+
+2. then init and update the submodules:
+
+ git submodule init
+ git submodule update
+
+### Updating Submodules
+
+At some point you will probably also want to upgrade your submodules. To update all of your submodules to the latest `HEAD` version:
+
+ git submodule foreach 'git checkout master && git pull origin master'
+
+To update a single submodule, for example, `system`:
+
+ cd system
+ git checkout master
+ git pull origin master
+ cd ..
+ git add system
+ git commit -m 'Updated system to latest version'
+
+If you want to update a single submodule to a specific commit:
+
+ cd modules/database
+ git pull origin master
+ git checkout fbfdea919028b951c23c3d99d2bc1f5bbeda0c0b
+ cd ../..
+ git add database
+ git commit -m 'Updated database module'
+
+Note that you can also check out the commit at a tagged official release point, for example:
+
+ git checkout 3.0.6
+
+Simply run `git tag` without arguments to get a list of all tags.
+
+### Removing Submodules
+To remove a submodule that is no longer needed complete the following steps:
+
+1. open .gitmodules and remove the reference to the to submodule
+ It will look something like this:
+
+ [submodule "modules/auth"]
+ path = modules/auth
+ url = git://github.com/kohana/auth.git
+
+2. open .git/config and remove the reference to the to submodule\\
+
+ [submodule "modules/auth"]
+ url = git://github.com/kohana/auth.git
+
+3. run git rm --cached path/to/submodule, e.g.
+
+ git rm --cached modules/auth
+
+**Note:** Do not put a trailing slash at the end of path. If you put a trailing slash at the end of the command, it will fail.
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/tutorials/hello-world.md b/includes/kohana/system/guide/kohana/tutorials/hello-world.md
new file mode 100644
index 0000000..748ad6f
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/tutorials/hello-world.md
@@ -0,0 +1,106 @@
+# Hello, World
+
+Just about every framework ever written has some kind of hello world example included, so it'd be pretty rude of us to break this tradition!
+
+We'll start out by creating a very very basic hello world, and then we'll expand it to follow MVC principles.
+
+## Bare bones
+
+First off we have to make a controller that Kohana can use to handle a request.
+
+Create the file `application/classes/controller/hello.php` in your application folder and fill it out like so:
+
+ template->message = 'hello, world!';
+ }
+ }
+
+`extends Controller_Template`
+: We're now extending the template controller, it makes it more convenient to use views within our controller.
+
+`public $template = 'site';`
+: The template controller needs to know what template you want to use. It'll automatically load the view defined in this variable and assign the view object to it.
+
+`$this->template->message = 'hello, world!';`
+: `$this->template` is a reference to the view object for our site template. What we're doing here is assigning a variable called "message", with a value of "hello, world!" to the view.
+
+Now lets try running our code...
+
+![Hello, World!](hello_world_2_error.png "Hello, World!")
+
+For some reason Kohana's thrown a wobbly and isn't showing our amazing message.
+
+If we look at the error message we can see that the View library wasn't able to find our site template, probably because we haven't made it yet – *doh*!
+
+Let's go and make the view file `application/views/site.php` for our message:
+
+
+
+ We've got a message for you!
+
+
+
+
+ We just wanted to say it! :)
+
+
+
+If we refresh the page then we can see the fruits of our labour:
+
+![hello, world! We just wanted to say it!](hello_world_2.png "hello, world! We just wanted to say it!")
+
+## Stage 3 – Profit!
+
+In this tutorial you've learnt how to create a controller and use a view to separate your logic from your display.
+
+This is obviously a very basic introduction to working with Kohana and doesn't even scrape the potential you have when developing applications with it.
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/tutorials/routes-and-links.md b/includes/kohana/system/guide/kohana/tutorials/routes-and-links.md
new file mode 100644
index 0000000..ec2abe7
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/tutorials/routes-and-links.md
@@ -0,0 +1,3 @@
+
+
+Not sure if this is actually necessary anymore with the [routing page](routing).
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/tutorials/sharing-kohana.md b/includes/kohana/system/guide/kohana/tutorials/sharing-kohana.md
new file mode 100644
index 0000000..8621452
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/tutorials/sharing-kohana.md
@@ -0,0 +1,54 @@
+# Sharing Kohana
+
+Because Kohana follows a [front controller] pattern, which means that all requests are sent to `index.php`, the filesystem is very configurable. Inside of `index.php` you can change the `$application`, `$modules`, and `$system` paths.
+
+[!!] There is a security check at the top of every Kohana file to prevent it from being accessed without using the front controller. Also, the `.htaccess` file should protect those folders as well. Moving the application, modules, and system directories to a location that cannot be accessed vie web can add another layer of security, but is optional.
+
+The `$application` variable lets you set the directory that contains your application files. By default, this is `application`. The `$modules` variable lets you set the directory that contains module files. The `$system` variable lets you set the directory that contains the default Kohana files. You can move these three directories anywhere.
+
+For instance, by default the directories are set up like this:
+
+ www/
+ index.php
+ application/
+ modules/
+ system/
+
+You could move the directories out of the web root so they look like this:
+
+ application/
+ modules/
+ system/
+ www/
+ index.php
+
+Then you would need to change the settings in `index.php` to be:
+
+ $application = '../application';
+ $modules = '../modules';
+ $system = '../system';
+
+## Sharing system and modules
+
+To take this a step further, we could point several kohana apps to the same system and modules folders. For example (and this is just an example, you could arrange these anyway you want):
+
+ apps/
+ foobar/
+ application/
+ www/
+ bazbar/
+ application/
+ www/
+ kohana/
+ 3.0.6/
+ 3.0.7/
+ 3.0.8/
+ modules/
+
+And you would need to change the settings in `index.php` to be:
+
+ $application = '../application';
+ $system = '../../../kohana/3.0.6';
+ $modules = '../../../kohana/modules';
+
+Using this method each app can point to a central copy of kohana, and you can add a new version, and quickly update your apps to point to the new version by editing their `index.php` files.
diff --git a/includes/kohana/system/guide/kohana/tutorials/simple-mvc.md b/includes/kohana/system/guide/kohana/tutorials/simple-mvc.md
new file mode 100644
index 0000000..b3a475d
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/tutorials/simple-mvc.md
@@ -0,0 +1 @@
+Simple example of controller model and view working together.
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/tutorials/templates.md b/includes/kohana/system/guide/kohana/tutorials/templates.md
new file mode 100644
index 0000000..4bfd9c2
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/tutorials/templates.md
@@ -0,0 +1,7 @@
+Making a template driven site.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/tutorials/translation.md b/includes/kohana/system/guide/kohana/tutorials/translation.md
new file mode 100644
index 0000000..12f7cfc
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/tutorials/translation.md
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/includes/kohana/system/guide/kohana/upgrading.md b/includes/kohana/system/guide/kohana/upgrading.md
new file mode 100644
index 0000000..b3b68f4
--- /dev/null
+++ b/includes/kohana/system/guide/kohana/upgrading.md
@@ -0,0 +1,292 @@
+# Upgrading from 2.3.x
+
+*This page needs reviewed for accuracy by the development team.*
+
+Most of Kohana v3 works very differently from Kohana 2.3, here's a list of common gotchas and tips for upgrading.
+
+## Naming conventions
+
+The 2.x series differentiated between different 'types' of class (i.e. controller, model etc.) using suffixes. Folders within model / controller folders didn't have any bearing on the name of the class.
+
+In 3.0 this approach has been scrapped in favour of the Zend framework filesystem conventions, where the name of the class is a path to the class itself, separated by underscores instead of slashes (i.e. `/some/class/file.php` becomes `Some_Class_File`).
+
+See the [conventions documentation](start.conventions) for more information.
+
+## Input Library
+
+The Input Library has been removed from 3.0 in favour of just using `$_GET` and `$_POST`.
+
+### XSS Protection
+
+If you need to XSS clean some user input you can use [Security::xss_clean] to sanitise it, like so:
+
+ $_POST['description'] = security::xss_clean($_POST['description']);
+
+You can also use the [Security::xss_clean] as a filter with the [Validate] library:
+
+ $validation = new Validate($_POST);
+
+ $validate->filter('description', 'Security::xss_clean');
+
+### POST & GET
+
+One of the great features of the Input library was that if you tried to access the value in one of the superglobal arrays and it didn't exist the Input library would return a default value that you could specify i.e.:
+
+ $_GET = array();
+
+ // $id is assigned the value 1
+ $id = Input::instance()->get('id', 1);
+
+ $_GET['id'] = 25;
+
+ // $id is assigned the value 25
+ $id = Input::instance()->get('id', 1);
+
+In 3.0 you can duplicate this functionality using [Arr::get]:
+
+ $_GET = array();
+
+ // $id is assigned the value 1
+ $id = Arr::get($_GET, 'id', 1);
+
+ $_GET['id'] = 42;
+
+ // $id is assigned the value 42
+ $id = Arr::get($_GET, 'id', 1);
+
+## ORM Library
+
+There have been quite a few major changes in ORM since 2.3, here's a list of the more common upgrading problems.
+
+### Member variables
+
+All member variables are now prefixed with an underscore (_) and are no longer accessible via `__get()`. Instead you have to call a function with the name of the property, minus the underscore.
+
+For instance, what was once `loaded` in 2.3 is now `_loaded` and can be accessed from outside the class via `$model->loaded()`.
+
+### Relationships
+
+In 2.3 if you wanted to iterate a model's related objects you could do:
+
+ foreach($model->{relation_name} as $relation)
+
+However, in the new system this won't work. In version 2.3 any queries generated using the Database library were generated in a global scope, meaning that you couldn't try and build two queries simultaneously. Take for example:
+
+# TODO: NEED A DECENT EXAMPLE!!!!
+
+This query would fail as the second, inner query would 'inherit' the conditions of the first one, thus causing pandemonia.
+In v3.0 this has been fixed by creating each query in its own scope, however this also means that some things won't work quite as expected. Take for example:
+
+ foreach(ORM::factory('user', 3)->where('post_date', '>', time() - (3600 * 24))->posts as $post)
+ {
+ echo $post->title;
+ }
+
+[!!] (See [the Database tutorial](tutorials.databases) for the new query syntax)
+
+In 2.3 you would expect this to return an iterator of all posts by user 3 where `post_date` was some time within the last 24 hours, however instead it'll apply the where condition to the user model and return a `Model_Post` with the joining conditions specified.
+
+To achieve the same effect as in 2.3 you need to rearrange the structure slightly:
+
+ foreach(ORM::factory('user', 3)->posts->where('post_date', '>', time() - (36000 * 24))->find_all() as $post)
+ {
+ echo $post->title;
+ }
+
+This also applies to `has_one` relationships:
+
+ // Incorrect
+ $user = ORM::factory('post', 42)->author;
+ // Correct
+ $user = ORM::factory('post', 42)->author->find();
+
+### Has and belongs to many relationships
+
+In 2.3 you could specify `has_and_belongs_to_many` relationships. In 3.0 this functionality has been refactored into `has_many` *through*.
+
+In your models you define a `has_many` relationship to the other model but then you add a `'through' => 'table'` attribute, where `'table'` is the name of your through table. For example (in the context of posts<>categories):
+
+ $_has_many = array
+ (
+ 'categories' => array
+ (
+ 'model' => 'category', // The foreign model
+ 'through' => 'post_categories' // The joining table
+ ),
+ );
+
+If you've set up kohana to use a table prefix then you don't need to worry about explicitly prefixing the table.
+
+### Foreign keys
+
+If you wanted to override a foreign key in 2.x's ORM you had to specify the relationship it belonged to, and your new foreign key in the member variable `$foreign_keys`.
+
+In 3.0 you now define a `foreign_key` key in the relationship's definition, like so:
+
+ Class Model_Post extends ORM
+ {
+ $_belongs_to = array
+ (
+ 'author' => array
+ (
+ 'model' => 'user',
+ 'foreign_key' => 'user_id',
+ ),
+ );
+ }
+
+In this example we should then have a `user_id` field in our posts table.
+
+
+
+In has_many relationships the `far_key` is the field in the through table which links it to the foreign table and the foreign key is the field in the through table which links "this" model's table to the through table.
+
+Consider the following setup, "Posts" have and belong to many "Categories" through `posts_sections`.
+
+| categories | posts_sections | posts |
+|------------|------------------|---------|
+| id | section_id | id |
+| name | post_id | title |
+| | | content |
+
+ Class Model_Post extends ORM
+ {
+ protected $_has_many = array(
+ 'sections' => array(
+ 'model' => 'category',
+ 'through' => 'posts_sections',
+ 'far_key' => 'section_id',
+ ),
+ );
+ }
+
+ Class Model_Category extends ORM
+ {
+ protected $_has_many = array (
+ 'posts' => array(
+ 'model' => 'post',
+ 'through' => 'posts_sections',
+ 'foreign_key' => 'section_id',
+ ),
+ );
+ }
+
+
+Obviously the aliasing setup here is a little crazy, but it's a good example of how the foreign/far key system works.
+
+### ORM Iterator
+
+It's also worth noting that `ORM_Iterator` has now been refactored into `Database_Result`.
+
+If you need to get an array of ORM objects with their keys as the object's pk, you need to call [Database_Result::as_array], e.g.
+
+ $objects = ORM::factory('user')->find_all()->as_array('id');
+
+Where `id` is the user table's primary key.
+
+## Router Library
+
+In version 2 there was a Router library that handled the main request. It let you define basic routes in a `config/routes.php` file and it would allow you to use custom regex for the routes, however it was fairly inflexible if you wanted to do something radical.
+
+## Routes
+
+The routing system (now refered to as the request system) is a lot more flexible in 3.0. Routes are now defined in the bootstrap file (`application/bootstrap.php`) and the module init.php (`modules/module_name/init.php`). It's also worth noting that routes are evaluated in the order that they are defined.
+
+Instead of defining an array of routes you now create a new [Route] object for each route. Unlike in the 2.x series there is no need to map one uri to another. Instead you specify a pattern for a uri, use variables to mark the segments (i.e. controller, method, id).
+
+For example, in 2.x these regexes:
+
+ $config['([a-z]+)/?(\d+)/?([a-z]*)'] = '$1/$3/$1';
+
+Would map the uri `controller/id/method` to `controller/method/id`. In 3.0 you'd use:
+
+ Route::set('reversed','((/(/)))')
+ ->defaults(array('controller' => 'posts', 'action' => 'index'));
+
+[!!] Each uri should have be given a unique name (in this case it's `reversed`), the reasoning behind this is explained in [the url tutorial](tutorials.urls).
+
+Angled brackets denote dynamic sections that should be parsed into variables. Rounded brackets mark an optional section which is not required. If you wanted to only match uris beginning with admin you could use:
+
+ Rouse::set('admin', 'admin(/(/(/)))');
+
+And if you wanted to force the user to specify a controller:
+
+ Route::set('admin', 'admin/(/(/))');
+
+Also, Kohana does not use any 'default defaults'. If you want Kohana to assume your default action is 'index', then you have to tell it so! You can do this via [Route::defaults]. If you need to use custom regex for uri segments then pass an array of `segment => regex` i.e.:
+
+ Route::set('reversed', '((/(/)))', array('id' => '[a-z_]+'))
+ ->defaults(array('controller' => 'posts', 'action' => 'index'))
+
+This would force the `id` value to consist of lowercase alpha characters and underscores.
+
+### Actions
+
+One more thing we need to mention is that methods in a controller that can be accessed via the url are now called "actions", and are prefixed with 'action_'. E.g. in the above example, if the user calls `admin/posts/1/edit` then the action is `edit` but the method called on the controller will be `action_edit`. See [the url tutorial](tutorials.urls) for more info.
+
+## Sessions
+
+There are no longer any Session::set_flash(), Session::keep_flash() or Session::expire_flash() methods, instead you must use [Session::get_once].
+
+## URL Helper
+
+Only a few things have changed with the url helper - `url::redirect()` has been moved into `$this->request->redirect()` within controllers) and `Request::instance()->redirect()` instead.
+
+`url::current` has now been replaced with `$this->request->uri()`
+
+## Valid / Validation
+
+These two classes have been merged into a single class called `Validate`.
+
+The syntax has also changed a little for validating arrays:
+
+ $validate = new Validate($_POST);
+
+ // Apply a filter to all items in the arrays
+ $validate->filter(TRUE, 'trim');
+
+ // To specify rules individually use rule()
+ $validate
+ ->rule('field', 'not_empty')
+ ->rule('field', 'matches', array('another_field'));
+
+ // To set multiple rules for a field use rules(), passing an array of rules => params as the second argument
+ $validate->rules('field', array(
+ 'not_empty' => NULL,
+ 'matches' => array('another_field')
+ ));
+
+The 'required' rule has also been renamed to 'not_empty' for clarity's sake.
+
+## View Library
+
+There have been a few minor changes to the View library which are worth noting.
+
+In 2.3 views were rendered within the scope of the controller, allowing you to use `$this` as a reference to the controller within the view, this has been changed in 3.0. Views now render in an empty scope. If you need to use `$this` in your view you can bind a reference to it using [View::bind]: `$view->bind('this', $this)`.
+
+It's worth noting, though, that this is *very* bad practice as it couples your view to the controller, preventing reuse. The recommended way is to pass the required variables to the view like so:
+
+ $view = View::factory('my/view');
+
+ $view->variable = $this->property;
+
+ // OR if you want to chain this
+
+ $view
+ ->set('variable', $this->property)
+ ->set('another_variable', 42);
+
+ // NOT Recommended
+ $view->bind('this', $this);
+
+Because the view is rendered in an empty scope `Controller::_kohana_load_view` is now redundant. If you need to modify the view before it's rendered (i.e. to add a generate a site-wide menu) you can use [Controller::after].
+
+ Class Controller_Hello extends Controller_Template
+ {
+ function after()
+ {
+ $this->template->menu = '...';
+
+ return parent::after();
+ }
+ }
\ No newline at end of file
diff --git a/includes/kohana/system/i18n/en.php b/includes/kohana/system/i18n/en.php
new file mode 100644
index 0000000..16276f1
--- /dev/null
+++ b/includes/kohana/system/i18n/en.php
@@ -0,0 +1,3 @@
+ 'Español',
+ 'Hello, world!' => '¡Hola, mundo!',
+);
\ No newline at end of file
diff --git a/includes/kohana/system/i18n/fr.php b/includes/kohana/system/i18n/fr.php
new file mode 100644
index 0000000..b8ed57f
--- /dev/null
+++ b/includes/kohana/system/i18n/fr.php
@@ -0,0 +1,7 @@
+ 'Français',
+ 'Hello, world!' => 'Bonjour, monde!',
+);
\ No newline at end of file
diff --git a/includes/kohana/system/media/guide/kohana/cascading_filesystem.png b/includes/kohana/system/media/guide/kohana/cascading_filesystem.png
new file mode 100644
index 0000000..52a3576
Binary files /dev/null and b/includes/kohana/system/media/guide/kohana/cascading_filesystem.png differ
diff --git a/includes/kohana/system/media/guide/kohana/hello_world_1.png b/includes/kohana/system/media/guide/kohana/hello_world_1.png
new file mode 100644
index 0000000..e4ea75d
Binary files /dev/null and b/includes/kohana/system/media/guide/kohana/hello_world_1.png differ
diff --git a/includes/kohana/system/media/guide/kohana/hello_world_2.png b/includes/kohana/system/media/guide/kohana/hello_world_2.png
new file mode 100644
index 0000000..9eb10f5
Binary files /dev/null and b/includes/kohana/system/media/guide/kohana/hello_world_2.png differ
diff --git a/includes/kohana/system/media/guide/kohana/hello_world_2_error.png b/includes/kohana/system/media/guide/kohana/hello_world_2_error.png
new file mode 100644
index 0000000..244c38e
Binary files /dev/null and b/includes/kohana/system/media/guide/kohana/hello_world_2_error.png differ
diff --git a/includes/kohana/system/media/guide/kohana/install.png b/includes/kohana/system/media/guide/kohana/install.png
new file mode 100644
index 0000000..d4ea788
Binary files /dev/null and b/includes/kohana/system/media/guide/kohana/install.png differ
diff --git a/includes/kohana/system/media/guide/kohana/welcome.png b/includes/kohana/system/media/guide/kohana/welcome.png
new file mode 100755
index 0000000..0d7ac3d
Binary files /dev/null and b/includes/kohana/system/media/guide/kohana/welcome.png differ
diff --git a/includes/kohana/system/messages/validate.php b/includes/kohana/system/messages/validate.php
new file mode 100644
index 0000000..f76cf62
--- /dev/null
+++ b/includes/kohana/system/messages/validate.php
@@ -0,0 +1,27 @@
+ ':field must contain only letters',
+ 'alpha_dash' => ':field must contain only numbers, letters and dashes',
+ 'alpha_numeric' => ':field must contain only letters and numbers',
+ 'color' => ':field must be a color',
+ 'credit_card' => ':field must be a credit card number',
+ 'date' => ':field must be a date',
+ 'decimal' => ':field must be a decimal with :param1 places',
+ 'digit' => ':field must be a digit',
+ 'email' => ':field must be a email address',
+ 'email_domain' => ':field must contain a valid email domain',
+ 'equals' => ':field must equal :param1',
+ 'exact_length' => ':field must be exactly :param1 characters long',
+ 'in_array' => ':field must be one of the available options',
+ 'ip' => ':field must be an ip address',
+ 'matches' => ':field must be the same as :param1',
+ 'min_length' => ':field must be at least :param1 characters long',
+ 'max_length' => ':field must be less than :param1 characters long',
+ 'not_empty' => ':field must not be empty',
+ 'numeric' => ':field must be numeric',
+ 'phone' => ':field must be a phone number',
+ 'range' => ':field must be within the range of :param1 to :param2',
+ 'regex' => ':field does not match the required format',
+ 'url' => ':field must be a url',
+);
\ No newline at end of file
diff --git a/includes/kohana/system/tests/kohana/ArrTest.php b/includes/kohana/system/tests/kohana/ArrTest.php
new file mode 100644
index 0000000..a1e06e3
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/ArrTest.php
@@ -0,0 +1,500 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+Class Kohana_ArrTest extends Kohana_Unittest_TestCase
+{
+ /**
+ * Provides test data for test_callback()
+ *
+ * @return array
+ */
+ public function provider_callback()
+ {
+ return array(
+ // Tests....
+ // That no parameters returns null
+ array('function', array('function', NULL)),
+ // That we can get an array of parameters values
+ array('function(1,2,3)', array('function', array('1', '2', '3'))),
+ // That it's not just using the callback "function"
+ array('different_name(harry,jerry)', array('different_name', array('harry', 'jerry'))),
+ // That static callbacks are parsed into arrays
+ array('kohana::appify(this)', array(array('kohana', 'appify'), array('this'))),
+ // Spaces are preserved in parameters
+ array('deal::make(me, my mate )', array(array('deal', 'make'), array('me', ' my mate ')))
+ // TODO: add more cases
+ );
+ }
+
+ /**
+ * Tests Arr::callback()
+ *
+ * @test
+ * @dataProvider provider_callback
+ * @param string $str String to parse
+ * @param array $expected Callback and its parameters
+ */
+ public function test_callback($str, $expected)
+ {
+ $result = Arr::callback($str);
+
+ $this->assertSame(2, count($result));
+ $this->assertSame($expected, $result);
+ }
+
+ /**
+ * Provides test data for test_extract
+ *
+ * @return array
+ */
+ public function provider_extract()
+ {
+ return array(
+ array(
+ array('kohana' => 'awesome', 'blueflame' => 'was'),
+ array('kohana', 'cakephp', 'symfony'),
+ NULL,
+ array('kohana' => 'awesome', 'cakephp' => NULL, 'symfony' => NULL)
+ ),
+ // I realise noone should EVER code like this in real life,
+ // but unit testing is very very very very boring
+ array(
+ array('chocolate cake' => 'in stock', 'carrot cake' => 'in stock'),
+ array('carrot cake', 'humble pie'),
+ 'not in stock',
+ array('carrot cake' => 'in stock', 'humble pie' => 'not in stock'),
+ ),
+ );
+ }
+
+ /**
+ * Tests Arr::extract()
+ *
+ * @test
+ * @dataProvider provider_extract
+ * @param array $array
+ * @param array $keys
+ * @param mixed $default
+ * @param array $expected
+ */
+ public function test_extract(array $array, array $keys, $default, $expected)
+ {
+ $array = Arr::extract($array, $keys, $default);
+
+ $this->assertSame(count($expected), count($array));
+ $this->assertSame($expected, $array);
+ }
+
+
+ /**
+ * Provides test data for test_get()
+ *
+ * @return array
+ */
+ public function provider_get()
+ {
+ return array(
+ array(array('uno', 'dos', 'tress'), 1, NULL, 'dos'),
+ array(array('we' => 'can', 'make' => 'change'), 'we', NULL, 'can'),
+
+ array(array('uno', 'dos', 'tress'), 10, NULL, NULL),
+ array(array('we' => 'can', 'make' => 'change'), 'he', NULL, NULL),
+ array(array('we' => 'can', 'make' => 'change'), 'he', 'who', 'who'),
+ array(array('we' => 'can', 'make' => 'change'), 'he', array('arrays'), array('arrays')),
+ );
+ }
+
+ /**
+ * Tests Arr::get()
+ *
+ * @test
+ * @dataProvider provider_get()
+ * @param array $array Array to look in
+ * @param string|integer $key Key to look for
+ * @param mixed $default What to return if $key isn't set
+ * @param mixed $expected The expected value returned
+ */
+ public function test_get(array $array, $key, $default, $expected)
+ {
+ $this->assertSame(
+ $expected,
+ Arr::get($array, $key, $default)
+ );
+ }
+
+ /**
+ * Provides test data for test_is_assoc()
+ *
+ * @return array
+ */
+ public function provider_is_assoc()
+ {
+ return array(
+ array(array('one', 'two', 'three'), FALSE),
+ array(array('one' => 'o clock', 'two' => 'o clock', 'three' => 'o clock'), TRUE),
+ );
+ }
+
+ /**
+ * Tests Arr::is_assoc()
+ *
+ * @test
+ * @dataProvider provider_is_assoc
+ * @param array $array Array to check
+ * @param boolean $expected Is $array assoc
+ */
+ public function test_is_assoc(array $array, $expected)
+ {
+ $this->assertSame(
+ $expected,
+ Arr::is_assoc($array)
+ );
+ }
+
+ /**
+ * Provides test data for test_is_array()
+ *
+ * @return array
+ */
+ public function provider_is_array()
+ {
+ return array(
+ array($a = array('one', 'two', 'three'), TRUE),
+ array(new ArrayObject($a), TRUE),
+ array(new ArrayIterator($a), TRUE),
+ array('not an array', FALSE),
+ array(new stdClass, FALSE),
+ );
+ }
+
+ /**
+ * Tests Arr::is_array()
+ *
+ * @test
+ * @dataProvider provider_is_array
+ * @param mixed $value Value to check
+ * @param boolean $expected Is $value an array?
+ */
+ public function test_is_array($array, $expected)
+ {
+ $this->assertSame(
+ $expected,
+ Arr::is_array($array)
+ );
+ }
+
+ public function provider_merge()
+ {
+ return array(
+ // Test how it merges arrays and sub arrays with assoc keys
+ array(
+ array('name' => 'mary', 'children' => array('fred', 'paul', 'sally', 'jane')),
+ array('name' => 'john', 'children' => array('fred', 'paul', 'sally', 'jane')),
+ array('name' => 'mary', 'children' => array('jane')),
+ ),
+ // See how it merges sub-arrays with numerical indexes
+ array(
+ array(array('test1','test3'), array('test2','test4')),
+ array(array('test1'), array('test2')),
+ array(array('test3'), array('test4')),
+ ),
+ array(
+ array('digits' => array(0, 1, 2, 3)),
+ array('digits' => array(0, 1)),
+ array('digits' => array(2, 3)),
+ ),
+ // See how it manages merging items with numerical indexes
+ array(
+ array(0, 1, 2, 3),
+ array(0, 1),
+ array(2, 3),
+ ),
+ // Try and get it to merge assoc. arrays recursively
+ array(
+ array('foo' => 'bar', array('temp' => 'life')),
+ array('foo' => 'bin', array('temp' => 'name')),
+ array('foo' => 'bar', array('temp' => 'life')),
+ ),
+ // Bug #3139
+ array(
+ array('foo' => array('bar')),
+ array('foo' => 'bar'),
+ array('foo' => array('bar')),
+ ),
+ array(
+ array('foo' => 'bar'),
+ array('foo' => array('bar')),
+ array('foo' => 'bar'),
+ ),
+ );
+ }
+
+ /**
+ *
+ * @test
+ * @dataProvider provider_merge
+ */
+ public function test_merge($expected, $array1, $array2)
+ {
+ $this->assertSame(
+ $expected,
+ Arr::merge($array1,$array2)
+ );
+ }
+
+ /**
+ * Provides test data for test_path()
+ *
+ * @return array
+ */
+ public function provider_path()
+ {
+ $array = array(
+ 'foobar' => array('definition' => 'lost'),
+ 'kohana' => 'awesome',
+ 'users' => array(
+ 1 => array('name' => 'matt'),
+ 2 => array('name' => 'john', 'interests' => array('hocky' => array('length' => 2), 'football' => array())),
+ 3 => 'frank', // Issue #3194
+ ),
+ 'object' => new ArrayObject(array('iterator' => TRUE)), // Iterable object should work exactly the same
+ );
+
+ return array(
+ // Tests returns normal values
+ array($array['foobar'], $array, 'foobar'),
+ array($array['kohana'], $array, 'kohana'),
+ array($array['foobar']['definition'], $array, 'foobar.definition'),
+ // Custom delimiters
+ array($array['foobar']['definition'], $array, 'foobar/definition', NULL, '/'),
+ // We should be able to use NULL as a default, returned if the key DNX
+ array(NULL, $array, 'foobar.alternatives', NULL),
+ array(NULL, $array, 'kohana.alternatives', NULL),
+ // Try using a string as a default
+ array('nothing', $array, 'kohana.alternatives', 'nothing'),
+ // Make sure you can use arrays as defaults
+ array(array('far', 'wide'), $array, 'cheese.origins', array('far', 'wide')),
+ // Ensures path() casts ints to actual integers for keys
+ array($array['users'][1]['name'], $array, 'users.1.name'),
+ // Test that a wildcard returns the entire array at that "level"
+ array($array['users'], $array, 'users.*'),
+ // Now we check that keys after a wilcard will be processed
+ array(array(0 => array(0 => 2)), $array, 'users.*.interests.*.length'),
+ // See what happens when it can't dig any deeper from a wildcard
+ array(NULL, $array, 'users.*.fans'),
+ // Starting wildcards, issue #3269
+ array(array('matt', 'john'), $array['users'], '*.name'),
+ // Path as array, issue #3260
+ array($array['users'][2]['name'], $array, array('users', 2, 'name')),
+ array($array['object']['iterator'], $array, 'object.iterator'),
+ );
+ }
+
+ /**
+ * Tests Arr::path()
+ *
+ * @test
+ * @dataProvider provider_path
+ * @param string $path The path to follow
+ * @param mixed $default The value to return if dnx
+ * @param boolean $expected The expected value
+ * @param string $delimiter The path delimiter
+ */
+ public function test_path($expected, $array, $path, $default = NULL, $delimiter = NULL)
+ {
+ $this->assertSame(
+ $expected,
+ Arr::path($array, $path, $default, $delimiter)
+ );
+ }
+
+ /**
+ * Provides test data for test_range()
+ *
+ * @return array
+ */
+ public function provider_range()
+ {
+ return array(
+ array(1, 2),
+ array(1, 100),
+ array(25, 10),
+ );
+ }
+
+ /**
+ * Tests Arr::range()
+ *
+ * @dataProvider provider_range
+ * @param integer $step The step between each value in the array
+ * @param integer $max The max value of the range (inclusive)
+ */
+ public function test_range($step, $max)
+ {
+ $range = Arr::range($step, $max);
+
+ $this->assertSame((int) floor($max / $step), count($range));
+
+ $current = $step;
+
+ foreach($range as $key => $value)
+ {
+ $this->assertSame($key, $value);
+ $this->assertSame($current, $key);
+ $this->assertLessThanOrEqual($max, $key);
+ $current += $step;
+ }
+ }
+
+ /**
+ * Provides test data for test_unshift()
+ *
+ * @return array
+ */
+ public function provider_unshift()
+ {
+ return array(
+ array(array('one' => '1', 'two' => '2',), 'zero', '0'),
+ array(array('step 1', 'step 2', 'step 3'), 'step 0', 'wow')
+ );
+ }
+
+ /**
+ * Tests Arr::unshift()
+ *
+ * @test
+ * @dataProvider provider_unshift
+ * @param array $array
+ * @param string $key
+ * @param mixed $value
+ */
+ public function test_unshift(array $array, $key, $value)
+ {
+ $original = $array;
+
+ Arr::unshift($array, $key, $value);
+
+ $this->assertNotSame($original, $array);
+ $this->assertSame(count($original) + 1, count($array));
+ $this->assertArrayHasKey($key, $array);
+
+ $this->assertSame($value, reset($array));
+ $this->assertSame(key($array), $key);
+ }
+
+ /**
+ * Provies test data for test_overwrite
+ *
+ * @return array Test Data
+ */
+ public function provider_overwrite()
+ {
+ return array(
+ array(
+ array('name' => 'Henry', 'mood' => 'tired', 'food' => 'waffles', 'sport' => 'checkers'),
+ array('name' => 'John', 'mood' => 'bored', 'food' => 'bacon', 'sport' => 'checkers'),
+ array('name' => 'Matt', 'mood' => 'tired', 'food' => 'waffles'),
+ array('name' => 'Henry', 'age' => 18,),
+ ),
+ );
+ }
+
+ /**
+ *
+ * @test
+ * @dataProvider provider_overwrite
+ */
+ public function test_overwrite($expected, $arr1, $arr2, $arr3 = array(), $arr4 = array())
+ {
+ $this->assertSame(
+ $expected,
+ Arr::overwrite($arr1, $arr2, $arr3, $arr4)
+ );
+ }
+
+ /**
+ * Provides test data for test_binary_search
+ *
+ * @return array Test Data
+ */
+ public function provider_binary_search()
+ {
+ return array(
+ array(2, 'john', array('mary', 'louise', 'john', 'kent'))
+ );
+ }
+
+ /**
+ *
+ * @test
+ * @dataProvider provider_binary_search
+ */
+ public function test_binary_search($expected, $needle, $haystack, $sort = FALSE)
+ {
+ $this->assertSame(
+ $expected,
+ Arr::binary_search($needle, $haystack, $sort)
+ );
+ }
+
+ /**
+ * Provides test data for test_map
+ *
+ * @return array Test Data
+ */
+ public function provider_map()
+ {
+ return array(
+ array('strip_tags', array('foobar
'), array('foobar')),
+ array('strip_tags', array(array('foobar
'), array('foobar
')), array(array('foobar'), array('foobar'))),
+ );
+ }
+
+ /**
+ *
+ * @test
+ * @dataProvider provider_map
+ */
+ public function test_map($method, $source, $expected)
+ {
+ $this->assertSame(
+ $expected,
+ Arr::map($method, $source)
+ );
+ }
+
+ /**
+ * Provides test data for test_flatten
+ *
+ * @return array Test Data
+ */
+ public function provider_flatten()
+ {
+ return array(
+ array(array('set' => array('one' => 'something'), 'two' => 'other'), array('one' => 'something', 'two' => 'other')),
+ );
+ }
+
+ /**
+ *
+ * @test
+ * @dataProvider provider_flatten
+ */
+ public function test_flatten($source, $expected)
+ {
+ $this->assertSame(
+ $expected,
+ Arr::flatten($source)
+ );
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/CLITest.php b/includes/kohana/system/tests/kohana/CLITest.php
new file mode 100644
index 0000000..b7e1a75
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/CLITest.php
@@ -0,0 +1,167 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+Class Kohana_CLITest extends Kohana_Unittest_TestCase
+{
+
+ /**
+ * Tell PHPUnit to isolate globals during tests
+ * @var boolean
+ */
+ protected $backupGlobals = TRUE;
+
+ /**
+ * An array of arguments to put in $_SERVER['argv']
+ * @var array
+ */
+ protected $options = array(
+ '--uri' => 'test/something',
+ '--we_are_cool',
+ 'invalid option',
+ '--version' => '2.23',
+ '--important' => 'something=true',
+ '--name' => 'Jeremy Taylor',
+ );
+
+ /**
+ * Setup the enviroment for each test
+ *
+ * PHPUnit automatically backups up & restores global variables
+ */
+ public function setUp()
+ {
+ parent::setUp();
+
+ $_SERVER['argv'] = array('index.php');
+
+ foreach($this->options as $option => $value)
+ {
+ if(is_string($option))
+ {
+ $_SERVER['argv'][] = $option.'='.$value;
+ }
+ else
+ {
+ $_SERVER['argv'][] = $value;
+ }
+ }
+
+ $_SERVER['argc'] = count($_SERVER['argv']);
+ }
+
+ /**
+ * If for some reason arc != count(argv) then we need
+ * to fail gracefully.
+ *
+ * This test ensures it will
+ *
+ * @test
+ */
+ public function test_only_loops_over_available_arguments()
+ {
+ ++$_SERVER['argc'];
+
+ $options = CLI::options('uri');
+
+ $this->assertSame(1, count($options));
+ }
+
+ /**
+ * Options should only parse arguments requested
+ *
+ * @test
+ */
+ public function test_only_parses_wanted_arguments()
+ {
+ $options = CLI::options('uri');
+
+ $this->assertSame(1, count($options));
+
+ $this->assertArrayHasKey('uri', $options);
+ $this->assertSame($options['uri'], $this->options['--uri']);
+ }
+
+ /**
+ * Options should not parse invalid arguments (i.e. not starting with --_
+ *
+ * @test
+ */
+ public function test_does_not_parse_invalid_arguments()
+ {
+ $options = CLI::options('uri', 'invalid');
+
+ $this->assertSame(1, count($options));
+ $this->assertArrayHasKey('uri', $options);
+ $this->assertArrayNotHasKey('invalid', $options);
+ }
+
+ /**
+ * Options should parse multiple arguments & values correctly
+ *
+ * @test
+ */
+ public function test_parses_multiple_arguments()
+ {
+ $options = CLI::options('uri', 'version');
+
+ $this->assertSame(2, count($options));
+ $this->assertArrayHasKey('uri', $options);
+ $this->assertArrayHasKey('version', $options);
+ $this->assertSame($this->options['--uri'], $options['uri']);
+ $this->assertSame($this->options['--version'], $options['version']);
+ }
+
+ /**
+ * Options should parse arguments without specified values as NULL
+ *
+ * @test
+ */
+ public function test_parses_arguments_without_value_as_null()
+ {
+ $options = CLI::options('uri', 'we_are_cool');
+
+ $this->assertSame(2, count($options));
+ $this->assertSame(NULL, $options['we_are_cool']);
+ }
+
+ /**
+ * If the argument contains an equals sign then it shouldn't be split
+ *
+ * @test
+ * @ticket 2642
+ */
+ public function test_cli_only_splits_on_the_first_equals()
+ {
+ $options = CLI::options('important');
+
+ $this->assertSame(1, count($options));
+ $this->assertSame('something=true', reset($options));
+ }
+
+ /**
+ * Arguments enclosed with quote marks should be allowed to contain
+ * spaces
+ *
+ * @test
+ */
+ public function test_value_includes_spaces_when_enclosed_with_quotes()
+ {
+ $options = CLI::options('name');
+
+ $this->assertSame(array('name' => 'Jeremy Taylor'), $options);
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/ConfigTest.php b/includes/kohana/system/tests/kohana/ConfigTest.php
new file mode 100644
index 0000000..6d101b0
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/ConfigTest.php
@@ -0,0 +1,244 @@
+
+ * @author Matt Button
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+Class Kohana_ConfigTest extends Kohana_Unittest_TestCase
+{
+
+ /**
+ * Calling Kohana_Config::instance() should return the global singleton
+ * which should persist
+ *
+ * @test
+ * @covers Kohana_Config::instance
+ */
+ public function test_instance_returns_singleton_instance()
+ {
+ $this->assertSame(Kohana_Config::instance(), Kohana_Config::instance());
+ $this->assertNotSame(new Kohana_Config, Kohana_Config::instance());
+ }
+
+ /**
+ * When a config object is initially created there should be
+ * no readers attached
+ *
+ * @test
+ * @covers Kohana_Config
+ */
+ public function test_initially_there_are_no_readers()
+ {
+ $config = new Kohana_Config;
+
+ $this->assertAttributeSame(array(), '_readers', $config);
+ }
+
+ /**
+ * Test that calling attach() on a kohana config object
+ * adds the specified reader to the config object
+ *
+ * @test
+ * @covers Kohana_Config::attach
+ */
+ public function test_attach_adds_reader_and_returns_this()
+ {
+ $config = new Kohana_Config;
+ $reader = $this->getMock('Kohana_Config_Reader');
+
+ $this->assertSame($config, $config->attach($reader));
+
+ $this->assertAttributeContains($reader, '_readers', $config);
+ }
+
+ /**
+ * By default (or by passing TRUE as the second parameter) the config object
+ * should prepend the reader to the front of the readers queue
+ *
+ * @test
+ * @covers Kohana_Config::attach
+ */
+ public function test_attach_adds_reader_to_front_of_queue()
+ {
+ $config = new Kohana_Config;
+
+ $reader1 = $this->getMock('Kohana_Config_Reader');
+ $reader2 = $this->getMock('Kohana_Config_Reader');
+
+ $config->attach($reader1);
+ $config->attach($reader2);
+
+ // Rather than do two assertContains we'll do an assertSame to assert
+ // the order of the readers
+ $this->assertAttributeSame(array($reader2, $reader1), '_readers', $config);
+
+ // Now we test using the second parameter
+ $config = new Kohana_Config;
+
+ $config->attach($reader1);
+ $config->attach($reader2, TRUE);
+
+ $this->assertAttributeSame(array($reader2, $reader1), '_readers', $config);
+ }
+
+ /**
+ * Test that attaching a new reader (and passing FALSE as second param) causes
+ * phpunit to append the reader rather than prepend
+ *
+ * @test
+ * @covers Kohana_Config::attach
+ */
+ public function test_attach_can_add_reader_to_end_of_queue()
+ {
+ $config = new Kohana_Config;
+ $reader1 = $this->getMock('Kohana_Config_Reader');
+ $reader2 = $this->getMock('Kohana_Config_Reader');
+
+ $config->attach($reader1);
+ $config->attach($reader2, FALSE);
+
+ $this->assertAttributeSame(array($reader1, $reader2), '_readers', $config);
+ }
+
+ /**
+ * Calling detach() on a config object should remove it from the queue of readers
+ *
+ * @test
+ * @covers Kohana_Config::detach
+ */
+ public function test_detach_removes_reader_and_returns_this()
+ {
+ $config = new Kohana_Config;
+
+ // Due to the way phpunit mock generator works if you try and mock a class
+ // that has already been used then it just re-uses the first's name
+ //
+ // To get around this we have to specify a totally random name for the second mock object
+ $reader1 = $this->getMock('Kohana_Config_Reader');
+ $reader2 = $this->getMock('Kohana_Config_Reader', array(), array(), 'MY_AWESOME_READER');
+
+ $config->attach($reader1);
+ $config->attach($reader2);
+
+ $this->assertSame($config, $config->detach($reader1));
+
+ $this->assertAttributeNotContains($reader1, '_readers', $config);
+ $this->assertAttributeContains($reader2, '_readers', $config);
+
+ $this->assertSame($config, $config->detach($reader2));
+
+ $this->assertAttributeNotContains($reader2, '_readers', $config);
+ }
+
+ /**
+ * detach() should return $this even if the specified reader does not exist
+ *
+ * @test
+ * @covers Kohana_Config::detach
+ */
+ public function test_detach_returns_this_even_when_reader_dnx()
+ {
+ $config = new Kohana_Config;
+ $reader = $this->getMock('Kohana_Config_Reader');
+
+ $this->assertSame($config, $config->detach($reader));
+ }
+
+ /**
+ * If load() is called and there are no readers present then it should throw
+ * a kohana exception
+ *
+ * @test
+ * @covers Kohana_Config::load
+ * @expectedException Kohana_Exception
+ */
+ public function test_load_throws_exception_if_there_are_no_readers()
+ {
+ // The following code should throw an exception and phpunit will catch / handle it
+ // (see the @expectedException doccomment)
+ $config = new Kohana_config;
+
+ $config->load('random');
+ }
+
+ /**
+ * When load() is called it should interrogate each reader in turn until a match
+ * is found
+ *
+ * @test
+ * @covers Kohana_Config::load
+ */
+ public function test_load_interrogates_each_reader_until_group_found()
+ {
+ $config = new Kohana_Config;
+ $config_group = 'groupy';
+
+ $reader1 = $this->getMock('Kohana_Config_Reader', array('load'));
+ $reader1
+ ->expects($this->once())
+ ->method('load')
+ ->with($config_group)
+ ->will($this->returnValue(FALSE));
+
+ $reader2 = $this->getMock('Kohana_Config_Reader', array('load'));
+ $reader2
+ ->expects($this->once())
+ ->method('load')
+ ->with($config_group)
+ ->will($this->returnValue($reader2));
+
+ $reader3 = $this->getMock('Kohana_Config_Reader', array('load'));
+ $reader3->expects($this->never())->method('load');
+
+ $config->attach($reader1, FALSE);
+ $config->attach($reader2, FALSE);
+ $config->attach($reader3, FALSE);
+
+ // By asserting a return type we're making the test a little less brittle / less likely
+ // to break due to minor modifications
+ $this->assertInstanceOf('Kohana_Config_Reader', $config->load($config_group));
+ }
+
+ /**
+ * Calling load() with a group that doesn't exist, should get it to use the last reader
+ * to create a new config group
+ *
+ * @test
+ * @covers Kohana_Config::load
+ */
+ public function test_load_returns_new_config_group_if_one_dnx()
+ {
+ $config = new Kohana_Config;
+ $group = 'my_group';
+
+ $reader1 = $this->getMock('Kohana_Config_Reader');
+ $reader2 = $this->getMock('Kohana_Config_Reader', array('load'), array(), 'Kohana_Config_Waffles');
+
+ // This is a slightly hacky way of doing it, but it works
+ $reader2
+ ->expects($this->exactly(2))
+ ->method('load')
+ ->with($group)
+ ->will($this->onConsecutiveCalls(
+ $this->returnValue(FALSE),
+ $this->returnValue(clone $reader2)
+ ));
+
+ $config->attach($reader1)->attach($reader2);
+
+ $new_config = $config->load('my_group');
+
+ $this->assertInstanceOf('Kohana_Config_Waffles', $new_config);
+
+ // Slightly taboo, testing a different api!!
+ $this->assertSame(array(), $new_config->as_array());
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/CookieTest.php b/includes/kohana/system/tests/kohana/CookieTest.php
new file mode 100644
index 0000000..c5c553e
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/CookieTest.php
@@ -0,0 +1,130 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+Class Kohana_CookieTest extends Kohana_Unittest_TestCase
+{
+ /**
+ * Provides test data for test_set()
+ *
+ * @return array
+ */
+ public function provider_set()
+ {
+ return array(
+ array('foo', 'bar', NULL, TRUE),
+ array('foo', 'bar', 10, TRUE),
+ );
+ }
+
+ /**
+ * Tests cookie::set()
+ *
+ * @test
+ * @dataProvider provider_set
+ * @covers cookie::set
+ * @param mixed $key key to use
+ * @param mixed $value value to set
+ * @param mixed $exp exp to set
+ * @param boolean $expected Output for cookie::set()
+ */
+ public function test_set($key, $value, $exp, $expected)
+ {
+ $this->assertSame($expected, cookie::set($key, $value, $exp));
+ }
+
+ /**
+ * Provides test data for test_get()
+ *
+ * @return array
+ */
+ public function provider_get()
+ {
+ return array(
+ array('foo', Cookie::salt('foo', 'bar').'~bar', 'bar'),
+ array('bar', Cookie::salt('foo', 'bar').'~bar', NULL),
+ array(NULL, Cookie::salt('foo', 'bar').'~bar', NULL),
+ );
+ }
+
+ /**
+ * Tests cookie::set()
+ *
+ * @test
+ * @dataProvider provider_get
+ * @covers cookie::get
+ * @param mixed $key key to use
+ * @param mixed $value value to set
+ * @param boolean $expected Output for cookie::get()
+ */
+ public function test_get($key, $value, $expected)
+ {
+ // Force $_COOKIE
+ if ($key !== NULL)
+ $_COOKIE[$key] = $value;
+
+ $this->assertSame($expected, cookie::get($key));
+ }
+
+ /**
+ * Provides test data for test_delete()
+ *
+ * @return array
+ */
+ public function provider_delete()
+ {
+ return array(
+ array('foo', TRUE),
+ );
+ }
+
+ /**
+ * Tests cookie::delete()
+ *
+ * @test
+ * @dataProvider provider_delete
+ * @covers cookie::delete
+ * @param mixed $key key to use
+ * @param boolean $expected Output for cookie::delete()
+ */
+ public function test_delete($key, $expected)
+ {
+ $this->assertSame($expected, cookie::delete($key));
+ }
+
+ /**
+ * Provides test data for test_salt()
+ *
+ * @return array
+ */
+ public function provider_salt()
+ {
+ return array(
+ array('foo', 'bar', '795317c9df04d8061e6e134a9b3487dc9ad69117'),
+ );
+ }
+
+ /**
+ * Tests cookie::salt()
+ *
+ * @test
+ * @dataProvider provider_salt
+ * @covers cookie::salt
+ * @param mixed $key key to use
+ * @param mixed $value value to salt with
+ * @param boolean $expected Output for cookie::delete()
+ */
+ public function test_salt($key, $value, $expected)
+ {
+ $this->assertSame($expected, cookie::salt($key, $value));
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/CoreTest.php b/includes/kohana/system/tests/kohana/CoreTest.php
new file mode 100644
index 0000000..7c45bc3
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/CoreTest.php
@@ -0,0 +1,490 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+class Kohana_CoreTest extends Kohana_Unittest_TestCase
+{
+
+ /**
+ * Provides test data for test_sanitize()
+ *
+ * @return array
+ */
+ public function provider_sanitize()
+ {
+ return array(
+ // $value, $result
+ array('foo', 'foo'),
+ array("foo\r\nbar", "foo\nbar"),
+ array("foo\rbar", "foo\nbar"),
+ array("Is your name O\'reilly?", "Is your name O'reilly?")
+ );
+ }
+
+ /**
+ * Tests Kohana::santize()
+ *
+ * @test
+ * @dataProvider provider_sanitize
+ * @covers Kohana::sanitize
+ * @param boolean $value Input for Kohana::sanitize
+ * @param boolean $result Output for Kohana::sanitize
+ */
+ public function test_sanitize($value, $result)
+ {
+ $this->setEnvironment(array('Kohana::$magic_quotes' => TRUE));
+
+ $this->assertSame($result, Kohana::sanitize($value));
+ }
+
+ /**
+ * Passing FALSE for the file extension should prevent appending any extension.
+ * See issue #3214
+ *
+ * @test
+ * @covers Kohana::find_file
+ */
+ public function test_find_file_no_extension()
+ {
+ // EXT is manually appened to the _file name_, not passed as the extension
+ $path = Kohana::find_file('classes', $file = 'kohana/core'.EXT, FALSE);
+
+ $this->assertInternalType('string', $path);
+
+ $this->assertStringEndsWith($file, $path);
+ }
+
+ /**
+ * If a file can't be found then find_file() should return FALSE if
+ * only a single file was requested, or an empty array if multiple files
+ * (i.e. configuration files) were requested
+ *
+ * @test
+ * @covers Kohana::find_file
+ */
+ public function test_find_file_returns_false_or_array_on_failure()
+ {
+ $this->assertFalse(Kohana::find_file('configy', 'zebra'));
+
+ $this->assertSame(array(), Kohana::find_file('configy', 'zebra', NULL, TRUE));
+ }
+
+ /**
+ * Kohana::list_files() should return an array on success and an empty array on failure
+ *
+ * @test
+ * @covers Kohana::list_files
+ */
+ public function test_list_files_returns_array_on_success_and_failure()
+ {
+ $files = Kohana::list_files('config');
+
+ $this->assertInternalType('array', $files);
+ $this->assertGreaterThan(3, count($files));
+
+ $this->assertSame(array(), Kohana::list_files('geshmuck'));
+ }
+
+ /**
+ * Tests Kohana::globals()
+ *
+ * @test
+ * @covers Kohana::globals
+ */
+ public function test_globals_removes_user_def_globals()
+ {
+ $GLOBALS = array('hackers' => 'foobar','name' => array('','',''), '_POST' => array());
+
+ Kohana::globals();
+
+ $this->assertEquals(array('_POST' => array()), $GLOBALS);
+ }
+
+ /**
+ * Provides test data for testCache()
+ *
+ * @return array
+ */
+ public function provider_cache()
+ {
+ return array(
+ // $value, $result
+ array('foo', 'hello, world', 10),
+ array('bar', NULL, 10),
+ array('bar', NULL, -10),
+ );
+ }
+
+ /**
+ * Tests Kohana::cache()
+ *
+ * @test
+ * @dataProvider provider_cache
+ * @covers Kohana::cache
+ * @param boolean $key Key to cache/get for Kohana::cache
+ * @param boolean $value Output from Kohana::cache
+ * @param boolean $lifetime Lifetime for Kohana::cache
+ */
+ public function test_cache($key, $value, $lifetime)
+ {
+ Kohana::cache($key, $value, $lifetime);
+ $this->assertEquals($value, Kohana::cache($key));
+ }
+
+ /**
+ * Provides test data for test_message()
+ *
+ * @return array
+ */
+ public function provider_message()
+ {
+ return array(
+ // $value, $result
+ array(':field must not be empty', 'validate', 'not_empty'),
+ array(
+ array(
+ 'alpha' => ':field must contain only letters',
+ 'alpha_dash' => ':field must contain only numbers, letters and dashes',
+ 'alpha_numeric' => ':field must contain only letters and numbers',
+ 'color' => ':field must be a color',
+ 'credit_card' => ':field must be a credit card number',
+ 'date' => ':field must be a date',
+ 'decimal' => ':field must be a decimal with :param1 places',
+ 'digit' => ':field must be a digit',
+ 'email' => ':field must be a email address',
+ 'email_domain' => ':field must contain a valid email domain',
+ 'equals' => ':field must equal :param1',
+ 'exact_length' => ':field must be exactly :param1 characters long',
+ 'in_array' => ':field must be one of the available options',
+ 'ip' => ':field must be an ip address',
+ 'matches' => ':field must be the same as :param1',
+ 'min_length' => ':field must be at least :param1 characters long',
+ 'max_length' => ':field must be less than :param1 characters long',
+ 'not_empty' => ':field must not be empty',
+ 'numeric' => ':field must be numeric',
+ 'phone' => ':field must be a phone number',
+ 'range' => ':field must be within the range of :param1 to :param2',
+ 'regex' => ':field does not match the required format',
+ 'url' => ':field must be a url',
+ ),
+ 'validate', NULL,
+ ),
+ );
+ }
+
+ /**
+ * Tests Kohana::message()
+ *
+ * @test
+ * @dataProvider provider_message
+ * @covers Kohana::message
+ * @param boolean $expected Output for Kohana::message
+ * @param boolean $file File to look in for Kohana::message
+ * @param boolean $key Key for Kohana::message
+ */
+ public function test_message($expected, $file, $key)
+ {
+ $this->assertEquals($expected, Kohana::message($file, $key));
+ }
+
+ /**
+ * Provides test data for test_error_handler()
+ *
+ * @return array
+ */
+ public function provider_error_handler()
+ {
+ return array(
+ array(1, 'Foobar', 'foobar.php', __LINE__),
+ );
+ }
+
+ /**
+ * Tests Kohana::error_handler()
+ *
+ * @test
+ * @dataProvider provider_error_handler
+ * @covers Kohana::error_handler
+ * @param boolean $code Input for Kohana::sanitize
+ * @param boolean $error Input for Kohana::sanitize
+ * @param boolean $file Input for Kohana::sanitize
+ * @param boolean $line Output for Kohana::sanitize
+ */
+ public function test_error_handler($code, $error, $file, $line)
+ {
+ $error_level = error_reporting();
+ error_reporting(E_ALL);
+ try
+ {
+ Kohana::error_handler($code, $error, $file, $line);
+ }
+ catch (Exception $e)
+ {
+ $this->assertEquals($code, $e->getCode());
+ $this->assertEquals($error, $e->getMessage());
+ }
+ error_reporting($error_level);
+ }
+
+ /**
+ * Provides test data for testExceptionHandler()
+ *
+ * @return array
+ */
+ public function provider_exception_handler()
+ {
+ return array(
+ // $exception_type, $message, $is_cli, $expected
+ array('Kohana_Exception', 'hello, world!', TRUE, TRUE, 'hello, world!'),
+ array('ErrorException', 'hello, world!', TRUE, TRUE, 'hello, world!'),
+ // #3016
+ array('Kohana_Exception', '', FALSE, TRUE, '<hello, world!>'),
+ );
+ }
+
+ /**
+ * Tests Kohana::exception_handler()
+ *
+ * @test
+ * @dataProvider provider_exception_handler
+ * @covers Kohana::exception_handler
+ * @param boolean $exception_type Exception type to throw
+ * @param boolean $message Message to pass to exception
+ * @param boolean $is_cli Use cli mode?
+ * @param boolean $expected Output for Kohana::exception_handler
+ * @param string $expexcted_message What to look for in the output string
+ */
+ public function teste_exception_handler($exception_type, $message, $is_cli, $expected, $expected_message)
+ {
+ try
+ {
+ Kohana::$is_cli = $is_cli;
+ throw new $exception_type($message);
+ }
+ catch (Exception $e)
+ {
+ ob_start();
+ $this->assertEquals($expected, Kohana::exception_handler($e));
+ $view = ob_get_contents();
+ ob_clean();
+ $this->assertContains($expected_message, $view);
+ }
+
+ Kohana::$is_cli = TRUE;
+ }
+
+ /**
+ * Provides test data for test_debug()
+ *
+ * @return array
+ */
+ public function provider_debug()
+ {
+ return array(
+ // $exception_type, $message, $is_cli, $expected
+ array(array('foobar'), "array (1) (\n 0 => string (6) \"foobar\"\n) "),
+ );
+ }
+
+ /**
+ * Tests Kohana::debug()
+ *
+ * @test
+ * @dataProvider provider_debug
+ * @covers Kohana::debug
+ * @param boolean $thing The thing to debug
+ * @param boolean $expected Output for Kohana::debug
+ */
+ public function testdebug($thing, $expected)
+ {
+ $this->assertEquals($expected, Kohana::debug($thing));
+ }
+
+ /**
+ * Provides test data for testDebugPath()
+ *
+ * @return array
+ */
+ public function provider_debug_path()
+ {
+ return array(
+ array(
+ Kohana::find_file('classes', 'kohana'),
+ 'SYSPATH'.DIRECTORY_SEPARATOR.'classes'.DIRECTORY_SEPARATOR.'kohana.php'
+ ),
+ array(
+ Kohana::find_file('classes', $this->dirSeparator('kohana/unittest/runner')),
+ $this->dirSeparator('MODPATH/unittest/classes/kohana/unittest/runner.php')
+ ),
+ );
+ }
+
+ /**
+ * Tests Kohana::debug_path()
+ *
+ * @test
+ * @dataProvider provider_debug_path
+ * @covers Kohana::debug_path
+ * @param boolean $path Input for Kohana::debug_path
+ * @param boolean $expected Output for Kohana::debug_path
+ */
+ public function testDebugPath($path, $expected)
+ {
+ $this->assertEquals($expected, Kohana::debug_path($path));
+ }
+
+ /**
+ * Provides test data for test_modules_sets_and_returns_valid_modules()
+ *
+ * @return array
+ */
+ public function provider_modules_sets_and_returns_valid_modules()
+ {
+ return array(
+ array(array(), array()),
+ array(array('unittest' => MODPATH.'fo0bar'), array()),
+ array(array('unittest' => MODPATH.'unittest'), array('unittest' => $this->dirSeparator(MODPATH.'unittest/'))),
+ );
+ }
+
+ /**
+ * Tests Kohana::modules()
+ *
+ * @test
+ * @dataProvider provider_modules_sets_and_returns_valid_modules
+ * @param boolean $source Input for Kohana::modules
+ * @param boolean $expected Output for Kohana::modules
+ */
+ public function test_modules_sets_and_returns_valid_modules($source, $expected)
+ {
+ $modules = Kohana::modules();
+
+ try
+ {
+ $this->assertEquals($expected, Kohana::modules($source));
+ }
+ catch(Exception $e)
+ {
+ Kohana::modules($modules);
+
+ throw $e;
+ }
+
+ Kohana::modules($modules);
+ }
+
+ /**
+ * To make the tests as portable as possible this just tests that
+ * you get an array of modules when you can Kohana::modules() and that
+ * said array contains unittest
+ *
+ * @test
+ * @covers Kohana::modules
+ */
+ public function test_modules_returns_array_of_modules()
+ {
+ $modules = Kohana::modules();
+
+ $this->assertInternalType('array', $modules);
+
+ $this->assertArrayHasKey('unittest', $modules);
+ }
+
+ /**
+ * Tests Kohana::include_paths()
+ *
+ * The include paths must contain the apppath and syspath
+ * @test
+ * @covers Kohana::include_paths
+ */
+ public function test_include_paths()
+ {
+ $include_paths = Kohana::include_paths();
+ $modules = Kohana::modules();
+
+ $this->assertInternalType('array', $include_paths);
+
+ // We must have at least 2 items in include paths (APP / SYS)
+ $this->assertGreaterThan(2, count($include_paths));
+ // Make sure said paths are in the include paths
+ // And make sure they're in the correct positions
+ $this->assertSame(APPPATH, reset($include_paths));
+ $this->assertSame(SYSPATH, end($include_paths));
+
+ foreach($modules as $module)
+ {
+ $this->assertContains($module, $include_paths);
+ }
+ }
+
+ /**
+ * Provides test data for test_exception_text()
+ *
+ * @return array
+ */
+ public function provider_exception_text()
+ {
+ return array(
+ array(new Kohana_Exception('foobar'), $this->dirSeparator('Kohana_Exception [ 0 ]: foobar ~ SYSPATH/tests/kohana/CoreTest.php [ '.__LINE__.' ]')),
+ );
+ }
+
+ /**
+ * Tests Kohana::exception_text()
+ *
+ * @test
+ * @dataProvider provider_exception_text
+ * @covers Kohana::exception_text
+ * @param object $exception exception to test
+ * @param string $expected expected output
+ */
+ public function test_exception_text($exception, $expected)
+ {
+ $this->assertEquals($expected, Kohana::exception_text($exception));
+ }
+
+ /**
+ * Provides test data for test_dump()
+ *
+ * @return array
+ */
+ public function provider_dump()
+ {
+ return array(
+ array('foobar', 128, 'string (6) "foobar"'),
+ array('foobar', 2, 'string (6) "fo …"'),
+ array(NULL, 128, 'NULL '),
+ array(TRUE, 128, 'bool TRUE'),
+ array(array('foobar'), 128, "array (1) (\n 0 => string (6) \"foobar\"\n) "),
+ array(new StdClass, 128, "object stdClass(0) {\n}
"),
+ array("fo\x6F\xFF\x00bar\x8F\xC2\xB110", 128, 'string (10) "foobar±10"'),
+ );
+ }
+
+ /**
+ * Tests Kohana::dump()
+ *
+ * @test
+ * @dataProvider provider_dump
+ * @covers Kohana::dump
+ * @covers Kohana::_dump
+ * @param object $exception exception to test
+ * @param string $expected expected output
+ */
+ public function test_dump($input, $length, $expected)
+ {
+ $this->assertEquals($expected, Kohana::dump($input, $length));
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/DateTest.php b/includes/kohana/system/tests/kohana/DateTest.php
new file mode 100644
index 0000000..60af3f9
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/DateTest.php
@@ -0,0 +1,702 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+class Kohana_DateTest extends Kohana_Unittest_TestCase
+{
+ protected $_original_timezone = NULL;
+
+ /**
+ * Ensures we have a consistant timezone for testing.
+ */
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->_original_timezone = date_default_timezone_get();
+
+ date_default_timezone_set('America/Chicago');
+ }
+
+ /**
+ * Restores original timezone after testing.
+ */
+ public function tearDown()
+ {
+ date_default_timezone_set($this->_original_timezone);
+
+ parent::tearDown();
+ }
+
+ /**
+ * Provides test data for test_offset()
+ *
+ * @return array
+ */
+ public function provider_offset()
+ {
+ return array(
+ array(30600, 'Asia/Calcutta', 'America/Argentina/Buenos_Aires'),
+ );
+ }
+
+ /**
+ * Tests Date::offset()
+ *
+ * @test
+ * @dataProvider provider_offset
+ * @covers Date::offset
+ * @param integer $expected Expected offset
+ * @param string $remote Remote TZ
+ * @param string $local Local TZ
+ * @param integer $now Current timestamp
+ */
+ public function test_offset($expected, $remote, $local, $now = NULL)
+ {
+ $this->assertSame($expected, Date::offset($remote, $local, $now));
+ }
+
+ /**
+ * Provides test data for test_date()
+ *
+ * @return array
+ */
+ public function provider_am_pm()
+ {
+ return array(
+ // All possible values
+ array(0, 'AM'),
+ array(1, 'AM'),
+ array(2, 'AM'),
+ array(3, 'AM'),
+ array(4, 'AM'),
+ array(5, 'AM'),
+ array(6, 'AM'),
+ array(7, 'AM'),
+ array(8, 'AM'),
+ array(9, 'AM'),
+ array(10, 'AM'),
+ array(11, 'AM'),
+ array(12, 'PM'),
+ array(13, 'PM'),
+ array(14, 'PM'),
+ array(15, 'PM'),
+ array(16, 'PM'),
+ array(17, 'PM'),
+ array(18, 'PM'),
+ array(19, 'PM'),
+ array(20, 'PM'),
+ array(21, 'PM'),
+ array(22, 'PM'),
+ array(23, 'PM'),
+ array(24, 'PM'),
+ // ampm doesn't validate the hour, so I don't think we should test it..
+ // test strings are converted
+ array('0', 'AM'),
+ array('12', 'PM'),
+ );
+ }
+
+ /**
+ * Tests Date::ampm()
+ *
+ * @test
+ * @covers Date::ampm
+ * @dataProvider provider_am_pm
+ * @param $hour
+ * @param $expected
+ */
+ public function test_am_pm($hour, $expected)
+ {
+ $this->assertSame(
+ $expected,
+ Date::ampm($hour)
+ );
+ }
+
+ /**
+ * Provides test data for test_adjust()
+ *
+ * @return array
+ */
+ public function provider_adjust()
+ {
+ return array(
+ // Might as well test all possibilities
+ array(1, 'am', '01'),
+ array(2, 'am', '02'),
+ array(3, 'am', '03'),
+ array(4, 'am', '04'),
+ array(5, 'am', '05'),
+ array(6, 'am', '06'),
+ array(7, 'am', '07'),
+ array(8, 'am', '08'),
+ array(9, 'am', '09'),
+ array(10, 'am', '10'),
+ array(11, 'am', '11'),
+ array(12, 'am', '00'),
+ array(1, 'pm', '13'),
+ array(2, 'pm', '14'),
+ array(3, 'pm', '15'),
+ array(4, 'pm', '16'),
+ array(5, 'pm', '17'),
+ array(6, 'pm', '18'),
+ array(7, 'pm', '19'),
+ array(8, 'pm', '20'),
+ array(9, 'pm', '21'),
+ array(10, 'pm', '22'),
+ array(11, 'pm', '23'),
+ array(12, 'pm', '12'),
+ // It should also work with strings instead of ints
+ array('10', 'pm', '22'),
+ array('10', 'am', '10'),
+ );
+ }
+
+ /**
+ * Tests Date::ampm()
+ *
+ * @test
+ * @dataProvider provider_adjust
+ * @param integer $hour Hour in 12 hour format
+ * @param string $ampm Either am or pm
+ * @param string $expected Expected result
+ */
+ public function test_adjust($hour, $ampm, $expected)
+ {
+ $this->assertSame(
+ $expected,
+ Date::adjust($hour, $ampm)
+ );
+ }
+
+ /**
+ * Provides test data for test_days()
+ *
+ * @return array
+ */
+ public function provider_days()
+ {
+ return array(
+ // According to "the rhyme" these should be the same every year
+ array(9, FALSE, 30),
+ array(4, FALSE, 30),
+ array(6, FALSE, 30),
+ array(11, FALSE, 30),
+ array(1, FALSE, 31),
+ array(3, FALSE, 31),
+ array(5, FALSE, 31),
+ array(7, FALSE, 31),
+ array(8, FALSE, 31),
+ array(10, FALSE, 31),
+ // February is such a pain
+ array(2, 2001, 28),
+ array(2, 2000, 29),
+ array(2, 2012, 29),
+ );
+ }
+
+ /**
+ * Tests Date::days()
+ *
+ * @test
+ * @covers Date::days
+ * @dataProvider provider_days
+ * @param integer $month
+ * @param integer $year
+ * @param integer $expected
+ */
+ public function test_days($month, $year, $expected)
+ {
+ $days = Date::days($month, $year);
+
+ $this->assertSame(
+ $expected,
+ count($days)
+ );
+
+ // This should be a mirrored array, days => days
+ for($i = 1; $i <= $expected; ++$i)
+ {
+ $this->assertArrayHasKey($i, $days);
+ // Combining the type check into this saves about 400-500 assertions!
+ $this->assertSame((string) $i, $days[$i]);
+ }
+ }
+
+ /**
+ * Provides test data for test_formatted_time()
+ *
+ * @return array
+ */
+ public function provider_formatted_time()
+ {
+ return array(
+ // Test the default format
+ array('2010-04-16 17:00:00', '5:00PM 16th April 2010'),
+ // Now we use our own format
+ // Binary date!
+ array('01/01/2010 01:00', '1AM 1st January 2010', 'd/m/Y H:i'),
+ );
+ }
+
+ /**
+ * Tests Date::formatted_time()
+ *
+ * @test
+ * @dataProvider provider_formatted_time
+ * @covers Date::formatted_time
+ * @ticket 3035
+ * @param string $expected Expected output
+ * @param string|integer $datetime_str The datetime timestamp / string
+ * @param string|null $timestamp_format The output format
+ */
+ public function test_formatted_time($expected, $datetime_str, $timestamp_format = NULL)
+ {
+ $timestamp = Date::formatted_time($datetime_str, $timestamp_format);
+
+ $this->assertSame($expected, $timestamp);
+ }
+
+ /**
+ * Tests Date::months()
+ *
+ * @test
+ * @covers Date::months
+ */
+ public function test_months()
+ {
+ $months = Date::months();
+
+ $this->assertSame(12, count($months));
+
+ for($i = 1; $i <= 12; ++$i)
+ {
+ $this->assertArrayHasKey($i, $months);
+ $this->assertSame((string) $i, $months[$i]);
+ }
+ }
+
+ /**
+ * Provides test data for test_span()
+ *
+ * @return array
+ */
+ public function provider_span()
+ {
+ $time = time();
+ return array(
+ // Test that it must specify an output format
+ array(
+ $time,
+ $time,
+ '',
+ FALSE
+ ),
+ // Test that providing only one output just returns that output
+ array(
+ $time - 30,
+ $time,
+ 'seconds',
+ 30
+ ),
+ // Random tests
+ array(
+ $time - 30,
+ $time,
+ 'years,months,weeks,days,hours,minutes,seconds',
+ array('years' => 0, 'months' => 0, 'weeks' => 0, 'days' => 0, 'hours' => 0, 'minutes' => 0, 'seconds' => 30),
+ ),
+ array(
+ $time - (60 * 60 * 24 * 782) + (60 * 25),
+ $time,
+ 'years,months,weeks,days,hours,minutes,seconds',
+ array('years' => 2, 'months' => 1, 'weeks' => 3, 'days' => 0, 'hours' => 1, 'minutes' => 28, 'seconds' => 24),
+ ),
+ // Should be able to compare with the future & that it only uses formats specified
+ array(
+ $time + (60 * 60 * 24 * 15) + (60 * 5),
+ $time,
+ 'weeks,days,hours,minutes,seconds',
+ array('weeks' => 2, 'days' => 1, 'hours' => 0, 'minutes' => 5, 'seconds' => 0),
+ ),
+ array(
+ // Add a bit of extra time to account for phpunit processing
+ $time + (14 * 31 * 24* 60 * 60) + (79 * 80),
+ NULL,
+ 'months,years',
+ array('months' => 2, 'years' => 1),
+ ),
+ );
+ }
+
+ /**
+ * Tests Date::span()
+ *
+ * @test
+ * @covers Date::span
+ * @dataProvider provider_span
+ * @param integer $time1 Time in the past
+ * @param integer $time2 Time to compare against
+ * @param string $output Units to output
+ * @param array $expected Array of $outputs => values
+ */
+ public function test_span($time1, $time2, $output, $expected)
+ {
+ $this->assertSame(
+ $expected,
+ Date::span($time1, $time2, $output)
+ );
+ }
+
+ /**
+ * Provides test data to test_fuzzy_span
+ *
+ * This test data is provided on the assumption that it
+ * won't take phpunit more than 30 seconds to get the
+ * data from this provider to the test... ;)
+ *
+ * @return array Test Data
+ */
+ public function provider_fuzzy_span()
+ {
+ $now = time();
+
+ return array(
+ array('moments ago', $now - 30, $now),
+ array('in moments', $now + 30, $now),
+
+ array('a few minutes ago', $now - 10*60, $now),
+ array('in a few minutes', $now + 10*60, $now),
+
+ array('less than an hour ago', $now - 45*60, $now),
+ array('in less than an hour', $now + 45*60, $now),
+
+ array('a couple of hours ago', $now - 2*60*60, $now),
+ array('in a couple of hours', $now + 2*60*60, $now),
+
+ array('less than a day ago', $now - 12*60*60, $now),
+ array('in less than a day', $now + 12*60*60, $now),
+
+ array('about a day ago', $now - 30*60*60, $now),
+ array('in about a day', $now + 30*60*60, $now),
+
+ array('a couple of days ago', $now - 3*24*60*60, $now),
+ array('in a couple of days', $now + 3*24*60*60, $now),
+
+ array('less than a week ago', $now - 5*24*60*60, $now),
+ array('in less than a week', $now + 5*24*60*60, $now),
+
+ array('about a week ago', $now - 9*24*60*60, $now),
+ array('in about a week', $now + 9*24*60*60, $now),
+
+ array('less than a month ago', $now - 20*24*60*60, $now),
+ array('in less than a month', $now + 20*24*60*60, $now),
+
+ array('about a month ago', $now - 40*24*60*60, $now),
+ array('in about a month', $now + 40*24*60*60, $now),
+
+ array('a couple of months ago', $now - 3*30*24*60*60, $now),
+ array('in a couple of months', $now + 3*30*24*60*60, $now),
+
+ array('less than a year ago', $now - 7*31*24*60*60, $now),
+ array('in less than a year', $now + 7*31*24*60*60, $now),
+
+ array('about a year ago', $now - 18*31*24*60*60, $now),
+ array('in about a year', $now + 18*31*24*60*60, $now),
+
+ array('a couple of years ago', $now - 3*12*31*24*60*60, $now),
+ array('in a couple of years', $now + 3*12*31*24*60*60, $now),
+
+ array('a few years ago', $now - 5*12*31*24*60*60, $now),
+ array('in a few years', $now + 5*12*31*24*60*60, $now),
+
+ array('about a decade ago', $now - 11*12*31*24*60*60, $now),
+ array('in about a decade', $now + 11*12*31*24*60*60, $now),
+
+ array('a couple of decades ago', $now - 20*12*31*24*60*60, $now),
+ array('in a couple of decades', $now + 20*12*31*24*60*60, $now),
+
+ array('several decades ago', $now - 50*12*31*24*60*60, $now),
+ array('in several decades', $now + 50*12*31*24*60*60, $now),
+
+ array('a long time ago', $now - pow(10,10), $now),
+ array('in a long time', $now + pow(10,10), $now),
+ );
+ }
+
+ /**
+ * Test of Date::fuzy_span()
+ *
+ * @test
+ * @dataProvider provider_fuzzy_span
+ * @param string $expected Expected output
+ * @param integer $timestamp Timestamp to use
+ * @param integer $local_timestamp The local timestamp to use
+ */
+ public function test_fuzzy_span($expected, $timestamp, $local_timestamp)
+ {
+ $this->assertSame(
+ $expected,
+ Date::fuzzy_span($timestamp, $local_timestamp)
+ );
+ }
+
+ /**
+ * Provides test data for test_years()
+ *
+ * @return array Test Data
+ */
+ public function provider_years()
+ {
+ return array(
+ array(
+ array (
+ 2005 => '2005',
+ 2006 => '2006',
+ 2007 => '2007',
+ 2008 => '2008',
+ 2009 => '2009',
+ 2010 => '2010',
+ 2011 => '2011',
+ 2012 => '2012',
+ 2013 => '2013',
+ 2014 => '2014',
+ 2015 => '2015',
+ ),
+ 2005,
+ 2015
+ ),
+ );
+ }
+
+ /**
+ * Tests Data::years()
+ *
+ * @test
+ * @dataProvider provider_years
+ */
+ public function test_years($expected, $start = FALSE, $end = FALSE)
+ {
+ $this->assertSame(
+ $expected,
+ Date::years($start, $end)
+ );
+ }
+
+ public function provider_hours()
+ {
+ return array(
+ array(
+ array(
+ 1 => '1',
+ 2 => '2',
+ 3 => '3',
+ 4 => '4',
+ 5 => '5',
+ 6 => '6',
+ 7 => '7',
+ 8 => '8',
+ 9 => '9',
+ 10 => '10',
+ 11 => '11',
+ 12 => '12',
+ ),
+ ),
+ );
+ }
+
+ /**
+ * Test for Date::hours
+ *
+ * @test
+ * @dataProvider provider_hours
+ */
+ public function test_hours($expected, $step = 1, $long = FALSE, $start = NULL)
+ {
+ $this->assertSame(
+ $expected,
+ Date::hours($step, $long, $start)
+ );
+ }
+
+ /**
+ * Provides test data for test_seconds
+ *
+ * @return array Test data
+ */
+ public function provider_seconds()
+ {
+ return array(
+ array(
+ // Thank god for var_export()
+ array (
+ 0 => '00', 1 => '01', 2 => '02', 3 => '03', 4 => '04',
+ 5 => '05', 6 => '06', 7 => '07', 8 => '08', 9 => '09',
+ 10 => '10', 11 => '11', 12 => '12', 13 => '13', 14 => '14',
+ 15 => '15', 16 => '16', 17 => '17', 18 => '18', 19 => '19',
+ 20 => '20', 21 => '21', 22 => '22', 23 => '23', 24 => '24',
+ 25 => '25', 26 => '26', 27 => '27', 28 => '28', 29 => '29',
+ 30 => '30', 31 => '31', 32 => '32', 33 => '33', 34 => '34',
+ 35 => '35', 36 => '36', 37 => '37', 38 => '38', 39 => '39',
+ 40 => '40', 41 => '41', 42 => '42', 43 => '43', 44 => '44',
+ 45 => '45', 46 => '46', 47 => '47', 48 => '48', 49 => '49',
+ 50 => '50', 51 => '51', 52 => '52', 53 => '53', 54 => '54',
+ 55 => '55', 56 => '56', 57 => '57', 58 => '58', 59 => '59',
+ ),
+ 1,
+ 0,
+ 60
+ ),
+ );
+ }
+
+ /**
+ *
+ * @test
+ * @dataProvider provider_seconds
+ * @covers Date::seconds
+ */
+ public function test_seconds($expected, $step = 1, $start = 0, $end = 60)
+ {
+ $this->assertSame(
+ $expected,
+ Date::seconds($step, $start, $end)
+ );
+ }
+
+ /**
+ * Provides test data for test_minutes
+ *
+ * @return array Test data
+ */
+ public function provider_minutes()
+ {
+ return array(
+ array(
+ array(
+ 0 => '00', 5 => '05', 10 => '10',
+ 15 => '15', 20 => '20', 25 => '25',
+ 30 => '30', 35 => '35', 40 => '40',
+ 45 => '45', 50 => '50', 55 => '55',
+ ),
+ 5,
+ ),
+ );
+ }
+
+ /**
+ *
+ * @test
+ * @dataProvider provider_minutes
+ */
+ public function test_minutes($expected, $step)
+ {
+ $this->assertSame(
+ $expected,
+ Date::minutes($step)
+ );
+ }
+
+ /**
+ * This tests that the minutes helper defaults to using a $step of 5
+ * and thus returns an array of 5 minute itervals
+ *
+ * @test
+ * @covers Date::minutes
+ */
+ public function test_minutes_defaults_to_using_step_of5()
+ {
+ $minutes = array(
+ 0 => '00', 5 => '05', 10 => '10',
+ 15 => '15', 20 => '20', 25 => '25',
+ 30 => '30', 35 => '35', 40 => '40',
+ 45 => '45', 50 => '50', 55 => '55',
+ );
+
+ $this->assertSame(
+ $minutes,
+ Date::minutes()
+ );
+ }
+
+ /**
+ * Provids for test_unix2dos
+ *
+ * @return array Test Data
+ */
+ public function provider_unix2dos()
+ {
+ return array(
+ array(
+ 1024341746,
+ 1281786936
+ ),
+ array(
+ 2162688,
+ 315554400
+ )
+ );
+ }
+
+ /**
+ * Test Date::unix2dos()
+ *
+ * You should always pass a timestamp as otherwise the current
+ * date/time would be used and that's oviously variable
+ *
+ * Geert seems to be the only person who knows how unix2dos() works
+ * so we just throw in some random values and see what happens
+ *
+ * @test
+ * @dataProvider provider_unix2dos
+ * @covers Date::unix2dos
+ * @param integer $expected Expected output
+ * @param integer $timestamp Input timestamp
+ */
+ public function test_unix2dos($expected, $timestamp)
+ {
+ $this->assertSame($expected, Date::unix2dos($timestamp));
+ }
+
+ /**
+ * Provides test data for test_dos2unix
+ *
+ * @return array Test data
+ */
+ public function provider_dos2unix()
+ {
+ return array(
+ array(
+ 1281786936,
+ 1024341746,
+ ),
+ array(
+ 315554400,
+ 2162688,
+ ),
+ );
+ }
+
+ /**
+ * Tests Date::dos2unix
+ *
+ * @test
+ * @dataProvider provider_dos2unix
+ * @param integer $expected Expected output
+ * @param integer $timestamp Input timestamp
+ */
+ public function test_dos2unix($expected, $timestamp)
+ {
+ $this->assertEquals($expected, Date::dos2unix($timestamp));
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/FeedTest.php b/includes/kohana/system/tests/kohana/FeedTest.php
new file mode 100644
index 0000000..c6da787
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/FeedTest.php
@@ -0,0 +1,121 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+class Kohana_FeedTest extends Kohana_Unittest_TestCase
+{
+ /**
+ * Provides test data for test_parse()
+ *
+ * @return array
+ */
+ public function provider_parse()
+ {
+ return array(
+ // $source, $expected
+ array('http://dev.kohanaframework.org/projects/kohana3/activity.atom', 15),
+ );
+ }
+
+ /**
+ * Tests that Feed::parse gets the correct number of elements
+ *
+ * @test
+ * @dataProvider provider_parse
+ * @covers feed::parse
+ * @param string $source URL to test
+ * @param integer $expected Count of items
+ */
+ public function test_parse($source, $expected)
+ {
+ if ( ! $this->hasInternet())
+ $this->markTestSkipped('An internet connection is required for this test');
+
+ $this->assertEquals($expected, count(feed::parse($source)));
+ }
+
+ /**
+ * Provides test data for test_create()
+ *
+ * @return array
+ */
+ public function provider_create()
+ {
+ $info = array('pubDate' => 123, 'image' => array('link' => 'http://kohanaframework.org/image.png', 'url' => 'http://kohanaframework.org/', 'title' => 'title'));
+
+ return array(
+ // $source, $expected
+ array($info, array('foo' => array('foo' => 'bar', 'pubDate' => 123, 'link' => 'foo')), array('_SERVER' => array('HTTP_HOST' => 'localhost')+$_SERVER),
+ array(
+ 'tag' => 'channel',
+ 'descendant' => array(
+ 'tag' => 'item',
+ 'child' => array(
+ 'tag' => 'foo',
+ 'content' => 'bar'
+ )
+ )
+ ),
+ array(
+ $this->matcher_composer($info, 'image', 'link'),
+ $this->matcher_composer($info, 'image', 'url'),
+ $this->matcher_composer($info, 'image', 'title')
+ )
+ ),
+ );
+ }
+
+ /**
+ * Helper for handy matcher composing
+ *
+ * @param array $data
+ * @param string $tag
+ * @param string $child
+ * @return array
+ */
+ private function matcher_composer($data, $tag, $child)
+ {
+ return array(
+ 'tag' => 'channel',
+ 'descendant' => array(
+ 'tag' => $tag,
+ 'child' => array(
+ 'tag' => $child,
+ 'content' => $data[$tag][$child]
+ )
+ )
+ );
+ }
+
+ /**
+ * @test
+ *
+ * @dataProvider provider_create
+ *
+ * @covers feed::create
+ *
+ * @param string $info info to pass
+ * @param integer $items items to add
+ * @param integer $matcher output
+ */
+ public function test_create($info, $items, $enviroment, $matcher_item, $matchers_image)
+ {
+ $this->setEnvironment($enviroment);
+
+ $this->assertTag($matcher_item, feed::create($info, $items), '', FALSE);
+
+ foreach ($matchers_image as $matcher_image)
+ {
+ $this->assertTag($matcher_image, feed::create($info, $items), '', FALSE);
+ }
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/FileTest.php b/includes/kohana/system/tests/kohana/FileTest.php
new file mode 100644
index 0000000..d438f44
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/FileTest.php
@@ -0,0 +1,73 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+class Kohana_FileTest extends Kohana_Unittest_Testcase
+{
+ /**
+ * Provides test data for test_sanitize()
+ *
+ * @return array
+ */
+ public function provider_mime()
+ {
+ return array(
+ // $value, $result
+ array(Kohana::find_file('classes', 'file')),
+ array(Kohana::find_file('tests', 'test_data/github', 'png')),
+ );
+ }
+
+ /**
+ * Tests File::mime()
+ *
+ * @test
+ * @dataProvider provider_mime
+ * @param boolean $input Input for File::mime
+ * @param boolean $expected Output for File::mime
+ */
+ public function test_mime($input)
+ {
+ $this->assertSame(1, preg_match('/^(?:application|audio|image|message|multipart|text|video)\/[a-z.+0-9-]+$/i', File::mime($input)));
+ }
+
+ /**
+ * Provides test data for test_split_join()
+ *
+ * @return array
+ */
+ public function provider_split_join()
+ {
+ return array(
+ // $value, $result
+ array(Kohana::find_file('tests', 'test_data/github', 'png'), .01, 1),
+ );
+ }
+
+ /**
+ * Tests File::mime()
+ *
+ * @test
+ * @dataProvider provider_split_join
+ * @param boolean $input Input for File::split
+ * @param boolean $peices Input for File::split
+ * @param boolean $expected Output for File::splut
+ */
+ public function test_split_join($input, $peices, $expected)
+ {
+ $this->assertSame($expected, File::split($input, $peices));
+ $this->assertSame($expected, File::join($input));
+
+ foreach (glob(Kohana::find_file('tests', 'test_data/github', 'png') . '.*') as $file) unlink($file);
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/FormTest.php b/includes/kohana/system/tests/kohana/FormTest.php
new file mode 100644
index 0000000..8b3033b
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/FormTest.php
@@ -0,0 +1,372 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+class Kohana_FormTest extends Kohana_Unittest_Testcase
+{
+ /**
+ * Defaults for this test
+ * @var array
+ */
+ protected $environmentDefault = array(
+ 'Kohana::$base_url' => '/',
+ 'HTTP_HOST' => 'kohanaframework.org',
+ );
+
+ /**
+ * Provides test data for test_open()
+ *
+ * @return array
+ */
+ public function provider_open()
+ {
+ return array(
+ // $value, $result
+ #array(NULL, NULL, '', Form::close());
+ }
+
+ /**
+ * Provides test data for test_input()
+ *
+ * @return array
+ */
+ public function provider_input()
+ {
+ return array(
+ // $value, $result
+ array('input', 'foo', 'bar', NULL, 'input'),
+ array('input', 'foo', NULL, NULL, 'input'),
+ array('hidden', 'foo', 'bar', NULL, 'hidden'),
+ array('password', 'foo', 'bar', NULL, 'password'),
+ );
+ }
+
+ /**
+ * Tests Form::input()
+ *
+ * @test
+ * @dataProvider provider_input
+ * @param boolean $input Input for Form::input
+ * @param boolean $expected Output for Form::input
+ */
+ public function test_input($type, $name, $value, $attributes)
+ {
+ $matcher = array(
+ 'tag' => 'input',
+ 'attributes' => array('name' => $name, 'type' => $type)
+ );
+
+ // Form::input creates a text input
+ if($type === 'input')
+ $matcher['attributes']['type'] = 'text';
+
+ // NULL just means no value
+ if($value !== NULL)
+ $matcher['attributes']['value'] = $value;
+
+ // Add on any attributes
+ if(is_array($attributes))
+ $matcher['attributes'] = $attributes + $matcher['attributes'];
+
+ $tag = Form::$type($name, $value, $attributes);
+
+ $this->assertTag($matcher, $tag, $tag);
+ }
+
+ /**
+ * Provides test data for test_file()
+ *
+ * @return array
+ */
+ public function provider_file()
+ {
+ return array(
+ // $value, $result
+ array('foo', NULL, ' '),
+ );
+ }
+
+ /**
+ * Tests Form::file()
+ *
+ * @test
+ * @dataProvider provider_file
+ * @param boolean $input Input for Form::file
+ * @param boolean $expected Output for Form::file
+ */
+ public function test_file($name, $attributes, $expected)
+ {
+ $this->assertSame($expected, Form::file($name, $attributes));
+ }
+
+ /**
+ * Provides test data for test_check()
+ *
+ * @return array
+ */
+ public function provider_check()
+ {
+ return array(
+ // $value, $result
+ array('checkbox', 'foo', NULL, FALSE, NULL),
+ array('checkbox', 'foo', NULL, TRUE, NULL),
+ array('checkbox', 'foo', 'bar', TRUE, NULL),
+
+ array('radio', 'foo', NULL, FALSE, NULL),
+ array('radio', 'foo', NULL, TRUE, NULL),
+ array('radio', 'foo', 'bar', TRUE, NULL),
+ );
+ }
+
+ /**
+ * Tests Form::check()
+ *
+ * @test
+ * @dataProvider provider_check
+ * @param boolean $input Input for Form::check
+ * @param boolean $expected Output for Form::check
+ */
+ public function test_check($type, $name, $value, $checked, $attributes)
+ {
+ $matcher = array('tag' => 'input', 'attributes' => array('name' => $name, 'type' => $type));
+
+ if($value !== NULL)
+ $matcher['attributes']['value'] = $value;
+
+ if(is_array($attributes))
+ $matcher['attributes'] = $attributes + $matcher['attributes'];
+
+ if($checked === TRUE)
+ $matcher['attributes']['checked'] = 'checked';
+
+ $tag = Form::$type($name, $value, $checked, $attributes);
+ $this->assertTag($matcher, $tag, $tag);
+ }
+
+ /**
+ * Provides test data for test_text()
+ *
+ * @return array
+ */
+ public function provider_text()
+ {
+ return array(
+ // $value, $result
+ array('textarea', 'foo', 'bar', NULL),
+ array('textarea', 'foo', 'bar', array('rows' => 20, 'cols' => 20)),
+ array('button', 'foo', 'bar', NULL),
+ array('label', 'foo', 'bar', NULL),
+ array('label', 'foo', NULL, NULL),
+ );
+ }
+
+ /**
+ * Tests Form::textarea()
+ *
+ * @test
+ * @dataProvider provider_text
+ * @param boolean $input Input for Form::textarea
+ * @param boolean $expected Output for Form::textarea
+ */
+ public function test_text($type, $name, $body, $attributes)
+ {
+ $matcher = array(
+ 'tag' => $type,
+ 'attributes' => array(),
+ 'content' => $body,
+ );
+
+ if($type !== 'label')
+ $matcher['attributes'] = array('name' => $name);
+ else
+ $matcher['attributes'] = array('for' => $name);
+
+
+ if(is_array($attributes))
+ $matcher['attributes'] = $attributes + $matcher['attributes'];
+
+ $tag = Form::$type($name, $body, $attributes);
+
+ $this->assertTag($matcher, $tag, $tag);
+ }
+
+
+ /**
+ * Provides test data for test_select()
+ *
+ * @return array
+ */
+ public function provider_select()
+ {
+ return array(
+ // $value, $result
+ array('foo', NULL, NULL, " "),
+ array('foo', array('bar' => 'bar'), NULL, "\nbar \n "),
+ array('foo', array('bar' => 'bar'), 'bar', "\nbar \n "),
+ array('foo', array('bar' => array('foo' => 'bar')), NULL, "\n\nbar \n \n "),
+ array('foo', array('bar' => array('foo' => 'bar')), 'foo', "\n\nbar \n \n "),
+ // #2286
+ array('foo', array('bar' => 'bar', 'unit' => 'test', 'foo' => 'foo'), array('bar', 'foo'), "\nbar \ntest \nfoo \n "),
+ );
+ }
+
+ /**
+ * Tests Form::select()
+ *
+ * @test
+ * @dataProvider provider_select
+ * @param boolean $input Input for Form::select
+ * @param boolean $expected Output for Form::select
+ */
+ public function test_select($name, $options, $selected, $expected)
+ {
+ // Much more efficient just to assertSame() rather than assertTag() on each element
+ $this->assertSame($expected, Form::select($name, $options, $selected));
+ }
+
+ /**
+ * Provides test data for test_submit()
+ *
+ * @return array
+ */
+ public function provider_submit()
+ {
+ return array(
+ // $value, $result
+ array('foo', 'Foobar!', ' '),
+ );
+ }
+
+ /**
+ * Tests Form::submit()
+ *
+ * @test
+ * @dataProvider provider_submit
+ * @param boolean $input Input for Form::submit
+ * @param boolean $expected Output for Form::submit
+ */
+ public function test_submit($name, $value, $expected)
+ {
+ $matcher = array(
+ 'tag' => 'input',
+ 'attributes' => array('name' => $name, 'type' => 'submit', 'value' => $value)
+ );
+
+ $this->assertTag($matcher, Form::submit($name, $value));
+ }
+
+
+ /**
+ * Provides test data for test_image()
+ *
+ * @return array
+ */
+ public function provider_image()
+ {
+ return array(
+ // $value, $result
+ array('foo', 'bar', array('src' => 'media/img/login.png'), ' '),
+ );
+ }
+
+ /**
+ * Tests Form::image()
+ *
+ * @test
+ * @dataProvider provider_image
+ * @param boolean $name Input for Form::image
+ * @param boolean $value Input for Form::image
+ * @param boolean $attributes Input for Form::image
+ * @param boolean $expected Output for Form::image
+ */
+ public function test_image($name, $value, $attributes, $expected)
+ {
+ $this->assertSame($expected, Form::image($name, $value, $attributes));
+ }
+
+ /**
+ * Provides test data for testLabel()
+ *
+ * @return array
+ */
+ function providerLabel()
+ {
+ return array(
+ // $value, $result
+ // Single for provided
+ array('email', NULL, NULL, 'Email '),
+ array('email_address', NULL, NULL, 'Email Address '),
+ array('email-address', NULL, NULL, 'Email Address '),
+ // For and text values provided
+ array('name', 'First name', NULL, 'First name '),
+ // with attributes
+ array('lastname', 'Last name', array('class' => 'text'), 'Last name '),
+ array('lastname', 'Last name', array('class' => 'text', 'id'=>'txt_lastname'), 'Last name '),
+ );
+ }
+
+ /**
+ * Tests Form::label()
+ *
+ * @test
+ * @dataProvider providerLabel
+ * @param boolean $for Input for Form::label
+ * @param boolean $text Input for Form::label
+ * @param boolean $attributes Input for Form::label
+ * @param boolean $expected Output for Form::label
+ */
+ function testLabel($for, $text, $attributes, $expected)
+ {
+ $this->assertSame($expected, Form::label($for, $text, $attributes));
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/HTMLTest.php b/includes/kohana/system/tests/kohana/HTMLTest.php
new file mode 100644
index 0000000..77d92e1
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/HTMLTest.php
@@ -0,0 +1,231 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+Class Kohana_HTMLTest extends Kohana_Unittest_TestCase
+{
+ protected $environmentDefault = array(
+ 'Kohana::$base_url' => '/kohana/',
+ 'HTTP_HOST' => 'www.kohanaframework.org',
+ );
+
+ /**
+ * Provides test data for test_attributes()
+ *
+ * @return array
+ */
+ public function provider_attributes()
+ {
+ return array(
+ array(
+ array('name' => 'field', 'random' => 'not_quite', 'id' => 'unique_field'),
+ ' id="unique_field" name="field" random="not_quite"'
+ ),
+ array(
+ array('invalid' => NULL),
+ ''
+ ),
+ array(
+ array(),
+ ''
+ ),
+ array(
+ array('name' => 'field', 'checked'),
+ ' name="field" checked="checked"',
+ ),
+ );
+ }
+
+ /**
+ * Tests HTML::attributes()
+ *
+ * @test
+ * @dataProvider provider_attributes
+ * @param array $attributes Attributes to use
+ * @param string $expected Expected output
+ */
+ public function test_attributes($attributes, $expected)
+ {
+ $this->assertSame(
+ $expected,
+ HTML::attributes($attributes)
+ );
+ }
+
+ /**
+ * Provides test data for test_script
+ *
+ * @return array Array of test data
+ */
+ public function provider_script()
+ {
+ return array(
+ array(
+ '',
+ 'http://google.com/script.js',
+ ),
+ );
+ }
+
+ /**
+ * Tests HTML::script()
+ *
+ * @test
+ * @dataProvider provider_script
+ * @param string $expected Expected output
+ * @param string $file URL to script
+ * @param array $attributes HTML attributes for the anchor
+ * @param bool $index Should the index file be included in url?
+ */
+ public function test_script($expected, $file, array $attributes = NULL, $index = FALSE)
+ {
+ $this->assertSame(
+ $expected,
+ HTML::script($file, $attributes, $index)
+ );
+ }
+
+ /**
+ * Data provider for the style test
+ *
+ * @return array Array of test data
+ */
+ public function provider_style()
+ {
+ return array(
+ array(
+ ' ',
+ 'http://google.com/style.css',
+ array(),
+ FALSE
+ ),
+ );
+ }
+
+ /**
+ * Tests HTML::style()
+ *
+ * @test
+ * @dataProvider provider_style
+ * @param string $expected The expected output
+ * @param string $file The file to link to
+ * @param array $attributes Any extra attributes for the link
+ * @param bool $index Whether the index file should be added to the link
+ */
+ public function test_style($expected, $file, array $attributes = NULL, $index = FALSE)
+ {
+ $this->assertSame(
+ $expected,
+ HTML::style($file, $attributes, $index)
+ );
+ }
+
+ /**
+ * Provides test data for test_obfuscate
+ *
+ * @return array Array of test data
+ */
+ public function provider_obfuscate()
+ {
+ return array(
+ array('something crazy'),
+ array('me@google.com'),
+ );
+ }
+
+ /**
+ * Tests HTML::obfuscate
+ *
+ * @test
+ * @dataProvider provider_obfuscate
+ * @param string $string The string to obfuscate
+ */
+ public function test_obfuscate($string)
+ {
+ $this->assertNotSame(
+ $string,
+ HTML::obfuscate($string)
+ );
+ }
+
+ /**
+ * Provides test data for test_anchor
+ *
+ * @return array Test data
+ */
+ public function provider_anchor()
+ {
+ return array(
+ array(
+ 'Kohana ',
+ array(),
+ 'http://kohanaframework.org',
+ 'Kohana',
+ ),
+ array(
+ 'GOOGLE ',
+ array(),
+ 'http://google.com',
+ 'GOOGLE',
+ array('target' => '_blank'),
+ ),
+ );
+ }
+
+ /**
+ * Tests HTML::anchor
+ *
+ * @test
+ * @dataProvider provider_anchor
+ */
+ public function test_anchor($expected, array $options, $uri, $title = NULL, array $attributes = NULL, $protocol = NULL)
+ {
+ //$this->setEnvironment($options);
+
+ $this->assertSame(
+ $expected,
+ HTML::anchor($uri, $title, $attributes, $protocol)
+ );
+ }
+
+ /**
+ * Data provider for test_file_anchor
+ *
+ * @return array
+ */
+ public function provider_file_anchor()
+ {
+ return array(
+ array(
+ 'My picture file ',
+ array(),
+ 'mypic.png',
+ 'My picture file',
+ )
+ );
+ }
+
+ /**
+ * Test for HTML::file_anchor()
+ *
+ * @test
+ * @covers HTML::file_anchor
+ * @dataProvider provider_file_anchor
+ */
+ public function test_file_anchor($expected, array $options, $file, $title = NULL, array $attributes = NULL, $protocol = NULL)
+ {
+ $this->assertSame(
+ $expected,
+ HTML::file_anchor($file, $title, $attributes, $protocol)
+ );
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/I18nTest.php b/includes/kohana/system/tests/kohana/I18nTest.php
new file mode 100644
index 0000000..fee7a2e
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/I18nTest.php
@@ -0,0 +1,79 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+class Kohana_I18nTest extends Kohana_Unittest_TestCase
+{
+
+ /**
+ * Provides test data for test_lang()
+ *
+ * @return array
+ */
+ public function provider_lang()
+ {
+ return array(
+ // $value, $result
+ array(NULL, 'en-us'),
+ array('es-es', 'es-es'),
+ array(NULL, 'es-es'),
+ );
+ }
+
+ /**
+ * Tests i18n::lang()
+ *
+ * @test
+ * @dataProvider provider_lang
+ * @param boolean $input Input for File::mime
+ * @param boolean $expected Output for File::mime
+ */
+ public function test_open($input, $expected)
+ {
+ $this->assertSame($expected, I18n::lang($input));
+ }
+
+ /**
+ * Provides test data for test_get()
+ *
+ * @return array
+ */
+ public function provider_get()
+ {
+ return array(
+ // $value, $result
+ array('en-us', 'Hello, world!', 'Hello, world!'),
+ array('es-es', 'Hello, world!', '¡Hola, mundo!'),
+ array('fr-fr', 'Hello, world!', 'Bonjour, monde!'),
+ );
+ }
+
+ /**
+ * Tests i18n::get()
+ *
+ * @test
+ * @dataProvider provider_get
+ * @param boolean $input Input for File::mime
+ * @param boolean $expected Output for File::mime
+ */
+ public function test_get($lang, $input, $expected)
+ {
+ I18n::lang($lang);
+ $this->assertSame($expected, I18n::get($input));
+
+ // Test immediate translation, issue #3085
+ I18n::lang('en-us');
+ $this->assertSame($expected, I18n::get($input, $lang));
+ }
+
+}
diff --git a/includes/kohana/system/tests/kohana/InflectorTest.php b/includes/kohana/system/tests/kohana/InflectorTest.php
new file mode 100644
index 0000000..033aa4b
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/InflectorTest.php
@@ -0,0 +1,181 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+class Kohana_InflectorTest extends Kohana_Unittest_TestCase
+{
+ /**
+ * Provides test data for test_lang()
+ *
+ * @return array
+ */
+ public function provider_uncountable()
+ {
+ return array(
+ // $value, $result
+ array('fish', TRUE),
+ array('cat', FALSE),
+ );
+ }
+
+ /**
+ * Tests Inflector::uncountable
+ *
+ * @test
+ * @dataProvider provider_uncountable
+ * @param boolean $input Input for File::mime
+ * @param boolean $expected Output for File::mime
+ */
+ public function test_uncountable($input, $expected)
+ {
+ $this->assertSame($expected, Inflector::uncountable($input));
+ }
+
+ /**
+ * Provides test data for test_lang()
+ *
+ * @return array
+ */
+ public function provider_singular()
+ {
+ return array(
+ // $value, $result
+ array('fish', NULL, 'fish'),
+ array('cats', NULL, 'cat'),
+ array('cats', 2, 'cats'),
+ array('cats', '2', 'cats'),
+ array('children', NULL, 'child'),
+ array('meters', 0.6, 'meters'),
+ array('meters', 1.6, 'meters'),
+ array('meters', 1.0, 'meter'),
+ array('status', NULL, 'status'),
+ array('statuses', NULL, 'status'),
+ array('heroes', NULL, 'hero'),
+ );
+ }
+
+ /**
+ * Tests Inflector::singular
+ *
+ * @test
+ * @dataProvider provider_singular
+ * @param boolean $input Input for File::mime
+ * @param boolean $expected Output for File::mime
+ */
+ public function test_singular($input, $count, $expected)
+ {
+ $this->assertSame($expected, Inflector::singular($input, $count));
+ }
+
+ /**
+ * Provides test data for test_lang()
+ *
+ * @return array
+ */
+ public function provider_plural()
+ {
+ return array(
+ // $value, $result
+ array('fish', NULL, 'fish'),
+ array('cat', NULL, 'cats'),
+ array('cats', 1, 'cats'),
+ array('cats', '1', 'cats'),
+ array('movie', NULL, 'movies'),
+ array('meter', 0.6, 'meters'),
+ array('meter', 1.6, 'meters'),
+ array('meter', 1.0, 'meter'),
+ array('hero', NULL, 'heroes'),
+ array('Dog', NULL, 'Dogs'), // Titlecase
+ array('DOG', NULL, 'DOGS'), // Uppercase
+ );
+ }
+
+ /**
+ * Tests Inflector::plural
+ *
+ * @test
+ * @dataProvider provider_plural
+ * @param boolean $input Input for File::mime
+ * @param boolean $expected Output for File::mime
+ */
+ public function test_plural($input, $count, $expected)
+ {
+ $this->assertSame($expected, Inflector::plural($input, $count));
+ }
+
+ /**
+ * Provides test data for test_camelize()
+ *
+ * @return array
+ */
+ public function provider_camelize()
+ {
+ return array(
+ // $value, $result
+ array('mother cat', 'camelize', 'motherCat'),
+ array('kittens in bed', 'camelize', 'kittensInBed'),
+ array('mother cat', 'underscore', 'mother_cat'),
+ array('kittens in bed', 'underscore', 'kittens_in_bed'),
+ array('kittens-are-cats', 'humanize', 'kittens are cats'),
+ array('dogs_as_well', 'humanize', 'dogs as well'),
+ );
+ }
+
+ /**
+ * Tests Inflector::camelize
+ *
+ * @test
+ * @dataProvider provider_camelize
+ * @param boolean $input Input for File::mime
+ * @param boolean $expected Output for File::mime
+ */
+ public function test_camelize($input, $method, $expected)
+ {
+ $this->assertSame($expected, Inflector::$method($input));
+ }
+
+ /**
+ * Provides data for test_decamelize()
+ *
+ * @return array
+ */
+ public function provider_decamelize()
+ {
+ return array(
+ array('getText', '_', 'get_text'),
+ array('getJSON', '_', 'get_json'),
+ array('getLongText', '_', 'get_long_text'),
+ array('getI18N', '_', 'get_i18n'),
+ array('getL10n', '_', 'get_l10n'),
+ array('getTe5t1ng', '_', 'get_te5t1ng'),
+ array('OpenFile', '_', 'open_file'),
+ array('CloseIoSocket', '_', 'close_io_socket'),
+ array('fooBar', ' ', 'foo bar'),
+ array('camelCase', '+', 'camel+case'),
+ );
+ }
+
+ /**
+ * Tests Inflector::decamelize()
+ *
+ * @test
+ * @dataProvider provider_decamelize
+ * @param string Camelized string
+ * @param string Glue
+ * @param string Expected string
+ */
+ public function test_decamelize($input, $glue, $expected)
+ {
+ $this->assertSame($expected, Inflector::decamelize($input, $glue));
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/LogTest.php b/includes/kohana/system/tests/kohana/LogTest.php
new file mode 100644
index 0000000..f32e532
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/LogTest.php
@@ -0,0 +1,87 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+Class Kohana_LogTest extends Kohana_Unittest_TestCase
+{
+
+ /**
+ * Tests that when a new logger is created the list of messages is initially
+ * empty
+ *
+ * @test
+ * @covers Kohana_Log
+ */
+ public function test_messages_is_initially_empty()
+ {
+ $logger = new Kohana_Log;
+
+ $this->assertAttributeSame(array(), '_messages', $logger);
+ }
+
+ /**
+ * Tests that when a new logger is created the list of writers is initially
+ * empty
+ *
+ * @test
+ * @covers Kohana_Log
+ */
+ public function test_writers_is_initially_empty()
+ {
+ $logger = new Kohana_Log;
+
+ $this->assertAttributeSame(array(), '_writers', $logger);
+ }
+
+ /**
+ * Test that attaching a log writer adds it to the array of log writers
+ *
+ * @TODO Is this test too specific?
+ *
+ * @test
+ * @covers Kohana_Log::attach
+ */
+ public function test_attach_attaches_log_writer_and_returns_this()
+ {
+ $logger = new Kohana_Log;
+ $writer = $this->getMockForAbstractClass('Kohana_Log_Writer');
+
+ $this->assertSame($logger, $logger->attach($writer));
+
+ $this->assertAttributeSame(
+ array(spl_object_hash($writer) => array('object' => $writer, 'types' => NULL)),
+ '_writers',
+ $logger
+ );
+ }
+
+ /**
+ * When we call detach() we expect the specified log writer to be removed
+ *
+ * @test
+ * @covers Kohana_Log::detach
+ */
+ public function test_detach_removes_log_writer_and_returns_this()
+ {
+ $logger = new Kohana_Log;
+ $writer = $this->getMockForAbstractClass('Kohana_Log_Writer');
+
+ $logger->attach($writer);
+
+ $this->assertSame($logger, $logger->detach($writer));
+
+ $this->assertAttributeSame(array(), '_writers', $logger);
+ }
+
+
+}
diff --git a/includes/kohana/system/tests/kohana/ModelTest.php b/includes/kohana/system/tests/kohana/ModelTest.php
new file mode 100644
index 0000000..5692d01
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/ModelTest.php
@@ -0,0 +1,30 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+class Kohana_ModelTest extends Kohana_Unittest_TestCase
+{
+ /**
+ * @test
+ */
+ public function test_construct()
+ {
+ #$model = new Model_Foobar('foo');
+ #$model = Model::factory('Foobar', 'foo');
+ }
+}
+
+class Model_Foobar extends Model
+{
+
+}
diff --git a/includes/kohana/system/tests/kohana/NumTest.php b/includes/kohana/system/tests/kohana/NumTest.php
new file mode 100644
index 0000000..e5af207
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/NumTest.php
@@ -0,0 +1,95 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+Class Kohana_NumTest extends Kohana_Unittest_TestCase
+{
+ protected $default_locale;
+
+ /**
+ * SetUp test enviroment
+ */
+ public function setUp()
+ {
+ parent::setUp();
+
+ setlocale(LC_ALL, 'English');
+ }
+
+ /**
+ * Tear down environment
+ */
+ public function tearDown()
+ {
+ parent::tearDown();
+
+ setlocale(LC_ALL, $this->default_locale);
+ }
+
+ /**
+ * Provides test data for test_ordinal()
+ * @return array
+ */
+ public function provider_ordinal()
+ {
+ return array(
+ array(0, 'th'),
+ array(1, 'st'),
+ array(21, 'st'),
+ array(112, 'th'),
+ array(23, 'rd'),
+ array(42, 'nd'),
+ );
+ }
+
+ /**
+ *
+ * @test
+ * @dataProvider provider_ordinal
+ * @param integer $number
+ * @param $expected
+ */
+ public function test_ordinal($number, $expected)
+ {
+ $this->assertSame($expected, Num::ordinal($number));
+ }
+
+ /**
+ * Provides test data for test_format()
+ * @return array
+ */
+ public function provider_format()
+ {
+ return array(
+ // English
+ array(10000, 2, FALSE, '10,000.00'),
+ array(10000, 2, TRUE, '10,000.00'),
+
+ // Additional dp's should be removed
+ array(123.456, 2, FALSE, '123.46'),
+ array(123.456, 2, TRUE, '123.46'),
+ );
+ }
+
+ /**
+ * @todo test locales
+ * @test
+ * @dataProvider provider_format
+ * @param integer $number
+ * @param integer $places
+ * @param boolean $monetary
+ * @param string $expected
+ */
+ public function test_format($number, $places, $monetary, $expected)
+ {
+ $this->assertSame($expected, Num::format($number, $places, $monetary));
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/RemoteTest.php b/includes/kohana/system/tests/kohana/RemoteTest.php
new file mode 100644
index 0000000..f8131f2
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/RemoteTest.php
@@ -0,0 +1,77 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+class Kohana_RemoteTest extends Kohana_Unittest_TestCase
+{
+ /**
+ * Provides test data for test_get()
+ *
+ * @return array
+ */
+ public function provider_get()
+ {
+ return array(
+ // $value, $result
+ array('', TRUE),
+ array('cat', FALSE),
+ );
+ }
+
+ /**
+ * Tests Remote::get
+ *
+ * @test
+ * @dataProvider provider_get
+ * @param boolean $input Input for File::mime
+ * @param boolean $expected Output for File::mime
+ */
+ public function test_get($input, $expected)
+ {
+ if ( ! $this->hasInternet())
+ $this->markTestSkipped('An internet connection is required for this test');
+
+ #$this->assertSame($expected, Remote::get($input));
+ }
+
+ /**
+ * Provides test data for test_status()
+ *
+ * @return array
+ */
+ public function provider_status()
+ {
+ return array(
+ // $value, $result
+ array('http://kohanaframework.org/', 200),
+ array('http://kohanaframework.org', 200),
+ array('http://kohanaframework.org/foobar', 500),
+ );
+ }
+
+ /**
+ * Tests Remote::status
+ *
+ * @test
+ * @dataProvider provider_status
+ * @param boolean $input Input for File::mime
+ * @param boolean $expected Output for File::mime
+ */
+ public function test_status($input, $expected)
+ {
+ if ( ! $this->hasInternet())
+ $this->markTestSkipped('An internet connection is required for this test');
+
+ $this->assertSame($expected, Remote::status($input));
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/RequestTest.php b/includes/kohana/system/tests/kohana/RequestTest.php
new file mode 100644
index 0000000..3e82462
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/RequestTest.php
@@ -0,0 +1,191 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+class Kohana_RequestTest extends Kohana_Unittest_TestCase
+{
+ /**
+ * Route::matches() should return false if the route doesn't match against a uri
+ *
+ * @test
+ */
+ public function test_create()
+ {
+ $request = Request::factory('foo/bar')->execute();
+
+ $this->assertEquals(200, $request->status);
+ $this->assertEquals('foo', $request->response);
+
+ try
+ {
+ $request = new Request('bar/foo');
+ $request->execute();
+ }
+ catch (Exception $e)
+ {
+ $this->assertEquals(TRUE, $e instanceof ReflectionException);
+ $this->assertEquals('404', $request->status);
+ }
+ }
+
+ /**
+ * Tests Request::accept_type()
+ *
+ * @test
+ * @covers Request::accept_type
+ */
+ public function test_accept_type()
+ {
+ $this->assertEquals(array('*/*' => 1), Request::accept_type());
+ }
+
+ /**
+ * Provides test data for test_instance()
+ *
+ * @return array
+ */
+ public function provider_instance()
+ {
+ return array(
+ // $route, $is_cli, $_server, $status, $response
+ array('foo/bar', TRUE, array(), 200, ''), // Shouldn't this be 'foo' ?
+ array('foo/foo', TRUE, array(), 200, ''), // Shouldn't this be a 404?
+ array(
+ 'foo/bar',
+ FALSE,
+ array(
+ 'REQUEST_METHOD' => 'get',
+ 'HTTP_REFERER' => 'http://www.kohanaframework.org',
+ 'HTTP_USER_AGENT' => 'Kohana Unit Test',
+ 'REMOTE_ADDR' => '127.0.0.1',
+ ), 200, ''), // Shouldn't this be 'foo' ?
+ );
+ }
+
+ /**
+ * Tests Request::instance()
+ *
+ * @test
+ * @dataProvider provider_instance
+ * @covers Request::instance
+ * @param boolean $value Input for Kohana::sanitize
+ * @param boolean $result Output for Kohana::sanitize
+ */
+ public function test_instance($route, $is_cli, $server, $status, $response)
+ {
+ $this->setEnvironment(array(
+ '_SERVER' => $server+array('argc' => $_SERVER['argc']),
+ 'Kohana::$is_cli' => $is_cli,
+ 'Request::$instance' => NULL
+ ));
+
+ $request = Request::instance($route);
+
+ $this->assertEquals($status, $request->status);
+ $this->assertEquals($response, $request->response);
+ $this->assertEquals($route, $request->uri);
+
+ if ( ! $is_cli)
+ {
+ $this->assertEquals($server['REQUEST_METHOD'], Request::$method);
+ $this->assertEquals($server['HTTP_REFERER'], Request::$referrer);
+ $this->assertEquals($server['HTTP_USER_AGENT'], Request::$user_agent);
+ }
+ }
+
+ /**
+ * Provides test data for Request::accept_lang()
+ * @return array
+ */
+ public function provider_accept_lang()
+ {
+ return array(
+ array('en-us', 1, array('_SERVER' => array('HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5'))),
+ array('en-us', 1, array('_SERVER' => array('HTTP_ACCEPT_LANGUAGE' => 'en-gb'))),
+ array('en-us', 1, array('_SERVER' => array('HTTP_ACCEPT_LANGUAGE' => 'sp-sp;q=0.5')))
+ );
+ }
+
+ /**
+ * Tests Request::accept_lang()
+ *
+ * @test
+ * @covers Request::accept_lang
+ * @dataProvider provider_accept_lang
+ * @param array $params Query string
+ * @param string $expected Expected result
+ * @param array $enviroment Set environment
+ */
+ public function test_accept_lang($params, $expected, $enviroment)
+ {
+ $this->setEnvironment($enviroment);
+
+ $this->assertEquals(
+ $expected,
+ Request::accept_lang($params)
+ );
+ }
+
+ /**
+ * Provides test data for Request::url()
+ * @return array
+ */
+ public function provider_url()
+ {
+ return array(
+ array(
+ 'foo/bar',
+ array(),
+ 'http',
+ TRUE,
+ 'http://localhost/kohana/foo/bar'
+ ),
+ array(
+ 'foo',
+ array('action' => 'bar'),
+ 'http',
+ TRUE,
+ 'http://localhost/kohana/foo/bar'
+ ),
+ );
+ }
+
+ /**
+ * Tests Request::url()
+ *
+ * @test
+ * @dataProvider provider_url
+ * @covers Request::url
+ * @param string $route the route to use
+ * @param array $params params to pass to route::uri
+ * @param string $protocol the protocol to use
+ * @param array $expected The string we expect
+ */
+ public function test_url($uri, $params, $protocol, $is_cli, $expected)
+ {
+ $this->setEnvironment(array(
+ 'Kohana::$base_url' => '/kohana/',
+ '_SERVER' => array('HTTP_HOST' => 'localhost', 'argc' => $_SERVER['argc']),
+ 'Kohana::$index_file' => FALSE,
+ 'Kohana::$is_cli' => $is_cli,
+ ));
+
+ $this->assertEquals(Request::instance($uri)->url($params, $protocol), $expected);
+ }
+}
+
+class Controller_Foo extends Controller {
+ public function action_bar()
+ {
+ $this->request->response = 'foo';
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/RouteTest.php b/includes/kohana/system/tests/kohana/RouteTest.php
new file mode 100644
index 0000000..e62c748
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/RouteTest.php
@@ -0,0 +1,416 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+class Kohana_RouteTest extends Kohana_Unittest_TestCase
+{
+ /**
+ * Remove all caches
+ */
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->cleanCacheDir();
+ }
+
+ /**
+ * Removes cache files created during tests
+ */
+ public function tearDown()
+ {
+ parent::tearDown();
+
+ $this->cleanCacheDir();
+ }
+
+ /**
+ * If Route::get() is asked for a route that does not exist then
+ * it should throw a Kohana_Exception
+ *
+ * Note use of @expectedException
+ *
+ * @test
+ * @covers Route::get
+ * @expectedException Kohana_Exception
+ */
+ public function test_get_throws_exception_if_route_dnx()
+ {
+ Route::get('HAHAHAHAHAHAHAHAHA');
+ }
+
+ /**
+ * Route::all() should return all routes defined via Route::set()
+ * and not through new Route()
+ *
+ * @test
+ * @covers Route::all
+ */
+ public function test_all_returns_all_defined_routes()
+ {
+ $defined_routes = self::readAttribute('Route', '_routes');
+
+ $this->assertSame($defined_routes, Route::all());
+ }
+
+ /**
+ * Route::name() should fetch the name of a passed route
+ * If route is not found then it should return FALSE
+ *
+ * @TODO: This test needs to segregate the Route::$_routes singleton
+ * @test
+ * @covers Route::name
+ */
+ public function test_name_returns_routes_name_or_false_if_dnx()
+ {
+ $route = Route::set('flamingo_people', 'flamingo/dance');
+
+ $this->assertSame('flamingo_people', Route::name($route));
+
+ $route = new Route('dance/dance');
+
+ $this->assertFalse(Route::name($route));
+ }
+
+ /**
+ * If Route::cache() was able to restore routes from the cache then
+ * it should return TRUE and load the cached routes
+ *
+ * @test
+ * @covers Route::cache
+ */
+ public function test_cache_stores_route_objects()
+ {
+ $routes = Route::all();
+
+ // First we create the cache
+ Route::cache(TRUE);
+
+ // Now lets modify the "current" routes
+ Route::set('nonsensical_route', 'flabbadaga/ding_dong');
+
+ // Then try and load said cache
+ $this->assertTrue(Route::cache());
+
+ // And if all went ok the nonsensical route should be gone...
+ $this->assertEquals($routes, Route::all());
+ }
+
+ /**
+ * Route::cache() should return FALSE if cached routes could not be found
+ *
+ * The cache is cleared before and after each test in setUp tearDown
+ * by cleanCacheDir()
+ *
+ * @test
+ * @covers Route::cache
+ */
+ public function test_cache_returns_false_if_cache_dnx()
+ {
+ $this->assertSame(FALSE, Route::cache(), 'Route cache was not empty');
+ }
+
+ /**
+ * If the constructor is passed a NULL uri then it should assume it's
+ * being loaded from the cache & therefore shouldn't override the cached attributes
+ *
+ * @test
+ * @covers Route::__construct
+ */
+ public function test_constructor_returns_if_uri_is_null()
+ {
+ // We use a mock object to make sure that the route wasn't recompiled
+ $route = $this->getMock('Route', array('_compile'), array(), '', FALSE);
+
+ $route
+ ->expects($this->never())
+ ->method('_compile');
+
+ $route->__construct(NULL,NULL);
+
+ $this->assertAttributeSame('', '_uri', $route);
+ $this->assertAttributeSame(array(), '_regex', $route);
+ $this->assertAttributeSame(array('action' => 'index'), '_defaults', $route);
+ $this->assertAttributeSame(NULL, '_route_regex', $route);
+ }
+
+ /**
+ * The constructor should only use custom regex if passed a non-empty array
+ *
+ * Technically we can't "test" this as the default regex is an empty array, this
+ * is purely for improving test coverage
+ *
+ * @test
+ * @covers Route::__construct
+ */
+ public function test_constructor_only_changes_custom_regex_if_passed()
+ {
+ $route = new Route('/', array());
+
+ $this->assertAttributeSame(array(), '_regex', $route);
+
+ $route = new Route('/', NULL);
+
+ $this->assertAttributeSame(array(), '_regex', $route);
+ }
+
+ /**
+ * When we pass custom regex to the route's constructor it should it
+ * in leu of the default
+ *
+ * @test
+ * @covers Route::__construct
+ * @covers Route::_compile
+ */
+ public function test_route_uses_custom_regex_passed_to_constructor()
+ {
+ $regex = array('id' => '[0-9]{1,2}');
+
+ $route = new Route('(/(/))', $regex);
+
+ $this->assertAttributeSame($regex, '_regex', $route);
+ $this->assertAttributeContains(
+ $regex['id'],
+ '_route_regex',
+ $route
+ );
+ }
+
+ /**
+ * Route::matches() should return false if the route doesn't match against a uri
+ *
+ * @test
+ * @covers Route::matches
+ */
+ public function test_matches_returns_false_on_failure()
+ {
+ $route = new Route('projects/(/((/(/))))');
+
+ $this->assertSame(FALSE, $route->matches('apple/pie'));
+ }
+
+ /**
+ * Route::matches() should return an array of parameters when a match is made
+ * An parameters that are not matched should not be present in the array of matches
+ *
+ * @test
+ * @covers Route::matches
+ */
+ public function test_matches_returns_array_of_parameters_on_successful_match()
+ {
+ $route = new Route('((/(/)))');
+
+ $matches = $route->matches('welcome/index');
+
+ $this->assertInternalType('array', $matches);
+ $this->assertArrayHasKey('controller', $matches);
+ $this->assertArrayHasKey('action', $matches);
+ $this->assertArrayNotHasKey('id', $matches);
+ $this->assertSame(2, count($matches));
+ $this->assertSame('welcome', $matches['controller']);
+ $this->assertSame('index', $matches['action']);
+ }
+
+ /**
+ * Defaults specified with defaults() should be used if their values aren't
+ * present in the uri
+ *
+ * @test
+ * @covers Route::matches
+ */
+ public function test_defaults_are_used_if_params_arent_specified()
+ {
+ $route = new Route('((/(/)))');
+ $route->defaults(array('controller' => 'welcome', 'action' => 'index'));
+
+ $matches = $route->matches('');
+
+ $this->assertInternalType('array', $matches);
+ $this->assertArrayHasKey('controller', $matches);
+ $this->assertArrayHasKey('action', $matches);
+ $this->assertArrayNotHasKey('id', $matches);
+ $this->assertSame(2, count($matches));
+ $this->assertSame('welcome', $matches['controller']);
+ $this->assertSame('index', $matches['action']);
+ $this->assertSame('unit/test/1', $route->uri(array(
+ 'controller' => 'unit',
+ 'action' => 'test',
+ 'id' => '1'
+ )));
+ $this->assertSame('welcome/index', $route->uri());
+ }
+
+ /**
+ * This tests that routes with required parameters will not match uris without them present
+ *
+ * @test
+ * @covers Route::matches
+ */
+ public function test_required_parameters_are_needed()
+ {
+ $route = new Route('admin(/(/(/)))');
+
+ $this->assertFalse($route->matches(''));
+
+ $matches = $route->matches('admin');
+
+ $this->assertInternalType('array', $matches);
+
+ $matches = $route->matches('admin/users/add');
+
+ $this->assertSame(2, count($matches));
+ $this->assertInternalType('array', $matches);
+ $this->assertArrayHasKey('controller', $matches);
+ $this->assertArrayHasKey('action', $matches);
+ }
+
+ /**
+ * This tests the reverse routing returns the uri specified in the route
+ * if it's a static route
+ *
+ * A static route is a route without any parameters
+ *
+ * @test
+ * @covers Route::uri
+ */
+ public function test_reverse_routing_returns_routes_uri_if_route_is_static()
+ {
+ $route = new Route('info/about_us');
+
+ $this->assertSame('info/about_us', $route->uri(array('some' => 'random', 'params' => 'to confuse')));
+ }
+
+ /**
+ * When Route::uri is working on a uri that requires certain parameters to be present
+ * (i.e. in '(/uri(array('action' => 'awesome-action'));
+
+ $this->fail('Route::uri should throw exception if required param is not provided');
+ }
+ catch(Exception $e)
+ {
+ $this->assertInstanceOf('Kohana_Exception', $e);
+ // Check that the error in question is about the controller param
+ $this->assertContains('controller', $e->getMessage());
+ }
+ }
+
+ /**
+ * The logic for replacing required segments is separate (but similar) to that for
+ * replacing optional segments.
+ *
+ * This test asserts that Route::uri will replace required segments with provided
+ * params
+ *
+ * @test
+ * @covers Route::uri
+ */
+ public function test_uri_fills_required_uri_segments_from_params()
+ {
+ $route = new Route('/(/)');
+
+ $this->assertSame(
+ 'users/edit',
+ $route->uri(array(
+ 'controller' => 'users',
+ 'action' => 'edit',
+ ))
+ );
+
+ $this->assertSame(
+ 'users/edit/god',
+ $route->uri(array(
+ 'controller' => 'users',
+ 'action' => 'edit',
+ 'id' => 'god',
+ ))
+ );
+ }
+
+ /**
+ * Provides test data for test_composing_url_from_route()
+ * @return array
+ */
+ public function provider_composing_url_from_route()
+ {
+ return array(
+ array('/welcome'),
+ array('/news/view/42', array('controller' => 'news', 'action' => 'view', 'id' => 42)),
+ array('http://kohanaframework.org/news', array('controller' => 'news'), true)
+ );
+ }
+
+ /**
+ * Tests Route::url()
+ *
+ * Checks the url composing from specific route via Route::url() shortcut
+ *
+ * @test
+ * @dataProvider provider_composing_url_from_route
+ * @param string $expected
+ * @param array $params
+ * @param boolean $protocol
+ */
+ public function test_composing_url_from_route($expected, $params = NULL, $protocol = NULL)
+ {
+ Route::set('foobar', '((/(/)))')
+ ->defaults(array(
+ 'controller' => 'welcome',
+ )
+ );
+
+ $this->setEnvironment(array(
+ '_SERVER' => array('HTTP_HOST' => 'kohanaframework.org'),
+ 'Kohana::$base_url' => '/',
+ 'Request::$protocol' => 'http',
+ 'Kohana::$index_file' => '',
+ ));
+
+ $this->assertSame($expected, Route::url('foobar', $params, $protocol));
+ }
+
+ /**
+ * Tests Route::_compile()
+ *
+ * Makes sure that compile will use custom regex if specified
+ *
+ * @test
+ * @covers Route::_compile
+ */
+ public function test_compile_uses_custom_regex_if_specificed()
+ {
+ $route = new Route(
+ '(/(/))',
+ array(
+ 'controller' => '[a-z]+',
+ 'id' => '\d+',
+ )
+ );
+
+ $this->assertAttributeSame(
+ '#^(?P[a-z]+)(?:/(?P[^/.,;?\n]++)(?:/(?P\d+))?)?$#uD',
+ '_route_regex',
+ $route
+ );
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/SecurityTest.php b/includes/kohana/system/tests/kohana/SecurityTest.php
new file mode 100644
index 0000000..830aa1a
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/SecurityTest.php
@@ -0,0 +1,105 @@
+"),
+ );
+ }
+
+ /**
+ * Tests Security::encode_php_tags()
+ *
+ * @test
+ * @dataProvider provider_encode_php_tags
+ * @covers Security::encode_php_tags
+ */
+ public function test_encode_php_tags($expected, $input)
+ {
+ $this->assertSame($expected, Security::encode_php_tags($input));
+ }
+
+ /**
+ * Provides test data for test_strip_image_tags()
+ *
+ * @return array Test data sets
+ */
+ public function provider_strip_image_tags()
+ {
+ return array(
+ array('foo', ' '),
+ );
+ }
+
+ /**
+ * Tests Security::strip_image_tags()
+ *
+ * @test
+ * @dataProvider provider_strip_image_tags
+ * @covers Security::strip_image_tags
+ */
+ public function test_strip_image_tags($expected, $input)
+ {
+ $this->assertSame($expected, Security::strip_image_tags($input));
+ }
+
+ /**
+ * Provides test data for Security::token()
+ *
+ * @return array Test data sets
+ */
+ public function provider_csrf_token()
+ {
+ $array = array();
+ for ($i = 0; $i <= 4; $i++)
+ {
+ Security::$token_name = 'token_'.$i;
+ $array[] = array(Security::token(TRUE), Security::check(Security::token(FALSE)), $i);
+ }
+ return $array;
+ }
+
+ /**
+ * Tests Security::token()
+ *
+ * @test
+ * @dataProvider provider_csrf_token
+ * @covers Security::token
+ */
+ public function test_csrf_token($expected, $input, $iteration)
+ {
+ Security::$token_name = 'token_'.$iteration;
+ $this->assertSame(TRUE, $input);
+ $this->assertSame($expected, Security::token(FALSE));
+ Session::instance()->delete(Security::$token_name);
+ }
+
+ /**
+ * Tests that Security::xss_clean() removes null bytes
+ *
+ *
+ * @test
+ * @covers Security::xss_clean
+ * @ticket 2676
+ * @see http://www.hakipedia.com/index.php/Poison_Null_Byte#Perl_PHP_Null_Byte_Injection
+ */
+ public function test_xss_clean_removes_null_bytes()
+ {
+ $input = "<\0script>alert('XSS');<\0/script>";
+
+ $this->assertSame("alert('XSS');", Security::xss_clean($input));
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/SessionTest.php b/includes/kohana/system/tests/kohana/SessionTest.php
new file mode 100644
index 0000000..79c83a1
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/SessionTest.php
@@ -0,0 +1,497 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+Class Kohana_SessionTest extends Kohana_Unittest_TestCase
+{
+
+ /**
+ * Gets a mock of the session class
+ *
+ * @return Session
+ */
+ public function getMockSession(array $config = array())
+ {
+ return $this->getMockForAbstractClass('Session', array($config));
+ }
+
+ /**
+ * Provides test data for
+ *
+ * test_constructor_uses_name_from_config_and_casts()
+ *
+ * @return array
+ */
+ public function provider_constructor_uses_settings_from_config_and_casts()
+ {
+ return array(
+ // array(expected, input)
+ // data set 0
+ array(
+ array(
+ 'name' => 'awesomeness',
+ 'lifetime' => 1231456421,
+ 'encrypted' => FALSE
+ ),
+ array(
+ 'name' => 'awesomeness',
+ 'lifetime' => '1231456421',
+ 'encrypted' => FALSE,
+ ),
+ ),
+ // data set 1
+ array(
+ array(
+ 'name' => '123',
+ 'encrypted' => 'default',
+ ),
+ array(
+ 'name' => 123,
+ 'encrypted' => TRUE,
+ ),
+ ),
+ );
+ }
+
+ /**
+ * The constructor should change its attributes based on config
+ * passed as the first parameter
+ *
+ * @test
+ * @dataProvider provider_constructor_uses_settings_from_config_and_casts
+ * @covers Session::__construct
+ */
+ public function test_constructor_uses_settings_from_config_and_casts($expected, $config)
+ {
+ $session = $this->getMockForAbstractClass('Session', array($config));
+
+ foreach($expected as $var => $value)
+ {
+ $this->assertAttributeSame($value, '_'.$var, $session);
+ }
+ }
+
+ /**
+ * Check that the constructor will load a session if it's provided
+ * witha session id
+ *
+ * @test
+ * @covers Session::__construct
+ * @covers Session::read
+ */
+ public function test_constructor_loads_session_with_session_id()
+ {
+ $this->markTestIncomplete(
+ 'Need to work out why constructor is not being called'
+ );
+
+ $config = array();
+ $session_id = 'lolums';
+
+ // Don't auto-call constructor, we need to setup the mock first
+ $session = $this->getMockForAbstractClass(
+ 'Session',
+ array(),
+ '',
+ FALSE
+ );
+
+ $session
+ ->expects($this->once())
+ ->method('read')
+ ->with($session_id);
+
+ $session->__construct($config, $session_id);
+ }
+
+ /**
+ * Calling $session->bind() should allow you to bind a variable
+ * to a session variable
+ *
+ * @test
+ * @covers Session::bind
+ * @ticket 3164
+ */
+ public function test_bind_actually_binds_variable()
+ {
+ $session = $this->getMockForAbstractClass('Session');
+
+ $var = 'asd';
+
+ $session->bind('our_var', $var);
+
+ $var = 'foobar';
+
+ $this->assertSame('foobar', $session->get('our_var'));
+ }
+
+
+ /**
+ * When a session is initially created it should have no data
+ *
+ *
+ * @test
+ * @covers Session::__construct
+ * @covers Session::set
+ */
+ public function test_initially_session_has_no_data()
+ {
+ $session = $this->getMockSession();
+
+ $this->assertAttributeSame(array(), '_data', $session);
+ }
+
+ /**
+ * Make sure that the default session name (the one used if the
+ * driver does not set one) is 'session'
+ *
+ * @test
+ * @covers Session::__construct
+ */
+ public function test_default_session_name_is_set()
+ {
+ $session = $this->getMockSession();
+
+ $this->assertAttributeSame('session', '_name', $session);
+ }
+
+ /**
+ * By default sessions are unencrypted
+ *
+ * @test
+ * @covers Session::__construct
+ */
+ public function test_default_session_is_unencrypted()
+ {
+ $session = $this->getMockSession();
+
+ $this->assertAttributeSame(FALSE, '_encrypted', $session);
+ }
+
+ /**
+ * A new session should not be classed as destroyed
+ *
+ * @test
+ * @covers Session::__construct
+ */
+ public function test_default_session_is_not_classed_as_destroyed()
+ {
+ $session = $this->getMockSession();
+
+ $this->assertAttributeSame(FALSE, '_destroyed', $session);
+ }
+
+ /**
+ * Provides test data for test_get_returns_default_if_var_dnx()
+ *
+ * @return array
+ */
+ public function provider_get_returns_default_if_var_dnx()
+ {
+ return array(
+ array('something_crazy', FALSE),
+ array('a_true', TRUE),
+ array('an_int', 158163158),
+ );
+ }
+
+ /**
+ * Make sure that get() is using the default value we provide and
+ * isn't tampering with it
+ *
+ * @test
+ * @dataProvider provider_get_returns_default_if_var_dnx
+ * @covers Session::get
+ */
+ public function test_get_returns_default_if_var_dnx($var, $default)
+ {
+ $session = $this->getMockSession();
+
+ $this->assertSame($default, $session->get($var, $default));
+ }
+
+ /**
+ * By default get() should be using null as the var DNX return value
+ *
+ * @test
+ * @covers Session::get
+ */
+ public function test_get_uses_null_as_default_return_value()
+ {
+ $session = $this->getMockSession();
+
+ $this->assertSame(NULL, $session->get('level_of_cool'));
+ }
+
+ /**
+ * This test makes sure that session is using array_key_exists
+ * as isset will return FALSE if the value is NULL
+ *
+ * @test
+ * @covers Session::get
+ */
+ public function test_get_returns_value_if_it_equals_null()
+ {
+ $session = $this->getMockSession();
+
+ $session->set('arkward', NULL);
+
+ $this->assertSame(NULL, $session->get('arkward', 'uh oh'));
+ }
+
+ /**
+ * as_array() should return the session data by reference.
+ *
+ * i.e. if we modify the returned data, the session data also changes
+ *
+ * @test
+ * @covers Session::as_array
+ */
+ public function test_as_array_returns_data_by_ref_or_copy()
+ {
+ $session = $this->getMockSession();
+
+ $data_ref =& $session->as_array();
+
+ $data_ref['something'] = 'pie';
+
+ $this->assertAttributeSame($data_ref, '_data', $session);
+
+ $data_copy = $session->as_array();
+
+ $data_copy['pie'] = 'awesome';
+
+ $this->assertAttributeNotSame($data_copy, '_data', $session);
+ }
+
+ /**
+ * set() should add new session data and modify existing ones
+ *
+ * Also makes sure that set() returns $this
+ *
+ * @test
+ * @covers Session::set
+ */
+ public function test_set_adds_and_modifies_to_session_data()
+ {
+ $session = $this->getMockSession();
+
+ $this->assertSame($session, $session->set('pork', 'pie'));
+
+ $this->assertAttributeSame(
+ array('pork' => 'pie'),
+ '_data',
+ $session
+ );
+
+ $session->set('pork', 'delicious');
+
+ $this->assertAttributeSame(
+ array('pork' => 'delicious'),
+ '_data',
+ $session
+ );
+ }
+
+ /**
+ * This tests that delete() removes specified session data
+ *
+ * @test
+ * @covers Session::delete
+ */
+ public function test_delete_removes_select_session_data()
+ {
+ $session = $this->getMockSession();
+
+ // Bit of a hack for mass-loading session data
+ $data =& $session->as_array();
+
+ $data += array(
+ 'a' => 'A',
+ 'b' => 'B',
+ 'c' => 'C',
+ 'easy' => '123'
+ );
+
+ // Make a copy of $data for testing purposes
+ $copy = $data;
+
+ // First we make sure we can delete one item
+ // Also, check that delete returns $this
+ $this->assertSame($session, $session->delete('a'));
+
+ unset($copy['a']);
+
+ // We could test against $data but then we'd be testing
+ // that as_array() is returning by ref
+ $this->assertAttributeSame($copy, '_data', $session);
+
+ // Now we make sure we can delete multiple items
+ // We're checking $this is returned just in case
+ $this->assertSame($session, $session->delete('b', 'c'));
+ unset($copy['b'], $copy['c']);
+
+ $this->assertAttributeSame($copy, '_data', $session);
+ }
+
+ /**
+ * Provides test data for test_read_loads_session_data()
+ *
+ * @return array
+ */
+ public function provider_read_loads_session_data()
+ {
+ return array(
+ // If driver returns array then just load it up
+ array(
+ array(),
+ 'wacka_wacka',
+ array()
+ ),
+ array(
+ array('the it' => 'crowd'),
+ 'the_it_crowd',
+ array('the it' => 'crowd'),
+ ),
+ // If it's a string an encrpytion is disabled (by default) base64decode and unserialize
+ array(
+ array('dead' => 'arrival'),
+ 'lolums',
+ 'YToxOntzOjQ6ImRlYWQiO3M6NzoiYXJyaXZhbCI7fQ=='
+ ),
+ );
+ }
+
+ /**
+ * This is one of the "big" tests for the session lib
+ *
+ * The test makes sure that
+ *
+ * 1. Session asks the driver for the data relating to $session_id
+ * 2. That it will load the returned data into the session
+ *
+ * @test
+ * @dataProvider provider_read_loads_session_data
+ * @covers Session::read
+ */
+ public function test_read_loads_session_data($expected_data, $session_id, $driver_data, array $config = array())
+ {
+ $session = $this->getMockSession($config);
+
+ $session->expects($this->once())
+ ->method('_read')
+ ->with($session_id)
+ ->will($this->returnValue($driver_data));
+
+ $session->read($session_id);
+ $this->assertAttributeSame($expected_data, '_data', $session);
+ }
+
+ /**
+ * regenerate() should tell the driver to regenerate its id
+ *
+ * @test
+ * @covers Session::regenerate
+ */
+ public function test_regenerate_tells_driver_to_regenerate()
+ {
+ $session = $this->getMockSession();
+
+ $new_session_id = 'asdnoawdnoainf';
+
+ $session->expects($this->once())
+ ->method('_regenerate')
+ ->with()
+ ->will($this->returnValue($new_session_id));
+
+ $this->assertSame($new_session_id, $session->regenerate());
+ }
+
+ /**
+ * If the driver destroys the session then all session data should be
+ * removed
+ *
+ * @test
+ * @covers Session::destroy
+ */
+ public function test_destroy_deletes_data_if_driver_destroys_session()
+ {
+ $session = $this->getMockSession();
+
+ $session
+ ->set('asd', 'dsa')
+ ->set('dog', 'god');
+
+ $session
+ ->expects($this->once())
+ ->method('_destroy')
+ ->with()
+ ->will($this->returnValue(TRUE));
+
+ $this->assertTrue($session->destroy());
+
+ $this->assertAttributeSame(array(), '_data', $session);
+ }
+
+ /**
+ * The session data should only be deleted if the driver reports
+ * that the session was destroyed ok
+ *
+ * @test
+ * @covers Session::destroy
+ */
+ public function test_destroy_only_deletes_data_if_driver_destroys_session()
+ {
+ $session = $this->getMockSession();
+
+ $session
+ ->set('asd', 'dsa')
+ ->set('dog', 'god');
+
+ $session
+ ->expects($this->once())
+ ->method('_destroy')
+ ->with()
+ ->will($this->returnValue(FALSE));
+
+ $this->assertFalse($session->destroy());
+ $this->assertAttributeSame(
+ array('asd' => 'dsa', 'dog' => 'god'),
+ '_data',
+ $session
+ );
+ }
+
+ /**
+ * If a session variable exists then get_once should get it then remove it.
+ * If the variable does not exist then it should return the default
+ *
+ * @test
+ * @covers Session::get_once
+ */
+ public function test_get_once_gets_once_or_returns_default()
+ {
+ $session = $this->getMockSession();
+
+ $session->set('foo', 'bar');
+
+ // Test that a default is returned
+ $this->assertSame('mud', $session->get_once('fud', 'mud'));
+
+ // Now test that it actually removes the value
+ $this->assertSame('bar', $session->get_once('foo'));
+
+ $this->assertAttributeSame(array(), '_data', $session);
+
+ $this->assertSame('maybe', $session->get_once('foo', 'maybe'));
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/TextTest.php b/includes/kohana/system/tests/kohana/TextTest.php
new file mode 100644
index 0000000..1694aea
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/TextTest.php
@@ -0,0 +1,596 @@
+assertSame('', Text::auto_p(''));
+ }
+
+ /**
+ *
+ * @return array Test Data
+ */
+ public function provider_auto_para_does_not_enclose_html_tags_in_paragraphs()
+ {
+ return array(
+ array(
+ array('div'),
+ 'Pick a plum of peppers
',
+ ),
+ array(
+ array('div'),
+ 'Tangas
',
+ ),
+ );
+ }
+
+ /**
+ * This test makes sure that auto_p doesn't enclose HTML tags
+ * in paragraphs
+ *
+ * @test
+ * @covers Text::auto_p
+ * @dataProvider provider_auto_para_does_not_enclose_html_tags_in_paragraphs
+ */
+ public function test_auto_para_does_not_enclose_html_tags_in_paragraphs(array $tags, $text)
+ {
+ $output = Text::auto_p($text);
+
+ foreach($tags as $tag)
+ {
+ $this->assertNotTag(
+ array('tag' => $tag, 'ancestor' => array('tag' => 'p')),
+ $output
+ );
+ }
+ }
+
+ /**
+ * This test makes sure that auto_p surrounds a single line of text
+ * with paragraph tags
+ *
+ * @test
+ * @covers Text::auto_p
+ */
+ public function test_auto_para_encloses_slot_in_paragraph()
+ {
+ $text = 'Pick a pinch of purple pepper';
+
+ $this->assertSame(''.$text.'
', Text::auto_p($text));
+ }
+
+ /**
+ * Make sure that multiple new lines are replaced with paragraph tags
+ *
+ * @test
+ * @covers Text::auto_p
+ */
+ public function test_auto_para_replaces_multiple_newlines_with_paragraph()
+ {
+ $this->assertSame(
+ "My name is john
\n\nI'm a developer
",
+ Text::auto_p("My name is john\n\n\n\nI'm a developer")
+ );
+ }
+
+ /**
+ * Data provider for test_limit_words
+ *
+ * @return array Array of test data
+ */
+ public function provider_limit_words()
+ {
+ return array
+ (
+ array('', '', 100, NULL),
+ array('…', 'The rain in spain', -10, NULL),
+ array('The rain…', 'The rain in spain', 2, NULL),
+ array('The rain...', 'The rain in spain', 2, '...'),
+ );
+ }
+
+ /**
+ *
+ * @test
+ * @dataProvider provider_limit_words
+ */
+ public function test_limit_words($expected, $str, $limit, $end_char)
+ {
+ $this->assertSame($expected, Text::limit_words($str, $limit, $end_char));
+ }
+
+ /**
+ * Provides test data for test_limit_chars()
+ *
+ * @return array Test data
+ */
+ public function provider_limit_chars()
+ {
+ return array
+ (
+ array('', '', 100, NULL, FALSE),
+ array('…', 'BOO!', -42, NULL, FALSE),
+ array('making php bet…', 'making php better for the sane', 14, NULL, FALSE),
+ array('Garçon! Un café s.v.p.', 'Garçon! Un café s.v.p.', 50, '__', FALSE),
+ array('Garçon!__', 'Garçon! Un café s.v.p.', 8, '__', FALSE),
+ // @issue 3238
+ array('making php…', 'making php better for the sane', 14, NULL, TRUE),
+ array('Garçon!__', 'Garçon! Un café s.v.p.', 9, '__', TRUE),
+ array('Garçon!__', 'Garçon! Un café s.v.p.', 7, '__', TRUE),
+ array('__', 'Garçon! Un café s.v.p.', 5, '__', TRUE),
+ );
+ }
+
+ /**
+ * Tests Text::limit_chars()
+ *
+ * @test
+ * @dataProvider provider_limit_chars
+ */
+ public function test_limit_chars($expected, $str, $limit, $end_char, $preserve_words)
+ {
+ $this->assertSame($expected, Text::limit_chars($str, $limit, $end_char, $preserve_words));
+ }
+
+ /**
+ * Test Text::alternate()
+ *
+ * @test
+ */
+ public function test_alternate_alternates_between_parameters()
+ {
+ list($val_a, $val_b, $val_c) = array('good', 'bad', 'ugly');
+
+ $this->assertSame('good', Text::alternate($val_a, $val_b, $val_c));
+ $this->assertSame('bad', Text::alternate($val_a, $val_b, $val_c));
+ $this->assertSame('ugly', Text::alternate($val_a, $val_b, $val_c));
+
+ $this->assertSame('good', Text::alternate($val_a, $val_b, $val_c));
+ }
+
+ /**
+ * Tests Text::alternate()
+ *
+ * @test
+ * @covers Text::alternate
+ */
+ public function test_alternate_resets_when_called_with_no_params_and_returns_empty_string()
+ {
+ list($val_a, $val_b, $val_c) = array('yes', 'no', 'maybe');
+
+ $this->assertSame('yes', Text::alternate($val_a, $val_b, $val_c));
+
+ $this->assertSame('', Text::alternate());
+
+ $this->assertSame('yes', Text::alternate($val_a, $val_b, $val_c));
+ }
+
+ /**
+ * Provides test data for test_reducde_slashes()
+ *
+ * @returns array Array of test data
+ */
+ public function provider_reduce_slashes()
+ {
+ return array
+ (
+ array('/', '//'),
+ array('/google/php/kohana/', '//google/php//kohana//'),
+ );
+ }
+
+ /**
+ * Covers Text::reduce_slashes()
+ *
+ * @test
+ * @dataProvider provider_reduce_slashes
+ */
+ public function test_reduce_slashes($expected, $str)
+ {
+ $this->assertSame($expected, Text::reduce_slashes($str));
+ }
+
+ /**
+ * Provides test data for test_censor()
+ *
+ * @return array Test data
+ */
+ public function provider_censor()
+ {
+
+ return array
+ (
+ // If the replacement is 1 character long it should be repeated for the length of the removed word
+ array("A donkey is also an ***", 'A donkey is also an ass', array('ass'), '*', TRUE),
+ array("Cake### isn't nearly as good as kohana###", "CakePHP isn't nearly as good as kohanaphp", array('php'), '#', TRUE),
+ // If it's > 1 then it's just replaced straight out
+ array("If you're born out of wedlock you're a --expletive--", "If you're born out of wedlock you're a child", array('child'), '--expletive--', TRUE),
+
+ array('class', 'class', array('ass'), '*', FALSE),
+ );
+ }
+
+ /**
+ * Tests Text::censor
+ *
+ * @test
+ * @dataProvider provider_censor
+ */
+ public function test_censor($expected, $str, $badwords, $replacement, $replace_partial_words)
+ {
+ $this->assertSame($expected, Text::censor($str, $badwords, $replacement, $replace_partial_words));
+ }
+
+ /**
+ * Provides test data for test_random
+ *
+ * @return array Test Data
+ */
+ public function provider_random()
+ {
+ return array(
+ array('alnum', 8),
+ array('alpha', 10),
+ array('hexdec', 20),
+ array('nozero', 5),
+ array('numeric', 14),
+ array('distinct', 12),
+ array('aeiou', 4),
+ array('‹¡›«¿»', 8), // UTF8 characters
+ array(NULL, 8), // Issue #3256
+ );
+ }
+
+ /**
+ * Tests Text::random() as well as possible
+ *
+ * Obviously you can't compare a randomly generated string against a
+ * pre-generated one and check that they are the same as this goes
+ * against the whole ethos of random.
+ *
+ * This test just makes sure that the value returned is of the correct
+ * values and length
+ *
+ * @test
+ * @dataProvider provider_random
+ */
+ public function test_random($type, $length)
+ {
+ if ($type === NULL)
+ {
+ $type = 'alnum';
+ }
+
+ $pool = (string) $type;
+
+ switch ($pool)
+ {
+ case 'alnum':
+ $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ break;
+ case 'alpha':
+ $pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ break;
+ case 'hexdec':
+ $pool = '0123456789abcdef';
+ break;
+ case 'numeric':
+ $pool = '0123456789';
+ break;
+ case 'nozero':
+ $pool = '123456789';
+ break;
+ case 'distinct':
+ $pool = '2345679ACDEFHJKLMNPRSTUVWXYZ';
+ break;
+ }
+
+ $this->assertRegExp('/^['.$pool.']{'.$length.'}$/u', Text::random($type, $length));
+ }
+
+ /**
+ * Provides test data for test_similar
+ *
+ * @return array
+ */
+ public function provider_similar()
+ {
+ return array
+ (
+ // TODO: add some more cases
+ array('foo', array('foobar', 'food', 'fooberry')),
+ );
+ }
+
+ /**
+ * Tests Text::similar()
+ *
+ * @test
+ * @dataProvider provider_similar
+ * @covers Text::similar
+ */
+ public function test_similar($expected, $words)
+ {
+ $this->assertSame($expected, Text::similar($words));
+ }
+
+ /**
+ * Provides test data for test_bytes
+ *
+ * @return array
+ */
+ public function provider_bytes()
+ {
+ return array
+ (
+ // TODO: cover the other units
+ array('256.00 B', 256, NULL, NULL, TRUE),
+ array('1.02 kB', 1024, NULL, NULL, TRUE),
+
+ // In case you need to know the size of a floppy disk in petabytes
+ array('0.00147 GB', 1.44 * 1000 * 1024, 'GB', '%01.5f %s', TRUE),
+
+ // SI is the standard, but lets deviate slightly
+ array('1.00 MiB', 1024 * 1024, 'MiB', NULL, FALSE),
+ );
+ }
+
+ /**
+ * Tests Text::bytes()
+ *
+ * @test
+ * @dataProvider provider_bytes
+ */
+ public function test_bytes($expected, $bytes, $force_unit, $format, $si)
+ {
+ $this->assertSame($expected, Text::bytes($bytes, $force_unit, $format, $si));
+ }
+
+ /**
+ * Provides test data for test_widont()
+ *
+ * @return array Test data
+ */
+ public function provider_widont()
+ {
+ return array
+ (
+ array('No gain, no pain', 'No gain, no pain'),
+ array("spaces?what'rethey?", "spaces?what'rethey?"),
+ array('', ''),
+ );
+ }
+
+ /**
+ * Tests Text::widont()
+ *
+ * @test
+ * @dataProvider provider_widont
+ */
+ public function test_widont($expected, $string)
+ {
+ $this->assertSame($expected, Text::widont($string));
+ }
+
+
+ /**
+ * This checks that auto_link_emails() respects word boundaries and does not
+ * just blindly replace all occurences of the email address in the text.
+ *
+ * In the sample below the algorithm was replacing all occurences of voorzitter@xxxx.com
+ * inc the copy in the second list item.
+ *
+ * It was updated in 6c199366efc1115545ba13108b876acc66c54b2d to respect word boundaries
+ *
+ * @test
+ * @covers Text::auto_link_emails
+ * @ticket 2772
+ */
+ public function test_auto_link_emails_respects_word_boundaries()
+ {
+ $original = '
+ voorzitter@xxxx.com
+ vicevoorzitter@xxxx.com
+ ';
+
+ $this->assertFalse(strpos('vice', Text::auto_link_emails($original)));
+ }
+
+
+ /**
+ * Provides some test data for test_number()
+ *
+ * @return array
+ */
+ public function provider_number()
+ {
+ return array(
+ array('one', 1),
+ array('twenty-three', 23),
+ array('fourty-two', 42),
+ array('five million, six hundred and thirty-two', 5000632),
+ array('five million, six hundred and thirty', 5000630),
+ array('nine hundred million', 900000000),
+ array('thirty-seven thousand', 37000),
+ array('one thousand and twenty-four', 1024),
+ );
+ }
+
+ /**
+ * Checks that Text::number formats a number into english text
+ *
+ * @test
+ * @dataProvider provider_number
+ */
+ public function test_number($expected, $number)
+ {
+ $this->assertSame($expected, Text::number($number));
+ }
+
+ /**
+ * Provides test data for test_auto_link_urls()
+ *
+ * @return array
+ */
+ public function provider_auto_link_urls()
+ {
+ return array(
+ // First we try with the really obvious url
+ array(
+ 'Some random text http://www.google.com ',
+ 'Some random text http://www.google.com',
+ ),
+ // Then we try with varying urls
+ array(
+ 'Some random www.google.com ',
+ 'Some random www.google.com',
+ ),
+ array(
+ 'Some random google.com',
+ 'Some random google.com',
+ ),
+ // Check that it doesn't link urls in a href
+ array(
+ 'Look at me Awesome stuff ',
+ 'Look at me Awesome stuff ',
+ ),
+ array(
+ 'Look at me http://www.google.com ',
+ 'Look at me http://www.google.com ',
+ ),
+ // @issue 3190
+ array(
+ 'www.google.com ',
+ 'www.google.com ',
+ ),
+ array(
+ 'www.google.com http://www.google.com/ ',
+ 'www.google.com http://www.google.com/',
+ ),
+ );
+ }
+
+ /**
+ * Runs tests for Test::auto_link_urls
+ *
+ * @test
+ * @dataProvider provider_auto_link_urls
+ */
+ public function test_auto_link_urls($expected, $text)
+ {
+ $this->assertSame($expected, Text::auto_link_urls($text));
+ }
+
+ /**
+ * Provides test data for test_auto_link_emails()
+ *
+ * @return array
+ */
+ public function provider_auto_link_emails()
+ {
+ return array(
+ // @issue 3162
+ array(
+ 'info@test.com ',
+ 'info@test.com ',
+ ),
+ array(
+ 'info@test.com ',
+ 'info@test.com ',
+ ),
+ // @issue 3189
+ array(
+ 'email@address.com email@address.com ',
+ 'email@address.com email@address.com',
+ ),
+ );
+ }
+
+ /**
+ * Runs tests for Test::auto_link_emails
+ *
+ * @test
+ * @dataProvider provider_auto_link_emails
+ */
+ public function test_auto_link_emails($expected, $text)
+ {
+ // Use html_entity_decode because emails will be randomly encoded by HTML::mailto
+ $this->assertSame($expected, html_entity_decode(Text::auto_link_emails($text)));
+ }
+
+ /**
+ * Provides test data for test_auto_link
+ *
+ * @return array Test data
+ */
+ public function provider_auto_link()
+ {
+ return array(
+ array(
+ 'Hi there, my site is kohanaframework.org and you can email me at nobody@kohanaframework.org',
+ array('kohanaframework.org'),
+ ),
+
+ array(
+ 'Hi my.domain.com@domain.com you came from',
+ FALSE,
+ array('my.domain.com@domain.com'),
+ ),
+ );
+ }
+
+ /**
+ * Tests Text::auto_link()
+ *
+ * @test
+ * @dataProvider provider_auto_link
+ */
+ public function test_auto_link($text, $urls = array(), $emails = array())
+ {
+ $linked_text = Text::auto_link($text);
+
+ if($urls === FALSE)
+ {
+ $this->assertNotContains('http://', $linked_text);
+ }
+ elseif(count($urls))
+ {
+ foreach($urls as $url)
+ {
+ // Assert that all the urls have been caught by text auto_link_urls()
+ $this->assertContains(Text::auto_link_urls($url), $linked_text);
+ }
+ }
+
+ foreach($emails as $email)
+ {
+ $this->assertNotContains($email, $linked_text);
+ }
+
+ }
+
+}
diff --git a/includes/kohana/system/tests/kohana/URLTest.php b/includes/kohana/system/tests/kohana/URLTest.php
new file mode 100644
index 0000000..4b0f72b
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/URLTest.php
@@ -0,0 +1,275 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+Class Kohana_URLTest extends Kohana_Unittest_TestCase
+{
+ /**
+ * Default values for the environment, see setEnvironment
+ * @var array
+ */
+ protected $environmentDefault = array(
+ 'Kohana::$base_url' => '/kohana/',
+ 'Kohana::$index_file'=> 'index.php',
+ 'Request::$protocol' => 'http',
+ 'HTTP_HOST' => 'example.com',
+ '_GET' => array(),
+ );
+
+ /**
+ * Provides test data for test_base()
+ *
+ * @return array
+ */
+ public function provider_base()
+ {
+ return array(
+ // $index, $protocol, $expected, $enviroment
+ //
+ // Test with different combinations of parameters for max code coverage
+ array(FALSE, FALSE, '/kohana/'),
+ array(FALSE, TRUE, 'http://example.com/kohana/'),
+ array(TRUE, FALSE, '/kohana/index.php/'),
+ array(TRUE, FALSE, '/kohana/index.php/'),
+ array(TRUE, TRUE, 'http://example.com/kohana/index.php/'),
+ array(TRUE, 'http', 'http://example.com/kohana/index.php/'),
+ array(TRUE, 'https','https://example.com/kohana/index.php/'),
+ array(TRUE, 'ftp', 'ftp://example.com/kohana/index.php/'),
+
+ //
+ // These tests make sure that the protocol changes when the global setting changes
+ array(TRUE, TRUE, 'https://example.com/kohana/index.php/', array('Request::$protocol' => 'https')),
+ array(FALSE, TRUE, 'https://example.com/kohana/', array('Request::$protocol' => 'https')),
+
+ // Change base url'
+ array(FALSE, 'https', 'https://example.com/kohana/', array('Kohana::$base_url' => 'omglol://example.com/kohana/')),
+
+ // Use port in base url, issue #3307
+ array(FALSE, TRUE, 'http://example.com:8080/', array('Kohana::$base_url' => 'example.com:8080/')),
+
+ // Use protocol from base url if none specified
+ array(FALSE, FALSE, 'http://www.example.com/', array('Kohana::$base_url' => 'http://www.example.com/')),
+
+ // Use HTTP_HOST before SERVER_NAME
+ array(FALSE, 'http', 'http://example.com/kohana/', array('HTTP_HOST' => 'example.com', 'SERVER_NAME' => 'example.org')),
+
+ // Use SERVER_NAME if HTTP_HOST DNX
+ array(FALSE, 'http', 'http://example.org/kohana/', array('HTTP_HOST' => NULL, 'SERVER_NAME' => 'example.org')),
+ );
+ }
+
+ /**
+ * Tests URL::base()
+ *
+ * @test
+ * @dataProvider provider_base
+ * @param boolean $index Parameter for Url::base()
+ * @param boolean $protocol Parameter for Url::base()
+ * @param string $expected Expected url
+ * @param array $enviroment Array of enviroment vars to change @see Kohana_URLTest::setEnvironment()
+ */
+ public function test_base($index, $protocol, $expected, array $enviroment = array())
+ {
+ $this->setEnvironment($enviroment);
+
+ $this->assertSame(
+ $expected,
+ URL::base($index, $protocol)
+ );
+ }
+
+ /**
+ * Provides test data for test_site()
+ *
+ * @return array
+ */
+ public function provider_site()
+ {
+ return array(
+ array('', FALSE, '/kohana/index.php/'),
+ array('', TRUE, 'http://example.com/kohana/index.php/'),
+
+ array('my/site', FALSE, '/kohana/index.php/my/site'),
+ array('my/site', TRUE, 'http://example.com/kohana/index.php/my/site'),
+
+ // @ticket #3110
+ array('my/site/page:5', FALSE, '/kohana/index.php/my/site/page:5'),
+ array('my/site/page:5', TRUE, 'http://example.com/kohana/index.php/my/site/page:5'),
+
+ array('my/site?var=asd&kohana=awesome', FALSE, '/kohana/index.php/my/site?var=asd&kohana=awesome'),
+ array('my/site?var=asd&kohana=awesome', TRUE, 'http://example.com/kohana/index.php/my/site?var=asd&kohana=awesome'),
+
+ array('?kohana=awesome&life=good', FALSE, '/kohana/index.php/?kohana=awesome&life=good'),
+ array('?kohana=awesome&life=good', TRUE, 'http://example.com/kohana/index.php/?kohana=awesome&life=good'),
+
+ array('?kohana=awesome&life=good#fact', FALSE, '/kohana/index.php/?kohana=awesome&life=good#fact'),
+ array('?kohana=awesome&life=good#fact', TRUE, 'http://example.com/kohana/index.php/?kohana=awesome&life=good#fact'),
+
+ array('some/long/route/goes/here?kohana=awesome&life=good#fact', FALSE, '/kohana/index.php/some/long/route/goes/here?kohana=awesome&life=good#fact'),
+ array('some/long/route/goes/here?kohana=awesome&life=good#fact', TRUE, 'http://example.com/kohana/index.php/some/long/route/goes/here?kohana=awesome&life=good#fact'),
+
+ array('/route/goes/here?kohana=awesome&life=good#fact', 'https', 'https://example.com/kohana/index.php/route/goes/here?kohana=awesome&life=good#fact'),
+ array('/route/goes/here?kohana=awesome&life=good#fact', 'ftp', 'ftp://example.com/kohana/index.php/route/goes/here?kohana=awesome&life=good#fact'),
+ );
+ }
+
+ /**
+ * Tests URL::site()
+ *
+ * @test
+ * @dataProvider provider_site
+ * @param string $uri URI to use
+ * @param boolean|string $protocol Protocol to use
+ * @param string $expected Expected result
+ * @param array $enviroment Array of enviroment vars to set
+ */
+ public function test_site($uri, $protocol, $expected, array $enviroment = array())
+ {
+ $this->setEnvironment($enviroment);
+
+ $this->assertSame(
+ $expected,
+ URL::site($uri, $protocol)
+ );
+ }
+
+ /**
+ * Provides test data for test_site_url_encode_uri()
+ * See issue #2680
+ *
+ * @return array
+ */
+ public function provider_site_url_encode_uri()
+ {
+ $provider = array(
+ array('test', 'encode'),
+ array('test', 'éñçø∂ë∂'),
+ array('†é߆', 'encode'),
+ array('†é߆', 'éñçø∂ë∂', 'µåñ¥'),
+ );
+
+ foreach ($provider as $i => $params)
+ {
+ // Every non-ASCII character except for forward slash should be encoded...
+ $expected = implode('/', array_map('rawurlencode', $params));
+
+ // ... from a URI that is not encoded
+ $uri = implode('/', $params);
+
+ $provider[$i] = array("/kohana/index.php/{$expected}", $uri);
+ }
+
+ return $provider;
+ }
+
+ /**
+ * Tests URL::site for proper URL encoding when working with non-ASCII characters.
+ *
+ * @test
+ * @dataProvider provider_site_url_encode_uri
+ */
+ public function test_site_url_encode_uri($expected, $uri)
+ {
+ $this->assertSame($expected, URL::site($uri, FALSE));
+ }
+
+ /**
+ * Provides test data for test_title()
+ * @return array
+ */
+ public function provider_title()
+ {
+ return array(
+ // Tests that..
+ // Title is converted to lowercase
+ array('we-shall-not-be-moved', 'WE SHALL NOT BE MOVED', '-'),
+ // Excessive white space is removed and replaced with 1 char
+ array('thissssss-is-it', 'THISSSSSS IS IT ', '-'),
+ // separator is either - (dash) or _ (underscore) & others are converted to underscores
+ array('some-title', 'some title', '-'),
+ array('some_title', 'some title', '_'),
+ array('some!title', 'some title', '!'),
+ array('some:title', 'some title', ':'),
+ // Numbers are preserved
+ array('99-ways-to-beat-apple', '99 Ways to beat apple', '-'),
+ // ... with lots of spaces & caps
+ array('99_ways_to_beat_apple', '99 ways TO beat APPLE', '_'),
+ array('99-ways-to-beat-apple', '99 ways TO beat APPLE', '-'),
+ // Invalid characters are removed
+ array('each-gbp-is-now-worth-32-usd', 'Each GBP(£) is now worth 32 USD($)', '-'),
+ // ... inc. separator
+ array('is-it-reusable-or-re-usable', 'Is it reusable or re-usable?', '-'),
+ // Doing some crazy UTF8 tests
+ array('espana-wins', 'España-wins', '-', TRUE),
+ );
+ }
+
+ /**
+ * Tests URL::title()
+ *
+ * @test
+ * @dataProvider provider_title
+ * @param string $title Input to convert
+ * @param string $separator Seperate to replace invalid characters with
+ * @param string $expected Expected result
+ */
+ public function test_Title($expected, $title, $separator, $ascii_only = FALSE)
+ {
+ $this->assertSame(
+ $expected,
+ URL::title($title, $separator, $ascii_only)
+ );
+ }
+
+ /**
+ * Provides test data for URL::query()
+ * @return array
+ */
+ public function provider_Query()
+ {
+ return array(
+ array(array(), '', NULL),
+ array(array('_GET' => array('test' => 'data')), '?test=data', NULL),
+ array(array(), '?test=data', array('test' => 'data')),
+ array(array('_GET' => array('more' => 'data')), '?more=data&test=data', array('test' => 'data')),
+ array(array('_GET' => array('sort' => 'down')), '?test=data', array('test' => 'data'), FALSE),
+
+ // http://dev.kohanaframework.org/issues/3362
+ array(array(), '', array('key' => NULL)),
+ array(array(), '?key=0', array('key' => FALSE)),
+ array(array(), '?key=1', array('key' => TRUE)),
+ array(array('_GET' => array('sort' => 'down')), '?sort=down&key=1', array('key' => TRUE)),
+ array(array('_GET' => array('sort' => 'down')), '?sort=down&key=0', array('key' => FALSE)),
+ );
+ }
+
+ /**
+ * Tests URL::query()
+ *
+ * @test
+ * @dataProvider provider_query
+ * @param array $enviroment Set environment
+ * @param string $expected Expected result
+ * @param array $params Query string
+ * @param boolean $use_get Combine with GET parameters
+ */
+ public function test_query($enviroment, $expected, $params, $use_get = TRUE)
+ {
+ $this->setEnvironment($enviroment);
+
+ $this->assertSame(
+ $expected,
+ URL::query($params, $use_get)
+ );
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/UTF8Test.php b/includes/kohana/system/tests/kohana/UTF8Test.php
new file mode 100644
index 0000000..7fa11c0
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/UTF8Test.php
@@ -0,0 +1,647 @@
+assertSame($expected, UTF8::clean($input));
+ }
+
+ /**
+ * Provides test data for test_is_ascii()
+ */
+ public function provider_is_ascii()
+ {
+ return array(
+ array("\0", TRUE),
+ array("\$eno\r", TRUE),
+ array('Señor', FALSE),
+ array(array('Se', 'nor'), TRUE),
+ array(array('Se', 'ñor'), FALSE),
+ );
+ }
+
+ /**
+ * Tests UTF8::is_ascii
+ *
+ * @test
+ * @dataProvider provider_is_ascii
+ */
+ public function test_is_ascii($input, $expected)
+ {
+ $this->assertSame($expected, UTF8::is_ascii($input));
+ }
+
+ /**
+ * Provides test data for test_strip_ascii_ctrl()
+ */
+ public function provider_strip_ascii_ctrl()
+ {
+ return array(
+ array("\0", ''),
+ array("→foo\021", '→foo'),
+ array("\x7Fbar", 'bar'),
+ array("\xFF", "\xFF"),
+ array("\x41", 'A'),
+ );
+ }
+
+ /**
+ * Tests UTF8::strip_ascii_ctrl
+ *
+ * @test
+ * @dataProvider provider_strip_ascii_ctrl
+ */
+ public function test_strip_ascii_ctrl($input, $expected)
+ {
+ $this->assertSame($expected, UTF8::strip_ascii_ctrl($input));
+ }
+
+ /**
+ * Provides test data for test_strip_non_ascii()
+ */
+ public function provider_strip_non_ascii()
+ {
+ return array(
+ array("\0\021\x7F", "\0\021\x7F"),
+ array('I ♥ cocoñùт', 'I coco'),
+ );
+ }
+
+ /**
+ * Tests UTF8::strip_non_ascii
+ *
+ * @test
+ * @dataProvider provider_strip_non_ascii
+ */
+ public function test_strip_non_ascii($input, $expected)
+ {
+ $this->assertSame($expected, UTF8::strip_non_ascii($input));
+ }
+
+ /**
+ * Provides test data for test_transliterate_to_ascii()
+ */
+ public function provider_transliterate_to_ascii()
+ {
+ return array(
+ array('Cocoñùт', -1, 'Coconuт'),
+ array('COCOÑÙТ', -1, 'COCOÑÙТ'),
+ array('Cocoñùт', 0, 'Coconuт'),
+ array('COCOÑÙТ', 0, 'COCONUТ'),
+ array('Cocoñùт', 1, 'Cocoñùт'),
+ array('COCOÑÙТ', 1, 'COCONUТ'),
+ );
+ }
+
+ /**
+ * Tests UTF8::transliterate_to_ascii
+ *
+ * @test
+ * @dataProvider provider_transliterate_to_ascii
+ */
+ public function test_transliterate_to_ascii($input, $case, $expected)
+ {
+ $this->assertSame($expected, UTF8::transliterate_to_ascii($input, $case));
+ }
+
+ /**
+ * Provides test data for test_strlen()
+ */
+ public function provider_strlen()
+ {
+ return array(
+ array('Cocoñùт', 7),
+ array('Coconut', 7),
+ );
+ }
+
+ /**
+ * Tests UTF8::strlen
+ *
+ * @test
+ * @dataProvider provider_strlen
+ */
+ public function test_strlen($input, $expected)
+ {
+ $this->assertSame($expected, UTF8::strlen($input));
+ UTF8::$server_utf8 = ! UTF8::$server_utf8;
+ $this->assertSame($expected, UTF8::strlen($input));
+ UTF8::$server_utf8 = ! UTF8::$server_utf8;
+ }
+
+ /**
+ * Provides test data for test_strpos()
+ */
+ public function provider_strpos()
+ {
+ return array(
+ array('Cocoñùт', 'o', 0, 1),
+ array('Cocoñùт', 'ñ', 1, 4),
+ );
+ }
+
+ /**
+ * Tests UTF8::strpos
+ *
+ * @test
+ * @dataProvider provider_strpos
+ */
+ public function test_strpos($input, $str, $offset, $expected)
+ {
+ $this->assertSame($expected, UTF8::strpos($input, $str, $offset));
+ UTF8::$server_utf8 = ! UTF8::$server_utf8;
+ $this->assertSame($expected, UTF8::strpos($input, $str, $offset));
+ UTF8::$server_utf8 = ! UTF8::$server_utf8;
+ }
+
+ /**
+ * Provides test data for test_strrpos()
+ */
+ public function provider_strrpos()
+ {
+ return array(
+ array('Cocoñùт', 'o', 0, 3),
+ array('Cocoñùт', 'ñ', 2, 4),
+ );
+ }
+
+ /**
+ * Tests UTF8::strrpos
+ *
+ * @test
+ * @dataProvider provider_strrpos
+ */
+ public function test_strrpos($input, $str, $offset, $expected)
+ {
+ $this->assertSame($expected, UTF8::strrpos($input, $str, $offset));
+ UTF8::$server_utf8 = ! UTF8::$server_utf8;
+ $this->assertSame($expected, UTF8::strrpos($input, $str, $offset));
+ UTF8::$server_utf8 = ! UTF8::$server_utf8;
+ }
+
+ /**
+ * Provides test data for test_substr()
+ */
+ public function provider_substr()
+ {
+ return array(
+ array('Cocoñùт', 3, 2, 'oñ'),
+ array('Cocoñùт', 3, 9, 'oñùт'),
+ array('Cocoñùт', 3, NULL, 'oñùт'),
+ array('Cocoñùт', 3, -2, 'oñ'),
+ );
+ }
+
+ /**
+ * Tests UTF8::substr
+ *
+ * @test
+ * @dataProvider provider_substr
+ */
+ public function test_substr($input, $offset, $length, $expected)
+ {
+ $this->assertSame($expected, UTF8::substr($input, $offset, $length));
+ UTF8::$server_utf8 = ! UTF8::$server_utf8;
+ $this->assertSame($expected, UTF8::substr($input, $offset, $length));
+ UTF8::$server_utf8 = ! UTF8::$server_utf8;
+ }
+
+ /**
+ * Provides test data for test_substr_replace()
+ */
+ public function provider_substr_replace()
+ {
+ return array(
+ array('Cocoñùт', 'šš', 3, 2, 'Cocššùт'),
+ array('Cocoñùт', 'šš', 3, 9, 'Cocšš'),
+ );
+ }
+
+ /**
+ * Tests UTF8::substr_replace
+ *
+ * @test
+ * @dataProvider provider_substr_replace
+ */
+ public function test_substr_replace($input, $replacement, $offset, $length, $expected)
+ {
+ $this->assertSame($expected, UTF8::substr_replace($input, $replacement, $offset, $length));
+ }
+
+ /**
+ * Provides test data for test_strtolower()
+ */
+ public function provider_strtolower()
+ {
+ return array(
+ array('COCOÑÙТ', 'cocoñùт'),
+ array('JÄGER', 'jäger'),
+ );
+ }
+
+ /**
+ * Tests UTF8::strtolower
+ *
+ * @test
+ * @dataProvider provider_strtolower
+ */
+ public function test_strtolower($input, $expected)
+ {
+ $this->assertSame($expected, UTF8::strtolower($input));
+ UTF8::$server_utf8 = ! UTF8::$server_utf8;
+ $this->assertSame($expected, UTF8::strtolower($input));
+ UTF8::$server_utf8 = ! UTF8::$server_utf8;
+ }
+
+ /**
+ * Provides test data for test_strtoupper()
+ */
+ public function provider_strtoupper()
+ {
+ return array(
+ array('Cocoñùт', 'COCOÑÙТ'),
+ array('jäger', 'JÄGER'),
+ );
+ }
+
+ /**
+ * Tests UTF8::strtoupper
+ *
+ * @test
+ * @dataProvider provider_strtoupper
+ */
+ public function test_strtoupper($input, $expected)
+ {
+ $this->assertSame($expected, UTF8::strtoupper($input));
+ UTF8::$server_utf8 = ! UTF8::$server_utf8;
+ $this->assertSame($expected, UTF8::strtoupper($input));
+ UTF8::$server_utf8 = ! UTF8::$server_utf8;
+ }
+
+ /**
+ * Provides test data for test_ucfirst()
+ */
+ public function provider_ucfirst()
+ {
+ return array(
+ array('ñùт', 'Ñùт'),
+ );
+ }
+
+ /**
+ * Tests UTF8::ucfirst
+ *
+ * @test
+ * @dataProvider provider_ucfirst
+ */
+ public function test_ucfirst($input, $expected)
+ {
+ $this->assertSame($expected, UTF8::ucfirst($input));
+ }
+
+ /**
+ * Provides test data for test_strip_non_ascii()
+ */
+ public function provider_ucwords()
+ {
+ return array(
+ array('ExAmple', 'ExAmple'),
+ array('i ♥ Cocoñùт', 'I ♥ Cocoñùт'),
+ );
+ }
+
+ /**
+ * Tests UTF8::ucwords
+ *
+ * @test
+ * @dataProvider provider_ucwords
+ */
+ public function test_ucwords($input, $expected)
+ {
+ $this->assertSame($expected, UTF8::ucwords($input));
+ }
+
+ /**
+ * Provides test data for test_strcasecmp()
+ */
+ public function provider_strcasecmp()
+ {
+ return array(
+ array('Cocoñùт', 'Cocoñùт', 0),
+ array('Čau', 'Čauo', -1),
+ array('Čau', 'Ča', 1),
+ array('Cocoñùт', 'Cocoñ', 4),
+ array('Cocoñùт', 'Coco', 6),
+ );
+ }
+
+ /**
+ * Tests UTF8::strcasecmp
+ *
+ * @test
+ * @dataProvider provider_strcasecmp
+ */
+ public function test_strcasecmp($input, $input2, $expected)
+ {
+ $this->assertSame($expected, UTF8::strcasecmp($input, $input2));
+ }
+
+ /**
+ * Provides test data for test_str_ireplace()
+ */
+ public function provider_str_ireplace()
+ {
+ return array(
+ array('т', 't', 'cocoñuт', 'cocoñut'),
+ array('Ñ', 'N', 'cocoñuт', 'cocoNuт'),
+ array(array('т', 'Ñ', 'k' => 'k'), array('t', 'N', 'K'), array('cocoñuт'), array('cocoNut')),
+ array(array('ñ'), 'n', 'cocoñuт', 'coconuт'),
+ );
+ }
+
+ /**
+ * Tests UTF8::str_ireplace
+ *
+ * @test
+ * @dataProvider provider_str_ireplace
+ */
+ public function test_str_ireplace($search, $replace, $subject, $expected)
+ {
+ $this->assertSame($expected, UTF8::str_ireplace($search, $replace, $subject));
+ }
+
+ /**
+ * Provides test data for test_stristr()
+ */
+ public function provider_stristr()
+ {
+ return array(
+ array('Cocoñùт', 'oñ', 'oñùт'),
+ array('Cocoñùт', 'o', 'ocoñùт'),
+ array('Cocoñùт', 'k', FALSE),
+ );
+ }
+
+ /**
+ * Tests UTF8::stristr
+ *
+ * @test
+ * @dataProvider provider_stristr
+ */
+ public function test_stristr($input, $input2, $expected)
+ {
+ $this->assertSame($expected, UTF8::stristr($input, $input2));
+ }
+
+ /**
+ * Provides test data for test_strspn()
+ */
+ public function provider_strspn()
+ {
+ return array(
+ array("foo", "o", 1, 2, 2),
+ array('Cocoñùт', 'oñ', NULL, NULL, 1),
+ array('Cocoñùт', 'oñ', 2, 4, 1),
+ array('Cocoñùт', 'šš', 3, 9, 4),
+ );
+ }
+
+ /**
+ * Tests UTF8::strspn
+ *
+ * @test
+ * @dataProvider provider_strspn
+ */
+ public function test_strspn($input, $mask, $offset, $length, $expected)
+ {
+ $this->assertSame($expected, UTF8::strspn($input, $mask, $offset, $length));
+ }
+
+ /**
+ * Provides test data for test_strcspn()
+ */
+ public function provider_strcspn()
+ {
+ return array(
+ array('Cocoñùт', 'oñ', NULL, NULL, 1),
+ array('Cocoñùт', 'oñ', 2, 4, 1),
+ array('Cocoñùт', 'šš', 3, 9, 4),
+ );
+ }
+
+ /**
+ * Tests UTF8::strcspn
+ *
+ * @test
+ * @dataProvider provider_strcspn
+ */
+ public function test_strcspn($input, $mask, $offset, $length, $expected)
+ {
+ $this->assertSame($expected, UTF8::strcspn($input, $mask, $offset, $length));
+ }
+
+ /**
+ * Provides test data for test_str_pad()
+ */
+ public function provider_str_pad()
+ {
+ return array(
+ array('Cocoñùт', 10, 'š', STR_PAD_RIGHT, 'Cocoñùтššš'),
+ array('Cocoñùт', 10, 'š', STR_PAD_LEFT, 'šššCocoñùт'),
+ array('Cocoñùт', 10, 'š', STR_PAD_BOTH, 'šCocoñùтšš'),
+ );
+ }
+
+ /**
+ * Tests UTF8::str_pad
+ *
+ * @test
+ * @dataProvider provider_str_pad
+ */
+ public function test_str_pad($input, $length, $pad, $type, $expected)
+ {
+ $this->assertSame($expected, UTF8::str_pad($input, $length, $pad, $type));
+ }
+
+ /**
+ * Tests UTF8::str_pad error
+ *
+ * @test
+ * @expectedException Exception
+ */
+ public function test_str_pad_error()
+ {
+ UTF8::str_pad('Cocoñùт', 10, 'š', 15, 'šCocoñùтšš');
+ }
+
+ /**
+ * Provides test data for test_str_split()
+ */
+ public function provider_str_split()
+ {
+ return array(
+ array('Bár', 1, array('B', 'á', 'r')),
+ array('Cocoñùт', 2, array('Co', 'co', 'ñù', 'т')),
+ array('Cocoñùт', 3, array('Coc', 'oñù', 'т')),
+ );
+ }
+
+ /**
+ * Tests UTF8::str_split
+ *
+ * @test
+ * @dataProvider provider_str_split
+ */
+ public function test_str_split($input, $split_length, $expected)
+ {
+ $this->assertSame($expected, UTF8::str_split($input, $split_length));
+ }
+
+ /**
+ * Provides test data for test_strrev()
+ */
+ public function provider_strrev()
+ {
+ return array(
+ array('Cocoñùт', 'тùñocoC'),
+ );
+ }
+
+ /**
+ * Tests UTF8::strrev
+ *
+ * @test
+ * @dataProvider provider_strrev
+ */
+ public function test_strrev($input, $expected)
+ {
+ $this->assertSame($expected, UTF8::strrev($input));
+ }
+
+ /**
+ * Provides test data for test_trim()
+ */
+ public function provider_trim()
+ {
+ return array(
+ array(' bar ', NULL, 'bar'),
+ array('bar', 'b', 'ar'),
+ array('barb', 'b', 'ar'),
+ );
+ }
+
+ /**
+ * Tests UTF8::trim
+ *
+ * @test
+ * @dataProvider provider_trim
+ */
+ public function test_trim($input, $input2, $expected)
+ {
+ $this->assertSame($expected, UTF8::trim($input, $input2));
+ }
+
+ /**
+ * Provides test data for test_ltrim()
+ */
+ public function provider_ltrim()
+ {
+ return array(
+ array(' bar ', NULL, 'bar '),
+ array('bar', 'b', 'ar'),
+ array('barb', 'b', 'arb'),
+ array('ñùт', 'ñ', 'ùт'),
+ );
+ }
+
+ /**
+ * Tests UTF8::ltrim
+ *
+ * @test
+ * @dataProvider provider_ltrim
+ */
+ public function test_ltrim($input, $charlist, $expected)
+ {
+ $this->assertSame($expected, UTF8::ltrim($input, $charlist));
+ }
+
+ /**
+ * Provides test data for test_rtrim()
+ */
+ public function provider_rtrim()
+ {
+ return array(
+ array(' bar ', NULL, ' bar'),
+ array('bar', 'b', 'bar'),
+ array('barb', 'b', 'bar'),
+ array('Cocoñùт', 'т', 'Cocoñù'),
+ );
+ }
+
+ /**
+ * Tests UTF8::rtrim
+ *
+ * @test
+ * @dataProvider provider_rtrim
+ */
+ public function test_rtrim($input, $input2, $expected)
+ {
+ $this->assertSame($expected, UTF8::rtrim($input, $input2));
+ }
+
+ /**
+ * Provides test data for test_ord()
+ */
+ public function provider_ord()
+ {
+ return array(
+ array('f', 102),
+ array('ñ', 241),
+ array('Ñ', 209),
+ );
+ }
+
+ /**
+ * Tests UTF8::ord
+ *
+ * @test
+ * @dataProvider provider_ord
+ */
+ public function test_ord($input, $expected)
+ {
+ $this->assertSame($expected, UTF8::ord($input));
+ }
+}
\ No newline at end of file
diff --git a/includes/kohana/system/tests/kohana/UploadTest.php b/includes/kohana/system/tests/kohana/UploadTest.php
new file mode 100644
index 0000000..ddb69ab
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/UploadTest.php
@@ -0,0 +1,223 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+class Kohana_UploadTest extends Kohana_Unittest_TestCase
+{
+ /**
+ * Provides test data for test_size()
+ *
+ * @return array
+ */
+ public function provider_size()
+ {
+ return array(
+ // $field, $bytes, $environment, $expected
+ array(
+ 'unit_test',
+ 5,
+ array('_FILES' => array('unit_test' => array('error' => UPLOAD_ERR_INI_SIZE))),
+ FALSE
+ ),
+ array(
+ 'unit_test',
+ 5,
+ array('_FILES' => array('unit_test' => array('error' => UPLOAD_ERR_NO_FILE))),
+ TRUE
+ ),
+ array(
+ 'unit_test',
+ '6K',
+ array('_FILES' => array(
+ 'unit_test' => array(
+ 'error' => UPLOAD_ERR_OK,
+ 'name' => 'Unit_Test File',
+ 'type' => 'image/png',
+ 'tmp_name' => Kohana::find_file('tests', 'test_data/github', 'png'),
+ 'size' => filesize(Kohana::find_file('tests', 'test_data/github', 'png')),
+ )
+ )
+ ),
+ TRUE
+ ),
+ array(
+ 'unit_test',
+ '1B',
+ array('_FILES' => array(
+ 'unit_test' => array(
+ 'error' => UPLOAD_ERR_OK,
+ 'name' => 'Unit_Test File',
+ 'type' => 'image/png',
+ 'tmp_name' => Kohana::find_file('tests', 'test_data/github', 'png'),
+ 'size' => filesize(Kohana::find_file('tests', 'test_data/github', 'png')),
+ )
+ )
+ ),
+ FALSE
+ ),
+ );
+ }
+
+ /**
+ * Tests Upload::size
+ *
+ * @test
+ * @dataProvider provider_size
+ * @covers upload::size
+ * @param string $field the files field to test
+ * @param string $bytes valid bite size
+ * @param array $environment set the $_FILES array
+ * @param $expected what to expect
+ */
+ public function test_size($field, $bytes, $environment, $expected)
+ {
+ $this->setEnvironment($environment);
+
+ $this->assertSame($expected, Upload::size($_FILES[$field], $bytes));
+ }
+
+ /**
+ * size() should throw an exception of the supplied max size is invalid
+ *
+ * @test
+ * @covers upload::size
+ * @expectedException Kohana_Exception
+ */
+ public function test_size_throws_exception_for_invalid_size()
+ {
+ $this->setEnvironment(array(
+ '_FILES' => array(
+ 'unit_test' => array(
+ 'error' => UPLOAD_ERR_OK,
+ 'name' => 'Unit_Test File',
+ 'type' => 'image/png',
+ 'tmp_name' => Kohana::find_file('tests', 'test_data/github', 'png'),
+ 'size' => filesize(Kohana::find_file('tests', 'test_data/github', 'png')),
+ )
+ )
+ ));
+
+ Upload::size($_FILES['unit_test'], '1DooDah');
+ }
+
+ /**
+ * Provides test data for test_vali()
+ *
+ * @test
+ * @return array
+ */
+ public function provider_valid()
+ {
+ return array(
+ array(
+ TRUE,
+ array(
+ 'error' => UPLOAD_ERR_OK,
+ 'name' => 'Unit_Test File',
+ 'type' => 'image/png',
+ 'tmp_name' => Kohana::find_file('tests', 'test_data/github', 'png'),
+ 'size' => filesize(Kohana::find_file('tests', 'test_data/github', 'png')),
+ )
+ ),
+ array(
+ FALSE,
+ array(
+ 'name' => 'Unit_Test File',
+ 'type' => 'image/png',
+ 'tmp_name' => Kohana::find_file('tests', 'test_data/github', 'png'),
+ 'size' => filesize(Kohana::find_file('tests', 'test_data/github', 'png')),
+ )
+ ),
+ array(
+ FALSE,
+ array(
+ 'error' => UPLOAD_ERR_OK,
+ 'type' => 'image/png',
+ 'tmp_name' => Kohana::find_file('tests', 'test_data/github', 'png'),
+ 'size' => filesize(Kohana::find_file('tests', 'test_data/github', 'png')),
+ )
+ ),
+ array(
+ FALSE,
+ array(
+ 'name' => 'Unit_Test File',
+ 'error' => UPLOAD_ERR_OK,
+ 'tmp_name' => Kohana::find_file('tests', 'test_data/github', 'png'),
+ 'size' => filesize(Kohana::find_file('tests', 'test_data/github', 'png')),
+ )
+ ),
+ array(
+ FALSE,
+ array(
+ 'error' => UPLOAD_ERR_OK,
+ 'name' => 'Unit_Test File',
+ 'type' => 'image/png',
+ 'size' => filesize(Kohana::find_file('tests', 'test_data/github', 'png')),
+ )
+ ),
+ array(
+ FALSE,
+ array(
+ 'error' => UPLOAD_ERR_OK,
+ 'name' => 'Unit_Test File',
+ 'type' => 'image/png',
+ 'tmp_name' => Kohana::find_file('tests', 'test_data/github', 'png'),
+ )
+ ),
+
+ );
+ }
+
+ /**
+ * Test Upload::valid
+ *
+ * @test
+ * @dataProvider provider_valid
+ * @covers Upload::valid
+ */
+ public function test_valid($expected, $file)
+ {
+ $this->setEnvironment(array(
+ '_FILES' => array(
+ 'unit_test' => $file,
+ ),
+ ));
+
+ $this->assertSame($expected, Upload::valid($_FILES['unit_test']));
+ }
+
+ /**
+ * Tests Upload::type
+ *
+ * @test
+ * @covers Upload::type
+ */
+ public function test_type()
+ {
+ $this->setEnvironment(array(
+ '_FILES' => array(
+ 'unit_test' => array(
+ 'error' => UPLOAD_ERR_OK,
+ 'name' => 'github.png',
+ 'type' => 'image/png',
+ 'tmp_name' => Kohana::find_file('tests', 'test_data/github', 'png'),
+ 'size' => filesize(Kohana::find_file('tests', 'test_data/github', 'png')),
+ )
+ )
+ ));
+
+ $this->assertTrue(Upload::type($_FILES['unit_test'], array('jpg', 'png', 'gif')));
+
+ $this->assertFalse(Upload::type($_FILES['unit_test'], array('docx')));
+ }
+}
diff --git a/includes/kohana/system/tests/kohana/ValidateTest.php b/includes/kohana/system/tests/kohana/ValidateTest.php
new file mode 100644
index 0000000..9409526
--- /dev/null
+++ b/includes/kohana/system/tests/kohana/ValidateTest.php
@@ -0,0 +1,1317 @@
+
+ * @copyright (c) 2008-2010 Kohana Team
+ * @license http://kohanaframework.org/license
+ */
+Class Kohana_ValidateTest extends Kohana_Unittest_TestCase
+{
+
+ /**
+ * Provides test data for test_alpha()
+ * @return array
+ */
+ public function provider_alpha()
+ {
+ return array(
+ array('asdavafaiwnoabwiubafpowf', TRUE),
+ array('!aidhfawiodb', FALSE),
+ array('51535oniubawdawd78', FALSE),
+ array('!"£$(G$W£(HFW£F(HQ)"n', FALSE),
+ // UTF-8 tests
+ array('あいうえお', TRUE, TRUE),
+ array('¥', FALSE, TRUE)
+ );
+ }
+
+ /**
+ * Tests Validate::alpha()
+ *
+ * Checks whether a string consists of alphabetical characters only.
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_alpha
+ * @param string $string
+ * @param boolean $expected
+ */
+ public function test_alpha($string, $expected, $utf8 = FALSE)
+ {
+ $this->assertSame(
+ $expected,
+ Validate::alpha($string, $utf8)
+ );
+ }
+
+ /*
+ * Provides test data for test_alpha_numeric
+ */
+ public function provide_alpha_numeric()
+ {
+ return array(
+ array('abcd1234', TRUE),
+ array('abcd', TRUE),
+ array('1234', TRUE),
+ array('abc123&^/-', FALSE),
+ // UTF-8 tests
+ array('あいうえお', TRUE, TRUE),
+ array('零一二三四五', TRUE, TRUE),
+ array('あい四五£^£^', FALSE, TRUE),
+ );
+ }
+
+ /**
+ * Tests Validate::alpha_numberic()
+ *
+ * Checks whether a string consists of alphabetical characters and numbers only.
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provide_alpha_numeric
+ * @param string $input The string to test
+ * @param boolean $expected Is $input valid
+ */
+ public function test_alpha_numeric($input, $expected, $utf8 = FALSE)
+ {
+ $this->assertSame(
+ $expected,
+ Validate::alpha_numeric($input, $utf8)
+ );
+ }
+
+ /**
+ * Provides test data for test_alpha_dash
+ */
+ public function provider_alpha_dash()
+ {
+ return array(
+ array('abcdef', TRUE),
+ array('12345', TRUE),
+ array('abcd1234', TRUE),
+ array('abcd1234-', TRUE),
+ array('abc123&^/-', FALSE)
+ );
+ }
+
+ /**
+ * Tests Validate::alpha_dash()
+ *
+ * Checks whether a string consists of alphabetical characters, numbers, underscores and dashes only.
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_alpha_dash
+ * @param string $input The string to test
+ * @param boolean $contains_utf8 Does the string contain utf8 specific characters
+ * @param boolean $expected Is $input valid?
+ */
+ public function test_alpha_dash($input, $expected, $contains_utf8 = FALSE)
+ {
+ if( ! $contains_utf8)
+ {
+ $this->assertSame(
+ $expected,
+ Validate::alpha_dash($input)
+ );
+ }
+
+ $this->assertSame(
+ $expected,
+ Validate::alpha_dash($input, TRUE)
+ );
+ }
+
+ /**
+ * DataProvider for the valid::date() test
+ */
+ public function provider_date()
+ {
+ return array(
+ array('now',TRUE),
+ array('10 September 2010',TRUE),
+ array('+1 day',TRUE),
+ array('+1 week',TRUE),
+ array('+1 week 2 days 4 hours 2 seconds',TRUE),
+ array('next Thursday',TRUE),
+ array('last Monday',TRUE),
+
+ array('blarg',FALSE),
+ array('in the year 2000',FALSE),
+ array('324824',FALSE),
+ );
+ }
+
+ /**
+ * Tests Validate::date()
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_date
+ * @param string $date The date to validate
+ * @param integer $expected
+ */
+ public function test_date($date, $expected)
+ {
+ $this->assertSame(
+ $expected,
+ Validate::date($date, $expected)
+ );
+ }
+
+ /**
+ * DataProvider for the valid::decimal() test
+ */
+ public function provider_decimal()
+ {
+ return array(
+ array('45.1664', 3, NULL, FALSE),
+ array('45.1664', 4, NULL, TRUE),
+ array('45.1664', 4, 2, TRUE),
+ );
+ }
+
+ /**
+ * Tests Validate::decimal()
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_decimal
+ * @param string $decimal The decimal to validate
+ * @param integer $places The number of places to check to
+ * @param integer $digits The number of digits preceding the point to check
+ * @param boolean $expected Whether $decimal conforms to $places AND $digits
+ */
+ public function test_decimal($decimal, $places, $digits, $expected)
+ {
+ $this->assertSame(
+ $expected,
+ Validate::decimal($decimal, $places, $digits),
+ 'Decimal: "'.$decimal.'" to '.$places.' places and '.$digits.' digits (preceeding period)'
+ );
+ }
+
+ /**
+ * Provides test data for test_digit
+ * @return array
+ */
+ public function provider_digit()
+ {
+ return array(
+ array('12345', TRUE),
+ array('10.5', FALSE),
+ array('abcde', FALSE),
+ array('abcd1234', FALSE),
+ array('-5', FALSE),
+ array(-5, FALSE),
+ );
+ }
+
+ /**
+ * Tests Validate::digit()
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_digit
+ * @param mixed $input Input to validate
+ * @param boolean $expected Is $input valid
+ */
+ public function test_digit($input, $expected, $contains_utf8 = FALSE)
+ {
+ if( ! $contains_utf8)
+ {
+ $this->assertSame(
+ $expected,
+ Validate::digit($input)
+ );
+ }
+
+ $this->assertSame(
+ $expected,
+ Validate::digit($input, TRUE)
+ );
+
+ }
+
+ /**
+ * DataProvider for the valid::color() test
+ */
+ public function provider_color()
+ {
+ return array(
+ array('#000000', TRUE),
+ array('#GGGGGG', FALSE),
+ array('#AbCdEf', TRUE),
+ array('#000', TRUE),
+ array('#abc', TRUE),
+ array('#DEF', TRUE),
+ array('000000', TRUE),
+ array('GGGGGG', FALSE),
+ array('AbCdEf', TRUE),
+ array('000', TRUE),
+ array('DEF', TRUE)
+ );
+ }
+
+ /**
+ * Tests Validate::color()
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_color
+ * @param string $color The color to test
+ * @param boolean $expected Is $color valid
+ */
+ public function test_color($color, $expected)
+ {
+ $this->assertSame(
+ $expected,
+ Validate::color($color)
+ );
+ }
+
+ /**
+ * Provides test data for test_credit_card()
+ */
+ public function provider_credit_card()
+ {
+ return array(
+ array('4222222222222', 'visa', TRUE),
+ array('4012888888881881', 'visa', TRUE),
+ array('4012888888881881', NULL, TRUE),
+ array('4012888888881881', array('mastercard', 'visa'), TRUE),
+ array('4012888888881881', array('discover', 'mastercard'), FALSE),
+ array('4012888888881881', 'mastercard', FALSE),
+ array('5105105105105100', 'mastercard', TRUE),
+ array('6011111111111117', 'discover', TRUE),
+ array('6011111111111117', 'visa', FALSE)
+ );
+ }
+
+ /**
+ * Tests Validate::credit_card()
+ *
+ * @test
+ * @covers Validate::credit_card
+ * @group kohana.validation.helpers
+ * @dataProvider provider_credit_card()
+ * @param string $number Credit card number
+ * @param string $type Credit card type
+ * @param boolean $expected
+ */
+ public function test_credit_card($number, $type, $expected)
+ {
+ $this->assertSame(
+ $expected,
+ Validate::credit_card($number, $type)
+ );
+ }
+
+ /**
+ * Provides test data for test_credit_card()
+ */
+ public function provider_luhn()
+ {
+ return array(
+ array('4222222222222', TRUE),
+ array('4012888888881881', TRUE),
+ array('5105105105105100', TRUE),
+ array('6011111111111117', TRUE),
+ array('60111111111111.7', FALSE),
+ array('6011111111111117X', FALSE),
+ array('6011111111111117 ', FALSE),
+ array('WORD ', FALSE),
+ );
+ }
+
+ /**
+ * Tests Validate::luhn()
+ *
+ * @test
+ * @covers Validate::luhn
+ * @group kohana.validation.helpers
+ * @dataProvider provider_luhn()
+ * @param string $number Credit card number
+ * @param boolean $expected
+ */
+ public function test_luhn($number, $expected)
+ {
+ $this->assertSame(
+ $expected,
+ Validate::luhn($number)
+ );
+ }
+
+ /**
+ * Provides test data for test_email()
+ *
+ * @return array
+ */
+ public function provider_email()
+ {
+ return array(
+ array('foo', TRUE, FALSE),
+ array('foo', FALSE, FALSE),
+
+ // RFC is less strict than the normal regex, presumably to allow
+ // admin@localhost, therefore we IGNORE IT!!!
+ array('foo@bar', FALSE, FALSE),
+ array('foo@bar.com', FALSE, TRUE),
+ array('foo@bar.sub.com', FALSE, TRUE),
+ array('foo+asd@bar.sub.com', FALSE, TRUE),
+ array('foo.asd@bar.sub.com', FALSE, TRUE),
+ );
+ }
+
+ /**
+ * Tests Validate::email()
+ *
+ * Check an email address for correct format.
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_email
+ * @param string $email Address to check
+ * @param boolean $strict Use strict settings
+ * @param boolean $correct Is $email address valid?
+ */
+ public function test_email($email, $strict, $correct)
+ {
+ $this->assertSame(
+ $correct,
+ Validate::email($email, $strict)
+ );
+ }
+
+ /**
+ * Returns test data for test_email_domain()
+ *
+ * @return array
+ */
+ public function provider_email_domain()
+ {
+ return array(
+ array('google.com', TRUE),
+ // Don't anybody dare register this...
+ array('DAWOMAWIDAIWNDAIWNHDAWIHDAIWHDAIWOHDAIOHDAIWHD.com', FALSE)
+ );
+ }
+
+ /**
+ * Tests Validate::email_domain()
+ *
+ * Validate the domain of an email address by checking if the domain has a
+ * valid MX record.
+ *
+ * Test skips on windows
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_email_domain
+ * @param string $email Email domain to check
+ * @param boolean $correct Is it correct?
+ */
+ public function test_email_domain($email, $correct)
+ {
+ if ( ! $this->hasInternet())
+ $this->markTestSkipped('An internet connection is required for this test');
+
+ if( ! Kohana::$is_windows OR version_compare(PHP_VERSION, '5.3.0', '>='))
+ {
+ $this->assertSame(
+ $correct,
+ Validate::email_domain($email)
+ );
+ }
+ else
+ {
+ $this->markTestSkipped('checkdnsrr() was not added on windows until PHP 5.3');
+ }
+ }
+
+ /**
+ * Provides data for test_exact_length()
+ *
+ * @return array
+ */
+ public function provider_exact_length()
+ {
+ return array(
+ array('somestring', 10, TRUE),
+ array('anotherstring', 13, TRUE),
+ );
+ }
+
+ /**
+ *
+ * Tests Validate::exact_length()
+ *
+ * Checks that a field is exactly the right length.
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_exact_length
+ * @param string $string The string to length check
+ * @param integer $length The length of the string
+ * @param boolean $correct Is $length the actual length of the string?
+ * @return bool
+ */
+ public function test_exact_length($string, $length, $correct)
+ {
+ return $this->assertSame(
+ $correct,
+ Validate::exact_length($string, $length),
+ 'Reported string length is not correct'
+ );
+ }
+
+ /**
+ * Provides data for test_equals()
+ *
+ * @return array
+ */
+ public function provider_equals()
+ {
+ return array(
+ array('foo', 'foo', TRUE),
+ array('1', '1', TRUE),
+ array(1, '1', FALSE),
+ array('011', 011, FALSE),
+ );
+ }
+
+ /**
+ * Tests Validate::equals()
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_equals
+ * @param string $string value to check
+ * @param integer $required required value
+ * @param boolean $correct is $string the same as $required?
+ * @return boolean
+ */
+ public function test_equals($string, $required, $correct)
+ {
+ return $this->assertSame(
+ $correct,
+ Validate::equals($string, $required),
+ 'Values are not equal'
+ );
+ }
+
+ /**
+ * Tests Validate::factory()
+ *
+ * Makes sure that the factory method returns an instance of Validate lib
+ * and that it uses the variables passed
+ *
+ * @test
+ */
+ public function test_factory_method_returns_instance_with_values()
+ {
+ $values = array(
+ 'this' => 'something else',
+ 'writing tests' => 'sucks',
+ 'why the hell' => 'amIDoingThis',
+ );
+
+ $instance = Validate::factory($values);
+
+ $this->assertTrue($instance instanceof Validate);
+
+ $this->assertSame(
+ $values,
+ $instance->as_array()
+ );
+ }
+
+ /**
+ * DataProvider for the valid::ip() test
+ * @return array
+ */
+ public function provider_ip()
+ {
+ return array(
+ array('75.125.175.50', FALSE, TRUE),
+ array('127.0.0.1', FALSE, TRUE),
+ array('256.257.258.259', FALSE, FALSE),
+ array('255.255.255.255', FALSE, FALSE),
+ array('192.168.0.1', FALSE, FALSE),
+ array('192.168.0.1', TRUE, TRUE)
+ );
+ }
+
+ /**
+ * Tests Validate::ip()
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_ip
+ * @param string $input_ip
+ * @param boolean $allow_private
+ * @param boolean $expected_result
+ */
+ public function test_ip($input_ip, $allow_private, $expected_result)
+ {
+ $this->assertEquals(
+ $expected_result,
+ Validate::ip($input_ip, $allow_private)
+ );
+ }
+
+ /**
+ * Returns test data for test_max_length()
+ *
+ * @return array
+ */
+ public function provider_max_length()
+ {
+ return array(
+ // Border line
+ array('some', 4, TRUE),
+ // Exceeds
+ array('KOHANARULLLES', 2, FALSE),
+ // Under
+ array('CakeSucks', 10, TRUE)
+ );
+ }
+
+ /**
+ * Tests Validate::max_length()
+ *
+ * Checks that a field is short enough.
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_max_length
+ * @param string $string String to test
+ * @param integer $maxlength Max length for this string
+ * @param boolean $correct Is $string <= $maxlength
+ */
+ public function test_max_length($string, $maxlength, $correct)
+ {
+ $this->assertSame(
+ $correct,
+ Validate::max_length($string, $maxlength)
+ );
+ }
+
+ /**
+ * Returns test data for test_min_length()
+ *
+ * @return array
+ */
+ public function provider_min_length()
+ {
+ return array(
+ array('This is obviously long enough', 10, TRUE),
+ array('This is not', 101, FALSE),
+ array('This is on the borderline', 25, TRUE)
+ );
+ }
+
+ /**
+ * Tests Validate::min_length()
+ *
+ * Checks that a field is long enough.
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_min_length
+ * @param string $string String to compare
+ * @param integer $minlength The minimum allowed length
+ * @param boolean $correct Is $string 's length >= $minlength
+ */
+ public function test_min_length($string, $minlength, $correct)
+ {
+ $this->assertSame(
+ $correct,
+ Validate::min_length($string, $minlength)
+ );
+ }
+
+ /**
+ * Returns test data for test_not_empty()
+ *
+ * @return array
+ */
+ public function provider_not_empty()
+ {
+ // Create a blank arrayObject
+ $ao = new ArrayObject;
+
+ // arrayObject with value
+ $ao1 = new ArrayObject;
+ $ao1['test'] = 'value';
+
+ return array(
+ array(array(), FALSE),
+ array(NULL, FALSE),
+ array('', FALSE),
+ array($ao, FALSE),
+ array($ao1, TRUE),
+ array(array(NULL), TRUE),
+ array(0, TRUE),
+ array('0', TRUE),
+ array('Something', TRUE),
+ );
+ }
+
+ /**
+ * Tests Validate::not_empty()
+ *
+ * Checks if a field is not empty.
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_not_empty
+ * @param mixed $value Value to check
+ * @param boolean $empty Is the value really empty?
+ */
+ public function test_not_empty($value, $empty)
+ {
+ return $this->assertSame(
+ $empty,
+ Validate::not_empty($value)
+ );
+ }
+
+ /**
+ * DataProvider for the Validate::numeric() test
+ */
+ public function provider_numeric()
+ {
+ return array(
+ array(12345, TRUE),
+ array(123.45, TRUE),
+ array('12345', TRUE),
+ array('10.5', TRUE),
+ array('-10.5', TRUE),
+ array('10.5a', FALSE),
+ // @issue 3240
+ array(.4, TRUE),
+ array(-.4, TRUE),
+ array(4., TRUE),
+ array(-4., TRUE),
+ array('.5', TRUE),
+ array('-.5', TRUE),
+ array('5.', TRUE),
+ array('-5.', TRUE),
+ array('.', FALSE),
+ array('1.2.3', FALSE),
+ );
+ }
+
+ /**
+ * Tests Validate::numeric()
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_numeric
+ * @param string $input Input to test
+ * @param boolean $expected Whether or not $input is numeric
+ */
+ public function test_numeric($input, $expected)
+ {
+ $this->assertSame(
+ $expected,
+ Validate::numeric($input)
+ );
+ }
+
+ /**
+ * Provides test data for test_phone()
+ * @return array
+ */
+ public function provider_phone()
+ {
+ return array(
+ array('0163634840', NULL, TRUE),
+ array('+27173634840', NULL, TRUE),
+ array('123578', NULL, FALSE),
+ // Some uk numbers
+ array('01234456778', NULL, TRUE),
+ array('+0441234456778', NULL, FALSE),
+ // Google UK case you're interested
+ array('+44 20-7031-3000', array(12), TRUE),
+ // BT Corporate
+ array('020 7356 5000', NULL, TRUE),
+ );
+ }
+
+ /**
+ * Tests Validate::phone()
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_phone
+ * @param string $phone Phone number to test
+ * @param boolean $expected Is $phone valid
+ */
+ public function test_phone($phone, $lengths, $expected)
+ {
+ $this->assertSame(
+ $expected,
+ Validate::phone($phone, $lengths)
+ );
+ }
+
+ /**
+ * DataProvider for the valid::regex() test
+ */
+ public function provider_regex()
+ {
+ return array(
+ array('hello world', '/[a-zA-Z\s]++/', TRUE),
+ array('123456789', '/[0-9]++/', TRUE),
+ array('£$%£%', '/[abc]/', FALSE),
+ array('Good evening', '/hello/', FALSE),
+ );
+ }
+
+ /**
+ * Tests Validate::range()
+ *
+ * Tests if a number is within a range.
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_regex
+ * @param string Value to test against
+ * @param string Valid pcre regular expression
+ * @param bool Does the value match the expression?
+ */
+ public function test_regex($value, $regex, $expected)
+ {
+ $this->AssertSame(
+ $expected,
+ Validate::regex($value, $regex)
+ );
+ }
+
+ /**
+ * DataProvider for the valid::range() test
+ */
+ public function provider_range()
+ {
+ return array(
+ array(1, 0, 2, TRUE),
+ array(-1, -5, 0, TRUE),
+ array(-1, 0, 1, FALSE),
+ array(1, 0, 0, FALSE),
+ array(2147483647, 0, 200000000000000, TRUE),
+ array(-2147483647, -2147483655, 2147483645, TRUE)
+ );
+ }
+
+ /**
+ * Tests Validate::range()
+ *
+ * Tests if a number is within a range.
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_range
+ * @param integer $number Number to test
+ * @param integer $min Lower bound
+ * @param integer $max Upper bound
+ * @param boolean $expected Is Number within the bounds of $min && $max
+ */
+ public function test_range($number, $min, $max, $expected)
+ {
+ $this->AssertSame(
+ $expected,
+ Validate::range($number, $min, $max)
+ );
+ }
+
+ /**
+ * Provides test data for test_url()
+ *
+ * @return array
+ */
+ public function provider_url()
+ {
+ $data = array(
+ array('http://google.com', TRUE),
+ array('http://google.com/', TRUE),
+ array('http://google.com/?q=abc', TRUE),
+ array('http://google.com/#hash', TRUE),
+ array('http://localhost', TRUE),
+ array('http://hello-world.pl', TRUE),
+ array('http://hello--world.pl', TRUE),
+ array('http://h.e.l.l.0.pl', TRUE),
+ array('http://server.tld/get/info', TRUE),
+ array('http://127.0.0.1', TRUE),
+ array('http://127.0.0.1:80', TRUE),
+ array('http://user@127.0.0.1', TRUE),
+ array('http://user:pass@127.0.0.1', TRUE),
+ array('ftp://my.server.com', TRUE),
+ array('rss+xml://rss.example.com', TRUE),
+
+ array('http://google.2com', FALSE),
+ array('http://google.com?q=abc', FALSE),
+ array('http://google.com#hash', FALSE),
+ array('http://hello-.pl', FALSE),
+ array('http://hel.-lo.world.pl', FALSE),
+ array('http://ww£.google.com', FALSE),
+ array('http://127.0.0.1234', FALSE),
+ array('http://127.0.0.1.1', FALSE),
+ array('http://user:@127.0.0.1', FALSE),
+ array("http://finalnewline.com\n", FALSE),
+ );
+
+ $data[] = array('http://'.str_repeat('123456789.', 25).'com/', TRUE); // 253 chars
+ $data[] = array('http://'.str_repeat('123456789.', 25).'info/', FALSE); // 254 chars
+
+ return $data;
+ }
+
+ /**
+ * Tests Validate::url()
+ *
+ * @test
+ * @group kohana.validation.helpers
+ * @dataProvider provider_url
+ * @param string $url The url to test
+ * @param boolean $expected Is it valid?
+ */
+ public function test_url($url, $expected)
+ {
+ $this->assertSame(
+ $expected,
+ Validate::url($url)
+ );
+ }
+
+ /**
+ * When we copy() a validate object, we should have a new validate object
+ * with the exact same attributes, apart from the data, which should be the
+ * same as the array we pass to copy()
+ *
+ * @test
+ * @covers Validate::copy
+ */
+ public function test_copy_copies_all_attributes_except_data()
+ {
+ $validate = new Validate(array('foo' => 'bar', 'fud' => 'fear, uncertainty, doubt', 'num' => 9));
+
+ $validate->rule('num', 'is_int')->rule('foo', 'is_string');
+
+ $validate->callback('foo', 'heh', array('ding'));
+
+ $copy_data = array('foo' => 'no', 'fud' => 'maybe', 'num' => 42);
+
+ $copy = $validate->copy($copy_data);
+
+ $this->assertNotSame($validate, $copy);
+
+ foreach(array('_filters', '_rules', '_callbacks', '_labels', '_empty_rules', '_errors') as $attribute)
+ {
+ // This is just an easy way to check that the attributes are identical
+ // Without hardcoding the expected values
+ $this->assertAttributeSame(
+ self::readAttribute($validate, $attribute),
+ $attribute,
+ $copy
+ );
+ }
+
+ $this->assertSame($copy_data, $copy->as_array());
+ }
+
+ /**
+ * By default there should be no callbacks registered with validate
+ *
+ * @test
+ */
+ public function test_initially_there_are_no_callbacks()
+ {
+ $validate = new Validate(array());
+
+ $this->assertAttributeSame(array(), '_callbacks', $validate);
+ }
+
+ /**
+ * This is just a quick check that callback() returns a reference to $this
+ *
+ * @test
+ * @covers Validate::callback
+ */
+ public function test_callback_returns_chainable_this()
+ {
+ $validate = new Validate(array());
+
+ $this->assertSame($validate, $validate->callback('field', 'something'));
+ }
+
+ /**
+ * Check that callback() is storign callbacks in the correct manner
+ *
+ * @test
+ * @covers Validate::callback
+ */
+ public function test_callback_stores_callback()
+ {
+ $validate = new Validate(array('id' => 355));
+
+ $validate->callback('id', 'misc_callback');
+
+ $this->assertAttributeSame(
+ array(
+ 'id' => array(array('misc_callback', array())),
+ ),
+ '_callbacks',
+ $validate
+ );
+ }
+
+ /**
+ * Calling Validate::callbacks() should store multiple callbacks for the specified field
+ *
+ * @test
+ * @covers Validate::callbacks
+ * @covers Validate::callback
+ */
+ public function test_callbacks_stores_multiple_callbacks()
+ {
+ $validate = new Validate(array('year' => 1999));
+
+ $validate->callbacks('year', array('misc_callback', 'another_callback'));
+
+ $this->assertAttributeSame(
+ array(
+ 'year' => array(
+ array('misc_callback', array()),
+ array('another_callback', array()),
+ ),
+ ),
+ '_callbacks',
+ $validate
+ );
+ }
+
+ /**
+ * When the validate object is initially created there should be no labels
+ * specified
+ *
+ * @test
+ */
+ public function test_initially_there_are_no_labels()
+ {
+ $validate = new Validate(array());
+
+ $this->assertAttributeSame(array(), '_labels', $validate);
+ }
+
+ /**
+ * Adding a label to a field should set it in the labels array
+ * If the label already exists it should overwrite it
+ *
+ * In both cases thefunction should return a reference to $this
+ *
+ * @test
+ * @covers Validate::label
+ */
+ public function test_label_adds_and_overwrites_label_and_returns_this()
+ {
+ $validate = new Validate(array());
+
+ $this->assertSame($validate, $validate->label('email', 'Email Address'));
+
+ $this->assertAttributeSame(array('email' => 'Email Address'), '_labels', $validate);
+
+ $this->assertSame($validate, $validate->label('email', 'Your Email'));
+
+ $validate->label('name', 'Your Name');
+
+ $this->assertAttributeSame(
+ array('email' => 'Your Email', 'name' => 'Your Name'),
+ '_labels',
+ $validate
+ );
+ }
+
+ /**
+ * Using labels() we should be able to add / overwrite multiple labels
+ *
+ * The function should also return $this for chaining purposes
+ *
+ * @test
+ * @covers Validate::labels
+ */
+ public function test_labels_adds_and_overwrites_multiple_labels_and_returns_this()
+ {
+ $validate = new Validate(array());
+ $initial_data = array('kung fu' => 'fighting', 'fast' => 'cheetah');
+
+ $this->assertSame($validate, $validate->labels($initial_data));
+
+ $this->assertAttributeSame($initial_data, '_labels', $validate);
+
+ $this->assertSame($validate, $validate->labels(array('fast' => 'lightning')));
+
+ $this->assertAttributeSame(
+ array('fast' => 'lightning', 'kung fu' => 'fighting'),
+ '_labels',
+ $validate
+ );
+ }
+
+ /**
+ * We should be able to add a filter to the queue by calling filter()
+ *
+ * @test
+ * @covers Validate::filter
+ */
+ public function test_filter_adds_a_filter_and_returns_this()
+ {
+ $validate = new Validate(array());
+
+ $this->assertSame($validate, $validate->filter('name', 'trim'));
+
+ $this->assertAttributeSame(
+ array('name' => array('trim' => array())),
+ '_filters',
+ $validate
+ );
+ }
+
+ /**
+ * filters() should be able to add multiple filters for a field and return
+ * $this when done
+ *
+ * @test
+ * @covers Validate::filters
+ */
+ public function test_filters_adds_multiple_filters_and_returns_this()
+ {
+ $validate = new Validate(array());
+
+ $this->assertSame(
+ $validate,
+ $validate->filters('id', array('trim' => NULL, 'some_func' => array('yes', 'no')))
+ );
+
+ $this->assertAttributeSame(
+ array('id' => array('trim' => array(), 'some_func' => array('yes', 'no'))),
+ '_filters',
+ $validate
+ );
+ }
+
+ /**
+ * Provides test data for test_check
+ *
+ * @return array
+ */
+ public function provider_check()
+ {
+ $mock = $this->getMock('Crazy_Test', array('unit_test_callback'));
+ // TODO: enchance this / make params more specific
+ $mock
+ ->expects($this->once())
+ ->method('unit_test_callback')
+ ->withAnyParameters();
+
+ // $first_array, $second_array, $rules, $first_expected, $second_expected
+ return array(
+ array(
+ array('foo' => 'bar'),
+ array('foo' => array('not_empty', NULL)),
+ array('foo' => array($mock, 'unit_test_callback')),
+ TRUE,
+ array(),
+ ),
+ array(
+ array('unit' => 'test'),
+ array('foo' => array('not_empty', NULL), 'unit' => array('min_length', 6)),
+ array(),
+ FALSE,
+ array('foo' => 'foo must not be empty', 'unit' => 'unit must be at least 6 characters long'),
+ ),
+ );
+ }
+
+ /**
+ * Tests Validate::check()
+ *
+ * @test
+ * @covers Validate::check
+ * @covers Validate::callbacks
+ * @covers Validate::callback
+ * @covers Validate::rule
+ * @covers Validate::rules
+ * @covers Validate::errors
+ * @covers Validate::error
+ * @dataProvider provider_check
+ * @param string $url The url to test
+ * @param boolean $expected Is it valid?
+ */
+ public function test_check($array, $rules, $callbacks, $expected, $expected_errors)
+ {
+ $validate = new Validate($array);
+
+ foreach ($rules as $field => $rule)
+ {
+ $validate->rule($field, $rule[0], array($rule[1]));
+ }
+ foreach ($callbacks as $field => $callback)
+ $validate->callback($field, $callback);
+
+ $status = $validate->check();
+ $errors = $validate->errors(TRUE);
+
+ $this->assertSame($expected, $status);
+ $this->assertSame($expected_errors, $errors);
+
+ $validate = new Validate($array);
+ foreach ($rules as $field => $rule)
+ $validate->rules($field, array($rule[0] => array($rule[1])));
+ $this->assertSame($expected, $validate->check());
+ }
+
+ /**
+ * This test asserts that Validate::check will call callbacks with all of the
+ * parameters supplied when the callback was specified
+ *
+ * @test
+ * @covers Validate::callback
+ */
+ public function test_object_callback_with_parameters()
+ {
+ $params = array(42, 'kohana' => 'rocks');
+
+ $validate = new Validate(array('foo' => 'bar'));
+
+ // Generate an isolated callback
+ $mock = $this->getMock('Random_Class_That_DNX', array('unit_test_callback'));
+
+ $mock->expects($this->once())
+ ->method('unit_test_callback')
+ ->with($validate, 'foo', $params);
+
+ $validate->callback('foo', array($mock, 'unit_test_callback'), $params);
+
+ $validate->check();
+ }
+
+ /**
+ * In some cases (such as when validating search params in GET) it is necessary for
+ * an empty array to validate successfully
+ *
+ * This test checks that Validate::check() allows the user to specify this setting when
+ * calling check()
+ *
+ * @test
+ * @ticket 3059
+ * @covers Validate::check
+ */
+ public function test_check_allows_option_for_empty_data_array_to_validate()
+ {
+ $validate = new Validate(array());
+
+ $this->assertFalse($validate->check(FALSE));
+
+ $this->assertTrue($validate->check(TRUE));
+
+ $validate->rule('name', 'not_empty');
+
+ $this->assertFalse($validate->check(TRUE));
+ $this->assertFalse($validate->check());
+ }
+
+ /**
+ * If you add a rule that says a field should match another field then
+ * a label should be added for the field to match against to ensure that
+ * it will be available when check() is called
+ *
+ * @test
+ * @ticket 3158
+ * @covers Validate::rule
+ */
+ public function test_rule_adds_label_if_rule_is_match_and_label_dnx()
+ {
+ $data = array('password' => 'lolcats', 'password_confirm' => 'lolcats');
+ $labels = array('password' => 'password', 'password_confirm' => 'password confirm');
+
+ $validate = new Validate($data);
+
+ $validate->rule('password', 'matches', array('password_confirm'));
+
+ $this->assertAttributeSame($labels, '_labels', $validate);
+
+ $this->assertTrue($validate->check());
+
+ // Now we do the dnx check
+
+ $validate = new Validate($data);
+
+ $labels = array('password_confirm' => 'TEH PASS') + $labels;
+ $validate->label('password_confirm', $labels['password_confirm']);
+
+ $validate->rule('password', 'matches', array('password_confirm'));
+
+ $this->assertAttributeSame($labels, '_labels', $validate);
+
+ $this->assertTrue($validate->check());
+ }
+
+ /**
+ * Provides test data for test_errors()
+ *
+ * @return array
+ */
+ public function provider_errors()
+ {
+ // [data, rules, expected], ...
+ return array(
+ array(
+ array('username' => 'frank'),
+ array('username' => array('not_empty' => NULL)),
+ array(),
+ ),
+ array(
+ array('username' => ''),
+ array('username' => array('not_empty' => NULL)),
+ array('username' => 'username must not be empty'),
+ ),
+ array(
+ array('username1' => ''),
+ array('username1' => array('not_empty' => NULL)),
+ array('username1' => 'username must not be empty'),
+ ),
+ );
+ }
+
+ /**
+ * Tests Validate::errors()
+ *
+ * @test
+ * @covers Validate::errors
+ * @dataProvider provider_errors
+ * @param string $url The url to test
+ * @param boolean $expected Is it valid?
+ */
+ public function test_errors($array, $rules, $expected)
+ {
+ $validate = Validate::factory($array);
+
+ foreach($rules as $field => $field_rules)
+ {
+ $validate->rules($field, $field_rules);
+ }
+
+ $validate->check();
+
+ $this->assertSame($expected, $validate->errors('validate', FALSE));
+ }
+}
diff --git a/includes/kohana/system/tests/test_data/github.png b/includes/kohana/system/tests/test_data/github.png
new file mode 100644
index 0000000..8117684
Binary files /dev/null and b/includes/kohana/system/tests/test_data/github.png differ
diff --git a/includes/kohana/system/utf8/from_unicode.php b/includes/kohana/system/utf8/from_unicode.php
new file mode 100644
index 0000000..bd3cd30
--- /dev/null
+++ b/includes/kohana/system/utf8/from_unicode.php
@@ -0,0 +1,68 @@
+= 0) AND ($arr[$k] <= 0x007f))
+ {
+ echo chr($arr[$k]);
+ }
+ // 2 byte sequence
+ elseif ($arr[$k] <= 0x07ff)
+ {
+ echo chr(0xc0 | ($arr[$k] >> 6));
+ echo chr(0x80 | ($arr[$k] & 0x003f));
+ }
+ // Byte order mark (skip)
+ elseif ($arr[$k] == 0xFEFF)
+ {
+ // nop -- zap the BOM
+ }
+ // Test for illegal surrogates
+ elseif ($arr[$k] >= 0xD800 AND $arr[$k] <= 0xDFFF)
+ {
+ // Found a surrogate
+ trigger_error('UTF8::from_unicode: Illegal surrogate at index: '.$k.', value: '.$arr[$k], E_USER_WARNING);
+ return FALSE;
+ }
+ // 3 byte sequence
+ elseif ($arr[$k] <= 0xffff)
+ {
+ echo chr(0xe0 | ($arr[$k] >> 12));
+ echo chr(0x80 | (($arr[$k] >> 6) & 0x003f));
+ echo chr(0x80 | ($arr[$k] & 0x003f));
+ }
+ // 4 byte sequence
+ elseif ($arr[$k] <= 0x10ffff)
+ {
+ echo chr(0xf0 | ($arr[$k] >> 18));
+ echo chr(0x80 | (($arr[$k] >> 12) & 0x3f));
+ echo chr(0x80 | (($arr[$k] >> 6) & 0x3f));
+ echo chr(0x80 | ($arr[$k] & 0x3f));
+ }
+ // Out of range
+ else
+ {
+ trigger_error('UTF8::from_unicode: Codepoint out of Unicode range at index: '.$k.', value: '.$arr[$k], E_USER_WARNING);
+ return FALSE;
+ }
+ }
+
+ $result = ob_get_contents();
+ ob_end_clean();
+ return $result;
+}
diff --git a/includes/kohana/system/utf8/ltrim.php b/includes/kohana/system/utf8/ltrim.php
new file mode 100644
index 0000000..5f897c4
--- /dev/null
+++ b/includes/kohana/system/utf8/ltrim.php
@@ -0,0 +1,22 @@
+= 0 AND $ord0 <= 127)
+ return $ord0;
+
+ if ( ! isset($chr[1]))
+ {
+ trigger_error('Short sequence - at least 2 bytes expected, only 1 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ $ord1 = ord($chr[1]);
+
+ if ($ord0 >= 192 AND $ord0 <= 223)
+ return ($ord0 - 192) * 64 + ($ord1 - 128);
+
+ if ( ! isset($chr[2]))
+ {
+ trigger_error('Short sequence - at least 3 bytes expected, only 2 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ $ord2 = ord($chr[2]);
+
+ if ($ord0 >= 224 AND $ord0 <= 239)
+ return ($ord0 - 224) * 4096 + ($ord1 - 128) * 64 + ($ord2 - 128);
+
+ if ( ! isset($chr[3]))
+ {
+ trigger_error('Short sequence - at least 4 bytes expected, only 3 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ $ord3 = ord($chr[3]);
+
+ if ($ord0 >= 240 AND $ord0 <= 247)
+ return ($ord0 - 240) * 262144 + ($ord1 - 128) * 4096 + ($ord2-128) * 64 + ($ord3 - 128);
+
+ if ( ! isset($chr[4]))
+ {
+ trigger_error('Short sequence - at least 5 bytes expected, only 4 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ $ord4 = ord($chr[4]);
+
+ if ($ord0 >= 248 AND $ord0 <= 251)
+ return ($ord0 - 248) * 16777216 + ($ord1-128) * 262144 + ($ord2 - 128) * 4096 + ($ord3 - 128) * 64 + ($ord4 - 128);
+
+ if ( ! isset($chr[5]))
+ {
+ trigger_error('Short sequence - at least 6 bytes expected, only 5 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ if ($ord0 >= 252 AND $ord0 <= 253)
+ return ($ord0 - 252) * 1073741824 + ($ord1 - 128) * 16777216 + ($ord2 - 128) * 262144 + ($ord3 - 128) * 4096 + ($ord4 - 128) * 64 + (ord($chr[5]) - 128);
+
+ if ($ord0 >= 254 AND $ord0 <= 255)
+ {
+ trigger_error('Invalid UTF-8 with surrogate ordinal '.$ord0, E_USER_WARNING);
+ return FALSE;
+ }
+}
\ No newline at end of file
diff --git a/includes/kohana/system/utf8/rtrim.php b/includes/kohana/system/utf8/rtrim.php
new file mode 100644
index 0000000..72b8261
--- /dev/null
+++ b/includes/kohana/system/utf8/rtrim.php
@@ -0,0 +1,22 @@
+ $val)
+ {
+ $str[$key] = UTF8::str_ireplace($search, $replace, $val, $count);
+ }
+ return $str;
+ }
+
+ if (is_array($search))
+ {
+ $keys = array_keys($search);
+
+ foreach ($keys as $k)
+ {
+ if (is_array($replace))
+ {
+ if (array_key_exists($k, $replace))
+ {
+ $str = UTF8::str_ireplace($search[$k], $replace[$k], $str, $count);
+ }
+ else
+ {
+ $str = UTF8::str_ireplace($search[$k], '', $str, $count);
+ }
+ }
+ else
+ {
+ $str = UTF8::str_ireplace($search[$k], $replace, $str, $count);
+ }
+ }
+ return $str;
+ }
+
+ $search = UTF8::strtolower($search);
+ $str_lower = UTF8::strtolower($str);
+
+ $total_matched_strlen = 0;
+ $i = 0;
+
+ while (preg_match('/(.*?)'.preg_quote($search, '/').'/s', $str_lower, $matches))
+ {
+ $matched_strlen = strlen($matches[0]);
+ $str_lower = substr($str_lower, $matched_strlen);
+
+ $offset = $total_matched_strlen + strlen($matches[1]) + ($i * (strlen($replace) - 1));
+ $str = substr_replace($str, $replace, $offset, strlen($search));
+
+ $total_matched_strlen += $matched_strlen;
+ $i++;
+ }
+
+ $count += $i;
+ return $str;
+}
diff --git a/includes/kohana/system/utf8/str_pad.php b/includes/kohana/system/utf8/str_pad.php
new file mode 100644
index 0000000..3ae2d7b
--- /dev/null
+++ b/includes/kohana/system/utf8/str_pad.php
@@ -0,0 +1,50 @@
+0x0061, 0x03A6=>0x03C6, 0x0162=>0x0163, 0x00C5=>0x00E5, 0x0042=>0x0062,
+ 0x0139=>0x013A, 0x00C1=>0x00E1, 0x0141=>0x0142, 0x038E=>0x03CD, 0x0100=>0x0101,
+ 0x0490=>0x0491, 0x0394=>0x03B4, 0x015A=>0x015B, 0x0044=>0x0064, 0x0393=>0x03B3,
+ 0x00D4=>0x00F4, 0x042A=>0x044A, 0x0419=>0x0439, 0x0112=>0x0113, 0x041C=>0x043C,
+ 0x015E=>0x015F, 0x0143=>0x0144, 0x00CE=>0x00EE, 0x040E=>0x045E, 0x042F=>0x044F,
+ 0x039A=>0x03BA, 0x0154=>0x0155, 0x0049=>0x0069, 0x0053=>0x0073, 0x1E1E=>0x1E1F,
+ 0x0134=>0x0135, 0x0427=>0x0447, 0x03A0=>0x03C0, 0x0418=>0x0438, 0x00D3=>0x00F3,
+ 0x0420=>0x0440, 0x0404=>0x0454, 0x0415=>0x0435, 0x0429=>0x0449, 0x014A=>0x014B,
+ 0x0411=>0x0431, 0x0409=>0x0459, 0x1E02=>0x1E03, 0x00D6=>0x00F6, 0x00D9=>0x00F9,
+ 0x004E=>0x006E, 0x0401=>0x0451, 0x03A4=>0x03C4, 0x0423=>0x0443, 0x015C=>0x015D,
+ 0x0403=>0x0453, 0x03A8=>0x03C8, 0x0158=>0x0159, 0x0047=>0x0067, 0x00C4=>0x00E4,
+ 0x0386=>0x03AC, 0x0389=>0x03AE, 0x0166=>0x0167, 0x039E=>0x03BE, 0x0164=>0x0165,
+ 0x0116=>0x0117, 0x0108=>0x0109, 0x0056=>0x0076, 0x00DE=>0x00FE, 0x0156=>0x0157,
+ 0x00DA=>0x00FA, 0x1E60=>0x1E61, 0x1E82=>0x1E83, 0x00C2=>0x00E2, 0x0118=>0x0119,
+ 0x0145=>0x0146, 0x0050=>0x0070, 0x0150=>0x0151, 0x042E=>0x044E, 0x0128=>0x0129,
+ 0x03A7=>0x03C7, 0x013D=>0x013E, 0x0422=>0x0442, 0x005A=>0x007A, 0x0428=>0x0448,
+ 0x03A1=>0x03C1, 0x1E80=>0x1E81, 0x016C=>0x016D, 0x00D5=>0x00F5, 0x0055=>0x0075,
+ 0x0176=>0x0177, 0x00DC=>0x00FC, 0x1E56=>0x1E57, 0x03A3=>0x03C3, 0x041A=>0x043A,
+ 0x004D=>0x006D, 0x016A=>0x016B, 0x0170=>0x0171, 0x0424=>0x0444, 0x00CC=>0x00EC,
+ 0x0168=>0x0169, 0x039F=>0x03BF, 0x004B=>0x006B, 0x00D2=>0x00F2, 0x00C0=>0x00E0,
+ 0x0414=>0x0434, 0x03A9=>0x03C9, 0x1E6A=>0x1E6B, 0x00C3=>0x00E3, 0x042D=>0x044D,
+ 0x0416=>0x0436, 0x01A0=>0x01A1, 0x010C=>0x010D, 0x011C=>0x011D, 0x00D0=>0x00F0,
+ 0x013B=>0x013C, 0x040F=>0x045F, 0x040A=>0x045A, 0x00C8=>0x00E8, 0x03A5=>0x03C5,
+ 0x0046=>0x0066, 0x00DD=>0x00FD, 0x0043=>0x0063, 0x021A=>0x021B, 0x00CA=>0x00EA,
+ 0x0399=>0x03B9, 0x0179=>0x017A, 0x00CF=>0x00EF, 0x01AF=>0x01B0, 0x0045=>0x0065,
+ 0x039B=>0x03BB, 0x0398=>0x03B8, 0x039C=>0x03BC, 0x040C=>0x045C, 0x041F=>0x043F,
+ 0x042C=>0x044C, 0x00DE=>0x00FE, 0x00D0=>0x00F0, 0x1EF2=>0x1EF3, 0x0048=>0x0068,
+ 0x00CB=>0x00EB, 0x0110=>0x0111, 0x0413=>0x0433, 0x012E=>0x012F, 0x00C6=>0x00E6,
+ 0x0058=>0x0078, 0x0160=>0x0161, 0x016E=>0x016F, 0x0391=>0x03B1, 0x0407=>0x0457,
+ 0x0172=>0x0173, 0x0178=>0x00FF, 0x004F=>0x006F, 0x041B=>0x043B, 0x0395=>0x03B5,
+ 0x0425=>0x0445, 0x0120=>0x0121, 0x017D=>0x017E, 0x017B=>0x017C, 0x0396=>0x03B6,
+ 0x0392=>0x03B2, 0x0388=>0x03AD, 0x1E84=>0x1E85, 0x0174=>0x0175, 0x0051=>0x0071,
+ 0x0417=>0x0437, 0x1E0A=>0x1E0B, 0x0147=>0x0148, 0x0104=>0x0105, 0x0408=>0x0458,
+ 0x014C=>0x014D, 0x00CD=>0x00ED, 0x0059=>0x0079, 0x010A=>0x010B, 0x038F=>0x03CE,
+ 0x0052=>0x0072, 0x0410=>0x0430, 0x0405=>0x0455, 0x0402=>0x0452, 0x0126=>0x0127,
+ 0x0136=>0x0137, 0x012A=>0x012B, 0x038A=>0x03AF, 0x042B=>0x044B, 0x004C=>0x006C,
+ 0x0397=>0x03B7, 0x0124=>0x0125, 0x0218=>0x0219, 0x00DB=>0x00FB, 0x011E=>0x011F,
+ 0x041E=>0x043E, 0x1E40=>0x1E41, 0x039D=>0x03BD, 0x0106=>0x0107, 0x03AB=>0x03CB,
+ 0x0426=>0x0446, 0x00DE=>0x00FE, 0x00C7=>0x00E7, 0x03AA=>0x03CA, 0x0421=>0x0441,
+ 0x0412=>0x0432, 0x010E=>0x010F, 0x00D8=>0x00F8, 0x0057=>0x0077, 0x011A=>0x011B,
+ 0x0054=>0x0074, 0x004A=>0x006A, 0x040B=>0x045B, 0x0406=>0x0456, 0x0102=>0x0103,
+ 0x039B=>0x03BB, 0x00D1=>0x00F1, 0x041D=>0x043D, 0x038C=>0x03CC, 0x00C9=>0x00E9,
+ 0x00D0=>0x00F0, 0x0407=>0x0457, 0x0122=>0x0123,
+ );
+ }
+
+ $uni = UTF8::to_unicode($str);
+
+ if ($uni === FALSE)
+ return FALSE;
+
+ for ($i = 0, $c = count($uni); $i < $c; $i++)
+ {
+ if (isset($utf8_upper_to_lower[$uni[$i]]))
+ {
+ $uni[$i] = $utf8_upper_to_lower[$uni[$i]];
+ }
+ }
+
+ return UTF8::from_unicode($uni);
+}
\ No newline at end of file
diff --git a/includes/kohana/system/utf8/strtoupper.php b/includes/kohana/system/utf8/strtoupper.php
new file mode 100644
index 0000000..4c55c60
--- /dev/null
+++ b/includes/kohana/system/utf8/strtoupper.php
@@ -0,0 +1,81 @@
+0x0041, 0x03C6=>0x03A6, 0x0163=>0x0162, 0x00E5=>0x00C5, 0x0062=>0x0042,
+ 0x013A=>0x0139, 0x00E1=>0x00C1, 0x0142=>0x0141, 0x03CD=>0x038E, 0x0101=>0x0100,
+ 0x0491=>0x0490, 0x03B4=>0x0394, 0x015B=>0x015A, 0x0064=>0x0044, 0x03B3=>0x0393,
+ 0x00F4=>0x00D4, 0x044A=>0x042A, 0x0439=>0x0419, 0x0113=>0x0112, 0x043C=>0x041C,
+ 0x015F=>0x015E, 0x0144=>0x0143, 0x00EE=>0x00CE, 0x045E=>0x040E, 0x044F=>0x042F,
+ 0x03BA=>0x039A, 0x0155=>0x0154, 0x0069=>0x0049, 0x0073=>0x0053, 0x1E1F=>0x1E1E,
+ 0x0135=>0x0134, 0x0447=>0x0427, 0x03C0=>0x03A0, 0x0438=>0x0418, 0x00F3=>0x00D3,
+ 0x0440=>0x0420, 0x0454=>0x0404, 0x0435=>0x0415, 0x0449=>0x0429, 0x014B=>0x014A,
+ 0x0431=>0x0411, 0x0459=>0x0409, 0x1E03=>0x1E02, 0x00F6=>0x00D6, 0x00F9=>0x00D9,
+ 0x006E=>0x004E, 0x0451=>0x0401, 0x03C4=>0x03A4, 0x0443=>0x0423, 0x015D=>0x015C,
+ 0x0453=>0x0403, 0x03C8=>0x03A8, 0x0159=>0x0158, 0x0067=>0x0047, 0x00E4=>0x00C4,
+ 0x03AC=>0x0386, 0x03AE=>0x0389, 0x0167=>0x0166, 0x03BE=>0x039E, 0x0165=>0x0164,
+ 0x0117=>0x0116, 0x0109=>0x0108, 0x0076=>0x0056, 0x00FE=>0x00DE, 0x0157=>0x0156,
+ 0x00FA=>0x00DA, 0x1E61=>0x1E60, 0x1E83=>0x1E82, 0x00E2=>0x00C2, 0x0119=>0x0118,
+ 0x0146=>0x0145, 0x0070=>0x0050, 0x0151=>0x0150, 0x044E=>0x042E, 0x0129=>0x0128,
+ 0x03C7=>0x03A7, 0x013E=>0x013D, 0x0442=>0x0422, 0x007A=>0x005A, 0x0448=>0x0428,
+ 0x03C1=>0x03A1, 0x1E81=>0x1E80, 0x016D=>0x016C, 0x00F5=>0x00D5, 0x0075=>0x0055,
+ 0x0177=>0x0176, 0x00FC=>0x00DC, 0x1E57=>0x1E56, 0x03C3=>0x03A3, 0x043A=>0x041A,
+ 0x006D=>0x004D, 0x016B=>0x016A, 0x0171=>0x0170, 0x0444=>0x0424, 0x00EC=>0x00CC,
+ 0x0169=>0x0168, 0x03BF=>0x039F, 0x006B=>0x004B, 0x00F2=>0x00D2, 0x00E0=>0x00C0,
+ 0x0434=>0x0414, 0x03C9=>0x03A9, 0x1E6B=>0x1E6A, 0x00E3=>0x00C3, 0x044D=>0x042D,
+ 0x0436=>0x0416, 0x01A1=>0x01A0, 0x010D=>0x010C, 0x011D=>0x011C, 0x00F0=>0x00D0,
+ 0x013C=>0x013B, 0x045F=>0x040F, 0x045A=>0x040A, 0x00E8=>0x00C8, 0x03C5=>0x03A5,
+ 0x0066=>0x0046, 0x00FD=>0x00DD, 0x0063=>0x0043, 0x021B=>0x021A, 0x00EA=>0x00CA,
+ 0x03B9=>0x0399, 0x017A=>0x0179, 0x00EF=>0x00CF, 0x01B0=>0x01AF, 0x0065=>0x0045,
+ 0x03BB=>0x039B, 0x03B8=>0x0398, 0x03BC=>0x039C, 0x045C=>0x040C, 0x043F=>0x041F,
+ 0x044C=>0x042C, 0x00FE=>0x00DE, 0x00F0=>0x00D0, 0x1EF3=>0x1EF2, 0x0068=>0x0048,
+ 0x00EB=>0x00CB, 0x0111=>0x0110, 0x0433=>0x0413, 0x012F=>0x012E, 0x00E6=>0x00C6,
+ 0x0078=>0x0058, 0x0161=>0x0160, 0x016F=>0x016E, 0x03B1=>0x0391, 0x0457=>0x0407,
+ 0x0173=>0x0172, 0x00FF=>0x0178, 0x006F=>0x004F, 0x043B=>0x041B, 0x03B5=>0x0395,
+ 0x0445=>0x0425, 0x0121=>0x0120, 0x017E=>0x017D, 0x017C=>0x017B, 0x03B6=>0x0396,
+ 0x03B2=>0x0392, 0x03AD=>0x0388, 0x1E85=>0x1E84, 0x0175=>0x0174, 0x0071=>0x0051,
+ 0x0437=>0x0417, 0x1E0B=>0x1E0A, 0x0148=>0x0147, 0x0105=>0x0104, 0x0458=>0x0408,
+ 0x014D=>0x014C, 0x00ED=>0x00CD, 0x0079=>0x0059, 0x010B=>0x010A, 0x03CE=>0x038F,
+ 0x0072=>0x0052, 0x0430=>0x0410, 0x0455=>0x0405, 0x0452=>0x0402, 0x0127=>0x0126,
+ 0x0137=>0x0136, 0x012B=>0x012A, 0x03AF=>0x038A, 0x044B=>0x042B, 0x006C=>0x004C,
+ 0x03B7=>0x0397, 0x0125=>0x0124, 0x0219=>0x0218, 0x00FB=>0x00DB, 0x011F=>0x011E,
+ 0x043E=>0x041E, 0x1E41=>0x1E40, 0x03BD=>0x039D, 0x0107=>0x0106, 0x03CB=>0x03AB,
+ 0x0446=>0x0426, 0x00FE=>0x00DE, 0x00E7=>0x00C7, 0x03CA=>0x03AA, 0x0441=>0x0421,
+ 0x0432=>0x0412, 0x010F=>0x010E, 0x00F8=>0x00D8, 0x0077=>0x0057, 0x011B=>0x011A,
+ 0x0074=>0x0054, 0x006A=>0x004A, 0x045B=>0x040B, 0x0456=>0x0406, 0x0103=>0x0102,
+ 0x03BB=>0x039B, 0x00F1=>0x00D1, 0x043D=>0x041D, 0x03CC=>0x038C, 0x00E9=>0x00C9,
+ 0x00F0=>0x00D0, 0x0457=>0x0407, 0x0123=>0x0122,
+ );
+ }
+
+ $uni = UTF8::to_unicode($str);
+
+ if ($uni === FALSE)
+ return FALSE;
+
+ for ($i = 0, $c = count($uni); $i < $c; $i++)
+ {
+ if (isset($utf8_lower_to_upper[$uni[$i]]))
+ {
+ $uni[$i] = $utf8_lower_to_upper[$uni[$i]];
+ }
+ }
+
+ return UTF8::from_unicode($uni);
+}
\ No newline at end of file
diff --git a/includes/kohana/system/utf8/substr.php b/includes/kohana/system/utf8/substr.php
new file mode 100644
index 0000000..d74221d
--- /dev/null
+++ b/includes/kohana/system/utf8/substr.php
@@ -0,0 +1,72 @@
+= $strlen OR ($length < 0 AND $length <= $offset - $strlen))
+ return '';
+
+ // Whole string
+ if ($offset == 0 AND ($length === NULL OR $length >= $strlen))
+ return $str;
+
+ // Build regex
+ $regex = '^';
+
+ // Create an offset expression
+ if ($offset > 0)
+ {
+ // PCRE repeating quantifiers must be less than 65536, so repeat when necessary
+ $x = (int) ($offset / 65535);
+ $y = (int) ($offset % 65535);
+ $regex .= ($x == 0) ? '' : ('(?:.{65535}){'.$x.'}');
+ $regex .= ($y == 0) ? '' : ('.{'.$y.'}');
+ }
+
+ // Create a length expression
+ if ($length === NULL)
+ {
+ $regex .= '(.*)'; // No length set, grab it all
+ }
+ // Find length from the left (positive length)
+ elseif ($length > 0)
+ {
+ // Reduce length so that it can't go beyond the end of the string
+ $length = min($strlen - $offset, $length);
+
+ $x = (int) ($length / 65535);
+ $y = (int) ($length % 65535);
+ $regex .= '(';
+ $regex .= ($x == 0) ? '' : ('(?:.{65535}){'.$x.'}');
+ $regex .= '.{'.$y.'})';
+ }
+ // Find length from the right (negative length)
+ else
+ {
+ $x = (int) (-$length / 65535);
+ $y = (int) (-$length % 65535);
+ $regex .= '(.*)';
+ $regex .= ($x == 0) ? '' : ('(?:.{65535}){'.$x.'}');
+ $regex .= '.{'.$y.'}';
+ }
+
+ preg_match('/'.$regex.'/us', $str, $matches);
+ return $matches[1];
+}
\ No newline at end of file
diff --git a/includes/kohana/system/utf8/substr_replace.php b/includes/kohana/system/utf8/substr_replace.php
new file mode 100644
index 0000000..a86aca2
--- /dev/null
+++ b/includes/kohana/system/utf8/substr_replace.php
@@ -0,0 +1,22 @@
+ 0x10FFFF))
+ {
+ trigger_error('UTF8::to_unicode: Illegal sequence or codepoint in UTF-8 at byte '.$i, E_USER_WARNING);
+ return FALSE;
+ }
+
+ if (0xFEFF != $m_ucs4)
+ {
+ // BOM is legal but we don't want to output it
+ $out[] = $m_ucs4;
+ }
+
+ // Initialize UTF-8 cache
+ $m_state = 0;
+ $m_ucs4 = 0;
+ $m_bytes = 1;
+ }
+ }
+ else
+ {
+ // ((0xC0 & (*in) != 0x80) AND (m_state != 0))
+ // Incomplete multi-octet sequence
+ trigger_error('UTF8::to_unicode: Incomplete multi-octet sequence in UTF-8 at byte '.$i, E_USER_WARNING);
+ return FALSE;
+ }
+ }
+ }
+
+ return $out;
+}
\ No newline at end of file
diff --git a/includes/kohana/system/utf8/transliterate_to_ascii.php b/includes/kohana/system/utf8/transliterate_to_ascii.php
new file mode 100644
index 0000000..a34acad
--- /dev/null
+++ b/includes/kohana/system/utf8/transliterate_to_ascii.php
@@ -0,0 +1,77 @@
+ 'a', 'ô' => 'o', 'ď' => 'd', 'ḟ' => 'f', 'ë' => 'e', 'š' => 's', 'ơ' => 'o',
+ 'ß' => 'ss', 'ă' => 'a', 'ř' => 'r', 'ț' => 't', 'ň' => 'n', 'ā' => 'a', 'ķ' => 'k',
+ 'ŝ' => 's', 'ỳ' => 'y', 'ņ' => 'n', 'ĺ' => 'l', 'ħ' => 'h', 'ṗ' => 'p', 'ó' => 'o',
+ 'ú' => 'u', 'ě' => 'e', 'é' => 'e', 'ç' => 'c', 'ẁ' => 'w', 'ċ' => 'c', 'õ' => 'o',
+ 'ṡ' => 's', 'ø' => 'o', 'ģ' => 'g', 'ŧ' => 't', 'ș' => 's', 'ė' => 'e', 'ĉ' => 'c',
+ 'ś' => 's', 'î' => 'i', 'ű' => 'u', 'ć' => 'c', 'ę' => 'e', 'ŵ' => 'w', 'ṫ' => 't',
+ 'ū' => 'u', 'č' => 'c', 'ö' => 'o', 'è' => 'e', 'ŷ' => 'y', 'ą' => 'a', 'ł' => 'l',
+ 'ų' => 'u', 'ů' => 'u', 'ş' => 's', 'ğ' => 'g', 'ļ' => 'l', 'ƒ' => 'f', 'ž' => 'z',
+ 'ẃ' => 'w', 'ḃ' => 'b', 'å' => 'a', 'ì' => 'i', 'ï' => 'i', 'ḋ' => 'd', 'ť' => 't',
+ 'ŗ' => 'r', 'ä' => 'a', 'í' => 'i', 'ŕ' => 'r', 'ê' => 'e', 'ü' => 'u', 'ò' => 'o',
+ 'ē' => 'e', 'ñ' => 'n', 'ń' => 'n', 'ĥ' => 'h', 'ĝ' => 'g', 'đ' => 'd', 'ĵ' => 'j',
+ 'ÿ' => 'y', 'ũ' => 'u', 'ŭ' => 'u', 'ư' => 'u', 'ţ' => 't', 'ý' => 'y', 'ő' => 'o',
+ 'â' => 'a', 'ľ' => 'l', 'ẅ' => 'w', 'ż' => 'z', 'ī' => 'i', 'ã' => 'a', 'ġ' => 'g',
+ 'ṁ' => 'm', 'ō' => 'o', 'ĩ' => 'i', 'ù' => 'u', 'į' => 'i', 'ź' => 'z', 'á' => 'a',
+ 'û' => 'u', 'þ' => 'th', 'ð' => 'dh', 'æ' => 'ae', 'µ' => 'u', 'ĕ' => 'e', 'ı' => 'i',
+ );
+ }
+
+ $str = str_replace(
+ array_keys($utf8_lower_accents),
+ array_values($utf8_lower_accents),
+ $str
+ );
+ }
+
+ if ($case >= 0)
+ {
+ if ($utf8_upper_accents === NULL)
+ {
+ $utf8_upper_accents = array(
+ 'À' => 'A', 'Ô' => 'O', 'Ď' => 'D', 'Ḟ' => 'F', 'Ë' => 'E', 'Š' => 'S', 'Ơ' => 'O',
+ 'Ă' => 'A', 'Ř' => 'R', 'Ț' => 'T', 'Ň' => 'N', 'Ā' => 'A', 'Ķ' => 'K', 'Ĕ' => 'E',
+ 'Ŝ' => 'S', 'Ỳ' => 'Y', 'Ņ' => 'N', 'Ĺ' => 'L', 'Ħ' => 'H', 'Ṗ' => 'P', 'Ó' => 'O',
+ 'Ú' => 'U', 'Ě' => 'E', 'É' => 'E', 'Ç' => 'C', 'Ẁ' => 'W', 'Ċ' => 'C', 'Õ' => 'O',
+ 'Ṡ' => 'S', 'Ø' => 'O', 'Ģ' => 'G', 'Ŧ' => 'T', 'Ș' => 'S', 'Ė' => 'E', 'Ĉ' => 'C',
+ 'Ś' => 'S', 'Î' => 'I', 'Ű' => 'U', 'Ć' => 'C', 'Ę' => 'E', 'Ŵ' => 'W', 'Ṫ' => 'T',
+ 'Ū' => 'U', 'Č' => 'C', 'Ö' => 'O', 'È' => 'E', 'Ŷ' => 'Y', 'Ą' => 'A', 'Ł' => 'L',
+ 'Ų' => 'U', 'Ů' => 'U', 'Ş' => 'S', 'Ğ' => 'G', 'Ļ' => 'L', 'Ƒ' => 'F', 'Ž' => 'Z',
+ 'Ẃ' => 'W', 'Ḃ' => 'B', 'Å' => 'A', 'Ì' => 'I', 'Ï' => 'I', 'Ḋ' => 'D', 'Ť' => 'T',
+ 'Ŗ' => 'R', 'Ä' => 'A', 'Í' => 'I', 'Ŕ' => 'R', 'Ê' => 'E', 'Ü' => 'U', 'Ò' => 'O',
+ 'Ē' => 'E', 'Ñ' => 'N', 'Ń' => 'N', 'Ĥ' => 'H', 'Ĝ' => 'G', 'Đ' => 'D', 'Ĵ' => 'J',
+ 'Ÿ' => 'Y', 'Ũ' => 'U', 'Ŭ' => 'U', 'Ư' => 'U', 'Ţ' => 'T', 'Ý' => 'Y', 'Ő' => 'O',
+ 'Â' => 'A', 'Ľ' => 'L', 'Ẅ' => 'W', 'Ż' => 'Z', 'Ī' => 'I', 'Ã' => 'A', 'Ġ' => 'G',
+ 'Ṁ' => 'M', 'Ō' => 'O', 'Ĩ' => 'I', 'Ù' => 'U', 'Į' => 'I', 'Ź' => 'Z', 'Á' => 'A',
+ 'Û' => 'U', 'Þ' => 'Th', 'Ð' => 'Dh', 'Æ' => 'Ae', 'İ' => 'I',
+ );
+ }
+
+ $str = str_replace(
+ array_keys($utf8_upper_accents),
+ array_values($utf8_upper_accents),
+ $str
+ );
+ }
+
+ return $str;
+}
\ No newline at end of file
diff --git a/includes/kohana/system/utf8/trim.php b/includes/kohana/system/utf8/trim.php
new file mode 100644
index 0000000..1a1bfd7
--- /dev/null
+++ b/includes/kohana/system/utf8/trim.php
@@ -0,0 +1,17 @@
+
+
+
+
+
[ ]:
+
+
[ ]
+
+
+ $step): ?>
+
+
+
+
+ [ ]
+
+ {}
+
+
+ »
+ ( )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
()
+
+
+
()
+
+
+
+
+
+
+ $value): ?>
+
+
+
+
+
+
+
+
+
+
diff --git a/includes/kohana/system/views/kohana/generate_logo.php b/includes/kohana/system/views/kohana/generate_logo.php
new file mode 100644
index 0000000..43c0f6e
--- /dev/null
+++ b/includes/kohana/system/views/kohana/generate_logo.php
@@ -0,0 +1,14 @@
+ 'image/png', 'data' => '{$data}'); ?>");
\ No newline at end of file
diff --git a/includes/kohana/system/views/kohana/logo.php b/includes/kohana/system/views/kohana/logo.php
new file mode 100644
index 0000000..92aa02f
--- /dev/null
+++ b/includes/kohana/system/views/kohana/logo.php
@@ -0,0 +1,8 @@
+ 'image/png', 'data' => 'iVBORw0KGgoAAAANSUhEUgAAAL8AAAA+CAYAAAB6Bsp7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAACtppVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+Cjx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDQuMi4yLWMwNjMgNTMuMzUyNjI0LCAyMDA4LzA3LzMwLTE4OjA1OjQxICAgICAgICAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgeG1sbnM6eG1wUmlnaHRzPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvcmlnaHRzLyIKICAgeG1wUmlnaHRzOk1hcmtlZD0iRmFsc2UiCiAgIHhtcFJpZ2h0czpXZWJTdGF0ZW1lbnQ9IiI+CiAgIDxkYzpyaWdodHM+CiAgICA8cmRmOkFsdD4KICAgICA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiLz4KICAgIDwvcmRmOkFsdD4KICAgPC9kYzpyaWdodHM+CiAgIDx4bXBSaWdodHM6VXNhZ2VUZXJtcz4KICAgIDxyZGY6QWx0PgogICAgIDxyZGY6bGkgeG1sOmxhbmc9IngtZGVmYXVsdCIvPgogICAgPC9yZGY6QWx0PgogICA8L3htcFJpZ2h0czpVc2FnZVRlcm1zPgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIAo8P3hwYWNrZXQgZW5kPSJ3Ij8+yvyVbAAAFqJJREFUeNrsXQ1wHMWV7l3tjyzLklb4D6M/y+bniDkLywQCBsOh4OQghalgCFwdYEiZ4yoXE3KFuaPgHEhV7FTAQAIE+y4EX0GIfSRgDrCxD/NjA0kwJ0sG/4CEtRLYxrJ3Ja2k/Zvp6zfTo+3pHe282V1ppUNd1V7v7nS/9/q9fu97r2dWLkopmWim9jPWF/GOabt4/5eJpRv95jqzIfuxE8Y/1Gayvon1i7Mc/w7r17F+dGIpx4fxuyeWb6g9mYPhEz72yYllHEcbB+H5c4EB+YAQl7Pe5HAeuxbmnhpo/Zz1a1nfPEprPloyZpI3V/2SLHS6lvMxqvKyyPDzbIz/r1h/KkcYkO3YFtbvYv0+1hePsDECrZ2srzQ+UE5sJYlDPyaEKplH0iTxnrGOFE37TrZ0H2T9bNZ/MooOD+jewXooR/06bZ18A5xSCHnZJnjXifG/OQqGNyZbrHkpUXo/IESN2gVOkpx0Dik7/43xKOZbXzH9/pBtgF9iMP/dX1XDh6b27WX/JJhnJ7bdM9BKlNA741HMr5p+H6MHmxeIH3iGudCEw5JHN5HEp6ttYQBlnrJ4wRbinjI/ZUjhd0ls3/dREMJTdyfx1vxT2ldAX+neps3FiOSe6HjKiKfqNuKp/od0NqJBRoLRUBXETMz6XW4S2/s94iqarEUCu+u9dXdZ0032kPiBu/ImY7q8tzK6d1h+j9UvfO//640k0fUfSD4po/t9Rvd2xkN5weUdjMYeZy/fsIM9A6xPMt5EdzcSNX6UGUQsM1xIUOKfez8pmbsy9dmHACH2MCA9mHFskq27UnYJCVzwB/OcrbcRNfS2trEYE/YKQq2OG1aIFAUWEX/DJrPXD+0m0b03MGYGRqC8oNN1l55Dis/bZpa/8ymSaF/DxBvIj4xW8k67kvjnbUj7GqtfmMflPYWx169fa8cnp+uaVEeK5/+OvdYWXN6fPrHh1vseffzp4WDPQtHw1b5WQuMnmDHEbSGAwmRQpb2k9u5FjmX/FFWYxsb330nUEzsJTfSy1RrUvTECith28OxKgiSPv0HinRvN/A4G2TU0P3SGoav2fEhi7Y+ZPWE8zGwglj8ZLegmjvwxXV4H+oV5aKwbrwtOl/Z/Svr33TUm5J1+SiWcxVQNZ/z1JiYHO9lcCvO8VItOmbqaZPZbUieMDerjFMV2rAJjJ6XGJr/4PUkeeZlB7wHUeOddlyfS/luzUoBnRR0Bema6vQcfTdt0I0czRXfw6JskW/2m5qHO6DLHFmV0aSJccHkXzju7hr1cMRzmrzcrpSPlCW0aeH7R+NW+fTp+RoyFy4omC2Gx6zm2aFF9R41QA7GS0R4zHwNBtLw5UGbOM2xeu569KS84cmRJPLSXZKvfXAiDfhOMtm/64oLKW1c1axZ7gQ1QZWX8ATP2YJADIIlNTgJwB2TxBYRkd6BDt2pEPgOwzyNunJ4WPRxQe7qDMeZZWNR2s3xzkp/x4HVp/7fdcLD2RRIfA526vA7oQispZnQ9LlKEODOHsWk5XuAK0t35sZb7YBvQ0+gi5U3Cck4yJ55Y/Wq5QZwymSE/o6TE7yJ+n4t4ipAVNEbb5a0ouLwDg1Ej+ayxMv5GM2Zv5Z6B2hqSp6TW/GGiRzd+m7GUY37D8wP00CZEeP04U0jCU0dmXPIb4j+lgUQ/eZQkDz2AWkyIVO7SGsn4O/SdaMOzTDf++Usk3nwrLkIysdyTzGtVcvb9pJZ1p82JvCpbY2/FfJKNfhWAEFFKpix4mJSffgtR+g+TgV2XE5oME5fLPsKqKjVF9kLJ2xbsOmIYvy3mV/uDPJMlGbuqQR6zQtX+TttxxlgXS3Y9pXVD0IOqFDUWDGnKnJs1A4TmP+0apgw/aixgUX+gQTB8RjcW1jXtkK7vtKvRdEFeWSnZNkfysu6ZXEey0W8ywcaWN2iGD62IzeMNnEdc1IXWr9tXUXB5O48eDfH/nmrl+WebFicSROF2JUm1BZEXliJgT1quEG5FjdPPFpghlQpJNkQbhmUwpWOg6xNCsbbptGSXZkeXpVDUrlxoeCRprbKGtVAJI14cXYg4XrMBovXLeJ40c7FEO8K8epF2RmMHP9wlY0PecG+fAXsGPBZlTgF3txoxC5Ww+ksl42dGTNAbp9ZsSCouCdMiTmmtiWeKTOA0zyAaMBi/k+ReoEu1RJmgk/t8eEEwhPihh1BrnOK5Liv9UgueaT+uOACbXcwFCyUvtEOHOwzPf8RjB3moERIxCetksyHp9VYFtbD+SgF+iHAJkWSbDDjeg06ytdKssOl0mKbi6LKxPoFnfa2c0K2zVG6ycxNJtG/Q6uCochXzgFq9nCIuhUhXOT8/+mUOisZ6dIjo0MkUSl5or7z5joH5T9obv2YMFLHLqNkD87EYCAGeweVNZeVKfwcbp9jSBb25WJ4heiSdLrUdq/Ik2zNF8IRAV6EouqKsqTwFv1ayUpTud0l0z0rtEEkv8cZJPuuAmo16yi3WKkv9cmhKMahA0m+h5I3GYtH2zi4D9sQ8GcucGn6m6F0mGpJi4HZkeDJ570gQFdpUCXcbdDGQCfh1M08mG4N+QmJPV06kNAig4kqk2nhx0/XsI4Pv3czA8YBuCCNQ/FatvC94WyzPkn71jaNkpd9CydsdCoeEt2me31TmVELMkBQE/FDhVgopm9eqJvb1Y2pACF410RY1GkaFU/AosoC0t0NfbYTxeyusxqooDCvTVSPc+B0m95pH+vMPmCGCIQySkWqwxumVHiS8VPSEVdSvDi+d67eQ8n554qRo/JkxP+0Log0pzRiwC6vqhx9uf4XgQXGhGIoMpqQzjsehsgE7GSvTTa0VYuOAIUwXcgXmYNTez9icsRE97QR50woSJ1txOY6Vo4DNrjjXbyHl/fzYl4bxR62qPaYyp9KHK4Mlk1SDECbifbgyp1YGE0NiJDh0X4bjcNofHLr3xHGVKNSKHqtYJNlKXwdqreCwxyfCQ6CrJPVk2abFEoQMximJs/WG085JPhd7JbaHTEPyTjFvWI1nhHNTLCKdEhHOYhxUXAopb8cXRwzj1149w5U5NciDrNbAJN5p89OMX48a1HaH+qYKFYgIVwgyUTbhUM2TIccmpbEa3WR2dPu518dUtthYt688K3mjMUpKG1eTysY7tfeRt5YTJfii/WMEVjwbMA3Js0cyJPVEC1K/4ChqCy6vVObUbuhy20IexEmah21F9+dbUnnUgQ0pCGF7yso2jqgUoIs6neULWyZ44Fg4VW5EnDqKi0NjvSkM65RuXzAFHxAnw54s5FU5XcMQoPmmLWRe0JuVvJqjwK6VxLMGEbW8DHHyLzuZAskLbe+BQ4bxn5Q9f705rOllP+xDNsnufaT32WoWa4q1RwD1e7VxEMJUcenF1cuTYIieFJZMQS2CSsIUyYAV5skwY63oOlkr7VR5WoNzeS0ObNS+TiaL2x56GJtOlFfjWcXDyzIraIqDlybjL5C80N7+y55hYY+5zBntQWE6bk66wcMNICTiqBabSABkEo2hA504+qebf7BIOdGKuwNVSsJSMA2R/FnQxa7VUMlQNCR0hYnRnZqlvBBdp2bHM7XiGblWQ8Y/BuTtOnrsiFjmlI3fXObsbkVVa3JpMYbp3JUNQ8akh1MH1RppZ6s9uDKn9uBMudVYZJlTootdKzn5cyIvRBZvlvJqxlCWXpDIphqnj+1AlzlFD1xIeaUyZygj7NEERCaA2bRogpL+KCFVlz8iGFELx87OF8eEQ4l9EuYXEnQnY2E90g0Jt1YyXafyFvkrsuJZz6ukSk93i/Vzp1aOokK6VZ3lc5iTcH3jlBMPdzSFlFcoc1p6fnOZs4djszzbfiypl6/A+Kuvf4kUz5Dxr4LGoUVlEpZE4lC4xlM+22QI6DInPJQh3eClhDtQa0Ulj+REXigJi/I64RmildsfsOAZ50XFHEXj5XgrvsxZMTbk7YlEjB9hOiJWKdPLnN0cWyHCE1wywIwZjLrIRUiJz0W8HpL2pE0kygyeXQM12+mX/YRUn7uc5cbSXYJahYigb7TyltdJngyPQ01hvFeobGGSv3KL6gUSqolKcSKvFjWmN0g84+SVkz8nPIOxectnW8MPDPY2VeMKIy+0tmCnqcwpGr8Z8vR2oI0BnmiigQYy+4aXNGOO7F5NEh8+klaLhd0+9+6ezAL3BtF5BtRo3cWiAXeicOjQ4ggGrONQgk7+/EK0Uo63oO8ilTeOgvSgmqLYenpMXrQLLW8uPMuOwkl+ROSKWoHkhfbG+3/5QoQ8Yp3fbPw9QfQT+glmSJUX/GjIixfX/g3ULtOum+x1kfinWzKHui9bmE5wv5xQzMKMenwv9/ofkXj7Via0/VhYGEU2hl7cLwlocBOSP3HT9eB/7QE2nU+k29OBHgvyGusH8sb2P4+SV+HymgzJAc9JyVHo0IWixielSFcoeaUy54Ds+U0ASYXwpBLc3ZxwL/6pDabDIhcpSjv4c7ncZGDrCjLgXQnPtGmTe2YsIKVXb3aczafmu53Nxw9B4FwB8cC7Ful9ZgNW2KajyLs5vQEp2YWDNQd3c3pNRtiBfjDDtH6ap4jqzxY6jDZOeB7S70ypFI184AfG+sSxBZL3ZE+P+LBAGuwxlznBGBA3l2nZvK9cUqhxdyNNi4HaXXwJ404+Fwnt30aKFx8e2qV04KRuwLhTAmk+XIP1851mDolq+DB/1B9xVF8hl9+Gkzd9rTzl0s1wmryIClMO8iYSDLPXNmTNM+jX7CiMk2Fc4inaRqHkPdz1xRdi4Scz7Al3oB9a90q7zAht9mOpHlKFxXFXztN/uU4dmQ704OdGJp+11JSE0UH8rRhpXjQcRD/w7ikzJ46uyVW8ZDhy8sbihJSde4tZR8daUDxb6VeNhpH6TXcWhZL3g30fB+Uyp2j8s9PKYIC9KcnYYQ2KKmanGQM8B4sZK3tR39yrhkKt3XinPcEU0RelxH/GNaS8cbkpFGv5DYJnwLAyz8ljLai1gi7CQ2j+r/3dEBwaSXlL6i9LL3PCr7Rlo1/IFxD0tdsTpI1TKHlf3/1eUIY8hvHPsSpj0aRq68mg0lMkn3aGO9CesEjyhMVfX0ko/AZOgqOfPHgDmGeALUq4n4XDudeQ065/RjLevagoB7dhAF/+medK8EFFRklqgg+6vHcSd9WleZU3aSNvzvoNBdFRQ1yrQsm7a8//Nr+wbccRucZvYP421gETzdJ2A8OlibIzyMnPmjMiM9htxQw7ly9cbioZapAJk9TA+Fnpf0xs6q1vkdCudeTE2+tIIhImuTbgs2TOpWTm15eTsnnXpJfUqi8mkaSfRHoHbRPWwHnLiX+WuVqD/pUJNf2BEGiVN7xEwkze7jzJC+yUzVtKZp5zjaW8uehX3zyH0fqViwOFkPeRZ579s/A2KBs/NCi5DP2u+PTb3ibTszE04wef0Ac+1j/fEVj0I62PRoMTyOq7g9ktvBblHJwtBGZbflfBZK0YJXlz0i/cDDcQzlm/oyXv+t+/8Jrg9dOM38D8u/JBzOUv1+KdXkbL3OGJJivPMJ6ay1/GZE2iZIXun9UwvuUtLtchEwK0F1q/YPi33/+g6PWbWe+yMv7/ggiR++JUkKLqi/TDP0zCW2lenCPHu4+N9KLsPXDowLU//Of1LQcPHcx1Lm/tJXAHla28WvIXSK89j6a8W9/Z/X5eImX1hTrkR8hcSP1Khq8VfdJkWb16tfH/bRtffNl3tPuEr9jnKyorLZ1iRwjukX6vueXjuTXVVUO7aepZJNL6B6ImYtodDlZPnMWh5MiSnmlLHiAub/HQ5w1XX/dEoGzKoMvlSpZPKZ3sYS0fC9Le2RV8Z8+HrY9tfO7N2+5dvWt/W3vk17/bvK+++rSkE1owTywRj5eWlEzWNrunmFBfOen7+DUGcV2aJ3FZYOdYknnB2ZeRKeekMOn+ts/az1xy9UanPDiR9951v9q68qdr3wd5n93yahul9Hg8kYjkol9P1QUk8vGrRB3s0Z6lHVa/rM9Yav4DHGULLnxotOSVLnmd9Y/SnLX4Z4lcZzZ8jb0sc0L4H2+8bvbj//avN5kqKKHD5MTOh0noT09bnoUATD71ygfI1EtTuO9od/fxUy9qemKsh/5n1j540U1Lv9MkfjbYtpOE3n+a9LT80fKcDCJizd9vJJNPv0wMy1uZd/rTWJfXSr/qYJicfOth0r1zHVq/YKBzmq56ugAigOG/a4lU5L/JxTbAt9nL+U5mf3XDr6749iWLvpELh1veeHPX1Xfc+T/8LYTHGSO8KIdYf5t1hl3IGU4GfvL6y7fMra2uzYU4hGaejEHtOTqK8p7jVL/Pr1u7+Pq/XXLpONVv17Aw3eoP0vEIUMP7qQhCRzrf2nZL1cwZF2TDJWDBWYuafi18BH81e4HAQ3GeFiTI+2f0YHObIO8iJ7SWfeubiV+s+vHymlkz52TDBNSeL75x+Uv87QHGy/NOeXAoL9DoykW/h17fcu3ptTVN+dAv42X1SMubyejlUqcZpx5s/sgKI2VMds9s2PHcQz/b8N0lTRf5vF6fk4W55xePviJ81M3oD+SrAoUq4R1sdkyr6pIr3mUe8eFFjec6KuFAon3lih/8t1x+y4aHHOTNSr9Prr73se9d+a1FFWVTynLQb17lZXxlP5bS/D2qBZDpwnPnL1m1YnnjvNPn1tRXV9VkSlL2ffJpUAiFQ46RLcyOcVH6O7PhVrbZz79p6VVnZ5IXfiD10OGOYPP+g8GbV923W/r630WvPMblvYxFuyW/vO+ei8aKfseM8XNmbnSKoYV2jC3Mk2ScNCYr4NYreejOpjUzeV8k46gxmb/L84Yxod9cjN+d78Vhwj3HXnaynnC6MKy/Mp4MgckKPP8n0Q9QsknIxpW8XOYX2MtrrPeNd/3m3fMLO7JSSlozJinjBepkkBdq4WfZyBsV5N01zuUtYS8XFlq/Ywr2TLRRafD8BVSroNrUnsd5V7EOFZ1vjkGZgbc1rK9n/XanfFttEveEHU20sYyyuFFDW8H6PYLhU/5Z1s0zsb4TbSyjK+n9nhxylQnPP04b/MDAdpL6e4+bLMK+8V0b95Zr+P+NBretnOSQyep6K2j1gXDddpL6oYM2iZ/h5pD5/oDPu53zB62Rf7dKkKVN8vxUGGfwDu0pYZwRHajAZ4DLbBkhJox/fLQ1XPlzuDcMSQpfIXy3lm+OzXyM8eMETfyzpmGuD0g0t3M6cE0l/36TtDmMOXYMM8cyTr9S8OIr+PXLBL5CAp+NHNfLEaCdY/q1wly38/dGW8+vWyHQD1nMN2H846g1CYqFdp3wXaOQAFPuDQO8i0a2jBv/cNc3SoZdzzE24Qa0VvLuIj/3WMxBOP12vjGe4sa6mY81NmajMHeAv+ZSGVovGf/64S6cMP7xA3syfbeZe0Ox7+CfrxA84A6b67H0sA0MfyH32O0CTDF4aRI2thEVQrlgez5XgM/VNGH8478ZRlwvwCDRuy6TqiIGRt4s4Oj1iOvFxLJdoBPgc+wQINcKwdOvGcZoDfxez737ZiES7eDftwubYZXAc7bNgDlGWTQ0fBZM6UQf+z3A+naaah/w13r+/Rrhu5OsrxDGPiVdm+n6VZwO/L9RoEP558YcbRI/8L5pGN63D0MrwD9bI9CmnK4xlgrzyjSMeVdJfBu80ww8aX3ikGuiZdPauCdfP0b5M4oACzNdNFHnn2j/HzdmvZ3hQ/s/AQYA0JN3gGAK3/0AAAAASUVORK5CYII='); ?>
\ No newline at end of file
diff --git a/includes/kohana/system/views/profiler/stats.php b/includes/kohana/system/views/profiler/stats.php
new file mode 100755
index 0000000..ebfb489
--- /dev/null
+++ b/includes/kohana/system/views/profiler/stats.php
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+ $benchmarks): ?>
+
+
+
+ s
+
+
+ kB
+
+
+ $tokens): ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ s
+
+
+
+
+ kB
+
+
+
+
\ No newline at end of file
diff --git a/includes/kohana/system/views/profiler/style.css b/includes/kohana/system/views/profiler/style.css
new file mode 100644
index 0000000..e6af3a0
--- /dev/null
+++ b/includes/kohana/system/views/profiler/style.css
@@ -0,0 +1,27 @@
+.kohana table.profiler { width: 99%; margin: 0 auto 1em; border-collapse: collapse; }
+.kohana table.profiler th,
+.kohana table.profiler td { padding: 0.2em 0.4em; background: #fff; border: solid 1px #999; border-width: 1px 0; text-align: left; font-weight: normal; font-size: 1em; color: #111; vertical-align: top; text-align: right; }
+.kohana table.profiler th.name { text-align: left; }
+.kohana table.profiler tr.group th { font-size: 1.4em; background: #222; color: #eee; border-color: #222; }
+.kohana table.profiler tr.group td { background: #222; color: #777; border-color: #222; }
+.kohana table.profiler tr.group td.time { padding-bottom: 0; }
+.kohana table.profiler tr.headers th { text-transform: lowercase; font-variant: small-caps; background: #ddd; color: #777; }
+.kohana table.profiler tr.mark th.name { width: 40%; font-size: 1.2em; background: #fff; vertical-align: middle; }
+.kohana table.profiler tr.mark td { padding: 0; }
+.kohana table.profiler tr.mark.final td { padding: 0.2em 0.4em; }
+.kohana table.profiler tr.mark td > div { position: relative; padding: 0.2em 0.4em; }
+.kohana table.profiler tr.mark td div.value { position: relative; z-index: 2; }
+.kohana table.profiler tr.mark td div.graph { position: absolute; top: 0; bottom: 0; right: 0; left: 100%; background: #71bdf0; z-index: 1; }
+.kohana table.profiler tr.mark.memory td div.graph { background: #acd4f0; }
+.kohana table.profiler tr.mark td.current { background: #eddecc; }
+.kohana table.profiler tr.mark td.min { background: #d2f1cb; }
+.kohana table.profiler tr.mark td.max { background: #ead3cb; }
+.kohana table.profiler tr.mark td.average { background: #ddd; }
+.kohana table.profiler tr.mark td.total { background: #d0e3f0; }
+.kohana table.profiler tr.time td { border-bottom: 0; font-weight: bold; }
+.kohana table.profiler tr.memory td { border-top: 0; }
+.kohana table.profiler tr.final th.name { background: #222; color: #fff; }
+.kohana table.profiler abbr { border: 0; color: #777; font-weight: normal; }
+.kohana table.profiler:hover tr.group td { color: #ccc; }
+.kohana table.profiler:hover tr.mark td div.graph { background: #1197f0; }
+.kohana table.profiler:hover tr.mark.memory td div.graph { background: #7cc1f0; }
\ No newline at end of file
diff --git a/kh.php b/kh.php
new file mode 100644
index 0000000..0c88d13
--- /dev/null
+++ b/kh.php
@@ -0,0 +1,109 @@
+= 5.3, it is recommended to disable
+ * deprecated notices. Disable with: E_ALL & ~E_DEPRECATED
+ */
+error_reporting(E_ALL | E_STRICT);
+
+/**
+ * End of standard configuration! Changing any of the code below should only be
+ * attempted by those with a working knowledge of Kohana internals.
+ *
+ * @see http://kohanaframework.org/guide/using.configuration
+ */
+
+// Set the full path to the docroot
+define('DOCROOT', realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR);
+
+// Make the application relative to the docroot
+if ( ! is_dir($application) AND is_dir(DOCROOT.$application))
+{
+ $application = DOCROOT.$application;
+}
+
+// Make the modules relative to the docroot
+if ( ! is_dir($modules) AND is_dir(DOCROOT.$modules))
+{
+ $modules = DOCROOT.$modules;
+}
+
+// Make the system relative to the docroot
+if ( ! is_dir($system) AND is_dir(DOCROOT.$system))
+{
+ $system = DOCROOT.$system;
+}
+
+// Define the absolute paths for configured directories
+define('APPPATH', realpath($application).DIRECTORY_SEPARATOR);
+define('MODPATH', realpath($modules).DIRECTORY_SEPARATOR);
+define('SYSPATH', realpath($system).DIRECTORY_SEPARATOR);
+
+// Clean up the configuration vars
+unset($application, $modules, $system);
+
+if (file_exists('install'.EXT))
+{
+ // Load the installation check
+ return include 'install'.EXT;
+}
+
+// Load the base, low-level functions
+require SYSPATH.'base'.EXT;
+
+// Load the core Kohana class
+require SYSPATH.'classes/kohana/core'.EXT;
+
+if (is_file(APPPATH.'classes/kohana'.EXT))
+{
+ // Application extends the core
+ require APPPATH.'classes/kohana'.EXT;
+}
+else
+{
+ // Load empty core extension
+ require SYSPATH.'classes/kohana'.EXT;
+}
+
+// Bootstrap the application
+require APPPATH.'bootstrap'.EXT;